You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6bc8cc6
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli): os create plugin ships a starter test, so a fresh scaffold's npm test passes (#25236)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu4GrD7D5BC5v5WW4ryVhyCV)_Fixes#25175
Clause-②: no
Metadata-protocol: none
This is the `os create plugin` half of #25175, the half left after
#25196 landed the `os init -t plugin` half (`9c431bad3`). With it, both
plugin scaffolds pass their own `npm test` out of the box.
## What changes
A project made by `os create plugin NAME` declared `"test": "vitest"`
and contained no test file, so its first `npm test` exited 1 with `No
test files found, exiting with code 1`. The `plugin` template in
`packages/cli/src/commands/create.ts` (both placements) now:
- writes **`src/index.test.ts`**, a starter test that runs the checks
the kernel runs on a plugin when it loads one, before any of its code: a
`name`, an `init` function (the loader's `validatePluginStructure`), and
the protocol's `PluginSchema` (`assertPluginContract`), through
`@objectstack/spec/kernel`, which the manifest already declares. Its
header links the `@objectstack/verify` harness ("A runtime plugin" on
`protocol/kernel/plugin-spec`) for tests that need a booted kernel;
- writes **`tsconfig.build.json`**: `extends: './tsconfig.json'`,
`exclude: ['dist', 'node_modules', 'src/**/*.test.ts']`;
- emits `build: 'tsc -p tsconfig.build.json'`, `dev: 'tsc -p
tsconfig.build.json --watch'` and `test: 'vitest run'` (one pass that
exits, as `os init` emits). `typecheck` stays `tsc --noEmit`, on
`tsconfig.json`.
The three public pages whose printed file tree
`create-plugin-docs-parity.test.ts` holds equal to the emitted file set
gain one line per new file: `content/docs/plugins/index.mdx`,
`content/docs/protocol/kernel/index.mdx` and
`content/docs/protocol/kernel/plugin-spec.mdx` (its CLI transcript, in
emission order).
Changeset: `.changeset/25175-create-plugin-scaffold-test.md`
(`@objectstack/cli` patch), with the one-line fix for a project
scaffolded by an earlier release.
## The route, and why it is honest
The card allows a starter test or a script that does not fail on an
empty suite. This takes the starter test. The previous dev's four
measured placements (report on #25175) were re-measured on this branch:
| placement | `npm test` | after `npm run build` | `typecheck` covers
the test |
|:---|:---|:---|:---|
| S1 `src/index.test.ts`, build stays `tsc` | 1 file |
`dist/index.test.js` + `.d.ts` emitted, the package ships its tests, and
vitest collects the compiled copy too | yes |
| **S2 (this PR)** `src/index.test.ts` + `tsconfig.build.json` | 1 file
| `dist/` = `index.js`, `index.d.ts`; still 1 file | yes: a planted type
error fails `typecheck` |
| S3 `test/` outside `rootDir: 'src'` | 1 file | clean | no (TS6059 if
included) |
| S4 `vitest run --passWithNoTests` | exit 0, nothing ran | | |
S2 is the only row that keeps `dist/` clean without hiding the test from
the type checker. The pin below holds each of those properties, and the
ablation shows each one can go red.
The test is not a placeholder: emitted with `init` renamed to `start`,
it fails `expected 'undefined' to be 'function'`; emitted with `version:
'v0.1.0'`, `PluginSchema` refuses it (ablation legs E and F).
## Measured end to end (npm 10.9.4, registry `@objectstack/spec` 18.0.0,
vitest 4.1.12, typescript 5.9.3)
`os create plugin e2e-pkg` from this branch's built CLI, outside the
repository, then `npx -y npm@10 install`:
| step | exit | reading |
|:---|:---:|:---|
| `npm test` | 0 | `Test Files 1 passed (1)`, `Tests 1 passed (1)` |
| `npm run build` | 0 | `dist/` = `index.d.ts`, `index.js` |
| `npm test` after the build | 0 | `Test Files 1 passed (1)` |
| `npm run typecheck` | 0 | |
| `npm test` with `src/index.test.ts` removed | 1 | `No test files
found, exiting with code 1` |
| baseline: `main`'s `os create plugin`, `npm test` | 1 | `No test files
found, exiting with code 1` |
## The pin, and its ablation
`packages/cli/test/scaffold-test-script-passes.test.ts` (`unit` tier)
gains an `os create` block beside the `os init` one, sharing its sandbox
(OS temp dir, no ancestor runner config, one symlink per declared
dependency). Roster derived from `create`'s `templates` (default
placement) whose manifest declares `test`:
- the emitted `test` script exits 0 and the files that ran are exactly
the shipped test files;
- after the emitted `build` script, `dist/` holds `index.js` and no
`*.test.*`, and `test` still runs exactly the shipped files;
- the emitted `typecheck` passes, and fails once a type error is planted
in the starter test;
- control: the same project without its test files fails `test`.
Ablation through `scripts/ablation-replace.mjs` on `create.ts` (imported
by relative source specifier, no `dist/` on the path), each leg restored
with `blob == HEAD` and `git diff HEAD` empty:
| leg | mutation | result |
|:---|:---|:---|
| A | `build: 'tsc'` (S1) | red: `the build compiled the starter test
into dist/: expected [ 'index.test.d.ts', 'index.test.js' ] to deeply
equal []` |
| B | `tsconfig.build.json` without the test exclude | red: same case,
same reading |
| C | `typecheck: 'tsc -p tsconfig.build.json --noEmit'` (test outside
the checked program) | red: `a type error in the starter test passed
typecheck` |
| D | starter test emitted as `src/index.spec-off.ts` | red: 3 cases (no
test file emitted; `vitest run` finds none) |
| E | emitted plugin's `init` renamed to `start` | red: `expected
'undefined' to be 'function'` |
| F | emitted plugin's version `v0.1.0` | red: `PluginSchema` issue list
not empty |
## Verification (head `2aed9ddea`, after merging `origin/main`
`03de83bf`; the two incoming commits touch none of these files)
- New pin + `create-plugin-docs-parity` + `create`: `Test Files 3 passed
(3)`, `Tests 32 passed (32)`.
- `@objectstack/cli` `unit` tier, full (`vitest run --project unit
--maxWorkers=2`): `Test Files 324 passed (324)`, `Tests 4784 passed
(4784)`. The `integration` tier is declared to CI: the diff touches no
spawn entry and no integration-tier file.
- `pnpm --filter @objectstack/cli typecheck`: exit 0,
`check:test-typecheck` OK (no new signature). ESLint on the two changed
TS files: clean.
- Gates: all 97 commands from `dispatch-gates.mjs --commands`, plus
`check:template-version-sync` and `check:scaffold-emission-policy`, exit
0. Four first exited 3 on prerequisites outside this diff
(`check-plugin-teardown-shape --self-test` needs a pinned commit a
shallow clone lacks; `check:skill-examples`,
`check:dual-build-cjs-loads` and `check:i18n-coverage` read `dist/` of
packages outside the CLI closure) and exit 0 after `git fetch` of that
commit and a full `pnpm build`.
## Acceptance notes
- **The `--in-repo` placement** gets the same three changes; its sandbox
run is not pinned, because its `tsconfig.json` extends this monorepo's
root config and means nothing outside it. Its emission is covered by
`create.test.ts` and the docs-parity pin reads the default placement
only, as before.
- **"Next steps"** printed by `os create` still say `pnpm install` /
`pnpm build`; it does not mention `pnpm test`. Unchanged here, to keep
the plugin-spec transcript the only docs edit beyond the listings.
- **Process:** this card ran as a project card thread on the
harness-pinned branch `claude/project-thread-4oxk47`, not
`claude/issue-25175-*`; the claim on #25175 records that.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QkpbEcZUG5GHxLNpzuuR5y)_
Co-authored-by: Claude <noreply@anthropic.com>
fix(cli): `os create plugin` ships a starter test, so a new project's `npm test` passes
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A project made by `os create plugin NAME` declared `"test": "vitest"` and contained no test file, so its first `npm test` exited 1 with `No test files found, exiting with code 1`. The template now writes `src/index.test.ts`, which runs the checks the kernel runs on a plugin when it loads one: a `name`, an `init` function, and the protocol's `PluginSchema`. It boots no kernel; its header links the `@objectstack/verify` harness for tests that need one. The `test` script is now `vitest run`, one pass that exits.
11
+
12
+
The starter test lives in `src/`, which `tsc` compiles, so the template also writes `tsconfig.build.json`: it extends `tsconfig.json` and excludes `src/**/*.test.ts`, and `build` and `dev` now run `tsc -p tsconfig.build.json`. `typecheck` still reads `tsconfig.json`, so a type error in a test still fails it, while `dist/` holds no compiled test and `npm test` after `npm run build` runs each test once.
13
+
14
+
**An existing project made by an earlier `os create plugin`** still has no test file. Add a `*.test.ts` file, for example a copy of the `src/index.test.ts` that a new `os create plugin NAME` writes. If you put it under `src/`, also copy `tsconfig.build.json` and point the `build` and `dev` scripts at it (`tsc -p tsconfig.build.json`), or `tsc` compiles the test into `dist/` and vitest runs it twice.
0 commit comments