@neondatabase/env 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/cli.js +20 -15
- package/dist/cli.js.map +1 -1
- package/dist/env.js +120 -7
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +37 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +64 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ const env2 = parseEnv(config);
|
|
|
41
41
|
// Inside a deployed function, pass its slug for the typed `function` namespace:
|
|
42
42
|
const fnEnv = parseEnv(config, "hello");
|
|
43
43
|
fnEnv.function.resendApiKey; // typed from hello's declared env keys
|
|
44
|
+
fnEnv.functions.hello.baseUrl;
|
|
44
45
|
|
|
45
46
|
// Key filter — only enforce + return the vars you actually use (e.g. a Next.js app that
|
|
46
47
|
// reads the pooled URL but not the unpooled one). The keys autocomplete from the policy, so
|
|
@@ -54,7 +55,7 @@ Both return the same namespaced `NeonEnv` shape: `postgres` is always present; `
|
|
|
54
55
|
| Function | Description |
|
|
55
56
|
| --- | --- |
|
|
56
57
|
| `fetchEnv(config, { projectId, branch, ... })` | Async. Calls the Neon API for the given project + branch and returns live connection strings (and Auth/Data API values when enabled). `projectId` and `branch` are required; `branch` accepts a branch **name** (e.g. `main`) or a `br-…` id. (The legacy id-only `branchId` option still works.) Pass `keys` to fetch only some vars — see [Fetching a subset](#fetching-a-subset). Reads nothing from `process.env` or disk. |
|
|
57
|
-
| `parseEnv(config)` / `parseEnv(config, slug)` / `parseEnv(config, keys)` | Sync. Reads/validates the Neon env vars already present in `process.env` against the static policy toggles. With a function `slug`, also returns a typed `function` namespace of that function's declared env keys. With a `keys` array (e.g. `["DATABASE_URL"]`), only those vars are required and returned, as a narrowed namespaced shape — the keys are typesafe against the policy. Throws `PlatformError(EnvNotInjected)` listing missing vars when the env isn't populated. |
|
|
58
|
+
| `parseEnv(config)` / `parseEnv(config, slug)` / `parseEnv(config, keys)` | Sync. Reads/validates the Neon env vars already present in `process.env` against the static policy toggles. With a function `slug`, also returns a typed `function` namespace of that function's declared env keys; `functions.<slug>.baseUrl` is the invocation URL. With a `keys` array (e.g. `["DATABASE_URL"]`), only those vars are required and returned, as a narrowed namespaced shape — the keys are typesafe against the policy. Throws `PlatformError(EnvNotInjected)` listing missing vars when the env isn't populated. |
|
|
58
59
|
| `toEntries(env)` | Project a resolved `NeonEnv` into `{ KEY: value }` pairs for cross-process transport (named after the web `.entries()` convention; returns a `Record`). |
|
|
59
60
|
|
|
60
61
|
## CLI
|
|
@@ -68,7 +69,7 @@ neon-env run -- npm run dev
|
|
|
68
69
|
neon-env run -- pnpm dev
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
`run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH` / `NEON_BRANCH_ID`, or the `branch` field in `.neon[/project.json]` — by name or id), fetches the connection strings from Neon, and spawns the command with `NEON_BRANCH` / `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (plus the Auth, Data API, object-storage `AWS_*`,
|
|
72
|
+
`run` loads `neon.ts`, resolves the branch (via `--branch`, `NEON_BRANCH` / `NEON_BRANCH_ID`, or the `branch` field in `.neon[/project.json]` — by name or id), fetches the connection strings from Neon, and spawns the command with `NEON_BRANCH` / `DATABASE_URL` / `DATABASE_URL_UNPOOLED` (plus the Auth, Data API, object-storage `AWS_*`, AI Gateway `NEON_AI_GATEWAY_*`, and function `NEON_FUNCTION_*_BASE_URL` vars when the policy enables them — see [Env vars produced](#env-vars-produced)) injected on top of the inherited environment. Stdio is inherited so interactive dev servers keep working, and the parent exits with the child's exit code.
|
|
72
73
|
|
|
73
74
|
### `export` — print env to stdout
|
|
74
75
|
|
|
@@ -136,6 +137,20 @@ These are the OS-level vars `fetchEnv` / `parseEnv` read and `toEntries` (so `ne
|
|
|
136
137
|
| `NEON_AI_GATEWAY_TOKEN` | branch credential's API token (bearer) |
|
|
137
138
|
| `NEON_AI_GATEWAY_BASE_URL` | bare branch gateway host (`https://<branch>-api.ai.<region>.…`, no path) |
|
|
138
139
|
|
|
140
|
+
**Functions** (Preview — when `preview.functions` declares at least one slug). Each declared slug requires `NEON_FUNCTION_<SLUG>_BASE_URL`, typed as `env.functions.<slug>.baseUrl: string`. These are the public function URLs, not the function's declared `env` secrets (those are uploaded at deploy). `fetchEnv` / `neon env pull` derive the URL from the branch connection host; the function does not have to be deployed. `parseEnv` requires the var to be a URL. `neon dev` injects `http://localhost:<port>` instead.
|
|
141
|
+
|
|
142
|
+
| Key | From |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `NEON_FUNCTION_<SLUG>_BASE_URL` | the function's public URL (`https://<branchId>-<slug>.compute.…`), or `http://localhost:<port>` under `neon dev`. `<SLUG>` is the slug uppercased. |
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const env = parseEnv(config);
|
|
148
|
+
env.functions.hello.baseUrl; // string
|
|
149
|
+
|
|
150
|
+
const { functions } = await fetchEnv(config, { projectId, branch: "main" });
|
|
151
|
+
functions.hello.baseUrl; // string
|
|
152
|
+
```
|
|
153
|
+
|
|
139
154
|
### The branch credential
|
|
140
155
|
|
|
141
156
|
Object storage and the AI Gateway are backed by one branch credential, and the Neon API returns its secrets (`s3_secret_access_key`, `api_token`) **once**, at mint time — they aren't stored server-side, and the list endpoint returns metadata only. So there is nothing to *fetch*: `fetchEnv` mints. Call it on every `neon dev` start and you leave a live credential behind each time.
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as
|
|
2
|
+
import { c as isFunctionBaseUrlKey, d as previewCredentialScopes, f as resolveBranchPolicy, i as credentialName, n as createApiFromOptions, o as fetchEnvKeysState, p as toEntries, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as policyEnvKeys } from "./env.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { existsSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -50,7 +50,7 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
50
50
|
const { branch, desired } = await resolveBranchPolicy(config, options, api);
|
|
51
51
|
const allPolicyKeys = policyEnvKeys(desired);
|
|
52
52
|
const requested = requestedKeys ? new Set(requestedKeys) : null;
|
|
53
|
-
const selectedPolicyKeys = requested === null ? allPolicyKeys : allPolicyKeys.filter((key) => requested.has(key));
|
|
53
|
+
const selectedPolicyKeys = requested === null ? allPolicyKeys : [...allPolicyKeys.filter((key) => requested.has(key)), ...[...requested].filter(isFunctionBaseUrlKey).sort()];
|
|
54
54
|
const selected = new Set(selectedPolicyKeys);
|
|
55
55
|
const K = NEON_ENV_VAR_KEYS;
|
|
56
56
|
const storageCredentialSelected = (desired.preview?.buckets.length ?? 0) > 0 && (selected.has(K.storage.accessKeyId) || selected.has(K.storage.secretAccessKey));
|
|
@@ -60,15 +60,16 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
60
60
|
aiGateway: gatewayCredentialSelected
|
|
61
61
|
}).filter((key) => selected.has(key));
|
|
62
62
|
if (secretKeys.length === 0) {
|
|
63
|
-
const fetched = await
|
|
63
|
+
const fetched = await fetchEnvKeysState(config, fetchOptions, requested === null ? null : selectedPolicyKeys);
|
|
64
64
|
return {
|
|
65
|
-
vars: preferPersisted(toEntries(fetched), source),
|
|
65
|
+
vars: preferPersisted(toEntries(fetched.env), source),
|
|
66
66
|
credential: {
|
|
67
67
|
issued: false,
|
|
68
68
|
keys: [],
|
|
69
69
|
revoked: [],
|
|
70
70
|
superseded: []
|
|
71
|
-
}
|
|
71
|
+
},
|
|
72
|
+
...fetched.functionUrlsUnavailable ? { functionUrlsUnavailable: true } : {}
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
const persisted = readPersistedSecrets(source);
|
|
@@ -88,13 +89,15 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
88
89
|
aiGateway: gatewayCredentialSelected
|
|
89
90
|
});
|
|
90
91
|
const keep = reusable !== null && credentialScopesSatisfied(reusable.scopes, scopes);
|
|
91
|
-
const fetchKeys = keep ? selectedPolicyKeys.filter((key) => !secretKeys.includes(key)) : selectedPolicyKeys;
|
|
92
|
-
const fetched = await
|
|
92
|
+
const fetchKeys = requested === null ? null : keep ? selectedPolicyKeys.filter((key) => !secretKeys.includes(key)) : selectedPolicyKeys;
|
|
93
|
+
const fetched = await fetchEnvKeysState(config, {
|
|
93
94
|
...fetchOptions,
|
|
94
95
|
branchId: branch.id,
|
|
95
|
-
api
|
|
96
|
+
api,
|
|
97
|
+
...keep && requested === null ? { omitKeys: secretKeys } : {}
|
|
96
98
|
}, fetchKeys);
|
|
97
|
-
const vars = preferPersisted(toEntries(fetched), source);
|
|
99
|
+
const vars = preferPersisted(toEntries(fetched.env), source);
|
|
100
|
+
const unavailable = fetched.functionUrlsUnavailable ? { functionUrlsUnavailable: true } : {};
|
|
98
101
|
if (keep) {
|
|
99
102
|
for (const key of secretKeys) {
|
|
100
103
|
const value = source[key];
|
|
@@ -107,7 +110,8 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
107
110
|
keys: secretKeys,
|
|
108
111
|
revoked: [],
|
|
109
112
|
superseded: []
|
|
110
|
-
}
|
|
113
|
+
},
|
|
114
|
+
...unavailable
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
117
|
const ours = /* @__PURE__ */ new Set();
|
|
@@ -120,7 +124,8 @@ async function fetchEnvReusingSecrets(config, options) {
|
|
|
120
124
|
keys: secretKeys,
|
|
121
125
|
revoked: revokeSuperseded ? [...ours] : [],
|
|
122
126
|
superseded: revokeSuperseded ? [] : [...ours]
|
|
123
|
-
}
|
|
127
|
+
},
|
|
128
|
+
...unavailable
|
|
124
129
|
};
|
|
125
130
|
}
|
|
126
131
|
/** Read the branch credential's secrets out of an env source. */
|
|
@@ -727,7 +732,7 @@ const createCredentialStore = (dir, options = {}) => {
|
|
|
727
732
|
const assertKeyringWritable = (profile) => {
|
|
728
733
|
if (keyring === null) throw new KeyringUnavailableError(profile, "write");
|
|
729
734
|
};
|
|
730
|
-
const setKeyringOrRollback = (profile, credentials) => {
|
|
735
|
+
const setKeyringOrRollback = (profile, credentials, restorePrevious) => {
|
|
731
736
|
assertKeyringWritable(profile);
|
|
732
737
|
const kr = keyring;
|
|
733
738
|
if (kr === null) throw new KeyringUnavailableError(profile, "write");
|
|
@@ -747,7 +752,7 @@ const createCredentialStore = (dir, options = {}) => {
|
|
|
747
752
|
try {
|
|
748
753
|
if (kr.get("com.neon.neon-cli", account) === null) throw new Error(`Wrote credentials to the OS keyring for ${label} but could not read them back.`);
|
|
749
754
|
} catch (err) {
|
|
750
|
-
if (previous !== null) {
|
|
755
|
+
if (restorePrevious && previous !== null) {
|
|
751
756
|
try {
|
|
752
757
|
kr.set(KEYRING_SERVICE, account, previous);
|
|
753
758
|
} catch {
|
|
@@ -863,9 +868,9 @@ const createCredentialStore = (dir, options = {}) => {
|
|
|
863
868
|
profile: at.profile
|
|
864
869
|
};
|
|
865
870
|
};
|
|
866
|
-
const write = (at, credentials) => {
|
|
871
|
+
const write = (at, credentials, options) => {
|
|
867
872
|
if (at.storage === "keyring") {
|
|
868
|
-
setKeyringOrRollback(at.profile, credentials);
|
|
873
|
+
setKeyringOrRollback(at.profile, credentials, options?.restorePrevious !== false);
|
|
869
874
|
return {
|
|
870
875
|
credentials,
|
|
871
876
|
backend: CRED_STORAGE_KEYRING,
|