…(objectui#8786)
`prompts` returns the answers given so far when a question is cancelled, so
`answers.author` was absent after a cancel at `Plugin description:` or `Author
name:`; `licenseCopyrightHolder` then dereferenced `undefined.trim()` and the
run died with a TypeError after `fs.mkdirpSync` had already created the target
directory, leaving an empty `packages/plugin-NAME/` that made the retry fail on
"Directory already exists".
Separately, `prompts` never settles on a stdin that is not a TTY: the process
ran out of work and exited 0 having written nothing, while the published README
promised such a run takes MIT and still writes the text.
One rule now covers both: an unanswered question takes the default its prompt
offered and the complete ten-file set is still written. The plugin name keeps
its own behaviour as the one answer with no default, and now says so instead of
exiting 0 in silence. Templating moved ahead of `fs.mkdirpSync`, so no failure
inside `buildPluginFiles` can leave a half-created directory behind.
The new test drives the built bin — through a real pty for the cancels, through
a plain pipe for the non-TTY runs — because `index.ts` calls `program.parse()`
at import time and a `buildPluginFiles` test is green on both sides of this
defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Fixes #8786
Cancelling at
Plugin description:orAuthor name:crashed the generator with aTypeErrorand left an emptypackages/plugin-NAME/behind; and the published README's promise that a non-interactive run "takes MIT and still writes the text" was false — such a run wrote nothing at all and exited 0 in silence. Both are now one rule.The two premises this card's direction hangs on — measured first
The dispatching seat ruled direction (i) (make the non-TTY claim true) conditional on two premises, either of which being false meant stopping. Both hold:
packages/create-plugin/package.jsonlists"files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"], soREADME.mdis in the tarball by explicit declaration (and by npm's default inclusion regardless). The false sentence is a published one. ✅**Ruled.**paragraph: "--yes/ non-TTY runs take MIT and write the text". ✅What was measured on
origin/main(4e3a4f0), driven through a real pty and a pipePlugin description:TypeError, exit 1, empty dir leftAuthor name:TypeError, exit 1, empty dir leftLicense:(control)/dev/null/dev/null, no namePlugin name is requiredThe repair
promptsreturns a short answers object. A cancelled question and every question after it are simply absent, soanswers.authorwasundefinedandlicenseCopyrightHolderdereferenced.trim()on it. Both text answers are now merged against the sameinitialtheir prompt offered — one default written down once — andPluginAnswerstypes them optional, so reading one raw no longer compiles.promptsis called, not after.promptsnever settles there: readline reports EOF, neithersubmitnorabortever fires, and the process runs out of work. There is no result to inspect, which is why the check has to come first.fs.mkdirpSync. Nothing is created on disk until every file's contents exists, so no throw insidebuildPluginFilescan leave a half-created directory for the retry to trip over. That makes the "empty directory" half of the card unreachable by construction rather than merely unreached.README.mdgains a section stating the single rule, andcontent/docs/utilities/create-plugin.mdxgains the paragraph its Rule #2 counterpart owes.Tests — the gate this regression walked through
src/index.tscallsprogram.parse()at import time, so every existing test in this package stops atbuildPluginFiles— and abuildPluginFilestest is green on both sides of this defect. The newsrc/__tests__/cli-cancel-and-nontty-8786.test.tstherefore drives the built bin:script(1)for a real pty where a cancel exists to be made, an ordinary closed pipe for the non-TTY runs. It rebuildsdist/unconditionally inbeforeAll(scrubbingVITEST, per objectui#8598) so it can never assert about a stale bundle.At
4ae7e7c6f:pnpm exec vitest run packages/create-plugin/— Test Files 3 passed (3) · Tests 36 passed (36), the new file contributing 7.templates.test.ts's ten-key set andlicenses.test.tsare among them and stay green.pnpm --filter @object-ui/create-plugin type-check— exit 0 (tsc --noEmit && tsc -p tsconfig.test.json, both legs echoed).pnpm --filter @object-ui/create-plugin lint— exit 0;eslint --format jsonreports 7 files judged, 0 errors, 0 warnings — the whole of this package's TypeScript, the new test file included. The repo-widepnpm lintis left to CI: this narrowing excludes nothing, becauseeslint.config.jsdeclares noparserOptions.projectand noprojectService, so no untouched file's verdict can move on this diff.check:control-bytes,check:unreferenced-sources,check:doc-fences,check:esm-specifiers,check-changeset-presence,check-changeset-fixed,check-changeset-no-major,check-doc-links,check-type-check-coverage,tsconfig-test-parity-census— all exit 0. Ratchets that judge the new test file —spawned-build-vitest-env-8598,vitest-invocation-guard,one-authority-per-exported-name-6273— 50 tests passed.Ablation — the new tests are shown to be able to fail, from the committed tree
Each leg mutated
src/index.ts, proved the change landed on disk (anchor counts plus the blob hash), rebuilt, proved it reacheddist/index.js, ran, then restored withgit checkout HEAD --and proved the restore bygit diff HEADbeing empty and the blob matching, with a rebuild proving the mutated form was gone fromdist/too.author: answers.authorTS2322: Type 'string | undefined' is not assignable to type 'string'. A reading in its own right: the repair is compiler-enforced, so the exact regression cannot come back silently.TypeError. Green: theLicense:cancel (control), the answered run, and the name-required run (it exits before the merge).Clean rerun after each restore: 7 passed. ⭐ The
License:cancel is green in both legs, which is what makes it a control rather than a companion that reds whenever anything breaks.Position axis (objectui#8875)
Zero class-(A) citations — no in-tree
file:linereference is moved by this diff.scripts/cross-file-line-citation-census.mjs --jsonfinds exactly two citations resolving into this package, and both addresstemplates.ts/templates.test.ts, neither of which this diff touches; the census's own totals are unchanged. The line addresses in card #8786's own body (an issue body, not a tree file) do go stale as this lands, by construction. Everything else belongs to objectui#8875 / PR #8881 and is deliberately untouched here,CHANGELOG.mdline references included.Changeset
@object-ui/create-pluginminor— the non-TTY default path is published behaviour that grows. ⛔ Nevermajor(39 packages in onefixedgroup). The pending.changeset/8041-create-plugin-licence-prompt.mdneeds no correction: its sentence "A non-interactive run takes MIT and still writes the text" is true as of this PR rather than before it.验收备注
Out of scope, filed separately: objectui#8892.
buildLicenseFile's docblock argues its MIT fallback makes "a manifest claiming a licence with no text beside it" unreachable "by any route, including a future caller that buildsPluginTemplateVarsby hand", and closes "the manifest is written from the same resolved id, so the two still agree". That last sentence is false:buildPackageJsonwritesvars.licenseverbatim and nothing resolves it a second time, so a hand-built{ license: 'NOT-A-LICENCE' }yields a manifest naming it beside MIT text. Internal-only today — the package isbin-only and the CLI is the sole caller, passingresolveLicenseId's total output. This PR does not touch either function, so it does not falsify that docblock; it is not carried here, per the dispatching seat's ruling. objectui#8892 records it with three directions and no verdict.Not reopened: the licence set (four licences, no free text) is the maintainer's call, and objectui#8778 (copyright holder in the four emitted source headers) stays separate — neither is addressed here.
Noted, not filed.
content/docs/utilities/create-plugin.mdxstill enumerates the prompts as "a description and an author" — the licence question objectui#8041 added as the fourth prompt is absent from that sentence, which predates this card and is objectui#8779's documentation debt, not this one's. It sits directly above the paragraph this PR adds, so it is worth a line rather than silence; ⛔ not repaired here, because establishing that no other claim holds that file costs more than the sentence is worth and the dispatch's scope red line is explicit. Successor: the next PR touchingcontent/docs/utilities/create-plugin.mdx, or a docs pass over objectui#8041's landing.Seat session for this implementation:
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH.Clause-② is
yes, judged on the delivered diff: the emitted file set and the prompt behaviour of the scaffolder are its published behaviour, in ruling C's own words, and this diff changes what both do on two of the runs a user can perform. No new exported symbol (the package isbin-only, with noexports), and no new key on a published payload — theyesrests on the behaviour limb.needs:contract-reviewis on both carriers and this stays in draft until aCONTRACT_REVIEW_TIERverdict is on record.Generated by Claude Code
Generated by Claude Code