Part of EPIC #698.
npm init @jahia/module is a pleasant clack wizard, but it is interactive-only: three prompts (name, path, template), no flags, and piped stdin confuses the prompt rendering. Scripting it requires expect. This blocks CI pipelines, documentation-as-scripts, and AI coding agents — all of which scaffold projects unattended (this evaluation had to drive the wizard with an expect script).
Proposal: accept CLI flags that skip the corresponding prompts, e.g.
npm init @jahia/module@latest my-module -- --template hello-world --path ./my-module --yes
--template values: hello-world | template-set | module (mirroring the three presets). --yes accepts defaults for anything unspecified. Print the same outro.
For comparison: npm create astro@latest -- --template minimal --install --no-git --yes scaffolds unattended in one line.
Technical plan (agent-executable)
Written for execution by a Claude (Opus/Sonnet) agent inside the Jahia Cortex harness. Pure Node/JS change — no running Jahia needed.
Harness setup
- Open Cortex;
cortex-sources → javascript-modules @ main.
- Node ≥ 22 required (
.node-version), yarn install at repo root.
Code map
| File |
Role |
javascript-create-module/index.js |
The whole CLI: 3 @clack/prompts (name → path → template preset), then template copy + $MODULE/$NAMESPACE/$VERSION templating. |
javascript-create-module/tests/create-templatesSet-project.test.js |
Existing node --test test — follow its style. |
javascript-create-module/README.md, docs/1-getting-started/1-dev-environment/README.md |
Usage docs to update. |
Design
- Flags via
node:util parseArgs (no new dependency): positional name; --template <hello-world|template-set|module>; --path <dir>; --yes; --help.
- Single source of truth: extract the existing wizard's three template presets into a
TEMPLATES map ({ "hello-world": ["module","template-set","hello-world"], "template-set": ["module","template-set"], "module": ["module"] }) consumed by BOTH the wizard options and the flag values.
- Resolution matrix:
- Every input resolvable from flags/positional (with
--yes filling defaults: path = cwd/<name>, template = hello-world) → zero prompts, same outro as today.
- Partial flags + interactive TTY → prompt only the missing inputs (pass resolved values as
initialValue).
- Missing input + non-TTY (
!process.stdout.isTTY) → print usage to stderr, process.exit(1). Never hang waiting on stdin.
- Validation: reuse the wizard's exact validators (name regex
^[a-z][a-z0-9-]*$, path-must-not-exist) for flag values; identical error messages, exit 1 on violation.
--help: usage text listing flags + the three template values; exit 0.
Implementation steps
- Refactor
index.js: extract TEMPLATES, validateName(value), validatePath(value) from the inline prompt config (behavior-preserving — the wizard path must remain byte-identical in UX).
- Add
parseArgs handling before the wizard; branch per the matrix above.
- Keep the Node ≥22 warning for the wizard; in flag mode, make it a hard error only if
parseArgs/features actually require it (they don't below 22 — keep the warning).
- Update
README.md + getting-started doc with the one-liner:
npm init @jahia/module@latest my-module -- --template hello-world --yes
Verification
New javascript-create-module/tests/non-interactive.test.js (node --test, style of the existing test), covering:
node index.js foo --template hello-world --path <tmpdir>/foo --yes → exit 0; assert package.json has "name": "foo", settings/definitions.cnd exists with namespace foo templated, .env/.vscode dot-renaming happened.
--template template-set and --template module → preset-specific files present/absent (e.g. hello-world components only in preset 1).
- Invalid name (
Foo!) via flag → exit 1, stderr contains the same message as the wizard.
- Existing target path → exit 1.
- Non-TTY with no flags (spawn with
stdio: "pipe") → exit 1 + usage, process terminates (guard with a 5s timeout in the test).
--help → exit 0, usage on stdout.
Regression: run the existing wizard test; manually spot-check the interactive flow still prompts (TTY only — cannot be CI-automated, note it in the PR).
Acceptance criteria
Part of EPIC #698.
npm init @jahia/moduleis a pleasant clack wizard, but it is interactive-only: three prompts (name, path, template), no flags, and piped stdin confuses the prompt rendering. Scripting it requiresexpect. This blocks CI pipelines, documentation-as-scripts, and AI coding agents — all of which scaffold projects unattended (this evaluation had to drive the wizard with an expect script).Proposal: accept CLI flags that skip the corresponding prompts, e.g.
--templatevalues:hello-world|template-set|module(mirroring the three presets).--yesaccepts defaults for anything unspecified. Print the same outro.For comparison:
npm create astro@latest -- --template minimal --install --no-git --yesscaffolds unattended in one line.Technical plan (agent-executable)
Harness setup
cortex-sources→javascript-modules@main..node-version),yarn installat repo root.Code map
javascript-create-module/index.js@clack/prompts(name → path → template preset), then template copy +$MODULE/$NAMESPACE/$VERSIONtemplating.javascript-create-module/tests/create-templatesSet-project.test.jsnode --testtest — follow its style.javascript-create-module/README.md,docs/1-getting-started/1-dev-environment/README.mdDesign
node:utilparseArgs(no new dependency): positionalname;--template <hello-world|template-set|module>;--path <dir>;--yes;--help.TEMPLATESmap ({ "hello-world": ["module","template-set","hello-world"], "template-set": ["module","template-set"], "module": ["module"] }) consumed by BOTH the wizard options and the flag values.--yesfilling defaults: path =cwd/<name>, template =hello-world) → zero prompts, same outro as today.initialValue).!process.stdout.isTTY) → print usage to stderr,process.exit(1). Never hang waiting on stdin.^[a-z][a-z0-9-]*$, path-must-not-exist) for flag values; identical error messages, exit 1 on violation.--help: usage text listing flags + the three template values; exit 0.Implementation steps
index.js: extractTEMPLATES,validateName(value),validatePath(value)from the inline prompt config (behavior-preserving — the wizard path must remain byte-identical in UX).parseArgshandling before the wizard; branch per the matrix above.parseArgs/features actually require it (they don't below 22 — keep the warning).README.md+ getting-started doc with the one-liner:npm init @jahia/module@latest my-module -- --template hello-world --yesVerification
New
javascript-create-module/tests/non-interactive.test.js(node --test, style of the existing test), covering:node index.js foo --template hello-world --path <tmpdir>/foo --yes→ exit 0; assertpackage.jsonhas"name": "foo",settings/definitions.cndexists with namespacefootemplated,.env/.vscodedot-renaming happened.--template template-setand--template module→ preset-specific files present/absent (e.g. hello-world components only in preset 1).Foo!) via flag → exit 1, stderr contains the same message as the wizard.stdio: "pipe") → exit 1 + usage, process terminates (guard with a 5s timeout in the test).--help→ exit 0, usage on stdout.Regression: run the existing wizard test; manually spot-check the interactive flow still prompts (TTY only — cannot be CI-automated, note it in the PR).
Acceptance criteria
node --testsuite).