neonctl 2.28.0 → 2.29.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.
Files changed (135) hide show
  1. package/README.md +2 -2
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +33 -33
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -1,77 +1,76 @@
1
- import { spawn } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { join, relative, resolve } from 'node:path';
4
- import chalk from 'chalk';
5
- import { BootstrapInputError, FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds, } from 'neon-init/bootstrap';
6
- import prompts from 'prompts';
7
- import which from 'which';
8
- import { isCi } from '../env.js';
9
- import { log } from '../log.js';
1
+ import { existsSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import chalk from "chalk";
4
+ import { BootstrapInputError, ensureTargetUsable, FALLBACK_TEMPLATES, fetchTemplates, findTemplate, scaffoldTemplate, templateIds, } from "neon-init/bootstrap";
5
+ import prompts from "prompts";
6
+ import { isCi } from "../env.js";
7
+ import { log } from "../log.js";
8
+ import { detectPackageManager, installedPackageManagers, runCommand, } from "../utils/package_manager.js";
10
9
  // The directory positional is optional: omitting it in an interactive terminal
11
10
  // prompts for one. In a non-interactive context a missing directory is an error.
12
- export const command = 'bootstrap [directory]';
13
- export const describe = 'Scaffold a new project from a Neon starter template';
11
+ export const command = "bootstrap [directory]";
12
+ export const describe = "Scaffold a new project from a Neon starter template";
14
13
  export const builder = (argv) => argv
15
- .usage('$0 bootstrap [directory] [options]')
16
- .positional('directory', {
14
+ .usage("$0 bootstrap [directory] [options]")
15
+ .positional("directory", {
17
16
  describe: 'Directory to scaffold into. Use "." for the current directory. Omit to be prompted.',
18
- type: 'string',
17
+ type: "string",
19
18
  })
20
19
  .options({
21
20
  template: {
22
- describe: 'Template to use (skips the interactive picker). Run with --list-templates to see available templates.',
23
- type: 'string',
21
+ describe: "Template to use (skips the interactive picker). Run with --list-templates to see available templates.",
22
+ type: "string",
24
23
  },
25
- 'list-templates': {
26
- alias: ['list', 'ls'],
27
- describe: 'List available templates and exit.',
28
- type: 'boolean',
24
+ "list-templates": {
25
+ alias: ["list", "ls"],
26
+ describe: "List available templates and exit.",
27
+ type: "boolean",
29
28
  default: false,
30
29
  },
31
30
  force: {
32
- describe: 'Scaffold into the target directory even if it is not empty (colliding files are overwritten).',
33
- type: 'boolean',
31
+ describe: "Scaffold into the target directory even if it is not empty (colliding files are overwritten).",
32
+ type: "boolean",
34
33
  default: false,
35
34
  },
36
35
  agent: {
37
- 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.',
38
- type: 'boolean',
36
+ 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.",
37
+ type: "boolean",
39
38
  default: false,
40
39
  },
41
40
  default: {
42
- alias: 'y',
43
- describe: 'Quick start: scaffold the default template (or --template) and run the usual setup (install dependencies, git init) without prompting. Linking is left to you since it needs a project choice.',
44
- type: 'boolean',
41
+ alias: "y",
42
+ describe: "Quick start: scaffold the default template (or --template) and run the usual setup (install dependencies, git init) without prompting. Linking is left to you since it needs a project choice.",
43
+ type: "boolean",
45
44
  default: false,
46
45
  },
47
46
  install: {
48
- describe: 'Install dependencies after scaffolding. In interactive mode this is offered as a prompt; use --no-install to skip without being asked.',
49
- type: 'boolean',
47
+ describe: "Install dependencies after scaffolding. In interactive mode this is offered as a prompt; use --no-install to skip without being asked.",
48
+ type: "boolean",
50
49
  default: true,
51
50
  },
52
51
  git: {
53
- describe: 'Initialize a git repository after scaffolding. In interactive mode this is offered as a prompt; use --no-git to skip without being asked.',
54
- type: 'boolean',
52
+ describe: "Initialize a git repository after scaffolding. In interactive mode this is offered as a prompt; use --no-git to skip without being asked.",
53
+ type: "boolean",
55
54
  default: true,
56
55
  },
57
56
  link: {
58
- describe: 'Run `neon link` in the scaffolded directory after installing. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.',
59
- type: 'boolean',
57
+ describe: "Run `neon link` in the scaffolded directory after installing. In interactive mode this is offered as a prompt; use --no-link to skip without being asked.",
58
+ type: "boolean",
60
59
  default: true,
61
60
  },
62
61
  })
63
- .example('$0 bootstrap my-app', 'Create ./my-app from an interactively chosen template')
64
- .example('$0 bootstrap . --template hono', 'Scaffold the Hono template into the current directory')
65
- .example('$0 bootstrap my-app --default', 'Quick start: scaffold the default template and run setup without prompting')
66
- .example('$0 bootstrap my-app --template hono --agent', 'Scaffold without prompting and emit the JSON state machine for AI agents')
62
+ .example("$0 bootstrap my-app", "Create ./my-app from an interactively chosen template")
63
+ .example("$0 bootstrap . --template hono", "Scaffold the Hono template into the current directory")
64
+ .example("$0 bootstrap my-app --default", "Quick start: scaffold the default template and run setup without prompting")
65
+ .example("$0 bootstrap my-app --template hono --agent", "Scaffold without prompting and emit the JSON state machine for AI agents")
67
66
  .strict();
68
67
  export const handler = async (props) => {
69
68
  if (props.listTemplates) {
70
69
  const templates = await fetchTemplates();
71
70
  for (const t of templates) {
72
71
  const services = t.services && t.services.length > 0
73
- ? ` [${t.services.join(' · ')}]`
74
- : '';
72
+ ? ` [${t.services.join(" · ")}]`
73
+ : "";
75
74
  process.stdout.write(`${t.id} — ${t.description}${services}\n`);
76
75
  }
77
76
  return;
@@ -112,7 +111,7 @@ const formatTemplateTitle = (template) => {
112
111
  if (!template.services || template.services.length === 0) {
113
112
  return template.title;
114
113
  }
115
- return `${template.title} ${chalk.dim.italic(template.services.join(' · '))}`;
114
+ return `${template.title} ${chalk.dim.italic(template.services.join(" · "))}`;
116
115
  };
117
116
  const resolveSelectedTemplate = async (props, interactive, templates) => {
118
117
  if (props.template) {
@@ -127,7 +126,7 @@ const resolveSelectedTemplate = async (props, interactive, templates) => {
127
126
  if (props.default) {
128
127
  const fallback = templates[0];
129
128
  if (!fallback) {
130
- throw new Error('No templates available to scaffold from.');
129
+ throw new Error("No templates available to scaffold from.");
131
130
  }
132
131
  return fallback;
133
132
  }
@@ -136,9 +135,9 @@ const resolveSelectedTemplate = async (props, interactive, templates) => {
136
135
  }
137
136
  const { id } = await prompts({
138
137
  onState: onPromptState,
139
- type: 'select',
140
- name: 'id',
141
- message: 'Which template would you like to use?',
138
+ type: "select",
139
+ name: "id",
140
+ message: "Which template would you like to use?",
142
141
  choices: templates.map((template) => ({
143
142
  title: formatTemplateTitle(template),
144
143
  value: template.id,
@@ -147,7 +146,7 @@ const resolveSelectedTemplate = async (props, interactive, templates) => {
147
146
  });
148
147
  const template = findTemplate(templates, id);
149
148
  if (!template) {
150
- throw new Error('No template selected.');
149
+ throw new Error("No template selected.");
151
150
  }
152
151
  return template;
153
152
  };
@@ -164,9 +163,9 @@ const resolveTargetDir = async (props, interactive, template) => {
164
163
  }
165
164
  const { value } = await prompts({
166
165
  onState: onPromptState,
167
- type: 'text',
168
- name: 'value',
169
- message: 'Where should we scaffold your project?',
166
+ type: "text",
167
+ name: "value",
168
+ message: "Where should we scaffold your project?",
170
169
  initial: defaultDirName(template),
171
170
  validate: (input) => input && input.trim().length > 0
172
171
  ? true
@@ -174,9 +173,9 @@ const resolveTargetDir = async (props, interactive, template) => {
174
173
  });
175
174
  dir = String(value).trim();
176
175
  }
177
- return resolve(process.cwd(), dir === '.' ? '' : dir);
176
+ return resolve(process.cwd(), dir === "." ? "" : dir);
178
177
  };
179
- const defaultDirName = (template) => template.source.subdir.split('/').pop() || template.id;
178
+ const defaultDirName = (template) => template.source.subdir.split("/").pop() || template.id;
180
179
  /**
181
180
  * Download and materialize the template into `targetDir`. The actual
182
181
  * download/extract/write lives in the shared `neon-init/bootstrap` core
@@ -190,7 +189,7 @@ const scaffold = async (template, targetDir) => {
190
189
  log.warning(message);
191
190
  },
192
191
  });
193
- log.info('Scaffolded %d files into %s.', filesWritten, targetDir);
192
+ log.info("Scaffolded %d files into %s.", filesWritten, targetDir);
194
193
  return filesWritten;
195
194
  };
196
195
  // ----------------------------------------------------------------------------
@@ -208,11 +207,11 @@ const scaffold = async (template, targetDir) => {
208
207
  const runPostScaffoldSteps = async (props, targetDir, interactive) => {
209
208
  const detected = detectPackageManager();
210
209
  if (props.default) {
211
- await runDefaultSteps(props, targetDir, detected ?? 'npm');
210
+ await runDefaultSteps(props, targetDir, detected ?? "npm");
212
211
  return;
213
212
  }
214
213
  if (!interactive) {
215
- printNextSteps(targetDir, detected ?? 'npm', {
214
+ printNextSteps(targetDir, detected ?? "npm", {
216
215
  installed: false,
217
216
  suggestLink: true,
218
217
  });
@@ -221,15 +220,15 @@ const runPostScaffoldSteps = async (props, targetDir, interactive) => {
221
220
  // The package manager used for the install (and shown in the closing hint).
222
221
  // When we couldn't infer it from the invocation we ask, so a globally
223
222
  // installed `neon` doesn't silently force npm on a bun/pnpm user.
224
- let pm = detected ?? 'npm';
223
+ let pm = detected ?? "npm";
225
224
  let installed = false;
226
225
  if (props.install && (await confirm(installPrompt(detected)))) {
227
226
  pm = detected ?? (await selectPackageManager());
228
- installed = await runCommand(pm, ['install'], targetDir);
227
+ installed = await runCommand(pm, ["install"], targetDir);
229
228
  }
230
229
  if (props.git &&
231
230
  !isGitRepo(targetDir) &&
232
- (await confirm('Initialize a git repository?'))) {
231
+ (await confirm("Initialize a git repository?"))) {
233
232
  await initGitRepo(targetDir);
234
233
  }
235
234
  // `neon link` pulls env vars, which loads this project's neon.ts — and that
@@ -240,9 +239,9 @@ const runPostScaffoldSteps = async (props, targetDir, interactive) => {
240
239
  if (!installed && hasNeonConfig(targetDir)) {
241
240
  log.info("Skipping the Neon link step: `neon link` reads this project's neon.ts " +
242
241
  `to pull env vars, which needs its dependencies. Run \`${pm} install\`, ` +
243
- 'then `neon link`.');
242
+ "then `neon link`.");
244
243
  }
245
- else if (await confirm('Link this project to a Neon project now? (runs neon link)')) {
244
+ else if (await confirm("Link this project to a Neon project now? (runs neon link)")) {
246
245
  await runNeonLink(props, targetDir);
247
246
  // link prints its own summary (and pulls env), so end with just the run hint.
248
247
  printNextSteps(targetDir, pm, { installed, suggestLink: false });
@@ -251,7 +250,9 @@ const runPostScaffoldSteps = async (props, targetDir, interactive) => {
251
250
  }
252
251
  printNextSteps(targetDir, pm, { installed, suggestLink: true });
253
252
  };
254
- const installPrompt = (detected) => detected ? `Install dependencies with ${detected}?` : 'Install dependencies?';
253
+ const installPrompt = (detected) => detected
254
+ ? `Install dependencies with ${detected}?`
255
+ : "Install dependencies?";
255
256
  /**
256
257
  * `--default` quick start: run install + git init without prompting, honoring
257
258
  * --no-install / --no-git. Linking is intentionally skipped — it needs an
@@ -259,20 +260,20 @@ const installPrompt = (detected) => detected ? `Install dependencies with ${dete
259
260
  * closing hint instead.
260
261
  */
261
262
  const runDefaultSteps = async (props, targetDir, pm) => {
262
- log.info('Quick start (--default): running setup without prompting.');
263
+ log.info("Quick start (--default): running setup without prompting.");
263
264
  let installed = false;
264
265
  if (props.install) {
265
- installed = await runCommand(pm, ['install'], targetDir);
266
+ installed = await runCommand(pm, ["install"], targetDir);
266
267
  }
267
268
  if (props.git && !isGitRepo(targetDir)) {
268
269
  await initGitRepo(targetDir);
269
270
  }
270
271
  printNextSteps(targetDir, pm, { installed, suggestLink: true });
271
272
  };
272
- const isGitRepo = (dir) => existsSync(join(dir, '.git'));
273
- // Config filenames the runtime loads (mirrors @neondatabase/config). A scaffold
273
+ const isGitRepo = (dir) => existsSync(join(dir, ".git"));
274
+ // Config filenames the runtime loads (mirrors @neon/config). A scaffold
274
275
  // that ships one makes `neon link`'s env pull evaluate it — which needs deps.
275
- const NEON_CONFIG_FILENAMES = ['neon.ts', 'neon.mts', 'neon.js', 'neon.mjs'];
276
+ const NEON_CONFIG_FILENAMES = ["neon.ts", "neon.mts", "neon.js", "neon.mjs"];
276
277
  const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(join(dir, name)));
277
278
  /**
278
279
  * Initialize a git repository in the scaffolded directory. Just `git init` — we
@@ -280,43 +281,18 @@ const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(j
280
281
  * git identity configured and to leave the first commit to the user.
281
282
  */
282
283
  const initGitRepo = async (dir) => {
283
- await runCommand('git', ['init'], dir);
284
+ await runCommand("git", ["init"], dir);
284
285
  };
285
286
  const confirm = async (message) => {
286
287
  const { value } = await prompts({
287
288
  onState: onPromptState,
288
- type: 'confirm',
289
- name: 'value',
289
+ type: "confirm",
290
+ name: "value",
290
291
  message,
291
292
  initial: true,
292
293
  });
293
294
  return value === true;
294
295
  };
295
- // npm first so it's the default/preselected choice; the rest follow in rough
296
- // popularity order.
297
- const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
298
- /**
299
- * The package manager the CLI was invoked through, read from the
300
- * `npm_config_user_agent` npm sets for `npm exec`/`npx`, `pnpm dlx`, `yarn
301
- * dlx`, and `bunx` (so `pnpm dlx neonctl bootstrap` installs with pnpm).
302
- * Returns undefined when there's nothing to infer from — e.g. a
303
- * globally-installed `neon`/`neonctl` — so the caller can ask instead of
304
- * silently assuming npm.
305
- */
306
- const detectPackageManager = () => {
307
- const ua = process.env.npm_config_user_agent ?? '';
308
- if (ua.startsWith('pnpm'))
309
- return 'pnpm';
310
- if (ua.startsWith('yarn'))
311
- return 'yarn';
312
- if (ua.startsWith('bun'))
313
- return 'bun';
314
- if (ua.startsWith('npm'))
315
- return 'npm';
316
- return undefined;
317
- };
318
- /** The package managers actually on PATH, in {@link PACKAGE_MANAGERS} order. */
319
- const installedPackageManagers = () => PACKAGE_MANAGERS.filter((pm) => which.sync(pm, { nothrow: true }) !== null);
320
296
  /**
321
297
  * Ask which package manager to install with when we couldn't infer one from the
322
298
  * invocation. Offers the managers actually installed (npm preselected); with
@@ -326,45 +302,21 @@ const installedPackageManagers = () => PACKAGE_MANAGERS.filter((pm) => which.syn
326
302
  const selectPackageManager = async () => {
327
303
  const installed = installedPackageManagers();
328
304
  if (installed.length <= 1) {
329
- return installed[0] ?? 'npm';
305
+ return installed[0] ?? "npm";
330
306
  }
331
307
  const { pm } = await prompts({
332
308
  onState: onPromptState,
333
- type: 'select',
334
- name: 'pm',
335
- message: 'Which package manager should we use?',
309
+ type: "select",
310
+ name: "pm",
311
+ message: "Which package manager should we use?",
336
312
  choices: installed.map((manager) => ({
337
313
  title: manager,
338
314
  value: manager,
339
315
  })),
340
- initial: Math.max(0, installed.indexOf('npm')),
316
+ initial: Math.max(0, installed.indexOf("npm")),
341
317
  });
342
- return pm ?? 'npm';
318
+ return pm ?? "npm";
343
319
  };
344
- /**
345
- * Run a command inheriting our stdio so the user sees install / link output
346
- * live and can answer any prompts the child raises. Resolves to whether it
347
- * exited cleanly; a non-zero exit is reported but never aborts bootstrap — the
348
- * scaffold already succeeded, so we let the user retry the step by hand.
349
- */
350
- const runCommand = (cmd, args, cwd) => new Promise((resolvePromise) => {
351
- // npm/pnpm/yarn ship as .cmd shims on Windows, which need a shell to run.
352
- const child = spawn(cmd, args, {
353
- cwd,
354
- stdio: 'inherit',
355
- shell: process.platform === 'win32',
356
- });
357
- child.on('error', (err) => {
358
- log.warning('Could not run `%s %s`: %s', cmd, args.join(' '), err instanceof Error ? err.message : String(err));
359
- resolvePromise(false);
360
- });
361
- child.on('close', (code) => {
362
- if (code !== 0) {
363
- log.warning('`%s %s` exited with code %d.', cmd, args.join(' '), code);
364
- }
365
- resolvePromise(code === 0);
366
- });
367
- });
368
320
  /**
369
321
  * Re-invoke this same CLI as `neon link` inside the scaffolded directory, so the
370
322
  * new project's `.neon` context (and pulled `.env`) land in the right place and
@@ -373,16 +325,18 @@ const runCommand = (cmd, args, cwd) => new Promise((resolvePromise) => {
373
325
  * to the target dir, which is where its env pull writes.
374
326
  */
375
327
  const runNeonLink = async (props, targetDir) => {
376
- const args = [process.argv[1], 'link'];
328
+ const args = [process.argv[1], "link"];
377
329
  if (props.apiKey) {
378
- args.push('--api-key', props.apiKey);
330
+ args.push("--api-key", props.apiKey);
379
331
  }
380
- args.push('--api-host', props.apiHost, '--output', props.output);
332
+ args.push("--api-host", props.apiHost, "--output", props.output);
381
333
  await runCommand(process.execPath, args, targetDir);
382
334
  };
383
335
  const printScaffolded = (template, targetDir) => {
384
- log.info('');
385
- log.info('Done. Scaffolded "%s" into %s.', template.title, isCurrentDir(targetDir) ? 'the current directory' : displayDir(targetDir));
336
+ log.info("");
337
+ log.info('Done. Scaffolded "%s" into %s.', template.title, isCurrentDir(targetDir)
338
+ ? "the current directory"
339
+ : displayDir(targetDir));
386
340
  };
387
341
  /**
388
342
  * The closing "Next steps" hint. Skips `cd` for the current directory, omits
@@ -390,19 +344,19 @@ const printScaffolded = (template, targetDir) => {
390
344
  * wasn't already offered/run — so the user never sees a step they just did.
391
345
  */
392
346
  const printNextSteps = (targetDir, pm, opts) => {
393
- log.info('');
394
- log.info('Next steps:');
347
+ log.info("");
348
+ log.info("Next steps:");
395
349
  if (!isCurrentDir(targetDir)) {
396
- log.info(' cd %s', displayDir(targetDir));
350
+ log.info(" cd %s", displayDir(targetDir));
397
351
  }
398
352
  if (!opts.installed) {
399
- log.info(' %s install', pm);
353
+ log.info(" %s install", pm);
400
354
  }
401
355
  if (opts.suggestLink) {
402
- log.info(' neon link');
356
+ log.info(" neon link");
403
357
  }
404
- log.info(' See the README to run it.');
405
- log.info('');
358
+ log.info(" See the README to run it.");
359
+ log.info("");
406
360
  };
407
361
  const runAgentSafely = async (props) => {
408
362
  try {
@@ -425,55 +379,55 @@ const runAgent = async (props) => {
425
379
  if (!props.template) {
426
380
  const templates = await fetchTemplates();
427
381
  emitAgent({
428
- status: 'needs_template',
429
- 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'}.`,
382
+ status: "needs_template",
383
+ 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"}.`,
430
384
  options: templates.map((template) => ({
431
385
  id: template.id,
432
386
  title: template.title,
433
387
  description: template.description,
434
388
  ...(template.services ? { services: template.services } : {}),
435
389
  })),
436
- next_command_template: `neon bootstrap --agent ${props.directory ? shellArg(props.directory) : '<directory>'} --template <template_id>`,
390
+ next_command_template: `neon bootstrap --agent ${props.directory ? shellArg(props.directory) : "<directory>"} --template <template_id>`,
437
391
  });
438
392
  return;
439
393
  }
440
394
  const templates = await resolveTemplateList(props);
441
395
  const template = findTemplate(templates, props.template);
442
396
  if (!template) {
443
- throw new BootstrapInputError(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`, 'UNKNOWN_TEMPLATE');
397
+ throw new BootstrapInputError(`Unknown template "${props.template}". Available templates: ${templateIds(templates)}.`, "UNKNOWN_TEMPLATE");
444
398
  }
445
399
  if (props.directory === undefined) {
446
400
  emitAgent({
447
- status: 'needs_directory',
401
+ status: "needs_directory",
448
402
  instruction: 'Ask the user which directory to scaffold into (use "." for the current directory), then re-run the next_command_template with it.',
449
403
  next_command_template: `neon bootstrap --agent <directory> --template ${shellArg(template.id)}`,
450
404
  });
451
405
  return;
452
406
  }
453
- const targetDir = resolve(process.cwd(), props.directory === '.' ? '' : props.directory);
407
+ const targetDir = resolve(process.cwd(), props.directory === "." ? "" : props.directory);
454
408
  ensureTargetUsable(targetDir, props.force);
455
409
  const filesWritten = await scaffold(template, targetDir);
456
410
  const dir = displayDir(targetDir);
457
- const runIn = isCurrentDir(targetDir) ? '' : `cd ${shellArg(dir)} && `;
411
+ const runIn = isCurrentDir(targetDir) ? "" : `cd ${shellArg(dir)} && `;
458
412
  emitAgent({
459
- status: 'scaffolded',
413
+ status: "scaffolded",
460
414
  directory: targetDir,
461
415
  template: { id: template.id, title: template.title },
462
416
  files_written: filesWritten,
463
417
  next_steps: [
464
418
  {
465
- action: 'install_dependencies',
466
- instruction: 'Ask the user whether to install dependencies, then run this in the project directory.',
419
+ action: "install_dependencies",
420
+ instruction: "Ask the user whether to install dependencies, then run this in the project directory.",
467
421
  command: `${runIn}npm install`,
468
422
  },
469
423
  {
470
- action: 'initialize_git',
471
- instruction: 'Ask the user whether to initialize a git repository in the project directory.',
424
+ action: "initialize_git",
425
+ instruction: "Ask the user whether to initialize a git repository in the project directory.",
472
426
  command: `${runIn}git init`,
473
427
  },
474
428
  {
475
- action: 'link_neon_project',
476
- 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.',
429
+ action: "link_neon_project",
430
+ 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.",
477
431
  command: `${runIn}neon link --agent`,
478
432
  },
479
433
  ],
@@ -485,17 +439,21 @@ const emitAgent = (response) => {
485
439
  };
486
440
  const toAgentError = (err) => {
487
441
  if (err instanceof BootstrapInputError) {
488
- return { status: 'error', code: err.agentCode, message: err.message };
442
+ return { status: "error", code: err.agentCode, message: err.message };
489
443
  }
490
444
  if (err instanceof Error) {
491
- return { status: 'error', code: 'INTERNAL_ERROR', message: err.message };
445
+ return {
446
+ status: "error",
447
+ code: "INTERNAL_ERROR",
448
+ message: err.message,
449
+ };
492
450
  }
493
- return { status: 'error', code: 'INTERNAL_ERROR', message: String(err) };
451
+ return { status: "error", code: "INTERNAL_ERROR", message: String(err) };
494
452
  };
495
453
  // ----------------------------------------------------------------------------
496
454
  // Path display helpers
497
455
  // ----------------------------------------------------------------------------
498
- const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === '';
456
+ const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === "";
499
457
  /**
500
458
  * The path to show the user: the bare relative path for the common
501
459
  * `bootstrap my-app` case, the absolute path when the target sits outside the
@@ -503,10 +461,10 @@ const isCurrentDir = (targetDir) => relative(process.cwd(), targetDir) === '';
503
461
  */
504
462
  const displayDir = (targetDir) => {
505
463
  const rel = relative(process.cwd(), targetDir);
506
- if (rel === '') {
507
- return '.';
464
+ if (rel === "") {
465
+ return ".";
508
466
  }
509
- return rel.startsWith('..') ? targetDir : rel;
467
+ return rel.startsWith("..") ? targetDir : rel;
510
468
  };
511
469
  const shellArg = (value) => {
512
470
  if (/^[A-Za-z0-9._:/-]+$/.test(value)) {
@@ -516,8 +474,8 @@ const shellArg = (value) => {
516
474
  };
517
475
  const onPromptState = (state) => {
518
476
  if (state.aborted) {
519
- process.stdout.write('\x1B[?25h');
520
- process.stdout.write('\n');
477
+ process.stdout.write("\x1B[?25h");
478
+ process.stdout.write("\n");
521
479
  process.exit(1);
522
480
  }
523
481
  };