Skip to content

Commit cb8cb89

Browse files
committed
feat: non interactive mode for ai
resolves #8
1 parent 64c0d68 commit cb8cb89

3 files changed

Lines changed: 103 additions & 44 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"obuild": "^0.4.32",
3434
"oxfmt": "^0.42.0",
3535
"oxlint": "^1.57.0",
36+
"std-env": "^4.0.0",
3637
"tinyexec": "^1.0.4",
3738
"typescript": "^6.0.2",
3839
"vitest": "^4.1.1"

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.ts

Lines changed: 99 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { defineCommand, runMain } from "citty";
1010
import { consola } from "consola";
1111
import { colors } from "consola/utils";
1212
import { downloadTemplate } from "giget";
13+
import { hasTTY, isAgent } from "std-env";
1314
import { installDependencies, packageManagers, runScriptCommand } from "nypm";
1415

1516
// Based on: https://github.com/unjs/giget/blob/main/src/cli.ts
@@ -109,8 +110,22 @@ const mainCommand = defineCommand({
109110
type: "boolean",
110111
description: "Show verbose debugging info",
111112
},
113+
help: {
114+
type: "boolean",
115+
alias: "h",
116+
description: "Show usage information",
117+
},
112118
},
113119
run: async ({ args }) => {
120+
// Show usage and exit
121+
const hasArgs = args.dir || args.template;
122+
if (args.help || (isAgent && !hasArgs)) {
123+
console.log(getUsage());
124+
process.exit(args.help ? 0 : 1);
125+
}
126+
127+
const interactive = hasTTY && !isAgent;
128+
114129
process.stderr.write(BANNER);
115130

116131
if (args.verbose) {
@@ -119,14 +134,18 @@ const mainCommand = defineCommand({
119134

120135
// Prompt the user where to create the Nitro app
121136
if (!args.dir) {
122-
(args as any) /* readonly */.dir = await consola
123-
.prompt(`Where would you like to create your ${NAME} app?`, {
124-
placeholder: `./${DEFAULT_DIR}`,
125-
type: "text",
126-
default: DEFAULT_DIR,
127-
cancel: "reject",
128-
})
129-
.catch(() => process.exit(1));
137+
if (!interactive) {
138+
(args as any).dir = DEFAULT_DIR;
139+
} else {
140+
(args as any) /* readonly */.dir = await consola
141+
.prompt(`Where would you like to create your ${NAME} app?`, {
142+
placeholder: `./${DEFAULT_DIR}`,
143+
type: "text",
144+
default: DEFAULT_DIR,
145+
cancel: "reject",
146+
})
147+
.catch(() => process.exit(1));
148+
}
130149
}
131150

132151
const cwd = resolve(args.cwd);
@@ -138,6 +157,12 @@ const mainCommand = defineCommand({
138157
// Prompt the user if the template download directory already exists
139158
// when no `--force` flag is provided
140159
let shouldForce = Boolean(args.force);
160+
if (existsSync(templateDownloadPath) && !shouldForce && !interactive) {
161+
consola.error(
162+
`Directory ${colors.cyan(relative(process.cwd(), templateDownloadPath))} already exists. Use --force to override.`,
163+
);
164+
process.exit(1);
165+
}
141166
while (existsSync(templateDownloadPath) && !shouldForce) {
142167
const selectedAction = await consola.prompt(
143168
`The directory ${colors.cyan(relative(process.cwd(), templateDownloadPath))} already exists. What would you like to do?`,
@@ -176,16 +201,20 @@ const mainCommand = defineCommand({
176201

177202
// Prompt the user which template to use
178203
if (!args.template) {
179-
(args as any) /* readonly */.template = await consola
180-
.prompt(`What template would you like to use?`, {
181-
type: "select",
182-
options: TEMPLATES.map((t) => ({
183-
value: t.name,
184-
label: t.description,
185-
})),
186-
cancel: "reject",
187-
})
188-
.catch(() => process.exit(1));
204+
if (!interactive) {
205+
(args as any).template = TEMPLATES[0]!.name;
206+
} else {
207+
(args as any) /* readonly */.template = await consola
208+
.prompt(`What template would you like to use?`, {
209+
type: "select",
210+
options: TEMPLATES.map((t) => ({
211+
value: t.name,
212+
label: t.description,
213+
})),
214+
cancel: "reject",
215+
})
216+
.catch(() => process.exit(1));
217+
}
189218
}
190219

191220
// Download the template
@@ -210,26 +239,28 @@ const mainCommand = defineCommand({
210239
const packageManagerArg = args.packageManager as PackageManagerName;
211240
const selectedPackageManager = pmNames.includes(packageManagerArg)
212241
? packageManagerArg
213-
: await consola.prompt("Which package manager would you like to use?", {
214-
type: "select",
215-
initial: currentPackageManager,
216-
cancel: "undefined",
217-
options: [
218-
{
219-
label: "(none)",
220-
value: "" as PackageManagerName,
221-
hint: "Skip install dependencies step",
222-
},
223-
...pmNames.map(
224-
(pm) =>
225-
({
226-
label: pm,
227-
value: pm,
228-
hint: currentPackageManager === pm ? "current" : undefined,
229-
}) satisfies SelectPromptOptions["options"][number],
230-
),
231-
],
232-
});
242+
: !interactive
243+
? (currentPackageManager || "npm")
244+
: await consola.prompt("Which package manager would you like to use?", {
245+
type: "select",
246+
initial: currentPackageManager,
247+
cancel: "undefined",
248+
options: [
249+
{
250+
label: "(none)",
251+
value: "" as PackageManagerName,
252+
hint: "Skip install dependencies step",
253+
},
254+
...pmNames.map(
255+
(pm) =>
256+
({
257+
label: pm,
258+
value: pm,
259+
hint: currentPackageManager === pm ? "current" : undefined,
260+
}) satisfies SelectPromptOptions["options"][number],
261+
),
262+
],
263+
});
233264

234265
// Install project dependencies
235266
// or skip installation based on the '--no-install' flag
@@ -258,12 +289,16 @@ const mainCommand = defineCommand({
258289
}
259290

260291
if (args.gitInit === undefined) {
261-
(args as any) /* readonly */.gitInit = await consola
262-
.prompt("Initialize git repository?", {
263-
type: "confirm",
264-
cancel: "undefined",
265-
})
266-
.then(Boolean);
292+
if (!interactive) {
293+
(args as any).gitInit = false;
294+
} else {
295+
(args as any) /* readonly */.gitInit = await consola
296+
.prompt("Initialize git repository?", {
297+
type: "confirm",
298+
cancel: "undefined",
299+
})
300+
.then(Boolean);
301+
}
267302
}
268303
if (args.gitInit) {
269304
try {
@@ -297,6 +332,26 @@ runMain(mainCommand);
297332

298333
// ---- Internal utils ----
299334

335+
function getUsage() {
336+
const bin = globalThis.__pkg_name__ || "create-nitro-app";
337+
const templates = TEMPLATES.map((t) => t.name).join(", ");
338+
return `Usage: ${bin} <dir> [options]
339+
340+
Options:
341+
--template, -t <name> Template name (${templates})
342+
--packageManager, -p <name> Package manager (${pmNames.join(", ")})
343+
--force Overwrite existing directory
344+
--forceClean Remove existing directory before cloning
345+
--no-install Skip dependency installation
346+
--gitInit Initialize git repository
347+
--offline Do not attempt to download, use cache
348+
--preferOffline Use cache if exists, otherwise download
349+
--help, -h Show this help message
350+
351+
Example:
352+
${bin} my-app --template vite --packageManager npm --gitInit`;
353+
}
354+
300355
function detectCurrentPackageManager() {
301356
const userAgent = process.env.npm_config_user_agent;
302357
if (!userAgent) {

0 commit comments

Comments
 (0)