diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1f7d15b9..8386d7f8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,7 +11,7 @@ permissions: jobs: check: - name: npm run check:ci + name: npm run check runs-on: ubuntu-latest steps: @@ -30,4 +30,4 @@ jobs: run: npm ci --ignore-scripts - name: Check - run: npm run check:ci + run: npm run check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c5ddf5d..baf0dfe8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: RELEASE_VERSION: ${{ github.ref_name }} - name: Run checks and build - run: npm run check:ci + run: npm run check - name: Generate artifact attestations uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4 diff --git a/biome.jsonc b/biome.jsonc index a597e752..b817b3c1 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -7,28 +7,28 @@ }, "plugins": [ // App-server and generated binding boundaries. - "./scripts/lint/grit/no-app-server-projection-rpcs.grit", - "./scripts/lint/grit/no-app-server-protocol-boundary-imports.grit", - "./scripts/lint/grit/no-generated-app-server-boundary-imports.grit", - "./scripts/lint/grit/no-generated-app-server-import-shapes.grit", - "./scripts/lint/grit/no-lower-level-boundary-imports.grit", + "./scripts/lint/no-app-server-projection-rpcs.grit", + "./scripts/lint/no-app-server-protocol-boundary-imports.grit", + "./scripts/lint/no-generated-app-server-boundary-imports.grit", + "./scripts/lint/no-generated-app-server-import-shapes.grit", + "./scripts/lint/no-lower-level-boundary-imports.grit", // Chat feature architecture. - "./scripts/lint/grit/no-chat-domain-outer-layer-imports.grit", - "./scripts/lint/grit/no-chat-signal-imports.grit", - "./scripts/lint/grit/no-chat-state-escape-hatches.grit", - "./scripts/lint/grit/no-pure-chat-state-side-effects.grit", - "./scripts/lint/grit/no-ui-root-imports.grit", + "./scripts/lint/no-chat-domain-outer-layer-imports.grit", + "./scripts/lint/no-chat-signal-imports.grit", + "./scripts/lint/no-chat-state-escape-hatches.grit", + "./scripts/lint/no-pure-chat-state-side-effects.grit", + "./scripts/lint/no-ui-root-imports.grit", // Project-wide source shape. - "./scripts/lint/grit/no-handwritten-reexports.grit", - "./scripts/lint/grit/no-non-reexport-index.grit", - "./scripts/lint/grit/no-self-referential-initializer-callback.grit", - "./scripts/lint/grit/no-unsafe-iterator-value.grit", - "./scripts/lint/grit/no-uncontrolled-preact-form-state.grit", + "./scripts/lint/no-handwritten-reexports.grit", + "./scripts/lint/no-non-reexport-index.grit", + "./scripts/lint/no-self-referential-initializer-callback.grit", + "./scripts/lint/no-unsafe-iterator-value.grit", + "./scripts/lint/no-uncontrolled-preact-form-state.grit", // CSS source policy. - "./scripts/lint/grit/no-restricted-css-policy.grit" + "./scripts/lint/no-restricted-css-policy.grit" ], "files": { "includes": [ diff --git a/docs/development.md b/docs/development.md index f6c6935f..2e68ad44 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,9 +12,9 @@ npm run check Use this as the normal edit loop. `npm run format` applies Biome formatting and import organization; `npm run check` is the local preflight for type checking, tests, lint, format/assist checks, CSS checks, and the production bundle. -Use focused scripts for tight loops: `npm run typecheck`, `npm run test`, `npm run lint`, or `npm run build`. The `*:ci` variants match CI cache behavior. +Use focused scripts for tight loops: `npm run typecheck`, `npm run test`, `npm run build`, or the targeted `npm run check:*` scripts. CI and release preflight run the same `npm run check` command as local development. -Biome owns formatting, import organization, general JavaScript/TypeScript/JSON/CSS linting, GritQL source-shape plugins, GritQL import-boundary restrictions, and GritQL CSS source policy. Biome warnings fail `npm run lint` and `npm run check`; info diagnostics stay advisory, including accessibility while Obsidian-specific UI semantics are reviewed rule by rule. The CSS usage script still checks dead authored classes. ESLint remains for typed unsafe-any checks, Obsidian plugin policy, and Codex Panel responsibility-boundary rules that need TypeScript type information. +Biome owns formatting, import organization, general JavaScript/TypeScript/JSON/CSS linting, GritQL source-shape plugins, GritQL import-boundary restrictions, and GritQL CSS source policy. Biome warnings fail `npm run check`; info diagnostics stay advisory, including accessibility while Obsidian-specific UI semantics are reviewed rule by rule. The CSS usage script still checks dead authored classes. ESLint remains for typed unsafe-any checks, Obsidian plugin policy, and Codex Panel responsibility-boundary rules that need TypeScript type information. ## Generated and Loaded Files diff --git a/docs/release.md b/docs/release.md index 9ba0cfd7..f382cc06 100644 --- a/docs/release.md +++ b/docs/release.md @@ -20,8 +20,8 @@ jj git push --remote origin --bookmark main git push origin X.Y.Z ``` -`release:prepare` updates the version files and creates a `## Changes` release notes template. `release:preflight` verifies the local Jujutsu/Git state, release metadata, API baselines, lockfile, and the same non-cached `npm run check:ci` validation used by CI after the release commit is on `main`. +`release:prepare` updates the version files and creates a `## Changes` release notes template. `release:preflight` verifies the local Jujutsu/Git state, release metadata, API baselines, lockfile, and the same `npm run check` validation used by CI after the release commit is on `main`. Release notes should normally include only user-facing changes. Internal implementation changes, validation details, and release procedure notes should be omitted when minor; when they are important enough to mention, group them into at most one concise bullet. -The release workflow runs `npm ci`, `npm run release:check`, `npm run check:ci`, attaches the install assets, and generates GitHub artifact attestations for them. The release notes file is required and must contain a single `## Changes` section. If a tag-triggered release fails before creating the GitHub Release, fix the commit, move the local tag with `jj tag set --allow-move -r main X.Y.Z`, then update the remote tag with `git push --force origin X.Y.Z`. +The release workflow runs `npm ci`, `npm run release:check`, `npm run check`, attaches the install assets, and generates GitHub artifact attestations for them. The release notes file is required and must contain a single `## Changes` section. If a tag-triggered release fails before creating the GitHub Release, fix the commit, move the local tag with `jj tag set --allow-move -r main X.Y.Z`, then update the remote tag with `git push --force origin X.Y.Z`. diff --git a/package.json b/package.json index e1ea9405..03af1e4a 100644 --- a/package.json +++ b/package.json @@ -17,20 +17,20 @@ "api:baseline": "node scripts/api-baseline.mjs", "build": "node esbuild.config.mjs", "build:styles": "node scripts/build-styles.mjs", - "check": "node scripts/check.mjs", - "check:ci": "node scripts/check.mjs --ci", + "check": "concurrently --group --pad-prefix 'node:check:*' && node --run build", + "check:biome": "biome check --no-errors-on-unmatched --diagnostic-level=warn --error-on-warnings", + "check:css-usage": "node scripts/lint/check-css-usage.mjs", + "check:eslint": "eslint src --max-warnings=0", + "check:test": "node --run test", + "check:typecheck": "node --run typecheck", + "check:unused": "knip --no-progress", "format": "biome check --write --linter-enabled=false --no-errors-on-unmatched", - "format:check": "biome check --linter-enabled=false --no-errors-on-unmatched", "generate:app-server-types": "node scripts/generate-app-server-types.mjs", - "lint": "node scripts/check.mjs --lint", - "lint:ci": "node scripts/check.mjs --ci --lint", "release:check": "node scripts/release/check.mjs", "release:preflight": "node scripts/release/preflight.mjs", "release:prepare": "node scripts/release/prepare.mjs", - "test": "vitest run --cache --pool threads", - "test:ci": "vitest run --pool threads", - "typecheck": "tsc -p tsconfig.json --noEmit --incremental --tsBuildInfoFile node_modules/.cache/typescript/tsconfig.tsbuildinfo", - "typecheck:ci": "tsc -p tsconfig.json --noEmit" + "test": "vitest run --pool threads", + "typecheck": "tsc -p tsconfig.json --noEmit --incremental --tsBuildInfoFile node_modules/.cache/typescript/tsconfig.tsbuildinfo" }, "devDependencies": { "@biomejs/biome": "^2.5.1", diff --git a/scripts/check-plan.mjs b/scripts/check-plan.mjs deleted file mode 100644 index 56294ec7..00000000 --- a/scripts/check-plan.mjs +++ /dev/null @@ -1,45 +0,0 @@ -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 --max-warnings=0${eslintCacheArgs}`, - }, - { 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 deleted file mode 100644 index 910a7d86..00000000 --- a/scripts/check.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import concurrently from "concurrently"; -import { commandPlan } from "./check-plan.mjs"; - -const args = process.argv.slice(2); -const validArgs = new Set(["--ci", "--lint"]); -const unknownArgs = args.filter((arg) => !validArgs.has(arg)); - -if (unknownArgs.length > 0) { - console.error("Usage: node scripts/check.mjs [--ci] [--lint]"); - process.exit(1); -} - -const ciMode = args.includes("--ci"); -const lintOnly = args.includes("--lint"); -const concurrentOptions = { - group: true, - padPrefix: true, - prefix: "name", -}; - -try { - const plan = commandPlan({ ciMode, lintOnly }); - for (const phase of plan.phases) { - await runConcurrently(phase.commands); - } -} catch { - process.exit(1); -} - -async function runConcurrently(commands) { - await concurrently(commands, concurrentOptions).result; -} diff --git a/scripts/lint/grit/no-app-server-projection-rpcs.grit b/scripts/lint/no-app-server-projection-rpcs.grit similarity index 100% rename from scripts/lint/grit/no-app-server-projection-rpcs.grit rename to scripts/lint/no-app-server-projection-rpcs.grit diff --git a/scripts/lint/grit/no-app-server-protocol-boundary-imports.grit b/scripts/lint/no-app-server-protocol-boundary-imports.grit similarity index 100% rename from scripts/lint/grit/no-app-server-protocol-boundary-imports.grit rename to scripts/lint/no-app-server-protocol-boundary-imports.grit diff --git a/scripts/lint/grit/no-chat-domain-outer-layer-imports.grit b/scripts/lint/no-chat-domain-outer-layer-imports.grit similarity index 100% rename from scripts/lint/grit/no-chat-domain-outer-layer-imports.grit rename to scripts/lint/no-chat-domain-outer-layer-imports.grit diff --git a/scripts/lint/grit/no-chat-signal-imports.grit b/scripts/lint/no-chat-signal-imports.grit similarity index 100% rename from scripts/lint/grit/no-chat-signal-imports.grit rename to scripts/lint/no-chat-signal-imports.grit diff --git a/scripts/lint/grit/no-chat-state-escape-hatches.grit b/scripts/lint/no-chat-state-escape-hatches.grit similarity index 100% rename from scripts/lint/grit/no-chat-state-escape-hatches.grit rename to scripts/lint/no-chat-state-escape-hatches.grit diff --git a/scripts/lint/grit/no-generated-app-server-boundary-imports.grit b/scripts/lint/no-generated-app-server-boundary-imports.grit similarity index 100% rename from scripts/lint/grit/no-generated-app-server-boundary-imports.grit rename to scripts/lint/no-generated-app-server-boundary-imports.grit diff --git a/scripts/lint/grit/no-generated-app-server-import-shapes.grit b/scripts/lint/no-generated-app-server-import-shapes.grit similarity index 100% rename from scripts/lint/grit/no-generated-app-server-import-shapes.grit rename to scripts/lint/no-generated-app-server-import-shapes.grit diff --git a/scripts/lint/grit/no-handwritten-reexports.grit b/scripts/lint/no-handwritten-reexports.grit similarity index 100% rename from scripts/lint/grit/no-handwritten-reexports.grit rename to scripts/lint/no-handwritten-reexports.grit diff --git a/scripts/lint/grit/no-lower-level-boundary-imports.grit b/scripts/lint/no-lower-level-boundary-imports.grit similarity index 100% rename from scripts/lint/grit/no-lower-level-boundary-imports.grit rename to scripts/lint/no-lower-level-boundary-imports.grit diff --git a/scripts/lint/grit/no-non-reexport-index.grit b/scripts/lint/no-non-reexport-index.grit similarity index 100% rename from scripts/lint/grit/no-non-reexport-index.grit rename to scripts/lint/no-non-reexport-index.grit diff --git a/scripts/lint/grit/no-pure-chat-state-side-effects.grit b/scripts/lint/no-pure-chat-state-side-effects.grit similarity index 100% rename from scripts/lint/grit/no-pure-chat-state-side-effects.grit rename to scripts/lint/no-pure-chat-state-side-effects.grit diff --git a/scripts/lint/grit/no-restricted-css-policy.grit b/scripts/lint/no-restricted-css-policy.grit similarity index 100% rename from scripts/lint/grit/no-restricted-css-policy.grit rename to scripts/lint/no-restricted-css-policy.grit diff --git a/scripts/lint/grit/no-self-referential-initializer-callback.grit b/scripts/lint/no-self-referential-initializer-callback.grit similarity index 100% rename from scripts/lint/grit/no-self-referential-initializer-callback.grit rename to scripts/lint/no-self-referential-initializer-callback.grit diff --git a/scripts/lint/grit/no-ui-root-imports.grit b/scripts/lint/no-ui-root-imports.grit similarity index 100% rename from scripts/lint/grit/no-ui-root-imports.grit rename to scripts/lint/no-ui-root-imports.grit diff --git a/scripts/lint/grit/no-uncontrolled-preact-form-state.grit b/scripts/lint/no-uncontrolled-preact-form-state.grit similarity index 100% rename from scripts/lint/grit/no-uncontrolled-preact-form-state.grit rename to scripts/lint/no-uncontrolled-preact-form-state.grit diff --git a/scripts/lint/grit/no-unsafe-iterator-value.grit b/scripts/lint/no-unsafe-iterator-value.grit similarity index 100% rename from scripts/lint/grit/no-unsafe-iterator-value.grit rename to scripts/lint/no-unsafe-iterator-value.grit diff --git a/scripts/release/preflight.mjs b/scripts/release/preflight.mjs index d614f456..cfd6d8be 100644 --- a/scripts/release/preflight.mjs +++ b/scripts/release/preflight.mjs @@ -84,6 +84,6 @@ if (maybeRun("jj", ["root"])) { run("npm", ["run", "release:check"]); run("npm", ["run", "api:baseline"]); run("npm", ["ci", "--dry-run"]); -run("npm", ["run", "check:ci"]); +run("npm", ["run", "check"]); console.log(`release preflight passed for ${packageVersion}`); diff --git a/tests/scripts/check-plan.test.mjs b/tests/scripts/check-plan.test.mjs deleted file mode 100644 index 6b81cbac..00000000 --- a/tests/scripts/check-plan.test.mjs +++ /dev/null @@ -1,47 +0,0 @@ -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 --max-warnings=0 --cache --cache-strategy content --cache-location node_modules/.cache/eslint/.eslintcache", - }, - { 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]).toEqual({ - name: "lint", - commands: expect.arrayContaining([ - { - 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 --max-warnings=0", - }); - }); -}); diff --git a/tests/scripts/grit-policy.test.mjs b/tests/scripts/grit-policy.test.mjs index d8b24f14..8cef4062 100644 --- a/tests/scripts/grit-policy.test.mjs +++ b/tests/scripts/grit-policy.test.mjs @@ -646,7 +646,7 @@ async function tempBiomeWorkspace(plugins) { JSON.stringify({ $schema: "https://biomejs.dev/schemas/2.5.1/schema.json", vcs: { enabled: false }, - plugins: plugins.map((plugin) => path.join(repoRoot, "scripts", "lint", "grit", plugin)), + plugins: plugins.map((plugin) => path.join(repoRoot, "scripts", "lint", plugin)), css: { linter: { enabled: true } }, }), );