neonctl 2.27.1 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -388,12 +388,15 @@ export default defineConfig({
388
388
  });
389
389
  ```
390
390
 
391
- Three sub-commands plus a top-level alias drive it:
391
+ Three sub-commands plus two top-level aliases drive it:
392
392
 
393
393
  ```bash
394
394
  # Inspect the branch's live Neon state (read-only — never mutates)
395
395
  neon config status
396
396
 
397
+ # `neon status` is an alias for `neon config status`
398
+ neon status
399
+
397
400
  # Dry-run diff: show exactly what `apply` would change
398
401
  neon config plan
399
402
 
@@ -419,6 +422,35 @@ The branch is chosen with `--branch <id|name>`; without it the project's default
419
422
 
420
423
  **Output**: `status` prints the project, branch, and reverse-engineered config; `plan` / `apply` print the planned/applied changes and any conflicts as tables. Pass `--output json` (or `--output yaml`) to emit the full machine-readable result (`PushResult`) for piping into other tools or CI.
421
424
 
425
+ **`config status --current-branch`** (alias `neon status --current-branch`) prints _only_ the branch pinned in the local `.neon` file — no network, no auth, no analytics — and exits non-zero when none is pinned. This behavior lets it safely drive a shell prompt. Example [starship](https://starship.rs) segment:
426
+
427
+ ```toml
428
+ [custom.neon]
429
+ description = "Current Neon database branch"
430
+ format = "[$symbol$output]($style) "
431
+ style = "bold green"
432
+ # `symbol` below uses a Nerd Font glyph; swap it for a plain
433
+ # label/emoji if you don't have a Nerd Font installed.
434
+ symbol = " "
435
+ command = "neon status --current-branch"
436
+ # Starship evaluates this on EVERY prompt render. To keep prompts instant
437
+ # everywhere outside a Neon project, do a zero-subprocess walk-up for an
438
+ # ancestor `.neon` first (the same walk the CLI does, stopping at $HOME and /).
439
+ # Only when one is found do we invoke the CLI, whose exit code is the real
440
+ # gate: non-zero (no branch pinned) hides the segment cleanly.
441
+ when = '''
442
+ d="$PWD"
443
+ while [ "$d" != "$HOME" ] && [ "$d" != / ]; do
444
+ if [ -e "$d/.neon" ]; then
445
+ neon status --current-branch >/dev/null 2>&1
446
+ exit $?
447
+ fi
448
+ d=$(dirname "$d")
449
+ done
450
+ exit 1
451
+ '''
452
+ ```
453
+
422
454
  ```bash
423
455
  # CI gate: fail the build if the branch has drifted from the policy
424
456
  neon config plan --project-id polished-snowflake-12345678 --output json
package/dist/analytics.js CHANGED
@@ -1,13 +1,19 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { Analytics } from '@segment/analytics-node';
4
- import { isAxiosError } from 'axios';
5
4
  import { CREDENTIALS_FILE } from './config.js';
5
+ import { isCurrentBranchProbe } from './context.js';
6
6
  import { getGithubEnvVars, isCi } from './env.js';
7
7
  import { log } from './log.js';
8
8
  import pkg from './pkg.js';
9
- import { getApiClient } from './api.js';
9
+ import { getApiClient, isNeonApiError } from './api.js';
10
10
  const WRITE_KEY = '3SQXn5ejjXWLEJ8xU2PRYhAotLtTaeeV';
11
+ /**
12
+ * Raw-argv fallback for the offline `--current-branch` probe. The init
13
+ * middleware runs before validation, where the parsed `currentBranch` flag may
14
+ * not be populated yet, so we also scan `process.argv` directly to be safe.
15
+ */
16
+ const hasCurrentBranchArgv = () => process.argv.includes('--current-branch');
11
17
  let client;
12
18
  let clientInitialized = false;
13
19
  let userId = '';
@@ -20,6 +26,13 @@ export const initAnalyticsClientMiddleware = (args) => {
20
26
  if (!args.analytics || clientInitialized) {
21
27
  return;
22
28
  }
29
+ // The offline `--current-branch` probe must make zero network calls. This
30
+ // middleware runs before validation, so guard on the raw argv too (in case
31
+ // the parsed `currentBranch` flag isn't populated this early): never create
32
+ // the Segment client, which keeps trackEvent/closeAnalytics no-ops downstream.
33
+ if (isCurrentBranchProbe(args) || hasCurrentBranchArgv()) {
34
+ return;
35
+ }
23
36
  clientInitialized = true;
24
37
  client = new Analytics({
25
38
  writeKey: WRITE_KEY,
@@ -38,6 +51,9 @@ export const analyticsMiddleware = async (args) => {
38
51
  if (!client || !args.analytics) {
39
52
  return;
40
53
  }
54
+ if (isCurrentBranchProbe(args)) {
55
+ return;
56
+ }
41
57
  try {
42
58
  const credentialsPath = join(args.configDir, CREDENTIALS_FILE);
43
59
  const credentials = readFileSync(credentialsPath, { encoding: 'utf-8' });
@@ -98,8 +114,8 @@ export const sendError = (err, errCode) => {
98
114
  if (!client) {
99
115
  return;
100
116
  }
101
- const axiosError = isAxiosError(err) ? err : undefined;
102
- const requestId = axiosError?.response?.headers['x-neon-ret-request-id'];
117
+ const apiError = isNeonApiError(err) ? err : undefined;
118
+ const requestId = apiError?.headers?.['x-neon-ret-request-id'];
103
119
  if (requestId) {
104
120
  log.debug('Failed request ID: %s', requestId);
105
121
  }
@@ -110,7 +126,7 @@ export const sendError = (err, errCode) => {
110
126
  message: err.message,
111
127
  stack: err.stack,
112
128
  errCode,
113
- statusCode: axiosError?.response?.status,
129
+ statusCode: apiError?.status,
114
130
  requestId: requestId,
115
131
  },
116
132
  });