neon 8.11.1 → 8.11.2

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/_chunks/api-GbEaBZJ5.js +388 -0
  3. package/dist/_chunks/api_keys-CNnnugbQ.js +281 -0
  4. package/dist/_chunks/ask-DVwy0eUL.js +155 -0
  5. package/dist/_chunks/auth-CIUtXByB.js +495 -0
  6. package/dist/_chunks/bootstrap-wPZWa1sT.js +499 -0
  7. package/dist/_chunks/branches-BNa1xX1L.js +460 -0
  8. package/dist/_chunks/bucket-BCdryTkD.js +409 -0
  9. package/dist/_chunks/checkout-CXIOydqZ.js +348 -0
  10. package/dist/_chunks/claim-DqAvAvDm.js +473 -0
  11. package/dist/_chunks/config-BH_qRHiG.js +1113 -0
  12. package/dist/_chunks/connection_string-DB4awPzZ.js +154 -0
  13. package/dist/_chunks/credentials-BSjE9fRR.js +146 -0
  14. package/dist/_chunks/data_api-Djxj5ZQD.js +263 -0
  15. package/dist/_chunks/databases-Dok4wru7.js +84 -0
  16. package/dist/_chunks/deploy-D7B0Xfjg.js +33 -0
  17. package/dist/_chunks/dev-CSvxAecE.js +945 -0
  18. package/dist/_chunks/diff-DhxxbdQE.js +178 -0
  19. package/dist/_chunks/docs-CrVybkTP.js +197 -0
  20. package/dist/_chunks/env-5HS5qQGd.js +284 -0
  21. package/dist/_chunks/feedback-CMayyTa8.js +88 -0
  22. package/dist/_chunks/functions-Q0zuvtnk.js +464 -0
  23. package/dist/_chunks/git-H32TNt6M.js +356 -0
  24. package/dist/_chunks/init-BkA6RilG.js +111 -0
  25. package/dist/_chunks/inspect-g0wekylj.js +90 -0
  26. package/dist/_chunks/ip_allow-5lLFvIuE.js +106 -0
  27. package/dist/_chunks/link-BgtkOBws.js +936 -0
  28. package/dist/_chunks/logs-DWkpjzIH.js +291 -0
  29. package/dist/_chunks/mcp-Bo-7TKc7.js +253 -0
  30. package/dist/_chunks/neon_auth-CBs1_3qu.js +822 -0
  31. package/dist/_chunks/open-CX3BT3Tp.js +33 -0
  32. package/dist/_chunks/operations-D4I-AxVs.js +37 -0
  33. package/dist/_chunks/orgs-xGfANwHQ.js +52 -0
  34. package/dist/_chunks/plugins-DqD7BDO1.js +169 -0
  35. package/dist/_chunks/profile-yXOiKk9R.js +672 -0
  36. package/dist/_chunks/projects-BwI2Uo0z.js +439 -0
  37. package/dist/_chunks/psql-BM-doe8e.js +64 -0
  38. package/dist/_chunks/realtime-BRqIyGR-.js +79 -0
  39. package/dist/_chunks/roles-B4w3YT_7.js +79 -0
  40. package/dist/_chunks/set_context-DbrwoImy.js +38 -0
  41. package/dist/_chunks/skills-DyiBXKS3.js +255 -0
  42. package/dist/_chunks/snapshots-RJBzW8GM.js +338 -0
  43. package/dist/_chunks/status-DV8Z2kw3.js +44 -0
  44. package/dist/_chunks/triggers-BrRo1z4b.js +302 -0
  45. package/dist/_chunks/user-dlOGXbX9.js +60 -0
  46. package/dist/_chunks/vpc_endpoints-CNb0YfMo.js +105 -0
  47. package/dist/auth_recovery.js +1 -1
  48. package/dist/commands/api.js +1 -388
  49. package/dist/commands/api_keys.js +1 -281
  50. package/dist/commands/ask.js +1 -155
  51. package/dist/commands/auth.js +1 -495
  52. package/dist/commands/bootstrap.js +1 -499
  53. package/dist/commands/branches.js +1 -460
  54. package/dist/commands/bucket.js +1 -409
  55. package/dist/commands/checkout.js +1 -348
  56. package/dist/commands/claim.js +1 -473
  57. package/dist/commands/config.js +1 -1113
  58. package/dist/commands/connection_string.js +1 -154
  59. package/dist/commands/credentials.js +1 -146
  60. package/dist/commands/data_api.js +1 -263
  61. package/dist/commands/databases.js +1 -84
  62. package/dist/commands/deploy.js +1 -33
  63. package/dist/commands/dev.js +1 -945
  64. package/dist/commands/diff.js +1 -178
  65. package/dist/commands/docs.js +1 -197
  66. package/dist/commands/env.js +1 -284
  67. package/dist/commands/feedback.js +1 -88
  68. package/dist/commands/functions.js +1 -464
  69. package/dist/commands/functions_new.js +1 -1
  70. package/dist/commands/git.js +1 -356
  71. package/dist/commands/index.js +45 -45
  72. package/dist/commands/init.js +1 -111
  73. package/dist/commands/inspect.js +1 -90
  74. package/dist/commands/ip_allow.js +1 -106
  75. package/dist/commands/link.js +1 -936
  76. package/dist/commands/logs.js +1 -291
  77. package/dist/commands/mcp.js +1 -253
  78. package/dist/commands/neon_auth.js +1 -822
  79. package/dist/commands/open.js +1 -33
  80. package/dist/commands/operations.js +1 -37
  81. package/dist/commands/orgs.js +1 -52
  82. package/dist/commands/plugins.js +1 -169
  83. package/dist/commands/profile.js +1 -672
  84. package/dist/commands/projects.js +1 -439
  85. package/dist/commands/psql.js +1 -64
  86. package/dist/commands/realtime.js +1 -79
  87. package/dist/commands/roles.js +1 -79
  88. package/dist/commands/set_context.js +1 -38
  89. package/dist/commands/skills.js +1 -255
  90. package/dist/commands/snapshots.js +1 -338
  91. package/dist/commands/status.js +1 -44
  92. package/dist/commands/subscribe.js +1 -1
  93. package/dist/commands/triggers.js +1 -302
  94. package/dist/commands/user.js +1 -60
  95. package/dist/commands/vpc_endpoints.js +1 -105
  96. package/dist/functions/registry.js +29 -151
  97. package/dist/functions/source.js +42 -2
  98. package/dist/index.js +1 -1
  99. package/dist/init/auth.js +3 -3
  100. package/dist/init/link.js +2 -2
  101. package/dist/init/run.js +3 -3
  102. package/dist/init/tooling.js +3 -3
  103. package/dist/mcp/mint.js +1 -1
  104. package/dist/test_utils/registry.js +1 -2
  105. package/package.json +8 -6
