neonctl 2.27.1 → 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 (136) hide show
  1. package/README.md +35 -3
  2. package/dist/analytics.js +52 -34
  3. package/dist/api.js +643 -13
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +8 -1
  6. package/dist/commands/auth.js +64 -51
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +160 -150
  9. package/dist/commands/bucket.js +183 -146
  10. package/dist/commands/checkout.js +51 -51
  11. package/dist/commands/config.js +228 -82
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +100 -101
  14. package/dist/commands/databases.js +29 -26
  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 +101 -104
  19. package/dist/commands/index.js +27 -25
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +232 -182
  23. package/dist/commands/neon_auth.js +385 -370
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +103 -101
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +27 -24
  29. package/dist/commands/schema_diff.js +25 -26
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +40 -0
  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 +37 -14
  37. package/dist/current_branch_fast_path.js +55 -0
  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 +68 -5
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +110 -107
  48. package/dist/log.js +2 -2
  49. package/dist/parameters.gen.js +14 -14
  50. package/dist/pkg.js +5 -5
  51. package/dist/psql/cli.js +4 -2
  52. package/dist/psql/command/cmd_cond.js +61 -61
  53. package/dist/psql/command/cmd_connect.js +159 -154
  54. package/dist/psql/command/cmd_copy.js +107 -97
  55. package/dist/psql/command/cmd_describe.js +368 -363
  56. package/dist/psql/command/cmd_format.js +276 -263
  57. package/dist/psql/command/cmd_io.js +269 -263
  58. package/dist/psql/command/cmd_lo.js +74 -66
  59. package/dist/psql/command/cmd_meta.js +148 -148
  60. package/dist/psql/command/cmd_misc.js +17 -17
  61. package/dist/psql/command/cmd_pipeline.js +142 -135
  62. package/dist/psql/command/cmd_restrict.js +25 -25
  63. package/dist/psql/command/cmd_show.js +183 -168
  64. package/dist/psql/command/dispatch.js +26 -26
  65. package/dist/psql/command/shared.js +14 -14
  66. package/dist/psql/complete/filenames.js +16 -16
  67. package/dist/psql/complete/index.js +4 -4
  68. package/dist/psql/complete/matcher.js +33 -32
  69. package/dist/psql/complete/psqlVars.js +173 -173
  70. package/dist/psql/complete/queries.js +5 -3
  71. package/dist/psql/complete/rules.js +900 -863
  72. package/dist/psql/core/common.js +136 -133
  73. package/dist/psql/core/help.js +343 -343
  74. package/dist/psql/core/mainloop.js +160 -153
  75. package/dist/psql/core/prompt.js +126 -123
  76. package/dist/psql/core/settings.js +111 -111
  77. package/dist/psql/core/sqlHelp.js +150 -150
  78. package/dist/psql/core/startup.js +211 -205
  79. package/dist/psql/core/syncVars.js +14 -14
  80. package/dist/psql/core/variables.js +24 -24
  81. package/dist/psql/describe/formatters.js +302 -289
  82. package/dist/psql/describe/processNamePattern.js +28 -28
  83. package/dist/psql/describe/queries.js +656 -651
  84. package/dist/psql/index.js +436 -411
  85. package/dist/psql/io/history.js +36 -36
  86. package/dist/psql/io/input.js +15 -15
  87. package/dist/psql/io/lineEditor/buffer.js +27 -25
  88. package/dist/psql/io/lineEditor/complete.js +15 -15
  89. package/dist/psql/io/lineEditor/filename.js +22 -22
  90. package/dist/psql/io/lineEditor/index.js +65 -62
  91. package/dist/psql/io/lineEditor/keymap.js +325 -318
  92. package/dist/psql/io/lineEditor/vt100.js +60 -60
  93. package/dist/psql/io/pgpass.js +18 -18
  94. package/dist/psql/io/pgservice.js +14 -14
  95. package/dist/psql/io/psqlrc.js +46 -46
  96. package/dist/psql/print/aligned.js +175 -166
  97. package/dist/psql/print/asciidoc.js +51 -51
  98. package/dist/psql/print/crosstab.js +34 -31
  99. package/dist/psql/print/csv.js +25 -22
  100. package/dist/psql/print/html.js +54 -54
  101. package/dist/psql/print/json.js +12 -12
  102. package/dist/psql/print/latex.js +118 -118
  103. package/dist/psql/print/pager.js +28 -26
  104. package/dist/psql/print/troff.js +48 -48
  105. package/dist/psql/print/unaligned.js +15 -14
  106. package/dist/psql/print/units.js +17 -17
  107. package/dist/psql/scanner/slash.js +48 -46
  108. package/dist/psql/scanner/sql.js +88 -84
  109. package/dist/psql/scanner/stringutils.js +21 -17
  110. package/dist/psql/types/index.js +7 -7
  111. package/dist/psql/types/scanner.js +8 -8
  112. package/dist/psql/wire/connection.js +341 -327
  113. package/dist/psql/wire/copy.js +7 -7
  114. package/dist/psql/wire/pipeline.js +26 -24
  115. package/dist/psql/wire/protocol.js +102 -102
  116. package/dist/psql/wire/sasl.js +62 -62
  117. package/dist/psql/wire/tls.js +79 -73
  118. package/dist/storage_api.js +22 -23
  119. package/dist/test_utils/fixtures.js +74 -41
  120. package/dist/test_utils/oauth_server.js +5 -5
  121. package/dist/utils/api_enums.js +33 -0
  122. package/dist/utils/branch_notice.js +5 -5
  123. package/dist/utils/branch_picker.js +26 -26
  124. package/dist/utils/compute_units.js +4 -4
  125. package/dist/utils/enrichers.js +28 -16
  126. package/dist/utils/esbuild.js +28 -28
  127. package/dist/utils/formats.js +1 -1
  128. package/dist/utils/middlewares.js +3 -3
  129. package/dist/utils/package_manager.js +68 -0
  130. package/dist/utils/point_in_time.js +12 -12
  131. package/dist/utils/psql.js +30 -30
  132. package/dist/utils/string.js +2 -2
  133. package/dist/utils/ui.js +9 -9
  134. package/dist/utils/zip.js +1 -1
  135. package/dist/writer.js +17 -17
  136. package/package.json +10 -12
