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,672 +1 @@
1
- import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { t as writeSecretFile } from "../_chunks/secure_file-BucZj4yQ.js";
3
- import { c as describeScope, d as scopeOf, l as interpretCredentials, n as KEYRING_SERVICE, o as apiKeyCredentials, r as keyringAccount, s as credentialLabel, t as storeFor } from "../_chunks/credential_io-BS-0Pmwc.js";
4
- import { isCi } from "../env.js";
5
- import { log } from "../log.js";
6
- import { getApiClient, isNeonApiError } from "../api.js";
7
- import { setAuthContext } from "../auth_context.js";
8
- import { a as isOwnedCredentialPath, n as credentialsPath } from "../_chunks/paths-DMq0Lt7a.js";
9
- import { _ as upsertProfile, a as canDropProfilesFile, c as listProfiles, d as newProfileLocation, f as profilesFilePath, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, o as credentialsDisplay, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, u as locationOf, v as CRED_STORAGE_FILE, y as CRED_STORAGE_KEYRING } from "../_chunks/profiles-DIB1av2Q.js";
10
- import { auth, revokeToken } from "../auth.js";
11
- import { t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
12
- import { readOutgoingCredential, retirePreviousCredential, revokeTokenSet, withdrawKey } from "../retire_credential.js";
13
- import { authFlow, usableCredential } from "./auth.js";
14
- import { identityFromAuthDetails, isApiKeyMethod, mintedKeyName, notAnApiKeyMessage } from "../profile_keys.js";
15
- import { writer } from "../writer.js";
16
- import { noPassthrough, single } from "../utils/flags.js";
17
- import { orgIdForProject } from "./api_keys.js";
18
- import { existsSync, readFileSync, rmSync } from "node:fs";
19
- import { isAbsolute, relative } from "node:path";
20
- import prompts from "prompts";
21
- //#region src/commands/profile.ts
22
- var profile_exports = /* @__PURE__ */ __exportAll({
23
- aliases: () => aliases,
24
- builder: () => builder,
25
- command: () => command,
26
- describe: () => describe,
27
- handler: () => handler
28
- });
29
- const command = "profile";
30
- const aliases = ["profiles"];
31
- const describe = "Manage named sets of Neon credentials";
32
- const builder = (argv) => argv.usage("$0 profile <sub-command> [options]").command("list", "List profiles, the account each holds, and where its credentials live", (y) => y.strict().check(noPassthrough("profile list")), async (args) => await list(args)).command("create <name>", "Create a profile. It holds either a browser sign-in or an API key, never both", (y) => y.positional("name", {
33
- describe: "Name for the new profile",
34
- type: "string",
35
- demandOption: true
36
- }).options({
37
- "api-key": {
38
- describe: "API key to store, or \"-\" to read it from stdin",
39
- defaultDescription: "none; this command ignores NEON_API_KEY",
40
- type: "string",
41
- nargs: 1
42
- },
43
- mint: {
44
- describe: "Sign in once in the browser, then store a freshly minted API key and nothing else",
45
- type: "boolean",
46
- default: false
47
- },
48
- "org-id": {
49
- describe: "With --mint, mint a key for this organization instead of your account",
50
- type: "string",
51
- coerce: single("org-id")
52
- },
53
- "project-id": {
54
- describe: "With --mint, mint a key that can access only this project",
55
- type: "string",
56
- coerce: single("project-id")
57
- },
58
- keyring: {
59
- describe: "Store the credential in the OS keyring. Per profile; later create without the flag stays there. See `neon profile list`.",
60
- type: "boolean"
61
- },
62
- force: {
63
- hidden: true,
64
- type: "boolean"
65
- }
66
- }).conflicts("org-id", "project-id").strict().check((argv) => {
67
- noPassthrough("profile create")(argv);
68
- if (argv.force === true) {
69
- const name = typeof argv.name === "string" ? argv.name : "NAME";
70
- throw new Error(`--force is no longer a flag. \`neon profile create ${name}\` always replaces an existing profile and revokes the credential it held. Drop --force.`);
71
- }
72
- return true;
73
- }).example("$0 profile create work", "Sign in with the browser, like `neon login --profile work`").example("$0 profile create work --api-key \"$KEY\"", "Store a key you already have").example("echo \"$KEY\" | $0 profile create work --api-key -", "Or pipe it, so it never reaches the process arguments").example("$0 profile create ci --mint --org-id org-abc-123", "One browser sign-in, then an org-scoped key and no session left behind").example("$0 profile create work --keyring", "Store the credential in the OS keyring instead of a file"), async (args) => await create(args)).command("rotate-key <name>", "Mint a fresh API key for a profile, at the same scope, and revoke the one it replaces", (y) => y.positional("name", {
74
- describe: "Profile to rotate",
75
- type: "string",
76
- demandOption: true
77
- }).strict().check(noPassthrough("profile rotate-key")), async (args) => await rotateKey(args)).command("remove <name>", "Revoke a profile's credential where possible, and remove it", (y) => y.positional("name", {
78
- describe: "Profile to remove",
79
- type: "string",
80
- demandOption: true
81
- }).option("yes", {
82
- alias: "y",
83
- describe: "Skip the confirmation prompt",
84
- type: "boolean",
85
- default: false
86
- }).strict().check(noPassthrough("profile remove")), async (args) => await remove(args));
87
- const handler = (_args) => {};
88
- /**
89
- * Refuse a global credential flag that a `profile` subcommand cannot honour.
90
- *
91
- * These commands name their target as a positional and authenticate as that profile, so both
92
- * `--profile` and `--api-key` have nothing to select here and were silently dropped — which is
93
- * precisely the class of bug this command family exists to stop. `.strict()` cannot catch
94
- * either, because both are global options and yargs has already accepted them.
95
- *
96
- * No "did you mean". The suggestion this used to make was built from the flag rather than the
97
- * positional, so it renamed the target: `profile remove work --profile other` answered "did you
98
- * mean `neon profile remove other`". Since the name is a required positional, that was wrong in
99
- * every case it fired, and on `remove` it was copy-pasteable destructive advice for an account
100
- * the user had not mentioned.
101
- */
102
- const rejectProfileFlag = (props, sub) => {
103
- const named = props.profile?.trim();
104
- if (!named) return;
105
- throw new Error(`--profile does not apply to \`profile ${sub}\`, which takes the profile name as an argument. You passed both "${props.name}" and --profile ${named}; drop --profile.`);
106
- };
107
- /**
108
- * Refuse `--api-key` on a subcommand that authenticates as the profile it was given.
109
- *
110
- * The mirror image of the flag above, and it was the one left silent: `profile remove work
111
- * --api-key napi_…` ignored the key, used the stored one, and reported a revoke failure against
112
- * a credential the user had not passed. This PR exists because a dropped credential flag chose
113
- * the wrong account; leaving that unsaid on the commands that manage credentials would be the
114
- * same bug with the arguments swapped.
115
- */
116
- const rejectApiKeyFlag = (sub, instead) => {
117
- if (credentialInputs().apiKeyFlag.trim() === "") return;
118
- throw new Error(`--api-key does not apply to \`profile ${sub}\`, which uses the credential the profile already holds.${instead ? ` ${instead}` : ""}`);
119
- };
120
- /**
121
- * What kind of credential a file holds, for display.
122
- *
123
- * A file that declares a kind we cannot read is reported as `invalid` with the reason logged,
124
- * rather than throwing: one broken profile must not hide every other row, and the whole point
125
- * of `list` is to show the state of things including the broken parts.
126
- */
127
- const hintStore = (storage) => storage === "keyring" ? "keyring" : "file";
128
- const describeAuth = (stored, at, store = "file") => {
129
- if (stored === null) return "-";
130
- try {
131
- return interpretCredentials(stored, at, store).kind === "api_key" ? "api key" : "oauth";
132
- } catch (err) {
133
- log.warning(err instanceof Error ? err.message : String(err));
134
- return "invalid";
135
- }
136
- };
137
- /**
138
- * One row's view of a credentials file.
139
- *
140
- * Reports a damaged file as `invalid` and carries on, because one broken profile must not take
141
- * the whole listing with it — and unlike the authenticating path, listing has nothing to
142
- * recover, so the row itself is the report.
143
- *
144
- * One column answers "can this be used". A file whose JSON parses but whose `type` is
145
- * unreadable used to show `File: ok` beside `Auth: invalid`, so the column a user scans to find
146
- * the broken profile pointed at a different row than the one that was broken.
147
- */
148
- const inspectForListing = (configDir, at) => {
149
- const listing = storeFor(configDir).inspect(at);
150
- if (listing.reason !== void 0) log.warning(listing.reason);
151
- if (listing.credentials === null) return {
152
- stored: null,
153
- auth: "-",
154
- file: listing.file,
155
- storage: listing.storage
156
- };
157
- const auth = describeAuth(listing.credentials, at, hintStore(listing.storage));
158
- return {
159
- stored: listing.credentials,
160
- auth,
161
- file: auth === "invalid" ? "invalid" : listing.file,
162
- storage: listing.storage
163
- };
164
- };
165
- const list = async (props) => {
166
- rejectApiKeyFlag("list");
167
- const active = selectProfileName(props.profile);
168
- if (props.profile !== void 0 && props.profile.trim() !== "") resolveProfile(props.configDir, active);
169
- const rows = listProfiles(props.configDir).map((p) => {
170
- const { stored, auth, file, storage } = inspectForListing(props.configDir, locationOf(p));
171
- const storedUserId = typeof stored?.user_id === "string" ? stored.user_id : void 0;
172
- return {
173
- active: p.name === active ? "*" : "",
174
- name: p.name,
175
- account: p.label ?? p.userId ?? storedUserId ?? "-",
176
- auth,
177
- scope: auth === "oauth" ? describeScope({}) : stored !== null && auth === "api key" ? describeScope(scopeOf(stored)) : "-",
178
- file,
179
- storage,
180
- credentials: props.output === "table" ? credentialsListValue(p, props.configDir) : credentialsDisplay(p)
181
- };
182
- });
183
- writer(props).end(rows, {
184
- title: "Profiles",
185
- fields: [
186
- "active",
187
- "name",
188
- "account",
189
- "auth",
190
- "credentials",
191
- "scope"
192
- ]
193
- });
194
- };
195
- const credentialsListValue = (profile, configDir) => {
196
- if (profile.storage === "keyring") return KEYRING_CREDENTIALS;
197
- const rel = relative(configDir, profile.credentialsPath);
198
- if (rel.startsWith("..") || isAbsolute(rel)) return profile.credentialsPath;
199
- return rel;
200
- };
201
- const locationForCreate = (configDir, name, keyring) => {
202
- assertProfilesUsable(configDir, name);
203
- const declared = readProfiles(configDir)?.profiles[name];
204
- const pointer = declared !== void 0 && isKeyringPointer(declared.credentials);
205
- if (keyring === true || pointer) return {
206
- profile: name,
207
- storage: CRED_STORAGE_KEYRING
208
- };
209
- if (declared !== void 0 || name === "DEFAULT") return locationForName(configDir, name);
210
- return newProfileLocation(configDir, name, CRED_STORAGE_FILE);
211
- };
212
- const existingLocation = (configDir, name) => {
213
- try {
214
- if (readProfiles(configDir)?.profiles[name] !== void 0 || name === "DEFAULT") return locationForName(configDir, name);
215
- } catch {
216
- return null;
217
- }
218
- return null;
219
- };
220
- const warnIfKeyringRollbackUnconfirmed = (cleared, profile) => {
221
- if (cleared === "cleared") return;
222
- log.warning("Could not confirm the new OS keyring item for profile \"%s\" was removed after a failed save.", profile);
223
- };
224
- const deleteLeftoverOwnedFile = (configDir, path, except) => {
225
- if (!isOwnedCredentialPath(configDir, path)) return;
226
- if (profilesUsingPath(configDir, path, except).length > 0) return;
227
- try {
228
- storeFor(configDir).delete({
229
- profile: except,
230
- storage: CRED_STORAGE_FILE,
231
- path
232
- });
233
- } catch {
234
- log.warning("Saved the keyring item but could not delete %s", path);
235
- }
236
- };
237
- /** `--api-key -` means "read it from stdin", the usual convention for a piped value. */
238
- const STDIN = "-";
239
- /**
240
- * The key to store.
241
- *
242
- * Read from the *flag* and never from `NEON_API_KEY`. Everywhere else those are
243
- * interchangeable, but "store this credential permanently" is not something an exported
244
- * environment variable should be able to answer — a `create` that silently wrote whatever
245
- * happened to be in the shell would be storing a key the user never named.
246
- *
247
- * One flag, deliberately. Earlier revisions added `--api-key-file`, `--api-key-stdin` and
248
- * `--api-key-prompt` to keep the secret out of argv, which is four surfaces to document and
249
- * validate against each other for things the shell already does — `--api-key "$(cat file)"`
250
- * reads a file, `--api-key "$KEY"` takes a variable. Piping is the one case the shell cannot
251
- * express through an argument, so it gets the usual convention instead of a flag: `-`.
252
- */
253
- const resolveKeyToStore = (props) => {
254
- const fromFlag = credentialInputs().apiKeyFlag.trim();
255
- if (fromFlag === STDIN) return readKeyFromStdin();
256
- if (fromFlag !== "") return fromFlag;
257
- throw new Error(`Nothing to store for profile "${props.name}". Pass --api-key (or --api-key - to pipe it), --mint to have one minted, or no flags at all to sign in with the browser.`);
258
- };
259
- /**
260
- * Read the key from stdin, for `--api-key -`.
261
- *
262
- * A terminal is refused rather than read: `readFileSync(0)` on a tty waits for EOF with no
263
- * prompt and nothing echoed, which is indistinguishable from a hang — and this spelling exists
264
- * for pipes, so a terminal means the pipe is missing.
265
- */
266
- const readKeyFromStdin = () => {
267
- if (process.stdin.isTTY) throw new Error("`--api-key -` reads the key from stdin, but stdin is a terminal. Pipe it in: echo \"$KEY\" | neon profile create <name> --api-key -");
268
- const piped = readFileSync(0, "utf8").trim();
269
- if (piped === "") throw new Error("Nothing arrived on stdin, so there is no API key to store.");
270
- return piped;
271
- };
272
- /**
273
- * Confirm a key works and find out whose it is, before it is written anywhere.
274
- *
275
- * `getAuthDetails` rather than `GET /users/me`: an organization key gets `404 not allowed for
276
- * organization API keys` from the latter, so asking would fail a perfectly good key.
277
- */
278
- const verifyKey = async (props, apiKey, origin = "supplied") => {
279
- const apiClient = getApiClient({
280
- apiKey,
281
- apiHost: props.apiHost
282
- });
283
- const { data: details } = await apiClient.getAuthDetails().catch((err) => {
284
- if (isNeonApiError(err) && err.status === 401) throw new Error(origin === "minted" ? "Neon rejected the key it had just minted. Nothing was stored; the key is being revoked." : "The Neon API rejected the key passed to --api-key. Check the value, or use --mint to have one minted for this profile.");
285
- throw err;
286
- });
287
- if (!isApiKeyMethod(details.auth_method)) throw new Error(notAnApiKeyMessage(details.auth_method));
288
- const email = details.auth_method === "api_key_user" ? (await apiClient.getCurrentUserInfo()).data.email : void 0;
289
- return {
290
- ...identityFromAuthDetails(details, email),
291
- ...details.auth_method === "api_key_org" ? { orgId: details.account_id } : {},
292
- apiClient
293
- };
294
- };
295
- const create = async (props) => {
296
- const { name } = props;
297
- rejectProfileFlag(props, "create");
298
- assertValidProfileName(name);
299
- assertProfilesUsable(props.configDir, name);
300
- if (props.keyring === true) storeFor(props.configDir).assertKeyringWritable(existingLocation(props.configDir, name)?.storage === "keyring" ? name : void 0);
301
- const suppliedKey = credentialInputs().apiKeyFlag.trim() !== "";
302
- if (props.mint) {
303
- if (suppliedKey) throw new Error("--mint creates a new key, so it cannot be combined with --api-key. Drop one.");
304
- await createByMinting(props);
305
- return;
306
- }
307
- if ((props.orgId ?? props.projectId) !== void 0) throw new Error("--org-id and --project-id only apply with --mint: they choose what a key we mint can reach, and a key you supply already has a scope.");
308
- if (!suppliedKey) {
309
- if (isCi() && props.forceAuth !== true) throw new Error(`\`neon profile create ${name}\` with no key signs in through the browser, which cannot happen in CI. Pass a key instead: \`neon profile create ${name} --api-key "$KEY"\`, or pipe it with \`echo "$KEY" | neon profile create ${name} --api-key -\`.`);
310
- const at = locationForCreate(props.configDir, name, props.keyring);
311
- const existing = existingLocation(props.configDir, name);
312
- const previous = existing === null ? null : readOutgoingCredential(props.configDir, existing);
313
- if (await authFlow({
314
- ...props,
315
- _: ["auth"],
316
- profile: name,
317
- keyring: props.keyring
318
- }) === "") throw new Error(`Could not save credentials for profile "${name}".`);
319
- if (at.storage !== "keyring") await retirePreviousCredential(props, name, previous);
320
- const signedIn = readProfiles(props.configDir, log.warning)?.profiles[name];
321
- report(props, {
322
- name,
323
- account: signedIn?.label ?? signedIn?.userId ?? "unknown account",
324
- auth: "oauth",
325
- scope: "-",
326
- credentials: at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path,
327
- storage: at.storage
328
- });
329
- return;
330
- }
331
- const apiKey = resolveKeyToStore(props);
332
- const identity = await verifyKey(props, apiKey);
333
- const at = locationForCreate(props.configDir, name, props.keyring);
334
- const existing = existingLocation(props.configDir, name);
335
- if (at.storage === "keyring") storeFor(props.configDir).assertKeyringWritable(existing?.storage === "keyring" ? name : void 0);
336
- const previous = existing === null ? null : readOutgoingCredential(props.configDir, existing);
337
- storeFor(props.configDir).write(at, apiKeyCredentials({
338
- apiKey,
339
- ...identity.userId !== void 0 ? { userId: identity.userId } : {},
340
- ...identity.orgId !== void 0 ? { scope: { orgId: identity.orgId } } : {}
341
- }));
342
- try {
343
- recordProfile(props, name, at, identity);
344
- } catch (err) {
345
- if (at.storage === "keyring" && existing?.storage !== "keyring") warnIfKeyringRollbackUnconfirmed(storeFor(props.configDir).delete(at, { required: false }), name);
346
- throw err instanceof Error ? err : new Error(String(err));
347
- }
348
- await retirePreviousCredential(props, name, previous, apiKey);
349
- if (at.storage === "keyring" && existing?.storage === "file") deleteLeftoverOwnedFile(props.configDir, existing.path, name);
350
- report(props, {
351
- name,
352
- account: identity.label ?? "unknown account",
353
- auth: "api key",
354
- scope: describeScope(identity.orgId !== void 0 ? { orgId: identity.orgId } : {}),
355
- credentials: at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path,
356
- storage: at.storage
357
- });
358
- log.warning("A key you supplied cannot be revoked by `rotate-key` or `remove` — only keys minted here record an id.");
359
- };
360
- /**
361
- * What a minted key can reach, in the same words `neon api-keys create` uses for the same key.
362
- *
363
- * Saying nothing here would be worse than there: this is the command aimed at agents and shared
364
- * machines, which is exactly where an over-broad credential does the most damage.
365
- */
366
- const warnAboutReach = (scope) => {
367
- if (scope.projectId !== void 0) {
368
- log.info("Limited to %s: it cannot create projects, mint API keys, or read any other project. It can still change and delete everything inside that project.", scope.projectId);
369
- return;
370
- }
371
- if (scope.orgId !== void 0) {
372
- log.warning("This key reaches every project in %s, including ones created later. Pass --project-id instead to restrict it to one.", scope.orgId);
373
- return;
374
- }
375
- log.warning("This key reaches everything your account can, in every organization. Pass --org-id or --project-id to narrow it.");
376
- };
377
- /**
378
- * Report a profile that was just written.
379
- *
380
- * Through the writer rather than `log`, so `--output json` yields the record an agent needs
381
- * instead of nothing — it had to follow up with `profile list` to learn what it just created.
382
- */
383
- const report = (props, record) => {
384
- const out = writer(props);
385
- if (props.output === "table") {
386
- log.info("Profile \"%s\" now holds %s for %s (%s), in %s", record.name, record.auth, record.account, record.scope, record.credentials);
387
- return;
388
- }
389
- out.end(record, {
390
- fields: [
391
- "name",
392
- "account",
393
- "auth",
394
- "scope",
395
- ...record.keyId !== void 0 ? ["keyId"] : [],
396
- "credentials",
397
- ...record.storage !== void 0 ? ["storage"] : []
398
- ],
399
- title: "Profile"
400
- });
401
- };
402
- const recordProfile = (props, name, at, identity) => {
403
- upsertProfile(props.configDir, name, {
404
- credentials: at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path,
405
- ...identity.label ? { label: identity.label } : {},
406
- ...identity.userId ? { userId: identity.userId } : {}
407
- });
408
- };
409
- /**
410
- * Sign in once, mint a key, keep only the key.
411
- *
412
- * This is the reason to prefer a key-backed profile at all: after it, nothing about the profile
413
- * can trigger a browser. The OAuth session is a means to an end and is revoked at the end of
414
- * it, so no half-forgotten login is left behind — which also means the file holds exactly one
415
- * credential, for exactly one account.
416
- */
417
- const createByMinting = async (props) => {
418
- const { name } = props;
419
- if (isCi() && props.forceAuth !== true) throw new Error(`--mint needs a browser sign-in, which cannot happen in CI. Mint the key with \`neon api-keys create --name ${name}\` and pipe it in: echo "$KEY" | neon profile create ${name} --api-key -`);
420
- if (locationForCreate(props.configDir, name, props.keyring).storage === "keyring") storeFor(props.configDir).assertKeyringWritable(existingLocation(props.configDir, name)?.storage === "keyring" ? name : void 0);
421
- const oauthProps = {
422
- oauthHost: props.oauthHost,
423
- clientId: props.clientId,
424
- ...props.allowUnsafeTls ? { allowUnsafeTls: props.allowUnsafeTls } : {}
425
- };
426
- const tokenSet = await auth(oauthProps);
427
- const session = getApiClient({
428
- apiKey: tokenSet.access_token ?? "",
429
- apiHost: props.apiHost
430
- });
431
- let minted;
432
- let mintedScope = {};
433
- let keyIsReachable = false;
434
- try {
435
- const scope = await resolveMintScope(props, session);
436
- mintedScope = scope;
437
- minted = await mintKey(session, name, scope);
438
- const identity = await verifyKey(props, minted.key, "minted");
439
- const at = locationForCreate(props.configDir, name, props.keyring);
440
- const existing = existingLocation(props.configDir, name);
441
- const previous = existing === null ? null : readOutgoingCredential(props.configDir, existing);
442
- storeFor(props.configDir).write(at, apiKeyCredentials({
443
- apiKey: minted.key,
444
- keyId: minted.id,
445
- ...identity.userId !== void 0 ? { userId: identity.userId } : {},
446
- scope
447
- }));
448
- try {
449
- recordProfile(props, name, at, identity);
450
- } catch (err) {
451
- if (at.storage === "keyring" && existing?.storage !== "keyring") warnIfKeyringRollbackUnconfirmed(storeFor(props.configDir).delete(at, { required: false }), name);
452
- else keyIsReachable = true;
453
- throw err instanceof Error ? err : new Error(String(err));
454
- }
455
- await retirePreviousCredential(props, name, previous, minted.key);
456
- if (at.storage === "keyring" && existing?.storage === "file") deleteLeftoverOwnedFile(props.configDir, existing.path, name);
457
- keyIsReachable = true;
458
- report(props, {
459
- name,
460
- account: identity.label ?? "unknown account",
461
- auth: "api key",
462
- scope: describeScope(scope),
463
- keyId: minted.id,
464
- credentials: at.storage === "keyring" ? KEYRING_CREDENTIALS : at.path,
465
- storage: at.storage
466
- });
467
- warnAboutReach(scope);
468
- } finally {
469
- if (minted !== void 0 && !keyIsReachable) {
470
- if (await withdrawKey(session, mintedScope, minted.id)) log.info(`Revoked the key that was minted but not stored (id ${minted.id}).`);
471
- else log.warning(`Minted key ${minted.id} could be neither stored nor revoked, and may still be live. Remove it with: neon api-keys revoke ${minted.id}${mintedScope.orgId ? ` --org-id ${mintedScope.orgId}` : ""}`);
472
- }
473
- const revoked = await revokeToken(oauthProps, tokenSet);
474
- log.info(revoked ? "Signed the browser session back out, so nothing but the key remains." : "Could not sign the browser session back out; it will expire on its own.");
475
- }
476
- };
477
- /**
478
- * The scope to mint at, resolving a project to the organization that owns it.
479
- *
480
- * A project-scoped key exists only on the organization endpoint, so the org has to be looked up
481
- * rather than asked for — `--project-id` alone would otherwise fail for a reason invisible from
482
- * the command line.
483
- */
484
- const resolveMintScope = async (props, client) => {
485
- if (props.projectId === void 0) return props.orgId !== void 0 ? { orgId: props.orgId } : {};
486
- return {
487
- orgId: await orgIdForProject(client, props.projectId),
488
- projectId: props.projectId
489
- };
490
- };
491
- /**
492
- * Mint a key at the given scope and refuse anything that came back different.
493
- *
494
- * A 2xx is not enough. A response with no `key` leaves a live credential the user can never
495
- * see, and a `project_id` that does not match would mean storing a credential whose reach we
496
- * announced wrongly. Both are withdrawn before throwing.
497
- */
498
- const mintKey = async (client, profile, scope) => {
499
- const keyName = mintedKeyName(profile);
500
- const { data } = scope.orgId ? await client.createOrgApiKey(scope.orgId, {
501
- key_name: keyName,
502
- ...scope.projectId ? { project_id: scope.projectId } : {}
503
- }) : await client.createApiKey({ key_name: keyName });
504
- const issuedProject = "project_id" in data && typeof data.project_id === "string" ? data.project_id : void 0;
505
- const problem = typeof data.key !== "string" || data.key.trim() === "" ? "Neon returned no key." : issuedProject !== scope.projectId ? `Neon returned a key scoped to ${issuedProject ?? "nothing"} rather than ${scope.projectId ?? "the whole organization"}.` : null;
506
- if (problem === null) return {
507
- id: data.id,
508
- key: data.key,
509
- name: data.name
510
- };
511
- const withdrawn = await withdrawKey(client, scope, data.id);
512
- throw new Error(`${problem} ${withdrawn ? "The key has been revoked; nothing was stored." : `The key could NOT be revoked and may still be live. Remove it with \`neon api-keys revoke ${data.id}${scope.orgId ? ` --org-id ${scope.orgId}` : ""}\`.`}`);
513
- };
514
- const rotateKey = async (props) => {
515
- const { name } = props;
516
- rejectProfileFlag(props, "rotate-key");
517
- rejectApiKeyFlag("rotate-key", `To store a key you already have, use \`neon profile create ${name} --api-key -\`.`);
518
- const profile = resolveProfile(props.configDir, name);
519
- const at = locationOf(profile);
520
- const credential = await usableCredential(props, at);
521
- if (credential === null) throw new Error(`Profile "${name}" has no usable credential to mint with. Replace it with \`neon profile create ${name} --mint\`.`);
522
- if (credential.kind !== "api_key") throw new Error(`Profile "${name}" holds a browser sign-in, not an API key, so there is no key to rotate. Turn it into a key profile with \`neon profile create ${name} --mint\`.`);
523
- const stored = storeFor(props.configDir).read(at)?.credentials ?? null;
524
- const scope = stored !== null ? scopeOf(stored) : {};
525
- const previousKeyId = typeof stored?.key_id === "number" ? stored.key_id : void 0;
526
- const minting = getApiClient({
527
- apiKey: credential.apiKey,
528
- apiHost: props.apiHost
529
- });
530
- setAuthContext({
531
- source: "profile-api-key",
532
- configDir: props.configDir,
533
- profile: name,
534
- storage: at.storage,
535
- ...at.storage === "file" ? { credentialsPath: at.path } : {}
536
- });
537
- const { data: details } = await minting.getAuthDetails();
538
- if (details.auth_method === "api_key_org") {
539
- const advice = scope.projectId !== void 0 ? `\`neon profile create ${name} --mint --project-id ${scope.projectId}\`` : scope.orgId !== void 0 && previousKeyId !== void 0 ? `\`neon profile create ${name} --mint --org-id ${scope.orgId}\`` : `\`neon profile create ${name} --mint --org-id ${details.account_id}\` — but check \`neon api-keys list --org-id ${details.account_id}\` first, because a key you supplied may have been narrowed to a single project and an organization key would reach more`;
540
- throw new Error(`Profile "${name}" holds an organization key, and only a personal credential can mint organization keys — so it cannot mint its own replacement. Sign in and mint one with ${advice}.`);
541
- }
542
- const created = await mintKey(minting, name, scope);
543
- log.info("Minted %s (id %d, %s)", created.name, created.id, describeScope(scope));
544
- try {
545
- storeFor(props.configDir).write(at, apiKeyCredentials({
546
- apiKey: created.key,
547
- keyId: created.id,
548
- ...typeof stored?.user_id === "string" ? { userId: stored.user_id } : {},
549
- scope
550
- }));
551
- } catch (err) {
552
- log.error("Minted key %d but could not write %s. Revoke it with: neon api-keys revoke %d%s", created.id, credentialLabel(at), created.id, scope.orgId ? ` --org-id ${scope.orgId}` : "");
553
- throw err;
554
- }
555
- report(props, {
556
- name,
557
- account: profile.label ?? profile.userId ?? "unknown account",
558
- auth: "api key",
559
- scope: describeScope(scope),
560
- keyId: created.id,
561
- credentials: credentialsDisplay(profile),
562
- storage: at.storage
563
- });
564
- if (previousKeyId === void 0) {
565
- log.warning("No previous key id was recorded, so the key this replaces is still live. It was supplied rather than minted here, and `GET /api_keys` exposes no prefix to match it by — find it with `neon api-keys list`.");
566
- return;
567
- }
568
- const rotated = getApiClient({
569
- apiKey: created.key,
570
- apiHost: props.apiHost
571
- });
572
- if (await withdrawKey(rotated, scope, previousKeyId)) {
573
- log.info("Revoked the previous key (id %d)", previousKeyId);
574
- return;
575
- }
576
- log.warning("Stored the new key, but could not revoke the previous one (id %d) — it may still be live. Revoke it with: neon api-keys revoke %d%s", previousKeyId, previousKeyId, scope.orgId ? ` --org-id ${scope.orgId}` : "");
577
- };
578
- const remove = async (props) => {
579
- const { name } = props;
580
- rejectProfileFlag(props, "remove");
581
- rejectApiKeyFlag("remove", "It revokes the credential stored for that profile, which is the only one it can revoke.");
582
- const profile = resolveProfile(props.configDir, name);
583
- if (!props.yes) {
584
- if (isCi() || !process.stdin.isTTY) throw new Error("Refusing to remove a profile without confirmation when stdin is not a terminal. Pass --yes.");
585
- const who = profile.label ?? profile.userId ?? "unknown account";
586
- const { ok } = await prompts({
587
- type: "confirm",
588
- name: "ok",
589
- message: `Remove profile "${name}" (${who})?`,
590
- initial: false
591
- });
592
- if (!ok) throw new Error(`Cancelled — profile "${name}" was not removed.`);
593
- }
594
- const at = locationOf(profile);
595
- let stored = null;
596
- try {
597
- stored = readOutgoingCredential(props.configDir, at);
598
- } catch (err) {
599
- if (at.storage === "keyring") log.warning("Could not read the OS keyring item for profile \"%s\"; nothing in it could be revoked.", name);
600
- else log.warning("%s. Nothing in it could be revoked.", (err instanceof Error ? err.message : String(err)).replace(/\.$/, ""));
601
- }
602
- if (stored?.kind === "api_key") {
603
- const { keyId, scope } = stored;
604
- if (keyId === void 0) log.warning("Profile \"%s\" holds an API key that was supplied rather than minted here, so it stays live on the account — find it with `neon api-keys list`.", name);
605
- else {
606
- const client = getApiClient({
607
- apiKey: stored.apiKey,
608
- apiHost: props.apiHost
609
- });
610
- if (await withdrawKey(client, scope, keyId)) log.info(`Revoked the API key (id ${keyId})`);
611
- else log.warning(`Could not revoke the API key (id ${keyId}) — it may still be live. Remove it with: neon api-keys revoke ${keyId}${scope.orgId ? ` --org-id ${scope.orgId}` : ""}`);
612
- }
613
- } else if (stored !== null) {
614
- const revoked = await revokeTokenSet(stored.tokens, props);
615
- log.info(revoked ? "Revoked the OAuth token" : "Could not revoke the OAuth token — removing locally anyway");
616
- }
617
- const dropPointer = () => {
618
- assertProfilesUsable(props.configDir, name);
619
- const path = profilesFilePath(props.configDir);
620
- const file = readProfiles(props.configDir, log.warning);
621
- if (file?.profiles[name]) {
622
- delete file.profiles[name];
623
- if (canDropProfilesFile(file)) {
624
- rmSync(path);
625
- log.info("Removed \"%s\" — no profiles left, deleted %s", name, path);
626
- } else {
627
- writeSecretFile(path, `${JSON.stringify(file, null, 2)}\n`);
628
- log.info("Removed \"%s\" from %s", name, path);
629
- }
630
- } else if (name === "DEFAULT") log.info("Signed out of DEFAULT");
631
- };
632
- const listing = storeFor(props.configDir).inspect(at);
633
- if (at.storage === "keyring") {
634
- const account = keyringAccount(props.configDir, name);
635
- if (name === "DEFAULT") {
636
- const leftover = credentialsPath(props.configDir);
637
- if (isOwnedCredentialPath(props.configDir, leftover) && existsSync(leftover) && profilesUsingPath(props.configDir, leftover, name).length === 0) {
638
- storeFor(props.configDir).delete({
639
- profile: name,
640
- storage: CRED_STORAGE_FILE,
641
- path: leftover
642
- }, { required: false });
643
- if (existsSync(leftover)) throw new Error(`Could not delete ${leftover}. Refusing to reset DEFAULT — that file would become the profile.`);
644
- log.info("Deleted %s", leftover);
645
- }
646
- }
647
- dropPointer();
648
- if (listing.reason?.includes("cannot use the OS keyring")) {
649
- log.warning("This CLI cannot delete the OS keyring item for profile \"%s\". It remains in the OS store (service %s, account %s) until a CLI with the keyring addon removes it, or you delete it there.", name, KEYRING_SERVICE, account);
650
- return;
651
- }
652
- let cleared = "unconfirmed";
653
- try {
654
- cleared = storeFor(props.configDir).delete(at, {
655
- required: false,
656
- account
657
- });
658
- } catch {
659
- cleared = "unconfirmed";
660
- }
661
- if (cleared === "cleared") log.info("Deleted the OS keyring item for profile \"%s\"", name);
662
- else log.warning("Could not confirm the OS keyring item for profile \"%s\" is gone. A leftover may still be in the OS store (service %s, account %s); it is not used once the profile is gone.", name, KEYRING_SERVICE, account);
663
- return;
664
- }
665
- if (isOwnedCredentialPath(props.configDir, at.path)) {
666
- storeFor(props.configDir).delete(at);
667
- if (listing.credentials !== null || listing.file !== "missing") log.info("Deleted %s", at.path);
668
- } else if (existsSync(at.path)) log.info("Left %s on disk — not created by neon", at.path);
669
- dropPointer();
670
- };
671
- //#endregion
672
- export { aliases, builder, command, describe, handler, profile_exports as t };
1
+ export { t as aliases, n as builder, r as command, i as describe, a as handler } from "../_chunks/profile-yXOiKk9R.js";