neon 7.0.2 → 7.0.4

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.
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { describeOperation, getEndpoints, loadSpec } from "../utils/openapi.js";
3
2
  import { writer } from "../writer.js";
3
+ import { describeOperation, getEndpoints, loadSpec } from "../utils/openapi.js";
4
4
  import { readFileSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
6
  import YAML from "yaml";
@@ -4,13 +4,13 @@ import { log } from "../log.js";
4
4
  import { recordCommandSuccessExtras, recordScaffoldedTemplate } from "../analytics.js";
5
5
  import { getCliName } from "../utils/cli_name.js";
6
6
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
7
+ import { coerceAgentFlag } from "../utils/agent_flag.js";
8
+ import { assertNamedAgentTooling, chooseYesAgentTooling, initPluginAgents, initSkillsMcpAgents, postScaffoldActions, projectContextFile, resolveNamedAgents } from "../init/plan.js";
9
+ import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
7
10
  import { writer } from "../writer.js";
8
11
  import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
9
12
  import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
10
- import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
11
13
  import { runAuthenticatedLink } from "../init/link.js";
12
- import { coerceAgentFlag } from "../utils/agent_flag.js";
13
- import { assertNamedAgentTooling, chooseYesAgentTooling, initPluginAgents, initSkillsMcpAgents, postScaffoldActions, projectContextFile, resolveNamedAgents } from "../init/plan.js";
14
14
  import { formatTemplateTitle } from "../init/template_title.js";
15
15
  import { pickAgentSetupInteractively, pickInitLinkInteractively } from "../init/wizard.js";
16
16
  import { runAgentTooling } from "../init/tooling.js";
@@ -2,15 +2,16 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { retryOnLock } from "../api.js";
4
4
  import { contextBranch, readContextFile } from "../context.js";
5
+ import { branchCreateRequest } from "../parameters.gen.js";
5
6
  import { writer } from "../writer.js";
6
7
  import { EndpointType } from "../utils/api_enums.js";
7
8
  import { looksLikeBranchId, looksLikeLSN, looksLikeTimestamp } from "../utils/formats.js";
8
- import { branchIdFromProps, branchIdResolve, fillSingleProject } from "../utils/enrichers.js";
9
- import { branchCreateRequest } from "../parameters.gen.js";
9
+ import { branchIdFromProps, fillSingleProject, resolveBranchFromProps } from "../utils/enrichers.js";
10
10
  import { getComputeUnits } from "../utils/compute_units.js";
11
11
  import { psql } from "../utils/psql.js";
12
12
  import { parsePointInTime } from "../utils/point_in_time.js";
13
13
  import { parseSchemaDiffParams, schemaDiff } from "./schema_diff.js";
14
+ import chalk from "chalk";
14
15
  //#region src/commands/branches.ts
15
16
  var branches_exports = /* @__PURE__ */ __exportAll({
16
17
  BRANCH_FIELDS: () => BRANCH_FIELDS,
@@ -27,14 +28,64 @@ const BRANCH_FIELDS = [
27
28
  "expires_at",
28
29
  "created_at"
29
30
  ];
30
- const BRANCH_FIELDS_RESET = [
31
+ const BRANCH_FIELDS_RESET = [...BRANCH_FIELDS, "last_reset_at"];
32
+ const BRANCH_DETAIL_FIELDS = [
31
33
  "name",
32
34
  "id",
33
- "default",
35
+ "parent_id",
34
36
  "current_state",
37
+ "expires_at",
35
38
  "created_at",
36
- "last_reset_at"
39
+ "created_by",
40
+ "last_reset_at",
41
+ "logical_size"
37
42
  ];
43
+ /**
44
+ * The branch `.neon` pins, by name (preferred) or id, when `.neon` links this same project;
45
+ * a `--project-id` pointing elsewhere makes its branch not current.
46
+ */
47
+ const currentBranchOf = (props) => {
48
+ const context = readContextFile(props.contextFile);
49
+ return context.projectId === props.projectId ? contextBranch(context) : void 0;
50
+ };
51
+ const branchNameCell = (br, labels) => {
52
+ const tags = [];
53
+ if (br.default) tags.push(chalk.cyan("[default]"));
54
+ if (br.protected) tags.push(chalk.yellow("[protected]"));
55
+ if (labels.annotations?.[br.id]?.value.anonymized) tags.push(chalk.dim("[anon]"));
56
+ if (labels.current !== void 0 && (br.id === labels.current || br.name === labels.current)) tags.push(chalk.green("[current]"));
57
+ tags.push(br.name);
58
+ return tags.join(" ");
59
+ };
60
+ const stateCell = (state) => {
61
+ if (!state) return "";
62
+ if (state === "ready") return chalk.green(state);
63
+ if (state === "init" || state === "resetting") return chalk.yellow(state);
64
+ return chalk.dim(state);
65
+ };
66
+ const formatBytes = (bytes) => {
67
+ const units = [
68
+ "bytes",
69
+ "KiB",
70
+ "MiB",
71
+ "GiB",
72
+ "TiB"
73
+ ];
74
+ let value = bytes;
75
+ let unit = 0;
76
+ while (value >= 1024 && unit < units.length - 1) {
77
+ value /= 1024;
78
+ unit++;
79
+ }
80
+ return unit === 0 ? `${bytes} bytes` : `${value.toFixed(1)} ${units[unit]}`;
81
+ };
82
+ const branchColumns = (labels) => ({
83
+ name: (br) => branchNameCell(br, labels),
84
+ current_state: (br) => stateCell(br.current_state),
85
+ expires_at: (br) => br.expires_at || "never",
86
+ created_by: (br) => br.created_by?.name ?? "",
87
+ logical_size: (br) => br.logical_size === void 0 ? "" : formatBytes(br.logical_size)
88
+ });
38
89
  const command = "branches";
39
90
  const describe = "Manage branches";
40
91
  const aliases = ["branch"];
@@ -171,22 +222,12 @@ const handler = (args) => {
171
222
  };
172
223
  const list = async (props) => {
173
224
  const { data: { branches, annotations } } = await props.apiClient.listProjectBranches({ projectId: props.projectId });
174
- const currentBranch = contextBranch(readContextFile(props.contextFile));
175
225
  writer(props).end(branches, {
176
226
  fields: BRANCH_FIELDS,
177
- renderColumns: {
178
- expires_at: (br) => br.expires_at || "never",
179
- name: (br) => {
180
- const isAnon = annotations[br.id]?.value.anonymized;
181
- const labels = [];
182
- if (br.default) labels.push("[default]");
183
- if (br.protected) labels.push("[protected]");
184
- if (isAnon) labels.push("[anon]");
185
- if (currentBranch !== void 0 && (br.id === currentBranch || br.name === currentBranch)) labels.push("[current]");
186
- labels.push(br.name);
187
- return labels.join(" ");
188
- }
189
- }
227
+ renderColumns: branchColumns({
228
+ annotations,
229
+ current: currentBranchOf(props)
230
+ })
190
231
  });
191
232
  };
192
233
  const create = async (props) => {
@@ -237,16 +278,20 @@ const create = async (props) => {
237
278
  out.write(data.branch, {
238
279
  fields: BRANCH_FIELDS,
239
280
  title: "branch",
240
- emptyMessage: "No branches have been found."
281
+ humanTitle: "Branch",
282
+ emptyMessage: "No branches have been found.",
283
+ renderColumns: branchColumns({ current: currentBranchOf(props) })
241
284
  });
242
285
  if (writeEndpoints) out.write(endpoints, {
243
286
  fields: ["id", "created_at"],
244
287
  title: "endpoints",
288
+ humanTitle: "Compute",
245
289
  emptyMessage: "No endpoints have been found."
246
290
  });
247
291
  if (writeUris) out.write(connectionUris, {
248
292
  fields: ["connection_uri"],
249
293
  title: "connection_uris",
294
+ humanTitle: "Connection string",
250
295
  emptyMessage: "No connection uris have been found"
251
296
  });
252
297
  }
@@ -259,24 +304,54 @@ const create = async (props) => {
259
304
  }
260
305
  };
261
306
  const rename = async (props) => {
262
- const branchId = await branchIdFromProps(props);
307
+ const { branchId, listing } = await resolveBranchFromProps(props);
263
308
  const { data } = await retryOnLock(() => props.apiClient.updateProjectBranch(props.projectId, branchId, { branch: { name: props.newName } }));
264
- writer(props).end(data.branch, { fields: BRANCH_FIELDS });
309
+ writer(props).end(data.branch, {
310
+ fields: BRANCH_FIELDS,
311
+ renderColumns: branchColumns({
312
+ annotations: listing?.annotations,
313
+ current: currentBranchOf(props)
314
+ })
315
+ });
265
316
  };
266
317
  const setDefault = async (props) => {
267
- const branchId = await branchIdFromProps(props);
318
+ const { branchId, listing } = await resolveBranchFromProps(props);
268
319
  const { data } = await retryOnLock(() => props.apiClient.setDefaultProjectBranch(props.projectId, branchId));
269
- writer(props).end(data.branch, { fields: BRANCH_FIELDS });
320
+ writer(props).end(data.branch, {
321
+ fields: BRANCH_FIELDS,
322
+ renderColumns: branchColumns({
323
+ annotations: listing?.annotations,
324
+ current: currentBranchOf(props)
325
+ })
326
+ });
270
327
  };
271
328
  const deleteBranch = async (props) => {
272
- const branchId = await branchIdFromProps(props);
329
+ const { branchId, listing } = await resolveBranchFromProps(props);
273
330
  const { data } = await retryOnLock(() => props.apiClient.deleteProjectBranch(props.projectId, branchId));
274
- if (data) writer(props).end(data.branch, { fields: BRANCH_FIELDS });
331
+ if (data) writer(props).end(data.branch, {
332
+ fields: BRANCH_FIELDS,
333
+ renderColumns: branchColumns({
334
+ annotations: listing?.annotations,
335
+ current: currentBranchOf(props)
336
+ })
337
+ });
275
338
  };
276
339
  const get = async (props) => {
277
- const branchId = await branchIdFromProps(props);
278
- const { data } = await props.apiClient.getProjectBranch(props.projectId, branchId);
279
- writer(props).end(data.branch, { fields: BRANCH_FIELDS });
340
+ const resolved = await resolveBranchFromProps(props);
341
+ let branch = resolved.branch;
342
+ let annotations = resolved.listing?.annotations;
343
+ if (!branch) {
344
+ const { data } = await props.apiClient.getProjectBranch(props.projectId, resolved.branchId);
345
+ branch = data.branch;
346
+ annotations = data.annotation ? { [data.branch.id]: data.annotation } : void 0;
347
+ }
348
+ writer(props).end(branch, {
349
+ fields: BRANCH_DETAIL_FIELDS,
350
+ renderColumns: branchColumns({
351
+ annotations,
352
+ current: currentBranchOf(props)
353
+ })
354
+ });
280
355
  };
281
356
  const addCompute = async (props) => {
282
357
  const branchId = await branchIdFromProps(props);
@@ -291,34 +366,50 @@ const addCompute = async (props) => {
291
366
  };
292
367
  const reset = async (props) => {
293
368
  if (!props.parent) throw new Error("Only resetting to parent is supported for now");
294
- const branchId = await branchIdFromProps(props);
295
- const { data: { branch: { parent_id } } } = await props.apiClient.getProjectBranch(props.projectId, branchId);
369
+ const { branchId, branch, listing } = await resolveBranchFromProps(props);
370
+ const parent_id = branch?.parent_id ?? (await props.apiClient.getProjectBranch(props.projectId, branchId)).data.branch.parent_id;
296
371
  if (!parent_id) throw new Error("Branch has no parent");
297
372
  const { data } = await retryOnLock(() => props.apiClient.restoreProjectBranch(props.projectId, branchId, {
298
373
  source_branch_id: parent_id,
299
374
  preserve_under_name: props.preserveUnderName || void 0
300
375
  }));
301
- writer(props).end(data.branch, { fields: BRANCH_FIELDS_RESET });
376
+ writer(props).end(data.branch, {
377
+ fields: BRANCH_FIELDS_RESET,
378
+ renderColumns: branchColumns({
379
+ annotations: listing?.annotations,
380
+ current: currentBranchOf(props)
381
+ })
382
+ });
302
383
  };
303
384
  const restore = async (props) => {
304
- const targetBranchId = await branchIdResolve({
305
- branch: props.id,
385
+ const { branchId: targetBranchId, branch: targetBranch, listing } = await resolveBranchFromProps({
386
+ apiClient: props.apiClient,
306
387
  projectId: props.projectId,
307
- apiClient: props.apiClient
388
+ id: props.id
308
389
  });
309
390
  const pointInTime = await parsePointInTime({
310
391
  pointInTime: props.pointInTime,
311
392
  targetBranchId,
312
393
  projectId: props.projectId,
313
- api: props.apiClient
394
+ api: props.apiClient,
395
+ branches: listing?.branches,
396
+ targetBranch
314
397
  });
315
- log.info(`Restoring branch ${targetBranchId} to the branch ${pointInTime.branchId} ${pointInTime.tag === "lsn" && "LSN " + pointInTime.lsn || pointInTime.tag === "timestamp" && "timestamp " + pointInTime.timestamp || "head"}`);
398
+ if (props.output === "table") {
399
+ const nameOf = (id) => listing?.branches.find((b) => b.id === id)?.name ?? id;
400
+ const point = pointInTime.tag === "lsn" ? `LSN ${pointInTime.lsn} of` : pointInTime.tag === "timestamp" ? `timestamp ${pointInTime.timestamp} of` : "the head of";
401
+ log.info(`Restoring ${nameOf(targetBranchId)} to ${point} ${nameOf(pointInTime.branchId)}`);
402
+ } else log.info(`Restoring branch ${targetBranchId} to the branch ${pointInTime.branchId} ${pointInTime.tag === "lsn" && "LSN " + pointInTime.lsn || pointInTime.tag === "timestamp" && "timestamp " + pointInTime.timestamp || "head"}`);
316
403
  const { data } = await retryOnLock(() => props.apiClient.restoreProjectBranch(props.projectId, targetBranchId, {
317
404
  source_branch_id: pointInTime.branchId,
318
405
  preserve_under_name: props.preserveUnderName || void 0,
319
406
  ...pointInTime.tag === "lsn" && { source_lsn: pointInTime.lsn },
320
407
  ...pointInTime.tag === "timestamp" && { source_timestamp: pointInTime.timestamp }
321
408
  }));
409
+ const labels = {
410
+ annotations: listing?.annotations,
411
+ current: currentBranchOf(props)
412
+ };
322
413
  const writeInst = writer(props).write(data.branch, {
323
414
  title: "Restored branch",
324
415
  fields: [
@@ -326,7 +417,8 @@ const restore = async (props) => {
326
417
  "name",
327
418
  "last_reset_at"
328
419
  ],
329
- emptyMessage: "No branches have been restored."
420
+ emptyMessage: "No branches have been restored.",
421
+ renderColumns: branchColumns(labels)
330
422
  });
331
423
  const parentId = data.branch.parent_id;
332
424
  if (props.preserveUnderName && parentId) {
@@ -334,19 +426,26 @@ const restore = async (props) => {
334
426
  writeInst.write(data.branch, {
335
427
  title: "Backup branch",
336
428
  fields: ["id", "name"],
337
- emptyMessage: "Backup branch has not been found."
429
+ emptyMessage: "Backup branch has not been found.",
430
+ renderColumns: branchColumns(labels)
338
431
  });
339
432
  }
340
433
  writeInst.end();
341
434
  };
342
435
  const setExpiration = async (props) => {
343
- const branchId = await branchIdFromProps({
436
+ const { branchId, listing } = await resolveBranchFromProps({
344
437
  ...props,
345
438
  id: props.branchId
346
439
  });
347
440
  const expiresAt = typeof props.expiresAt === "string" ? new Date(props.expiresAt).toISOString() : null;
348
441
  const { data } = await retryOnLock(() => props.apiClient.updateProjectBranch(props.projectId, branchId, { branch: { expires_at: expiresAt } }));
349
- writer(props).end(data.branch, { fields: BRANCH_FIELDS });
442
+ writer(props).end(data.branch, {
443
+ fields: BRANCH_FIELDS,
444
+ renderColumns: branchColumns({
445
+ annotations: listing?.annotations,
446
+ current: currentBranchOf(props)
447
+ })
448
+ });
350
449
  };
351
450
  //#endregion
352
451
  export { BRANCH_FIELDS, aliases, builder, command, describe, handler, branches_exports as t };
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { isNeonApiError, retryOnLock } from "../api.js";
4
+ import { createProjectBranchBucket, deleteProjectBranchBucket, deleteProjectBranchBucketObject, deleteProjectBranchBucketObjectsByPrefix, getProjectBranchBucketObject, listProjectBranchBucketObjects, listProjectBranchBuckets, presignUpload } from "../storage_api.js";
4
5
  import { writer } from "../writer.js";
5
6
  import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
6
- import { createProjectBranchBucket, deleteProjectBranchBucket, deleteProjectBranchBucketObject, deleteProjectBranchBucketObjectsByPrefix, getProjectBranchBucketObject, listProjectBranchBucketObjects, listProjectBranchBuckets, presignUpload } from "../storage_api.js";
7
7
  import { createReadStream, createWriteStream } from "node:fs";
8
8
  import { basename } from "node:path";
9
9
  import { stat, unlink } from "node:fs/promises";
@@ -10,12 +10,12 @@ import { editNeonConfig } from "../config_edit.js";
10
10
  import { toNeonConfigView } from "../config_format.js";
11
11
  import { declaredNeonServices } from "../config_services.js";
12
12
  import { loadEnvFileIntoProcess } from "../env_file.js";
13
+ import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
13
14
  import { writer } from "../writer.js";
14
15
  import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
15
16
  import { assertAiGatewayProvisionable, warnAiGateway } from "../utils/ai_gateway_notice.js";
16
17
  import { announceTargetBranch } from "../utils/branch_notice.js";
17
18
  import { renderAppliedChanges, renderBranchSettingConflicts, renderCustomDomainFollowup } from "../utils/config_diff.js";
18
- import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
19
19
  import { bundleEntry } from "../utils/esbuild.js";
20
20
  import { readGitContext } from "../utils/git.js";
21
21
  import { resolveHookEnv, runDeployAfterHook, runDeployBeforeHook } from "../utils/hooks.js";
@@ -13,8 +13,8 @@ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs"
13
13
  import { basename, dirname, join, relative, resolve } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import chalk from "chalk";
16
- import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry } from "@neon/config-runtime";
17
16
  import { spawn, spawnSync } from "node:child_process";
17
+ import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry } from "@neon/config-runtime";
18
18
  import { once } from "node:events";
19
19
  import { createServer } from "node:net";
20
20
  //#region src/commands/dev.ts
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { getCliName } from "../utils/cli_name.js";
4
+ import { projectUpdateRequest } from "../parameters.gen.js";
4
5
  import { writer } from "../writer.js";
5
6
  import { fillSingleProject } from "../utils/enrichers.js";
6
- import { projectUpdateRequest } from "../parameters.gen.js";
7
7
  //#region src/commands/ip_allow.ts
8
8
  var ip_allow_exports = /* @__PURE__ */ __exportAll({
9
9
  builder: () => builder,
@@ -0,0 +1,316 @@
1
+ import { getCliName } from "../utils/cli_name.js";
2
+ import { Parser } from "yargs/helpers";
3
+ //#region src/commands/manifest.ts
4
+ /**
5
+ * Every top-level command, in help order, with the metadata yargs needs to list and route it.
6
+ * A command's module (and everything it imports) loads only when yargs selects that command,
7
+ * so `neon --help` and `neon projects list` don't pay for `init`, `dev`, or `psql`.
8
+ *
9
+ * `command`, `aliases`, and `describe` must equal the module's own exports; `manifest.test.ts`
10
+ * enforces that. `load` uses a literal specifier so bundlers can see and package each chunk.
11
+ */
12
+ const commandManifest = [
13
+ {
14
+ command: "auth",
15
+ aliases: ["login"],
16
+ describe: "Sign in with a browser. See --help for API keys and profiles",
17
+ load: () => import("./auth.js")
18
+ },
19
+ {
20
+ command: "profile",
21
+ aliases: ["profiles"],
22
+ describe: "Manage named sets of Neon credentials",
23
+ load: () => import("./profile.js")
24
+ },
25
+ {
26
+ command: "api-keys",
27
+ aliases: ["api-key"],
28
+ describe: "Manage API keys",
29
+ load: () => import("./api_keys.js")
30
+ },
31
+ {
32
+ command: "api [path]",
33
+ describe: "Call any Neon API route directly (authenticated passthrough)",
34
+ load: () => import("./api.js")
35
+ },
36
+ {
37
+ command: "me",
38
+ describe: "Show current user",
39
+ load: () => import("./user.js")
40
+ },
41
+ {
42
+ command: "orgs",
43
+ aliases: ["org"],
44
+ describe: "Manage organizations",
45
+ load: () => import("./orgs.js")
46
+ },
47
+ {
48
+ command: "projects",
49
+ aliases: ["project"],
50
+ describe: "Manage projects",
51
+ load: () => import("./projects.js")
52
+ },
53
+ {
54
+ command: "ip-allow",
55
+ describe: "Manage IP Allow",
56
+ load: () => import("./ip_allow.js")
57
+ },
58
+ {
59
+ command: "vpc",
60
+ describe: "Manage VPC endpoints and project VPC restrictions",
61
+ load: () => import("./vpc_endpoints.js")
62
+ },
63
+ {
64
+ command: "neon-auth",
65
+ describe: "Manage Neon Auth",
66
+ load: () => import("./neon_auth.js")
67
+ },
68
+ {
69
+ command: "branches",
70
+ aliases: ["branch"],
71
+ describe: "Manage branches",
72
+ load: () => import("./branches.js")
73
+ },
74
+ {
75
+ command: "databases",
76
+ aliases: ["database", "db"],
77
+ describe: "Manage databases",
78
+ load: () => import("./databases.js")
79
+ },
80
+ {
81
+ command: "roles",
82
+ aliases: ["role"],
83
+ describe: "Manage roles",
84
+ load: () => import("./roles.js")
85
+ },
86
+ {
87
+ command: "operations",
88
+ aliases: ["operation"],
89
+ describe: "Manage operations",
90
+ load: () => import("./operations.js")
91
+ },
92
+ {
93
+ command: "logs",
94
+ describe: "Query branch logs",
95
+ load: () => import("./logs.js")
96
+ },
97
+ {
98
+ command: "snapshots",
99
+ aliases: ["snapshot"],
100
+ describe: "Manage snapshots",
101
+ load: () => import("./snapshots.js")
102
+ },
103
+ {
104
+ command: "inspect",
105
+ aliases: ["inspection"],
106
+ describe: "Inspect a branch's Postgres health and configuration",
107
+ load: () => import("./inspect.js")
108
+ },
109
+ {
110
+ command: "connection-string [branch]",
111
+ aliases: ["cs"],
112
+ describe: "Get connection string",
113
+ load: () => import("./connection_string.js")
114
+ },
115
+ {
116
+ command: "psql [branch]",
117
+ describe: "Connect to a database via psql",
118
+ load: () => import("./psql.js")
119
+ },
120
+ {
121
+ command: "set-context",
122
+ describe: `Deprecated: use \`${getCliName()} link\`. Set the .neon context (raw write).`,
123
+ load: () => import("./set_context.js")
124
+ },
125
+ {
126
+ command: "checkout [id|name]",
127
+ describe: "Pin a branch in the local context (.neon) so subsequent commands target it",
128
+ load: () => import("./checkout.js")
129
+ },
130
+ {
131
+ command: "git",
132
+ aliases: [],
133
+ describe: "Sync the checked-out Neon branch to your git branch (Preview)",
134
+ load: () => import("./git.js")
135
+ },
136
+ {
137
+ command: "link",
138
+ describe: "Link the current directory to a Neon project",
139
+ load: () => import("./link.js")
140
+ },
141
+ {
142
+ command: "open",
143
+ describe: "Open the linked project in the Neon Console",
144
+ load: () => import("./open.js")
145
+ },
146
+ {
147
+ command: "claim",
148
+ aliases: ["claimable"],
149
+ describe: "Create and claim temporary Neon projects",
150
+ load: () => import("./claim.js")
151
+ },
152
+ {
153
+ command: "init",
154
+ describe: "Set up coding agents and this directory for Neon. -y is Recommended. -y with Custom flags applies those answers without prompts.",
155
+ load: () => import("./init.js")
156
+ },
157
+ {
158
+ command: "mcp",
159
+ describe: "Install the Neon MCP server into coding agents",
160
+ load: () => import("./mcp.js")
161
+ },
162
+ {
163
+ command: "plugins",
164
+ aliases: ["plugin"],
165
+ describe: "Install the Neon plugin into coding agents",
166
+ load: () => import("./plugins.js")
167
+ },
168
+ {
169
+ command: "skills",
170
+ aliases: ["skill"],
171
+ describe: "Install Neon agent skills into coding agents",
172
+ load: () => import("./skills.js")
173
+ },
174
+ {
175
+ command: "ask",
176
+ describe: "Ask a question about Neon",
177
+ load: () => import("./ask.js")
178
+ },
179
+ {
180
+ command: "data-api",
181
+ describe: "Manage the Neon Data API for a database",
182
+ load: () => import("./data_api.js")
183
+ },
184
+ {
185
+ command: "functions",
186
+ aliases: ["function"],
187
+ describe: "Manage Neon Functions",
188
+ load: () => import("./functions.js")
189
+ },
190
+ {
191
+ command: "triggers",
192
+ aliases: ["trigger"],
193
+ describe: "Manage function triggers",
194
+ load: () => import("./triggers.js")
195
+ },
196
+ {
197
+ command: "credentials",
198
+ aliases: ["credential"],
199
+ describe: "Manage branch credentials",
200
+ load: () => import("./credentials.js")
201
+ },
202
+ {
203
+ command: "dev",
204
+ describe: "Run Neon Functions locally with a dev server",
205
+ load: () => import("./dev.js")
206
+ },
207
+ {
208
+ command: "diff [compare-branch]",
209
+ describe: "Show a git-style schema diff between the current branch and another branch",
210
+ load: () => import("./diff.js")
211
+ },
212
+ {
213
+ command: "config",
214
+ describe: "Manage a branch with a neon.ts policy",
215
+ load: () => import("./config.js")
216
+ },
217
+ {
218
+ command: "status",
219
+ describe: "Show the branch's live Neon state (alias of `config status`)",
220
+ load: () => import("./status.js")
221
+ },
222
+ {
223
+ command: "deploy",
224
+ describe: "Apply a neon.ts policy to a branch (alias for `config apply`)",
225
+ load: () => import("./deploy.js")
226
+ },
227
+ {
228
+ command: "env",
229
+ describe: "Manage a branch's Neon env variables locally",
230
+ load: () => import("./env.js")
231
+ },
232
+ {
233
+ command: "buckets",
234
+ aliases: ["bucket"],
235
+ describe: "Manage branch object-storage buckets and their objects",
236
+ load: () => import("./bucket.js")
237
+ },
238
+ {
239
+ command: "bootstrap [directory]",
240
+ describe: "Scaffold a new project from a Neon starter template, then install agent tooling and link a Neon project",
241
+ load: () => import("./bootstrap.js")
242
+ }
243
+ ];
244
+ const COMPLETION_COMMAND = "completion";
245
+ const COMPLETION_FLAG = "--get-yargs-completions";
246
+ const verbOf = (entry) => entry.command.split(" ")[0];
247
+ const entryByName = new Map(commandManifest.flatMap((entry) => [verbOf(entry), ...entry.aliases ?? []].map((name) => [name, entry])));
248
+ /**
249
+ * The command yargs 17 will run for these top-level positionals: it walks them in order,
250
+ * skips `completion`, runs the first one that names a command, and stops at the first one
251
+ * that doesn't (`kRunYargsParserAndExecuteCommands` in yargs-factory.js).
252
+ */
253
+ const selectCommand = (positionals) => {
254
+ const first = positionals.find((p) => String(p) !== COMPLETION_COMMAND);
255
+ return first === void 0 ? void 0 : entryByName.get(String(first));
256
+ };
257
+ /**
258
+ * The command whose builder yargs 17 runs to complete `--get-yargs-completions <words>`: the
259
+ * first word that is a command's canonical name, at any position; aliases don't count there
260
+ * (`defaultCompletion` in completion.js looks words up in the handler map, keyed by name).
261
+ */
262
+ const selectCompletionCommand = (words) => words.map((word) => commandManifest.find((entry) => verbOf(entry) === word)).find((entry) => entry);
263
+ function assertHasGetOptions(argv) {
264
+ if (!("getOptions" in argv) || typeof argv.getOptions !== "function") throw new Error("yargs no longer exposes getOptions(); update preloadCommand for the installed yargs");
265
+ }
266
+ const loaded = /* @__PURE__ */ new Map();
267
+ /**
268
+ * Import the module of the command yargs is about to run, before it runs, so its builder can
269
+ * stay synchronous. Parses argv with yargs' own parser and the top-level options yargs will
270
+ * use, so an option value that happens to be a command name (`--api-key projects`) can't
271
+ * change the answer.
272
+ */
273
+ const preloadCommand = async (cli, args) => {
274
+ const doubleDash = args.indexOf("--");
275
+ const completionAt = (doubleDash === -1 ? args : args.slice(0, doubleDash)).indexOf(COMPLETION_FLAG);
276
+ let entry;
277
+ if (completionAt >= 0) entry = selectCompletionCommand(args.slice(completionAt + 1));
278
+ else {
279
+ assertHasGetOptions(cli);
280
+ const options = cli.getOptions();
281
+ const { argv } = Parser.detailed([...args], {
282
+ ...options,
283
+ configuration: {
284
+ ...options?.configuration,
285
+ "populate--": true,
286
+ "parse-positional-numbers": false
287
+ }
288
+ });
289
+ entry = selectCommand(argv._);
290
+ }
291
+ if (entry && !loaded.has(entry)) loaded.set(entry, await entry.load());
292
+ return entry;
293
+ };
294
+ /**
295
+ * The manifest as yargs command modules. The builder is synchronous when `preloadCommand`
296
+ * already imported the module, which is every invocation it predicts correctly. A mispredicted
297
+ * command still runs, because yargs 17 awaits a builder's promise when it runs a command; shell
298
+ * completion does not await it, so a mispredicted completion lists no subcommands.
299
+ */
300
+ const lazyCommands = commandManifest.map((entry) => ({
301
+ command: entry.command,
302
+ aliases: entry.aliases,
303
+ describe: entry.describe,
304
+ builder: (argv) => {
305
+ const mod = loaded.get(entry);
306
+ return mod ? mod.builder(argv) : entry.load().then((m) => {
307
+ loaded.set(entry, m);
308
+ return m.builder(argv);
309
+ });
310
+ },
311
+ handler: async (args) => {
312
+ return (loaded.get(entry) ?? await entry.load()).handler?.(args);
313
+ }
314
+ }));
315
+ //#endregion
316
+ export { commandManifest, lazyCommands, preloadCommand, selectCommand, selectCompletionCommand };
@@ -4,11 +4,11 @@ import { readContextFile } from "../context.js";
4
4
  import { recordCommandSuccessExtras } from "../analytics.js";
5
5
  import { getCliName } from "../utils/cli_name.js";
6
6
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
7
+ import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
8
+ import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
7
9
  import { writer } from "../writer.js";
8
10
  import { noPassthrough, single } from "../utils/flags.js";
9
11
  import { AGENT_MCP_SKIP_MESSAGE } from "../init/copy.js";
10
- import { NEON_MCP_CATEGORIES, NEON_MCP_URL, existingNeonApiKey, installNeonMcpServer, neonMcpUrl, parseMcpCategories, trackedProjectMcpConfig } from "../mcp/install.js";
11
- import { mcpInstallableAgents, resolveInstallTargets } from "../mcp/targets.js";
12
12
  import { mintMcpApiKey, mintedKeyRevokeCommand, withdrawMintedKey } from "../mcp/mint.js";
13
13
  import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
14
14
  import { resolveMcpPlan } from "../mcp/plan.js";
@@ -2,13 +2,13 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
- import { writer } from "../writer.js";
6
- import { noPassthrough } from "../utils/flags.js";
7
- import { AGENT_PLUGIN_SKIP_MESSAGE } from "../init/copy.js";
8
5
  import { getAgentDisplayName } from "../mcp/agents.js";
9
6
  import { NEON_MCP_URL } from "../mcp/install.js";
10
7
  import "../init/agents.js";
11
8
  import { pluginsInstallableAgents } from "../plugins/targets.js";
9
+ import { writer } from "../writer.js";
10
+ import { noPassthrough } from "../utils/flags.js";
11
+ import { AGENT_PLUGIN_SKIP_MESSAGE } from "../init/copy.js";
12
12
  import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
13
13
  import { resolvePluginsPlan } from "../plugins/plan.js";
14
14
  import { NEON_PLUGIN_NAME, PLUGIN_SKILLS, PLUGIN_SOURCE, neonPluginsRetryCommand, pluginsAddArgs, runPluginsCli } from "../plugins/run.js";
@@ -11,10 +11,10 @@ import { auth, revokeToken } from "../auth.js";
11
11
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
12
12
  import { readOutgoingCredential, retirePreviousCredential, revokeTokenSet, withdrawKey } from "../retire_credential.js";
13
13
  import { authFlow, usableCredential } from "./auth.js";
14
+ import { identityFromAuthDetails, isApiKeyMethod, mintedKeyName, notAnApiKeyMessage } from "../profile_keys.js";
14
15
  import { writer } from "../writer.js";
15
16
  import { noPassthrough, single } from "../utils/flags.js";
16
17
  import { orgIdForProject } from "./api_keys.js";
17
- import { identityFromAuthDetails, isApiKeyMethod, mintedKeyName, notAnApiKeyMessage } from "../profile_keys.js";
18
18
  import { existsSync, readFileSync, rmSync } from "node:fs";
19
19
  import { isAbsolute, relative } from "node:path";
20
20
  import prompts from "prompts";
@@ -4,8 +4,8 @@ import { log } from "../log.js";
4
4
  import { isNeonApiError, messageFromBody } from "../api.js";
5
5
  import { updateContextFile } from "../context.js";
6
6
  import { getCliName } from "../utils/cli_name.js";
7
- import { writer } from "../writer.js";
8
7
  import { projectCreateRequest, projectUpdateRequest } from "../parameters.gen.js";
8
+ import { writer } from "../writer.js";
9
9
  import { getComputeUnits } from "../utils/compute_units.js";
10
10
  import { psql } from "../utils/psql.js";
11
11
  import prompts from "prompts";
@@ -2,7 +2,7 @@ import { log } from "../log.js";
2
2
  import { isNeonApiError, messageFromBody } from "../api.js";
3
3
  import { sendError } from "../analytics.js";
4
4
  import { writer } from "../writer.js";
5
- import { branchIdFromProps, listAllProjectBranches } from "../utils/enrichers.js";
5
+ import { listAllProjectBranchesWithAnnotations, resolveBranchFromProps } from "../utils/enrichers.js";
6
6
  import { parsePointInTime } from "../utils/point_in_time.js";
7
7
  import chalk from "chalk";
8
8
  import { createPatch } from "diff";
@@ -15,12 +15,14 @@ const COLORS = {
15
15
  };
16
16
  const schemaDiff = async (props) => {
17
17
  props.branch = props.baseBranch || props.branch;
18
- const baseBranch = await branchIdFromProps(props);
18
+ const { branchId: baseBranch, branch: baseBranchData, listing } = await resolveBranchFromProps(props, props.branchListing);
19
19
  let pointInTime = await parsePointInTime({
20
20
  pointInTime: props.compareSource,
21
21
  targetBranchId: baseBranch,
22
22
  projectId: props.projectId,
23
- api: props.apiClient
23
+ api: props.apiClient,
24
+ branches: listing?.branches,
25
+ targetBranch: baseBranchData
24
26
  });
25
27
  const comparingWithParent = props.compareSource.startsWith("^parent");
26
28
  let baseBranchPoint = {
@@ -32,21 +34,35 @@ const schemaDiff = async (props) => {
32
34
  if (props.database) {
33
35
  const database = baseDatabases.find((db) => db.name === props.database);
34
36
  if (!database) throw new Error(`Database ${props.database} does not exist in base branch ${baseBranch}`);
35
- const patch = await createSchemaDiff(baseBranchPoint, pointInTime, database, props);
36
- writer(props).text(colorize(patch));
37
+ const diff = await createSchemaDiff(baseBranchPoint, pointInTime, database, props);
38
+ writer(props).text(colorize(diff.patch));
39
+ reportUnchanged(diff, [baseBranchPoint, pointInTime], database, props, listing);
37
40
  return;
38
41
  }
39
42
  await Promise.all(baseDatabases.map(async (database) => {
40
- const patch = await createSchemaDiff(baseBranchPoint, pointInTime, database, props);
41
- writer(props).text(colorize(patch));
43
+ const diff = await createSchemaDiff(baseBranchPoint, pointInTime, database, props);
44
+ writer(props).text(colorize(diff.patch));
45
+ reportUnchanged(diff, [baseBranchPoint, pointInTime], database, props, listing);
42
46
  }));
43
47
  };
48
+ /** The patch alone is two header lines when nothing differs, which reads like missing output. */
49
+ const reportUnchanged = (diff, [base, compare], database, props, listing) => {
50
+ if (!diff.unchanged || props.output !== "table") return;
51
+ const label = (point) => {
52
+ const name = listing?.branches.find((b) => b.id === point.branchId)?.name ?? point.branchId;
53
+ return point.tag === "lsn" ? `${name} at ${point.lsn}` : point.tag === "timestamp" ? `${name} at ${point.timestamp}` : name;
54
+ };
55
+ log.info(`No schema differences for database ${database.name} between ${label(base)} and ${label(compare)}.`);
56
+ };
44
57
  const fetchDatabases = async (branch, props) => {
45
58
  return props.apiClient.listProjectBranchDatabases(props.projectId, branch).then((response) => response.data.databases);
46
59
  };
47
60
  const createSchemaDiff = async (baseBranch, pointInTime, database, props) => {
48
61
  const [baseSchema, compareSchema] = await Promise.all([fetchSchema(baseBranch, database, props), fetchSchema(pointInTime, database, props)]);
49
- return createPatch(`Database: ${database.name}`, baseSchema, compareSchema, generateHeader(baseBranch), generateHeader(pointInTime));
62
+ return {
63
+ patch: createPatch(`Database: ${database.name}`, baseSchema, compareSchema, generateHeader(baseBranch), generateHeader(pointInTime)),
64
+ unchanged: baseSchema === compareSchema
65
+ };
50
66
  };
51
67
  const fetchSchema = async (pointInTime, database, props) => {
52
68
  try {
@@ -68,6 +84,7 @@ const colorize = (patch) => {
68
84
  return patch.replace(/^([^\n]+)\n([^\n]+)\n/m, "").replace(/^-.*/gm, colorizer("removed")).replace(/^\+.*/gm, colorizer("added")).replace(/^@@.+@@.*/gm, colorizer("section"));
69
85
  };
70
86
  const colorizer = (colorId) => {
87
+ if (process.env.NO_COLOR) return (line) => line;
71
88
  const color = COLORS[colorId];
72
89
  return (line) => color(line);
73
90
  };
@@ -92,11 +109,13 @@ const parseSchemaDiffParams = async (props) => {
92
109
  props.compareSource = props.baseBranch;
93
110
  props.baseBranch = props.branch;
94
111
  } else if (props.branch) {
95
- if ((await listAllProjectBranches(props.apiClient, props.projectId)).find((b) => b.id === props.branch || b.name === props.branch)?.parent_id == void 0) throw new Error(`No branch specified. Your context branch (${props.branch}) has no parent, so no comparison is possible.`);
112
+ props.branchListing = await listAllProjectBranchesWithAnnotations(props.apiClient, props.projectId);
113
+ if (props.branchListing.branches.find((b) => b.id === props.branch || b.name === props.branch)?.parent_id == void 0) throw new Error(`No branch specified. Your context branch (${props.branch}) has no parent, so no comparison is possible.`);
96
114
  log.info(`No branches specified. Comparing your context branch '${props.branch}' with its parent`);
97
115
  props.compareSource = "^parent";
98
116
  } else {
99
- if ((await listAllProjectBranches(props.apiClient, props.projectId)).find((b) => b.default)?.parent_id == void 0) throw new Error("No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.");
117
+ props.branchListing = await listAllProjectBranchesWithAnnotations(props.apiClient, props.projectId);
118
+ if (props.branchListing.branches.find((b) => b.default)?.parent_id == void 0) throw new Error("No branch specified. Include a base branch or add a set-context branch to continue. Your default branch has no parent, so no comparison is possible.");
100
119
  log.info(`No branches specified. Comparing default branch with its parent`);
101
120
  props.compareSource = "^parent";
102
121
  }
@@ -2,12 +2,12 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
2
  import { log } from "../log.js";
3
3
  import { recordCommandSuccessExtras } from "../analytics.js";
4
4
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
5
- import { writer } from "../writer.js";
6
- import { noPassthrough } from "../utils/flags.js";
7
- import { AGENT_SKILLS_SKIP_MESSAGE } from "../init/copy.js";
8
5
  import { getAgentDisplayName } from "../mcp/agents.js";
9
6
  import "../init/agents.js";
10
7
  import { mappedSkillsAgentNames, skillsInstallableAgents } from "../skills/targets.js";
8
+ import { writer } from "../writer.js";
9
+ import { noPassthrough } from "../utils/flags.js";
10
+ import { AGENT_SKILLS_SKIP_MESSAGE } from "../init/copy.js";
11
11
  import { canPickAgentsInteractively, skippableAgentPicker } from "../utils/agent_picker.js";
12
12
  import { skillsHelpValues, skillsYesHelp } from "../skills/catalog.js";
13
13
  import { confirmSkillsUpdate } from "../skills/wizard.js";
@@ -22,8 +22,11 @@ function parseColumns(value) {
22
22
  if (!Number.isFinite(n) || n <= 0) return;
23
23
  return n;
24
24
  }
25
+ function stripAnsi(s) {
26
+ return s.replace(ANSI_RE, "");
27
+ }
25
28
  function displayWidth(s) {
26
- const plain = s.replace(ANSI_RE, "");
29
+ const plain = stripAnsi(s);
27
30
  let width = 0;
28
31
  for (const char of plain) {
29
32
  const code = char.codePointAt(0);
@@ -203,4 +206,4 @@ function charWidth(code) {
203
206
  return 1;
204
207
  }
205
208
  //#endregion
206
- export { displayWidth, formatHumanChunk, parseColumns, resolveOutputWidth };
209
+ export { displayWidth, formatHumanChunk, parseColumns, resolveOutputWidth, stripAnsi };
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ import { getCliName } from "./utils/cli_name.js";
12
12
  import { ensureAuth } from "./commands/auth.js";
13
13
  import { recoverFrom401 } from "./auth_recovery.js";
14
14
  import { UsageError, failOnUsageError, isBareParentCommand, showHelp, showUsageErrorHelp } from "./help.js";
15
+ import { lazyCommands, preloadCommand } from "./commands/manifest.js";
15
16
  import { rewriteUnknownAgentArg } from "./init/plan.js";
16
- import commands_default from "./commands/index.js";
17
17
  import { recoverFromSSO } from "./sso_recovery.js";
18
18
  import { notifyIfUpdateAvailable } from "./update_notifier.js";
19
19
  import { fillInArgs, resolveApiKeyFromEnv } from "./utils/middlewares.js";
@@ -128,7 +128,7 @@ builder = builder.scriptName(pkg_default.name).locale("en").usage("$0 <command>
128
128
  if (args.help) await showHelp(builder);
129
129
  }, true).middleware(async (args) => {
130
130
  if (args._.length === 1 && !NO_SUBCOMMANDS_VERBS.includes(args._[0]) || args._.length > 1 && isBareParentCommand(builder, args._)) await showHelp(builder);
131
- }).middleware(ensureAuth).middleware(enrichFromContext).middleware(analyticsMiddleware).command(commands_default).strictCommands().version(pkg_default.version).group("version", "Global options:").alias("version", "v").completion().scriptName(getCliName()).epilog("For more information, visit https://neon.com/docs/reference/neon-cli").wrap(null).fail(failOnUsageError);
131
+ }).middleware(ensureAuth).middleware(enrichFromContext).middleware(analyticsMiddleware).command(lazyCommands).strictCommands().version(pkg_default.version).group("version", "Global options:").alias("version", "v").completion().scriptName(getCliName()).epilog("For more information, visit https://neon.com/docs/reference/neon-cli").wrap(null).fail(failOnUsageError);
132
132
  async function handleError(msg, err, canRetry) {
133
133
  if (process.argv.some((arg) => arg === "--help" || arg === "-h")) {
134
134
  await showHelp(builder);
@@ -194,6 +194,7 @@ async function handleError(msg, err, canRetry) {
194
194
  (async () => {
195
195
  let attempts = 0;
196
196
  const MAX_ATTEMPTS = 2;
197
+ await preloadCommand(builder, hideBin(process.argv));
197
198
  while (attempts < MAX_ATTEMPTS) try {
198
199
  const args = await builder.argv;
199
200
  trackEvent("cli_command_success", commandSuccessProperties(args));
package/dist/init/mode.js CHANGED
@@ -1,5 +1,5 @@
1
- import { NON_TTY_AGENT_SETUP, NO_AGENT_SETUP_CONFLICT } from "./copy.js";
2
1
  import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
2
+ import { NON_TTY_AGENT_SETUP, NO_AGENT_SETUP_CONFLICT } from "./copy.js";
3
3
  //#region src/init/mode.ts
4
4
  const hasInitMcpFlags = (input) => input.mcpConfigLocation !== void 0 || input.mcpAuth !== void 0 || input.mcpProjectScoped === true;
5
5
  const hasSkillsFlag = (skills) => skills !== void 0 && skills.length > 0;
package/dist/init/run.js CHANGED
@@ -8,16 +8,16 @@ import { readLinkedClaimableCredentials } from "../claimable/state.js";
8
8
  import { getCliName } from "../utils/cli_name.js";
9
9
  import { deprecatedServiceMessage, parseServices, servicesFlagValue } from "../neon_services.js";
10
10
  import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE } from "../config_template.js";
11
- import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, YES_LINK_NEEDS_AUTH, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, installFailedNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, namedAgentsUnavailable, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
12
- import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
13
- import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
14
- import { ConfigInstallFailed, hasNeonConfigFile, initCmd, neonConfigFilename } from "../commands/config.js";
15
- import { runAuthenticatedLink } from "./link.js";
16
11
  import { getAgentDisplayName } from "../mcp/agents.js";
17
12
  import { mcpInstallableAgents } from "../mcp/targets.js";
18
13
  import { missingPluginsCommands, pluginsInstallableAgents } from "../plugins/targets.js";
19
14
  import { skillsInstallableAgents } from "../skills/targets.js";
20
15
  import { FALLBACK_SKILLS_AGENTS, assertNamedAgentTooling, funnelAgentSetup, planInitToolingSteps, recommendedTooling, resolveNamedAgents, splitInitTooling } from "./plan.js";
16
+ import { inferPackageManager, resolvePackageManager } from "../utils/package_manager.js";
17
+ import { CANCELLED_BODY, CLAIMABLE_ACCOUNT_FLAGS, CLAIMABLE_ALREADY_LINKED, CLAIMABLE_MCP_API_KEY, CLAIMABLE_NO_LINK, MCP_SCOPED_NEEDS_PROJECT, NON_TTY_LINK_NEEDS_AUTH, NO_AGENTS_FALLBACK_STATUS, PROGRESS, YES_LINK_NEEDS_AUTH, claimableNext, claimableThenServicesNext, envPullFailedNext, existingConfigNext, extraServicesNext, installFailedNext, mcpConfigLocationSkipped, mcpConfigLocationUnavailable, namedAgentsUnavailable, skippedLinkNext, unattendedUnauthedNext } from "./copy.js";
18
+ import { agentsRowValue, configRowValue, formatInitDone, headingForKind, printInitBanner, printInitDone, printInitProgress, projectRowValue, shouldPrintInitBanner } from "./chrome.js";
19
+ import { ConfigInstallFailed, hasNeonConfigFile, initCmd, neonConfigFilename } from "../commands/config.js";
20
+ import { runAuthenticatedLink } from "./link.js";
21
21
  import { pullInitEnv } from "./auth.js";
22
22
  import { InitCancelled, raceSigint, restoreCursor } from "./cancelled.js";
23
23
  import { pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively } from "./wizard.js";
@@ -1,7 +1,7 @@
1
1
  import { log } from "../log.js";
2
2
  import { getCliName } from "../utils/cli_name.js";
3
- import { PROGRESS } from "./copy.js";
4
3
  import { assertNamedAgentTooling, chooseYesAgentTooling, collectYesAgents, initYesSupportedAgents, noDetectedAgentsMessage, planAgentSteps, planToolingSteps, planYesAgentSteps, resolveInitAgentSetup } from "./plan.js";
4
+ import { PROGRESS } from "./copy.js";
5
5
  import { detectAgent } from "./detect_host.js";
6
6
  import { AgentSelectionSkipped, canPickAgentsInteractively } from "../utils/agent_picker.js";
7
7
  import { mcpInstallError, reportMcpInstall } from "../commands/mcp.js";
@@ -1,9 +1,9 @@
1
1
  import { CONFIG_INIT_SERVICES } from "../config_template.js";
2
- import { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, PACKAGE_MANAGER_MESSAGE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, agentMissingCommandDescription } from "./copy.js";
3
- import { installedPackageManagers } from "../utils/package_manager.js";
4
- import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
5
2
  import { getAgentDisplayName, listMcpAgentIds } from "../mcp/agents.js";
6
3
  import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
4
+ import { installedPackageManagers } from "../utils/package_manager.js";
5
+ import { AGENT_DETECTED_DESCRIPTION, AGENT_MCP_PICKER_MESSAGE, AGENT_MCP_SKIP_MESSAGE, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_PLUGIN_SKIP_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, AGENT_SKILLS_PICKER_MESSAGE, AGENT_SKILLS_SKIP_MESSAGE, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, PACKAGE_MANAGER_MESSAGE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE, agentMissingCommandDescription } from "./copy.js";
6
+ import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
7
7
  import { canPickAgentsInteractively, pickAgentsInteractively } from "../utils/agent_picker.js";
8
8
  import { NEON_SKILL_CATALOG, listSkillIds } from "../skills/catalog.js";
9
9
  import { InitCancelled, restoreCursorVisibilityOnAbort } from "./cancelled.js";
@@ -1,7 +1,7 @@
1
1
  import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
- import { execFileSync } from "node:child_process";
4
3
  import { agents, upsertServer } from "add-mcp";
4
+ import { execFileSync } from "node:child_process";
5
5
  //#region src/mcp/install.ts
6
6
  const NEON_MCP_URL = "https://mcp.neon.tech/mcp";
7
7
  const NEON_MCP_NAME = "Neon";
package/dist/mcp/mint.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { log } from "../log.js";
2
2
  import { isNeonApiError } from "../api.js";
3
3
  import { getCliName } from "../utils/cli_name.js";
4
- import { orgIdForProject } from "../commands/api_keys.js";
5
4
  import { mintedKeyName } from "../profile_keys.js";
5
+ import { orgIdForProject } from "../commands/api_keys.js";
6
6
  //#region src/mcp/mint.ts
7
7
  const cannotMintMessage = "This CLI credential cannot mint API keys. Organization and project-scoped keys cannot create other keys. Sign in with `neon auth` or pass a personal API key.";
8
8
  function mintedKeyRevokeCommand(key) {
@@ -1,8 +1,8 @@
1
1
  import { detectInstalledAgents, listMcpAgentIds, tryResolveAddMcpAgentId, uniqueAgentIds } from "../mcp/agents.js";
2
2
  import "../init/agents.js";
3
3
  import { delimiter, dirname, join, resolve } from "node:path";
4
- import which from "which";
5
4
  import { detectProjectAgents } from "add-mcp";
5
+ import which from "which";
6
6
  //#region src/plugins/targets.ts
7
7
  const CLAUDE_CODE = { target: "claude-code" };
8
8
  const PLUGINS_TARGET_BY_TYPE = {
@@ -26,8 +26,15 @@ const reserveClosedPort = () => new Promise((resolve, reject) => {
26
26
  const test = test$1.extend({
27
27
  runMockServer: async ({}, use) => {
28
28
  let startedServer;
29
- await use(async (mockDir) => {
29
+ await use(async (mockDir, onRequest) => {
30
30
  const app = express();
31
+ if (onRequest) app.use((req, _res, next) => {
32
+ onRequest({
33
+ method: req.method,
34
+ path: req.path
35
+ });
36
+ next();
37
+ });
31
38
  app.use(express.json());
32
39
  app.use("/", emocks(join(process.cwd(), "mocks", mockDir), { "404": (_req, res) => res.status(404).send({ message: "Not Found" }) }));
33
40
  const server = await new Promise((resolve) => {
@@ -1,8 +1,8 @@
1
1
  import { isCi } from "./env.js";
2
2
  import { log } from "./log.js";
3
- import { detectCliInstallMethod, recommendedCliUpgradeCommand } from "./utils/package_manager.js";
4
3
  import { writeJsonFile } from "./utils/json_file.js";
5
4
  import { showNotice } from "./notices.js";
5
+ import { detectCliInstallMethod, recommendedCliUpgradeCommand } from "./utils/package_manager.js";
6
6
  import { existsSync, readFileSync, realpathSync } from "node:fs";
7
7
  import { basename, dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
@@ -7,8 +7,9 @@ const BRANCHES_LIST_LIMIT = 100;
7
7
  * Branch list pages cap at 100. A name stored in `.neon` can live on a later
8
8
  * page, so resolution has to walk `pagination.next` before treating it as missing.
9
9
  */
10
- const listAllProjectBranches = async (apiClient, projectId) => {
11
- const result = [];
10
+ const listAllProjectBranchesWithAnnotations = async (apiClient, projectId) => {
11
+ const branches = [];
12
+ const annotations = {};
12
13
  let cursor;
13
14
  while (true) {
14
15
  const { data } = await apiClient.listProjectBranches({
@@ -16,16 +17,21 @@ const listAllProjectBranches = async (apiClient, projectId) => {
16
17
  limit: BRANCHES_LIST_LIMIT,
17
18
  cursor
18
19
  });
19
- result.push(...data.branches);
20
+ branches.push(...data.branches);
21
+ Object.assign(annotations, data.annotations);
20
22
  cursor = data.pagination?.next;
21
23
  if (!cursor || data.branches.length === 0) break;
22
24
  }
23
- return result;
25
+ return {
26
+ branches,
27
+ annotations
28
+ };
24
29
  };
25
- const branchIdResolve = async ({ branch, apiClient, projectId }) => {
30
+ const listAllProjectBranches = async (apiClient, projectId) => (await listAllProjectBranchesWithAnnotations(apiClient, projectId)).branches;
31
+ const branchIdResolve = async ({ branch, apiClient, projectId, branches: listed }) => {
26
32
  branch = branch.toString();
27
33
  if (looksLikeBranchId(branch)) return branch;
28
- const branches = await listAllProjectBranches(apiClient, projectId);
34
+ const branches = listed ?? await listAllProjectBranches(apiClient, projectId);
29
35
  const branchData = branches.find((b) => b.name === branch);
30
36
  if (!branchData) throw new Error(`Branch ${branch} not found.\nAvailable branches: ${branches.map((b) => b.name).join(", ")}`);
31
37
  return branchData.id;
@@ -45,6 +51,33 @@ const branchIdFromProps = async (props) => {
45
51
  props.branchId = await getBranchIdFromProps(props);
46
52
  return props.branchId;
47
53
  };
54
+ /**
55
+ * {@link branchIdFromProps}, plus whatever the resolution already fetched: a name or the
56
+ * default branch is resolved from a listing, so the branch object and the listing come back
57
+ * with the id and callers need not fetch them again. An explicit `br-…` id is not listed.
58
+ */
59
+ const resolveBranchFromProps = async (props, known) => {
60
+ const ref = typeof props.branch === "string" ? props.branch : props.id;
61
+ const explicit = ref ? String(ref) : void 0;
62
+ if (explicit !== void 0 && looksLikeBranchId(explicit)) {
63
+ props.branchId = explicit;
64
+ const branch = known?.branches.find((b) => b.id === explicit);
65
+ return branch ? {
66
+ branchId: explicit,
67
+ branch,
68
+ listing: known
69
+ } : { branchId: explicit };
70
+ }
71
+ const listing = known ?? await listAllProjectBranchesWithAnnotations(props.apiClient, props.projectId);
72
+ const branch = explicit === void 0 ? listing.branches.find((b) => b.default) : listing.branches.find((b) => b.name === explicit);
73
+ if (!branch) throw new Error(explicit === void 0 ? "No default branch found" : `Branch ${explicit} not found.\nAvailable branches: ${listing.branches.map((b) => b.name).join(", ")}`);
74
+ props.branchId = branch.id;
75
+ return {
76
+ branchId: branch.id,
77
+ branch,
78
+ listing
79
+ };
80
+ };
48
81
  const resolveBranchRef = async (props) => {
49
82
  const branch = typeof props.branch === "string" ? props.branch : props.id;
50
83
  const branches = await listAllProjectBranches(props.apiClient, props.projectId);
@@ -130,4 +163,4 @@ const fillSingleOrg = async (props) => {
130
163
  };
131
164
  };
132
165
  //#endregion
133
- export { branchIdFromProps, branchIdResolve, fillSingleOrg, fillSingleProject, listAllProjectBranches, resolveBranchRef, resolveSingleDatabase };
166
+ export { branchIdFromProps, branchIdResolve, fillSingleOrg, fillSingleProject, listAllProjectBranches, listAllProjectBranchesWithAnnotations, resolveBranchFromProps, resolveBranchRef, resolveSingleDatabase };
@@ -28,19 +28,19 @@ const parsePITBranch = (input) => {
28
28
  }
29
29
  return result;
30
30
  };
31
- const parsePointInTime = async ({ pointInTime, targetBranchId, projectId, api }) => {
31
+ const parsePointInTime = async ({ pointInTime, targetBranchId, projectId, api, branches, targetBranch }) => {
32
32
  const parsedPIT = parsePITBranch(pointInTime);
33
33
  let branchId = "";
34
34
  if (parsedPIT.branch === "^self") branchId = targetBranchId;
35
35
  else if (parsedPIT.branch === "^parent") {
36
- const { data } = await api.getProjectBranch(projectId, targetBranchId);
37
- const { parent_id: parentId } = data.branch;
36
+ const parentId = (targetBranch?.id === targetBranchId ? targetBranch : (await api.getProjectBranch(projectId, targetBranchId)).data.branch).parent_id;
38
37
  if (parentId == null) throw new PointInTimeParseError("Branch has no parent");
39
38
  branchId = parentId;
40
39
  } else branchId = await branchIdResolve({
41
40
  branch: parsedPIT.branch,
42
41
  projectId,
43
- apiClient: api
42
+ apiClient: api,
43
+ branches
44
44
  });
45
45
  delete parsedPIT.branch;
46
46
  return {
package/dist/writer.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { isCi } from "./env.js";
2
2
  import { toSnakeCase } from "./utils/string.js";
3
- import { formatHumanChunk, resolveOutputWidth } from "./human_table.js";
3
+ import { formatHumanChunk, resolveOutputWidth, stripAnsi } from "./human_table.js";
4
4
  import YAML from "yaml";
5
5
  //#region src/writer.ts
6
6
  const writeYaml = (chunks) => {
@@ -11,17 +11,20 @@ const writeJson = (chunks) => {
11
11
  return json === void 0 ? json : `${json}\n`;
12
12
  };
13
13
  const writeTable = (chunks, out, width) => {
14
+ const noColor = Boolean(process.env.NO_COLOR);
14
15
  chunks.forEach(({ data, config }, i) => {
15
- out.write(formatHumanChunk({
16
+ const title = config.humanTitle ?? config.title;
17
+ const text = formatHumanChunk({
16
18
  data,
17
19
  fields: config.fields,
18
- title: config.title,
20
+ title,
19
21
  emptyMessage: config.emptyMessage,
20
22
  renderColumns: config.renderColumns,
21
23
  width,
22
24
  colorTitle: !isCi(),
23
- leadingBlank: i > 0 && Boolean(config.title)
24
- }));
25
+ leadingBlank: i > 0 && Boolean(title)
26
+ });
27
+ out.write(noColor ? stripAnsi(text) : text);
25
28
  });
26
29
  };
27
30
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "7.0.2",
3
+ "version": "7.0.4",
4
4
  "description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -64,9 +64,9 @@
64
64
  "yaml": "^2.9.0",
65
65
  "yargs": "17.7.2",
66
66
  "yoctocolors": "^2.1.2",
67
+ "@neon/config-runtime": "1.7.3",
67
68
  "@neon/config": "1.8.3",
68
- "@neon/sdk": "7.0.0",
69
- "@neon/config-runtime": "1.7.3"
69
+ "@neon/sdk": "7.0.0"
70
70
  },
71
71
  "optionalDependencies": {
72
72
  "@napi-rs/keyring": "1.3.0",
@@ -134,6 +134,9 @@
134
134
  "test": "pnpm --filter neonctl... build && vitest run",
135
135
  "test:ci": "pnpm build && vitest run",
136
136
  "test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
137
+ "test:perf": "pnpm --filter neon... build && vitest run src/perf_budget.test.ts",
138
+ "perf": "tsx scripts/perf/perf.ts",
139
+ "perf:ci-report": "tsx scripts/perf/ci-report.ts",
137
140
  "test:conformance": "vitest run --config tests/psql-conformance/vitest.config.ts"
138
141
  }
139
142
  }