@neondatabase/env 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/cli.js +971 -6
- package/dist/cli.js.map +1 -1
- package/dist/{_shared/env-core/env.js → env.js} +62 -41
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +528 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -2
- package/dist/{lib/parse-env.js.map → index.js.map} +1 -1
- package/package.json +8 -6
- package/dist/_shared/auth_selection.d.ts +0 -95
- package/dist/_shared/auth_selection.d.ts.map +0 -1
- package/dist/_shared/auth_selection.js +0 -85
- package/dist/_shared/auth_selection.js.map +0 -1
- package/dist/_shared/credentials.d.ts +0 -186
- package/dist/_shared/credentials.d.ts.map +0 -1
- package/dist/_shared/credentials.js +0 -190
- package/dist/_shared/credentials.js.map +0 -1
- package/dist/_shared/env-core/env.d.ts +0 -424
- package/dist/_shared/env-core/env.d.ts.map +0 -1
- package/dist/_shared/env-core/env.js.map +0 -1
- package/dist/_shared/env-core/reuse-secrets.d.ts +0 -95
- package/dist/_shared/env-core/reuse-secrets.d.ts.map +0 -1
- package/dist/_shared/env-core/reuse-secrets.js +0 -181
- package/dist/_shared/env-core/reuse-secrets.js.map +0 -1
- package/dist/_shared/paths.d.ts +0 -116
- package/dist/_shared/paths.d.ts.map +0 -1
- package/dist/_shared/paths.js +0 -153
- package/dist/_shared/paths.js.map +0 -1
- package/dist/_shared/profiles.d.ts +0 -145
- package/dist/_shared/profiles.d.ts.map +0 -1
- package/dist/_shared/profiles.js +0 -228
- package/dist/_shared/profiles.js.map +0 -1
- package/dist/_shared/secure_file.d.ts +0 -25
- package/dist/_shared/secure_file.d.ts.map +0 -1
- package/dist/_shared/secure_file.js +0 -43
- package/dist/_shared/secure_file.js.map +0 -1
- package/dist/config/dist/lib/define-config.d.ts +0 -20
- package/dist/config/dist/lib/define-config.d.ts.map +0 -1
- package/dist/config/dist/lib/neon-api.d.ts +0 -375
- package/dist/config/dist/lib/neon-api.d.ts.map +0 -1
- package/dist/config/dist/lib/types.d.ts +0 -603
- package/dist/config/dist/lib/types.d.ts.map +0 -1
- package/dist/config/dist/v1.d.ts +0 -5
- package/dist/lib/cli/commands.d.ts +0 -68
- package/dist/lib/cli/commands.d.ts.map +0 -1
- package/dist/lib/cli/commands.js +0 -233
- package/dist/lib/cli/commands.js.map +0 -1
- package/dist/lib/cli/resolve-api-key.d.ts +0 -29
- package/dist/lib/cli/resolve-api-key.d.ts.map +0 -1
- package/dist/lib/cli/resolve-api-key.js +0 -74
- package/dist/lib/cli/resolve-api-key.js.map +0 -1
- package/dist/lib/cli/resolve-context.d.ts +0 -34
- package/dist/lib/cli/resolve-context.d.ts.map +0 -1
- package/dist/lib/cli/resolve-context.js +0 -88
- package/dist/lib/cli/resolve-context.js.map +0 -1
- package/dist/lib/parse-env.d.ts +0 -95
- package/dist/lib/parse-env.d.ts.map +0 -1
- package/dist/lib/parse-env.js +0 -198
package/dist/index.js
CHANGED
|
@@ -1,3 +1,198 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as fetchEnv, t as NEON_ENV_VAR_KEYS, u as toEntries } from "./env.js";
|
|
2
|
+
import { ErrorCode, PlatformError } from "@neon/config/v1";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/lib/parse-env.ts
|
|
5
|
+
/**
|
|
6
|
+
* `parseEnv` — the synchronous, network-free counterpart to `fetchEnv`: read the Neon env vars
|
|
7
|
+
* already injected into `process.env`, validate them against the policy, and return them in the
|
|
8
|
+
* same namespaced shape.
|
|
9
|
+
*
|
|
10
|
+
* Lives in this package rather than in `@neon-internals/env-core` because nothing else needs
|
|
11
|
+
* it. The `neon` CLI resolves env from the API and injects it; it never reads it back. Keeping
|
|
12
|
+
* it here also keeps `zod` out of that package, and so out of every consumer that bundles it.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from
|
|
16
|
+
* `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.
|
|
17
|
+
*
|
|
18
|
+
* `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include
|
|
19
|
+
* URL-illegal characters in the password (rare but legal in Neon's connection-string
|
|
20
|
+
* format) fail the WHATWG `URL` parse, so we settle for "non-empty string".
|
|
21
|
+
*/
|
|
22
|
+
const postgresEnvSchema = z.object({
|
|
23
|
+
DATABASE_URL: z.string({ message: "DATABASE_URL is missing" }).min(1, "DATABASE_URL must not be empty"),
|
|
24
|
+
DATABASE_URL_UNPOOLED: z.string({ message: "DATABASE_URL_UNPOOLED is missing" }).min(1, "DATABASE_URL_UNPOOLED must not be empty")
|
|
25
|
+
});
|
|
26
|
+
const authEnvSchema = z.object({
|
|
27
|
+
NEON_AUTH_BASE_URL: z.string({ message: "NEON_AUTH_BASE_URL is missing" }).min(1, "NEON_AUTH_BASE_URL must not be empty"),
|
|
28
|
+
NEON_AUTH_JWKS_URL: z.string({ message: "NEON_AUTH_JWKS_URL is missing" }).min(1, "NEON_AUTH_JWKS_URL must not be empty")
|
|
29
|
+
});
|
|
30
|
+
const dataApiEnvSchema = z.object({ NEON_DATA_API_URL: z.string({ message: "NEON_DATA_API_URL is missing" }).min(1, "NEON_DATA_API_URL must not be empty") });
|
|
31
|
+
const storageEnvSchema = z.object({
|
|
32
|
+
AWS_ACCESS_KEY_ID: z.string({ message: "AWS_ACCESS_KEY_ID is missing" }).min(1, "AWS_ACCESS_KEY_ID must not be empty"),
|
|
33
|
+
AWS_SECRET_ACCESS_KEY: z.string({ message: "AWS_SECRET_ACCESS_KEY is missing" }).min(1, "AWS_SECRET_ACCESS_KEY must not be empty"),
|
|
34
|
+
AWS_ENDPOINT_URL_S3: z.string({ message: "AWS_ENDPOINT_URL_S3 is missing" }).min(1, "AWS_ENDPOINT_URL_S3 must not be empty"),
|
|
35
|
+
AWS_REGION: z.string({ message: "AWS_REGION is missing" }).min(1, "AWS_REGION must not be empty")
|
|
36
|
+
});
|
|
37
|
+
const aiGatewayEnvSchema = z.object({
|
|
38
|
+
NEON_AI_GATEWAY_TOKEN: z.string({ message: "NEON_AI_GATEWAY_TOKEN is missing" }).min(1, "NEON_AI_GATEWAY_TOKEN must not be empty"),
|
|
39
|
+
NEON_AI_GATEWAY_BASE_URL: z.string({ message: "NEON_AI_GATEWAY_BASE_URL is missing" }).min(1, "NEON_AI_GATEWAY_BASE_URL must not be empty")
|
|
40
|
+
});
|
|
41
|
+
/** Whether a **static** policy declares object storage (`preview.buckets`). No network. */
|
|
42
|
+
function configWantsStorage(config) {
|
|
43
|
+
return Object.keys(config.preview?.buckets ?? {}).length > 0;
|
|
44
|
+
}
|
|
45
|
+
/** Whether a **static** policy enables the AI Gateway (`preview.aiGateway`). No network. */
|
|
46
|
+
function configWantsAiGateway(config) {
|
|
47
|
+
return isServiceEnabledInput(config.preview?.aiGateway);
|
|
48
|
+
}
|
|
49
|
+
/** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */
|
|
50
|
+
function isServiceEnabledInput(toggle) {
|
|
51
|
+
if (toggle === void 0) return false;
|
|
52
|
+
if (typeof toggle === "boolean") return toggle;
|
|
53
|
+
return toggle.enabled !== false;
|
|
54
|
+
}
|
|
55
|
+
function parseEnv(config, scopeOrKeys) {
|
|
56
|
+
const source = process.env;
|
|
57
|
+
if (Array.isArray(scopeOrKeys)) return parseFilteredEnv(source, scopeOrKeys);
|
|
58
|
+
const scope = typeof scopeOrKeys === "string" ? scopeOrKeys : void 0;
|
|
59
|
+
const issues = [];
|
|
60
|
+
const result = {};
|
|
61
|
+
const pg = postgresEnvSchema.safeParse({
|
|
62
|
+
DATABASE_URL: source.DATABASE_URL,
|
|
63
|
+
DATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED
|
|
64
|
+
});
|
|
65
|
+
if (pg.success) result.postgres = {
|
|
66
|
+
databaseUrl: pg.data.DATABASE_URL,
|
|
67
|
+
databaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED
|
|
68
|
+
};
|
|
69
|
+
else for (const issue of pg.error.issues) issues.push(issue.message);
|
|
70
|
+
const branchName = source[NEON_ENV_VAR_KEYS.branch.name];
|
|
71
|
+
if (branchName !== void 0 && branchName !== "") result.branch = { name: branchName };
|
|
72
|
+
if (isServiceEnabledInput(config.auth)) {
|
|
73
|
+
const auth = authEnvSchema.safeParse({
|
|
74
|
+
NEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,
|
|
75
|
+
NEON_AUTH_JWKS_URL: source.NEON_AUTH_JWKS_URL
|
|
76
|
+
});
|
|
77
|
+
if (auth.success) result.auth = {
|
|
78
|
+
baseUrl: auth.data.NEON_AUTH_BASE_URL,
|
|
79
|
+
jwksUrl: auth.data.NEON_AUTH_JWKS_URL
|
|
80
|
+
};
|
|
81
|
+
else for (const issue of auth.error.issues) issues.push(issue.message);
|
|
82
|
+
}
|
|
83
|
+
if (isServiceEnabledInput(config.dataApi)) {
|
|
84
|
+
const dataApi = dataApiEnvSchema.safeParse({ NEON_DATA_API_URL: source.NEON_DATA_API_URL });
|
|
85
|
+
if (dataApi.success) result.dataApi = { url: dataApi.data.NEON_DATA_API_URL };
|
|
86
|
+
else for (const issue of dataApi.error.issues) issues.push(issue.message);
|
|
87
|
+
}
|
|
88
|
+
if (configWantsStorage(config)) {
|
|
89
|
+
const storage = storageEnvSchema.safeParse({
|
|
90
|
+
AWS_ACCESS_KEY_ID: source.AWS_ACCESS_KEY_ID,
|
|
91
|
+
AWS_SECRET_ACCESS_KEY: source.AWS_SECRET_ACCESS_KEY,
|
|
92
|
+
AWS_ENDPOINT_URL_S3: source.AWS_ENDPOINT_URL_S3,
|
|
93
|
+
AWS_REGION: source.AWS_REGION
|
|
94
|
+
});
|
|
95
|
+
if (storage.success) result.storage = {
|
|
96
|
+
accessKeyId: storage.data.AWS_ACCESS_KEY_ID,
|
|
97
|
+
secretAccessKey: storage.data.AWS_SECRET_ACCESS_KEY,
|
|
98
|
+
endpoint: storage.data.AWS_ENDPOINT_URL_S3,
|
|
99
|
+
region: storage.data.AWS_REGION
|
|
100
|
+
};
|
|
101
|
+
else for (const issue of storage.error.issues) issues.push(issue.message);
|
|
102
|
+
}
|
|
103
|
+
if (configWantsAiGateway(config)) {
|
|
104
|
+
const aiGateway = aiGatewayEnvSchema.safeParse({
|
|
105
|
+
NEON_AI_GATEWAY_TOKEN: source.NEON_AI_GATEWAY_TOKEN,
|
|
106
|
+
NEON_AI_GATEWAY_BASE_URL: source.NEON_AI_GATEWAY_BASE_URL
|
|
107
|
+
});
|
|
108
|
+
if (aiGateway.success) result.aiGateway = {
|
|
109
|
+
apiKey: aiGateway.data.NEON_AI_GATEWAY_TOKEN,
|
|
110
|
+
baseUrl: aiGateway.data.NEON_AI_GATEWAY_BASE_URL
|
|
111
|
+
};
|
|
112
|
+
else for (const issue of aiGateway.error.issues) issues.push(issue.message);
|
|
113
|
+
}
|
|
114
|
+
if (scope !== void 0) {
|
|
115
|
+
const fn = config.preview?.functions?.[scope];
|
|
116
|
+
if (!fn) throw new PlatformError(ErrorCode.EnvNotInjected, [`parseEnv: no function "${scope}" is declared in this policy's preview.functions.`, "Pass a declared function slug (or omit the scope to read external env)."].join("\n"), { details: { scope } });
|
|
117
|
+
const envOut = {};
|
|
118
|
+
for (const key of Object.keys(fn.env ?? {})) {
|
|
119
|
+
const value = source[key];
|
|
120
|
+
if (value === void 0) issues.push(`${key} is missing (function "${scope}")`);
|
|
121
|
+
else envOut[key] = value;
|
|
122
|
+
}
|
|
123
|
+
result.function = envOut;
|
|
124
|
+
}
|
|
125
|
+
if (issues.length > 0) throw new PlatformError(ErrorCode.EnvNotInjected, [
|
|
126
|
+
"parseEnv: the required Neon env variables are not present in process.env.",
|
|
127
|
+
...issues.map((i) => ` - ${i}`),
|
|
128
|
+
"Inject them via one of:",
|
|
129
|
+
" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)",
|
|
130
|
+
" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)",
|
|
131
|
+
" - for the `function` namespace: deploy the function (`neon deploy` / `config apply`) so its env is uploaded.",
|
|
132
|
+
"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O."
|
|
133
|
+
].join("\n"), { details: { missing: issues } });
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Runtime reverse map for filtered `parseEnv`: OS-level env-var key → `[namespace, property]`
|
|
138
|
+
* in the {@link NeonEnv} shape. The compile-time mirror is {@link EnvKeysByNamespace} /
|
|
139
|
+
* {@link EnvKeyToProp}; keep all three in sync. Only input vars appear (no output-only
|
|
140
|
+
* aliases).
|
|
141
|
+
*/
|
|
142
|
+
const FILTERABLE_ENV_KEYS = {
|
|
143
|
+
DATABASE_URL: ["postgres", "databaseUrl"],
|
|
144
|
+
DATABASE_URL_UNPOOLED: ["postgres", "databaseUrlUnpooled"],
|
|
145
|
+
NEON_BRANCH: ["branch", "name"],
|
|
146
|
+
NEON_AUTH_BASE_URL: ["auth", "baseUrl"],
|
|
147
|
+
NEON_AUTH_JWKS_URL: ["auth", "jwksUrl"],
|
|
148
|
+
NEON_DATA_API_URL: ["dataApi", "url"],
|
|
149
|
+
AWS_ACCESS_KEY_ID: ["storage", "accessKeyId"],
|
|
150
|
+
AWS_SECRET_ACCESS_KEY: ["storage", "secretAccessKey"],
|
|
151
|
+
AWS_ENDPOINT_URL_S3: ["storage", "endpoint"],
|
|
152
|
+
AWS_REGION: ["storage", "region"],
|
|
153
|
+
NEON_AI_GATEWAY_TOKEN: ["aiGateway", "apiKey"],
|
|
154
|
+
NEON_AI_GATEWAY_BASE_URL: ["aiGateway", "baseUrl"]
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Filtered counterpart to the {@link parseEnv} body: validate and return only the explicitly
|
|
158
|
+
* selected OS-level env-var keys, projected back into the narrowed namespaced shape. Unlike
|
|
159
|
+
* the full reader it never consults the policy — the selection alone decides what's required —
|
|
160
|
+
* so vars the caller didn't ask for (e.g. `DATABASE_URL_UNPOOLED`) can be absent without
|
|
161
|
+
* throwing. Mirrors the same non-empty constraint and {@link PlatformError} aggregation.
|
|
162
|
+
*/
|
|
163
|
+
function parseFilteredEnv(source, keys) {
|
|
164
|
+
const issues = [];
|
|
165
|
+
const result = {};
|
|
166
|
+
for (const key of keys) {
|
|
167
|
+
if (!Object.hasOwn(FILTERABLE_ENV_KEYS, key)) {
|
|
168
|
+
issues.push(`${key} is not a selectable Neon env variable`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const value = source[key];
|
|
172
|
+
if (value === void 0) {
|
|
173
|
+
issues.push(`${key} is missing`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (value === "") {
|
|
177
|
+
issues.push(`${key} must not be empty`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const [namespace, property] = FILTERABLE_ENV_KEYS[key];
|
|
181
|
+
const bucket = result[namespace] ?? {};
|
|
182
|
+
bucket[property] = value;
|
|
183
|
+
result[namespace] = bucket;
|
|
184
|
+
}
|
|
185
|
+
if (issues.length > 0) throw new PlatformError(ErrorCode.EnvNotInjected, [
|
|
186
|
+
"parseEnv: the required Neon env variables are not present in process.env.",
|
|
187
|
+
...issues.map((i) => ` - ${i}`),
|
|
188
|
+
"Inject them via one of:",
|
|
189
|
+
" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)",
|
|
190
|
+
" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)",
|
|
191
|
+
"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O."
|
|
192
|
+
].join("\n"), { details: { missing: issues } });
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
3
196
|
export { NEON_ENV_VAR_KEYS, fetchEnv, parseEnv, toEntries };
|
|
197
|
+
|
|
198
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-env.js","names":[],"sources":["../../src/lib/parse-env.ts"],"sourcesContent":["/**\n * `parseEnv` — the synchronous, network-free counterpart to `fetchEnv`: read the Neon env vars\n * already injected into `process.env`, validate them against the policy, and return them in the\n * same namespaced shape.\n *\n * Lives in this package rather than in `shared/env-core` because nothing else needs it. The\n * `neon` CLI resolves env from the API and injects it; it never reads it back. Keeping it here\n * also keeps `zod` out of the shared tree, and so out of every consumer that copies it.\n */\n\nimport {\n\ttype Config,\n\tErrorCode,\n\tPlatformError,\n\ttype ServiceToggleInput,\n} from \"@neon/config/v1\";\nimport { z } from \"zod\";\n\nimport {\n\ttype FilteredNeonEnv,\n\tNEON_ENV_VAR_KEYS,\n\ttype NeonAiGatewayEnv,\n\ttype NeonAuthEnv,\n\ttype NeonBranchEnv,\n\ttype NeonDataApiEnv,\n\ttype NeonEnv,\n\ttype NeonPostgresEnv,\n\ttype NeonStorageEnv,\n\ttype SelectableEnvKey,\n} from \"../_shared/env-core/env.js\";\n\n/** The static `preview.functions` record of a config, or an empty record when absent. */\ntype PreviewFunctionsOf<C extends Config> =\n\tNonNullable<C[\"preview\"]> extends {\n\t\tfunctions: infer F;\n\t}\n\t\t? F\n\t\t: Record<never, never>;\n\n/** The declared function slugs of a config (record keys), as a string union. */\nexport type FunctionSlugOf<C extends Config> = Extract<\n\tkeyof PreviewFunctionsOf<C>,\n\tstring\n>;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `parseEnv`'s `scope` argument when\n * the policy declares no functions at all. Without it the argument's expected type is the bare\n * `never` {@link FunctionSlugOf} yields, and TypeScript reports the opaque `Type '\"x\"' is not\n * assignable to type 'never'`; the literal turns that into a sentence naming the fix (and the\n * editor offers it as the single completion, so the empty completion list is explained rather\n * than just empty). Mirrors `NeonAuthRequiredHint` in `@neon/config`.\n */\n// Exported (type-only) for the type tests in `env.test-d.ts`; intentionally not re-exported\n// from `index.ts`, so it stays an internal implementation detail.\nexport type NoFunctionScopeHint =\n\t\"this policy declares no `preview.functions`, so there is no function scope to read. Declare the function in `neon.ts` first, or omit the scope to read the branch env\";\n\n/**\n * The expected type of `parseEnv`'s function-slug `scope` argument: the caller's inferred slug\n * `S` normally, and the {@link NoFunctionScopeHint} message when the policy declares no\n * functions. Keeping `S` (rather than `FunctionSlugOf<C>`) in the enabled branch is what makes\n * the returned `function` namespace exact — it stays the one function's env keys instead of\n * widening to every declared function's.\n */\ntype FunctionScopeField<C extends Config, S extends string> = [\n\tFunctionSlugOf<C>,\n] extends [never]\n\t? NoFunctionScopeHint\n\t: S;\n\n/** The declared env-var keys of one function `S`, as a string union. */\ntype FunctionEnvKeysOf<\n\tC extends Config,\n\tS extends string,\n> = S extends keyof PreviewFunctionsOf<C>\n\t? NonNullable<PreviewFunctionsOf<C>[S]> extends { env: infer E }\n\t\t? Extract<keyof E, string>\n\t\t: never\n\t: never;\n\n/**\n * The extra `function` namespace added to `parseEnv`'s result when called with a function\n * slug scope: the declared env-var keys for that function, each resolved to a `string`.\n */\nexport type NeonFunctionEnv<C extends Config, S extends string> = {\n\tfunction: Record<FunctionEnvKeysOf<C, S>, string>;\n};\n\n// ───────────────────────── parseEnv ─────────────────────────\n\n/**\n * Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from\n * `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.\n *\n * `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include\n * URL-illegal characters in the password (rare but legal in Neon's connection-string\n * format) fail the WHATWG `URL` parse, so we settle for \"non-empty string\".\n */\nconst postgresEnvSchema = z.object({\n\tDATABASE_URL: z\n\t\t.string({ message: \"DATABASE_URL is missing\" })\n\t\t.min(1, \"DATABASE_URL must not be empty\"),\n\tDATABASE_URL_UNPOOLED: z\n\t\t.string({ message: \"DATABASE_URL_UNPOOLED is missing\" })\n\t\t.min(1, \"DATABASE_URL_UNPOOLED must not be empty\"),\n});\n\nconst authEnvSchema = z.object({\n\tNEON_AUTH_BASE_URL: z\n\t\t.string({ message: \"NEON_AUTH_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_BASE_URL must not be empty\"),\n\tNEON_AUTH_JWKS_URL: z\n\t\t.string({ message: \"NEON_AUTH_JWKS_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_JWKS_URL must not be empty\"),\n});\n\nconst dataApiEnvSchema = z.object({\n\tNEON_DATA_API_URL: z\n\t\t.string({ message: \"NEON_DATA_API_URL is missing\" })\n\t\t.min(1, \"NEON_DATA_API_URL must not be empty\"),\n});\n\nconst storageEnvSchema = z.object({\n\tAWS_ACCESS_KEY_ID: z\n\t\t.string({ message: \"AWS_ACCESS_KEY_ID is missing\" })\n\t\t.min(1, \"AWS_ACCESS_KEY_ID must not be empty\"),\n\tAWS_SECRET_ACCESS_KEY: z\n\t\t.string({ message: \"AWS_SECRET_ACCESS_KEY is missing\" })\n\t\t.min(1, \"AWS_SECRET_ACCESS_KEY must not be empty\"),\n\tAWS_ENDPOINT_URL_S3: z\n\t\t.string({ message: \"AWS_ENDPOINT_URL_S3 is missing\" })\n\t\t.min(1, \"AWS_ENDPOINT_URL_S3 must not be empty\"),\n\tAWS_REGION: z\n\t\t.string({ message: \"AWS_REGION is missing\" })\n\t\t.min(1, \"AWS_REGION must not be empty\"),\n});\n\nconst aiGatewayEnvSchema = z.object({\n\tNEON_AI_GATEWAY_TOKEN: z\n\t\t.string({ message: \"NEON_AI_GATEWAY_TOKEN is missing\" })\n\t\t.min(1, \"NEON_AI_GATEWAY_TOKEN must not be empty\"),\n\tNEON_AI_GATEWAY_BASE_URL: z\n\t\t.string({ message: \"NEON_AI_GATEWAY_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AI_GATEWAY_BASE_URL must not be empty\"),\n});\n\n/** Whether a **static** policy declares object storage (`preview.buckets`). No network. */\nfunction configWantsStorage(config: Config): boolean {\n\treturn Object.keys(config.preview?.buckets ?? {}).length > 0;\n}\n\n/** Whether a **static** policy enables the AI Gateway (`preview.aiGateway`). No network. */\nfunction configWantsAiGateway(config: Config): boolean {\n\treturn isServiceEnabledInput(config.preview?.aiGateway);\n}\n\n/** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */\nfunction isServiceEnabledInput(\n\ttoggle: ServiceToggleInput | undefined,\n): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/**\n * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env`, validates\n * the required Neon env vars with zod, and returns the same {@link NeonEnv} shape — so the\n * rest of your app touches `env.postgres.databaseUrl` instead of stringly-typed\n * `process.env.DATABASE_URL` lookups.\n *\n * Designed for the **\"env-vars-already-injected\"** path:\n * - You wrapped your dev command with `neon-env run -- <cmd>` or `neon dev`.\n * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.\n * - You are **inside a deployed Neon Function**, whose env was uploaded at `config apply`.\n *\n * Unlike the old API, `parseEnv` does **not** take a branch name: the secret set is now\n * static (top-level `config.auth` / `config.dataApi`), so it reads those directly without\n * evaluating the per-branch closure.\n *\n * The second argument is a **scope** or a **key filter**:\n * - omitted — *external* scope (app bootstrap, build scripts, your dev machine). Returns the\n * full `{ postgres, auth?, dataApi?, … }` the policy enables.\n * - a **function slug** (a key of `config.preview.functions`) — *function* scope: you are\n * running inside that function. Returns the same branch secrets **plus** a typed\n * `function` namespace with the function's declared env-var keys. The slug autocompletes\n * from the policy ({@link FunctionSlugOf}) and an undeclared one is a type error.\n * - an **array of OS-level env-var keys** (e.g. `[\"DATABASE_URL\", \"NEON_AUTH_BASE_URL\"]`) —\n * *filtered* mode: only those vars are required and returned, as a narrowed namespaced\n * shape. The keys autocomplete from the policy ({@link SelectableEnvKey}), so you can only\n * pick vars the policy actually enables. Use this when a process needs just a subset (a\n * Next.js app that reads `DATABASE_URL` but not `DATABASE_URL_UNPOOLED`, say) and you don't\n * want `parseEnv` to throw over vars you never use.\n *\n * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env\n * isn't fully populated, with a fix hint pointing back at `neon dev` / `neon-env run`.\n *\n * ```ts\n * import config from \"../neon\";\n * import { parseEnv } from \"@neon/env\";\n *\n * // External (app / build):\n * const env = parseEnv(config);\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n *\n * // Inside the \"hello\" function:\n * const env = parseEnv(config, \"hello\");\n * env.function.resendApiKey; // typed from hello's declared env keys\n *\n * // Filtered: only enforce + return the pooled URL.\n * const { postgres } = parseEnv(config, [\"DATABASE_URL\"]);\n * postgres.databaseUrl; // string — `databaseUrlUnpooled` is absent\n * ```\n */\nexport function parseEnv<const C extends Config>(config: C): NeonEnv<C>;\n// Overload order is load-bearing for **editor autocomplete**, not for type checking: when the\n// argument is a half-typed string literal the call resolves against no signature, and the\n// editor takes its string-literal completions from the first candidate overload. With the\n// `keys` overload listed first, the expected type of `parseEnv(config, \"…\")` is read as\n// `readonly K[]` — an array has no literal completions, so typing a function slug offered\n// nothing. Keep the slug overload ahead of the array one: `env.completions.test.ts` asserts the\n// completions through the language service, and `env.test-d.ts` locks the order itself (the\n// last overload is observable as `Parameters<typeof parseEnv>`), so `tsc` fails on a reorder.\nexport function parseEnv<\n\tconst C extends Config,\n\tconst S extends FunctionSlugOf<C>,\n>(\n\tconfig: C,\n\tscope: FunctionScopeField<C, S>,\n): NeonEnv<C> & NeonFunctionEnv<C, S>;\nexport function parseEnv<\n\tconst C extends Config,\n\tconst K extends SelectableEnvKey<C>,\n>(config: C, keys: readonly K[]): FilteredNeonEnv<K>;\nexport function parseEnv(\n\tconfig: Config,\n\tscopeOrKeys?: string | readonly string[],\n): unknown {\n\tconst source = process.env;\n\tif (Array.isArray(scopeOrKeys)) {\n\t\treturn parseFilteredEnv(source, scopeOrKeys);\n\t}\n\t// `Array.isArray` doesn't narrow a `readonly string[]` out of the union, so re-derive the\n\t// function-slug scope from the remaining `string` shape explicitly.\n\tconst scope = typeof scopeOrKeys === \"string\" ? scopeOrKeys : undefined;\n\tconst issues: string[] = [];\n\tconst result: Record<string, unknown> = {};\n\n\tconst pg = postgresEnvSchema.safeParse({\n\t\tDATABASE_URL: source.DATABASE_URL,\n\t\tDATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED,\n\t});\n\tif (pg.success) {\n\t\tresult.postgres = {\n\t\t\tdatabaseUrl: pg.data.DATABASE_URL,\n\t\t\tdatabaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED,\n\t\t} satisfies NeonPostgresEnv;\n\t} else {\n\t\tfor (const issue of pg.error.issues) issues.push(issue.message);\n\t}\n\n\t// Branch identity is optional: the Functions runtime injects `NEON_BRANCH` on every\n\t// branch by default and `neon dev` / `neon-env run` / `env pull` emit it too, but older\n\t// runtimes and platform integrations may not, so a missing value is not an error — we\n\t// just omit the namespace rather than failing the whole parse.\n\tconst branchName = source[NEON_ENV_VAR_KEYS.branch.name];\n\tif (branchName !== undefined && branchName !== \"\") {\n\t\tresult.branch = { name: branchName } satisfies NeonBranchEnv;\n\t}\n\n\tif (isServiceEnabledInput(config.auth)) {\n\t\tconst auth = authEnvSchema.safeParse({\n\t\t\tNEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,\n\t\t\tNEON_AUTH_JWKS_URL: source.NEON_AUTH_JWKS_URL,\n\t\t});\n\t\tif (auth.success) {\n\t\t\tresult.auth = {\n\t\t\t\tbaseUrl: auth.data.NEON_AUTH_BASE_URL,\n\t\t\t\tjwksUrl: auth.data.NEON_AUTH_JWKS_URL,\n\t\t\t} satisfies NeonAuthEnv;\n\t\t} else {\n\t\t\tfor (const issue of auth.error.issues) issues.push(issue.message);\n\t\t}\n\t}\n\n\tif (isServiceEnabledInput(config.dataApi)) {\n\t\tconst dataApi = dataApiEnvSchema.safeParse({\n\t\t\tNEON_DATA_API_URL: source.NEON_DATA_API_URL,\n\t\t});\n\t\tif (dataApi.success) {\n\t\t\tresult.dataApi = {\n\t\t\t\turl: dataApi.data.NEON_DATA_API_URL,\n\t\t\t} satisfies NeonDataApiEnv;\n\t\t} else {\n\t\t\tfor (const issue of dataApi.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (configWantsStorage(config)) {\n\t\tconst storage = storageEnvSchema.safeParse({\n\t\t\tAWS_ACCESS_KEY_ID: source.AWS_ACCESS_KEY_ID,\n\t\t\tAWS_SECRET_ACCESS_KEY: source.AWS_SECRET_ACCESS_KEY,\n\t\t\tAWS_ENDPOINT_URL_S3: source.AWS_ENDPOINT_URL_S3,\n\t\t\tAWS_REGION: source.AWS_REGION,\n\t\t});\n\t\tif (storage.success) {\n\t\t\tresult.storage = {\n\t\t\t\taccessKeyId: storage.data.AWS_ACCESS_KEY_ID,\n\t\t\t\tsecretAccessKey: storage.data.AWS_SECRET_ACCESS_KEY,\n\t\t\t\tendpoint: storage.data.AWS_ENDPOINT_URL_S3,\n\t\t\t\tregion: storage.data.AWS_REGION,\n\t\t\t} satisfies NeonStorageEnv;\n\t\t} else {\n\t\t\tfor (const issue of storage.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (configWantsAiGateway(config)) {\n\t\tconst aiGateway = aiGatewayEnvSchema.safeParse({\n\t\t\tNEON_AI_GATEWAY_TOKEN: source.NEON_AI_GATEWAY_TOKEN,\n\t\t\tNEON_AI_GATEWAY_BASE_URL: source.NEON_AI_GATEWAY_BASE_URL,\n\t\t});\n\t\tif (aiGateway.success) {\n\t\t\tresult.aiGateway = {\n\t\t\t\tapiKey: aiGateway.data.NEON_AI_GATEWAY_TOKEN,\n\t\t\t\tbaseUrl: aiGateway.data.NEON_AI_GATEWAY_BASE_URL,\n\t\t\t} satisfies NeonAiGatewayEnv;\n\t\t} else {\n\t\t\tfor (const issue of aiGateway.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (scope !== undefined) {\n\t\tconst fn = config.preview?.functions?.[scope];\n\t\tif (!fn) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.EnvNotInjected,\n\t\t\t\t[\n\t\t\t\t\t`parseEnv: no function \"${scope}\" is declared in this policy's preview.functions.`,\n\t\t\t\t\t\"Pass a declared function slug (or omit the scope to read external env).\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t{ details: { scope } },\n\t\t\t);\n\t\t}\n\t\tconst envOut: Record<string, string> = {};\n\t\tfor (const key of Object.keys(fn.env ?? {})) {\n\t\t\tconst value = source[key];\n\t\t\t// Only a truly *unset* var is \"not injected\". Function env values carry no\n\t\t\t// non-empty constraint (unlike DATABASE_URL / NEON_AUTH_BASE_URL), so a\n\t\t\t// deliberately empty value is a present, valid value and is passed through.\n\t\t\tif (value === undefined) {\n\t\t\t\tissues.push(`${key} is missing (function \"${scope}\")`);\n\t\t\t} else {\n\t\t\t\tenvOut[key] = value;\n\t\t\t}\n\t\t}\n\t\tresult.function = envOut;\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\" - for the `function` namespace: deploy the function (`neon deploy` / `config apply`) so its env is uploaded.\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\n\treturn result;\n}\n\n/**\n * Runtime reverse map for filtered `parseEnv`: OS-level env-var key → `[namespace, property]`\n * in the {@link NeonEnv} shape. The compile-time mirror is {@link EnvKeysByNamespace} /\n * {@link EnvKeyToProp}; keep all three in sync. Only input vars appear (no output-only\n * aliases).\n */\nconst FILTERABLE_ENV_KEYS: Record<string, readonly [string, string]> = {\n\tDATABASE_URL: [\"postgres\", \"databaseUrl\"],\n\tDATABASE_URL_UNPOOLED: [\"postgres\", \"databaseUrlUnpooled\"],\n\tNEON_BRANCH: [\"branch\", \"name\"],\n\tNEON_AUTH_BASE_URL: [\"auth\", \"baseUrl\"],\n\tNEON_AUTH_JWKS_URL: [\"auth\", \"jwksUrl\"],\n\tNEON_DATA_API_URL: [\"dataApi\", \"url\"],\n\tAWS_ACCESS_KEY_ID: [\"storage\", \"accessKeyId\"],\n\tAWS_SECRET_ACCESS_KEY: [\"storage\", \"secretAccessKey\"],\n\tAWS_ENDPOINT_URL_S3: [\"storage\", \"endpoint\"],\n\tAWS_REGION: [\"storage\", \"region\"],\n\tNEON_AI_GATEWAY_TOKEN: [\"aiGateway\", \"apiKey\"],\n\tNEON_AI_GATEWAY_BASE_URL: [\"aiGateway\", \"baseUrl\"],\n};\n\n/**\n * Filtered counterpart to the {@link parseEnv} body: validate and return only the explicitly\n * selected OS-level env-var keys, projected back into the narrowed namespaced shape. Unlike\n * the full reader it never consults the policy — the selection alone decides what's required —\n * so vars the caller didn't ask for (e.g. `DATABASE_URL_UNPOOLED`) can be absent without\n * throwing. Mirrors the same non-empty constraint and {@link PlatformError} aggregation.\n */\nfunction parseFilteredEnv(\n\tsource: NodeJS.ProcessEnv,\n\tkeys: readonly string[],\n): Record<string, Record<string, string>> {\n\tconst issues: string[] = [];\n\tconst result: Record<string, Record<string, string>> = {};\n\tfor (const key of keys) {\n\t\t// Unknown keys are blocked at the type level; a runtime caller bypassing the types\n\t\t// gets a clear error rather than a silently-dropped selection.\n\t\tif (!Object.hasOwn(FILTERABLE_ENV_KEYS, key)) {\n\t\t\tissues.push(`${key} is not a selectable Neon env variable`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst value = source[key];\n\t\tif (value === undefined) {\n\t\t\tissues.push(`${key} is missing`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value === \"\") {\n\t\t\tissues.push(`${key} must not be empty`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst [namespace, property] = FILTERABLE_ENV_KEYS[key];\n\t\tconst bucket = result[namespace] ?? {};\n\t\tbucket[property] = value;\n\t\tresult[namespace] = bucket;\n\t}\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/lib/parse-env.ts"],"sourcesContent":["/**\n * `parseEnv` — the synchronous, network-free counterpart to `fetchEnv`: read the Neon env vars\n * already injected into `process.env`, validate them against the policy, and return them in the\n * same namespaced shape.\n *\n * Lives in this package rather than in `@neon-internals/env-core` because nothing else needs\n * it. The `neon` CLI resolves env from the API and injects it; it never reads it back. Keeping\n * it here also keeps `zod` out of that package, and so out of every consumer that bundles it.\n */\n\nimport {\n\ttype Config,\n\tErrorCode,\n\tPlatformError,\n\ttype ServiceToggleInput,\n} from \"@neon/config/v1\";\nimport {\n\ttype FilteredNeonEnv,\n\tNEON_ENV_VAR_KEYS,\n\ttype NeonAiGatewayEnv,\n\ttype NeonAuthEnv,\n\ttype NeonBranchEnv,\n\ttype NeonDataApiEnv,\n\ttype NeonEnv,\n\ttype NeonPostgresEnv,\n\ttype NeonStorageEnv,\n\ttype SelectableEnvKey,\n} from \"@neon-internals/env-core/env\";\nimport { z } from \"zod\";\n\n/** The static `preview.functions` record of a config, or an empty record when absent. */\ntype PreviewFunctionsOf<C extends Config> =\n\tNonNullable<C[\"preview\"]> extends {\n\t\tfunctions: infer F;\n\t}\n\t\t? F\n\t\t: Record<never, never>;\n\n/** The declared function slugs of a config (record keys), as a string union. */\nexport type FunctionSlugOf<C extends Config> = Extract<\n\tkeyof PreviewFunctionsOf<C>,\n\tstring\n>;\n\n/**\n * Human-readable hint surfaced as the **expected type** of `parseEnv`'s `scope` argument when\n * the policy declares no functions at all. Without it the argument's expected type is the bare\n * `never` {@link FunctionSlugOf} yields, and TypeScript reports the opaque `Type '\"x\"' is not\n * assignable to type 'never'`; the literal turns that into a sentence naming the fix (and the\n * editor offers it as the single completion, so the empty completion list is explained rather\n * than just empty). Mirrors `NeonAuthRequiredHint` in `@neon/config`.\n */\n// Exported (type-only) for the type tests in `env.test-d.ts`; intentionally not re-exported\n// from `index.ts`, so it stays an internal implementation detail.\nexport type NoFunctionScopeHint =\n\t\"this policy declares no `preview.functions`, so there is no function scope to read. Declare the function in `neon.ts` first, or omit the scope to read the branch env\";\n\n/**\n * The expected type of `parseEnv`'s function-slug `scope` argument: the caller's inferred slug\n * `S` normally, and the {@link NoFunctionScopeHint} message when the policy declares no\n * functions. Keeping `S` (rather than `FunctionSlugOf<C>`) in the enabled branch is what makes\n * the returned `function` namespace exact — it stays the one function's env keys instead of\n * widening to every declared function's.\n */\ntype FunctionScopeField<C extends Config, S extends string> = [\n\tFunctionSlugOf<C>,\n] extends [never]\n\t? NoFunctionScopeHint\n\t: S;\n\n/** The declared env-var keys of one function `S`, as a string union. */\ntype FunctionEnvKeysOf<\n\tC extends Config,\n\tS extends string,\n> = S extends keyof PreviewFunctionsOf<C>\n\t? NonNullable<PreviewFunctionsOf<C>[S]> extends { env: infer E }\n\t\t? Extract<keyof E, string>\n\t\t: never\n\t: never;\n\n/**\n * The extra `function` namespace added to `parseEnv`'s result when called with a function\n * slug scope: the declared env-var keys for that function, each resolved to a `string`.\n */\nexport type NeonFunctionEnv<C extends Config, S extends string> = {\n\tfunction: Record<FunctionEnvKeysOf<C, S>, string>;\n};\n\n// ───────────────────────── parseEnv ─────────────────────────\n\n/**\n * Per-namespace zod schemas. Each defines exactly the OS-level keys parsed from\n * `process.env` for its namespace. Keep in sync with {@link NEON_ENV_VAR_KEYS}.\n *\n * `z.string().url()` would be tighter than `min(1)` but Postgres URIs that include\n * URL-illegal characters in the password (rare but legal in Neon's connection-string\n * format) fail the WHATWG `URL` parse, so we settle for \"non-empty string\".\n */\nconst postgresEnvSchema = z.object({\n\tDATABASE_URL: z\n\t\t.string({ message: \"DATABASE_URL is missing\" })\n\t\t.min(1, \"DATABASE_URL must not be empty\"),\n\tDATABASE_URL_UNPOOLED: z\n\t\t.string({ message: \"DATABASE_URL_UNPOOLED is missing\" })\n\t\t.min(1, \"DATABASE_URL_UNPOOLED must not be empty\"),\n});\n\nconst authEnvSchema = z.object({\n\tNEON_AUTH_BASE_URL: z\n\t\t.string({ message: \"NEON_AUTH_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_BASE_URL must not be empty\"),\n\tNEON_AUTH_JWKS_URL: z\n\t\t.string({ message: \"NEON_AUTH_JWKS_URL is missing\" })\n\t\t.min(1, \"NEON_AUTH_JWKS_URL must not be empty\"),\n});\n\nconst dataApiEnvSchema = z.object({\n\tNEON_DATA_API_URL: z\n\t\t.string({ message: \"NEON_DATA_API_URL is missing\" })\n\t\t.min(1, \"NEON_DATA_API_URL must not be empty\"),\n});\n\nconst storageEnvSchema = z.object({\n\tAWS_ACCESS_KEY_ID: z\n\t\t.string({ message: \"AWS_ACCESS_KEY_ID is missing\" })\n\t\t.min(1, \"AWS_ACCESS_KEY_ID must not be empty\"),\n\tAWS_SECRET_ACCESS_KEY: z\n\t\t.string({ message: \"AWS_SECRET_ACCESS_KEY is missing\" })\n\t\t.min(1, \"AWS_SECRET_ACCESS_KEY must not be empty\"),\n\tAWS_ENDPOINT_URL_S3: z\n\t\t.string({ message: \"AWS_ENDPOINT_URL_S3 is missing\" })\n\t\t.min(1, \"AWS_ENDPOINT_URL_S3 must not be empty\"),\n\tAWS_REGION: z\n\t\t.string({ message: \"AWS_REGION is missing\" })\n\t\t.min(1, \"AWS_REGION must not be empty\"),\n});\n\nconst aiGatewayEnvSchema = z.object({\n\tNEON_AI_GATEWAY_TOKEN: z\n\t\t.string({ message: \"NEON_AI_GATEWAY_TOKEN is missing\" })\n\t\t.min(1, \"NEON_AI_GATEWAY_TOKEN must not be empty\"),\n\tNEON_AI_GATEWAY_BASE_URL: z\n\t\t.string({ message: \"NEON_AI_GATEWAY_BASE_URL is missing\" })\n\t\t.min(1, \"NEON_AI_GATEWAY_BASE_URL must not be empty\"),\n});\n\n/** Whether a **static** policy declares object storage (`preview.buckets`). No network. */\nfunction configWantsStorage(config: Config): boolean {\n\treturn Object.keys(config.preview?.buckets ?? {}).length > 0;\n}\n\n/** Whether a **static** policy enables the AI Gateway (`preview.aiGateway`). No network. */\nfunction configWantsAiGateway(config: Config): boolean {\n\treturn isServiceEnabledInput(config.preview?.aiGateway);\n}\n\n/** Static-toggle helper mirroring `config`'s `isServiceEnabled` for the env reader. */\nfunction isServiceEnabledInput(\n\ttoggle: ServiceToggleInput | undefined,\n): boolean {\n\tif (toggle === undefined) return false;\n\tif (typeof toggle === \"boolean\") return toggle;\n\treturn toggle.enabled !== false;\n}\n\n/**\n * Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env`, validates\n * the required Neon env vars with zod, and returns the same {@link NeonEnv} shape — so the\n * rest of your app touches `env.postgres.databaseUrl` instead of stringly-typed\n * `process.env.DATABASE_URL` lookups.\n *\n * Designed for the **\"env-vars-already-injected\"** path:\n * - You wrapped your dev command with `neon-env run -- <cmd>` or `neon dev`.\n * - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.\n * - You are **inside a deployed Neon Function**, whose env was uploaded at `config apply`.\n *\n * Unlike the old API, `parseEnv` does **not** take a branch name: the secret set is now\n * static (top-level `config.auth` / `config.dataApi`), so it reads those directly without\n * evaluating the per-branch closure.\n *\n * The second argument is a **scope** or a **key filter**:\n * - omitted — *external* scope (app bootstrap, build scripts, your dev machine). Returns the\n * full `{ postgres, auth?, dataApi?, … }` the policy enables.\n * - a **function slug** (a key of `config.preview.functions`) — *function* scope: you are\n * running inside that function. Returns the same branch secrets **plus** a typed\n * `function` namespace with the function's declared env-var keys. The slug autocompletes\n * from the policy ({@link FunctionSlugOf}) and an undeclared one is a type error.\n * - an **array of OS-level env-var keys** (e.g. `[\"DATABASE_URL\", \"NEON_AUTH_BASE_URL\"]`) —\n * *filtered* mode: only those vars are required and returned, as a narrowed namespaced\n * shape. The keys autocomplete from the policy ({@link SelectableEnvKey}), so you can only\n * pick vars the policy actually enables. Use this when a process needs just a subset (a\n * Next.js app that reads `DATABASE_URL` but not `DATABASE_URL_UNPOOLED`, say) and you don't\n * want `parseEnv` to throw over vars you never use.\n *\n * Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env\n * isn't fully populated, with a fix hint pointing back at `neon dev` / `neon-env run`.\n *\n * ```ts\n * import config from \"../neon\";\n * import { parseEnv } from \"@neon/env\";\n *\n * // External (app / build):\n * const env = parseEnv(config);\n * const db = drizzle(neon(env.postgres.databaseUrl), { schema });\n *\n * // Inside the \"hello\" function:\n * const env = parseEnv(config, \"hello\");\n * env.function.resendApiKey; // typed from hello's declared env keys\n *\n * // Filtered: only enforce + return the pooled URL.\n * const { postgres } = parseEnv(config, [\"DATABASE_URL\"]);\n * postgres.databaseUrl; // string — `databaseUrlUnpooled` is absent\n * ```\n */\nexport function parseEnv<const C extends Config>(config: C): NeonEnv<C>;\n// Overload order is load-bearing for **editor autocomplete**, not for type checking: when the\n// argument is a half-typed string literal the call resolves against no signature, and the\n// editor takes its string-literal completions from the first candidate overload. With the\n// `keys` overload listed first, the expected type of `parseEnv(config, \"…\")` is read as\n// `readonly K[]` — an array has no literal completions, so typing a function slug offered\n// nothing. Keep the slug overload ahead of the array one: `env.completions.test.ts` asserts the\n// completions through the language service, and `env.test-d.ts` locks the order itself (the\n// last overload is observable as `Parameters<typeof parseEnv>`), so `tsc` fails on a reorder.\nexport function parseEnv<\n\tconst C extends Config,\n\tconst S extends FunctionSlugOf<C>,\n>(\n\tconfig: C,\n\tscope: FunctionScopeField<C, S>,\n): NeonEnv<C> & NeonFunctionEnv<C, S>;\nexport function parseEnv<\n\tconst C extends Config,\n\tconst K extends SelectableEnvKey<C>,\n>(config: C, keys: readonly K[]): FilteredNeonEnv<K>;\nexport function parseEnv(\n\tconfig: Config,\n\tscopeOrKeys?: string | readonly string[],\n): unknown {\n\tconst source = process.env;\n\tif (Array.isArray(scopeOrKeys)) {\n\t\treturn parseFilteredEnv(source, scopeOrKeys);\n\t}\n\t// `Array.isArray` doesn't narrow a `readonly string[]` out of the union, so re-derive the\n\t// function-slug scope from the remaining `string` shape explicitly.\n\tconst scope = typeof scopeOrKeys === \"string\" ? scopeOrKeys : undefined;\n\tconst issues: string[] = [];\n\tconst result: Record<string, unknown> = {};\n\n\tconst pg = postgresEnvSchema.safeParse({\n\t\tDATABASE_URL: source.DATABASE_URL,\n\t\tDATABASE_URL_UNPOOLED: source.DATABASE_URL_UNPOOLED,\n\t});\n\tif (pg.success) {\n\t\tresult.postgres = {\n\t\t\tdatabaseUrl: pg.data.DATABASE_URL,\n\t\t\tdatabaseUrlUnpooled: pg.data.DATABASE_URL_UNPOOLED,\n\t\t} satisfies NeonPostgresEnv;\n\t} else {\n\t\tfor (const issue of pg.error.issues) issues.push(issue.message);\n\t}\n\n\t// Branch identity is optional: the Functions runtime injects `NEON_BRANCH` on every\n\t// branch by default and `neon dev` / `neon-env run` / `env pull` emit it too, but older\n\t// runtimes and platform integrations may not, so a missing value is not an error — we\n\t// just omit the namespace rather than failing the whole parse.\n\tconst branchName = source[NEON_ENV_VAR_KEYS.branch.name];\n\tif (branchName !== undefined && branchName !== \"\") {\n\t\tresult.branch = { name: branchName } satisfies NeonBranchEnv;\n\t}\n\n\tif (isServiceEnabledInput(config.auth)) {\n\t\tconst auth = authEnvSchema.safeParse({\n\t\t\tNEON_AUTH_BASE_URL: source.NEON_AUTH_BASE_URL,\n\t\t\tNEON_AUTH_JWKS_URL: source.NEON_AUTH_JWKS_URL,\n\t\t});\n\t\tif (auth.success) {\n\t\t\tresult.auth = {\n\t\t\t\tbaseUrl: auth.data.NEON_AUTH_BASE_URL,\n\t\t\t\tjwksUrl: auth.data.NEON_AUTH_JWKS_URL,\n\t\t\t} satisfies NeonAuthEnv;\n\t\t} else {\n\t\t\tfor (const issue of auth.error.issues) issues.push(issue.message);\n\t\t}\n\t}\n\n\tif (isServiceEnabledInput(config.dataApi)) {\n\t\tconst dataApi = dataApiEnvSchema.safeParse({\n\t\t\tNEON_DATA_API_URL: source.NEON_DATA_API_URL,\n\t\t});\n\t\tif (dataApi.success) {\n\t\t\tresult.dataApi = {\n\t\t\t\turl: dataApi.data.NEON_DATA_API_URL,\n\t\t\t} satisfies NeonDataApiEnv;\n\t\t} else {\n\t\t\tfor (const issue of dataApi.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (configWantsStorage(config)) {\n\t\tconst storage = storageEnvSchema.safeParse({\n\t\t\tAWS_ACCESS_KEY_ID: source.AWS_ACCESS_KEY_ID,\n\t\t\tAWS_SECRET_ACCESS_KEY: source.AWS_SECRET_ACCESS_KEY,\n\t\t\tAWS_ENDPOINT_URL_S3: source.AWS_ENDPOINT_URL_S3,\n\t\t\tAWS_REGION: source.AWS_REGION,\n\t\t});\n\t\tif (storage.success) {\n\t\t\tresult.storage = {\n\t\t\t\taccessKeyId: storage.data.AWS_ACCESS_KEY_ID,\n\t\t\t\tsecretAccessKey: storage.data.AWS_SECRET_ACCESS_KEY,\n\t\t\t\tendpoint: storage.data.AWS_ENDPOINT_URL_S3,\n\t\t\t\tregion: storage.data.AWS_REGION,\n\t\t\t} satisfies NeonStorageEnv;\n\t\t} else {\n\t\t\tfor (const issue of storage.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (configWantsAiGateway(config)) {\n\t\tconst aiGateway = aiGatewayEnvSchema.safeParse({\n\t\t\tNEON_AI_GATEWAY_TOKEN: source.NEON_AI_GATEWAY_TOKEN,\n\t\t\tNEON_AI_GATEWAY_BASE_URL: source.NEON_AI_GATEWAY_BASE_URL,\n\t\t});\n\t\tif (aiGateway.success) {\n\t\t\tresult.aiGateway = {\n\t\t\t\tapiKey: aiGateway.data.NEON_AI_GATEWAY_TOKEN,\n\t\t\t\tbaseUrl: aiGateway.data.NEON_AI_GATEWAY_BASE_URL,\n\t\t\t} satisfies NeonAiGatewayEnv;\n\t\t} else {\n\t\t\tfor (const issue of aiGateway.error.issues)\n\t\t\t\tissues.push(issue.message);\n\t\t}\n\t}\n\n\tif (scope !== undefined) {\n\t\tconst fn = config.preview?.functions?.[scope];\n\t\tif (!fn) {\n\t\t\tthrow new PlatformError(\n\t\t\t\tErrorCode.EnvNotInjected,\n\t\t\t\t[\n\t\t\t\t\t`parseEnv: no function \"${scope}\" is declared in this policy's preview.functions.`,\n\t\t\t\t\t\"Pass a declared function slug (or omit the scope to read external env).\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\t{ details: { scope } },\n\t\t\t);\n\t\t}\n\t\tconst envOut: Record<string, string> = {};\n\t\tfor (const key of Object.keys(fn.env ?? {})) {\n\t\t\tconst value = source[key];\n\t\t\t// Only a truly *unset* var is \"not injected\". Function env values carry no\n\t\t\t// non-empty constraint (unlike DATABASE_URL / NEON_AUTH_BASE_URL), so a\n\t\t\t// deliberately empty value is a present, valid value and is passed through.\n\t\t\tif (value === undefined) {\n\t\t\t\tissues.push(`${key} is missing (function \"${scope}\")`);\n\t\t\t} else {\n\t\t\t\tenvOut[key] = value;\n\t\t\t}\n\t\t}\n\t\tresult.function = envOut;\n\t}\n\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\" - for the `function` namespace: deploy the function (`neon deploy` / `config apply`) so its env is uploaded.\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\n\treturn result;\n}\n\n/**\n * Runtime reverse map for filtered `parseEnv`: OS-level env-var key → `[namespace, property]`\n * in the {@link NeonEnv} shape. The compile-time mirror is {@link EnvKeysByNamespace} /\n * {@link EnvKeyToProp}; keep all three in sync. Only input vars appear (no output-only\n * aliases).\n */\nconst FILTERABLE_ENV_KEYS: Record<string, readonly [string, string]> = {\n\tDATABASE_URL: [\"postgres\", \"databaseUrl\"],\n\tDATABASE_URL_UNPOOLED: [\"postgres\", \"databaseUrlUnpooled\"],\n\tNEON_BRANCH: [\"branch\", \"name\"],\n\tNEON_AUTH_BASE_URL: [\"auth\", \"baseUrl\"],\n\tNEON_AUTH_JWKS_URL: [\"auth\", \"jwksUrl\"],\n\tNEON_DATA_API_URL: [\"dataApi\", \"url\"],\n\tAWS_ACCESS_KEY_ID: [\"storage\", \"accessKeyId\"],\n\tAWS_SECRET_ACCESS_KEY: [\"storage\", \"secretAccessKey\"],\n\tAWS_ENDPOINT_URL_S3: [\"storage\", \"endpoint\"],\n\tAWS_REGION: [\"storage\", \"region\"],\n\tNEON_AI_GATEWAY_TOKEN: [\"aiGateway\", \"apiKey\"],\n\tNEON_AI_GATEWAY_BASE_URL: [\"aiGateway\", \"baseUrl\"],\n};\n\n/**\n * Filtered counterpart to the {@link parseEnv} body: validate and return only the explicitly\n * selected OS-level env-var keys, projected back into the narrowed namespaced shape. Unlike\n * the full reader it never consults the policy — the selection alone decides what's required —\n * so vars the caller didn't ask for (e.g. `DATABASE_URL_UNPOOLED`) can be absent without\n * throwing. Mirrors the same non-empty constraint and {@link PlatformError} aggregation.\n */\nfunction parseFilteredEnv(\n\tsource: NodeJS.ProcessEnv,\n\tkeys: readonly string[],\n): Record<string, Record<string, string>> {\n\tconst issues: string[] = [];\n\tconst result: Record<string, Record<string, string>> = {};\n\tfor (const key of keys) {\n\t\t// Unknown keys are blocked at the type level; a runtime caller bypassing the types\n\t\t// gets a clear error rather than a silently-dropped selection.\n\t\tif (!Object.hasOwn(FILTERABLE_ENV_KEYS, key)) {\n\t\t\tissues.push(`${key} is not a selectable Neon env variable`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst value = source[key];\n\t\tif (value === undefined) {\n\t\t\tissues.push(`${key} is missing`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (value === \"\") {\n\t\t\tissues.push(`${key} must not be empty`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst [namespace, property] = FILTERABLE_ENV_KEYS[key];\n\t\tconst bucket = result[namespace] ?? {};\n\t\tbucket[property] = value;\n\t\tresult[namespace] = bucket;\n\t}\n\tif (issues.length > 0) {\n\t\tthrow new PlatformError(\n\t\t\tErrorCode.EnvNotInjected,\n\t\t\t[\n\t\t\t\t\"parseEnv: the required Neon env variables are not present in process.env.\",\n\t\t\t\t...issues.map((i) => ` - ${i}`),\n\t\t\t\t\"Inject them via one of:\",\n\t\t\t\t\" - `neon dev` / `neon-env run -- <your dev command>` (wraps the command with the vars injected)\",\n\t\t\t\t\" - your hosting platform's Neon integration (Vercel, Fly, Railway, …)\",\n\t\t\t\t\"Or switch the call to `await fetchEnv(config, …)` if you're in a context that can do async I/O.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ details: { missing: issues } },\n\t\t);\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neondatabase/env",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Resolve and inject Neon connection strings for the branch selected by your neon.ts policy. fetchEnv / parseEnv plus a `neon-env` CLI with `run` and `export`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"tsdown": "^0.14.1",
|
|
46
46
|
"typescript": "^5.9.0",
|
|
47
47
|
"vitest": "^3.0.9",
|
|
48
|
+
"@neon-internals/cli-core": "0.0.0",
|
|
49
|
+
"@neon-internals/env-core": "0.0.0",
|
|
48
50
|
"@neon/e2e-harness": "0.0.0",
|
|
49
51
|
"@neon/sdk": "2.0.0"
|
|
50
52
|
},
|
|
@@ -60,11 +62,11 @@
|
|
|
60
62
|
"provenance": false
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|
|
63
|
-
"build": "
|
|
64
|
-
"test": "
|
|
65
|
-
"test:ci": "
|
|
66
|
-
"test:types": "
|
|
67
|
-
"test:e2e": "
|
|
65
|
+
"build": "tsc --noEmit && tsdown",
|
|
66
|
+
"test": "pnpm --filter @neon/env... build && vitest --passWithNoTests",
|
|
67
|
+
"test:ci": "vitest run --passWithNoTests",
|
|
68
|
+
"test:types": "vitest run --typecheck.enabled --typecheck.only",
|
|
69
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
68
70
|
"tsc": "tsc"
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
//#region src/_shared/auth_selection.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* # Which credential an invocation authenticates with
|
|
4
|
-
*
|
|
5
|
-
* Four inputs can each answer "who am I": the `--api-key` flag, `NEON_API_KEY`, the
|
|
6
|
-
* `--profile` flag, and `NEON_PROFILE`. This module decides between them, and it is pure so
|
|
7
|
-
* the decision can be tested without a filesystem, a network, or a config directory.
|
|
8
|
-
*
|
|
9
|
-
* ## The rule
|
|
10
|
-
*
|
|
11
|
-
* **An explicit flag beats an ambient environment variable.** That single rule fixes the bug
|
|
12
|
-
* this module exists for: before it, any API key — including one merely exported into the
|
|
13
|
-
* shell — silently voided `--profile`, so `neon --profile work …` would quietly run as
|
|
14
|
-
* whoever `NEON_API_KEY` belonged to and say nothing about it.
|
|
15
|
-
*
|
|
16
|
-
* | Given | What runs |
|
|
17
|
-
* | --- | --- |
|
|
18
|
-
* | `--api-key` and `--profile` | neither: contradictory explicit flags, so this throws |
|
|
19
|
-
* | `--api-key` and `NEON_PROFILE` | the flag's key |
|
|
20
|
-
* | `--profile` and `NEON_API_KEY` | the profile |
|
|
21
|
-
* | `NEON_API_KEY` and `NEON_PROFILE` | the key, and the ignored profile is named in a warning |
|
|
22
|
-
* | `--profile` or `NEON_PROFILE` alone | that profile |
|
|
23
|
-
* | nothing | `DEFAULT` |
|
|
24
|
-
*
|
|
25
|
-
* Two explicit flags throw rather than picking a winner. They express different intents —
|
|
26
|
-
* `--api-key` supplies a credential, `--profile` selects a stored one — so there is no
|
|
27
|
-
* reading of the command that makes both true, and guessing is how the original bug behaved.
|
|
28
|
-
*
|
|
29
|
-
* When both are merely ambient, the key wins. That keeps CI exactly as it was: a pipeline
|
|
30
|
-
* that injects `NEON_API_KEY` must not change behaviour because a `NEON_PROFILE` leaked into
|
|
31
|
-
* the environment. It warns instead of staying silent, because a disregarded account
|
|
32
|
-
* selection is precisely what nobody noticed last time.
|
|
33
|
-
*
|
|
34
|
-
* `auth` and the `profile` subcommands do not use any of this. They read the same flags with
|
|
35
|
-
* different meanings — `neon auth --profile work` names where to *write* a credential, and
|
|
36
|
-
* `neon profile create work --api-key …` names one to *store* — so their callers skip
|
|
37
|
-
* selection entirely rather than passing exemptions down here.
|
|
38
|
-
*/
|
|
39
|
-
type CredentialSelection = /** `--api-key`. Used as given; no profile is consulted and no stored file is touched. */
|
|
40
|
-
{
|
|
41
|
-
source: "explicit-api-key";
|
|
42
|
-
apiKey: string;
|
|
43
|
-
}
|
|
44
|
-
/** `NEON_API_KEY`, with the profile it displaced when there was one. */ | {
|
|
45
|
-
source: "ambient-api-key";
|
|
46
|
-
apiKey: string;
|
|
47
|
-
ignoredProfile?: string;
|
|
48
|
-
}
|
|
49
|
-
/** A profile, whose file decides whether that means an API key or OAuth. */ | {
|
|
50
|
-
source: "profile";
|
|
51
|
-
profile: string;
|
|
52
|
-
explicit: boolean;
|
|
53
|
-
};
|
|
54
|
-
type SelectionInput = {
|
|
55
|
-
/** The `--api-key` flag, before any environment fallback has been folded into it. */
|
|
56
|
-
apiKeyFlag?: string;
|
|
57
|
-
/** The `--profile` flag. */
|
|
58
|
-
profileFlag?: string;
|
|
59
|
-
/** `NEON_API_KEY`. */
|
|
60
|
-
apiKeyEnv?: string;
|
|
61
|
-
/** `NEON_PROFILE`. */
|
|
62
|
-
profileEnv?: string;
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* What the four credential inputs were for this invocation, captured by
|
|
66
|
-
* `resolveApiKeyFromEnv` — which is the one place that reads the environment.
|
|
67
|
-
*
|
|
68
|
-
* Two reasons this is module state rather than fields on the parsed arguments, the same two
|
|
69
|
-
* that put `auth_context` here: an extra key on `args` is rejected by every command calling
|
|
70
|
-
* `.strict()`, and a hidden option to carry it would be a second undocumented way to pass a
|
|
71
|
-
* credential. One process is one invocation, so there is nothing to get out of step.
|
|
72
|
-
*
|
|
73
|
-
* Capturing the environment here rather than reading it inside {@link selectCredential} keeps
|
|
74
|
-
* the selection a function of its arguments. That is not tidiness: `ensureAuth` is called
|
|
75
|
-
* directly by tests, and reading `process.env` down in the decision made those tests depend on
|
|
76
|
-
* whether the developer running them happened to have `NEON_API_KEY` exported.
|
|
77
|
-
*/
|
|
78
|
-
type CredentialInputs = {
|
|
79
|
-
apiKeyFlag: string;
|
|
80
|
-
apiKeyEnv: string;
|
|
81
|
-
profileEnv: string;
|
|
82
|
-
};
|
|
83
|
-
declare const recordCredentialInputs: (recorded: CredentialInputs) => void;
|
|
84
|
-
declare const credentialInputs: () => CredentialInputs;
|
|
85
|
-
declare const selectCredential: ({
|
|
86
|
-
apiKeyFlag,
|
|
87
|
-
profileFlag,
|
|
88
|
-
apiKeyEnv,
|
|
89
|
-
profileEnv
|
|
90
|
-
}: SelectionInput) => CredentialSelection;
|
|
91
|
-
/** The warning for an ambient key that displaced an ambient profile, or `null`. */
|
|
92
|
-
declare const displacedProfileWarning: (selection: CredentialSelection) => string | null;
|
|
93
|
-
//#endregion
|
|
94
|
-
export { CredentialInputs, CredentialSelection, SelectionInput, credentialInputs, displacedProfileWarning, recordCredentialInputs, selectCredential };
|
|
95
|
-
//# sourceMappingURL=auth_selection.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth_selection.d.ts","names":[],"sources":["../../src/_shared/auth_selection.ts"],"mappings":";;AAwCA;AAQA;AAyBA;AAcA;AAIA;AAEA;AAuCC;AAvCgC;AAAA;AAAA;AAAA;AAK9B;AAAiB;AAkCnB;AAGD;;;;;;;;;;;;;;;;;;;;;;KA/FY,mBAAA;;;;;;;;;;;;;;;KAQA,cAAA;;;;;;;;;;;;;;;;;;;;;;;;KAyBA,gBAAA;;;;;cAcC,mCAAoC;cAIpC,wBAAuB;cAEvB;;;;;GAKV,mBAAiB;;cAqCP,qCACD"}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import "./profiles.js";
|
|
2
|
-
//#region src/_shared/auth_selection.ts
|
|
3
|
-
/**
|
|
4
|
-
* # Which credential an invocation authenticates with
|
|
5
|
-
*
|
|
6
|
-
* Four inputs can each answer "who am I": the `--api-key` flag, `NEON_API_KEY`, the
|
|
7
|
-
* `--profile` flag, and `NEON_PROFILE`. This module decides between them, and it is pure so
|
|
8
|
-
* the decision can be tested without a filesystem, a network, or a config directory.
|
|
9
|
-
*
|
|
10
|
-
* ## The rule
|
|
11
|
-
*
|
|
12
|
-
* **An explicit flag beats an ambient environment variable.** That single rule fixes the bug
|
|
13
|
-
* this module exists for: before it, any API key — including one merely exported into the
|
|
14
|
-
* shell — silently voided `--profile`, so `neon --profile work …` would quietly run as
|
|
15
|
-
* whoever `NEON_API_KEY` belonged to and say nothing about it.
|
|
16
|
-
*
|
|
17
|
-
* | Given | What runs |
|
|
18
|
-
* | --- | --- |
|
|
19
|
-
* | `--api-key` and `--profile` | neither: contradictory explicit flags, so this throws |
|
|
20
|
-
* | `--api-key` and `NEON_PROFILE` | the flag's key |
|
|
21
|
-
* | `--profile` and `NEON_API_KEY` | the profile |
|
|
22
|
-
* | `NEON_API_KEY` and `NEON_PROFILE` | the key, and the ignored profile is named in a warning |
|
|
23
|
-
* | `--profile` or `NEON_PROFILE` alone | that profile |
|
|
24
|
-
* | nothing | `DEFAULT` |
|
|
25
|
-
*
|
|
26
|
-
* Two explicit flags throw rather than picking a winner. They express different intents —
|
|
27
|
-
* `--api-key` supplies a credential, `--profile` selects a stored one — so there is no
|
|
28
|
-
* reading of the command that makes both true, and guessing is how the original bug behaved.
|
|
29
|
-
*
|
|
30
|
-
* When both are merely ambient, the key wins. That keeps CI exactly as it was: a pipeline
|
|
31
|
-
* that injects `NEON_API_KEY` must not change behaviour because a `NEON_PROFILE` leaked into
|
|
32
|
-
* the environment. It warns instead of staying silent, because a disregarded account
|
|
33
|
-
* selection is precisely what nobody noticed last time.
|
|
34
|
-
*
|
|
35
|
-
* `auth` and the `profile` subcommands do not use any of this. They read the same flags with
|
|
36
|
-
* different meanings — `neon auth --profile work` names where to *write* a credential, and
|
|
37
|
-
* `neon profile create work --api-key …` names one to *store* — so their callers skip
|
|
38
|
-
* selection entirely rather than passing exemptions down here.
|
|
39
|
-
*/
|
|
40
|
-
let inputs = {
|
|
41
|
-
apiKeyFlag: "",
|
|
42
|
-
apiKeyEnv: "",
|
|
43
|
-
profileEnv: ""
|
|
44
|
-
};
|
|
45
|
-
const recordCredentialInputs = (recorded) => {
|
|
46
|
-
inputs = recorded;
|
|
47
|
-
};
|
|
48
|
-
const credentialInputs = () => inputs;
|
|
49
|
-
const selectCredential = ({ apiKeyFlag, profileFlag, apiKeyEnv, profileEnv }) => {
|
|
50
|
-
const flagKey = nonEmpty(apiKeyFlag);
|
|
51
|
-
const flagProfile = nonEmpty(profileFlag);
|
|
52
|
-
if (flagKey !== void 0 && flagProfile !== void 0) throw new Error("Pass either --api-key or --profile, not both. --api-key supplies a credential directly; --profile selects a stored one.");
|
|
53
|
-
if (flagKey !== void 0) return {
|
|
54
|
-
source: "explicit-api-key",
|
|
55
|
-
apiKey: flagKey
|
|
56
|
-
};
|
|
57
|
-
if (flagProfile !== void 0) return {
|
|
58
|
-
source: "profile",
|
|
59
|
-
profile: flagProfile,
|
|
60
|
-
explicit: true
|
|
61
|
-
};
|
|
62
|
-
const envKey = nonEmpty(apiKeyEnv);
|
|
63
|
-
const envProfile = nonEmpty(profileEnv);
|
|
64
|
-
if (envKey !== void 0) return {
|
|
65
|
-
source: "ambient-api-key",
|
|
66
|
-
apiKey: envKey,
|
|
67
|
-
...envProfile !== void 0 ? { ignoredProfile: envProfile } : {}
|
|
68
|
-
};
|
|
69
|
-
return {
|
|
70
|
-
source: "profile",
|
|
71
|
-
profile: envProfile ?? "DEFAULT",
|
|
72
|
-
explicit: envProfile !== void 0
|
|
73
|
-
};
|
|
74
|
-
};
|
|
75
|
-
/** The warning for an ambient key that displaced an ambient profile, or `null`. */
|
|
76
|
-
const displacedProfileWarning = (selection) => selection.source === "ambient-api-key" && selection.ignoredProfile !== void 0 ? `NEON_API_KEY is set, so profile "${selection.ignoredProfile}" from NEON_PROFILE was ignored. Pass --profile ${selection.ignoredProfile} to use it instead.` : null;
|
|
77
|
-
function nonEmpty(value) {
|
|
78
|
-
if (typeof value !== "string") return void 0;
|
|
79
|
-
const trimmed = value.trim();
|
|
80
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
|
-
export { credentialInputs, displacedProfileWarning, recordCredentialInputs, selectCredential };
|
|
84
|
-
|
|
85
|
-
//# sourceMappingURL=auth_selection.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth_selection.js","names":[],"sources":["../../src/_shared/auth_selection.ts"],"sourcesContent":["/**\n * # Which credential an invocation authenticates with\n *\n * Four inputs can each answer \"who am I\": the `--api-key` flag, `NEON_API_KEY`, the\n * `--profile` flag, and `NEON_PROFILE`. This module decides between them, and it is pure so\n * the decision can be tested without a filesystem, a network, or a config directory.\n *\n * ## The rule\n *\n * **An explicit flag beats an ambient environment variable.** That single rule fixes the bug\n * this module exists for: before it, any API key — including one merely exported into the\n * shell — silently voided `--profile`, so `neon --profile work …` would quietly run as\n * whoever `NEON_API_KEY` belonged to and say nothing about it.\n *\n * | Given | What runs |\n * | --- | --- |\n * | `--api-key` and `--profile` | neither: contradictory explicit flags, so this throws |\n * | `--api-key` and `NEON_PROFILE` | the flag's key |\n * | `--profile` and `NEON_API_KEY` | the profile |\n * | `NEON_API_KEY` and `NEON_PROFILE` | the key, and the ignored profile is named in a warning |\n * | `--profile` or `NEON_PROFILE` alone | that profile |\n * | nothing | `DEFAULT` |\n *\n * Two explicit flags throw rather than picking a winner. They express different intents —\n * `--api-key` supplies a credential, `--profile` selects a stored one — so there is no\n * reading of the command that makes both true, and guessing is how the original bug behaved.\n *\n * When both are merely ambient, the key wins. That keeps CI exactly as it was: a pipeline\n * that injects `NEON_API_KEY` must not change behaviour because a `NEON_PROFILE` leaked into\n * the environment. It warns instead of staying silent, because a disregarded account\n * selection is precisely what nobody noticed last time.\n *\n * `auth` and the `profile` subcommands do not use any of this. They read the same flags with\n * different meanings — `neon auth --profile work` names where to *write* a credential, and\n * `neon profile create work --api-key …` names one to *store* — so their callers skip\n * selection entirely rather than passing exemptions down here.\n */\n\nimport { DEFAULT_PROFILE } from \"./profiles.js\";\n\nexport type CredentialSelection =\n\t/** `--api-key`. Used as given; no profile is consulted and no stored file is touched. */\n\t| { source: \"explicit-api-key\"; apiKey: string }\n\t/** `NEON_API_KEY`, with the profile it displaced when there was one. */\n\t| { source: \"ambient-api-key\"; apiKey: string; ignoredProfile?: string }\n\t/** A profile, whose file decides whether that means an API key or OAuth. */\n\t| { source: \"profile\"; profile: string; explicit: boolean };\n\nexport type SelectionInput = {\n\t/** The `--api-key` flag, before any environment fallback has been folded into it. */\n\tapiKeyFlag?: string;\n\t/** The `--profile` flag. */\n\tprofileFlag?: string;\n\t/** `NEON_API_KEY`. */\n\tapiKeyEnv?: string;\n\t/** `NEON_PROFILE`. */\n\tprofileEnv?: string;\n};\n\n/**\n * What the four credential inputs were for this invocation, captured by\n * `resolveApiKeyFromEnv` — which is the one place that reads the environment.\n *\n * Two reasons this is module state rather than fields on the parsed arguments, the same two\n * that put `auth_context` here: an extra key on `args` is rejected by every command calling\n * `.strict()`, and a hidden option to carry it would be a second undocumented way to pass a\n * credential. One process is one invocation, so there is nothing to get out of step.\n *\n * Capturing the environment here rather than reading it inside {@link selectCredential} keeps\n * the selection a function of its arguments. That is not tidiness: `ensureAuth` is called\n * directly by tests, and reading `process.env` down in the decision made those tests depend on\n * whether the developer running them happened to have `NEON_API_KEY` exported.\n */\nexport type CredentialInputs = {\n\tapiKeyFlag: string;\n\tapiKeyEnv: string;\n\tprofileEnv: string;\n};\n\nconst NO_INPUTS: CredentialInputs = {\n\tapiKeyFlag: \"\",\n\tapiKeyEnv: \"\",\n\tprofileEnv: \"\",\n};\n\nlet inputs: CredentialInputs = NO_INPUTS;\n\nexport const recordCredentialInputs = (recorded: CredentialInputs): void => {\n\tinputs = recorded;\n};\n\nexport const credentialInputs = (): CredentialInputs => inputs;\n\nexport const selectCredential = ({\n\tapiKeyFlag,\n\tprofileFlag,\n\tapiKeyEnv,\n\tprofileEnv,\n}: SelectionInput): CredentialSelection => {\n\tconst flagKey = nonEmpty(apiKeyFlag);\n\tconst flagProfile = nonEmpty(profileFlag);\n\n\tif (flagKey !== undefined && flagProfile !== undefined) {\n\t\tthrow new Error(\n\t\t\t\"Pass either --api-key or --profile, not both. --api-key supplies a credential directly; --profile selects a stored one.\",\n\t\t);\n\t}\n\n\tif (flagKey !== undefined) {\n\t\treturn { source: \"explicit-api-key\", apiKey: flagKey };\n\t}\n\n\tif (flagProfile !== undefined) {\n\t\treturn { source: \"profile\", profile: flagProfile, explicit: true };\n\t}\n\n\tconst envKey = nonEmpty(apiKeyEnv);\n\tconst envProfile = nonEmpty(profileEnv);\n\n\tif (envKey !== undefined) {\n\t\treturn {\n\t\t\tsource: \"ambient-api-key\",\n\t\t\tapiKey: envKey,\n\t\t\t...(envProfile !== undefined ? { ignoredProfile: envProfile } : {}),\n\t\t};\n\t}\n\n\treturn {\n\t\tsource: \"profile\",\n\t\tprofile: envProfile ?? DEFAULT_PROFILE,\n\t\texplicit: envProfile !== undefined,\n\t};\n};\n\n/** The warning for an ambient key that displaced an ambient profile, or `null`. */\nexport const displacedProfileWarning = (\n\tselection: CredentialSelection,\n): string | null =>\n\tselection.source === \"ambient-api-key\" &&\n\tselection.ignoredProfile !== undefined\n\t\t? `NEON_API_KEY is set, so profile \"${selection.ignoredProfile}\" from NEON_PROFILE was ignored. Pass --profile ${selection.ignoredProfile} to use it instead.`\n\t\t: null;\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed === \"\" ? undefined : trimmed;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,IAAI,SAA2B;CAL9B,YAAY;CACZ,WAAW;CACX,YAAY;AAG0B;AAEvC,MAAa,0BAA0B,aAAqC;CAC3E,SAAS;AACV;AAEA,MAAa,yBAA2C;AAExD,MAAa,oBAAoB,EAChC,YACA,aACA,WACA,iBAC0C;CAC1C,MAAM,UAAU,SAAS,UAAU;CACnC,MAAM,cAAc,SAAS,WAAW;CAExC,IAAI,YAAY,KAAA,KAAa,gBAAgB,KAAA,GAC5C,MAAM,IAAI,MACT,yHACD;CAGD,IAAI,YAAY,KAAA,GACf,OAAO;EAAE,QAAQ;EAAoB,QAAQ;CAAQ;CAGtD,IAAI,gBAAgB,KAAA,GACnB,OAAO;EAAE,QAAQ;EAAW,SAAS;EAAa,UAAU;CAAK;CAGlE,MAAM,SAAS,SAAS,SAAS;CACjC,MAAM,aAAa,SAAS,UAAU;CAEtC,IAAI,WAAW,KAAA,GACd,OAAO;EACN,QAAQ;EACR,QAAQ;EACR,GAAI,eAAe,KAAA,IAAY,EAAE,gBAAgB,WAAW,IAAI,CAAC;CAClE;CAGD,OAAO;EACN,QAAQ;EACR,SAAS,cAAA;EACT,UAAU,eAAe,KAAA;CAC1B;AACD;;AAGA,MAAa,2BACZ,cAEA,UAAU,WAAW,qBACrB,UAAU,mBAAmB,KAAA,IAC1B,oCAAoC,UAAU,eAAe,kDAAkD,UAAU,eAAe,uBACxI;AAEJ,SAAS,SAAS,OAA+C;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,YAAY,KAAK,KAAA,IAAY;AACrC"}
|