@@ -1,39 +1,39 @@
1
- import chalk from 'chalk';
2
- import { NEON_ENV_VAR_KEYS } from '@neondatabase/env';
3
- import { existsSync } from 'node:fs';
4
- import { log } from '../log.js';
5
- import { resolveNeonEnvVars } from '../dev/env.js';
6
- import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from '../env_file.js';
7
- import { fillSingleProject, resolveBranchRef } from '../utils/enrichers.js';
8
- import { announceTargetBranch } from '../utils/branch_notice.js';
9
- export const command = 'env';
1
+ import { existsSync } from "node:fs";
2
+ import { NEON_ENV_VAR_KEYS } from "@neon/env";
3
+ import chalk from "chalk";
4
+ import { resolveNeonEnvVars } from "../dev/env.js";
5
+ import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
+ import { log } from "../log.js";
7
+ import { announceTargetBranch } from "../utils/branch_notice.js";
8
+ import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
9
+ export const command = "env";
10
10
  export const describe = "Manage a branch's Neon env variables locally";
11
11
  /**
12
12
  * Shown (to stderr) when `link` / `checkout` skip the bundled env pull because the user passed
13
13
  * `--no-env-pull`. Names the two ways to get the branch's vars without an on-disk file written
14
14
  * eagerly: an explicit `neonctl env pull`, or runtime injection via `neon-env run`.
15
15
  */
16
- export const ENV_PULL_SKIPPED_HINT = 'Skipped env pull (--no-env-pull). Run `neonctl env pull` to write this branch’s env vars ' +
17
- '(DATABASE_URL, …) into a local .env, or inject them at runtime with `neon-env run -- <your dev command>`.';
16
+ export const ENV_PULL_SKIPPED_HINT = "Skipped env pull (--no-env-pull). Run `neonctl env pull` to write this branch’s env vars " +
17
+ "(DATABASE_URL, …) into a local .env, or inject them at runtime with `neon-env run -- <your dev command>`.";
18
18
  export const builder = (argv) => argv
19
- .usage('$0 env <sub-command> [options]')
19
+ .usage("$0 env <sub-command> [options]")
20
20
  .options({
21
- 'project-id': { describe: 'Project ID', type: 'string' },
22
- branch: { describe: 'Branch ID or name', type: 'string' },
21
+ "project-id": { describe: "Project ID", type: "string" },
22
+ branch: { describe: "Branch ID or name", type: "string" },
23
23
  })
24
24
  .middleware(fillSingleProject)
