diff --git a/biome.jsonc b/biome.jsonc index ef1b2bb0..d38b6d60 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -14,7 +14,7 @@ "*.mjs", "scripts/**/*.mjs", "src/**/*.{css,ts,tsx}", - "tests/**/*.{ts,tsx}", + "tests/**/*.{mjs,ts,tsx}", "!src/generated" ] }, diff --git a/eslint.config.mjs b/eslint.config.mjs index 2ec67e42..100a77b1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import tseslint from "typescript-eslint"; import codexPanelEslintPlugin from "./scripts/lint/eslint-plugin-codex-panel.mjs"; const typeScriptFiles = ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}"]; -const nodeJavaScriptFiles = ["*.mjs", "scripts/**/*.mjs"]; +const nodeJavaScriptFiles = ["*.mjs", "scripts/**/*.mjs", "tests/**/*.mjs"]; const typeScriptConfigFiles = ["*.config.ts"]; const lintedTypeScriptFiles = [...typeScriptFiles, ...typeScriptConfigFiles]; const unsafeAnyTypeScriptRules = { diff --git a/scripts/check-plan.mjs b/scripts/check-plan.mjs new file mode 100644 index 00000000..3abcff00 --- /dev/null +++ b/scripts/check-plan.mjs @@ -0,0 +1,46 @@ +export const biomeLintCommand = "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings"; +export const biomeCheckCommand = "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings"; + +export function commandPlan({ ciMode, lintOnly }) { + if (lintOnly) { + return { + phases: [{ name: "lint", commands: lintCommands({ ciMode }) }], + }; + } + + return { + phases: [ + { name: "check", commands: checkCommands({ ciMode }) }, + { name: "build", commands: [{ name: "build", command: "node esbuild.config.mjs" }] }, + ], + }; +} + +function checkCommands({ ciMode }) { + const suffix = ciMode ? ":ci" : ""; + + return [ + { name: "typecheck", command: `npm run --silent typecheck${suffix}` }, + { name: "test", command: `npm run --silent test${suffix}` }, + { name: "lint:biome", command: biomeCheckCommand }, + ...nonBiomeLintCommands({ ciMode, namePrefix: "lint:" }), + ]; +} + +function lintCommands({ ciMode, namePrefix = "" }) { + return [{ name: `${namePrefix}biome`, command: biomeLintCommand }, ...nonBiomeLintCommands({ ciMode, namePrefix })]; +} + +function nonBiomeLintCommands({ ciMode, namePrefix = "" }) { + const eslintCacheArgs = ciMode ? "" : " --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache"; + + return [ + { + name: `${namePrefix}eslint`, + command: `eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0${eslintCacheArgs}`, + }, + { name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' }, + { name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs" }, + { name: `${namePrefix}unused`, command: "knip --no-progress" }, + ]; +} diff --git a/scripts/check.mjs b/scripts/check.mjs index c34dbfc0..910a7d86 100644 --- a/scripts/check.mjs +++ b/scripts/check.mjs @@ -1,4 +1,5 @@ import concurrently from "concurrently"; +import { commandPlan } from "./check-plan.mjs"; const args = process.argv.slice(2); const validArgs = new Set(["--ci", "--lint"]); @@ -16,15 +17,11 @@ const concurrentOptions = { padPrefix: true, prefix: "name", }; -const biomeLintCommand = "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings"; -const biomeCheckCommand = "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings"; try { - if (lintOnly) { - await runConcurrently(lintCommands({ ciMode })); - } else { - await runConcurrently(checkCommands({ ciMode })); - await runConcurrently([{ name: "build", command: "node esbuild.config.mjs" }]); + const plan = commandPlan({ ciMode, lintOnly }); + for (const phase of plan.phases) { + await runConcurrently(phase.commands); } } catch { process.exit(1); @@ -33,32 +30,3 @@ try { async function runConcurrently(commands) { await concurrently(commands, concurrentOptions).result; } - -function checkCommands({ ciMode }) { - const suffix = ciMode ? ":ci" : ""; - - return [ - { name: "typecheck", command: `npm run --silent typecheck${suffix}` }, - { name: "test", command: `npm run --silent test${suffix}` }, - { name: "lint:biome", command: biomeCheckCommand }, - ...nonBiomeLintCommands({ ciMode, namePrefix: "lint:" }), - ]; -} - -function lintCommands({ ciMode, namePrefix = "" }) { - return [{ name: `${namePrefix}biome`, command: biomeLintCommand }, ...nonBiomeLintCommands({ ciMode, namePrefix })]; -} - -function nonBiomeLintCommands({ ciMode, namePrefix = "" }) { - const eslintCacheArgs = ciMode ? "" : " --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache"; - - return [ - { - name: `${namePrefix}eslint`, - command: `eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0${eslintCacheArgs}`, - }, - { name: `${namePrefix}css`, command: 'stylelint "src/**/*.css" --max-warnings=0' }, - { name: `${namePrefix}css-usage`, command: "node scripts/lint/check-css-usage.mjs" }, - { name: `${namePrefix}unused`, command: "knip --no-progress" }, - ]; -} diff --git a/tests/scripts/check-plan.test.mjs b/tests/scripts/check-plan.test.mjs new file mode 100644 index 00000000..e760fa7c --- /dev/null +++ b/tests/scripts/check-plan.test.mjs @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { commandPlan } from "../../scripts/check-plan.mjs"; + +describe("check command plan", () => { + it("runs the local preflight checks before the build", () => { + expect(commandPlan({ ciMode: false, lintOnly: false }).phases).toEqual([ + { + name: "check", + commands: [ + { name: "typecheck", command: "npm run --silent typecheck" }, + { name: "test", command: "npm run --silent test" }, + { name: "lint:biome", command: "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings" }, + { + name: "lint:eslint", + command: + 'eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0 --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache', + }, + { name: "lint:css", command: 'stylelint "src/**/*.css" --max-warnings=0' }, + { name: "lint:css-usage", command: "node scripts/lint/check-css-usage.mjs" }, + { name: "lint:unused", command: "knip --no-progress" }, + ], + }, + { + name: "build", + commands: [{ name: "build", command: "node esbuild.config.mjs" }], + }, + ]); + }); + + it("keeps lint-only mode on Biome lint", () => { + expect(commandPlan({ ciMode: false, lintOnly: true }).phases[0]?.commands).toContainEqual({ + name: "biome", + command: "biome lint --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings", + }); + }); + + it("omits cache-only local flags in CI mode", () => { + expect(commandPlan({ ciMode: true, lintOnly: false }).phases[0]?.commands).toContainEqual({ + name: "lint:eslint", + command: 'eslint src tests scripts "*.config.ts" "*.config.mjs" --max-warnings=0', + }); + }); +}); diff --git a/tests/scripts/development-scripts.test.ts b/tests/scripts/development-scripts.test.ts index 6153b71f..c3b86729 100644 --- a/tests/scripts/development-scripts.test.ts +++ b/tests/scripts/development-scripts.test.ts @@ -116,28 +116,6 @@ describe("development scripts", () => { expect(report.failures).toEqual([]); }); - it("runs Biome lint with warning-level diagnostics", async () => { - const cwd = await tempWorkspace(); - const binDir = path.join(cwd, "bin"); - await mkdir(binDir, { recursive: true }); - await mkdir(path.join(cwd, "scripts", "lint"), { recursive: true }); - await writeFile(path.join(cwd, "scripts", "lint", "check-css-usage.mjs"), ""); - - await writeCaptureBin(path.join(binDir, "biome"), "biome-args.txt"); - await writeExitZeroBin(path.join(binDir, "eslint")); - await writeExitZeroBin(path.join(binDir, "stylelint")); - await writeExitZeroBin(path.join(binDir, "knip")); - - const result = runNodeScript("scripts/check.mjs", ["--lint"], cwd, { - PATH: `${binDir}${path.delimiter}${process.env["PATH"] ?? ""}`, - }); - - expect(result.status).toBe(0); - await expect(readFile(path.join(cwd, "biome-args.txt"), "utf8")).resolves.toBe( - "lint\n--no-errors-on-unmatched\n--diagnostic-level=warn\n--error-on-warnings\n", - ); - }); - it("keeps CSS usage checks quiet when every class is used", async () => { const cwd = await cssUsageFixture({ "src/styles/10-component.css": ".codex-panel__used { display: block; }\n", @@ -272,20 +250,3 @@ async function writeJson(file: string, value: unknown): Promise { async function readJson(file: string): Promise { return JSON.parse(await readFile(file, "utf8")); } - -async function writeCaptureBin(file: string, outputFile: string): Promise { - await writeFile( - file, - [ - "#!/usr/bin/env node", - "const { appendFileSync } = require('node:fs');", - `appendFileSync(${JSON.stringify(outputFile)}, \`\${process.argv.slice(2).join("\\n")}\\n\`);`, - ].join("\n"), - ); - await chmod(file, 0o755); -} - -async function writeExitZeroBin(file: string): Promise { - await writeFile(file, "#!/usr/bin/env node\n"); - await chmod(file, 0o755); -}