Skip to content

create-indiekit ignores CLI arguments: --help becomes the project directory, and cancelling scaffolds with empty answers #908

Description

@rmdes

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions