@neondatabase/env 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +21 -1
  2. package/dist/cli.js +1304 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/{_shared/env-core/env.js → env.js} +62 -41
  5. package/dist/env.js.map +1 -0
  6. package/dist/index.d.ts +528 -3
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +197 -2
  9. package/dist/{lib/parse-env.js.map → index.js.map} +1 -1
  10. package/package.json +12 -7
  11. package/dist/_shared/auth_selection.d.ts +0 -95
  12. package/dist/_shared/auth_selection.d.ts.map +0 -1
  13. package/dist/_shared/auth_selection.js +0 -85
  14. package/dist/_shared/auth_selection.js.map +0 -1
  15. package/dist/_shared/credentials.d.ts +0 -186
  16. package/dist/_shared/credentials.d.ts.map +0 -1
  17. package/dist/_shared/credentials.js +0 -190
  18. package/dist/_shared/credentials.js.map +0 -1
  19. package/dist/_shared/env-core/env.d.ts +0 -424
  20. package/dist/_shared/env-core/env.d.ts.map +0 -1
  21. package/dist/_shared/env-core/env.js.map +0 -1
  22. package/dist/_shared/env-core/reuse-secrets.d.ts +0 -95
  23. package/dist/_shared/env-core/reuse-secrets.d.ts.map +0 -1
  24. package/dist/_shared/env-core/reuse-secrets.js +0 -181
  25. package/dist/_shared/env-core/reuse-secrets.js.map +0 -1
  26. package/dist/_shared/paths.d.ts +0 -116
  27. package/dist/_shared/paths.d.ts.map +0 -1
  28. package/dist/_shared/paths.js +0 -153
  29. package/dist/_shared/paths.js.map +0 -1
  30. package/dist/_shared/profiles.d.ts +0 -145
  31. package/dist/_shared/profiles.d.ts.map +0 -1
  32. package/dist/_shared/profiles.js +0 -228
  33. package/dist/_shared/profiles.js.map +0 -1
  34. package/dist/_shared/secure_file.d.ts +0 -25
  35. package/dist/_shared/secure_file.d.ts.map +0 -1
  36. package/dist/_shared/secure_file.js +0 -43
  37. package/dist/_shared/secure_file.js.map +0 -1
  38. package/dist/config/dist/lib/define-config.d.ts +0 -20
  39. package/dist/config/dist/lib/define-config.d.ts.map +0 -1
  40. package/dist/config/dist/lib/neon-api.d.ts +0 -375
  41. package/dist/config/dist/lib/neon-api.d.ts.map +0 -1
  42. package/dist/config/dist/lib/types.d.ts +0 -603
  43. package/dist/config/dist/lib/types.d.ts.map +0 -1
  44. package/dist/config/dist/v1.d.ts +0 -5
  45. package/dist/lib/cli/commands.d.ts +0 -68
  46. package/dist/lib/cli/commands.d.ts.map +0 -1
  47. package/dist/lib/cli/commands.js +0 -233
  48. package/dist/lib/cli/commands.js.map +0 -1
  49. package/dist/lib/cli/resolve-api-key.d.ts +0 -29
  50. package/dist/lib/cli/resolve-api-key.d.ts.map +0 -1
  51. package/dist/lib/cli/resolve-api-key.js +0 -74
  52. package/dist/lib/cli/resolve-api-key.js.map +0 -1
  53. package/dist/lib/cli/resolve-context.d.ts +0 -34
  54. package/dist/lib/cli/resolve-context.d.ts.map +0 -1
  55. package/dist/lib/cli/resolve-context.js +0 -88
  56. package/dist/lib/cli/resolve-context.js.map +0 -1
  57. package/dist/lib/parse-env.d.ts +0 -95
  58. package/dist/lib/parse-env.d.ts.map +0 -1
  59. package/dist/lib/parse-env.js +0 -198
