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 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 { BootstrapInputError, FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
7
- import { DO_NOT_SUBSTITUTE_HINT, formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
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
- describe: "Emit a JSON state-machine response designed for AI agents instead of prompting. The output is a single JSON object with a discriminated `status` field describing the next step.",
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 my-app --template hono --agent", "Scaffold without prompting and emit the JSON state machine for AI agents").strict();
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");
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "4.5.2",
3
+ "version": "4.6.0",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",