@neondatabase/env 1.1.6 → 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 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_*`, and AI Gateway `NEON_AI_GATEWAY_*` 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
+ `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 previewCredentialScopes, i as credentialName, l as resolveBranchPolicy, n as createApiFromOptions, o as fetchEnvKeys, r as credentialEnvKeys, s as policyEnvKeys, t as NEON_ENV_VAR_KEYS, u as toEntries } from "./env.js";
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 fetchEnvKeys(config, fetchOptions, requested === null ? null : selectedPolicyKeys);
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 fetchEnvKeys(config, {
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,