@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
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
//#region src/lib/cli/resolve-context.ts
|
|
5
|
-
/**
|
|
6
|
-
* Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the
|
|
7
|
-
* next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.
|
|
8
|
-
*
|
|
9
|
-
* Returns the resolved values plus a list of human-readable reasons for any field that
|
|
10
|
-
* could not be resolved (so the caller can render one combined error).
|
|
11
|
-
*/
|
|
12
|
-
function resolveContext(options) {
|
|
13
|
-
const env = options.env ?? process.env;
|
|
14
|
-
const file = findNeonFile(options.cwd);
|
|
15
|
-
const projectId = nonEmpty(options.projectId) ?? nonEmpty(env.NEON_PROJECT_ID) ?? file?.projectId;
|
|
16
|
-
const branch = nonEmpty(options.branch) ?? nonEmpty(env.NEON_BRANCH) ?? nonEmpty(env.NEON_BRANCH_ID) ?? file?.branch;
|
|
17
|
-
const missing = [];
|
|
18
|
-
if (!projectId) missing.push("project id — pass `--project-id`, set `NEON_PROJECT_ID`, or add `projectId` to `.neon` (run `npx neon link`).");
|
|
19
|
-
if (!branch) missing.push("branch — pass `--branch`, set `NEON_BRANCH`/`NEON_BRANCH_ID`, or add `branch` to `.neon` (run `npx neon link` / `neon checkout <branch>`).");
|
|
20
|
-
if (!projectId || !branch) return {
|
|
21
|
-
ok: false,
|
|
22
|
-
missing
|
|
23
|
-
};
|
|
24
|
-
return {
|
|
25
|
-
ok: true,
|
|
26
|
-
context: {
|
|
27
|
-
projectId,
|
|
28
|
-
branch
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Walk up from `cwd` looking for `.neon/project.json` (preferred) or `.neon` (neonctl
|
|
34
|
-
* convention). Stops at the first `.git` directory or the home directory. Read-only.
|
|
35
|
-
*/
|
|
36
|
-
function findNeonFile(cwd) {
|
|
37
|
-
let current = resolve(cwd);
|
|
38
|
-
const stop = resolve(homedir());
|
|
39
|
-
let lastSeen = null;
|
|
40
|
-
while (true) {
|
|
41
|
-
const parsed = readNeonFileAt(resolve(current, ".neon", "project.json")) ?? readNeonFileAt(resolve(current, ".neon"));
|
|
42
|
-
if (parsed) return parsed;
|
|
43
|
-
if (current === stop) return null;
|
|
44
|
-
if (existsSync(resolve(current, ".git"))) return null;
|
|
45
|
-
const parent = dirname(current);
|
|
46
|
-
if (parent === current || parent === lastSeen) return null;
|
|
47
|
-
lastSeen = current;
|
|
48
|
-
current = parent;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function readNeonFileAt(path) {
|
|
52
|
-
if (!isFile(path)) return null;
|
|
53
|
-
let raw;
|
|
54
|
-
try {
|
|
55
|
-
raw = readFileSync(path, "utf-8");
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
let parsed;
|
|
60
|
-
try {
|
|
61
|
-
parsed = JSON.parse(raw);
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
66
|
-
const obj = parsed;
|
|
67
|
-
const out = {};
|
|
68
|
-
if (typeof obj.projectId === "string" && obj.projectId !== "") out.projectId = obj.projectId;
|
|
69
|
-
const branch = typeof obj.branch === "string" && obj.branch !== "" ? obj.branch : typeof obj.branchId === "string" && obj.branchId !== "" ? obj.branchId : void 0;
|
|
70
|
-
if (branch) out.branch = branch;
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
function isFile(path) {
|
|
74
|
-
try {
|
|
75
|
-
return statSync(path).isFile();
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function nonEmpty(value) {
|
|
81
|
-
if (typeof value !== "string") return void 0;
|
|
82
|
-
const trimmed = value.trim();
|
|
83
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { resolveContext };
|
|
87
|
-
|
|
88
|
-
//# sourceMappingURL=resolve-context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-context.js","names":[],"sources":["../../../src/lib/cli/resolve-context.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, resolve } from \"node:path\";\n\n/**\n * Resolved project + branch context for the `neon-env` CLI. The CLI owns this resolution\n * (flags → `NEON_*` env → `.neon[/project.json]` file) so the `@neon/env` library\n * functions can stay filesystem- and env-agnostic.\n */\nexport interface ResolvedContext {\n\tprojectId: string;\n\t/** Branch ref — a name (preferred for readability) or an id (`br-…`). */\n\tbranch: string;\n}\n\nexport interface ResolveContextOptions {\n\tprojectId?: string;\n\tbranch?: string;\n\tcwd: string;\n\tenv?: NodeJS.ProcessEnv;\n}\n\n/**\n * Resolve `projectId` and `branch` for a CLI invocation. Precedence (each wins over the\n * next): explicit flag → `NEON_*` env var → `.neon[/project.json]` walked up from `cwd`.\n *\n * Returns the resolved values plus a list of human-readable reasons for any field that\n * could not be resolved (so the caller can render one combined error).\n */\nexport function resolveContext(\n\toptions: ResolveContextOptions,\n): { ok: true; context: ResolvedContext } | { ok: false; missing: string[] } {\n\tconst env = options.env ?? process.env;\n\tconst file = findNeonFile(options.cwd);\n\n\tconst projectId =\n\t\tnonEmpty(options.projectId) ??\n\t\tnonEmpty(env.NEON_PROJECT_ID) ??\n\t\tfile?.projectId;\n\n\t// A branch ref — name (preferred) or id. `NEON_BRANCH` carries the name; `NEON_BRANCH_ID`\n\t// is the legacy id-only var. The `.neon` file pins `branch` (name) via `neonctl link`,\n\t// with legacy `branchId` still honored. fetchEnv resolves either form by name or id.\n\tconst branch =\n\t\tnonEmpty(options.branch) ??\n\t\tnonEmpty(env.NEON_BRANCH) ??\n\t\tnonEmpty(env.NEON_BRANCH_ID) ??\n\t\tfile?.branch;\n\n\tconst missing: string[] = [];\n\tif (!projectId) {\n\t\tmissing.push(\n\t\t\t\"project id — pass `--project-id`, set `NEON_PROJECT_ID`, or add `projectId` to `.neon` (run `npx neon link`).\",\n\t\t);\n\t}\n\tif (!branch) {\n\t\tmissing.push(\n\t\t\t\"branch — pass `--branch`, set `NEON_BRANCH`/`NEON_BRANCH_ID`, or add `branch` to `.neon` (run `npx neon link` / `neon checkout <branch>`).\",\n\t\t);\n\t}\n\tif (!projectId || !branch) return { ok: false, missing };\n\n\treturn {\n\t\tok: true,\n\t\tcontext: { projectId, branch },\n\t};\n}\n\ninterface NeonFile {\n\tprojectId?: string;\n\t/** Branch ref — name (preferred) or id. Reads `branch`, falling back to legacy `branchId`. */\n\tbranch?: string;\n}\n\n/**\n * Walk up from `cwd` looking for `.neon/project.json` (preferred) or `.neon` (neonctl\n * convention). Stops at the first `.git` directory or the home directory. Read-only.\n */\nfunction findNeonFile(cwd: string): NeonFile | null {\n\tlet current = resolve(cwd);\n\tconst stop = resolve(homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tconst parsed =\n\t\t\treadNeonFileAt(resolve(current, \".neon\", \"project.json\")) ??\n\t\t\treadNeonFileAt(resolve(current, \".neon\"));\n\t\tif (parsed) return parsed;\n\n\t\tif (current === stop) return null;\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nfunction readNeonFileAt(path: string): NeonFile | null {\n\tif (!isFile(path)) return null;\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\tif (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed))\n\t\treturn null;\n\tconst obj = parsed as Record<string, unknown>;\n\tconst out: NeonFile = {};\n\tif (typeof obj.projectId === \"string\" && obj.projectId !== \"\")\n\t\tout.projectId = obj.projectId;\n\t// Prefer the `branch` field (name or id, written by `neonctl link`); fall back to the\n\t// legacy id-only `branchId`.\n\tconst branch =\n\t\ttypeof obj.branch === \"string\" && obj.branch !== \"\"\n\t\t\t? obj.branch\n\t\t\t: typeof obj.branchId === \"string\" && obj.branchId !== \"\"\n\t\t\t\t? obj.branchId\n\t\t\t\t: undefined;\n\tif (branch) out.branch = branch;\n\treturn out;\n}\n\nfunction isFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\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":";;;;;;;;;;;AA6BA,SAAgB,eACf,SAC4E;CAC5E,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,OAAO,aAAa,QAAQ,GAAG;CAErC,MAAM,YACL,SAAS,QAAQ,SAAS,KAC1B,SAAS,IAAI,eAAe,KAC5B,MAAM;CAKP,MAAM,SACL,SAAS,QAAQ,MAAM,KACvB,SAAS,IAAI,WAAW,KACxB,SAAS,IAAI,cAAc,KAC3B,MAAM;CAEP,MAAM,UAAoB,CAAC;CAC3B,IAAI,CAAC,WACJ,QAAQ,KACP,+GACD;CAED,IAAI,CAAC,QACJ,QAAQ,KACP,4IACD;CAED,IAAI,CAAC,aAAa,CAAC,QAAQ,OAAO;EAAE,IAAI;EAAO;CAAQ;CAEvD,OAAO;EACN,IAAI;EACJ,SAAS;GAAE;GAAW;EAAO;CAC9B;AACD;;;;;AAYA,SAAS,aAAa,KAA8B;CACnD,IAAI,UAAU,QAAQ,GAAG;CACzB,MAAM,OAAO,QAAQ,QAAQ,CAAC;CAC9B,IAAI,WAA0B;CAE9B,OAAO,MAAM;EACZ,MAAM,SACL,eAAe,QAAQ,SAAS,SAAS,cAAc,CAAC,KACxD,eAAe,QAAQ,SAAS,OAAO,CAAC;EACzC,IAAI,QAAQ,OAAO;EAEnB,IAAI,YAAY,MAAM,OAAO;EAC7B,IAAI,WAAW,QAAQ,SAAS,MAAM,CAAC,GAAG,OAAO;EAEjD,MAAM,SAAS,QAAQ,OAAO;EAC9B,IAAI,WAAW,WAAW,WAAW,UAAU,OAAO;EACtD,WAAW;EACX,UAAU;CACX;AACD;AAEA,SAAS,eAAe,MAA+B;CACtD,IAAI,CAAC,OAAO,IAAI,GAAG,OAAO;CAC1B,IAAI;CACJ,IAAI;EACH,MAAM,aAAa,MAAM,OAAO;CACjC,QAAQ;EACP,OAAO;CACR;CACA,IAAI;CACJ,IAAI;EACH,SAAS,KAAK,MAAM,GAAG;CACxB,QAAQ;EACP,OAAO;CACR;CACA,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GACxE,OAAO;CACR,MAAM,MAAM;CACZ,MAAM,MAAgB,CAAC;CACvB,IAAI,OAAO,IAAI,cAAc,YAAY,IAAI,cAAc,IAC1D,IAAI,YAAY,IAAI;CAGrB,MAAM,SACL,OAAO,IAAI,WAAW,YAAY,IAAI,WAAW,KAC9C,IAAI,SACJ,OAAO,IAAI,aAAa,YAAY,IAAI,aAAa,KACpD,IAAI,WACJ,KAAA;CACL,IAAI,QAAQ,IAAI,SAAS;CACzB,OAAO;AACR;AAEA,SAAS,OAAO,MAAuB;CACtC,IAAI;EACH,OAAO,SAAS,IAAI,CAAC,CAAC,OAAO;CAC9B,QAAQ;EACP,OAAO;CACR;AACD;AAEA,SAAS,SAAS,OAA+C;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,YAAY,KAAK,KAAA,IAAY;AACrC"}
|
package/dist/lib/parse-env.d.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { Config } from "../config/dist/lib/types.js";
|
|
2
|
-
import "../config/dist/v1.js";
|
|
3
|
-
import { FilteredNeonEnv, NeonEnv, SelectableEnvKey } from "../_shared/env-core/env.js";
|
|
4
|
-
|
|
5
|
-
//#region src/lib/parse-env.d.ts
|
|
6
|
-
|
|
7
|
-
/** The static `preview.functions` record of a config, or an empty record when absent. */
|
|
8
|
-
type PreviewFunctionsOf<C extends Config> = NonNullable<C["preview"]> extends {
|
|
9
|
-
functions: infer F;
|
|
10
|
-
} ? F : Record<never, never>;
|
|
11
|
-
/** The declared function slugs of a config (record keys), as a string union. */
|
|
12
|
-
type FunctionSlugOf<C extends Config> = Extract<keyof PreviewFunctionsOf<C>, string>;
|
|
13
|
-
/**
|
|
14
|
-
* Human-readable hint surfaced as the **expected type** of `parseEnv`'s `scope` argument when
|
|
15
|
-
* the policy declares no functions at all. Without it the argument's expected type is the bare
|
|
16
|
-
* `never` {@link FunctionSlugOf} yields, and TypeScript reports the opaque `Type '"x"' is not
|
|
17
|
-
* assignable to type 'never'`; the literal turns that into a sentence naming the fix (and the
|
|
18
|
-
* editor offers it as the single completion, so the empty completion list is explained rather
|
|
19
|
-
* than just empty). Mirrors `NeonAuthRequiredHint` in `@neon/config`.
|
|
20
|
-
*/
|
|
21
|
-
type NoFunctionScopeHint = "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";
|
|
22
|
-
/**
|
|
23
|
-
* The expected type of `parseEnv`'s function-slug `scope` argument: the caller's inferred slug
|
|
24
|
-
* `S` normally, and the {@link NoFunctionScopeHint} message when the policy declares no
|
|
25
|
-
* functions. Keeping `S` (rather than `FunctionSlugOf<C>`) in the enabled branch is what makes
|
|
26
|
-
* the returned `function` namespace exact — it stays the one function's env keys instead of
|
|
27
|
-
* widening to every declared function's.
|
|
28
|
-
*/
|
|
29
|
-
type FunctionScopeField<C extends Config, S extends string> = [FunctionSlugOf<C>] extends [never] ? NoFunctionScopeHint : S;
|
|
30
|
-
/** The declared env-var keys of one function `S`, as a string union. */
|
|
31
|
-
type FunctionEnvKeysOf<C extends Config, S extends string> = S extends keyof PreviewFunctionsOf<C> ? NonNullable<PreviewFunctionsOf<C>[S]> extends {
|
|
32
|
-
env: infer E;
|
|
33
|
-
} ? Extract<keyof E, string> : never : never;
|
|
34
|
-
/**
|
|
35
|
-
* The extra `function` namespace added to `parseEnv`'s result when called with a function
|
|
36
|
-
* slug scope: the declared env-var keys for that function, each resolved to a `string`.
|
|
37
|
-
*/
|
|
38
|
-
type NeonFunctionEnv<C extends Config, S extends string> = {
|
|
39
|
-
function: Record<FunctionEnvKeysOf<C, S>, string>;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Synchronous, network-free counterpart to {@link fetchEnv}. Reads `process.env`, validates
|
|
43
|
-
* the required Neon env vars with zod, and returns the same {@link NeonEnv} shape — so the
|
|
44
|
-
* rest of your app touches `env.postgres.databaseUrl` instead of stringly-typed
|
|
45
|
-
* `process.env.DATABASE_URL` lookups.
|
|
46
|
-
*
|
|
47
|
-
* Designed for the **"env-vars-already-injected"** path:
|
|
48
|
-
* - You wrapped your dev command with `neon-env run -- <cmd>` or `neon dev`.
|
|
49
|
-
* - Your platform (Vercel, Fly, Railway, …) injected the vars via its own integration.
|
|
50
|
-
* - You are **inside a deployed Neon Function**, whose env was uploaded at `config apply`.
|
|
51
|
-
*
|
|
52
|
-
* Unlike the old API, `parseEnv` does **not** take a branch name: the secret set is now
|
|
53
|
-
* static (top-level `config.auth` / `config.dataApi`), so it reads those directly without
|
|
54
|
-
* evaluating the per-branch closure.
|
|
55
|
-
*
|
|
56
|
-
* The second argument is a **scope** or a **key filter**:
|
|
57
|
-
* - omitted — *external* scope (app bootstrap, build scripts, your dev machine). Returns the
|
|
58
|
-
* full `{ postgres, auth?, dataApi?, … }` the policy enables.
|
|
59
|
-
* - a **function slug** (a key of `config.preview.functions`) — *function* scope: you are
|
|
60
|
-
* running inside that function. Returns the same branch secrets **plus** a typed
|
|
61
|
-
* `function` namespace with the function's declared env-var keys. The slug autocompletes
|
|
62
|
-
* from the policy ({@link FunctionSlugOf}) and an undeclared one is a type error.
|
|
63
|
-
* - an **array of OS-level env-var keys** (e.g. `["DATABASE_URL", "NEON_AUTH_BASE_URL"]`) —
|
|
64
|
-
* *filtered* mode: only those vars are required and returned, as a narrowed namespaced
|
|
65
|
-
* shape. The keys autocomplete from the policy ({@link SelectableEnvKey}), so you can only
|
|
66
|
-
* pick vars the policy actually enables. Use this when a process needs just a subset (a
|
|
67
|
-
* Next.js app that reads `DATABASE_URL` but not `DATABASE_URL_UNPOOLED`, say) and you don't
|
|
68
|
-
* want `parseEnv` to throw over vars you never use.
|
|
69
|
-
*
|
|
70
|
-
* Throws `PlatformError(EnvNotInjected)` listing every missing/invalid var when the env
|
|
71
|
-
* isn't fully populated, with a fix hint pointing back at `neon dev` / `neon-env run`.
|
|
72
|
-
*
|
|
73
|
-
* ```ts
|
|
74
|
-
* import config from "../neon";
|
|
75
|
-
* import { parseEnv } from "@neon/env";
|
|
76
|
-
*
|
|
77
|
-
* // External (app / build):
|
|
78
|
-
* const env = parseEnv(config);
|
|
79
|
-
* const db = drizzle(neon(env.postgres.databaseUrl), { schema });
|
|
80
|
-
*
|
|
81
|
-
* // Inside the "hello" function:
|
|
82
|
-
* const env = parseEnv(config, "hello");
|
|
83
|
-
* env.function.resendApiKey; // typed from hello's declared env keys
|
|
84
|
-
*
|
|
85
|
-
* // Filtered: only enforce + return the pooled URL.
|
|
86
|
-
* const { postgres } = parseEnv(config, ["DATABASE_URL"]);
|
|
87
|
-
* postgres.databaseUrl; // string — `databaseUrlUnpooled` is absent
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
declare function parseEnv<const C extends Config>(config: C): NeonEnv<C>;
|
|
91
|
-
declare function parseEnv<const C extends Config, const S extends FunctionSlugOf<C>>(config: C, scope: FunctionScopeField<C, S>): NeonEnv<C> & NeonFunctionEnv<C, S>;
|
|
92
|
-
declare function parseEnv<const C extends Config, const K extends SelectableEnvKey<C>>(config: C, keys: readonly K[]): FilteredNeonEnv<K>;
|
|
93
|
-
//#endregion
|
|
94
|
-
export { FunctionSlugOf, NeonFunctionEnv, NoFunctionScopeHint, parseEnv };
|
|
95
|
-
//# sourceMappingURL=parse-env.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse-env.d.ts","names":[],"sources":["../../src/lib/parse-env.ts"],"mappings":";;;;;;AAqCI;AAAM,KALL,kBAKK,CAAA,UALwB,MAKxB,CAAA,GAJT,WAIS,CAJG,CAIH,CAAA,SAAA,CAAA,CAAA,SAAA;EAGE,SAAA,EAAA,KAAc,EAAA;AAAA,CAAA,GAJtB,CAIsB,GAHtB,MAGsB,CAAA,KAAA,EAAA,KAAA,CAAA;AAAW;AACX,KADd,cACc,CAAA,UADW,MACX,CAAA,GADqB,OACrB,CAAA,MAAnB,kBAAmB,CAAA,CAAA,CAAA,EAAA,MAAA,CAAA;AAAnB;AADwC;AAAO;AAetD;AACyK;AASlJ;AAAW;AAClB;AAAf,KAXW,mBAAA,GAWX,uKAAA;AAEE;AACA;AAAC;AAAA;AAGkB;AACX;AAEP;AAAmC,KAVlC,kBAUkC,CAAA,UAVL,MAUK,EAAA,UAAA,MAAA,CAAA,GAAA,CATtC,cASmB,CATJ,CASI,CAAA,CACc,SAAA,CAAA,KAAA,CAAA,GAR/B,mBAQ+B,GAP/B,CAO+B;AAAnB;AAAsB,KAJhC,iBAIgC,CAAA,UAH1B,MAG0B,EAAA,UAAA,MAAA,CAAA,GADjC,CACiC,SAAA,MADjB,kBACiB,CADE,CACF,CAAA,GAAlC,WAAkC,CAAtB,kBAAsB,CAAH,CAAG,CAAA,CAAA,CAAA,CAAA,CAAA,SAAA;EAAlC,GAAA,EAAA,KAAA,EAAA;AACe,CAAA,GAAd,OAAc,CAAA,MAAA,CAAA,EAAA,MAAA,CAAA,GAAA,KAAA,GAAA,KAAA;AAAd;AAAO;AAQX;AAA2B;AAAW,KAA1B,eAA0B,CAAA,UAAA,MAAA,EAAA,UAAA,MAAA,CAAA,GAAA;EACF,QAAA,EAAzB,MAAyB,CAAlB,iBAAkB,CAAA,CAAA,EAAG,CAAH,CAAA,EAAA,MAAA,CAAA;AAAG,CAAA;AAArB;AAAP;AAAM;AAiIjB;AAAwB;AAAiB;AAAgB;AAAY;AAAR;AAAO;AASpE;AAAwB;AACP;AACe;AAAf;AAER;AACkB;AAAG;AAAtB;AACG;AAAR;AAA6B;AAAG;AAAnB;AAAe;AAC/B;AAAwB;AACP;AACiB;AAAjB;AACP;AAAkB;AAAsB;AAAhB;AAAe;;;;;;;;;;;;;;;iBAnBjC,yBAAyB,gBAAgB,IAAI,QAAQ;iBASrD,yBACC,wBACA,eAAe,YAEvB,UACD,mBAAmB,GAAG,KAC3B,QAAQ,KAAK,gBAAgB,GAAG;iBACnB,yBACC,wBACA,iBAAiB,YACxB,kBAAkB,MAAM,gBAAgB"}
|
package/dist/lib/parse-env.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { NEON_ENV_VAR_KEYS } from "../_shared/env-core/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 `shared/env-core` because nothing else needs it. The
|
|
11
|
-
* `neon` CLI resolves env from the API and injects it; it never reads it back. Keeping it here
|
|
12
|
-
* also keeps `zod` out of the shared tree, and so out of every consumer that copies 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
|
|
196
|
-
export { parseEnv };
|
|
197
|
-
|
|
198
|
-
//# sourceMappingURL=parse-env.js.map
|