neon 2.38.0 → 2.38.1

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.
@@ -0,0 +1,16 @@
1
+ let current = null;
2
+ export const setAuthContext = (context) => {
3
+ current = context;
4
+ };
5
+ export const getAuthContext = () => current;
6
+ /**
7
+ * The config directory whose credentials a 401 should clear, or `null` to leave
8
+ * stored credentials alone.
9
+ *
10
+ * An API key passed on the command line never touches the stored OAuth token,
11
+ * so a 401 on that key says nothing about whether the stored credentials are
12
+ * still good — clearing them would sign the user out of an account the failed
13
+ * request never used. The directory comes from the context rather than the
14
+ * default so that `--config-dir` isolates the deletion too.
15
+ */
16
+ export const credentialsToClearOn401 = (context) => context?.source === "stored-credentials" ? context.configDir : null;
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { getApiClient } from "../api.js";
5
5
  import { auth, refreshToken } from "../auth.js";
6
+ import { setAuthContext } from "../auth_context.js";
6
7
  import { CREDENTIALS_FILE } from "../config.js";
7
8
  import { isConfigInit, isCurrentBranchProbe } from "../context.js";
8
9
  import { isCi } from "../env.js";
@@ -129,6 +130,10 @@ export const ensureAuth = async (props) => {
129
130
  if (props.apiKey || props._[0] === "auth") {
130
131
  if (props.apiKey) {
131
132
  log.debug("Using an API key to authorize requests");
133
+ setAuthContext({
134
+ source: "api-key",
135
+ configDir: props.configDir,
136
+ });
132
137
  }
133
138
  props.apiClient = getApiClient({
134
139
  apiKey: props.apiKey,
@@ -149,6 +154,10 @@ export const ensureAuth = async (props) => {
149
154
  if (result) {
150
155
  props.apiKey = result.apiKey;
151
156
  props.apiClient = result.apiClient;
157
+ setAuthContext({
158
+ source: "stored-credentials",
159
+ configDir: props.configDir,
160
+ });
152
161
  return;
153
162
  }
154
163
  }
@@ -189,6 +198,10 @@ export const ensureAuth = async (props) => {
189
198
  apiKey,
190
199
  apiHost: props.apiHost,
191
200
  });
201
+ setAuthContext({
202
+ source: "stored-credentials",
203
+ configDir: props.configDir,
204
+ });
192
205
  };
193
206
  /**
194
207
  * Deletes the credentials file at the specified path
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { hideBin } from "yargs/helpers";
4
4
  import { analyticsMiddleware, closeAnalytics, getAnalyticsEventProperties, initAnalyticsClientMiddleware, sendError, trackEvent, } from "./analytics.js";
5
5
  import { isNeonApiError, messageFromBody } from "./api.js";
6
6
  import { defaultClientID } from "./auth.js";
7
+ import { credentialsToClearOn401, getAuthContext } from "./auth_context.js";
7
8
  import { deleteCredentials, ensureAuth } from "./commands/auth.js";
8
9
  import commands from "./commands/index.js";
9
10
  import { defaultDir, ensureConfigDir } from "./config.js";
@@ -175,9 +176,17 @@ async function handleError(msg, err) {
175
176
  }
176
177
  else if (err.status === 401) {
177
178
  sendError(err, "AUTH_FAILED");
179
+ const configDir = credentialsToClearOn401(getAuthContext());
180
+ // The request was authorized with a key the user supplied, so there
181
+ // is nothing of ours to clear and nothing to retry — the same key
182
+ // would just be rejected again.
183
+ if (configDir === null) {
184
+ log.error("Authentication failed: the Neon API rejected the API key. Check --api-key or NEON_API_KEY.");
185
+ return false;
186
+ }
178
187
  log.info("Authentication failed, deleting credentials...");
179
188
  try {
180
- deleteCredentials(defaultDir);
189
+ deleteCredentials(configDir);
181
190
  return true; // Allow retry for auth failures
182
191
  }
183
192
  catch (deleteErr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon",
3
- "version": "2.38.0",
3
+ "version": "2.38.1",
4
4
  "description": "CLI tool for Neon Serverless Postgres",
5
5
  "keywords": [
6
6
  "neon",
@@ -47,7 +47,7 @@
47
47
  "cliui": "8.0.1",
48
48
  "diff": "5.2.0",
49
49
  "fflate": "^0.8.3",
50
- "neon-init": "0.20.2",
50
+ "neon-init": "0.20.3",
51
51
  "open": "^10.2.0",
52
52
  "openid-client": "6.8.1",
53
53
  "pg-protocol": "^1.14.0",
@@ -58,8 +58,8 @@
58
58
  "yargs": "17.7.2",
59
59
  "@neon/sdk": "1.3.0",
60
60
  "@neon/config": "0.10.0",
61
- "@neon/config-runtime": "0.10.0",
62
- "@neon/env": "0.11.8"
61
+ "@neon/env": "0.11.8",
62
+ "@neon/config-runtime": "0.10.0"
63
63
  },
64
64
  "optionalDependencies": {
65
65
  "esbuild": "0.28.1"
@@ -88,7 +88,8 @@
88
88
  "strip-ansi": "7.1.0",
89
89
  "tsx": "4.22.3",
90
90
  "typescript": "^5.9.0",
91
- "vitest": "^3.0.9"
91
+ "vitest": "^3.0.9",
92
+ "@neon/e2e-harness": "0.0.0"
92
93
  },
93
94
  "publishConfig": {
94
95
  "access": "public",
@@ -118,6 +119,7 @@
118
119
  "lint:fix": "pnpm typecheck && biome check src --write",
119
120
  "test": "pnpm --filter neonctl... build && vitest run",
120
121
  "test:ci": "pnpm build && vitest run",
122
+ "test:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts",
121
123
  "test:conformance": "vitest run --config tests/psql-conformance/vitest.config.ts"
122
124
  }
123
125
  }