Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/rules/01-standards/2-tracker-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Tracker IDs

- No ticket/run IDs in code, comments, tests or CI.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
5 changes: 3 additions & 2 deletions .github/workflows/cli-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ jobs:
- name: Plugin suite
run: node scripts/check-tests-leave-git-alone.js -- node --test 'scripts/__tests__/**/*.test.js'

# A Windows commit's hooks (#948), less the three cli jobs that need cli/'s dependencies.
# A Windows commit's hooks, less the three cli jobs that need cli/'s dependencies.
- name: Hooks - a commit's hooks pass on Windows
run: |
echo "# lefthook subject" > "$RUNNER_TEMP/msg"
Expand All @@ -530,7 +530,7 @@ jobs:
pnpm exec lefthook run prepare-commit-msg "$RUNNER_TEMP/msg" message --no-auto-install --no-tty >> "$RUNNER_TEMP/hooks.log" 2>&1 || status=1
cat "$RUNNER_TEMP/hooks.log"
if grep -n "syntax error" "$RUNNER_TEMP/hooks.log"; then
echo "::error::lefthook handed sh a broken command line (#948)"
echo "::error::lefthook handed sh a broken command line"
exit 1
fi
exit $status
Expand Down Expand Up @@ -560,6 +560,7 @@ jobs:
grep -E "tool files readable +ok" telemetry-check.out
- run: cd cli && pnpm test:unit
- run: cd cli && pnpm test:integration
- run: cd cli && pnpm test:arch
# Every e2e but the two named below. `telemetry-commit-trailer.e2e.test.ts` is why the
# step matters most: it is the only thing proving the `prepare-commit-msg` hook is
# reachable by the shell Git for Windows ships, which a backslash path is not.
Expand Down
55 changes: 43 additions & 12 deletions cli/tests/architecture/biome-guards-bite.arch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,44 @@
import { spawnSync } from "node:child_process";
import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, relative, sep } from "node:path";
import { join, relative, resolve, sep } from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { CLI_ROOT, read } from "./helpers.js";

const BIOME = join(CLI_ROOT, "node_modules", ".bin", "biome");
// The package's own node launcher: `.bin/biome` is a shell shim, a `.cmd` on Windows.
const BIOME = join(CLI_ROOT, "node_modules", "@biomejs", "biome", "bin", "biome");
const PLUGIN = "biome-plugins/no-process-exit.grit";

/** A file property as the GitHub reporter escapes it, `C:` written `C%3A` on Windows. */
function reportedFile(raw: string): string {
return raw.replace(/%3A/gi, ":").replace(/%2C/gi, ",").replace(/%25/g, "%");
}

/** The diagnostics biome prints for one planted tree, as `<file>:<rule>` lines. */
function biomeFindings(projectDir: string): string[] {
const result = spawnSync(BIOME, ["lint", "--reporter=github", "."], {
function biomeFindings(projectDir: string, launcher = BIOME): string[] {
const result = spawnSync(process.execPath, [launcher, "lint", "--reporter=github", "."], {
cwd: projectDir,
encoding: "utf8",
});
const lines = `${result.stdout}\n${result.stderr}`.split("\n");
return lines
const findings = `${result.stdout}\n${result.stderr}`
.split("\n")
.map((line) => /^\s*::(?:error|warning) title=(\S+),file=(\S+?),/.exec(line))
.filter((match): match is RegExpExecArray => match !== null)
.map(
(match) => `${relative(realpathSync(projectDir), match[2]).split(sep).join("/")}:${match[1]}`
(match) =>
`${relative(
realpathSync(projectDir),
realpathSync(resolve(projectDir, reportedFile(match[2] as string)))
)
.split(sep)
.join("/")}:${match[1]}`
)
.sort();
// biome exits 0 when clean and 1 with diagnostics; anything else never linted.
if (result.status !== 0 && !(result.status === 1 && findings.length > 0)) {
throw new Error(`biome did not run: ${result.error?.message ?? result.stderr}`);
}
return findings;
}

describe("biome carries the exit and default-export rules", () => {
Expand Down Expand Up @@ -110,28 +127,42 @@ describe("the guard itself", () => {

it("flags process.exit under kernel/, contexts/ and runtime/, and leaves the command edge alone", () => {
const findings = biomeFindings(planted);
expect(findings).toContain("src/kernel/exit.ts:plugin");
expect(findings, findings.join(", ")).toContain("src/kernel/exit.ts:plugin");
expect(findings.filter((f) => f.startsWith("src/presentation/"))).toEqual([]);
});

it("flags the process global in a context's domain and application, and not in its infrastructure", () => {
const findings = biomeFindings(planted);

expect(findings).toContain(
expect(findings, findings.join(", ")).toContain(
"src/contexts/framework/domain/env.ts:lint/style/noRestrictedGlobals"
);
expect(findings).toContain(
expect(findings, findings.join(", ")).toContain(
"src/contexts/framework/application/env.ts:lint/style/noRestrictedGlobals"
);
expect(findings.filter((f) => f.startsWith("src/contexts/framework/infrastructure/"))).toEqual(
[]
);
});

it("reads a file the GitHub reporter escaped, as on Windows", () => {
expect(reportedFile("C%3A\\tmp\\a%2Cb%25.ts")).toBe("C:\\tmp\\a,b%.ts");
});

it("fails when biome does not run, rather than reading as no findings", () => {
expect(() => biomeFindings(planted, join(planted, "missing-biome"))).toThrow(
/biome did not run/
);
});

it("flags a default export under src/ and tests/ alike, and a clean module not at all", () => {
const findings = biomeFindings(planted);
expect(findings).toContain("src/kernel/default.ts:lint/style/noDefaultExport");
expect(findings).toContain("tests/kernel/default.ts:lint/style/noDefaultExport");
expect(findings, findings.join(", ")).toContain(
"src/kernel/default.ts:lint/style/noDefaultExport"
);
expect(findings, findings.join(", ")).toContain(
"tests/kernel/default.ts:lint/style/noDefaultExport"
);
expect(findings.filter((f) => f.startsWith("src/kernel/clean.ts"))).toEqual([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const yaml = require("js-yaml");

const root = path.resolve(__dirname, "../..");

// Lefthook on Windows passes a run to `sh -c` unescaped, so a `"` truncates it (#948).
// Lefthook on Windows passes a run to `sh -c` unescaped, so a `"` truncates it.
test("no run in lefthook.yml holds a double quote", () => {
const config = yaml.load(fs.readFileSync(path.join(root, "lefthook.yml"), "utf8"));
const offenders = [];
Expand Down
Loading