neon 8.11.1 → 8.11.2

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/_chunks/api-GbEaBZJ5.js +388 -0
  3. package/dist/_chunks/api_keys-CNnnugbQ.js +281 -0
  4. package/dist/_chunks/ask-DVwy0eUL.js +155 -0
  5. package/dist/_chunks/auth-CIUtXByB.js +495 -0
  6. package/dist/_chunks/bootstrap-wPZWa1sT.js +499 -0
  7. package/dist/_chunks/branches-BNa1xX1L.js +460 -0
  8. package/dist/_chunks/bucket-BCdryTkD.js +409 -0
  9. package/dist/_chunks/checkout-CXIOydqZ.js +348 -0
  10. package/dist/_chunks/claim-DqAvAvDm.js +473 -0
  11. package/dist/_chunks/config-BH_qRHiG.js +1113 -0
  12. package/dist/_chunks/connection_string-DB4awPzZ.js +154 -0
  13. package/dist/_chunks/credentials-BSjE9fRR.js +146 -0
  14. package/dist/_chunks/data_api-Djxj5ZQD.js +263 -0
  15. package/dist/_chunks/databases-Dok4wru7.js +84 -0
  16. package/dist/_chunks/deploy-D7B0Xfjg.js +33 -0
  17. package/dist/_chunks/dev-CSvxAecE.js +945 -0
  18. package/dist/_chunks/diff-DhxxbdQE.js +178 -0
  19. package/dist/_chunks/docs-CrVybkTP.js +197 -0
  20. package/dist/_chunks/env-5HS5qQGd.js +284 -0
  21. package/dist/_chunks/feedback-CMayyTa8.js +88 -0
  22. package/dist/_chunks/functions-Q0zuvtnk.js +464 -0
  23. package/dist/_chunks/git-H32TNt6M.js +356 -0
  24. package/dist/_chunks/init-BkA6RilG.js +111 -0
  25. package/dist/_chunks/inspect-g0wekylj.js +90 -0
  26. package/dist/_chunks/ip_allow-5lLFvIuE.js +106 -0
  27. package/dist/_chunks/link-BgtkOBws.js +936 -0
  28. package/dist/_chunks/logs-DWkpjzIH.js +291 -0
  29. package/dist/_chunks/mcp-Bo-7TKc7.js +253 -0
  30. package/dist/_chunks/neon_auth-CBs1_3qu.js +822 -0
  31. package/dist/_chunks/open-CX3BT3Tp.js +33 -0
  32. package/dist/_chunks/operations-D4I-AxVs.js +37 -0
  33. package/dist/_chunks/orgs-xGfANwHQ.js +52 -0
  34. package/dist/_chunks/plugins-DqD7BDO1.js +169 -0
  35. package/dist/_chunks/profile-yXOiKk9R.js +672 -0
  36. package/dist/_chunks/projects-BwI2Uo0z.js +439 -0
  37. package/dist/_chunks/psql-BM-doe8e.js +64 -0
  38. package/dist/_chunks/realtime-BRqIyGR-.js +79 -0
  39. package/dist/_chunks/roles-B4w3YT_7.js +79 -0
  40. package/dist/_chunks/set_context-DbrwoImy.js +38 -0
  41. package/dist/_chunks/skills-DyiBXKS3.js +255 -0
  42. package/dist/_chunks/snapshots-RJBzW8GM.js +338 -0
  43. package/dist/_chunks/status-DV8Z2kw3.js +44 -0
  44. package/dist/_chunks/triggers-BrRo1z4b.js +302 -0
  45. package/dist/_chunks/user-dlOGXbX9.js +60 -0
  46. package/dist/_chunks/vpc_endpoints-CNb0YfMo.js +105 -0
  47. package/dist/auth_recovery.js +1 -1
  48. package/dist/commands/api.js +1 -388
  49. package/dist/commands/api_keys.js +1 -281
  50. package/dist/commands/ask.js +1 -155
  51. package/dist/commands/auth.js +1 -495
  52. package/dist/commands/bootstrap.js +1 -499
  53. package/dist/commands/branches.js +1 -460
  54. package/dist/commands/bucket.js +1 -409
  55. package/dist/commands/checkout.js +1 -348
  56. package/dist/commands/claim.js +1 -473
  57. package/dist/commands/config.js +1 -1113
  58. package/dist/commands/connection_string.js +1 -154
  59. package/dist/commands/credentials.js +1 -146
  60. package/dist/commands/data_api.js +1 -263
  61. package/dist/commands/databases.js +1 -84
  62. package/dist/commands/deploy.js +1 -33
  63. package/dist/commands/dev.js +1 -945
  64. package/dist/commands/diff.js +1 -178
  65. package/dist/commands/docs.js +1 -197
  66. package/dist/commands/env.js +1 -284
  67. package/dist/commands/feedback.js +1 -88
  68. package/dist/commands/functions.js +1 -464
  69. package/dist/commands/functions_new.js +1 -1
  70. package/dist/commands/git.js +1 -356
  71. package/dist/commands/index.js +45 -45
  72. package/dist/commands/init.js +1 -111
  73. package/dist/commands/inspect.js +1 -90
  74. package/dist/commands/ip_allow.js +1 -106
  75. package/dist/commands/link.js +1 -936
  76. package/dist/commands/logs.js +1 -291
  77. package/dist/commands/mcp.js +1 -253
  78. package/dist/commands/neon_auth.js +1 -822
  79. package/dist/commands/open.js +1 -33
  80. package/dist/commands/operations.js +1 -37
  81. package/dist/commands/orgs.js +1 -52
  82. package/dist/commands/plugins.js +1 -169
  83. package/dist/commands/profile.js +1 -672
  84. package/dist/commands/projects.js +1 -439
  85. package/dist/commands/psql.js +1 -64
  86. package/dist/commands/realtime.js +1 -79
  87. package/dist/commands/roles.js +1 -79
  88. package/dist/commands/set_context.js +1 -38
  89. package/dist/commands/skills.js +1 -255
  90. package/dist/commands/snapshots.js +1 -338
  91. package/dist/commands/status.js +1 -44
  92. package/dist/commands/subscribe.js +1 -1
  93. package/dist/commands/triggers.js +1 -302
  94. package/dist/commands/user.js +1 -60
  95. package/dist/commands/vpc_endpoints.js +1 -105
  96. package/dist/functions/registry.js +29 -151
  97. package/dist/functions/source.js +42 -2
  98. package/dist/index.js +1 -1
  99. package/dist/init/auth.js +3 -3
  100. package/dist/init/link.js +2 -2
  101. package/dist/init/run.js +3 -3
  102. package/dist/init/tooling.js +3 -3
  103. package/dist/mcp/mint.js +1 -1
  104. package/dist/test_utils/registry.js +1 -2
  105. package/package.json +8 -6