@@ -1,155 +1 @@
1
- import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { isCi } from "../env.js";
3
- import { isNetworkError } from "../errors.js";
4
- import { writer } from "../writer.js";
5
- import { noPassthrough, single } from "../utils/flags.js";
6
- import { isEventStreamContentType, readAskSse } from "./ask_sse.js";
7
- import { createThinkingSpinner } from "./thinking_spinner.js";
8
- //#region src/commands/ask.ts
9
- var ask_exports = /* @__PURE__ */ __exportAll({
10
- DEFAULT_ASK_URL: () => DEFAULT_ASK_URL,
11
- builder: () => builder,
12
- command: () => "ask",
13
- describe: () => describe,
14
- handler: () => handler,
15
- resolveAskUrl: () => resolveAskUrl
16
- });
17
- const DEFAULT_ASK_URL = "https://br-frosty-cell-a5smzg39-assistant.compute.c-1.us-east-2.aws.neon.tech/ask";
18
- const ASK_TIMEOUT_MS = 12e4;
19
- const command = "ask";
20
- const describe = "Ask a question about Neon";
21
- const builder = (argv) => argv.usage("$0 ask --prompt <question>").option("prompt", {
22
- describe: "The question to ask",
23
- type: "string",
24
- demandOption: true,
25
- coerce: single("prompt", { required: true })
26
- }).option("url", {
27
- describe: "Override the assistant URL",
28
- type: "string",
29
- hidden: true,
30
- coerce: single("url")
31
- }).strict().check(noPassthrough("ask")).example("$0 ask --prompt \"How do schema-only branches work?\"", describe);
32
- function resolveAskUrl(opts) {
33
- const fromFlag = opts.url?.trim();
34
- if (fromFlag) return fromFlag;
35
- const fromEnv = opts.envUrl?.trim();
36
- if (fromEnv) return fromEnv;
37
- return DEFAULT_ASK_URL;
38
- }
39
- function isMachineOutput(output) {
40
- return output === "json" || output === "yaml";
41
- }
42
- const handler = async (props) => {
43
- const url = resolveAskUrl({
44
- url: props.url,
45
- envUrl: process.env.NEON_ASK_URL
46
- });
47
- const human = !isMachineOutput(props.output);
48
- const spinner = createThinkingSpinner({
49
- out: process.stderr,
50
- isTty: Boolean(process.stderr.isTTY) && !isCi() && human
51
- });
52
- let streamed = "";
53
- try {
54
- spinner.start();
55
- const text = await askAssistant({
56
- prompt: props.prompt,
57
- url,
58
- analytics: props.analytics,
59
- acceptEventStream: human,
60
- onStatus: (message) => {
61
- spinner.setMessage(message);
62
- },
63
- onText: (chunk) => {
64
- if (!human) return;
65
- spinner.stop();
66
- streamed += chunk;
67
- writer(props).text(chunk);
68
- }
69
- });
70
- spinner.stop();
71
- if (!human) {
72
- writer(props).end({ text }, { fields: ["text"] });
73
- return;
74
- }
75
- if (streamed === "") {
76
- writer(props).text(`${text}\n`);
77
- return;
78
- }
79
- if (!streamed.endsWith("\n")) writer(props).text("\n");
80
- } catch (error) {
81
- spinner.stop();
82
- if (human && streamed !== "" && !streamed.endsWith("\n")) writer(props).text("\n");
83
- throw error;
84
- } finally {
85
- spinner.stop();
86
- }
87
- };
88
- function isAskTimeout(error) {
89
- return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
90
- }
91
- function askErrorMessage(body, status) {
92
- if (typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" && body.error.trim() !== "") return body.error;
93
- return `The Neon assistant returned ${status}.`;
94
- }
95
- function askText(body) {
96
- if (typeof body === "object" && body !== null && "text" in body && typeof body.text === "string") return body.text;
97
- throw new Error("The Neon assistant returned an unexpected response.");
98
- }
99
- async function readJsonBody(response) {
100
- const raw = await response.text();
101
- if (raw.trim() === "") return;
102
- try {
103
- return JSON.parse(raw);
104
- } catch {
105
- return;
106
- }
107
- }
108
- async function readStreamedAsk(body, opts) {
109
- let text = "";
110
- let sawDone = false;
111
- for await (const event of readAskSse(body)) switch (event.type) {
112
- case "status":
113
- opts.onStatus?.(event.message);
114
- break;
115
- case "text":
116
- text += event.text;
117
- opts.onText?.(event.text);
118
- break;
119
- case "error": throw new Error(event.error);
120
- case "done": sawDone = true;
121
- }
122
- if (!sawDone) throw new Error("The Neon assistant returned an unexpected response.");
123
- return text;
124
- }
125
- async function askAssistant(opts) {
126
- let response;
127
- try {
128
- response = await fetch(opts.url, {
129
- method: "POST",
130
- headers: {
131
- "Content-Type": "application/json",
132
- Accept: opts.acceptEventStream ? "text/event-stream" : "application/json",
133
- "x-caller-source": "cli",
134
- ...opts.analytics ? {} : { "x-caller-analytics": "off" }
135
- },
136
- body: JSON.stringify({ prompt: opts.prompt }),
137
- signal: AbortSignal.timeout(ASK_TIMEOUT_MS)
138
- });
139
- } catch (error) {
140
- if (isAskTimeout(error)) throw new Error("The Neon assistant did not respond in time.");
141
- if (isNetworkError(error)) throw new Error("Could not reach the Neon assistant. Check your internet connection and try again.");
142
- throw error;
143
- }
144
- if (!response.ok) {
145
- const body = await readJsonBody(response);
146
- throw new Error(askErrorMessage(body, response.status));
147
- }
148
- if (opts.acceptEventStream && isEventStreamContentType(response.headers.get("content-type") ?? void 0)) {
149
- if (!response.body) throw new Error("The Neon assistant returned an unexpected response.");
150
- return readStreamedAsk(response.body, opts);
151
- }
152
- return askText(await readJsonBody(response));
153
- }
154
- //#endregion
155
- export { DEFAULT_ASK_URL, builder, command, describe, handler, resolveAskUrl, ask_exports as t };
1
+ export { t as DEFAULT_ASK_URL, r as builder, i as command, a as describe, o as handler, s as resolveAskUrl } from "../_chunks/ask-DVwy0eUL.js";
@@ -1,495 +1 @@
1
- import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { a as OAUTH, i as API_KEY, l as interpretCredentials, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-BS-0Pmwc.js";
3
- import { isCi } from "../env.js";
4
- import { log } from "../log.js";
5
- import { withExclusiveLock } from "../refresh_lock.js";
6
- import { getApiClient, isNeonApiError } from "../api.js";
7
- import { setAuthContext } from "../auth_context.js";
8
- import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
9
- import { currentContextFile, isAskCommand, isClaimCommand, isConfigAdd, isConfigInit, isCurrentBranchProbe, isDocsCommand, isFeedbackCommand, isFunctionsTemplateCommand, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
10
- import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-DIB1av2Q.js";
11
- import { extendTokenSet } from "../utils/auth.js";
12
- import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
13
- import { i as selectCredential, n as displacedProfileWarning, t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
14
- import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
15
- import { assertionHasExpired, claimableCredentialsExist, claimableCredentialsPath, readLinkedClaimableCredentials, shouldUseClaimableCredentials } from "../claimable/state.js";
16
- import { readOutgoingCredential, retirePreviousCredential } from "../retire_credential.js";
17
- import { getCliName } from "../utils/cli_name.js";
18
- import { helpEpilogue, helpWidth, wrapHelpText } from "../utils/help_text.js";
19
- import { existsSync } from "node:fs";
20
- import { join } from "node:path";
21
- import { createHash } from "node:crypto";
22
- //#region src/commands/auth.ts
23
- var auth_exports = /* @__PURE__ */ __exportAll({
24
- aliases: () => aliases,
25
- authFlow: () => authFlow,
26
- builder: () => builder,
27
- command: () => command,
28
- deleteCredentialsAt: () => deleteCredentialsAt,
29
- describe: () => describe,
30
- ensureAuth: () => ensureAuth,
31
- handler: () => handler,
32
- isAccessTokenUsable: () => isAccessTokenUsable,
33
- locationForAuth: () => locationForAuth,
34
- refreshStoredCredentials: () => refreshStoredCredentials,
35
- usableCredential: () => usableCredential
36
- });
37
- const locationForAuth = (configDir, name, keyring, options = {}) => {
38
- const declared = readProfiles(configDir, log.warning)?.profiles[name];
39
- const pointer = declared !== void 0 && isKeyringPointer(declared.credentials);
40
- if (keyring === true || pointer) return {
41
- profile: name,
42
- storage: "keyring"
43
- };
44
- if (declared !== void 0 || name === "DEFAULT") return locationForName(configDir, name);
45
- if (options.create === true) return newProfileLocation(configDir, name, "file");
46
- return locationForName(configDir, name);
47
- };
48
- const command = "login";
49
- const aliases = ["auth"];
50
- const describe = "Sign in with a browser. See --help for API keys and profiles";
51
- const wrapAuthHelp = (text) => wrapHelpText(text, helpWidth());
52
- const authHelpEpilogue = () => {
53
- const cli = getCliName();
54
- return helpEpilogue(wrapAuthHelp("Non-interactive (CI, scripts, agents):"), wrapAuthHelp("Use an existing API key or an API-key profile."), ` ${cli} projects list --profile agent`, wrapAuthHelp(`${cli} profile create <name> --mint needs a browser during setup, then stores an API key and drops the session. It cannot run in CI. Default key is account-wide; --org-id or --project-id (not both) restricts it.`), wrapAuthHelp(`A profile holds a browser session or an API key. Select it with --profile <name> or NEON_PROFILE. List profiles with ${cli} profile list.`), wrapAuthHelp("On commands that use a credential, --api-key or --profile overrides NEON_API_KEY and NEON_PROFILE. Pass either --api-key or --profile, not both. When neither flag is passed and both NEON_API_KEY and NEON_PROFILE are set, the key wins and stderr names the ignored profile. When no flag or environment variable selects a credential, DEFAULT is used."), wrapAuthHelp(`${cli} profile create <name> replaces an existing profile of that name and tries to revoke the credential it held. An imported key with no recorded id stays live. Pick a new name to keep the old profile.`), wrapAuthHelp(`See ${cli} profile --help and ${cli} profile create --help.`));
55
- };
56
- const builder = (yargs) => yargs.option("context-file", { hidden: true }).option("keyring", {
57
- describe: "Store the credential in the OS keyring. Per profile; a later login without the flag stays there. See `neon profile list`.",
58
- type: "boolean"
59
- }).example("$0 login", "Sign in with a browser").example("$0 login --profile work", "Save a browser session in profile work").example("$0 projects list --api-key \"$KEY\"", "Use an existing API key for this command").example("NEON_API_KEY=\"$KEY\" $0 projects list", "Use an existing API key from the environment").example("$0 profile create agent --api-key \"$KEY\"", "Store an existing API key").example("echo \"$KEY\" | $0 profile create agent --api-key -", "Store a key read from stdin").example("$0 profile create ci --mint", "Sign in once in a browser and store a new API key").epilogue(authHelpEpilogue());
60
- const handler = async (args) => {
61
- await authFlow(args);
62
- };
63
- const authFlow = async ({ configDir, oauthHost, clientId, apiHost, forceAuth, "force-auth": forceAuthKebab, allowUnsafeTls, profile, keyring }) => {
64
- const profileName = selectProfileName(profile);
65
- const isNamed = profileName !== DEFAULT_PROFILE;
66
- if (isNamed) assertValidProfileName(profileName);
67
- assertProfilesUsable(configDir, profileName);
68
- const at = locationForAuth(configDir, profileName, keyring, { create: true });
69
- if (at.storage === "keyring") {
70
- const declared = readProfiles(configDir)?.profiles[profileName];
71
- storeFor(configDir).assertKeyringWritable(declared !== void 0 && isKeyringPointer(declared.credentials) ? profileName : void 0);
72
- }
73
- if (!(forceAuth ?? forceAuthKebab) && isCi()) throw new Error("Cannot run interactive auth in CI");
74
- let previousFile;
75
- let previousWasKeyring = false;
76
- let previousOutgoing = null;
77
- try {
78
- const previous = resolveProfile(configDir, profileName);
79
- previousWasKeyring = previous.storage === "keyring";
80
- if (previous.storage === "file") previousFile = previous.credentialsPath;
81
- previousOutgoing = readOutgoingCredential(configDir, locationOf(previous));
82
- } catch {
83
- previousFile = void 0;
84
- }
85
- const tokenSet = await auth({
86
- oauthHost,
87
- clientId,
88
- allowUnsafeTls
89
- });
90
- let identity = {};
91
- try {
92
- identity = await persistNewSession(at, tokenSet, getApiClient({
93
- apiKey: tokenSet.access_token || "",
94
- apiHost
95
- }), configDir);
96
- } catch (err) {
97
- log.error("Failed to save credentials");
98
- throw err instanceof Error ? err : new Error(String(err));
99
- }
100
- if (at.storage === "keyring" || isNamed) {
101
- try {
102
- upsertProfile(configDir, profileName, {
103
- credentials: at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path,
104
- ...identity.email ? { label: identity.email } : {},
105
- ...identity.id ? { userId: identity.id } : {}
106
- });
107
- } catch (err) {
108
- if (at.storage === "keyring" && !previousWasKeyring) {
109
- if (storeFor(configDir).delete(at, { required: false }) !== "cleared") log.warning("Could not confirm the new OS keyring item for profile \"%s\" was removed after a failed save.", profileName);
110
- }
111
- log.error("Failed to save credentials");
112
- throw err instanceof Error ? err : new Error(String(err));
113
- }
114
- if (at.storage === "keyring") await retirePreviousCredential({
115
- apiHost,
116
- oauthHost,
117
- clientId,
118
- ...allowUnsafeTls ? { allowUnsafeTls } : {}
119
- }, profileName, previousOutgoing);
120
- if (at.storage === "keyring" && previousFile !== void 0 && profilesUsingPath(configDir, previousFile, profileName).length === 0) {
121
- if (isOwnedCredentialPath(configDir, previousFile)) try {
122
- storeFor(configDir).delete({
123
- profile: profileName,
124
- storage: "file",
125
- path: previousFile
126
- });
127
- log.info("Deleted %s", previousFile);
128
- } catch {
129
- log.warning("Saved the keyring item but could not delete %s", previousFile);
130
- }
131
- else if (existsSync(previousFile)) log.info("Left %s on disk — not created by neon", previousFile);
132
- }
133
- log.info("Saved profile \"%s\" (%s)", profileName, at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path);
134
- }
135
- log.info(`Auth complete (profile ${profileName})`);
136
- return tokenSet.access_token || "";
137
- };
138
- /**
139
- * One write, after the lookup: a second write that adds `user_id` could fail the keyring size
140
- * check after the first had already replaced the previous credential.
141
- */
142
- const persistNewSession = async (at, credentials, apiClient, configDir) => {
143
- const identity = await fetchIdentity(apiClient);
144
- const stored = oauthCredentialsFromTokenSet(credentials, null);
145
- writeOAuthCredentials(configDir, at, identity.id ? {
146
- ...stored,
147
- user_id: identity.id
148
- } : stored);
149
- return identity;
150
- };
151
- const fetchIdentity = async (apiClient) => {
152
- try {
153
- const { data: { id, email } } = await apiClient.getCurrentUserInfo();
154
- return {
155
- ...id ? { id } : {},
156
- ...email ? { email } : {}
157
- };
158
- } catch (err) {
159
- if (isNeonApiError(err) && err.status === 401) throw new Error("Signed in, but the Neon API rejected the new access token. Try `neon login` again.");
160
- log.warning("Signed in, but could not look up the account: %s", err instanceof Error ? err.message : String(err));
161
- return {};
162
- }
163
- };
164
- const isAccessTokenUsable = (credentials, now) => {
165
- const token = credentials.access_token;
166
- if (typeof token !== "string" || token === "") return false;
167
- const expiresAt = credentials.expires_at;
168
- if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return false;
169
- return expiresAt > now;
170
- };
171
- const handleExistingToken = async (tokenSet, props, at) => {
172
- if (isAccessTokenUsable(tokenSet, Date.now())) {
173
- log.debug("Using existing valid access_token");
174
- const apiKey = tokenSet.access_token ?? "";
175
- return {
176
- apiKey,
177
- apiClient: getApiClient({
178
- apiKey,
179
- apiHost: props.apiHost
180
- }),
181
- refreshed: false
182
- };
183
- }
184
- if (typeof tokenSet.refresh_token !== "string" || tokenSet.refresh_token === "") {
185
- log.debug("Stored credentials hold no refresh_token");
186
- return null;
187
- }
188
- log.debug("Access token is missing or expired, attempting refresh");
189
- const next = await performRefresh(tokenSet, props, at);
190
- const apiKey = next.credentials.access_token ?? "";
191
- return {
192
- apiKey,
193
- apiClient: getApiClient({
194
- apiKey,
195
- apiHost: props.apiHost
196
- }),
197
- refreshed: next.rotated
198
- };
199
- };
200
- /**
201
- * Serialize exchanges because presenting a one-time refresh token twice can revoke
202
- * the newly issued token family. Persist immediately because the presented token is
203
- * invalid once the server answers.
204
- */
205
- const performRefresh = async (credentials, props, at) => {
206
- return await withExclusiveLock(refreshLockPath(props.configDir, at), async () => {
207
- const loaded = storeFor(props.configDir).read(at);
208
- const current = loaded?.credentials ?? credentials;
209
- if (loaded !== null && isAccessTokenUsable(current, Date.now()) && current.access_token !== credentials.access_token) {
210
- log.debug("Refresh lost a race; adopting the credentials another command wrote");
211
- return {
212
- credentials: current,
213
- rotated: false
214
- };
215
- }
216
- let refreshed;
217
- try {
218
- refreshed = extendTokenSet(await refreshToken({
219
- oauthHost: props.oauthHost,
220
- clientId: props.clientId,
221
- allowUnsafeTls: props.allowUnsafeTls
222
- }, current));
223
- } catch (err) {
224
- throw err instanceof AuthRefreshError ? err : classifyRefreshFailure(err);
225
- }
226
- const next = oauthCredentialsFromTokenSet(refreshed, loaded?.credentials ?? credentials);
227
- writeOAuthCredentials(props.configDir, at, next, { restorePrevious: false });
228
- log.debug("Token refresh successful");
229
- return {
230
- credentials: next,
231
- rotated: true
232
- };
233
- });
234
- };
235
- /** A 401 proves the access token is stale even before `expires_at`. */
236
- const refreshStoredCredentials = async (at, props) => {
237
- const loaded = storeFor(props.configDir).read(at);
238
- if (loaded === null) return false;
239
- if (interpretCredentials(loaded.credentials, at, loaded.backend).kind === "api_key") return false;
240
- if (typeof loaded.credentials.refresh_token !== "string" || loaded.credentials.refresh_token === "") return false;
241
- await performRefresh(loaded.credentials, props, at);
242
- return true;
243
- };
244
- const oauthCredentialsFromTokenSet = (tokenSet, previous) => {
245
- const refreshTokenValue = typeof tokenSet.refresh_token === "string" && tokenSet.refresh_token !== "" ? tokenSet.refresh_token : previous?.refresh_token;
246
- const stored = {
247
- type: OAUTH,
248
- ...typeof tokenSet.access_token === "string" && tokenSet.access_token !== "" ? { access_token: tokenSet.access_token } : {},
249
- expires_at: tokenSet.expires_at,
250
- ...typeof refreshTokenValue === "string" && refreshTokenValue !== "" ? { refresh_token: refreshTokenValue } : {}
251
- };
252
- if (previous?.user_id !== void 0) stored.user_id = previous.user_id;
253
- return stored;
254
- };
255
- const writeOAuthCredentials = (configDir, at, credentials, options) => {
256
- storeFor(configDir).write(at, credentials, options);
257
- log.debug("Saved credentials to %s", credentialLabel(at));
258
- log.debug("Credentials MD5 hash: %s", md5hash(JSON.stringify(credentials)));
259
- };
260
- const refreshLockPath = (configDir, at) => {
261
- const id = createHash("sha256").update(credentialLabel(at)).digest("hex").slice(0, 16);
262
- return join(configDir, `.refresh-${id}.lock`);
263
- };
264
- /**
265
- * The credential a stored file can authenticate with right now — its API key, or an OAuth
266
- * access token, refreshed first when it has expired. Never launches a browser.
267
- *
268
- * The `profile` subcommands need exactly this. They deliberately skip `ensureAuth`, but
269
- * `rotate-key` still has to call the API as the account it is rotating, and falling through to
270
- * an interactive login would be authenticating something other than what the command is about.
271
- */
272
- const usableCredential = async (props, at) => {
273
- const loaded = storeFor(props.configDir).read(at);
274
- if (loaded === null) return null;
275
- const credential = interpretCredentials(loaded.credentials, at, loaded.backend);
276
- if (credential.kind === "api_key") return {
277
- apiKey: credential.apiKey,
278
- kind: API_KEY
279
- };
280
- try {
281
- const result = await handleExistingToken(loaded.credentials, props, at);
282
- return result ? {
283
- apiKey: result.apiKey,
284
- kind: OAUTH
285
- } : null;
286
- } catch (err) {
287
- log.debug("Could not use the stored OAuth session at %s: %s", credentialLabel(at), err instanceof Error ? err.message : "unknown error");
288
- return null;
289
- }
290
- };
291
- const ensureAuth = async (props) => {
292
- if (props._.length === 0 || props.help) return;
293
- if (isCurrentBranchProbe(props)) return;
294
- if (isConfigInit(props)) return;
295
- if (isConfigAdd(props)) return;
296
- if (isFunctionsTemplateCommand(props)) return;
297
- if (isProfileCommand(props)) return;
298
- if (isGitLocalCommand(props)) return;
299
- if (isUnfollowedGitHookSync(props)) return;
300
- if (isMcpOauth(props)) return;
301
- if (isInspectDbUrl(props)) return;
302
- if (props._[0] === "open") return;
303
- if (isSkillsCommand(props) || isPluginsCommand(props)) return;
304
- if (isAskCommand(props) || isFeedbackCommand(props) || isDocsCommand(props)) return;
305
- if (props._[0] === "init") {
306
- selectCredential({
307
- ...credentialInputs(),
308
- profileFlag: props.profile
309
- });
310
- log.debug("init: skipping global auth; child commands authenticate");
311
- return;
312
- }
313
- if (isClaimCommand(props)) return;
314
- const isLocalDev = props._[0] === "dev";
315
- const isBootstrap = props._[0] === "bootstrap";
316
- const isMcp = isMcpCommand(props);
317
- if (props._[0] === "login" || props._[0] === "auth") {
318
- props.apiClient = getApiClient({
319
- apiKey: props.apiKey,
320
- apiHost: props.apiHost
321
- });
322
- return;
323
- }
324
- const inputs = credentialInputs();
325
- const contextFile = typeof props.contextFile === "function" ? props.contextFile() : props.contextFile ?? currentContextFile();
326
- const localContext = readContextFile(contextFile);
327
- if (shouldUseClaimableCredentials(inputs, props.profile, localContext, props.configDir)) {
328
- const stored = readLinkedClaimableCredentials(props.configDir, localContext);
329
- if (stored === null || localContext.projectId === void 0) throw new Error("The linked Claimable Neon identity assertion could not be read.");
330
- const path = claimableCredentialsPath(props.configDir, localContext.projectId);
331
- if (assertionHasExpired(stored)) throw new Error(`The identity assertion for ${localContext.projectId} has expired. Run \`neon claim delete ${localContext.projectId} --yes\` to drop the local record.`);
332
- const client = new ClaimableClient(stored.origin);
333
- let token;
334
- try {
335
- token = await client.exchange(stored.identityAssertion);
336
- } catch (error) {
337
- if (error instanceof ClaimableServiceError && error.code === "project_claimed") throw new Error("This project was claimed. Run `neon claim status` to drop the local assertion, then `neon login` or `neon link`.");
338
- throw error;
339
- }
340
- props.apiKey = token.accessToken;
341
- props.apiHost = `${client.origin}/v1`;
342
- props.apiClient = getApiClient({
343
- apiKey: token.accessToken,
344
- apiHost: props.apiHost
345
- });
346
- setAuthContext({
347
- source: "claimable",
348
- configDir: props.configDir,
349
- credentialsPath: path
350
- });
351
- log.debug("Using the linked Claimable Neon project's short-lived access token");
352
- return;
353
- }
354
- if (localContext.projectId !== void 0 && claimableCredentialsExist(props.configDir, localContext.projectId)) log.warning("This directory is linked to a claimable project, but NEON_API_KEY or NEON_PROFILE is set. This command will use that account credential instead of the unclaimed project. Unset them to keep using the unclaimed project.");
355
- const selection = selectCredential({
356
- ...inputs,
357
- profileFlag: props.profile
358
- });
359
- const displaced = displacedProfileWarning(selection);
360
- if (displaced !== null) log.warning(displaced);
361
- if (selection.source !== "profile") {
362
- props.apiKey = selection.apiKey;
363
- log.debug("Using an API key to authorize requests");
364
- setAuthContext({
365
- source: "api-key",
366
- apiKeyFrom: selection.source === "explicit-api-key" ? "flag" : "env",
367
- configDir: props.configDir
368
- });
369
- props.apiClient = getApiClient({
370
- apiKey: props.apiKey,
371
- apiHost: props.apiHost
372
- });
373
- return;
374
- }
375
- props.apiKey = "";
376
- const at = locationForAuth(props.configDir, selection.profile);
377
- const loaded = storeFor(props.configDir).read(at);
378
- if (loaded !== null) {
379
- log.debug("Trying to read credentials from %s", credentialLabel(at));
380
- const credential = interpretCredentials(loaded.credentials, at, loaded.backend);
381
- if (credential.kind === "api_key") {
382
- log.debug("Using profile \"%s\"'s API key to authorize requests", selection.profile);
383
- props.apiKey = credential.apiKey;
384
- setAuthContext({
385
- source: "profile-api-key",
386
- configDir: props.configDir,
387
- profile: selection.profile,
388
- storage: at.storage,
389
- ...at.storage === "file" ? { credentialsPath: at.path } : {}
390
- });
391
- props.apiClient = getApiClient({
392
- apiKey: credential.apiKey,
393
- apiHost: props.apiHost
394
- });
395
- return;
396
- }
397
- try {
398
- setAuthContext({
399
- source: "stored-credentials",
400
- configDir: props.configDir,
401
- profile: selection.profile,
402
- storage: at.storage,
403
- accessToken: loaded.credentials.access_token,
404
- refreshed: false,
405
- oauthHost: props.oauthHost,
406
- clientId: props.clientId,
407
- ...at.storage === "file" ? { credentialsPath: at.path } : {}
408
- });
409
- const result = await handleExistingToken(loaded.credentials, props, at);
410
- if (result) {
411
- props.apiKey = result.apiKey;
412
- props.apiClient = result.apiClient;
413
- setAuthContext({
414
- source: "stored-credentials",
415
- configDir: props.configDir,
416
- profile: selection.profile,
417
- storage: at.storage,
418
- accessToken: result.apiKey,
419
- refreshed: result.refreshed,
420
- oauthHost: props.oauthHost,
421
- clientId: props.clientId,
422
- ...at.storage === "file" ? { credentialsPath: at.path } : {}
423
- });
424
- return;
425
- }
426
- } catch (err) {
427
- if (err instanceof AuthRefreshError) {
428
- if (isLocalDev || isBootstrap || isMcp) {
429
- log.warning("%s Continuing without credentials.", err.message);
430
- return;
431
- }
432
- throw err;
433
- }
434
- throw err;
435
- }
436
- } else log.debug("No usable credentials at %s, starting authentication", credentialLabel(at));
437
- if (isLocalDev) {
438
- log.debug("dev: no usable credentials; running without env injection");
439
- return;
440
- }
441
- if (isBootstrap) {
442
- log.debug("bootstrap: no usable credentials; continuing without auth");
443
- return;
444
- }
445
- if (isMcp) {
446
- log.debug("mcp: no usable credentials; minting requires auth or --oauth");
447
- return;
448
- }
449
- if (props._[0] === "me") {
450
- const state = loaded === null ? "has no stored credential" : "holds a session that has expired";
451
- log.info(`Not signed in: profile "${selection.profile}" ${state}. Run \`${getCliName()} login --profile ${selection.profile}\` to sign in, or use an API key with --api-key or NEON_API_KEY.`);
452
- process.exitCode = 1;
453
- return;
454
- }
455
- const apiKey = await authFlow({
456
- ...props,
457
- profile: selection.profile
458
- });
459
- props.apiKey = apiKey;
460
- props.apiClient = getApiClient({
461
- apiKey,
462
- apiHost: props.apiHost
463
- });
464
- setAuthContext({
465
- source: "stored-credentials",
466
- configDir: props.configDir,
467
- profile: selection.profile,
468
- storage: at.storage,
469
- accessToken: apiKey,
470
- refreshed: true,
471
- oauthHost: props.oauthHost,
472
- clientId: props.clientId,
473
- ...at.storage === "file" ? { credentialsPath: at.path } : {}
474
- });
475
- };
476
- /**
477
- * Accept an exact location because deriving it from a config directory used to
478
- * delete DEFAULT when a named profile failed.
479
- */
480
- const deleteCredentialsAt = (at, configDir) => {
481
- try {
482
- const store = storeFor(configDir);
483
- const before = store.inspect(at);
484
- store.delete(at);
485
- if (before.credentials !== null || before.file === "ok") log.info("Deleted credentials from %s", credentialLabel(at));
486
- else log.debug("No stored credential at %s", credentialLabel(at));
487
- } catch (err) {
488
- const typedErr = err instanceof Error ? err : /* @__PURE__ */ new Error("Unknown error");
489
- log.error("Failed to delete credentials: %s", typedErr.message);
490
- throw new Error("CREDENTIALS_DELETE_FAILED");
491
- }
492
- };
493
- const md5hash = (s) => createHash("md5").update(s).digest("hex");
494
- //#endregion
495
- export { aliases, authFlow, builder, command, deleteCredentialsAt, describe, ensureAuth, handler, isAccessTokenUsable, locationForAuth, refreshStoredCredentials, auth_exports as t, usableCredential };
1
+ export { t as aliases, n as authFlow, i as builder, a as command, o as deleteCredentialsAt, s as describe, c as ensureAuth, l as handler, u as isAccessTokenUsable, d as locationForAuth, f as refreshStoredCredentials, p as usableCredential } from "../_chunks/auth-CIUtXByB.js";