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.
- package/README.md +1 -1
- package/dist/_chunks/api-GbEaBZJ5.js +388 -0
- package/dist/_chunks/api_keys-CNnnugbQ.js +281 -0
- package/dist/_chunks/ask-DVwy0eUL.js +155 -0
- package/dist/_chunks/auth-CIUtXByB.js +495 -0
- package/dist/_chunks/bootstrap-wPZWa1sT.js +499 -0
- package/dist/_chunks/branches-BNa1xX1L.js +460 -0
- package/dist/_chunks/bucket-BCdryTkD.js +409 -0
- package/dist/_chunks/checkout-CXIOydqZ.js +348 -0
- package/dist/_chunks/claim-DqAvAvDm.js +473 -0
- package/dist/_chunks/config-BH_qRHiG.js +1113 -0
- package/dist/_chunks/connection_string-DB4awPzZ.js +154 -0
- package/dist/_chunks/credentials-BSjE9fRR.js +146 -0
- package/dist/_chunks/data_api-Djxj5ZQD.js +263 -0
- package/dist/_chunks/databases-Dok4wru7.js +84 -0
- package/dist/_chunks/deploy-D7B0Xfjg.js +33 -0
- package/dist/_chunks/dev-CSvxAecE.js +945 -0
- package/dist/_chunks/diff-DhxxbdQE.js +178 -0
- package/dist/_chunks/docs-CrVybkTP.js +197 -0
- package/dist/_chunks/env-5HS5qQGd.js +284 -0
- package/dist/_chunks/feedback-CMayyTa8.js +88 -0
- package/dist/_chunks/functions-Q0zuvtnk.js +464 -0
- package/dist/_chunks/git-H32TNt6M.js +356 -0
- package/dist/_chunks/init-BkA6RilG.js +111 -0
- package/dist/_chunks/inspect-g0wekylj.js +90 -0
- package/dist/_chunks/ip_allow-5lLFvIuE.js +106 -0
- package/dist/_chunks/link-BgtkOBws.js +936 -0
- package/dist/_chunks/logs-DWkpjzIH.js +291 -0
- package/dist/_chunks/mcp-Bo-7TKc7.js +253 -0
- package/dist/_chunks/neon_auth-CBs1_3qu.js +822 -0
- package/dist/_chunks/open-CX3BT3Tp.js +33 -0
- package/dist/_chunks/operations-D4I-AxVs.js +37 -0
- package/dist/_chunks/orgs-xGfANwHQ.js +52 -0
- package/dist/_chunks/plugins-DqD7BDO1.js +169 -0
- package/dist/_chunks/profile-yXOiKk9R.js +672 -0
- package/dist/_chunks/projects-BwI2Uo0z.js +439 -0
- package/dist/_chunks/psql-BM-doe8e.js +64 -0
- package/dist/_chunks/realtime-BRqIyGR-.js +79 -0
- package/dist/_chunks/roles-B4w3YT_7.js +79 -0
- package/dist/_chunks/set_context-DbrwoImy.js +38 -0
- package/dist/_chunks/skills-DyiBXKS3.js +255 -0
- package/dist/_chunks/snapshots-RJBzW8GM.js +338 -0
- package/dist/_chunks/status-DV8Z2kw3.js +44 -0
- package/dist/_chunks/triggers-BrRo1z4b.js +302 -0
- package/dist/_chunks/user-dlOGXbX9.js +60 -0
- package/dist/_chunks/vpc_endpoints-CNb0YfMo.js +105 -0
- package/dist/auth_recovery.js +1 -1
- package/dist/commands/api.js +1 -388
- package/dist/commands/api_keys.js +1 -281
- package/dist/commands/ask.js +1 -155
- package/dist/commands/auth.js +1 -495
- package/dist/commands/bootstrap.js +1 -499
- package/dist/commands/branches.js +1 -460
- package/dist/commands/bucket.js +1 -409
- package/dist/commands/checkout.js +1 -348
- package/dist/commands/claim.js +1 -473
- package/dist/commands/config.js +1 -1113
- package/dist/commands/connection_string.js +1 -154
- package/dist/commands/credentials.js +1 -146
- package/dist/commands/data_api.js +1 -263
- package/dist/commands/databases.js +1 -84
- package/dist/commands/deploy.js +1 -33
- package/dist/commands/dev.js +1 -945
- package/dist/commands/diff.js +1 -178
- package/dist/commands/docs.js +1 -197
- package/dist/commands/env.js +1 -284
- package/dist/commands/feedback.js +1 -88
- package/dist/commands/functions.js +1 -464
- package/dist/commands/functions_new.js +1 -1
- package/dist/commands/git.js +1 -356
- package/dist/commands/index.js +45 -45
- package/dist/commands/init.js +1 -111
- package/dist/commands/inspect.js +1 -90
- package/dist/commands/ip_allow.js +1 -106
- package/dist/commands/link.js +1 -936
- package/dist/commands/logs.js +1 -291
- package/dist/commands/mcp.js +1 -253
- package/dist/commands/neon_auth.js +1 -822
- package/dist/commands/open.js +1 -33
- package/dist/commands/operations.js +1 -37
- package/dist/commands/orgs.js +1 -52
- package/dist/commands/plugins.js +1 -169
- package/dist/commands/profile.js +1 -672
- package/dist/commands/projects.js +1 -439
- package/dist/commands/psql.js +1 -64
- package/dist/commands/realtime.js +1 -79
- package/dist/commands/roles.js +1 -79
- package/dist/commands/set_context.js +1 -38
- package/dist/commands/skills.js +1 -255
- package/dist/commands/snapshots.js +1 -338
- package/dist/commands/status.js +1 -44
- package/dist/commands/subscribe.js +1 -1
- package/dist/commands/triggers.js +1 -302
- package/dist/commands/user.js +1 -60
- package/dist/commands/vpc_endpoints.js +1 -105
- package/dist/functions/registry.js +29 -151
- package/dist/functions/source.js +42 -2
- package/dist/index.js +1 -1
- package/dist/init/auth.js +3 -3
- package/dist/init/link.js +2 -2
- package/dist/init/run.js +3 -3
- package/dist/init/tooling.js +3 -3
- package/dist/mcp/mint.js +1 -1
- package/dist/test_utils/registry.js +1 -2
- package/package.json +8 -6
package/dist/commands/profile.js
CHANGED
|
@@ -1,672 +1 @@
|
|
|
1
|
-
|
|
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";
|