neon 3.0.0 → 3.1.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.
Files changed (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -0,0 +1,585 @@
1
+ import { log } from "../log.js";
2
+ import { getCliName } from "../utils/cli_name.js";
3
+ import { _ as policyEnvKeys, a as envKeysForSelection, b as toEntries, d as servicesForEnvKeys, f as NEON_ENV_VAR_KEYS, g as fetchEnvKeys, h as credentialName, m as credentialEnvKeys, p as createApiFromOptions, r as ENV_PULL_SERVICES, u as serviceForEnvKey, v as previewCredentialScopes, y as resolveBranchPolicy } from "./env_services-Tz9G4JeT.js";
4
+ import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
5
+ import { credentialScopesSatisfied } from "@neon/config/v1";
6
+ import { ErrorCode as ErrorCode$1, createNeonApiFromOptions as createNeonApiFromOptions$1, isPlatformError, loadConfigFromFile } from "@neon/config";
7
+ import { plan, pullConfig } from "@neon/config-runtime";
8
+ //#region ../../internals/env-core/dist/reuse-secrets.js
9
+ /**
10
+ * Resolve a branch's env while keeping one-time secrets the caller already holds.
11
+ *
12
+ * {@link fetchEnvKeys} — and the public `fetchEnv` — only ever *fetch*. The Neon API returns a
13
+ * credential's `api_token` / `s3_secret_access_key` exactly once, at mint time, so "fetching"
14
+ * them means minting a new credential; a plain `fetchEnv` on every `neon dev` start or `env
15
+ * pull` would leave a live credential behind each time. This is the wrapper that avoids that:
16
+ * it looks at what the caller already has, decides what is still usable, and asks `fetchEnv`
17
+ * for only the rest.
18
+ *
19
+ * The check is a real verification, not a presence test. A persisted secret is kept only when
20
+ * it names a credential that still exists on this branch, is not revoked or expired, and
21
+ * carries every scope the policy needs. A `.env.example` placeholder, a credential revoked in
22
+ * the console, one copied in from another branch, or one predating a newly-enabled feature all
23
+ * fail that check and get replaced.
24
+ *
25
+ * None of this needs local bookkeeping, because the secrets carry their own credential id:
26
+ * `AWS_ACCESS_KEY_ID` **is** the credential's `tokenId` (the storage gateway authenticates
27
+ * against the full id), and the AI Gateway token is minted as `nt_live_<tokenIdShort>_<secret>`,
28
+ * where `tokenIdShort` is what the credentials list reports. The env source being replaced is
29
+ * the record of what the last call issued.
30
+ *
31
+ * ```ts
32
+ * import { fetchEnvReusingSecrets } from "@neon-internals/env-core/reuse-secrets";
33
+ *
34
+ * const { vars, credential } = await fetchEnvReusingSecrets(config, {
35
+ * projectId,
36
+ * branch: "main",
37
+ * env: { ...process.env, ...readEnvFile(".env") },
38
+ * });
39
+ * if (credential.issued) console.log(`new values for ${credential.keys.join(", ")}`);
40
+ * ```
41
+ */
42
+ async function fetchEnvReusingSecrets(config, options) {
43
+ const { env: source = process.env, keys: requestedKeys, revokeSuperseded = true, ...fetchOptions } = options;
44
+ const api = options.api ?? createApiFromOptions(options);
45
+ const { branch, desired } = await resolveBranchPolicy(config, options, api);
46
+ const allPolicyKeys = policyEnvKeys(desired);
47
+ const requested = requestedKeys ? new Set(requestedKeys) : null;
48
+ const selectedPolicyKeys = requested === null ? allPolicyKeys : allPolicyKeys.filter((key) => requested.has(key));
49
+ const selected = new Set(selectedPolicyKeys);
50
+ const K = NEON_ENV_VAR_KEYS;
51
+ const storageCredentialSelected = (desired.preview?.buckets.length ?? 0) > 0 && (selected.has(K.storage.accessKeyId) || selected.has(K.storage.secretAccessKey));
52
+ const gatewayCredentialSelected = (desired.preview?.aiGatewayEnabled ?? false) && selected.has(K.aiGateway.apiKey);
53
+ const secretKeys = credentialEnvKeys({
54
+ storage: storageCredentialSelected,
55
+ aiGateway: gatewayCredentialSelected
56
+ }).filter((key) => selected.has(key));
57
+ if (secretKeys.length === 0) {
58
+ const fetched = await fetchEnvKeys(config, fetchOptions, requested === null ? null : selectedPolicyKeys);
59
+ return {
60
+ vars: preferPersisted(toEntries(fetched), source),
61
+ credential: {
62
+ issued: false,
63
+ keys: [],
64
+ revoked: [],
65
+ superseded: []
66
+ }
67
+ };
68
+ }
69
+ const persisted = readPersistedSecrets(source);
70
+ const storageCredentialManaged = requested === null || storageCredentialSelected;
71
+ const gatewayCredentialManaged = requested === null || gatewayCredentialSelected;
72
+ const complete = (!storageCredentialSelected || Boolean(persisted.accessKeyId && persisted.secretAccessKey)) && (!gatewayCredentialSelected || Boolean(persisted.apiToken));
73
+ const named = storageCredentialManaged && persisted.accessKeyId !== "" || gatewayCredentialManaged && persisted.apiToken !== "" ? namedCredentials(await api.listCredentials(options.projectId, branch.id), persisted) : {
74
+ storage: null,
75
+ gateway: null
76
+ };
77
+ const reusable = complete ? reusableCredential(named, {
78
+ storageEnabled: storageCredentialSelected,
79
+ gatewayEnabled: gatewayCredentialSelected
80
+ }) : null;
81
+ const scopes = previewCredentialScopes(desired.preview, {
82
+ storage: storageCredentialSelected,
83
+ aiGateway: gatewayCredentialSelected
84
+ });
85
+ const keep = reusable !== null && credentialScopesSatisfied(reusable.scopes, scopes);
86
+ const fetchKeys = keep ? selectedPolicyKeys.filter((key) => !secretKeys.includes(key)) : selectedPolicyKeys;
87
+ const fetched = await fetchEnvKeys(config, {
88
+ ...fetchOptions,
89
+ branchId: branch.id,
90
+ api
91
+ }, fetchKeys);
92
+ const vars = preferPersisted(toEntries(fetched), source);
93
+ if (keep) {
94
+ for (const key of secretKeys) {
95
+ const value = source[key];
96
+ if (value !== void 0) vars[key] = value;
97
+ }
98
+ return {
99
+ vars,
100
+ credential: {
101
+ issued: false,
102
+ keys: secretKeys,
103
+ revoked: [],
104
+ superseded: []
105
+ }
106
+ };
107
+ }
108
+ const ours = /* @__PURE__ */ new Set();
109
+ for (const meta of [storageCredentialManaged ? named.storage : null, gatewayCredentialManaged ? named.gateway : null]) if (meta !== null && meta.principalType === "user" && meta.name === credentialName(branch.name)) ours.add(meta.tokenId);
110
+ if (revokeSuperseded) for (const tokenId of ours) await api.revokeCredential(options.projectId, branch.id, tokenId);
111
+ return {
112
+ vars,
113
+ credential: {
114
+ issued: true,
115
+ keys: secretKeys,
116
+ revoked: revokeSuperseded ? [...ours] : [],
117
+ superseded: revokeSuperseded ? [] : [...ours]
118
+ }
119
+ };
120
+ }
121
+ /** Read the branch credential's secrets out of an env source. */
122
+ function readPersistedSecrets(source) {
123
+ const storage = NEON_ENV_VAR_KEYS.storage;
124
+ const gateway = NEON_ENV_VAR_KEYS.aiGateway;
125
+ return {
126
+ accessKeyId: source[storage.accessKeyId] ?? "",
127
+ secretAccessKey: source[storage.secretAccessKey] ?? "",
128
+ apiToken: source[gateway.apiKey] ?? ""
129
+ };
130
+ }
131
+ /**
132
+ * Keep a persisted value rather than overwriting it with an empty fetched one.
133
+ *
134
+ * Neon Auth's `base_url` is the case that needs this: integrations created before the API
135
+ * returned it answer with an empty string, and the persisted copy is the only one left. An
136
+ * empty fetched value never carries more information than a non-empty persisted one, so
137
+ * preferring the latter is safe for every var — and it keeps a pull from blanking a working
138
+ * line in someone's `.env`.
139
+ */
140
+ function preferPersisted(vars, source) {
141
+ const out = { ...vars };
142
+ for (const [key, value] of Object.entries(out)) {
143
+ if (value !== "") continue;
144
+ const persisted = source[key];
145
+ if (persisted !== void 0 && persisted !== "") out[key] = persisted;
146
+ }
147
+ return out;
148
+ }
149
+ /**
150
+ * The credential id embedded in an AI Gateway token. The API mints them as
151
+ * `nt_live_<tokenIdShort>_<secret>`, and `tokenIdShort` is the public identifier the credentials
152
+ * list reports — so a persisted token names the credential that issued it. Returns `null` for
153
+ * anything not in that shape (a `.env.example` placeholder, a hand-typed value), which callers
154
+ * treat as unverifiable.
155
+ */
156
+ function gatewayTokenIdShort(apiToken) {
157
+ return /^nt_live_([^_]+)_.+$/.exec(apiToken)?.[1] ?? null;
158
+ }
159
+ /** Whether an issued credential can still be used: not revoked, not past its expiry. */
160
+ function isLiveCredential(meta, now) {
161
+ if (meta.revokedAt !== void 0) return false;
162
+ if (meta.expiresAt === void 0) return true;
163
+ const expiresAt = Date.parse(meta.expiresAt);
164
+ return Number.isNaN(expiresAt) || expiresAt > now;
165
+ }
166
+ /**
167
+ * The live credentials the persisted secrets name — at most one per half. A half that names
168
+ * nothing contributes nothing, which is what a placeholder, a credential revoked in the
169
+ * console, and one copied in from another branch all look like from here.
170
+ */
171
+ function namedCredentials(live, persisted) {
172
+ const usable = live.filter((meta) => isLiveCredential(meta, Date.now()));
173
+ const shortId = persisted.apiToken ? gatewayTokenIdShort(persisted.apiToken) : null;
174
+ return {
175
+ storage: persisted.accessKeyId ? usable.find((meta) => meta.tokenId === persisted.accessKeyId) ?? null : null,
176
+ gateway: shortId ? usable.find((meta) => meta.tokenIdShort === shortId) ?? null : null
177
+ };
178
+ }
179
+ /**
180
+ * The credential the persisted secrets can be *reused* as, or `null`.
181
+ *
182
+ * Strict on purpose: every half the policy enables has to name a live credential, and when both
183
+ * features are enabled they must name the *same* one — they share a single credential, so
184
+ * halves that disagree came from two different calls and neither can be trusted.
185
+ */
186
+ function reusableCredential(named, enabled) {
187
+ if (enabled.storageEnabled && enabled.gatewayEnabled) return named.storage && named.gateway && named.storage.tokenId === named.gateway.tokenId ? named.storage : null;
188
+ if (enabled.storageEnabled) return named.storage;
189
+ if (enabled.gatewayEnabled) return named.gateway;
190
+ return null;
191
+ }
192
+ //#endregion
193
+ //#region src/dev/env.ts
194
+ /** The API-targeting options every runtime call forwards from the context. */
195
+ const apiOptions = (ctx) => ({
196
+ ...ctx.apiKey ? { apiKey: ctx.apiKey } : {},
197
+ ...ctx.apiHost ? { apiHost: ctx.apiHost } : {},
198
+ ...ctx.api ? { api: ctx.api } : {}
199
+ });
200
+ /**
201
+ * Thrown when a `neon.ts` policy declares a branch-level resource (Neon Auth,
202
+ * Data API, a bucket, the AI Gateway) that the linked remote branch does not
203
+ * have yet. Unlike every other failure in {@link resolveDevEnv} — which degrades
204
+ * to "run without injection" — this is a hard stop: the user's intent (a policy)
205
+ * cannot be honored, and silently dropping the secret would be more confusing
206
+ * than refusing to start. The fix is to provision the resource first.
207
+ */
208
+ var DevEnvMismatchError = class extends Error {
209
+ constructor(..._args) {
210
+ super(..._args);
211
+ this.name = "DevEnvMismatchError";
212
+ }
213
+ };
214
+ /**
215
+ * Signals that no project/branch context could be resolved, so there is nothing to
216
+ * resolve env from. `resolveDevEnv` degrades on this (dev runs without injection);
217
+ * `env pull` surfaces it (an explicit pull needs a branch).
218
+ */
219
+ var MissingBranchContextError = class extends Error {
220
+ constructor(..._args2) {
221
+ super(..._args2);
222
+ this.name = "MissingBranchContextError";
223
+ }
224
+ };
225
+ /**
226
+ * Thrown when an explicit `--service` / `--env` selection needs a service the branch lacks.
227
+ * Unlike the policy path — where the same situation is a {@link DevEnvMismatchError} pointing
228
+ * at `deploy` — the user named the service on the command line, so the fix is to provision it
229
+ * or drop it from the selection.
230
+ */
231
+ var ServiceNotOnBranchError = class extends Error {
232
+ constructor(..._args3) {
233
+ super(..._args3);
234
+ this.name = "ServiceNotOnBranchError";
235
+ }
236
+ };
237
+ /**
238
+ * Resolve the branch's Neon env vars (pooled / direct `DATABASE_URL`, plus Auth /
239
+ * Data API when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
240
+ * injects them) and `neon env pull` (which writes them to a `.env` file).
241
+ *
242
+ * Tiered:
243
+ *
244
+ * 0. {@link DevEnvContext.services} or {@link DevEnvContext.envKeys} is set -> that
245
+ * selection *is* the policy, and any `neon.ts` is ignored. See
246
+ * {@link resolveSelectedServices}.
247
+ * 1. a `neon.ts` policy is found -> the policy is the source of truth. We first
248
+ * check it against the branch's live state (`plan`); if it declares a resource
249
+ * the branch is missing, we stop with a {@link DevEnvMismatchError} pointing at
250
+ * `neonctl deploy`. Otherwise `fetchEnv` evaluates the policy.
251
+ * 2. no `neon.ts`, but a project + branch are known -> `pullConfig` reads the
252
+ * branch's live state (Auth / Data API enablement plus any object-storage
253
+ * buckets) into a config, then `fetchEnv` resolves what is actually enabled —
254
+ * so a branch with a bucket gets its `AWS_*` storage vars pulled with no policy.
255
+ * With {@link DevEnvContext.implyAiGateway}, the AI Gateway is added on top, since
256
+ * `pullConfig` cannot read it back.
257
+ * 3. otherwise -> throw {@link MissingBranchContextError}.
258
+ *
259
+ * Unlike {@link resolveDevEnv}, this never swallows errors — callers decide how to
260
+ * handle them.
261
+ */
262
+ const resolveNeonEnvVars = async (ctx) => {
263
+ if (ctx.services !== void 0 || ctx.envKeys !== void 0) return await resolveSelectedServices(ctx, ctx.services ?? [], ctx.envKeys ?? []);
264
+ const config = await loadNeonConfig(ctx.cwd);
265
+ if (config) {
266
+ if (!ctx.projectId || !ctx.branchId) throw new MissingBranchContextError(`Found a neon.ts but could not resolve the project/branch. Run \`${getCliName()} link\` and \`${getCliName()} checkout <branch>\`, or pass --project-id / --branch.`);
267
+ const envConfig = withoutPreviewFunctions(config);
268
+ await assertPolicyMatchesBranch(envConfig, ctx);
269
+ return await fetchAndProject(envConfig, ctx);
270
+ }
271
+ if (ctx.projectId && ctx.branchId) {
272
+ const pulled = await pullConfig({
273
+ projectId: ctx.projectId,
274
+ branchId: ctx.branchId,
275
+ ...apiOptions(ctx)
276
+ });
277
+ if (!ctx.implyAiGateway) return await fetchAndProject(pulled.config, ctx);
278
+ return await resolveWithImpliedGateway(pulled.config, ctx, {
279
+ projectId: ctx.projectId,
280
+ branchId: ctx.branchId
281
+ });
282
+ }
283
+ throw new MissingBranchContextError(`No project/branch context found. Link a branch (\`${getCliName()} link\` / \`${getCliName()} checkout\`) or pass --project-id and --branch.`);
284
+ };
285
+ /** The same config with the AI Gateway enabled, leaving any other `preview` entries intact. */
286
+ const withAiGateway = (config) => ({
287
+ ...config,
288
+ preview: {
289
+ ...config.preview,
290
+ aiGateway: true
291
+ }
292
+ });
293
+ /**
294
+ * Tier-2 resolution with the AI Gateway added on top of the branch's read-back state.
295
+ *
296
+ * The gateway is not detectable — `pullConfig` reports no enabled flag for it — so it is
297
+ * implied rather than observed. Nobody named it, so it must never be the reason the whole
298
+ * resolve fails: a project outside the regions where branch credentials exist would otherwise
299
+ * lose its `DATABASE_URL` too, and `neon dev` would start with no env at all.
300
+ *
301
+ * So the gateway is only added once its credential endpoint has been shown to answer, by
302
+ * reading the branch's credentials first. A project that does not have them says so on a
303
+ * read, before anything is minted — which is the whole question, since the gateway's env is a
304
+ * credential and nothing else.
305
+ *
306
+ * Deciding this **before** resolving, rather than by catching and retrying, is what keeps it
307
+ * honest. A retry re-runs every call the first attempt made, so it would blame the gateway for
308
+ * a one-off failure in shared work, and — worse — a first attempt that minted a credential and
309
+ * then failed would be papered over by a second that succeeds without one, swallowing the
310
+ * error and stranding a secret nobody holds. Once the read succeeds, a later failure is a real
311
+ * failure and propagates: the same thing already happens on a branch with object storage,
312
+ * whose credential is minted whether or not the gateway is involved.
313
+ */
314
+ const resolveWithImpliedGateway = async (config, ctx, branch) => {
315
+ const unreachable = await credentialsUnreachable(ctx, branch);
316
+ if (unreachable === null) return await fetchAndProject(withAiGateway(config), ctx);
317
+ log.warning(`Could not reach the AI Gateway's credentials, so %s were not resolved. Everything else was. Either this project does not have the AI Gateway, or the call failed — \`${getCliName()} env pull -s ai-gateway\` will say which.\nDetails: %s`, [NEON_ENV_VAR_KEYS.aiGateway.apiKey, NEON_ENV_VAR_KEYS.aiGateway.baseUrl].join(" and "), unreachable);
318
+ return {
319
+ ...await fetchAndProject(config, ctx),
320
+ skipped: ["ai-gateway"]
321
+ };
322
+ };
323
+ /**
324
+ * Why the branch's credentials could not be read, or `null` when they could. A plain read: it
325
+ * mints nothing, revokes nothing, and changes nothing, so asking is free of the side effects
326
+ * that make a failed resolve ambiguous.
327
+ */
328
+ const credentialsUnreachable = async (ctx, branch) => {
329
+ try {
330
+ await apiFor(ctx).listCredentials(branch.projectId, branch.branchId);
331
+ return null;
332
+ } catch (err) {
333
+ return err instanceof Error ? err.message : String(err);
334
+ }
335
+ };
336
+ /** The adapter for direct branch reads: the injected one in tests, else built from options. */
337
+ const apiFor = (ctx) => ctx.api ?? createNeonApiFromOptions$1("neon env", {
338
+ ...ctx.apiKey ? { apiKey: ctx.apiKey } : {},
339
+ ...ctx.apiHost ? { apiHost: ctx.apiHost } : {}
340
+ });
341
+ /**
342
+ * Tier-0: resolve exactly what `--service` / `--env` selected, with `neon.ts` ignored.
343
+ *
344
+ * The selection is checked against live project and branch state so a service that is named
345
+ * but not provisioned fails by name, instead of quietly contributing dead vars. `postgres`
346
+ * needs no check because every branch has it.
347
+ */
348
+ const resolveSelectedServices = async (ctx, directServices, envKeys) => {
349
+ const { projectId, branchId } = ctx;
350
+ if (!projectId || !branchId) throw new MissingBranchContextError(`An explicit env selection needs a project and branch to read from. Run \`${getCliName()} link\` and \`${getCliName()} checkout <branch>\`, or pass --project-id / --branch.`);
351
+ const impliedServices = servicesForEnvKeys(envKeys);
352
+ const services = ENV_PULL_SERVICES.filter((service) => directServices.includes(service) || impliedServices.includes(service));
353
+ const selectedKeys = envKeysForSelection(directServices, envKeys);
354
+ const api = apiFor(ctx);
355
+ const has = (service) => services.includes(service);
356
+ const checkAiGateway = has("ai-gateway") && !selectedKeys.includes(NEON_ENV_VAR_KEYS.aiGateway.apiKey);
357
+ const [auth, dataApiEnabled, buckets, aiGatewayAvailable] = await Promise.all([
358
+ has("auth") ? api.getNeonAuth(projectId, branchId) : null,
359
+ has("data-api") ? readDataApiEnabled(api, projectId, branchId) : null,
360
+ has("object-storage") ? api.listBranchBuckets(projectId, branchId) : null,
361
+ checkAiGateway ? readAiGatewayAvailable(api, projectId, branchId) : null
362
+ ]);
363
+ const config = configForServices(services, branchId, {
364
+ authEnabled: auth !== null,
365
+ dataApiEnabled,
366
+ buckets: buckets ?? [],
367
+ aiGatewayAvailable: aiGatewayAvailable ?? true
368
+ }, {
369
+ directServices,
370
+ envKeys
371
+ });
372
+ return await fetchAndProject(config, ctx, {
373
+ keys: selectedKeys,
374
+ revokeSuperseded: false
375
+ });
376
+ };
377
+ /**
378
+ * Whether the branch has a Data API integration — or `null` when that cannot be determined.
379
+ *
380
+ * It is enabled per branch *and database*, so this has to probe the database `fetchEnv` will
381
+ * resolve the URL from, or the two would disagree. That is Neon's default `neondb`, else the
382
+ * only database; several databases with no `neondb` is a case `fetchEnv` refuses to auto-pick
383
+ * at all. Reporting "no Data API integration" there would be a claim this read cannot support,
384
+ * so it answers `null` and lets `fetchEnv` raise its own ambiguity error, which names the
385
+ * databases and the fix.
386
+ */
387
+ const readDataApiEnabled = async (api, projectId, branchId) => {
388
+ const databases = await api.listBranchDatabases(projectId, branchId);
389
+ const database = databases.find((db) => db.name === NEON_DEFAULT_DATABASE) ?? (databases.length === 1 ? databases[0] : void 0);
390
+ if (!database) return databases.length === 0 ? false : null;
391
+ return await api.getNeonDataApi(projectId, branchId, database.name) !== null;
392
+ };
393
+ const readAiGatewayAvailable = async (api, projectId, branchId) => {
394
+ try {
395
+ await api.listCredentials(projectId, branchId);
396
+ return true;
397
+ } catch (error) {
398
+ if (isPlatformError(error) && error.code === ErrorCode$1.FeatureUnavailable && (error.details.status === 404 || error.details.status === 501)) return false;
399
+ throw error;
400
+ }
401
+ };
402
+ /** Neon's default database, and the one `fetchEnv` prefers when a branch has several. */
403
+ const NEON_DEFAULT_DATABASE = "neondb";
404
+ /**
405
+ * Build the `Config` an explicit `--service` / `--env` selection stands for, raising
406
+ * {@link ServiceNotOnBranchError} for anything the branch does not have. Naming a service
407
+ * that isn't there has to fail rather than contribute no vars, or a scoped pull would report
408
+ * "no Neon env variables to pull" — which reads as a statement about the branch rather than
409
+ * about the selection.
410
+ */
411
+ const configForServices = (services, branchId, branch, selection) => {
412
+ const provisionWith = {
413
+ auth: `${getCliName()} neon-auth enable`,
414
+ "data-api": `${getCliName()} data-api create`,
415
+ "object-storage": `${getCliName()} buckets create <name>`
416
+ };
417
+ const notOnBranch = (service, what) => {
418
+ if (!selection.directServices.includes(service)) {
419
+ const names = selection.envKeys.filter((key) => serviceForEnvKey(key) === service).join(", ");
420
+ throw new ServiceNotOnBranchError(`--env ${names}: branch ${branchId} has no ${what}, so ${names} cannot be pulled. Provision it first (\`${provisionWith[service]}\`, or in the Neon Console), or drop ${names} from --env.`);
421
+ }
422
+ throw new ServiceNotOnBranchError(`--service ${service}: branch ${branchId} has no ${what}, so there are no ${service} env vars to pull. Provision it first (\`${provisionWith[service]}\`, or in the Neon Console), or drop ${service} from --service.`);
423
+ };
424
+ const aiGatewayUnavailable = () => {
425
+ const reason = "AI Gateway is not available for this Neon project because branch credentials are unavailable";
426
+ if (selection.directServices.includes("ai-gateway")) throw new ServiceNotOnBranchError(`--service ai-gateway: ${reason}. Use another project, or drop ai-gateway from --service.`);
427
+ const names = selection.envKeys.filter((key) => serviceForEnvKey(key) === "ai-gateway").join(", ");
428
+ throw new ServiceNotOnBranchError(`--env ${names}: ${reason}, so ${names} cannot be pulled. Use another project, or drop ${names} from --env.`);
429
+ };
430
+ const config = {};
431
+ if (services.includes("auth")) {
432
+ if (!branch.authEnabled) notOnBranch("auth", "Neon Auth integration");
433
+ config.auth = true;
434
+ }
435
+ if (services.includes("data-api")) {
436
+ if (branch.dataApiEnabled === false) notOnBranch("data-api", "Data API integration");
437
+ config.dataApi = true;
438
+ }
439
+ const preview = {};
440
+ if (services.includes("object-storage")) {
441
+ if (branch.buckets.length === 0) notOnBranch("object-storage", "object-storage buckets");
442
+ preview.buckets = Object.fromEntries(branch.buckets.map((bucket) => [bucket.name, { access: bucket.accessLevel }]));
443
+ }
444
+ if (services.includes("ai-gateway")) {
445
+ if (!branch.aiGatewayAvailable) aiGatewayUnavailable();
446
+ preview.aiGateway = true;
447
+ }
448
+ if (Object.keys(preview).length > 0) config.preview = preview;
449
+ return config;
450
+ };
451
+ /**
452
+ * `neon dev`'s env resolver: {@link resolveNeonEnvVars} with graceful degradation.
453
+ *
454
+ * - Success → `{ vars }` (possibly just the always-present Postgres URLs).
455
+ * - No linked branch / project → `{ vars: {}, skipped }` with a "link a branch" hint; the
456
+ * function still runs locally, just without Neon env.
457
+ * - Any other failure (offline, transient API error) → `{ vars: {}, skipped }` naming the
458
+ * cause; again non-fatal.
459
+ * - {@link DevEnvMismatchError} (policy declares a secret-bearing service the branch lacks)
460
+ * is the one hard stop and is re-thrown for the caller to surface.
461
+ */
462
+ const resolveDevEnv = async (ctx) => {
463
+ try {
464
+ const { vars, credential } = await resolveNeonEnvVars(ctx);
465
+ return {
466
+ vars,
467
+ credential
468
+ };
469
+ } catch (err) {
470
+ if (err instanceof DevEnvMismatchError) throw err;
471
+ if (err instanceof MissingBranchContextError) {
472
+ log.debug("dev: %s; skipping env injection", err.message);
473
+ return {
474
+ vars: {},
475
+ skipped: { reason: `no linked Neon branch — run \`${getCliName()} link\`, then \`${getCliName()} checkout <branch>\`, to inject DATABASE_URL and friends` }
476
+ };
477
+ }
478
+ const detail = err instanceof Error ? err.message : String(err);
479
+ log.debug("dev: env resolution failed: %s", detail);
480
+ return {
481
+ vars: {},
482
+ skipped: { reason: `could not reach Neon (${detail}); running without Neon env` }
483
+ };
484
+ }
485
+ };
486
+ /**
487
+ * Return the policy with its `preview.functions` removed, so the env path never enumerates
488
+ * functions against the Neon API. Functions are local-source-bundled and produce no
489
+ * branch-level secrets, so they are irrelevant to env resolution; probing them only risks
490
+ * failing the whole resolve (undeployed function, or Functions Preview disabled on the
491
+ * project). Buckets / AI Gateway and the top-level Auth / Data API toggles are preserved —
492
+ * they DO carry env, so they must still be checked and resolved. Returns the config
493
+ * unchanged when it declares no functions.
494
+ */
495
+ const withoutPreviewFunctions = (config) => {
496
+ const preview = config.preview;
497
+ if (!preview?.functions) return config;
498
+ const previewWithoutFunctions = { ...preview };
499
+ delete previewWithoutFunctions.functions;
500
+ return {
501
+ ...config,
502
+ preview: previewWithoutFunctions
503
+ };
504
+ };
505
+ /**
506
+ * Tier-1 guard. Dry-run the policy against the branch's live state and stop if
507
+ * it declares a branch-level resource the branch is missing. Built on `plan` so
508
+ * it covers every present and future provisionable resource for free: any
509
+ * `create` action is a resource `neonctl deploy` would provision.
510
+ *
511
+ * Called with functions already stripped (see {@link withoutPreviewFunctions}), so the
512
+ * `plan` probe never enumerates the functions API — an undeployed function, or a project
513
+ * without the Functions Preview, must never block local dev or sink env injection.
514
+ */
515
+ const assertPolicyMatchesBranch = async (config, ctx) => {
516
+ const missing = (await plan(config, {
517
+ projectId: ctx.projectId,
518
+ branchId: ctx.branchId,
519
+ ...apiOptions(ctx)
520
+ })).applied.filter(isMissingResource);
521
+ if (missing.length === 0) return;
522
+ throw new DevEnvMismatchError(`Your neon.ts declares ${missing.map((change) => change.identifier).join(", ")} for branch ${ctx.branchId}, but the branch does not have it yet, so the matching env vars cannot be injected. Provision it first with \`${getCliName()} deploy\` (or \`${getCliName()} config apply\`), then re-run \`${getCliName()} dev\`.`);
523
+ };
524
+ /**
525
+ * A planned change that provisions a branch-level resource the branch lacks: a
526
+ * `create` on a service (Neon Auth, Data API, a bucket, the AI Gateway). Branch
527
+ * setting drift (`update`) and `noop`s are ignored — they don't block local dev
528
+ * — and functions are excluded (see {@link assertPolicyMatchesBranch}).
529
+ */
530
+ const isMissingResource = (change) => change.kind === "service" && change.action === "create" && !change.identifier.startsWith("function:");
531
+ const fetchAndProject = async (config, ctx, opts = {}) => fetchEnvReusingSecrets(config, {
532
+ projectId: ctx.projectId,
533
+ branch: ctx.branchId,
534
+ ...apiOptions(ctx),
535
+ ...ctx.env ? { env: ctx.env } : {},
536
+ ...opts.keys ? { keys: opts.keys } : {},
537
+ ...opts.revokeSuperseded === false ? { revokeSuperseded: false } : {}
538
+ });
539
+ /**
540
+ * Load a `neon.ts` policy if one exists on the path from `cwd` up to the repo
541
+ * root. Returns `null` when there is none (the common "no config" case), and
542
+ * surfaces real load errors (e.g. a syntax error in an existing file).
543
+ */
544
+ /**
545
+ * Substrings that mark a module-resolution failure while loading `neon.ts` —
546
+ * almost always because the project's dependencies aren't installed yet (the
547
+ * config imports `@neon/config` & friends). Deliberately specific:
548
+ * the generic "…or a missing dependency…" hint the loader always appends is
549
+ * NOT in here, so a real syntax/runtime error doesn't get mislabeled.
550
+ */
551
+ const MISSING_DEPENDENCY_HINTS = [
552
+ "cannot find module",
553
+ "cannot find package",
554
+ "err_module_not_found",
555
+ "failed to resolve",
556
+ "could not resolve",
557
+ "module not found"
558
+ ];
559
+ /** Flatten an error and its `cause` chain to one lowercased string for matching. */
560
+ const errorChainText = (err) => {
561
+ const parts = [];
562
+ let current = err;
563
+ for (let depth = 0; current instanceof Error && depth < 6; depth++) {
564
+ parts.push(current.message);
565
+ current = current.cause;
566
+ }
567
+ return parts.join("\n").toLowerCase();
568
+ };
569
+ const looksLikeMissingDependency = (err) => {
570
+ const text = errorChainText(err);
571
+ return MISSING_DEPENDENCY_HINTS.some((hint) => text.includes(hint));
572
+ };
573
+ const loadNeonConfig = async (cwd) => {
574
+ try {
575
+ const { config } = await loadConfigFromFile({ cwd });
576
+ return config;
577
+ } catch (err) {
578
+ const message = err instanceof Error ? err.message : String(err);
579
+ if (/Could not find a Neon config file/i.test(message)) return null;
580
+ if (looksLikeMissingDependency(err)) throw new Error(`Could not load neon.ts: a package it imports is not installed. Run \`${formatInstallCommand(resolvePackageManager(cwd))}\`, then try again.\nOriginal error: ${message}`);
581
+ throw err;
582
+ }
583
+ };
584
+ //#endregion
585
+ export { resolveNeonEnvVars as a, resolveDevEnv as i, MissingBranchContextError as n, ServiceNotOnBranchError as r, DevEnvMismatchError as t };