25
- .command('pull', "Write the branch's Neon env variables to a local .env file", (yargs) => yargs
26
- .usage('$0 env pull [options]')
25
+ .command("pull", "Write the branch's Neon env variables to a local .env file", (yargs) => yargs
26
+ .usage("$0 env pull [options]")
27
27
  .options({
28
28
  file: {
29
- describe: 'Target .env file to write. Defaults to an existing .env, ' +
30
- 'otherwise .env.local. Only Neon variables are updated; other ' +
31
- 'lines are preserved.',
32
- type: 'string',
29
+ describe: "Target .env file to write. Defaults to an existing .env, " +
30
+ "otherwise .env.local. Only Neon variables are updated; other " +
31
+ "lines are preserved.",
32
+ type: "string",
33
33
  },
34
34
  })
35
- .example('$0 env pull', "Write the linked branch's Neon vars into .env.local (or .env if present)")
36
- .example('$0 env pull --branch preview --file .env.preview', 'Pull a specific branch into a specific file'), async (args) => {
35
+ .example("$0 env pull", "Write the linked branch's Neon vars into .env.local (or .env if present)")
36
+ .example("$0 env pull --branch preview --file .env.preview", "Pull a specific branch into a specific file"), async (args) => {
37
37
  // Explicit `env pull` announces the branch it's reading from up front so the user
38
38
  // can catch "pulled env from the wrong branch" before it overwrites their .env. The
39
39
  // bundled auto-pull (link / checkout / apply) stays quiet — those already report the
@@ -42,7 +42,7 @@ export const builder = (argv) => argv
42
42
  })
43
43
  .demandCommand(1);
44
44
  export const handler = (args) => args;
45
- /** Every OS-level env var name `@neondatabase/env` can emit, used only for reporting. */
45
+ /** Every OS-level env var name `@neon/env` can emit, used only for reporting. */
46
46
  const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Object.values(group));
47
47
  /**
48
48
  * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
@@ -68,7 +68,7 @@ export const pull = async (props, opts = {}) => {
68
68
  const cwd = props.cwd ?? process.cwd();
69
69
  const branch = await resolveBranchRef(props);
70
70
  if (opts.announce) {
71
- announceTargetBranch(props, branch, 'Pulling env from branch');
71
+ announceTargetBranch(props, branch, "Pulling env from branch");
72
72
  }
73
73
  const branchId = branch.branchId;
74
74
  // Resolve the target file first and layer its current contents under the resolver's env
@@ -91,9 +91,9 @@ export const pull = async (props, opts = {}) => {
91
91
  });
92
92
  const neonVars = pickNeonVars(vars);
93
93
  if (Object.keys(neonVars).length === 0) {
94
- log.info('No Neon env variables to pull for this branch (no DATABASE_URL or ' +
95
- 'enabled Auth / Data API).');
96
- return { status: 'empty' };
94
+ log.info("No Neon env variables to pull for this branch (no DATABASE_URL or " +
95
+ "enabled Auth / Data API).");
96
+ return { status: "empty" };
97
97
  }
98
98
  // Reconcile rather than blindly merge: write the branch's current Neon vars and prune any
99
99
  // Neon-owned vars the branch no longer has (e.g. NEON_AUTH_* / NEON_DATA_API_* carried over
@@ -101,11 +101,11 @@ export const pull = async (props, opts = {}) => {
101
101
  const { written, removed } = mergeEnvFile(targetPath, neonVars, {
102
102
  managedKeys: NEON_OWNED_ENV_KEYS,
103
103
  });
104
- log.info('Pulled %d Neon variable%s into %s: %s', written.length, written.length === 1 ? '' : 's', targetPath, written.join(', '));
104
+ log.info("Pulled %d Neon variable%s into %s: %s", written.length, written.length === 1 ? "" : "s", targetPath, written.join(", "));
105
105
  if (removed.length > 0) {
106
- log.info('Removed %d stale Neon variable%s not enabled on this branch: %s', removed.length, removed.length === 1 ? '' : 's', removed.join(', '));
106
+ log.info("Removed %d stale Neon variable%s not enabled on this branch: %s", removed.length, removed.length === 1 ? "" : "s", removed.join(", "));
107
107
  }
108
- return { status: 'written', written, file: targetPath };
108
+ return { status: "written", written, file: targetPath };
109
109
  };
110
110
  /**
111
111
  * Pull a freshly-pinned branch's Neon env vars into a local `.env`, bundled into `link` and
@@ -120,17 +120,17 @@ export const pull = async (props, opts = {}) => {
120
120
  export const autoPullEnvAfterPin = async (props) => {
121
121
  if (!props.envPull) {
122
122
  log.info(chalk.dim(ENV_PULL_SKIPPED_HINT));
123
- return { status: 'skipped' };
123
+ return { status: "skipped" };
124
124
  }
125
125
  try {
126
126
  return await pull(props);
127
127
  }
128
128
  catch (err) {
129
129
  const message = err instanceof Error ? err.message : String(err);
130
- log.warning('Branch pinned, but pulling its Neon env vars failed: %s\n' +
131
- 'Run `neonctl env pull` once resolved (e.g. `neonctl deploy` if a declared service ' +
132
- 'is missing), or inject them at runtime with `neon-env run -- <your dev command>`.', message);
133
- return { status: 'failed', message };
130
+ log.warning("Branch pinned, but pulling its Neon env vars failed: %s\n" +
131
+ "Run `neonctl env pull` once resolved (e.g. `neonctl deploy` if a declared service " +
132
+ "is missing), or inject them at runtime with `neon-env run -- <your dev command>`.", message);
133
+ return { status: "failed", message };
134
134
  }
135
135
  };
136
136
  /**
@@ -139,14 +139,14 @@ export const autoPullEnvAfterPin = async (props) => {
139
139
  */
140
140
  export const renderAgentPullNote = (result) => {
141
141
  switch (result.status) {
142
- case 'written':
143
- return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? '' : 's'} into ${result.file}.`;
144
- case 'empty':
145
- return ' No Neon env vars to pull for this branch yet.';
146
- case 'skipped':
147
- return (' Skipped env pull (--no-env-pull); run `neonctl env pull` later, ' +
148
- 'or inject env at runtime with `neon-env run -- <your dev command>`.');
149
- case 'failed':
142
+ case "written":
143
+ return ` Pulled ${result.written.length} Neon env var${result.written.length === 1 ? "" : "s"} into ${result.file}.`;
144
+ case "empty":
145
+ return " No Neon env vars to pull for this branch yet.";
146
+ case "skipped":
147
+ return (" Skipped env pull (--no-env-pull); run `neonctl env pull` later, " +
148
+ "or inject env at runtime with `neon-env run -- <your dev command>`.");
149
+ case "failed":
150
150
  return ` Could not pull env vars (${result.message}); run \`neonctl env pull\` once resolved.`;
151
151
  }
152
152
  };
