neonctl 2.26.2 → 2.26.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.
package/README.md CHANGED
@@ -455,7 +455,7 @@ The target directory must be empty unless you pass `--force` (a lone `.git` is i
455
455
  | [me](https://neon.com/docs/reference/cli-me) | | Show current user |
456
456
  | [branches](https://neon.com/docs/reference/cli-branches) | `list`, `create`, `rename`, `add-compute`, `set-default`, `set-expiration`, `delete`, `get` | Manage branches |
457
457
  | [databases](https://neon.com/docs/reference/cli-databases) | `list`, `create`, `delete` | Manage databases |
458
- | functions | `deploy`, `list`, `get`, `delete` | Manage Neon Functions |
458
+ | function | `deploy`, `list`, `get`, `delete` | Manage Neon Functions |
459
459
  | [roles](https://neon.com/docs/reference/cli-roles) | `list`, `create`, `delete` | Manage roles |
460
460
  | [operations](https://neon.com/docs/reference/cli-operations) | `list` | Manage operations |
461
461
  | [connection-string](https://neon.com/docs/reference/cli-connection-string) | | Get connection string |
@@ -95,7 +95,7 @@ export const builder = (argv) => argv
95
95
  .command({
96
96
  command: 'list <target>',
97
97
  aliases: ['ls'],
98
- describe: 'List objects in a bucket',
98
+ describe: 'List objects in a bucket. By default folders are collapsed (like "aws s3 ls"); pass --recursive for a flat listing of every key',
99
99
  builder: (yargs) => yargs
100
100
  .usage('$0 bucket object list <bucket>[/<prefix>] [options]')
101
101
  .positional('target', {
@@ -105,8 +105,13 @@ export const builder = (argv) => argv
105
105
  })
106
106
  .options({
107
107
  ...scopeOptions,
108
+ recursive: {
109
+ describe: 'List every key flat, descending into nested folders (no delimiter). Mutually exclusive with --delimiter. Mirrors "aws s3 ls --recursive"',
110
+ type: 'boolean',
111
+ default: false,
112
+ },
108
113
  delimiter: {
109
- describe: 'Collapse keys sharing a common prefix (e.g. "/") into folders',
114
+ describe: 'Collapse keys sharing this prefix separator into folders. Defaults to "/" (folder view); ignored when --recursive is set',
110
115
  type: 'string',
111
116
  },
112
117
  cursor: {
@@ -223,7 +228,26 @@ const deleteBucket = async (props) => {
223
228
  }
224
229
  log.info(`Bucket "${props.name}" deleted from branch ${branchId}`);
225
230
  };
231
+ // Resolve the delimiter to send to the backend, mirroring `aws s3 ls`:
232
+ // - default (neither flag): "/" so the listing is folder-collapsed;
233
+ // - --recursive: no delimiter, so every nested key is returned flat;
234
+ // - explicit --delimiter <x>: that value (an empty string lists flat too).
235
+ // `--recursive` together with an explicit `--delimiter` is nonsensical and is
236
+ // rejected client-side before any HTTP request is made.
237
+ export const resolveListDelimiter = (props) => {
238
+ if (props.recursive && props.delimiter !== undefined) {
239
+ throw new Error('--recursive and --delimiter cannot be used together. Use --recursive for a flat listing, or --delimiter to collapse on a separator.');
240
+ }
241
+ if (props.recursive) {
242
+ return undefined;
243
+ }
244
+ if (props.delimiter !== undefined) {
245
+ return props.delimiter;
246
+ }
247
+ return '/';
248
+ };
226
249
  const listObjects = async (props) => {
250
+ const delimiter = resolveListDelimiter(props);
227
251
  const branchId = await branchIdFromProps(props);
228
252
  const { bucket, rest } = splitBucketTarget(props.target);
229
253
  const { data } = await listProjectBranchBucketObjects(props.apiClient, {
@@ -231,7 +255,7 @@ const listObjects = async (props) => {
231
255
  branchId,
232
256
  bucketName: bucket,
233
257
  prefix: rest === '' ? undefined : rest,
234
- delimiter: props.delimiter,
258
+ delimiter,
235
259
  cursor: props.cursor,
236
260
  limit: props.limit,
237
261
  });
@@ -1,7 +1,9 @@
1
+ import chalk from 'chalk';
1
2
  import { resolveConfig } from '@neondatabase/config';
2
3
  import { apply, createBranch as createBranchFromPolicy, inspect, loadConfigFromFile, plan, } from '@neondatabase/config-runtime';
3
4
  import { toNeonConfigView } from '../config_format.js';
4
5
  import { log } from '../log.js';
6
+ import { isCi } from '../env.js';
5
7
  import { loadEnvFileIntoProcess } from '../env_file.js';
6
8
  import { fillSingleProject, resolveBranchRef } from '../utils/enrichers.js';
7
9
  import { announceTargetBranch } from '../utils/branch_notice.js';
@@ -17,8 +19,12 @@ import { autoPullEnvAfterPin } from './env.js';
17
19
  */
18
20
  const neonctlBundler = async (fn) => zipBundle(await bundleEntry(fn.source));
19
21
  const INSPECT_FIELDS = ['project', 'branch', 'config'];
20
- const APPLIED_FIELDS = ['action', 'kind', 'identifier', 'details'];
21
- const FUNCTION_FIELDS = ['slug', 'invocation_url'];
22
+ // Deliberately minimal: action/kind/identifier are short and fixed-ish, so the table can
23
+ // never overflow. Per-change `details` (a function's long invocationUrl in particular) are
24
+ // intentionally NOT a column — they used to be JSON-stringified into a cell and blew the
25
+ // table past 190 cols. Function URLs are printed below as a plain list (see reportPushResult),
26
+ // and the full details are still available via `--output json`.
27
+ const APPLIED_FIELDS = ['action', 'kind', 'identifier'];
22
28
  const CONFLICT_FIELDS = [
23
29
  'identifier',
24
30
  'field',
@@ -252,7 +258,6 @@ const reportPushResult = (props, result, mode, services) => {
252
258
  action: change.action,
253
259
  kind: change.kind,
254
260
  identifier: change.identifier,
255
- details: change.details ? JSON.stringify(change.details) : '',
256
261
  }));
257
262
  const conflicts = result.conflicts.map((conflict) => ({
258
263
  identifier: conflict.identifier,
@@ -261,9 +266,9 @@ const reportPushResult = (props, result, mode, services) => {
261
266
  desired: stringify(conflict.desired),
262
267
  reason: conflict.reason,
263
268
  }));
264
- // Deployed functions carry their invocation URL in the change details — pull them into a
265
- // dedicated table so users can see where to call each function without digging through the
266
- // raw details blob. Keyed by slug so a function never shows twice.
269
+ // Deployed functions carry their invocation URL in the change details — collect them so
270
+ // we can list where to call each function without digging through the raw details blob.
271
+ // Keyed by slug so a function never shows twice.
267
272
  const functionUrlBySlug = new Map();
268
273
  for (const change of result.applied) {
269
274
  if (change.action === 'noop')
@@ -274,10 +279,6 @@ const reportPushResult = (props, result, mode, services) => {
274
279
  functionUrlBySlug.set(slug, invocationUrl);
275
280
  }
276
281
  }
277
- const functions = [...functionUrlBySlug].map(([slug, invocation_url]) => ({
278
- slug,
279
- invocation_url,
280
- }));
281
282
  const out = writer(props);
282
283
  const noChanges = changes.length === 0 && conflicts.length === 0;
283
284
  if (changes.length > 0) {
@@ -286,17 +287,21 @@ const reportPushResult = (props, result, mode, services) => {
286
287
  title: mode === 'plan' ? 'Planned changes' : 'Applied changes',
287
288
  });
288
289
  }
289
- if (functions.length > 0) {
290
- out.write(functions, {
291
- fields: FUNCTION_FIELDS,
292
- title: mode === 'plan' ? 'Function URLs (after apply)' : 'Function URLs',
293
- });
294
- }
295
290
  if (conflicts.length > 0) {
296
291
  out.write(conflicts, { fields: CONFLICT_FIELDS, title: 'Conflicts' });
297
292
  }
298
- // Flush any tables, then append the summary so it reads directly below them.
293
+ // Flush any tables, then append the lists/summary so they read directly below them.
299
294
  out.end();
295
+ // Function URLs are a plain list rather than a table: an invocation URL can be 70+ chars,
296
+ // which makes any bordered table overflow and wrap awkwardly in a normal terminal. A list
297
+ // lets each URL reflow on its own line, and stays copy-pasteable.
298
+ if (functionUrlBySlug.size > 0) {
299
+ const heading = mode === 'plan' ? 'Function URLs (after apply)' : 'Function URLs';
300
+ out.text(`\n${isCi() ? heading : chalk.bold(heading)}\n`);
301
+ for (const [slug, invocationUrl] of functionUrlBySlug) {
302
+ out.text(` • ${slug}: ${invocationUrl}\n`);
303
+ }
304
+ }
300
305
  if (noChanges) {
301
306
  log.info(`No changes — branch ${result.branchName} already matches the policy.`);
302
307
  }
package/commands/env.js CHANGED
@@ -44,6 +44,28 @@ export const builder = (argv) => argv
44
44
  export const handler = (args) => args;
45
45
  /** Every OS-level env var name `@neondatabase/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
+ /**
48
+ * The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
49
+ * it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
50
+ * vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
51
+ * directory to a project/branch without Auth / the Data API drops the now-stale
52
+ * `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
53
+ * aren't enabled.
54
+ *
55
+ * Deliberately **excludes** the storage / AI Gateway vars Neon projects onto third-party SDK
56
+ * names (`AWS_*`, `OPENAI_*`): those collide with credentials a user may set by hand, so
57
+ * `env pull` only ever writes them, never prunes them. (Their Neon-branded siblings —
58
+ * `NEON_STORAGE_*` / `NEON_AI_GATEWAY_*` — are owned and pruned.)
59
+ */
60
+ const NEON_OWNED_ENV_KEYS = [
61
+ ...Object.values(NEON_ENV_VAR_KEYS.postgres),
62
+ ...Object.values(NEON_ENV_VAR_KEYS.auth),
63
+ ...Object.values(NEON_ENV_VAR_KEYS.dataApi),
64
+ NEON_ENV_VAR_KEYS.storage.regionNeon,
65
+ NEON_ENV_VAR_KEYS.storage.forcePathStyle,
66
+ NEON_ENV_VAR_KEYS.aiGateway.neonToken,
67
+ NEON_ENV_VAR_KEYS.aiGateway.neonBaseUrl,
68
+ ];
47
69
  export const pull = async (props, opts = {}) => {
48
70
  const cwd = props.cwd ?? process.cwd();
49
71
  const branch = await resolveBranchRef(props);
@@ -75,8 +97,16 @@ export const pull = async (props, opts = {}) => {
75
97
  'enabled Auth / Data API).');
76
98
  return { status: 'empty' };
77
99
  }
78
- const { written } = mergeEnvFile(targetPath, neonVars);
100
+ // Reconcile rather than blindly merge: write the branch's current Neon vars and prune any
101
+ // Neon-owned vars the branch no longer has (e.g. NEON_AUTH_* / NEON_DATA_API_* carried over
102
+ // from a previous project/branch). Non-Neon lines are always preserved.
103
+ const { written, removed } = mergeEnvFile(targetPath, neonVars, {
104
+ managedKeys: NEON_OWNED_ENV_KEYS,
105
+ });
79
106
  log.info('Pulled %d Neon variable%s into %s: %s', written.length, written.length === 1 ? '' : 's', targetPath, written.join(', '));
107
+ if (removed.length > 0) {
108
+ log.info('Removed %d stale Neon variable%s not enabled on this branch: %s', removed.length, removed.length === 1 ? '' : 's', removed.join(', '));
109
+ }
80
110
  return { status: 'written', written, file: targetPath };
81
111
  };
82
112
  /**
@@ -14,6 +14,16 @@ const FUNCTION_FIELDS = [
14
14
  'invocation_url',
15
15
  'created_at',
16
16
  ];
17
+ const FUNCTIONS_LIST_LIMIT = 100;
18
+ // Table columns for `functions list`. `status` is a derived field (the
19
+ // table writer reads flat fields only): the current deployment's status.
20
+ const LIST_TABLE_FIELDS = [
21
+ 'slug',
22
+ 'name',
23
+ 'status',
24
+ 'invocation_url',
25
+ 'created_at',
26
+ ];
17
27
  const DEPLOYMENT_FIELDS = [
18
28
  'id',
19
29
  'status',
@@ -45,14 +55,14 @@ const ENTRY_CANDIDATES = ['index.ts', 'index.mjs', 'index.js'];
45
55
  // Overridable so tests can poll fast; defaults to 2s in real use.
46
56
  const POLL_INTERVAL_MS = Number(process.env.NEON_FUNCTIONS_POLL_INTERVAL_MS) || 2000;
47
57
  // Upper bound on --wait polling so the CLI never hangs (e.g. if our deployment
48
- // never becomes active_deployment). Overridable so tests can time out fast;
58
+ // never shows up as current_deployment). Overridable so tests can time out fast;
49
59
  // defaults to 10 minutes in real use.
50
60
  const POLL_TIMEOUT_MS = Number(process.env.NEON_FUNCTIONS_POLL_TIMEOUT_MS) || 600000;
51
- export const command = 'functions';
61
+ export const command = 'function';
52
62
  export const describe = 'Manage Neon Functions';
53
- export const aliases = ['function'];
63
+ export const aliases = ['functions'];
54
64
  export const builder = (argv) => argv
55
- .usage('$0 functions <sub-command> [options]')
65
+ .usage('$0 function <sub-command> [options]')
56
66
  .options({
57
67
  'project-id': {
58
68
  describe: 'Project ID',
@@ -137,7 +147,7 @@ const parseEnv = (entries) => {
137
147
  }
138
148
  return JSON.stringify(map);
139
149
  };
140
- const statusHint = (slug, projectId, branchId) => `Check status with: neonctl functions get ${slug} --project-id ${projectId} --branch ${branchId}`;
150
+ const statusHint = (slug, projectId, branchId) => `Check status with: neonctl function get ${slug} --project-id ${projectId} --branch ${branchId}`;
141
151
  // Emit the resolved deployment together with the function's invocation_url, so the
142
152
  // deploy output shows where the function is reachable (not just the deployment id).
143
153
  const emitDeployResult = (props, deployment, fn) => {
@@ -165,7 +175,7 @@ const deploy = async (props) => {
165
175
  props.runtime !== undefined;
166
176
  if (!hasOption) {
167
177
  throw new Error('Provide at least one option to deploy, e.g. --src or --env. ' +
168
- 'See: neonctl functions deploy --help.');
178
+ 'See: neonctl function deploy --help.');
169
179
  }
170
180
  // Cheap, offline validation first - fail before any network round-trip.
171
181
  if (!SLUG_PATTERN.test(props.slug)) {
@@ -188,12 +198,12 @@ const deploy = async (props) => {
188
198
  // Bundle before any network round-trip so a bundling failure fails fast.
189
199
  const zip = zipBundle(await bundleEntry(source));
190
200
  const branchId = await branchIdFromProps(props);
191
- // Snapshot the active version before deploy so we can detect the new one
192
- // afterward. A missing function (404) or no active version → undefined.
201
+ // Snapshot the current version before deploy so we can detect the new one
202
+ // afterward. A missing function (404) or no deployment yet → undefined.
193
203
  let before;
194
204
  try {
195
205
  const fn = await getFunction(props.apiClient, props.projectId, branchId, props.slug);
196
- before = fn.active_deployment?.id;
206
+ before = fn.current_deployment?.id;
197
207
  }
198
208
  catch (err) {
199
209
  if (!(isAxiosError(err) && err.response?.status === 404))
@@ -213,12 +223,12 @@ const deploy = async (props) => {
213
223
  };
214
224
  process.once('SIGINT', onSignal);
215
225
  process.once('SIGTERM', onSignal);
216
- // Poll until a NEW active version appears (id greater than the snapshot, or
226
+ // Poll until a NEW version appears (id greater than the snapshot, or
217
227
  // any version if there was none). --no-wait stops there; --wait stops at a
218
228
  // terminal status. Bounded by POLL_TIMEOUT_MS so it never hangs.
219
229
  let resolved;
220
230
  // The function carries the invocation_url; keep the whole record (not just its
221
- // active_deployment) so we can surface that URL on success.
231
+ // current_deployment) so we can surface that URL on success.
222
232
  let resolvedFn;
223
233
  const deadline = Date.now() + POLL_TIMEOUT_MS;
224
234
  try {
@@ -237,7 +247,7 @@ const deploy = async (props) => {
237
247
  continue;
238
248
  throw err;
239
249
  }
240
- const dep = fn.active_deployment;
250
+ const dep = fn.current_deployment;
241
251
  const isNew = dep !== undefined && (before === undefined || dep.id > before);
242
252
  if (isNew && dep) {
243
253
  resolved = dep;
@@ -290,12 +300,31 @@ const get = async (props) => {
290
300
  fields: FUNCTION_FIELDS,
291
301
  title: 'function',
292
302
  });
293
- if (fn.active_deployment) {
294
- out.write(fn.active_deployment, {
303
+ const current = fn.current_deployment;
304
+ const active = fn.active_deployment;
305
+ if (current && active && current.id === active.id) {
306
+ out.write(current, {
295
307
  fields: DEPLOYMENT_FIELDS,
296
- title: 'active deployment',
308
+ title: 'deployment (current, active)',
297
309
  });
298
- writeDeploymentErrorSection(out, fn.active_deployment);
310
+ writeDeploymentErrorSection(out, current);
311
+ }
312
+ else {
313
+ if (current) {
314
+ out.write(current, {
315
+ fields: DEPLOYMENT_FIELDS,
316
+ title: 'current deployment',
317
+ });
318
+ // The failure reason is shown only for the current deployment;
319
+ // the active one completed successfully by definition.
320
+ writeDeploymentErrorSection(out, current);
321
+ }
322
+ if (active) {
323
+ out.write(active, {
324
+ fields: DEPLOYMENT_FIELDS,
325
+ title: 'active deployment',
326
+ });
327
+ }
299
328
  }
300
329
  if (props.listEnvVariables) {
301
330
  out.write((fn.active_deployment?.environment ?? []).map((name) => ({ name })), {
@@ -321,13 +350,27 @@ const deleteFn = async (props) => {
321
350
  };
322
351
  const list = async (props) => {
323
352
  const branchId = await branchIdFromProps(props);
324
- const functions = await listFunctions(props.apiClient, props.projectId, branchId);
353
+ const functions = [];
354
+ let cursor;
355
+ for (;;) {
356
+ const page = await listFunctions(props.apiClient, props.projectId, branchId, { cursor, limit: FUNCTIONS_LIST_LIMIT });
357
+ functions.push(...page.functions);
358
+ log.debug('Got %d functions, next cursor: %s', page.functions.length, page.next);
359
+ // A server echoing the same cursor would loop forever; treat it as
360
+ // the end of the list.
361
+ if (!page.next || page.next === cursor)
362
+ break;
363
+ cursor = page.next;
364
+ }
325
365
  if (props.output === 'json' || props.output === 'yaml') {
326
366
  writer(props).end(functions, { fields: FUNCTION_FIELDS });
327
367
  return;
328
368
  }
329
- writer(props).end(functions, {
330
- fields: FUNCTION_FIELDS,
369
+ writer(props).end(functions.map((fn) => ({
370
+ ...fn,
371
+ status: fn.current_deployment?.status ?? '',
372
+ })), {
373
+ fields: LIST_TABLE_FIELDS,
331
374
  emptyMessage: 'No functions found on this branch.',
332
375
  });
333
376
  };
package/env_file.js CHANGED
@@ -16,35 +16,47 @@ export const resolveEnvFilePath = (cwd, file) => {
16
16
  * Merge `updates` into the dotenv content at `path`, preserving every other line
17
17
  * (comments, blank lines, unrelated keys) and the file's existing order. Keys present in
18
18
  * both are updated in place; keys only in `updates` are appended. A non-existent file is
19
- * treated as empty. Returns the list of keys that were written (for reporting).
19
+ * treated as empty. When `managedKeys` is given, any owned key on disk that is absent from
20
+ * `updates` is removed. Returns the keys written and the (managed) keys removed.
20
21
  */
21
- export const mergeEnvFile = (path, updates) => {
22
+ export const mergeEnvFile = (path, updates, options = {}) => {
22
23
  const original = existsSync(path) ? readFileSync(path, 'utf8') : '';
23
- const { content, written } = mergeEnvContent(original, updates);
24
+ const { content, written, removed } = mergeEnvContent(original, updates, options);
24
25
  writeFileSync(path, content);
25
- return { written };
26
+ return { written, removed };
26
27
  };
27
28
  /**
28
29
  * Pure core of {@link mergeEnvFile}: takes the current file content and the updates, and
29
- * returns the new content plus which keys were written. Kept side-effect-free so it can be
30
- * unit-tested without touching the filesystem.
30
+ * returns the new content plus which keys were written / removed. Kept side-effect-free so
31
+ * it can be unit-tested without touching the filesystem.
31
32
  */
32
- export const mergeEnvContent = (original, updates) => {
33
+ export const mergeEnvContent = (original, updates, options = {}) => {
33
34
  const keys = Object.keys(updates);
34
- if (keys.length === 0)
35
- return { content: original, written: [] };
35
+ // Owned keys the current pull did not produce: stale Neon-managed vars to prune. Anything
36
+ // not in `managedKeys` is always kept, so a user's own lines are never removed.
37
+ const stale = new Set([...(options.managedKeys ?? [])].filter((key) => !(key in updates)));
38
+ if (keys.length === 0 && stale.size === 0) {
39
+ return { content: original, written: [], removed: [] };
40
+ }
36
41
  const remaining = new Set(keys);
42
+ const removed = [];
37
43
  const lines = original === '' ? [] : original.split('\n');
38
- // Update keys in place where they already appear, so their position and any surrounding
39
- // comments are preserved.
40
- const updatedLines = lines.map((line) => {
44
+ // Walk the file: drop stale owned lines, update existing keys in place (so their position
45
+ // and any surrounding comments are preserved), and pass everything else through untouched.
46
+ const updatedLines = [];
47
+ for (const line of lines) {
41
48
  const key = parseKey(line);
49
+ if (key !== null && stale.has(key)) {
50
+ removed.push(key);
51
+ continue;
52
+ }
42
53
  if (key !== null && remaining.has(key)) {
43
54
  remaining.delete(key);
44
- return formatLine(key, updates[key]);
55
+ updatedLines.push(formatLine(key, updates[key]));
56
+ continue;
45
57
  }
46
- return line;
47
- });
58
+ updatedLines.push(line);
59
+ }
48
60
  // Append keys that weren't already present, in the order they were given.
49
61
  const appended = keys
50
62
  .filter((key) => remaining.has(key))
@@ -55,6 +67,7 @@ export const mergeEnvContent = (original, updates) => {
55
67
  // A dotenv file ends with a trailing newline.
56
68
  content: content === '' ? '' : `${content}\n`,
57
69
  written: keys,
70
+ removed,
58
71
  };
59
72
  };
60
73
  /**
package/functions_api.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { ContentType } from '@neondatabase/api-client';
2
2
  const functionsPath = (projectId, branchId) => `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/functions`;
3
- export const listFunctions = async (apiClient, projectId, branchId) => {
3
+ export const listFunctions = async (apiClient, projectId, branchId, { cursor, limit } = {}) => {
4
4
  const { data } = await apiClient.request({
5
5
  path: functionsPath(projectId, branchId),
6
6
  method: 'GET',
7
+ query: { cursor, limit },
7
8
  secure: true,
8
9
  format: 'json',
9
10
  });
10
- return data.functions;
11
+ return { functions: data.functions ?? [], next: data.pagination?.next };
11
12
  };
12
13
  export const getFunction = async (apiClient, projectId, branchId, slug) => {
13
14
  const { data } = await apiClient.request({
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+ssh://git@github.com/neondatabase/neonctl.git"
6
6
  },
7
7
  "type": "module",
8
- "version": "2.26.2",
8
+ "version": "2.26.4",
9
9
  "description": "CLI tool for NeonDB Cloud management",
10
10
  "main": "index.js",
11
11
  "author": "NeonDB",
@@ -59,9 +59,9 @@
59
59
  "dependencies": {
60
60
  "@hono/node-server": "2.0.4",
61
61
  "@neondatabase/api-client": "2.7.1",
62
- "@neondatabase/config": "0.7.1",
63
- "@neondatabase/config-runtime": "0.7.1",
64
- "@neondatabase/env": "0.5.1",
62
+ "@neondatabase/config": "0.8.0",
63
+ "@neondatabase/config-runtime": "0.8.0",
64
+ "@neondatabase/env": "0.6.0",
65
65
  "@segment/analytics-node": "1.3.0",
66
66
  "axios": "1.7.2",
67
67
  "axios-debug-log": "1.0.0",
@@ -71,7 +71,7 @@
71
71
  "cliui": "8.0.1",
72
72
  "diff": "5.2.0",
73
73
  "fflate": "^0.8.3",
74
- "neon-init": "0.16.3",
74
+ "neon-init": "0.17.0",
75
75
  "open": "10.1.0",
76
76
  "openid-client": "6.8.1",
77
77
  "pg-protocol": "^1.14.0",
@@ -41,7 +41,7 @@ export const test = originalTest.extend({
41
41
  '--api-host',
42
42
  `http://localhost:${server.address().port}`,
43
43
  '--output',
44
- options.outputTable ? 'table' : 'yaml',
44
+ options.output ?? (options.outputTable ? 'table' : 'yaml'),
45
45
  '--api-key',
46
46
  'test-key',
47
47
  '--no-analytics',