@@ -0,0 +1,945 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { log } from "../log.js";
3
+ import { isClaimableEnvTarget } from "../claimable/state.js";
4
+ import { getCliName } from "../utils/cli_name.js";
5
+ import { defaultDevEnvFiles, readEnvFile, resolveEnvFilePath } from "../env_file.js";
6
+ import { l as functionBaseUrlKey } from "./env-BpP3Dqpe.js";
7
+ import { branchIdResolve } from "../utils/enrichers.js";
8
+ import { bundleEntry } from "../utils/esbuild.js";
9
+ import { a as resolveDevEnv } from "./env-BNm69Qh8.js";
10
+ import { f as envFlag } from "./config-BH_qRHiG.js";
11
+ import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
12
+ import { resolveWatchInputs } from "../dev/inputs.js";
13
+ import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
14
+ import { basename, dirname, join, relative, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import chalk from "chalk";
17
+ import { spawn, spawnSync } from "node:child_process";
18
+ import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry } from "@neon/config-runtime";
19
+ import { once } from "node:events";
20
+ import { createServer } from "node:net";
21
+ //#region src/commands/dev.ts
22
+ var dev_exports = /* @__PURE__ */ __exportAll({
23
+ builder: () => builder,
24
+ captureShellEnv: () => captureShellEnv,
25
+ command: () => "dev",
26
+ describe: () => describe,
27
+ devBundleDir: () => devBundleDir,
28
+ devEnvContext: () => devEnvContext,
29
+ diffUnits: () => diffUnits,
30
+ formatEnvFiles: () => formatEnvFiles,
31
+ formatEnvSummary: () => formatEnvSummary,
32
+ handler: () => handler,
33
+ loadDevEnvFiles: () => loadDevEnvFiles,
34
+ localFunctionUrlEnv: () => localFunctionUrlEnv,
35
+ planFunctionsToUnits: () => planFunctionsToUnits,
36
+ reportDevCredential: () => reportDevCredential,
37
+ sourceFunctionUrlOverlay: () => sourceFunctionUrlOverlay
38
+ });
39
+ const command = "dev";
40
+ const describe = "Run Neon Functions locally with a dev server";
41
+ const builder = (argv) => argv.usage("$0 dev [--source <path>] [options]").example("$0 dev --source ./functions/hello.ts", "Serve one function on a free port with hot reload").example("$0 dev", "Serve every function declared in neon.ts (one dev server each)").example("$0 dev --source ./functions/hello.ts --port 3000", "Serve one function on an explicit port (fails if the port is taken)").options({
42
+ source: {
43
+ describe: "Path to a single function entry module. Omit to serve every function declared in neon.ts.",
44
+ type: "string"
45
+ },
46
+ port: {
47
+ describe: "Port to listen on (single-function mode only, with --source). Fails if taken. Without it (and without a PORT env var) a free port is chosen automatically.",
48
+ type: "number"
49
+ },
50
+ ...envFlag
51
+ }).epilogue([
52
+ "",
53
+ "Functions run with the linked branch's Neon env injected, the same set the",
54
+ "deployed runtime gives them: DATABASE_URL, plus Neon Auth, the Data API,",
55
+ "object storage and the AI Gateway where the branch has them. A neon.ts in",
56
+ "this directory decides instead, exactly as it does for `env pull`.",
57
+ "",
58
+ "NEON_FUNCTION_<SLUG>_BASE_URL from `env pull` / `neon-env run` is the production",
59
+ "invocation URL. `dev` rewrites it to `http://localhost:<port>` for each function",
60
+ "this process is serving (every neon.ts function, or the matching slug when",
61
+ "`--source` names one).",
62
+ "",
63
+ "Before neon.ts is evaluated, `dev` loads the project's .env, then .env.local,",
64
+ "which overrides it, into the environment, so Function env values that read",
65
+ "process.env resolve. A variable already set in the shell wins. `--env <file>`",
66
+ "loads only that file instead. Functions inherit every loaded variable.",
67
+ "",
68
+ "`dev` reuses the branch credential behind the AI Gateway and object storage from",
69
+ "the file `env pull` writes, and never writes to it. With no such file it issues a",
70
+ "credential on every start, so run `env pull` once if you restart often."
71
+ ].join("\n")).strict();
72
+ /**
73
+ * The resolver context for a `neon dev` run.
74
+ *
75
+ * Two things here are what make local dev match the deployed runtime, which injects a
76
+ * branch's whole env into a function:
77
+ *
78
+ * - **The AI Gateway is asked for**, like `env pull` does, because nothing can detect it.
79
+ * Without this, a function that works deployed fails locally with no `NEON_AI_GATEWAY_*`,
80
+ * which is exactly the difference `dev` exists to eliminate.
81
+ * - **The local dotenv file is layered in**, so the branch credential behind the gateway and
82
+ * object storage is *reused* rather than re-minted. `dev` writes no file of its own, so
83
+ * without a source of persisted secrets every start would mint a credential and leave the
84
+ * last one live — one orphan per restart. `neon-env run` already reads the file for this
85
+ * reason; `dev` was the one that didn't.
86
+ */
87
+ const devEnvContext = (props, branchId, cwd, baseEnv = process.env) => {
88
+ const location = {
89
+ cwd,
90
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
91
+ };
92
+ const envFile = resolveEnvFilePath(location);
93
+ return {
94
+ ...location,
95
+ implyAiGateway: true,
96
+ env: {
97
+ ...baseEnv,
98
+ ...existsSync(envFile) ? readEnvFile(envFile) : {}
99
+ },
100
+ ...props.projectId ? { projectId: props.projectId } : {},
101
+ ...branchId ? { branchId } : {},
102
+ ...props.apiKey ? { apiKey: props.apiKey } : {},
103
+ ...props.apiHost ? { apiHost: props.apiHost } : {},
104
+ ...isClaimableEnvTarget({
105
+ apiHost: props.apiHost,
106
+ contextFile: props.contextFile ?? "",
107
+ configDir: props.configDir ?? ""
108
+ }) ? { claimable: true } : {}
109
+ };
110
+ };
111
+ /**
112
+ * Say when this run obtained credential secrets it did not already hold.
113
+ *
114
+ * `dev` writes no file. With platform defaults, each start re-reveals the same secrets until
115
+ * `env pull` persists them. In a region with no defaults, each start mints another credential
116
+ * and cannot revoke the last one. The note names the one action that stops either loop.
117
+ */
118
+ const reportDevCredential = (credential) => {
119
+ if (!credential?.issued) return;
120
+ if (credential.revoked.length > 0) {
121
+ log.info("Wrote credential secrets — %s changed. Revoked the leftover it replaced (%s).", credential.keys.join(", "), credential.revoked.join(", "));
122
+ return;
123
+ }
124
+ log.warning(`Resolved credential secrets for this run (%s) without writing them to a file. Run \`${getCliName()} env pull\` once so restarts reuse them instead of revealing or minting again.`, credential.keys.join(", "));
125
+ };
126
+ const handler = async (props) => {
127
+ const shellEnv = captureShellEnv();
128
+ const envFiles = formatEnvFiles(loadDevEnvFiles(props, process.cwd()), process.cwd());
129
+ if (props.source !== void 0) {
130
+ await runSingleSource(props, shellEnv, envFiles);
131
+ return;
132
+ }
133
+ if (props.port !== void 0) throw new Error("--port can only be used with --source. To set ports for the functions in neon.ts, give each one a `dev.port` in its config.");
134
+ await runFromConfig(props, shellEnv, envFiles);
135
+ };
136
+ /**
137
+ * Copy of the shell env before any dotenv file loads. PORT is read through `process.env`,
138
+ * which is case-insensitive on Windows, so a shell `Port` still sets the port. Exported for
139
+ * unit testing.
140
+ */
141
+ const captureShellEnv = () => ({
142
+ ...process.env,
143
+ PORT: process.env.PORT
144
+ });
145
+ /**
146
+ * Load dotenv files into process.env before neon.ts is evaluated: `--env <file>` alone
147
+ * (relative to cwd), else the project's {@link defaultDevEnvFiles}. A later file overrides
148
+ * an earlier one; a key set before loading (the shell) is never overridden.
149
+ */
150
+ const loadDevEnvFiles = (props, cwd) => {
151
+ const files = props.env ? [resolve(cwd, props.env)] : defaultDevEnvFiles({
152
+ cwd,
153
+ ...props.contextFile ? { contextFile: props.contextFile } : {}
154
+ });
155
+ const name = (key) => process.platform === "win32" ? key.toUpperCase() : key;
156
+ const preset = new Set(Object.keys(process.env).filter((key) => process.env[key] !== void 0).map(name));
157
+ return files.map((path) => {
158
+ const applied = [];
159
+ for (const [key, value] of Object.entries(readEnvFile(path))) {
160
+ if (preset.has(name(key))) continue;
161
+ process.env[key] = value;
162
+ applied.push(key);
163
+ }
164
+ return {
165
+ path,
166
+ applied
167
+ };
168
+ });
169
+ };
170
+ /**
171
+ * The banner's account of the dotenv files, in load order, e.g.
172
+ * `.env.local (4 vars) → .env (2 vars)`. Counts are the keys each file set. Exported for
173
+ * unit testing.
174
+ */
175
+ const formatEnvFiles = (files, cwd) => {
176
+ if (files.length === 0) return "none (no .env.local or .env)";
177
+ return files.map(({ path, applied }) => {
178
+ const count = `${applied.length} var${applied.length === 1 ? "" : "s"}`;
179
+ return `${relative(cwd, path) || path} (${count})`;
180
+ }).join(" → ");
181
+ };
182
+ /** Single-function mode: serve exactly the `--source` path (legacy behavior). */
183
+ const runSingleSource = async (props, shellEnv, envFiles) => {
184
+ const source = resolve(process.cwd(), props.source);
185
+ if (!existsSync(source)) throw new Error(`Source file not found: ${source}`);
186
+ const branchId = await resolveBranchId(props);
187
+ const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd(), shellEnv));
188
+ reportDevCredential(credential);
189
+ const match = await findConfigFunctionBySource(process.cwd(), source, props.contextFile);
190
+ const { overlay, port } = await sourceFunctionUrlOverlay(match, props.port, shellEnv);
191
+ const unit = {
192
+ slug: null,
193
+ source,
194
+ bundleDir: devBundleDir(process.cwd()),
195
+ childEnv: buildChildEnv({
196
+ ...neonEnv,
197
+ ...overlay
198
+ }, port),
199
+ label: null,
200
+ envSummary: {
201
+ neon: Object.keys(neonEnv),
202
+ fn: []
203
+ }
204
+ };
205
+ await runSupervisor([unit], {
206
+ envFiles,
207
+ ...skipped ? { envNote: skipped.reason } : {}
208
+ });
209
+ };
210
+ /**
211
+ * Multi-function mode: serve every function declared in neon.ts. Requires a neon.ts
212
+ * (there is no single source to fall back on), one dev server per function.
213
+ */
214
+ const runFromConfig = async (props, shellEnv, envFiles) => {
215
+ const branchId = await resolveBranchId(props);
216
+ const resolved = await resolveFunctionsFromConfig(process.cwd(), void 0, props.contextFile);
217
+ if (resolved === null) throw new Error("No --source given and no neon.ts found. Pass --source <path> to run a single function, or add a neon.ts that declares functions under `functions`.");
218
+ const { configPath, functions } = resolved;
219
+ if (functions.length === 0) throw new Error("neon.ts has no functions to serve. Add at least one under `functions`, or pass --source <path>.");
220
+ const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd(), shellEnv));
221
+ reportDevCredential(credential);
222
+ const units = await planFunctionsToUnits(functions, neonEnv, DEFAULT_PORT_BASE);
223
+ const replan = async (searchBase, keepPorts) => {
224
+ const re = await resolveFunctionsFromConfig(process.cwd(), void 0, props.contextFile);
225
+ if (re === null) return null;
226
+ return planFunctionsToUnits(re.functions, neonEnv, searchBase, keepPorts);
227
+ };
228
+ await runSupervisor(units, {
229
+ reload: {
230
+ configPath,
231
+ replan
232
+ },
233
+ envFiles,
234
+ ...skipped ? { envNote: skipped.reason } : {}
235
+ });
236
+ };
237
+ /** Ports are chosen before spawn so every sibling's localhost URL can be overlaid. */
238
+ const planFunctionsToUnits = async (functions, neonEnv, searchBase, keepPorts = /* @__PURE__ */ new Map()) => {
239
+ const reserved = /* @__PURE__ */ new Set();
240
+ const portBySlug = /* @__PURE__ */ new Map();
241
+ for (const fn of functions) {
242
+ if (fn.port === void 0) continue;
243
+ portBySlug.set(fn.slug, fn.port);
244
+ reserved.add(fn.port);
245
+ }
246
+ for (const fn of functions) {
247
+ if (portBySlug.has(fn.slug)) continue;
248
+ const kept = keepPorts.get(fn.slug);
249
+ if (kept !== void 0 && !reserved.has(kept)) {
250
+ portBySlug.set(fn.slug, kept);
251
+ reserved.add(kept);
252
+ }
253
+ }
254
+ let searchOffset = 0;
255
+ for (const fn of functions) {
256
+ if (portBySlug.has(fn.slug)) continue;
257
+ const port = await pickFreePort(searchBase + searchOffset, reserved);
258
+ portBySlug.set(fn.slug, port);
259
+ reserved.add(port);
260
+ searchOffset = port - searchBase + 1;
261
+ }
262
+ const assigned = functions.map((fn) => {
263
+ const port = portBySlug.get(fn.slug);
264
+ if (port === void 0) throw new Error(`planFunctionsToUnits: no port for ${fn.slug}`);
265
+ return {
266
+ fn,
267
+ port
268
+ };
269
+ });
270
+ const overlay = localFunctionUrlEnv(assigned.map(({ fn, port }) => ({
271
+ slug: fn.slug,
272
+ port
273
+ })));
274
+ const branchEnv = {
275
+ ...neonEnv,
276
+ ...overlay
277
+ };
278
+ return assigned.map(({ fn, port }) => plannedToUnit(fn, branchEnv, {
279
+ mode: "explicit",
280
+ port
281
+ }, overlay));
282
+ };
283
+ /**
284
+ * Local invocation URLs for functions `neon dev` is serving, so siblings call each
285
+ * other on localhost instead of the production compute host.
286
+ */
287
+ const localFunctionUrlEnv = (functions) => {
288
+ const out = {};
289
+ for (const fn of functions) out[functionBaseUrlKey(fn.slug)] = `http://localhost:${fn.port}`;
290
+ return out;
291
+ };
292
+ /**
293
+ * `--source` serves one file. If that file is a neon.ts function, rewrite only that
294
+ * slug to localhost — this process is not listening for the others.
295
+ */
296
+ const sourceFunctionUrlOverlay = async (match, cliPort, shellEnv = process.env) => {
297
+ const portSpec = portFromProps(cliPort, shellEnv);
298
+ if (match === void 0) return {
299
+ overlay: {},
300
+ port: portSpec
301
+ };
302
+ const port = portSpec.mode === "explicit" ? portSpec.port : match.port ?? await pickFreePort(portSpec.from);
303
+ return {
304
+ overlay: localFunctionUrlEnv([{
305
+ slug: match.slug,
306
+ port
307
+ }]),
308
+ port: {
309
+ mode: "explicit",
310
+ port
311
+ }
312
+ };
313
+ };
314
+ const pickFreePort = (from, reserved = /* @__PURE__ */ new Set()) => new Promise((resolvePort, reject) => {
315
+ const tryPort = (port) => {
316
+ if (port > 65535) {
317
+ reject(/* @__PURE__ */ new Error(`No free port at or above ${from}`));
318
+ return;
319
+ }
320
+ if (reserved.has(port)) {
321
+ tryPort(port + 1);
322
+ return;
323
+ }
324
+ const server = createServer();
325
+ server.once("error", (err) => {
326
+ if (err.code === "EADDRINUSE") {
327
+ tryPort(port + 1);
328
+ return;
329
+ }
330
+ reject(err);
331
+ });
332
+ server.listen(port, "127.0.0.1", () => {
333
+ const addr = server.address();
334
+ const bound = typeof addr === "object" && addr !== null ? addr.port : port;
335
+ server.close((closeErr) => {
336
+ if (closeErr) reject(closeErr);
337
+ else resolvePort(bound);
338
+ });
339
+ });
340
+ };
341
+ tryPort(from);
342
+ });
343
+ /**
344
+ * Resolve the selected branch id from props, if any. Best-effort: a failure here only
345
+ * means env injection is skipped, so it never throws.
346
+ */
347
+ const resolveBranchId = async (props) => {
348
+ if (!props.apiClient || !props.projectId) return void 0;
349
+ const branch = props.branch ?? props.id;
350
+ if (!branch) return void 0;
351
+ try {
352
+ return await branchIdResolve({
353
+ branch,
354
+ apiClient: props.apiClient,
355
+ projectId: props.projectId
356
+ });
357
+ } catch (err) {
358
+ log.debug("dev: could not resolve branch id: %s", err instanceof Error ? err.message : String(err));
359
+ return;
360
+ }
361
+ };
362
+ const DEFAULT_PORT_BASE = 8787;
363
+ const portFromProps = (port, shellEnv) => {
364
+ if (port !== void 0) return {
365
+ mode: "explicit",
366
+ port
367
+ };
368
+ if (shellEnv.PORT !== void 0 && shellEnv.PORT !== "") return {
369
+ mode: "explicit",
370
+ port: Number(shellEnv.PORT)
371
+ };
372
+ return {
373
+ mode: "search",
374
+ from: DEFAULT_PORT_BASE
375
+ };
376
+ };
377
+ /**
378
+ * Where a locally-served function's bundle is written.
379
+ *
380
+ * The location inside the project's own `node_modules` is load-bearing, not a tidiness
381
+ * choice. A function's `externalPackages` are left unbundled, and Node resolves an unbundled
382
+ * import by walking up from the importing file — so from here it reaches the project's real
383
+ * `node_modules` and finds them, at the host architecture that can actually run locally.
384
+ * Moving this directory anywhere outside `node_modules` breaks `neon dev` for those
385
+ * functions with a `Cannot find module`, so the path is pinned by a test.
386
+ */
387
+ const devBundleDir = (cwd, slug) => slug === void 0 ? join(cwd, "node_modules", ".neon-dev") : join(cwd, "node_modules", ".neon-dev", slug);
388
+ /**
389
+ * Translate a {@link PlannedFunction} into a {@link ServedUnit}. The bind port is
390
+ * already chosen by the caller so sibling localhost URLs can be overlaid first.
391
+ * Per-function neon.ts env layers over the shared branch env.
392
+ */
393
+ const plannedToUnit = (fn, branchEnv, port, overlay = {}) => {
394
+ const childEnv = buildChildEnv({
395
+ ...branchEnv,
396
+ ...fn.env
397
+ }, port);
398
+ return {
399
+ slug: fn.slug,
400
+ source: fn.source,
401
+ bundleDir: devBundleDir(process.cwd(), fn.slug),
402
+ childEnv,
403
+ label: fn.slug,
404
+ envSummary: {
405
+ neon: Object.keys(branchEnv),
406
+ fn: Object.keys(fn.env)
407
+ },
408
+ ...fn.externalPackages ? { externalPackages: fn.externalPackages } : {},
409
+ ...fn.bundler !== void 0 ? { bundler: fn.bundler } : {},
410
+ configKey: JSON.stringify({
411
+ source: fn.source,
412
+ port: fn.port ?? null,
413
+ env: fn.env,
414
+ overlay,
415
+ externalPackages: fn.externalPackages ?? null,
416
+ bundler: typeof fn.bundler === "function" ? "custom" : fn.bundler ?? null
417
+ })
418
+ };
419
+ };
420
+ /**
421
+ * Build a child's env. Neon vars layer over the inherited environment (so the branch's
422
+ * DATABASE_URL wins over a stale inherited value); a function that loads its own `.env`
423
+ * at runtime still overrides them. The port spec is encoded for the runtime via
424
+ * NEON_DEV_PORT (explicit) or NEON_DEV_PORT_BASE (search).
425
+ */
426
+ const buildChildEnv = (neonEnv, port) => {
427
+ const env = {
428
+ ...process.env,
429
+ ...neonEnv
430
+ };
431
+ delete env.NEON_DEV_PORT;
432
+ delete env.NEON_DEV_PORT_BASE;
433
+ if (port.mode === "explicit") env.NEON_DEV_PORT = String(port.port);
434
+ else if (port.mode === "search") env.NEON_DEV_PORT_BASE = String(port.from);
435
+ return env;
436
+ };
437
+ const READY_PATTERN = /neon-dev:ready (\d+)/;
438
+ /**
439
+ * Supervise one or more {@link ServedUnit}s: bundle + start each in its own child, watch
440
+ * its inputs for hot reload, and tear everything down cleanly on shutdown. Units are
441
+ * independent — one crashing or failing to start does not stop the others (it is shown
442
+ * as errored and recovered on the next edit). A single SIGINT/SIGTERM shuts all of them
443
+ * down, tree-killing each child so no descendant it spawned is orphaned.
444
+ *
445
+ * In config mode, `reload` lets the supervisor watch `neon.ts` and reconcile the live set
446
+ * of units when it changes: a newly-declared function is hot-added (its own child, watcher,
447
+ * and port) and a removed one is torn down — all without disturbing the functions that
448
+ * stayed the same. A function whose config (env/port/source) changed is restarted
449
+ * in place; siblings are untouched.
450
+ */
451
+ const runSupervisor = async (units, options = {}) => {
452
+ const { reload, envNote, envFiles } = options;
453
+ const runtimePath = resolveRuntimePath();
454
+ let shuttingDown = false;
455
+ const running = units.map(makeRunningUnit);
456
+ const bundleAndStart = async (r) => {
457
+ let bundlePath;
458
+ try {
459
+ bundlePath = await writeBundle(r.unit.source, r.unit.bundleDir, r.unit.externalPackages, r.unit.slug ?? void 0, r.unit.bundler);
460
+ } catch (err) {
461
+ r.status = "error";
462
+ logUnit(r.unit, chalk.red("bundle failed: ") + (err instanceof Error ? err.message : String(err)));
463
+ return;
464
+ }
465
+ if (r.watcher) await r.watcher.sync();
466
+ const next = spawnChild(r.unit, runtimePath, bundlePath);
467
+ r.child = next;
468
+ const ready = waitForReady(next);
469
+ pipeChildOutput(next, r.unit.label);
470
+ next.on("exit", (code, signal) => {
471
+ if (shuttingDown || r.child !== next) return;
472
+ if (signal) {
473
+ log.debug("runtime for %s exited via %s", r.unit.slug ?? "(source)", signal);
474
+ return;
475
+ }
476
+ if (code && code !== 0 && r.everReady) {
477
+ r.status = "error";
478
+ logUnit(r.unit, chalk.red(`exited with code ${code} (waiting for a change)`));
479
+ }
480
+ });
481
+ const port = await ready;
482
+ if (port !== null) {
483
+ r.boundPort = port;
484
+ r.everReady = true;
485
+ r.status = "ready";
486
+ } else r.status = "error";
487
+ };
488
+ const restart = (r) => {
489
+ if (shuttingDown) return;
490
+ if (r.restartTimer) clearTimeout(r.restartTimer);
491
+ r.restartTimer = setTimeout(() => {
492
+ (async () => {
493
+ logUnit(r.unit, chalk.dim("change detected, restarting…"));
494
+ if (r.child) await killTree(r.child);
495
+ if (shuttingDown) return;
496
+ await bundleAndStart(r);
497
+ if (r.status === "ready") logUnit(r.unit, chalk.green("ready") + ` ${urlFor(r.boundPort)}`);
498
+ })();
499
+ }, 150);
500
+ };
501
+ const startUnit = async (r) => {
502
+ r.watcher = await startWatcher(r.unit, () => {
503
+ restart(r);
504
+ });
505
+ await bundleAndStart(r);
506
+ };
507
+ const stopUnit = async (r) => {
508
+ if (r.restartTimer) clearTimeout(r.restartTimer);
509
+ await r.watcher?.close();
510
+ if (r.child) await killTree(r.child);
511
+ rmSync(r.unit.bundleDir, {
512
+ recursive: true,
513
+ force: true
514
+ });
515
+ };
516
+ await Promise.all(running.map((r) => startUnit(r)));
517
+ if (running.every((r) => r.status === "error")) {
518
+ await Promise.all(running.map((r) => stopUnit(r)));
519
+ throw new Error("No function started. See the output above for details.");
520
+ }
521
+ printBanner(running, envNote, envFiles);
522
+ let configWatcher = null;
523
+ if (reload) {
524
+ const ops = {
525
+ isShuttingDown: () => shuttingDown,
526
+ startUnit,
527
+ stopUnit,
528
+ restartUnit: restart
529
+ };
530
+ let inFlight = null;
531
+ let pending = false;
532
+ const drive = () => {
533
+ if (inFlight) {
534
+ pending = true;
535
+ return;
536
+ }
537
+ inFlight = (async () => {
538
+ do {
539
+ pending = false;
540
+ await reconcileOnce(running, reload.replan, ops);
541
+ } while (pending && !shuttingDown);
542
+ })().finally(() => {
543
+ inFlight = null;
544
+ });
545
+ };
546
+ configWatcher = await startConfigWatcher(reload.configPath, drive);
547
+ }
548
+ await new Promise((resolveRun) => {
549
+ const shutdown = () => {
550
+ if (shuttingDown) return;
551
+ shuttingDown = true;
552
+ (async () => {
553
+ await configWatcher?.close();
554
+ await Promise.all(running.map((r) => stopUnit(r)));
555
+ log.info(chalk.dim("Stopped the dev server."));
556
+ resolveRun();
557
+ })();
558
+ };
559
+ process.on("SIGINT", shutdown);
560
+ process.on("SIGTERM", shutdown);
561
+ });
562
+ };
563
+ const makeRunningUnit = (unit) => ({
564
+ unit,
565
+ child: null,
566
+ boundPort: null,
567
+ everReady: false,
568
+ restartTimer: null,
569
+ watcher: null,
570
+ status: "starting"
571
+ });
572
+ /**
573
+ * Pure slug-keyed diff of the live units against the freshly-resolved desired set:
574
+ * - a slug present now but not before → **add** (new child + watcher + port),
575
+ * - a slug gone from neon.ts → **remove** (torn down),
576
+ * - a slug whose config (source/port/env) changed → **restart** in place,
577
+ * - an unchanged slug → left out of the plan entirely (never touched).
578
+ * Functions that stayed the same never die, so an edit that only adds a function is
579
+ * non-disruptive. `desired === null` (neon.ts deleted) is treated as "no functions".
580
+ */
581
+ const diffUnits = (running, desired) => {
582
+ const desiredBySlug = /* @__PURE__ */ new Map();
583
+ for (const u of desired ?? []) if (u.slug !== null) desiredBySlug.set(u.slug, u);
584
+ const runningBySlug = /* @__PURE__ */ new Map();
585
+ for (const r of running) if (r.unit.slug !== null) runningBySlug.set(r.unit.slug, r);
586
+ const plan = {
587
+ remove: [],
588
+ restart: [],
589
+ add: []
590
+ };
591
+ for (const [slug, r] of runningBySlug) if (!desiredBySlug.has(slug)) plan.remove.push(r);
592
+ for (const [slug, want] of desiredBySlug) {
593
+ const r = runningBySlug.get(slug);
594
+ if (r) {
595
+ if (r.unit.configKey !== want.configKey) {
596
+ r.unit = want;
597
+ plan.restart.push(r);
598
+ }
599
+ } else plan.add.push(want);
600
+ }
601
+ return plan;
602
+ };
603
+ /**
604
+ * Run one reconcile: re-resolve neon.ts (ignoring the change with a clear message if it no
605
+ * longer loads), {@link diffUnits} against the live set, then apply the plan — tearing down
606
+ * removed functions, restarting changed ones in place, and hot-adding new ones. Mutates
607
+ * `running` in place so the surrounding supervisor sees the converged set.
608
+ */
609
+ const reconcileOnce = async (running, replan, ops) => {
610
+ if (ops.isShuttingDown()) return;
611
+ let desired;
612
+ try {
613
+ desired = await replan(nextSearchBase(running), keepPortsFromRunning(running));
614
+ } catch (err) {
615
+ log.warning("neon.ts change ignored: " + (err instanceof Error ? err.message : String(err)) + chalk.dim(" (fix it and save again)"));
616
+ return;
617
+ }
618
+ if (ops.isShuttingDown()) return;
619
+ const plan = diffUnits(running, desired);
620
+ for (const r of plan.remove) {
621
+ logUnit(r.unit, chalk.dim("removed from neon.ts, stopping…"));
622
+ await ops.stopUnit(r);
623
+ const idx = running.indexOf(r);
624
+ if (idx !== -1) running.splice(idx, 1);
625
+ }
626
+ for (const r of plan.restart) ops.restartUnit(r);
627
+ if (plan.add.length > 0) {
628
+ const added = plan.add.map((unit) => {
629
+ const r = makeRunningUnit(unit);
630
+ running.push(r);
631
+ logUnit(unit, chalk.dim("added in neon.ts, starting…"));
632
+ return r;
633
+ });
634
+ await Promise.all(added.map((r) => ops.startUnit(r)));
635
+ for (const r of added) if (r.status === "ready") {
636
+ const env = formatEnvSummary(r.unit.envSummary);
637
+ logUnit(r.unit, chalk.green("ready") + ` ${urlFor(r.boundPort)}` + (env ? chalk.dim(` ${env}`) : ""));
638
+ }
639
+ }
640
+ };
641
+ const keepPortsFromRunning = (running) => {
642
+ const keep = /* @__PURE__ */ new Map();
643
+ for (const r of running) {
644
+ if (r.unit.slug === null) continue;
645
+ const bound = r.boundPort;
646
+ if (bound !== null) {
647
+ keep.set(r.unit.slug, bound);
648
+ continue;
649
+ }
650
+ const pending = Number(r.unit.childEnv.NEON_DEV_PORT);
651
+ if (Number.isFinite(pending) && pending > 0) keep.set(r.unit.slug, pending);
652
+ }
653
+ return keep;
654
+ };
655
+ /**
656
+ * Start pickFreePort above every port live units already bound, so a hot-added
657
+ * function is not probed on a port a sibling is serving.
658
+ */
659
+ const nextSearchBase = (running) => {
660
+ let max = 8786;
661
+ for (const r of running) if (r.boundPort !== null && r.boundPort > max) max = r.boundPort;
662
+ return max + 1;
663
+ };
664
+ /**
665
+ * Spawn the child for a unit: the bundled output run directly under `node`.
666
+ *
667
+ * Spawned detached (own process group) so killTree can reap the whole group.
668
+ */
669
+ const spawnChild = (unit, runtimePath, bundlePath) => {
670
+ return spawn(process.execPath, [runtimePath, bundlePath], {
671
+ stdio: [
672
+ "ignore",
673
+ "pipe",
674
+ "pipe"
675
+ ],
676
+ env: unit.childEnv,
677
+ detached: true
678
+ });
679
+ };
680
+ /**
681
+ * Findings already reported for a served unit, so an advisory that cannot change between
682
+ * saves is not reprinted on every one. A dev session on a project with a standing false
683
+ * positive would otherwise repeat the whole block for its lifetime.
684
+ */
685
+ const reportedFindings = /* @__PURE__ */ new Map();
686
+ const writeBundle = async (source, bundleDir, externalPackages, label, bundler) => {
687
+ if (bundler !== void 0 && bundler !== "esbuild") {
688
+ const files = await bundleForDev(source, label, bundler);
689
+ return writeBundleFiles(bundleDir, files);
690
+ }
691
+ const entry = await resolveEsbuildEntry(source);
692
+ const { files, metafile, warnings } = await bundleEntry(entry, { ...externalPackages ? { externalPackages } : {} });
693
+ for (const warning of warnings) log.warning(warning);
694
+ const findings = findUndeclaredNativePackages({
695
+ metafile,
696
+ declared: externalPackages ?? [],
697
+ projectDir: dirname(entry)
698
+ });
699
+ const signature = findings.map((f) => f.name).join(",");
700
+ if (reportedFindings.get(source) !== signature) {
701
+ reportedFindings.set(source, signature);
702
+ for (const finding of findings) log.warning(describeNativeFinding(label ?? basename(source), finding));
703
+ }
704
+ return writeBundleFiles(bundleDir, files);
705
+ };
706
+ const bundleForDev = async (source, label, bundler) => {
707
+ const fn = {
708
+ slug: label ?? basename(source),
709
+ name: label ?? basename(source),
710
+ source,
711
+ env: {},
712
+ runtime: "nodejs24",
713
+ bundler
714
+ };
715
+ return bundler === "none" ? bundleAsIs(fn) : bundler(fn);
716
+ };
717
+ const writeBundleFiles = (bundleDir, files) => {
718
+ rmSync(bundleDir, {
719
+ recursive: true,
720
+ force: true
721
+ });
722
+ mkdirSync(bundleDir, { recursive: true });
723
+ for (const [name, contents] of Object.entries(files)) {
724
+ const dest = join(bundleDir, name);
725
+ mkdirSync(dirname(dest), { recursive: true });
726
+ writeFileSync(dest, contents);
727
+ }
728
+ const entry = ["index.mjs", "index.js"].find((name) => name in files);
729
+ return join(bundleDir, entry ?? "index.mjs");
730
+ };
731
+ const urlFor = (port) => port === null ? chalk.red("not running") : `http://localhost:${port}`;
732
+ const waitForReady = (child) => new Promise((resolveReady) => {
733
+ let settled = false;
734
+ let buffer = "";
735
+ const onData = (chunk) => {
736
+ buffer += chunk.toString();
737
+ const match = READY_PATTERN.exec(buffer);
738
+ if (match && !settled) {
739
+ settled = true;
740
+ child.stdout?.off("data", onData);
741
+ resolveReady(Number(match[1]));
742
+ }
743
+ };
744
+ child.stdout?.on("data", onData);
745
+ child.once("exit", () => {
746
+ if (!settled) {
747
+ settled = true;
748
+ resolveReady(null);
749
+ }
750
+ });
751
+ });
752
+ /**
753
+ * Forward the child's stdout/stderr to the parent, swallowing the machine-readable
754
+ * `neon-dev:ready` line. When `label` is set (multi-function mode), every line is
755
+ * prefixed with `[slug]` so concurrent servers' output stays readable.
756
+ */
757
+ const pipeChildOutput = (child, label) => {
758
+ const prefix = label ? chalk.dim(`[${label}] `) : "";
759
+ const forward = (stream) => {
760
+ let buffer = "";
761
+ child[stream]?.on("data", (chunk) => {
762
+ buffer += chunk.toString();
763
+ const lines = buffer.split("\n");
764
+ buffer = lines.pop() ?? "";
765
+ for (const line of lines) {
766
+ if (READY_PATTERN.test(line)) continue;
767
+ process[stream].write(`${prefix}${line}\n`);
768
+ }
769
+ });
770
+ };
771
+ forward("stdout");
772
+ forward("stderr");
773
+ };
774
+ const printBanner = (running, envNote, envFiles) => {
775
+ log.info("");
776
+ log.info(chalk.green.bold(" Neon Functions dev server"));
777
+ log.info("");
778
+ if (envFiles) {
779
+ log.info(` ${chalk.dim("env files".padEnd(20))} ${chalk.dim(envFiles)}`);
780
+ log.info("");
781
+ }
782
+ for (const r of running) {
783
+ const name = r.unit.label ?? "function";
784
+ const url = urlFor(r.boundPort);
785
+ log.info(` ${chalk.dim(name.padEnd(20))} ${url}`);
786
+ const env = formatEnvSummary(r.unit.envSummary);
787
+ if (env) log.info(` ${" ".repeat(20)} ${chalk.dim(env)}`);
788
+ }
789
+ if (envNote) {
790
+ log.info("");
791
+ log.info(` ${chalk.yellow("!")} ${chalk.dim(`Neon env: ${envNote}`)}`);
792
+ }
793
+ log.info("");
794
+ };
795
+ /**
796
+ * Render a unit's injected env into one transparent line for the banner, e.g.
797
+ * `env: DATABASE_URL, DATABASE_URL_UNPOOLED · neon.ts: RESEND_API_KEY`. Var **names** only
798
+ * (never values — they're secrets). Returns `''` when nothing is injected, so the caller can
799
+ * skip the line. Exported for unit testing.
800
+ */
801
+ const formatEnvSummary = (summary) => {
802
+ if (!summary) return "";
803
+ const parts = [];
804
+ if (summary.neon.length > 0) parts.push(`env: ${[...summary.neon].sort().join(", ")}`);
805
+ if (summary.fn.length > 0) parts.push(`neon.ts: ${[...summary.fn].sort().join(", ")}`);
806
+ return parts.join(" · ");
807
+ };
808
+ const logUnit = (unit, message) => {
809
+ const prefix = unit.label ? chalk.dim(`[${unit.label}] `) : "";
810
+ log.info(`${prefix}${message}`);
811
+ };
812
+ const startWatcher = async (unit, restart) => {
813
+ const { default: chokidar } = await import("chokidar");
814
+ if (unit.bundler !== void 0 && unit.bundler !== "esbuild") {
815
+ const dir = statSync(unit.source, { throwIfNoEntry: false })?.isDirectory() === true ? unit.source : dirname(unit.source);
816
+ return startDirectoryWatcher(chokidar, dir, restart, { ignoreDist: false });
817
+ }
818
+ let entry = unit.source;
819
+ try {
820
+ entry = await resolveEsbuildEntry(unit.source);
821
+ } catch {}
822
+ const initialInputs = await resolveWatchInputs(entry);
823
+ if (initialInputs === null) return startDirectoryWatcher(chokidar, entry, restart, { ignoreDist: true });
824
+ return startInputWatcher(chokidar, entry, initialInputs, restart);
825
+ };
826
+ /**
827
+ * Watch the neon.ts file itself for changes, firing `onChange` on every save. Used by the
828
+ * supervisor (config mode only) to hot-add/remove/restart functions when the declared set
829
+ * changes. We watch the single config file rather than its import graph: editing neon.ts to
830
+ * add or remove a function is the case that matters, and a plain file watch is robust where
831
+ * the esbuild-based input resolution (built for function bundles) is not a fit for a
832
+ * jiti-loaded config.
833
+ */
834
+ const startConfigWatcher = async (configPath, onChange) => {
835
+ const { default: chokidar } = await import("chokidar");
836
+ const watcher = chokidar.watch(configPath, { ignoreInitial: true });
837
+ await once(watcher, "ready");
838
+ watcher.on("all", () => {
839
+ onChange();
840
+ });
841
+ return {
842
+ sync: () => Promise.resolve(),
843
+ close: () => watcher.close()
844
+ };
845
+ };
846
+ const startInputWatcher = async (chokidar, source, initialInputs, restart) => {
847
+ const watched = /* @__PURE__ */ new Set([source, ...initialInputs]);
848
+ const watcher = chokidar.watch([...watched], { ignoreInitial: true });
849
+ await once(watcher, "ready");
850
+ watcher.on("all", () => {
851
+ restart();
852
+ });
853
+ const sync = async () => {
854
+ const next = await resolveWatchInputs(source);
855
+ if (next === null) return;
856
+ const desired = /* @__PURE__ */ new Set([source, ...next]);
857
+ for (const file of desired) if (!watched.has(file)) {
858
+ watcher.add(file);
859
+ watched.add(file);
860
+ }
861
+ for (const file of watched) if (!desired.has(file)) {
862
+ watcher.unwatch(file);
863
+ watched.delete(file);
864
+ }
865
+ };
866
+ return {
867
+ sync,
868
+ close: () => watcher.close()
869
+ };
870
+ };
871
+ const startDirectoryWatcher = async (chokidar, source, restart, options) => {
872
+ const watchedDir = statSync(source, { throwIfNoEntry: false })?.isDirectory() ? source : dirname(source);
873
+ const isIgnored = (p) => {
874
+ const rel = relative(watchedDir, p);
875
+ if (rel === "" || rel === ".") return false;
876
+ if (rel.startsWith("..")) return false;
877
+ const segments = rel.split(/[/\\]/);
878
+ if (segments.includes("node_modules") || segments.includes(".git")) return true;
879
+ return options.ignoreDist && segments.includes("dist");
880
+ };
881
+ const watcher = chokidar.watch(watchedDir, {
882
+ ignoreInitial: true,
883
+ ignored: (path) => isIgnored(path)
884
+ });
885
+ await once(watcher, "ready");
886
+ watcher.on("all", () => {
887
+ restart();
888
+ });
889
+ return {
890
+ sync: () => Promise.resolve(),
891
+ close: () => watcher.close()
892
+ };
893
+ };
894
+ /**
895
+ * Terminate a child and every descendant it spawned. The child is started `detached`, so
896
+ * on POSIX it leads its own process group and a negative-PID signal reaps the group
897
+ * (covering the runtime and anything it spawned). On Windows there are no POSIX groups, so we
898
+ * shell out to `taskkill /T` to kill the tree. Escalates SIGTERM -> SIGKILL after 2s.
899
+ */
900
+ const killTree = (child) => {
901
+ if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
902
+ const pid = child.pid;
903
+ return new Promise((resolveKill) => {
904
+ const timeout = setTimeout(() => {
905
+ forceKill(child, pid);
906
+ }, 2e3);
907
+ child.once("exit", () => {
908
+ clearTimeout(timeout);
909
+ resolveKill();
910
+ });
911
+ if (pid !== void 0 && process.platform !== "win32") try {
912
+ process.kill(-pid, "SIGTERM");
913
+ return;
914
+ } catch {}
915
+ child.kill("SIGTERM");
916
+ });
917
+ };
918
+ const forceKill = (child, pid) => {
919
+ if (pid === void 0) {
920
+ child.kill("SIGKILL");
921
+ return;
922
+ }
923
+ if (process.platform === "win32") {
924
+ spawnSync("taskkill", [
925
+ "/pid",
926
+ String(pid),
927
+ "/T",
928
+ "/F"
929
+ ]);
930
+ return;
931
+ }
932
+ try {
933
+ process.kill(-pid, "SIGKILL");
934
+ } catch {
935
+ child.kill("SIGKILL");
936
+ }
937
+ };
938
+ const resolveRuntimePath = () => {
939
+ const here = dirname(fileURLToPath(import.meta.url));
940
+ const candidate = join(here, "..", "dev", "runtime.js");
941
+ if (!existsSync(candidate)) throw new Error(`Could not locate the dev runtime at ${candidate}`);
942
+ return candidate;
943
+ };
944
+ //#endregion
945
+ export { devBundleDir as a, diffUnits as c, handler as d, loadDevEnvFiles as f, sourceFunctionUrlOverlay as g, reportDevCredential as h, describe as i, formatEnvFiles as l, planFunctionsToUnits as m, captureShellEnv as n, devEnvContext as o, localFunctionUrlEnv as p, command as r, dev_exports as s, builder as t, formatEnvSummary as u };