@@ -1,134 +1,133 @@
1
- import { existsSync, statSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { isAxiosError } from 'axios';
4
- import { retryOnLock } from '../api.js';
5
- import { log } from '../log.js';
6
- import { branchIdFromProps, fillSingleProject } from '../utils/enrichers.js';
7
- import { zipBundle } from '../utils/zip.js';
8
- import { bundleEntry } from '../utils/esbuild.js';
9
- import { writer } from '../writer.js';
10
- import { createDeployment, deleteFunction, getFunction, listFunctions, } from '../functions_api.js';
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { isNeonApiError, retryOnLock } from "../api.js";
4
+ import { createDeployment, deleteFunction, getFunction, listFunctions, } from "../functions_api.js";
5
+ import { log } from "../log.js";
6
+ import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
7
+ import { bundleEntry } from "../utils/esbuild.js";
8
+ import { zipBundle } from "../utils/zip.js";
9
+ import { writer } from "../writer.js";
11
10
  const FUNCTION_FIELDS = [
12
- 'slug',
13
- 'name',
14
- 'invocation_url',
15
- 'created_at',
11
+ "slug",
12
+ "name",
13
+ "invocation_url",
14
+ "created_at",
16
15
  ];
17
16
  const FUNCTIONS_LIST_LIMIT = 100;
18
17
  // Table columns for `functions list`. `status` is a derived field (the
19
18
  // table writer reads flat fields only): the current deployment's status.
20
19
  const LIST_TABLE_FIELDS = [
21
- 'slug',
22
- 'name',
23
- 'status',
24
- 'invocation_url',
25
- 'created_at',
20
+ "slug",
21
+ "name",
22
+ "status",
23
+ "invocation_url",
24
+ "created_at",
26
25
  ];
27
26
  const DEPLOYMENT_FIELDS = [
28
- 'id',
29
- 'status',
30
- 'runtime',
31
- 'memory_mib',
32
- 'created_at',
27
+ "id",
28
+ "status",
29
+ "runtime",
30
+ "memory_mib",
31
+ "created_at",
33
32
  ];
34
33
  // Deploy emits the resolved deployment plus the function's invocation_url, so a
35
34
  // successful `functions deploy` tells the user exactly where to call the function.
36
35
  const DEPLOY_RESULT_FIELDS = [
37
- 'id',
38
- 'status',
39
- 'invocation_url',
40
- 'runtime',
41
- 'memory_mib',
42
- 'created_at',
36
+ "id",
37
+ "status",
38
+ "invocation_url",
39
+ "runtime",
40
+ "memory_mib",
41
+ "created_at",
43
42
  ];
44
43
  // In table mode a failed build's reason gets its own "deployment error"
45
44
  // section after the deployment table; json/yaml carry the raw `error` field.
46
45
  const writeDeploymentErrorSection = (out, dep) => {
47
- if (dep.status === 'failed' && dep.error) {
48
- out.write({ reason: dep.error }, { fields: ['reason'], title: 'deployment error' });
46
+ if (dep.status === "failed" && dep.error) {
47
+ out.write({ reason: dep.error }, { fields: ["reason"], title: "deployment error" });
49
48
  }
50
49
  };
51
50
  const SLUG_PATTERN = /^[a-z0-9]{1,20}$/;
52
- const SLUG_HELP = 'Use 1-20 lowercase letters and digits (no hyphens or other characters).';
51
+ const SLUG_HELP = "Use 1-20 lowercase letters and digits (no hyphens or other characters).";
53
52
  // Entry-point discovery order inside --src.
54
- const ENTRY_CANDIDATES = ['index.ts', 'index.mjs', 'index.js'];
53
+ const ENTRY_CANDIDATES = ["index.ts", "index.mjs", "index.js"];
55
54
  // Overridable so tests can poll fast; defaults to 2s in real use.
56
55
  const POLL_INTERVAL_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || 2000;
57
56
  // Upper bound on --wait polling so the CLI never hangs (e.g. if our deployment
58
57
  // never shows up as current_deployment). Overridable so tests can time out fast;
59
58
  // defaults to 10 minutes in real use.
60
59
  const POLL_TIMEOUT_MS = Number(process.env.NEON_FUNCTIONS_POLL_TIMEOUT_MS) || 600000;
61
- export const command = 'functions';
62
- export const describe = 'Manage Neon Functions';
63
- export const aliases = ['function'];
60
+ export const command = "functions";
61
+ export const describe = "Manage Neon Functions";
62
+ export const aliases = ["function"];
64
63
  export const builder = (argv) => argv
65
- .usage('$0 function <sub-command> [options]')
64
+ .usage("$0 function <sub-command> [options]")
66
65
  .options({
67
- 'project-id': {
68
- describe: 'Project ID',
69
- type: 'string',
66
+ "project-id": {
67
+ describe: "Project ID",
68
+ type: "string",
70
69
  },
71
70
  branch: {
72
- describe: 'Branch ID or name',
73
- type: 'string',
71
+ describe: "Branch ID or name",
72
+ type: "string",
74
73
  },
75
74
  })
76
75
  .middleware(fillSingleProject)
77
- .command('deploy <slug>', 'Deploy a function from a local directory', (yargs) => yargs
78
- .positional('slug', {
79
- describe: 'Function slug (1-20 lowercase letters and digits)',
80
- type: 'string',
76
+ .command("deploy <slug>", "Deploy a function from a local directory", (yargs) => yargs
77
+ .positional("slug", {
78
+ describe: "Function slug (1-20 lowercase letters and digits)",
79
+ type: "string",
81
80
  demandOption: true,
82
81
  })
83
82
  .options({
84
83
  src: {
85
- describe: 'Function source: a directory containing index.ts, index.mjs, or index.js, or a path to the entry file',
86
- type: 'string',
84
+ describe: "Function source: a directory containing index.ts, index.mjs, or index.js, or a path to the entry file",
85
+ type: "string",
87
86
  },
88
87
  // Removed flags, kept hidden so old invocations fail loudly instead
89
88
  // of being silently ignored (the CLI has no .strictOptions()).
90
89
  path: {
91
- type: 'string',
90
+ type: "string",
92
91
  hidden: true,
93
92
  },
94
93
  entry: {
95
- type: 'string',
94
+ type: "string",
96
95
  hidden: true,
97
96
  },
98
97
  runtime: {
99
- describe: 'Function runtime',
100
- type: 'string',
101
- choices: ['nodejs24'],
98
+ describe: "Function runtime",
99
+ type: "string",
100
+ choices: ["nodejs24"],
102
101
  },
103
102
  env: {
104
- describe: 'Environment variable as KEY=VALUE (repeatable)',
105
- type: 'string',
103
+ describe: "Environment variable as KEY=VALUE (repeatable)",
104
+ type: "string",
106
105
  array: true,
107
106
  },
108
107
  wait: {
109
- describe: 'Wait for the deployment to finish building',
110
- type: 'boolean',
108
+ describe: "Wait for the deployment to finish building",
109
+ type: "boolean",
111
110
  default: true,
112
111
  },
113
112
  }), (args) => deploy(args))
114
- .command('list', 'List functions on the branch', (yargs) => yargs, (args) => list(args))
115
- .command('get <slug>', "Show a function's details", (yargs) => yargs
116
- .positional('slug', {
117
- describe: 'Function slug',
118
- type: 'string',
113
+ .command("list", "List functions on the branch", (yargs) => yargs, (args) => list(args))
114
+ .command("get <slug>", "Show a function's details", (yargs) => yargs
115
+ .positional("slug", {
116
+ describe: "Function slug",
117
+ type: "string",
119
118
  demandOption: true,
120
119
  })
121
120
  .options({
122
- 'list-env-variables': {
123
- describe: 'List the environment variable names of the active deployment',
124
- type: 'boolean',
125
- alias: 'E',
121
+ "list-env-variables": {
122
+ describe: "List the environment variable names of the active deployment",
123
+ type: "boolean",
124
+ alias: "E",
126
125
  default: false,
127
126
  },
128
127
  }), (args) => get(args))
129
- .command('delete <slug>', 'Delete a function on the branch', (yargs) => yargs.positional('slug', {
130
- describe: 'Function slug',
131
- type: 'string',
128
+ .command("delete <slug>", "Delete a function on the branch", (yargs) => yargs.positional("slug", {
129
+ describe: "Function slug",
130
+ type: "string",
132
131
  demandOption: true,
133
132
  }), (args) => deleteFn(args));
134
133
  export const handler = (args) => {
@@ -139,7 +138,7 @@ const parseEnv = (entries) => {
139
138
  return undefined;
140
139
  const map = {};
141
140
  for (const entry of entries) {
142
- const eq = entry.indexOf('=');
141
+ const eq = entry.indexOf("=");
143
142
  if (eq <= 0) {
144
143
  throw new Error(`Invalid --env value "${entry}". Expected KEY=VALUE.`);
145
144
  }
@@ -152,21 +151,19 @@ const statusHint = (slug, projectId, branchId) => `Check status with: neonctl fu
152
151
  // deploy output shows where the function is reachable (not just the deployment id).
153
152
  const emitDeployResult = (props, deployment, fn) => {
154
153
  const out = writer(props).write({ ...deployment, invocation_url: fn?.invocation_url }, { fields: DEPLOY_RESULT_FIELDS });
155
- if (props.output !== 'json' && props.output !== 'yaml') {
154
+ if (props.output !== "json" && props.output !== "yaml") {
156
155
  writeDeploymentErrorSection(out, deployment);
157
156
  }
158
157
  out.end();
159
158
  };
160
159
  // A poll error worth retrying: a network error (no HTTP response), a 5xx, or a
161
160
  // 404 from eventual consistency. Anything else (e.g. 401/403) is surfaced.
162
- const isTransient = (err) => isAxiosError(err) &&
163
- (err.response === undefined ||
164
- err.response.status === 404 ||
165
- err.response.status >= 500);
161
+ const isTransient = (err) => isNeonApiError(err) &&
162
+ (err.status === undefined || err.status === 404 || err.status >= 500);
166
163
  const deploy = async (props) => {
167
164
  if (props.path !== undefined || props.entry !== undefined) {
168
- throw new Error('--path and --entry were removed. Use --src <dir>; the entry point ' +
169
- 'is discovered as index.ts, index.mjs, or index.js in that directory.');
165
+ throw new Error("--path and --entry were removed. Use --src <dir>; the entry point " +
166
+ "is discovered as index.ts, index.mjs, or index.js in that directory.");
170
167
  }
171
168
  // At least one deploy option must be passed (--wait is excluded: it controls
172
169
  // output, not what gets deployed).
@@ -174,15 +171,15 @@ const deploy = async (props) => {
174
171
  props.env !== undefined ||
175
172
  props.runtime !== undefined;
176
173
  if (!hasOption) {
177
- throw new Error('Provide at least one option to deploy, e.g. --src or --env. ' +
178
- 'See: neonctl function deploy --help.');
174
+ throw new Error("Provide at least one option to deploy, e.g. --src or --env. " +
175
+ "See: neonctl function deploy --help.");
179
176
  }
180
177
  // Cheap, offline validation first - fail before any network round-trip.
181
178
  if (!SLUG_PATTERN.test(props.slug)) {
182
179
  throw new Error(`Invalid function slug "${props.slug}". ${SLUG_HELP}`);
183
180
  }
184
- const src = props.src ?? '.';
185
- const runtime = props.runtime ?? 'nodejs24';
181
+ const src = props.src ?? ".";
182
+ const runtime = props.runtime ?? "nodejs24";
186
183
  const environment = parseEnv(props.env);
187
184
  const srcStat = statSync(src, { throwIfNoEntry: false });
188
185
  if (srcStat === undefined) {
@@ -193,7 +190,7 @@ const deploy = async (props) => {
193
190
  ? src
194
191
  : ENTRY_CANDIDATES.map((name) => join(src, name)).find((p) => existsSync(p));
195
192
  if (source === undefined) {
196
- throw new Error(`No entry file found in ${src}. Expected one of: ${ENTRY_CANDIDATES.join(', ')}.`);
193
+ throw new Error(`No entry file found in ${src}. Expected one of: ${ENTRY_CANDIDATES.join(", ")}.`);
197
194
  }
198
195
  // Bundle before any network round-trip so a bundling failure fails fast.
199
196
  const zip = zipBundle(await bundleEntry(source));
@@ -206,7 +203,7 @@ const deploy = async (props) => {
206
203
  before = fn.current_deployment?.id;
207
204
  }
208
205
  catch (err) {
209
- if (!(isAxiosError(err) && err.response?.status === 404))
206
+ if (!(isNeonApiError(err) && err.status === 404))
210
207
  throw err;
211
208
  }
212
209
  await retryOnLock(() => createDeployment(props.apiClient, props.projectId, branchId, props.slug, {
@@ -221,8 +218,8 @@ const deploy = async (props) => {
221
218
  const onSignal = () => {
222
219
  interrupted = true;
223
220
  };
224
- process.once('SIGINT', onSignal);
225
- process.once('SIGTERM', onSignal);
221
+ process.once("SIGINT", onSignal);
222
+ process.once("SIGTERM", onSignal);
226
223
  // Poll until a NEW version appears (id greater than the snapshot, or
227
224
  // any version if there was none). --no-wait stops there; --wait stops at a
228
225
  // terminal status. Bounded by POLL_TIMEOUT_MS so it never hangs.
@@ -254,14 +251,14 @@ const deploy = async (props) => {
254
251
  resolvedFn = fn;
255
252
  if (!props.wait)
256
253
  break;
257
- if (dep.status === 'completed' || dep.status === 'failed')
254
+ if (dep.status === "completed" || dep.status === "failed")
258
255
  break;
259
256
  }
260
257
  }
261
258
  }
262
259
  finally {
263
- process.removeListener('SIGINT', onSignal);
264
- process.removeListener('SIGTERM', onSignal);
260
+ process.removeListener("SIGINT", onSignal);
261
+ process.removeListener("SIGTERM", onSignal);
265
262
  }
266
263
  if (interrupted) {
267
264
  log.info(statusHint(props.slug, props.projectId, branchId));
@@ -278,11 +275,11 @@ const deploy = async (props) => {
278
275
  log.info(statusHint(props.slug, props.projectId, branchId));
279
276
  return;
280
277
  }
281
- if (resolved.status === 'completed') {
278
+ if (resolved.status === "completed") {
282
279
  log.info(`Function deployment ${props.slug}/${resolved.id} completed.`);
283
280
  return;
284
281
  }
285
- if (resolved.status === 'failed') {
282
+ if (resolved.status === "failed") {
286
283
  throw new Error(`Function deployment ${props.slug}/${resolved.id} failed.`);
287
284
  }
288
285
  // --wait, new version appeared but the deadline hit before it finished.
@@ -292,20 +289,20 @@ const deploy = async (props) => {
292
289
  const get = async (props) => {
293
290
  const branchId = await branchIdFromProps(props);
294
291
  const fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
295
- if (props.output === 'json' || props.output === 'yaml') {
292
+ if (props.output === "json" || props.output === "yaml") {
296
293
  writer(props).end(fn, { fields: FUNCTION_FIELDS });
297
294
  return;
298
295
  }
299
296
  const out = writer(props).write(fn, {
300
297
  fields: FUNCTION_FIELDS,
301
- title: 'function',
298
+ title: "function",
302
299
  });
303
300
  const current = fn.current_deployment;
304
301
  const active = fn.active_deployment;
305
302
  if (current && active && current.id === active.id) {
306
303
  out.write(current, {
307
304
  fields: DEPLOYMENT_FIELDS,
308
- title: 'deployment (current, active)',
305
+ title: "deployment (current, active)",
309
306
  });
310
307
  writeDeploymentErrorSection(out, current);
311
308
  }
@@ -313,7 +310,7 @@ const get = async (props) => {
313
310
  if (current) {
314
311
  out.write(current, {
315
312
  fields: DEPLOYMENT_FIELDS,
316
- title: 'current deployment',
313
+ title: "current deployment",
317
314
  });
318
315
  // The failure reason is shown only for the current deployment;
319
316
  // the active one completed successfully by definition.
@@ -322,15 +319,15 @@ const get = async (props) => {
322
319
  if (active) {
323
320
  out.write(active, {
324
321
  fields: DEPLOYMENT_FIELDS,
325
- title: 'active deployment',
322
+ title: "active deployment",
326
323
  });
327
324
  }
328
325
  }
329
326
  if (props.listEnvVariables) {
330
327
  out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
331
- fields: ['name'],
332
- title: 'environment',
333
- emptyMessage: 'No environment variables on the active deployment.',
328
+ fields: ["name"],
329
+ title: "environment",
330
+ emptyMessage: "No environment variables on the active deployment.",
334
331
  });
335
332
  }
336
333
  out.end();
@@ -341,7 +338,7 @@ const deleteFn = async (props) => {
341
338
  await retryOnLock(() => deleteFunction(props.apiClient, props.projectId, branchId, props.slug));
342
339
  }
343
340
  catch (err) {
344
- if (isAxiosError(err) && err.response?.status === 404) {
341
+ if (isNeonApiError(err) && err.status === 404) {
345
342
  throw new Error(`Function "${props.slug}" not found on branch ${branchId}.`);
346
343
  }
347
344
  throw err;
@@ -355,22 +352,22 @@ const list = async (props) => {
355
352
  for (;;) {
356
353
  const page = await listFunctions(props.apiClient, props.projectId, branchId, { cursor, limit: FUNCTIONS_LIST_LIMIT });
357
354
  functions.push(...page.functions);
358
- log.debug('Got %d functions, next cursor: %s', page.functions.length, page.next);
355
+ log.debug("Got %d functions, next cursor: %s", page.functions.length, page.next);
359
356
  // A server echoing the same cursor would loop forever; treat it as
360
357
  // the end of the list.
361
358
  if (!page.next || page.next === cursor)
362
359
  break;
363
360
  cursor = page.next;
364
361
  }
365
- if (props.output === 'json' || props.output === 'yaml') {
362
+ if (props.output === "json" || props.output === "yaml") {
366
363
  writer(props).end(functions, { fields: FUNCTION_FIELDS });
367
364
  return;
368
365
  }
369
366
  writer(props).end(functions.map((fn) => ({
370
367
  ...fn,
371
- status: fn.current_deployment?.status ?? '',
368
+ status: fn.current_deployment?.status ?? "",
372
369
  })), {
373
370
  fields: LIST_TABLE_FIELDS,
374
- emptyMessage: 'No functions found on this branch.',
371
+ emptyMessage: "No functions found on this branch.",
375
372
  });
376
373
  };
@@ -1,28 +1,29 @@
1
- import * as auth from './auth.js';
2
- import * as projects from './projects.js';
3
- import * as ipAllow from './ip_allow.js';
4
- import * as vpcEndpoints from './vpc_endpoints.js';
5
- import * as users from './user.js';
6
- import * as orgs from './orgs.js';
7
- import * as branches from './branches.js';
8
- import * as databases from './databases.js';
9
- import * as roles from './roles.js';
10
- import * as operations from './operations.js';
11
- import * as cs from './connection_string.js';
12
- import * as psql from './psql.js';
13
- import * as setContext from './set_context.js';
14
- import * as checkout from './checkout.js';
15
- import * as link from './link.js';
16
- import * as init from './init.js';
17
- import * as dataApi from './data_api.js';
18
- import * as neonAuth from './neon_auth.js';
19
- import * as functions from './functions.js';
20
- import * as dev from './dev.js';
21
- import * as config from './config.js';
22
- import * as deploy from './deploy.js';
23
- import * as env from './env.js';
24
- import * as bucket from './bucket.js';
25
- import * as bootstrap from './bootstrap.js';
1
+ import * as auth from "./auth.js";
2
+ import * as bootstrap from "./bootstrap.js";
3
+ import * as branches from "./branches.js";
4
+ import * as bucket from "./bucket.js";
5
+ import * as checkout from "./checkout.js";
6
+ import * as config from "./config.js";
7
+ import * as cs from "./connection_string.js";
8
+ import * as dataApi from "./data_api.js";
9
+ import * as databases from "./databases.js";
10
+ import * as deploy from "./deploy.js";
11
+ import * as dev from "./dev.js";
12
+ import * as env from "./env.js";
13
+ import * as functions from "./functions.js";
14
+ import * as init from "./init.js";
15
+ import * as ipAllow from "./ip_allow.js";
16
+ import * as link from "./link.js";
17
+ import * as neonAuth from "./neon_auth.js";
18
+ import * as operations from "./operations.js";
19
+ import * as orgs from "./orgs.js";
20
+ import * as projects from "./projects.js";
21
+ import * as psql from "./psql.js";
22
+ import * as roles from "./roles.js";
23
+ import * as setContext from "./set_context.js";
24
+ import * as status from "./status.js";
25
+ import * as users from "./user.js";
26
+ import * as vpcEndpoints from "./vpc_endpoints.js";
26
27
  export default [
27
28
  auth,
28
29
  users,
@@ -45,6 +46,7 @@ export default [
45
46
  functions,
46
47
  dev,
47
48
  config,
49
+ status,
48
50
  deploy,
49
51
  env,
50
52
  bucket,