From 89b96df50873c4fc7d9395d50f7dfdc3c4eb7847 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 12:44:22 +0000 Subject: [PATCH] fix(cli): os init writes a lint script into all three scaffold templates `packages/create-objectstack`'s blank template declares `lint` and ships a CI workflow that runs `pnpm lint`; the three script maps in `os init` declared `validate` and no `lint`, so the two scaffolders emitted different projects and only one of them could run that workflow. Adds `lint: 'objectstack lint'` after `validate` in all three maps, and a pin that derives the required script set from the workflow the on-ramp template ships, so the next divergence reddens instead of being discovered. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 --- .../init-scaffold-lint-script-parity.md | 20 +++ packages/cli/src/commands/init.ts | 3 + .../test/scaffold-ci-script-parity.test.ts | 153 ++++++++++++++++++ scripts/cross-package-test-inputs.mjs | 9 ++ turbo.json | 1 + 5 files changed, 186 insertions(+) create mode 100644 .changeset/init-scaffold-lint-script-parity.md create mode 100644 packages/cli/test/scaffold-ci-script-parity.test.ts diff --git a/.changeset/init-scaffold-lint-script-parity.md b/.changeset/init-scaffold-lint-script-parity.md new file mode 100644 index 0000000000..04aab17bee --- /dev/null +++ b/.changeset/init-scaffold-lint-script-parity.md @@ -0,0 +1,20 @@ +--- +"@objectstack/cli": patch +--- + +`os init` / `os create` now write a `lint` script into every scaffolded project, matching what `npx create-objectstack` already emits. + +The two scaffolders had diverged. `npx create-objectstack` copies a template that declares `dev`, `start`, `build`, `validate`, `lint` and `typecheck`, and ships a CI workflow that runs `pnpm validate`, `pnpm lint` and `pnpm typecheck`. The three script maps in `os init` each declared `validate` and no `lint`, so a project scaffolded through `os init` that adopted that workflow — the documented next step — failed its first push with `Command "lint" not found`. + +`objectstack lint` is not a second spelling of `objectstack validate`. Both run the shared authoring-rule engine, but only `lint` reaches the hook-body lowering check, so `hook-body/not-lowerable` — a handler that has silently stopped lowering to a metadata-only body, a change of deployment shape from a refactor that looks like tidying — was unreachable from a project scaffolded this way. + +The new entry sits after `validate` in each map, matching the template's order, and its value is `objectstack lint` on both sides. Existing projects are unaffected; add the script by hand to pick the check up: + +```json +"scripts": { + "validate": "objectstack validate", + "lint": "objectstack lint" +} +``` + +A pin now holds the two scaffolders equal on the scripts the shipped CI workflow runs, derived from that workflow rather than transcribed, so the next divergence is a red test instead of a discovery. diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 1284381f9b..504f2858e4 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -547,6 +547,7 @@ export const TEMPLATES: Record `import { defineStack } from '@objectstack/spec'; @@ -633,6 +634,7 @@ export default ${toCamelCase(namespace)}Item; scripts: { build: 'objectstack compile', validate: 'objectstack validate', + lint: 'objectstack lint', test: 'vitest run', typecheck: 'tsc --noEmit', }, @@ -706,6 +708,7 @@ export default ${toCamelCase(namespace)}Item; scripts: { build: 'objectstack compile', validate: 'objectstack validate', + lint: 'objectstack lint', typecheck: 'tsc --noEmit', }, configContent: (name: string, namespace: string) => `import { defineStack } from '@objectstack/spec'; diff --git a/packages/cli/test/scaffold-ci-script-parity.test.ts b/packages/cli/test/scaffold-ci-script-parity.test.ts new file mode 100644 index 0000000000..e5dc96a443 --- /dev/null +++ b/packages/cli/test/scaffold-ci-script-parity.test.ts @@ -0,0 +1,153 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * PIN — every scaffolder emits a project that can run the on-ramp's CI workflow. + * + * ## The defect this exists for (#16350) + * + * Two scaffolders write a new project's `package.json`: `npx create-objectstack` + * copies `packages/create-objectstack/src/templates/blank/`, and `os create` / + * `os init` render one of the `TEMPLATES` maps in `src/commands/init.ts`. #16330 + * added a `lint` script to the template and a `pnpm lint` step to the workflow it + * ships — and did not touch `init.ts`, whose THREE script maps each declared + * `validate` and no `lint`. The two script sets diverged inside a single PR, and + * the divergence went unnoticed because nothing held them equal. + * + * The harm is not hypothetical and not cosmetic. The template's + * `.github/workflows/ci.yml` is the CI a scaffolded project starts with, and the + * docs point an `os init` user at it; a project scaffolded through `init.ts` that + * copies that workflow dies on `Command "lint" not found` on its first push. Nor + * is `lint` a second spelling of `validate`: both call `runAuthoringRules`, but + * `checkHookBodyLowering` is imported by `src/commands/lint.ts` and by nothing + * else (`git grep hook-body-lowering -- packages` returns that one import and the + * rule's own test), so `hook-body/not-lowerable` is reachable from `pnpm lint` + * alone. + * + * ## What is asserted, and why nothing here is transcribed + * + * The required script set is DERIVED from the workflow the on-ramp ships — the + * `pnpm