neon 4.5.2 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/commands/bootstrap.js +25 -121
- package/dist/init/bootstrap.js +1 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -710,6 +710,12 @@ $ neon bootstrap my-app
|
|
|
710
710
|
|
|
711
711
|
# Scaffold a specific template into the current directory (no prompts)
|
|
712
712
|
$ neon bootstrap . --template hono
|
|
713
|
+
|
|
714
|
+
# List templates
|
|
715
|
+
$ neon bootstrap --list-templates
|
|
716
|
+
|
|
717
|
+
# Machine-readable catalog
|
|
718
|
+
$ neon bootstrap --list-templates --output json
|
|
713
719
|
```
|
|
714
720
|
|
|
715
721
|
The target directory must be empty unless you pass `--force` (a lone `.git` is ignored, so a freshly `git init`ed folder is fine). Symlinks and executable bits in the template are preserved.
|
|
@@ -3,8 +3,9 @@ import { isCi } from "../env.js";
|
|
|
3
3
|
import { log } from "../log.js";
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
5
|
import { t as credentialInputs } from "../_chunks/auth_selection-ktL6uoEe.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { writer } from "../writer.js";
|
|
7
|
+
import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
8
|
+
import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
8
9
|
import { existsSync } from "node:fs";
|
|
9
10
|
import { join, relative, resolve } from "node:path";
|
|
10
11
|
import chalk from "chalk";
|
|
@@ -16,6 +17,7 @@ var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
|
16
17
|
describe: () => describe,
|
|
17
18
|
handler: () => handler
|
|
18
19
|
});
|
|
20
|
+
const removedAgent = () => `\`${getCliName()} bootstrap --agent\` was removed. List templates with \`${getCliName()} bootstrap --list-templates --output json\`. Scaffold with \`${getCliName()} bootstrap <directory> --template <id>\` or \`${getCliName()} bootstrap <directory> --default\`.`;
|
|
19
21
|
const command = "bootstrap [directory]";
|
|
20
22
|
const describe = "Scaffold a new project from a Neon starter template";
|
|
21
23
|
const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").positional("directory", {
|
|
@@ -28,7 +30,7 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
28
30
|
},
|
|
29
31
|
"list-templates": {
|
|
30
32
|
alias: ["list", "ls"],
|
|
31
|
-
describe: "List available templates and exit.",
|
|
33
|
+
describe: "List available templates and exit. --output json and --output yaml print a machine-readable catalog.",
|
|
32
34
|
type: "boolean",
|
|
33
35
|
default: false
|
|
34
36
|
},
|
|
@@ -38,9 +40,8 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
38
40
|
default: false
|
|
39
41
|
},
|
|
40
42
|
agent: {
|
|
41
|
-
|
|
42
|
-
type: "boolean"
|
|
43
|
-
default: false
|
|
43
|
+
hidden: true,
|
|
44
|
+
type: "boolean"
|
|
44
45
|
},
|
|
45
46
|
default: {
|
|
46
47
|
alias: "y",
|
|
@@ -63,20 +64,33 @@ const builder = (argv) => argv.usage("$0 bootstrap [directory] [options]").posit
|
|
|
63
64
|
type: "boolean",
|
|
64
65
|
default: true
|
|
65
66
|
}
|
|
66
|
-
}).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap
|
|
67
|
+
}).example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template").example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory").example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting").example("$0 bootstrap --list-templates --output json", "Print the template catalog as JSON").check((argv) => {
|
|
68
|
+
if (argv.agent === true) throw new Error(removedAgent());
|
|
69
|
+
return true;
|
|
70
|
+
}).strict();
|
|
67
71
|
const handler = async (props) => {
|
|
68
72
|
if (props.listTemplates) {
|
|
69
73
|
const templates = await fetchTemplates();
|
|
74
|
+
if (props.output === "json" || props.output === "yaml") {
|
|
75
|
+
writer(props).end(templates.map((t) => ({
|
|
76
|
+
id: t.id,
|
|
77
|
+
title: t.title,
|
|
78
|
+
description: t.description,
|
|
79
|
+
services: t.services ?? []
|
|
80
|
+
})), { fields: [
|
|
81
|
+
"id",
|
|
82
|
+
"title",
|
|
83
|
+
"description",
|
|
84
|
+
"services"
|
|
85
|
+
] });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
70
88
|
for (const t of templates) {
|
|
71
89
|
const services = t.services && t.services.length > 0 ? ` [${t.services.join(" · ")}]` : "";
|
|
72
90
|
process.stdout.write(`${t.id} — ${t.description}${services}\n`);
|
|
73
91
|
}
|
|
74
92
|
return;
|
|
75
93
|
}
|
|
76
|
-
if (props.agent) {
|
|
77
|
-
await runAgentSafely(props);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
94
|
const templates = await resolveTemplateList(props);
|
|
81
95
|
const interactive = !props.default && Boolean(process.stdout.isTTY) && !isCi();
|
|
82
96
|
const template = await resolveSelectedTemplate(props, interactive, templates);
|
|
@@ -163,15 +177,6 @@ const scaffold = async (template, targetDir) => {
|
|
|
163
177
|
log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
|
|
164
178
|
return filesWritten;
|
|
165
179
|
};
|
|
166
|
-
/**
|
|
167
|
-
* After a human scaffold, offer the things you almost always do next: install
|
|
168
|
-
* dependencies, initialize a git repo, and link the directory to a Neon
|
|
169
|
-
* project. In an interactive terminal each is a y/n prompt (skippable up front
|
|
170
|
-
* with --no-install / --no-git / --no-link); `--default` runs install + git
|
|
171
|
-
* without asking; otherwise we just print the manual steps so nothing runs
|
|
172
|
-
* behind the user's back. Agent mode never reaches here — it returns these as
|
|
173
|
-
* structured `next_steps` instead (see {@link runAgent}).
|
|
174
|
-
*/
|
|
175
180
|
const runPostScaffoldSteps = async (props, targetDir, interactive) => {
|
|
176
181
|
const inferred = inferPackageManager(targetDir);
|
|
177
182
|
const defaultPm = resolvePackageManager(targetDir);
|
|
@@ -307,103 +312,6 @@ const printNextSteps = (targetDir, pm, opts) => {
|
|
|
307
312
|
log.info(" See the README to run it.");
|
|
308
313
|
log.info("");
|
|
309
314
|
};
|
|
310
|
-
const runAgentSafely = async (props) => {
|
|
311
|
-
try {
|
|
312
|
-
await runAgent(props);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
emitAgent(toAgentError(err));
|
|
315
|
-
process.exit(1);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
/**
|
|
319
|
-
* The `--agent` flow: resolve what the flags determine and emit one JSON object
|
|
320
|
-
* describing either the next input needed (`needs_template` / `needs_directory`)
|
|
321
|
-
* or the terminal result (`scaffolded`). Unlike interactive mode it never
|
|
322
|
-
* prompts and never runs install/git/link itself — those come back as structured
|
|
323
|
-
* `next_steps` so the agent can confirm with the user and run them (the link
|
|
324
|
-
* step chains into `neon link --agent`).
|
|
325
|
-
*/
|
|
326
|
-
const runAgent = async (props) => {
|
|
327
|
-
if (!props.template) {
|
|
328
|
-
const templates = await fetchTemplates();
|
|
329
|
-
emitAgent({
|
|
330
|
-
status: "needs_template",
|
|
331
|
-
instruction: `Ask the user which template to scaffold, then re-run the next_command_template with the chosen --template value${props.directory ? "" : " and a target directory"}.`,
|
|
332
|
-
options: templates.map((template) => ({
|
|
333
|
-
id: template.id,
|
|
334
|
-
title: template.title,
|
|
335
|
-
description: template.description,
|
|
336
|
-
...template.services ? { services: template.services } : {}
|
|
337
|
-
})),
|
|
338
|
-
next_command_template: `${getCliName()} bootstrap --agent ${props.directory ? shellArg(props.directory) : "<directory>"} --template <template_id>`
|
|
339
|
-
});
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
const templates = await resolveTemplateList(props);
|
|
343
|
-
const template = findTemplate(templates, props.template);
|
|
344
|
-
if (!template) throw new BootstrapInputError(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`, "UNKNOWN_TEMPLATE");
|
|
345
|
-
if (props.directory === void 0) {
|
|
346
|
-
emitAgent({
|
|
347
|
-
status: "needs_directory",
|
|
348
|
-
instruction: "Ask the user which directory to scaffold into (use \".\" for the current directory), then re-run the next_command_template with it.",
|
|
349
|
-
next_command_template: `${getCliName()} bootstrap --agent <directory> --template ${shellArg(template.id)}`
|
|
350
|
-
});
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const targetDir = resolve(process.cwd(), props.directory === "." ? "" : props.directory);
|
|
354
|
-
ensureTargetUsable(targetDir, props.force);
|
|
355
|
-
const filesWritten = await scaffold(template, targetDir);
|
|
356
|
-
const dir = displayDir(targetDir);
|
|
357
|
-
const runIn = isCurrentDir(targetDir) ? "" : `cd ${shellArg(dir)} && `;
|
|
358
|
-
const installPm = resolvePackageManager(targetDir);
|
|
359
|
-
emitAgent({
|
|
360
|
-
status: "scaffolded",
|
|
361
|
-
directory: targetDir,
|
|
362
|
-
template: {
|
|
363
|
-
id: template.id,
|
|
364
|
-
title: template.title
|
|
365
|
-
},
|
|
366
|
-
files_written: filesWritten,
|
|
367
|
-
next_steps: [
|
|
368
|
-
{
|
|
369
|
-
action: "install_dependencies",
|
|
370
|
-
instruction: `Ask the user whether to install dependencies, then run this in the project directory. ${DO_NOT_SUBSTITUTE_HINT}`,
|
|
371
|
-
command: `${runIn}${formatInstallCommand(installPm)}`
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
action: "initialize_git",
|
|
375
|
-
instruction: "Ask the user whether to initialize a git repository in the project directory.",
|
|
376
|
-
command: `${runIn}git init`
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
action: "link_neon_project",
|
|
380
|
-
instruction: "Ask the user whether to link the project to a Neon project now. This runs the link state machine — follow its JSON output for the next step.",
|
|
381
|
-
command: `${runIn}${getCliName()} link --agent`
|
|
382
|
-
}
|
|
383
|
-
],
|
|
384
|
-
message: `Scaffolded "${template.title}" (${filesWritten} files) into ${dir}. Offer the next_steps to the user: install dependencies, initialize git, then link a Neon project.`
|
|
385
|
-
});
|
|
386
|
-
};
|
|
387
|
-
const emitAgent = (response) => {
|
|
388
|
-
process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
389
|
-
};
|
|
390
|
-
const toAgentError = (err) => {
|
|
391
|
-
if (err instanceof BootstrapInputError) return {
|
|
392
|
-
status: "error",
|
|
393
|
-
code: err.agentCode,
|
|
394
|
-
message: err.message
|
|
395
|
-
};
|
|
396
|
-
if (err instanceof Error) return {
|
|
397
|
-
status: "error",
|
|
398
|
-
code: "INTERNAL_ERROR",
|
|
399
|
-
message: err.message
|
|
400
|
-
};
|
|
401
|
-
return {
|
|
402
|
-
status: "error",
|
|
403
|
-
code: "INTERNAL_ERROR",
|
|
404
|
-
message: String(err)
|
|
405
|
-
};
|
|
406
|
-
};
|
|
407
315
|
const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
|
|
408
316
|
/**
|
|
409
317
|
* The path to show the user: the bare relative path for the common
|
|
@@ -415,10 +323,6 @@ const displayDir = (targetDir) => {
|
|
|
415
323
|
if (rel === "") return ".";
|
|
416
324
|
return rel.startsWith("..") ? targetDir : rel;
|
|
417
325
|
};
|
|
418
|
-
const shellArg = (value) => {
|
|
419
|
-
if (/^[A-Za-z0-9._:/-]+$/.test(value)) return value;
|
|
420
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
421
|
-
};
|
|
422
326
|
const onPromptState = (state) => {
|
|
423
327
|
if (state.aborted) {
|
|
424
328
|
process.stdout.write("\x1B[?25h");
|
package/dist/init/bootstrap.js
CHANGED
|
@@ -305,12 +305,7 @@ const downloadTemplate = async (template) => {
|
|
|
305
305
|
if (files.length === 0) throw new Error(`Template subdirectory "${subdir}" was not found in ${owner}/${repo}@${ref}.`);
|
|
306
306
|
return files;
|
|
307
307
|
};
|
|
308
|
-
/**
|
|
309
|
-
* A bad caller-supplied input that an agent (or human) can correct: an unknown
|
|
310
|
-
* template id or a non-empty target directory. Carries an `agentCode` so an
|
|
311
|
-
* agent surface can report a precise error code instead of a generic
|
|
312
|
-
* INTERNAL_ERROR, while a human path just surfaces the clear `message`.
|
|
313
|
-
*/
|
|
308
|
+
/** `agentCode` lets programmatic callers branch without parsing human-readable messages. */
|
|
314
309
|
var BootstrapInputError = class extends Error {
|
|
315
310
|
constructor(message, agentCode) {
|
|
316
311
|
super(message);
|