@@ -1,186 +0,0 @@
1
- //#region src/_shared/credentials.d.ts
2
- /**
3
- * # Stored credentials — one file per account, two kinds
4
- *
5
- * A profile points at exactly one credentials file (see `./profiles.ts`), and that file says
6
- * what kind of credential it holds. Adding API-key support this way rather than adding a
7
- * second pointer to `profiles.json` keeps a profile what it already was — one name, one path
8
- * — and means `profiles.json` needs no schema change at all.
9
- *
10
- * ```json
11
- * // oauth: every file written before this existed. An absent `type` means this.
12
- * { "access_token": "…", "refresh_token": "…", "expires_at": 1786…, "user_id": "…" }
13
- *
14
- * // api_key, stored by `neon profile create --api-key`
15
- * { "type": "api_key", "api_key": "napi_…", "user_id": "…" }
16
- *
17
- * // api_key minted by `--mint --org-id`, which records the scope it was issued at
18
- * { "type": "api_key", "api_key": "napi_…", "key_id": 123, "org_id": "org-…" }
19
- * ```
20
- *
21
- * ## One profile, one kind
22
- *
23
- * A credentials file holds an API key or an OAuth session, never both, and `type` states
24
- * which. An earlier draft let the two coexist — the idea being that a key could keep the
25
- * session it was minted from and so rotate without a browser. It did not survive review, for
26
- * two reasons that are worth recording so nobody rebuilds it:
27
- *
28
- * 1. **It never worked.** The resolver returned the key without testing it, so a revoked key
29
- * failed to mint and never fell back to the session sitting beside it.
30
- * 2. **It could mix accounts.** Nothing compared the identity of the credential being written
31
- * with the one already there, so a profile could hold one account's session and another's
32
- * key, told apart only by a single string. Flip or lose `type` and the profile silently
33
- * becomes a different person.
34
- *
35
- * Recovery from a dead key is therefore one browser login — `neon profile create <name>
36
- * --mint --force` — which is what the retained session was supposed to save and never did.
37
- *
38
- * ## Older releases
39
- *
40
- * A CLI predating this reads the pointer, finds no `type` it understands, ignores it, and
41
- * looks for `access_token`. An `api_key` profile has none, so an older release falls through
42
- * to its browser login rather than crashing. That it does not crash is why `credentials`
43
- * stays a required pointer: an entry without one makes 2.41 and 2.42 throw
44
- * `ERR_INVALID_ARG_TYPE` from `resolveEntryPath`.
45
- */
46
- declare const OAUTH = "oauth";
47
- declare const API_KEY = "api_key";
48
- type CredentialKind = typeof OAUTH | typeof API_KEY;
49
- /**
50
- * The on-disk shape. Every field is optional because the two kinds overlap and because an
51
- * OAuth token endpoint response carries more than we name here — the index signature keeps
52
- * those extra fields on a round-trip rather than dropping them.
53
- */
54
- type StoredCredentials = {
55
- type?: string;
56
- api_key?: string;
57
- key_id?: number;
58
- /** Set when the key was minted for an organization rather than the account. */
59
- org_id?: string;
60
- /** Set when the key was narrowed to a single project. Implies `org_id`. */
61
- project_id?: string;
62
- user_id?: string;
63
- access_token?: string;
64
- refresh_token?: string;
65
- expires_at?: number;
66
- [key: string]: unknown;
67
- };
68
- /**
69
- * Where a credential lives, and which profile points at it.
70
- *
71
- * Both halves are needed to report a broken file: the path says which file to open, and the
72
- * profile is what every recovery command takes as its argument. Carrying only the path is what
73
- * produced errors telling the user to run `neon profile create <name> --force` with the
74
- * placeholder intact — a command an agent will run verbatim and be told `Invalid profile name
75
- * "<name>"`.
76
- */
77
- type CredentialLocation = {
78
- path: string;
79
- profile: string;
80
- };
81
- /**
82
- * Which credential in this file authenticates, by declaration alone.
83
- *
84
- * An unrecognised `type` throws rather than falling back to `oauth`. A file we cannot
85
- * interpret is a misconfiguration the user has to see: treating it as OAuth would send them
86
- * to a browser login that silently replaces a credential they meant to keep, and treating it
87
- * as an API key would authenticate with whatever `api_key` happened to be there.
88
- *
89
- * This deliberately does not check that an `api_key` file has a key — `neon profile list`
90
- * needs the kind of a file it is not about to authenticate with, and must be able to report a
91
- * broken one rather than throwing halfway through a table.
92
- */
93
- declare const credentialKind: (credentials: StoredCredentials, at: CredentialLocation) => CredentialKind;
94
- /** A credentials file resolved far enough to authenticate with. */
95
- type InterpretedCredentials = {
96
- kind: typeof API_KEY;
97
- apiKey: string;
98
- } | {
99
- kind: typeof OAUTH;
100
- };
101
- /**
102
- * Resolve what to authenticate with, validating that the declared kind is actually usable.
103
- *
104
- * An `api_key` file with no key is a hard error rather than a fall-through to OAuth: the user
105
- * asked for a key, and quietly opening a browser instead would replace the credential they
106
- * were trying to fix.
107
- */
108
- declare const interpretCredentials: (credentials: StoredCredentials, at: CredentialLocation) => InterpretedCredentials;
109
- /**
110
- * Read a credentials file, or `null` when there is nothing usable there.
111
- *
112
- * Missing and unparseable both return `null`, because both are recoverable by authenticating
113
- * again and the callers already treat "no credentials" as "log in". A file that parses but
114
- * contradicts itself is different — {@link credentialKind} throws for that, since re-running
115
- * `auth` would paper over a mistake rather than fix it.
116
- */
117
- type CredentialsRead = {
118
- kind: "ok";
119
- credentials: StoredCredentials;
120
- } | {
121
- kind: "absent";
122
- }
123
- /** The file is there but cannot be understood. `reason` is safe to print. */ | {
124
- kind: "unusable";
125
- reason: string;
126
- };
127
- /**
128
- * Read and classify a credentials file, without deciding what to do about it.
129
- *
130
- * A permission or I/O error still throws: there may be a perfectly good credential here that
131
- * we cannot see, and treating that as absent would send the user to a browser login that
132
- * overwrites it.
133
- */
134
- declare const inspectCredentials: (path: string) => CredentialsRead;
135
- /**
136
- * The credential at `path`, or `null` when the file is not there.
137
- *
138
- * A damaged file is an error, not an absence. Treating it as absent — which is what this used to
139
- * do — meant any read-only command could repair it by starting a browser sign-in and overwriting
140
- * it, **possibly as a different account**, with the user never having asked for a repair and no
141
- * way back to whatever was in the file. Failing here costs one deliberate command; the message
142
- * names it.
143
- *
144
- * `profile list` and telemetry use {@link inspectCredentials} instead, because describing a
145
- * broken credential is not the same as using one.
146
- */
147
- declare const readCredentials: (at: CredentialLocation) => StoredCredentials | null;
148
- declare const writeCredentials: (path: string, credentials: StoredCredentials) => void;
149
- /** The scope a minted key was issued at. Absent org means an account key. */
150
- type KeyScope = {
151
- orgId?: string;
152
- projectId?: string;
153
- };
154
- /**
155
- * Build an `api_key` credentials object. Nothing from a previous credential is carried over.
156
- *
157
- * The scope is stored because it is not recoverable from the secret: `rotate-key` has to mint
158
- * the replacement on the same endpoint, and an org or project key minted as an account key
159
- * would silently widen what the profile reaches.
160
- */
161
- declare const apiKeyCredentials: ({
162
- apiKey,
163
- keyId,
164
- userId,
165
- scope
166
- }: {
167
- apiKey: string;
168
- keyId?: number;
169
- userId?: string;
170
- scope?: KeyScope;
171
- }) => StoredCredentials;
172
- /** The scope recorded on a stored credential. */
173
- declare const scopeOf: (credentials: StoredCredentials) => KeyScope;
174
- /** How to describe a scope in output. */
175
- declare const describeScope: (scope: KeyScope) => string;
176
- /**
177
- * Whether a stored credential is the same secret as the one about to replace it.
178
- *
179
- * Re-storing the key a profile already holds is a no-op, not a replacement — and retiring it
180
- * would revoke the credential the command has just committed to. Trimmed on both sides, because
181
- * a key read from a file or a pipe arrives with a trailing newline.
182
- */
183
- declare const isSameCredential: (existingKey: string | undefined, replacementKey: string | undefined) => boolean;
184
- //#endregion
185
- export { API_KEY, CredentialKind, CredentialLocation, CredentialsRead, InterpretedCredentials, KeyScope, OAUTH, StoredCredentials, apiKeyCredentials, credentialKind, describeScope, inspectCredentials, interpretCredentials, isSameCredential, readCredentials, scopeOf, writeCredentials };
186
- //# sourceMappingURL=credentials.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.d.ts","names":[],"sources":["../../src/_shared/credentials.ts"],"mappings":";;AAgDA;AACA;AAEA;AAA0B;AAAU;AAAe;AAAO;AAO1D;AAwBA;AAiBA;AAaC;AAZa;AACT;AACF;AAUF;AAYD;AAAkC;AAChB;AACA;AAAK;AASvB;AAYC;AAXa;AACT;AACF;AASF;AAUD;AAaA;AAiDA;AAQC;AAPI;AACF;AAAiB;AAQpB;AAQA;AAYA;AAiBE;AAjBgC;AAAA;AAAA;AAAA;AASzB;AACL;AAOF,cA1NW,KAAA,GA0NX,OAAA;AAGW,cA5NA,OAAA,GAmOX,SAAA;AAAA,KAjOU,cAAA,GAiOV,OAjOkC,KAiOlC,GAAA,OAjOiD,OAiOjD;AAPmC;AAAoB;AAOvD;AAGF;AAmBA;KAhPY,iBAAA;;;;;;;;;;;;;;;;;;;;;;;KAwBA,kBAAA;;;;;;;;;;;;;;;;cAiBC,8BACC,uBACT,uBACF;;KAsBS,sBAAA;eACM;;;eACA;;;;;;;;;cASL,oCACC,uBACT,uBACF;;;;;;;;;KAmBS,eAAA;;eACkB;;;;;;;;;;;;;;;cAYjB,sCAAqC;;;;;;;;;;;;;cAiDrC,sBACR,uBACF;cAQU,8CAEC;;KAMF,QAAA;;;;;;;;;;;cAYC;;;;;;;;;UASJ;MACL;;cAUS,uBAAwB,sBAAoB;;cAU5C,uBAAwB;;;;;;;;cAmBxB"}
@@ -1,190 +0,0 @@
1
- import { writeSecretFile } from "./secure_file.js";
2
- import { readFileSync } from "node:fs";
3
- //#region src/_shared/credentials.ts
4
- /**
5
- * # Stored credentials — one file per account, two kinds
6
- *
7
- * A profile points at exactly one credentials file (see `./profiles.ts`), and that file says
8
- * what kind of credential it holds. Adding API-key support this way rather than adding a
9
- * second pointer to `profiles.json` keeps a profile what it already was — one name, one path
10
- * — and means `profiles.json` needs no schema change at all.
11
- *
12
- * ```json
13
- * // oauth: every file written before this existed. An absent `type` means this.
14
- * { "access_token": "…", "refresh_token": "…", "expires_at": 1786…, "user_id": "…" }
15
- *
16
- * // api_key, stored by `neon profile create --api-key`
17
- * { "type": "api_key", "api_key": "napi_…", "user_id": "…" }
18
- *
19
- * // api_key minted by `--mint --org-id`, which records the scope it was issued at
20
- * { "type": "api_key", "api_key": "napi_…", "key_id": 123, "org_id": "org-…" }
21
- * ```
22
- *
23
- * ## One profile, one kind
24
- *
25
- * A credentials file holds an API key or an OAuth session, never both, and `type` states
26
- * which. An earlier draft let the two coexist — the idea being that a key could keep the
27
- * session it was minted from and so rotate without a browser. It did not survive review, for
28
- * two reasons that are worth recording so nobody rebuilds it:
29
- *
30
- * 1. **It never worked.** The resolver returned the key without testing it, so a revoked key
31
- * failed to mint and never fell back to the session sitting beside it.
32
- * 2. **It could mix accounts.** Nothing compared the identity of the credential being written
33
- * with the one already there, so a profile could hold one account's session and another's
34
- * key, told apart only by a single string. Flip or lose `type` and the profile silently
35
- * becomes a different person.
36
- *
37
- * Recovery from a dead key is therefore one browser login — `neon profile create <name>
38
- * --mint --force` — which is what the retained session was supposed to save and never did.
39
- *
40
- * ## Older releases
41
- *
42
- * A CLI predating this reads the pointer, finds no `type` it understands, ignores it, and
43
- * looks for `access_token`. An `api_key` profile has none, so an older release falls through
44
- * to its browser login rather than crashing. That it does not crash is why `credentials`
45
- * stays a required pointer: an entry without one makes 2.41 and 2.42 throw
46
- * `ERR_INVALID_ARG_TYPE` from `resolveEntryPath`.
47
- */
48
- const OAUTH = "oauth";
49
- const API_KEY = "api_key";
50
- /**
51
- * Which credential in this file authenticates, by declaration alone.
52
- *
53
- * An unrecognised `type` throws rather than falling back to `oauth`. A file we cannot
54
- * interpret is a misconfiguration the user has to see: treating it as OAuth would send them
55
- * to a browser login that silently replaces a credential they meant to keep, and treating it
56
- * as an API key would authenticate with whatever `api_key` happened to be there.
57
- *
58
- * This deliberately does not check that an `api_key` file has a key — `neon profile list`
59
- * needs the kind of a file it is not about to authenticate with, and must be able to report a
60
- * broken one rather than throwing halfway through a table.
61
- */
62
- const credentialKind = (credentials, at) => {
63
- const declared = credentials.type;
64
- if (declared === void 0 || declared === "oauth") return OAUTH;
65
- if (declared === "api_key") return API_KEY;
66
- throw new Error(`${at.path} declares a "type" this version does not understand. Expected "${OAUTH}" or "${API_KEY}". ${repair(at)}`);
67
- };
68
- /**
69
- * The way out of a credentials file that cannot be read.
70
- *
71
- * One sentence, shared by every such error, because they all have the same two answers: write
72
- * a new credential over it, or delete it and start again.
73
- */
74
- const repair = (at) => `Replace it deliberately with \`neon profile create ${at.profile} --force\`, or delete the file.`;
75
- /**
76
- * Resolve what to authenticate with, validating that the declared kind is actually usable.
77
- *
78
- * An `api_key` file with no key is a hard error rather than a fall-through to OAuth: the user
79
- * asked for a key, and quietly opening a browser instead would replace the credential they
80
- * were trying to fix.
81
- */
82
- const interpretCredentials = (credentials, at) => {
83
- if (credentialKind(credentials, at) === "oauth") return { kind: OAUTH };
84
- const apiKey = nonEmpty(credentials.api_key);
85
- if (apiKey === void 0) throw new Error(`${at.path} declares "type": "${API_KEY}" but has no "api_key" value. ${repair(at)}`);
86
- return {
87
- kind: API_KEY,
88
- apiKey
89
- };
90
- };
91
- /**
92
- * Read and classify a credentials file, without deciding what to do about it.
93
- *
94
- * A permission or I/O error still throws: there may be a perfectly good credential here that
95
- * we cannot see, and treating that as absent would send the user to a browser login that
96
- * overwrites it.
97
- */
98
- const inspectCredentials = (path) => {
99
- let contents;
100
- try {
101
- contents = readFileSync(path, "utf8");
102
- } catch (err) {
103
- if (err.code === "ENOENT") return { kind: "absent" };
104
- throw err;
105
- }
106
- let parsed;
107
- try {
108
- parsed = JSON.parse(contents);
109
- } catch {
110
- return {
111
- kind: "unusable",
112
- reason: `${path} is not valid JSON, so the credential in it cannot be read`
113
- };
114
- }
115
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {
116
- kind: "unusable",
117
- reason: `${path} does not contain a credentials object`
118
- };
119
- return {
120
- kind: "ok",
121
- credentials: parsed
122
- };
123
- };
124
- /**
125
- * The credential at `path`, or `null` when the file is not there.
126
- *
127
- * A damaged file is an error, not an absence. Treating it as absent — which is what this used to
128
- * do — meant any read-only command could repair it by starting a browser sign-in and overwriting
129
- * it, **possibly as a different account**, with the user never having asked for a repair and no
130
- * way back to whatever was in the file. Failing here costs one deliberate command; the message
131
- * names it.
132
- *
133
- * `profile list` and telemetry use {@link inspectCredentials} instead, because describing a
134
- * broken credential is not the same as using one.
135
- */
136
- const readCredentials = (at) => {
137
- const read = inspectCredentials(at.path);
138
- if (read.kind === "unusable") throw new Error(`${read.reason}. ${repair(at)}`);
139
- return read.kind === "ok" ? read.credentials : null;
140
- };
141
- const writeCredentials = (path, credentials) => {
142
- writeSecretFile(path, JSON.stringify(credentials));
143
- };
144
- /**
145
- * Build an `api_key` credentials object. Nothing from a previous credential is carried over.
146
- *
147
- * The scope is stored because it is not recoverable from the secret: `rotate-key` has to mint
148
- * the replacement on the same endpoint, and an org or project key minted as an account key
149
- * would silently widen what the profile reaches.
150
- */
151
- const apiKeyCredentials = ({ apiKey, keyId, userId, scope }) => ({
152
- type: API_KEY,
153
- api_key: apiKey,
154
- ...keyId !== void 0 ? { key_id: keyId } : {},
155
- ...userId !== void 0 ? { user_id: userId } : {},
156
- ...scope?.orgId !== void 0 ? { org_id: scope.orgId } : {},
157
- ...scope?.projectId !== void 0 ? { project_id: scope.projectId } : {}
158
- });
159
- /** The scope recorded on a stored credential. */
160
- const scopeOf = (credentials) => ({
161
- ...typeof credentials.org_id === "string" ? { orgId: credentials.org_id } : {},
162
- ...typeof credentials.project_id === "string" ? { projectId: credentials.project_id } : {}
163
- });
164
- /** How to describe a scope in output. */
165
- const describeScope = (scope) => {
166
- if (scope.projectId !== void 0) return `project ${scope.projectId}`;
167
- if (scope.orgId !== void 0) return `org ${scope.orgId}`;
168
- return "account";
169
- };
170
- function nonEmpty(value) {
171
- if (typeof value !== "string") return void 0;
172
- const trimmed = value.trim();
173
- return trimmed === "" ? void 0 : trimmed;
174
- }
175
- /**
176
- * Whether a stored credential is the same secret as the one about to replace it.
177
- *
178
- * Re-storing the key a profile already holds is a no-op, not a replacement — and retiring it
179
- * would revoke the credential the command has just committed to. Trimmed on both sides, because
180
- * a key read from a file or a pipe arrives with a trailing newline.
181
- */
182
- const isSameCredential = (existingKey, replacementKey) => {
183
- if (existingKey === void 0 || replacementKey === void 0) return false;
184
- const trimmed = existingKey.trim();
185
- return trimmed !== "" && trimmed === replacementKey.trim();
186
- };
187
- //#endregion
188
- export { API_KEY, OAUTH, apiKeyCredentials, credentialKind, describeScope, inspectCredentials, interpretCredentials, isSameCredential, readCredentials, scopeOf, writeCredentials };
189
-
190
- //# sourceMappingURL=credentials.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"credentials.js","names":[],"sources":["../../src/_shared/credentials.ts"],"sourcesContent":["/**\n * # Stored credentials — one file per account, two kinds\n *\n * A profile points at exactly one credentials file (see `./profiles.ts`), and that file says\n * what kind of credential it holds. Adding API-key support this way rather than adding a\n * second pointer to `profiles.json` keeps a profile what it already was — one name, one path\n * — and means `profiles.json` needs no schema change at all.\n *\n * ```json\n * // oauth: every file written before this existed. An absent `type` means this.\n * { \"access_token\": \"…\", \"refresh_token\": \"…\", \"expires_at\": 1786…, \"user_id\": \"…\" }\n *\n * // api_key, stored by `neon profile create --api-key`\n * { \"type\": \"api_key\", \"api_key\": \"napi_…\", \"user_id\": \"…\" }\n *\n * // api_key minted by `--mint --org-id`, which records the scope it was issued at\n * { \"type\": \"api_key\", \"api_key\": \"napi_…\", \"key_id\": 123, \"org_id\": \"org-…\" }\n * ```\n *\n * ## One profile, one kind\n *\n * A credentials file holds an API key or an OAuth session, never both, and `type` states\n * which. An earlier draft let the two coexist — the idea being that a key could keep the\n * session it was minted from and so rotate without a browser. It did not survive review, for\n * two reasons that are worth recording so nobody rebuilds it:\n *\n * 1. **It never worked.** The resolver returned the key without testing it, so a revoked key\n * failed to mint and never fell back to the session sitting beside it.\n * 2. **It could mix accounts.** Nothing compared the identity of the credential being written\n * with the one already there, so a profile could hold one account's session and another's\n * key, told apart only by a single string. Flip or lose `type` and the profile silently\n * becomes a different person.\n *\n * Recovery from a dead key is therefore one browser login — `neon profile create <name>\n * --mint --force` — which is what the retained session was supposed to save and never did.\n *\n * ## Older releases\n *\n * A CLI predating this reads the pointer, finds no `type` it understands, ignores it, and\n * looks for `access_token`. An `api_key` profile has none, so an older release falls through\n * to its browser login rather than crashing. That it does not crash is why `credentials`\n * stays a required pointer: an entry without one makes 2.41 and 2.42 throw\n * `ERR_INVALID_ARG_TYPE` from `resolveEntryPath`.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { writeSecretFile } from \"./secure_file.js\";\n\nexport const OAUTH = \"oauth\";\nexport const API_KEY = \"api_key\";\n\nexport type CredentialKind = typeof OAUTH | typeof API_KEY;\n\n/**\n * The on-disk shape. Every field is optional because the two kinds overlap and because an\n * OAuth token endpoint response carries more than we name here — the index signature keeps\n * those extra fields on a round-trip rather than dropping them.\n */\nexport type StoredCredentials = {\n\ttype?: string;\n\tapi_key?: string;\n\tkey_id?: number;\n\t/** Set when the key was minted for an organization rather than the account. */\n\torg_id?: string;\n\t/** Set when the key was narrowed to a single project. Implies `org_id`. */\n\tproject_id?: string;\n\tuser_id?: string;\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_at?: number;\n\t[key: string]: unknown;\n};\n\n/**\n * Where a credential lives, and which profile points at it.\n *\n * Both halves are needed to report a broken file: the path says which file to open, and the\n * profile is what every recovery command takes as its argument. Carrying only the path is what\n * produced errors telling the user to run `neon profile create <name> --force` with the\n * placeholder intact — a command an agent will run verbatim and be told `Invalid profile name\n * \"<name>\"`.\n */\nexport type CredentialLocation = {\n\tpath: string;\n\tprofile: string;\n};\n\n/**\n * Which credential in this file authenticates, by declaration alone.\n *\n * An unrecognised `type` throws rather than falling back to `oauth`. A file we cannot\n * interpret is a misconfiguration the user has to see: treating it as OAuth would send them\n * to a browser login that silently replaces a credential they meant to keep, and treating it\n * as an API key would authenticate with whatever `api_key` happened to be there.\n *\n * This deliberately does not check that an `api_key` file has a key — `neon profile list`\n * needs the kind of a file it is not about to authenticate with, and must be able to report a\n * broken one rather than throwing halfway through a table.\n */\nexport const credentialKind = (\n\tcredentials: StoredCredentials,\n\tat: CredentialLocation,\n): CredentialKind => {\n\tconst declared = credentials.type;\n\tif (declared === undefined || declared === OAUTH) return OAUTH;\n\tif (declared === API_KEY) return API_KEY;\n\t// The value is not quoted back. Everything in this file is secret material, and a\n\t// corrupted or hand-edited file can put a key anywhere in it — including here. Naming the\n\t// file is enough to act on, and it cannot leak what the file holds.\n\tthrow new Error(\n\t\t`${at.path} declares a \"type\" this version does not understand. Expected \"${OAUTH}\" or \"${API_KEY}\". ${repair(at)}`,\n\t);\n};\n\n/**\n * The way out of a credentials file that cannot be read.\n *\n * One sentence, shared by every such error, because they all have the same two answers: write\n * a new credential over it, or delete it and start again.\n */\nconst repair = (at: CredentialLocation): string =>\n\t`Replace it deliberately with \\`neon profile create ${at.profile} --force\\`, or delete the file.`;\n\n/** A credentials file resolved far enough to authenticate with. */\nexport type InterpretedCredentials =\n\t| { kind: typeof API_KEY; apiKey: string }\n\t| { kind: typeof OAUTH };\n\n/**\n * Resolve what to authenticate with, validating that the declared kind is actually usable.\n *\n * An `api_key` file with no key is a hard error rather than a fall-through to OAuth: the user\n * asked for a key, and quietly opening a browser instead would replace the credential they\n * were trying to fix.\n */\nexport const interpretCredentials = (\n\tcredentials: StoredCredentials,\n\tat: CredentialLocation,\n): InterpretedCredentials => {\n\tif (credentialKind(credentials, at) === OAUTH) return { kind: OAUTH };\n\tconst apiKey = nonEmpty(credentials.api_key);\n\tif (apiKey === undefined) {\n\t\tthrow new Error(\n\t\t\t`${at.path} declares \"type\": \"${API_KEY}\" but has no \"api_key\" value. ${repair(at)}`,\n\t\t);\n\t}\n\treturn { kind: API_KEY, apiKey };\n};\n\n/**\n * Read a credentials file, or `null` when there is nothing usable there.\n *\n * Missing and unparseable both return `null`, because both are recoverable by authenticating\n * again and the callers already treat \"no credentials\" as \"log in\". A file that parses but\n * contradicts itself is different — {@link credentialKind} throws for that, since re-running\n * `auth` would paper over a mistake rather than fix it.\n */\nexport type CredentialsRead =\n\t| { kind: \"ok\"; credentials: StoredCredentials }\n\t| { kind: \"absent\" }\n\t/** The file is there but cannot be understood. `reason` is safe to print. */\n\t| { kind: \"unusable\"; reason: string };\n\n/**\n * Read and classify a credentials file, without deciding what to do about it.\n *\n * A permission or I/O error still throws: there may be a perfectly good credential here that\n * we cannot see, and treating that as absent would send the user to a browser login that\n * overwrites it.\n */\nexport const inspectCredentials = (path: string): CredentialsRead => {\n\tlet contents: string;\n\ttry {\n\t\tcontents = readFileSync(path, \"utf8\");\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code === \"ENOENT\")\n\t\t\treturn { kind: \"absent\" };\n\t\tthrow err;\n\t}\n\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(contents);\n\t} catch {\n\t\t// The parser's message is deliberately discarded. V8 quotes a window of the input\n\t\t// around the syntax error — on Node 24 a truncated credentials file produced\n\t\t// `Unexpected token 'a', ...\"api_key\":napi_SUPERS\"... is not valid JSON` — and this\n\t\t// reason is printed by `profile list` and by every failed authentication. A malformed\n\t\t// secret file is exactly when a diagnostic must say less, not more.\n\t\treturn {\n\t\t\tkind: \"unusable\",\n\t\t\treason: `${path} is not valid JSON, so the credential in it cannot be read`,\n\t\t};\n\t}\n\tif (\n\t\tparsed === null ||\n\t\ttypeof parsed !== \"object\" ||\n\t\tArray.isArray(parsed)\n\t) {\n\t\treturn {\n\t\t\tkind: \"unusable\",\n\t\t\treason: `${path} does not contain a credentials object`,\n\t\t};\n\t}\n\treturn { kind: \"ok\", credentials: parsed as StoredCredentials };\n};\n\n/**\n * The credential at `path`, or `null` when the file is not there.\n *\n * A damaged file is an error, not an absence. Treating it as absent — which is what this used to\n * do — meant any read-only command could repair it by starting a browser sign-in and overwriting\n * it, **possibly as a different account**, with the user never having asked for a repair and no\n * way back to whatever was in the file. Failing here costs one deliberate command; the message\n * names it.\n *\n * `profile list` and telemetry use {@link inspectCredentials} instead, because describing a\n * broken credential is not the same as using one.\n */\nexport const readCredentials = (\n\tat: CredentialLocation,\n): StoredCredentials | null => {\n\tconst read = inspectCredentials(at.path);\n\tif (read.kind === \"unusable\") {\n\t\tthrow new Error(`${read.reason}. ${repair(at)}`);\n\t}\n\treturn read.kind === \"ok\" ? read.credentials : null;\n};\n\nexport const writeCredentials = (\n\tpath: string,\n\tcredentials: StoredCredentials,\n): void => {\n\twriteSecretFile(path, JSON.stringify(credentials));\n};\n\n/** The scope a minted key was issued at. Absent org means an account key. */\nexport type KeyScope = {\n\torgId?: string;\n\tprojectId?: string;\n};\n\n/**\n * Build an `api_key` credentials object. Nothing from a previous credential is carried over.\n *\n * The scope is stored because it is not recoverable from the secret: `rotate-key` has to mint\n * the replacement on the same endpoint, and an org or project key minted as an account key\n * would silently widen what the profile reaches.\n */\nexport const apiKeyCredentials = ({\n\tapiKey,\n\tkeyId,\n\tuserId,\n\tscope,\n}: {\n\tapiKey: string;\n\tkeyId?: number;\n\tuserId?: string;\n\tscope?: KeyScope;\n}): StoredCredentials => ({\n\ttype: API_KEY,\n\tapi_key: apiKey,\n\t...(keyId !== undefined ? { key_id: keyId } : {}),\n\t...(userId !== undefined ? { user_id: userId } : {}),\n\t...(scope?.orgId !== undefined ? { org_id: scope.orgId } : {}),\n\t...(scope?.projectId !== undefined ? { project_id: scope.projectId } : {}),\n});\n\n/** The scope recorded on a stored credential. */\nexport const scopeOf = (credentials: StoredCredentials): KeyScope => ({\n\t...(typeof credentials.org_id === \"string\"\n\t\t? { orgId: credentials.org_id }\n\t\t: {}),\n\t...(typeof credentials.project_id === \"string\"\n\t\t? { projectId: credentials.project_id }\n\t\t: {}),\n});\n\n/** How to describe a scope in output. */\nexport const describeScope = (scope: KeyScope): string => {\n\tif (scope.projectId !== undefined) return `project ${scope.projectId}`;\n\tif (scope.orgId !== undefined) return `org ${scope.orgId}`;\n\treturn \"account\";\n};\n\nfunction nonEmpty(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Whether a stored credential is the same secret as the one about to replace it.\n *\n * Re-storing the key a profile already holds is a no-op, not a replacement — and retiring it\n * would revoke the credential the command has just committed to. Trimmed on both sides, because\n * a key read from a file or a pipe arrives with a trailing newline.\n */\nexport const isSameCredential = (\n\texistingKey: string | undefined,\n\treplacementKey: string | undefined,\n): boolean => {\n\tif (existingKey === undefined || replacementKey === undefined) return false;\n\tconst trimmed = existingKey.trim();\n\treturn trimmed !== \"\" && trimmed === replacementKey.trim();\n};\n"],"mappings":"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"}