diff --git a/packages/create/README.md b/packages/create/README.md index c140b1a..2134aeb 100644 --- a/packages/create/README.md +++ b/packages/create/README.md @@ -47,6 +47,7 @@ pnpx @marko/create ## Options +- `--name`: The name of the new app (also accepted as the first positional argument). - `--dir`: Provide a different directory to setup the project in (default to `pwd`). - `--template`: The name of an example from [marko-js/examples](https://github.com/marko-js/examples/tree/master/examples). - An example name @@ -64,6 +65,19 @@ pnpx @marko/create - ```bash marko-create --installer pnpm ``` +- `--yes` (`-y`): Skip the interactive prompts and accept the defaults. + +## Non-interactive usage (CI & AI agents) + +In a real terminal the prompts behave exactly as before. When there is no human +to answer them — `--yes` is passed, `CI` is set, or an AI agent is detected +(e.g. `CLAUDECODE`/`CURSOR_TRACE_ID`/`AGENT`, even with a pseudo-TTY) — the +prompts are skipped and the name defaults to `my-app` and the template to the +starter app instead of hanging. Failures exit non-zero. + +```bash +npx @marko/create my-app --template basic --yes +``` # API diff --git a/packages/create/src/cli.js b/packages/create/src/cli.js index 77ca874..0b3688a 100644 --- a/packages/create/src/cli.js +++ b/packages/create/src/cli.js @@ -33,6 +33,11 @@ exports.parse = function parse(argv) { description: "Override the package manager used to install dependencies. By default will determine from create command and fallback to npm." }, + "--yes -y": { + type: "boolean", + description: + "Skip interactive prompts and accept defaults (also implied under CI and AI agents)." + }, "--version -v": { type: "boolean", descrption: `print ${details.name} version` @@ -81,11 +86,29 @@ exports.parse = function parse(argv) { return options; }; +const AGENT_ENV = ["CLAUDECODE", "CURSOR_TRACE_ID", "AI_AGENT", "AGENT"]; +const isAgent = () => AGENT_ENV.some(v => process.env[v]); + exports.run = async function run(options = {}) { + const acceptDefaults = options.yes || isAgent() || process.env.CI; + const canPrompt = process.stdin.isTTY && !acceptDefaults; const spinner = ora("Starting...").start(); try { - if (!options.name || !options.template) { + if ((!options.name || !options.template) && !canPrompt) { + if (!acceptDefaults) { + throw new Error( + "An interactive terminal is required to choose a project name and template.\n" + + "Pass --name and --template, or --yes to accept the defaults." + ); + } + if (!options.name) { + options.name = "my-app"; + spinner.info(chalk.yellow(`No project name given; using "my-app".`)); + } + } + + if ((!options.name || !options.template) && canPrompt) { spinner.stop(); const examples = !options.template && getExamples(); const trimHints = choices => @@ -168,6 +191,7 @@ exports.run = async function run(options = {}) { } catch (err) { spinner.fail(err.message + "\n"); console.error(err); + process.exitCode = 1; } finally { clearTimeout(spinner.timeout); } diff --git a/packages/migrate/README.md b/packages/migrate/README.md index 4f0af2e..2a5b960 100644 --- a/packages/migrate/README.md +++ b/packages/migrate/README.md @@ -38,6 +38,18 @@ npx @marko/migrate ./components/my-component.marko - `--dry-run`: Runs the migration in memory only. - `--safe`: Run all safe migrations ignoring any prompts. +## Non-interactive usage (CI & AI agents) + +Some migrations ask for a decision via an interactive prompt. When there is no +human to answer — no TTY on `stdin`, `CI` is set, or an AI agent is detected +(e.g. `CLAUDECODE`/`CURSOR_TRACE_ID`/`AGENT`) — the automatic migrations still +run, but rather than hanging on a prompt the command exits with guidance to +re-run with `--safe` (which applies only the automatic migrations): + +```bash +npx @marko/migrate ./src --safe +``` + # API ## Installation diff --git a/packages/migrate/src/cli.js b/packages/migrate/src/cli.js index f66aad4..e261068 100644 --- a/packages/migrate/src/cli.js +++ b/packages/migrate/src/cli.js @@ -82,7 +82,18 @@ export function parse(argv) { return options; } +const AGENT_ENV = ["CLAUDECODE", "CURSOR_TRACE_ID", "AI_AGENT", "AGENT"]; +const isAgent = () => AGENT_ENV.some(v => process.env[v]); + +const nonInteractivePrompt = () => { + throw new Error( + "This migration needs an interactive prompt, but no interactive terminal is available.\n" + + "Re-run with `--safe` to apply only the automatic migrations, or run in an interactive terminal." + ); +}; + export async function run(options) { + const interactive = process.stdin.isTTY && !process.env.CI && !isAgent(); await markoMigrate({ syntax: "html", maxLen: 80, @@ -91,7 +102,7 @@ export async function run(options) { ignore: ["/node_modules", ".*"], dir: process.cwd(), ...options, - prompt, + prompt: interactive || options.safe ? prompt : nonInteractivePrompt, onWriteFile(file, source) { return fs.writeFile(file, source, "utf-8"); },