Split off from #553, where I spotted this while re-testing the old npm install report.
create-indiekit never looks at process.argv. Everything below follows from that.
1. --help and --version aren't handled
$ npx create-indiekit@latest --help
Welcome to Indiekit! (create-indiekit v1.0.0-beta.29)
If you encounter a problem, visit https://github.com/getindiekit/indiekit/issues to file a new issue.
> Gathering details…
? What is your website’s URL? ›
No usage, no version — straight into the question flow.
(Note for reproducing: npm create indiekit@latest --help won't show this, because npm intercepts --help and prints its own. Use npx create-indiekit@latest --help, npm create indiekit@latest -- --help, or the installed binary.)
2. A stray argument becomes the project directory, unvalidated
base-create takes process.argv[2] verbatim as the directory to create (base-create/index.js:125), so once the questions are answered create-indiekit --help creates a directory called --help:
Creating project directory: --help
Creating file: --help/README.md
Creating file: --help/.gitignore
Command (in "--help"): npm init -y
3. Non-interactive stdin exits 13 with no usable error
$ npx create-indiekit@latest < /dev/null
…
? What is your website’s URL? ›
Warning: Detected unsettled top-level await at …/create-indiekit/bin/create.js:15
await init();
^
$ echo $?
13
prompts never settles at EOF, so the top-level await init() in bin/create.js never resolves and Node exits 13. Any non-interactive run — CI, a piped invocation, a container build — gets this rather than "this command needs a terminal".
4. Cancelling doesn't abort, it scaffolds with empty answers
This is the one I'd fix first. prompts is called without an onCancel handler (index.js:29), and its default on cancel is to resolve with whatever has been collected:
const answers = await prompts([{ type: "text", name: "me", message: "URL?" }]);
// cancelled → RESOLVED: {} (exit 0)
So cancelling at the first question stops nothing. init() carries on into getPackageValues({}), getFiles({}) and create(), and the scaffold runs with undefined answers — in my run it created the directory, wrote README.md and .gitignore, and ran npm init -y and npm add before dying.
Worth noting: with no argument at all, base-create prints "Must provide directory as an argument." and exits 1 — but only after you have answered every question, since create() runs at the end of init(). The answers are lost.
Cause
There is no argv handling in either entry point. bin/create.js checks the Node version and calls await init(); init() goes straight to prompts(setupPrompts).
Reproducing near a checkout of this repo
base-create runs npm init -y and an install as part of scaffolding, and this repo's root manifest declares workspaces: ["helpers/*", "packages/*"]. Scaffolding into a directory that matches one of those globs makes npm adopt it as a workspace member: a node_modules/<name> symlink appears at the repo root and the root package-lock.json gains entries for it.
Anywhere else in the checkout is inert — from a non-matching subdirectory neither npm init -y nor npm add escaped upward for me, and the root lockfile, node_modules and sibling manifests were untouched. So the rule is "not under packages/ or helpers/" rather than "not in the repo". Easiest to just use a scratch directory outside it.
Suggested fix
parseArgs from node:util covers 1 and 2 with no new dependency: --help/--version print and exit, and positionals[0] becomes the directory, validated before anything is written. The indiekit CLI itself uses commander, if consistency across the two binaries is preferred — happy to switch.
3 and 4 are about a line each — pass onCancel to prompts so cancelling exits non-zero, and check process.stdin.isTTY before prompting.
Unrelated but adjacent, while reading these files: package.json declares engines.node: ">=24.17" and index.js writes ">=24" into the generated project, but the guard in bin/create.js only rejects Node < 20. Also docs/get-started.md documents npm create indiekit [directory] with the directory optional, though base-create requires it.
I've opened a PR implementing the above.
Environment: Node 24.18.0, npm 11.16.0, create-indiekit@1.0.0-beta.29 (from the registry, not a local build), Linux.
Split off from #553, where I spotted this while re-testing the old
npm installreport.create-indiekitnever looks atprocess.argv. Everything below follows from that.1.
--helpand--versionaren't handledNo usage, no version — straight into the question flow.
(Note for reproducing:
npm create indiekit@latest --helpwon't show this, because npm intercepts--helpand prints its own. Usenpx create-indiekit@latest --help,npm create indiekit@latest -- --help, or the installed binary.)2. A stray argument becomes the project directory, unvalidated
base-createtakesprocess.argv[2]verbatim as the directory to create (base-create/index.js:125), so once the questions are answeredcreate-indiekit --helpcreates a directory called--help:3. Non-interactive stdin exits 13 with no usable error
promptsnever settles at EOF, so the top-levelawait init()inbin/create.jsnever resolves and Node exits 13. Any non-interactive run — CI, a piped invocation, a container build — gets this rather than "this command needs a terminal".4. Cancelling doesn't abort, it scaffolds with empty answers
This is the one I'd fix first.
promptsis called without anonCancelhandler (index.js:29), and its default on cancel is to resolve with whatever has been collected:So cancelling at the first question stops nothing.
init()carries on intogetPackageValues({}),getFiles({})andcreate(), and the scaffold runs with undefined answers — in my run it created the directory, wroteREADME.mdand.gitignore, and rannpm init -yandnpm addbefore dying.Worth noting: with no argument at all,
base-createprints "Must provide directory as an argument." and exits 1 — but only after you have answered every question, sincecreate()runs at the end ofinit(). The answers are lost.Cause
There is no argv handling in either entry point.
bin/create.jschecks the Node version and callsawait init();init()goes straight toprompts(setupPrompts).Reproducing near a checkout of this repo
base-createrunsnpm init -yand an install as part of scaffolding, and this repo's root manifest declaresworkspaces: ["helpers/*", "packages/*"]. Scaffolding into a directory that matches one of those globs makes npm adopt it as a workspace member: anode_modules/<name>symlink appears at the repo root and the rootpackage-lock.jsongains entries for it.Anywhere else in the checkout is inert — from a non-matching subdirectory neither
npm init -ynornpm addescaped upward for me, and the root lockfile,node_modulesand sibling manifests were untouched. So the rule is "not underpackages/orhelpers/" rather than "not in the repo". Easiest to just use a scratch directory outside it.Suggested fix
parseArgsfromnode:utilcovers 1 and 2 with no new dependency:--help/--versionprint and exit, andpositionals[0]becomes the directory, validated before anything is written. TheindiekitCLI itself usescommander, if consistency across the two binaries is preferred — happy to switch.3 and 4 are about a line each — pass
onCanceltopromptsso cancelling exits non-zero, and checkprocess.stdin.isTTYbefore prompting.Unrelated but adjacent, while reading these files:
package.jsondeclaresengines.node: ">=24.17"andindex.jswrites">=24"into the generated project, but the guard inbin/create.jsonly rejects Node < 20. Alsodocs/get-started.mddocumentsnpm create indiekit [directory]with the directory optional, thoughbase-createrequires it.I've opened a PR implementing the above.
Environment: Node 24.18.0, npm 11.16.0,
create-indiekit@1.0.0-beta.29(from the registry, not a local build), Linux.