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.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
package/dist/commands/dev.js
CHANGED
|
@@ -1,798 +1,718 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { once } from "node:events";
|
|
3
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { describeNativeFinding, findUndeclaredNativePackages, } from "@neon/config-runtime";
|
|
7
|
-
import chalk from "chalk";
|
|
8
|
-
import { resolveDevEnv } from "../dev/env.js";
|
|
9
|
-
import { resolveFunctionsFromConfig, } from "../dev/functions.js";
|
|
10
|
-
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
11
|
-
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
1
|
+
import { t as __exportAll } from "../_chunks/rolldown-runtime-D7D4PA-g.js";
|
|
12
2
|
import { log } from "../log.js";
|
|
13
3
|
import { getCliName } from "../utils/cli_name.js";
|
|
4
|
+
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
14
5
|
import { branchIdResolve } from "../utils/enrichers.js";
|
|
15
6
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
7
|
+
import { i as resolveDevEnv } from "../_chunks/env-NbA61JR3.js";
|
|
8
|
+
import { resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
9
|
+
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
10
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
15
|
+
import { describeNativeFinding, findUndeclaredNativePackages } from "@neon/config-runtime";
|
|
16
|
+
import { once } from "node:events";
|
|
17
|
+
//#region src/commands/dev.ts
|
|
18
|
+
var dev_exports = /* @__PURE__ */ __exportAll({
|
|
19
|
+
builder: () => builder,
|
|
20
|
+
command: () => "dev",
|
|
21
|
+
describe: () => describe,
|
|
22
|
+
devBundleDir: () => devBundleDir,
|
|
23
|
+
devEnvContext: () => devEnvContext,
|
|
24
|
+
diffUnits: () => diffUnits,
|
|
25
|
+
formatEnvSummary: () => formatEnvSummary,
|
|
26
|
+
handler: () => handler,
|
|
27
|
+
reportDevCredential: () => reportDevCredential
|
|
28
|
+
});
|
|
29
|
+
const command = "dev";
|
|
30
|
+
const describe = "Run Neon Functions locally with a dev server";
|
|
31
|
+
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({
|
|
32
|
+
source: {
|
|
33
|
+
describe: "Path to a single function entry module. Omit to serve every function declared in neon.ts.",
|
|
34
|
+
type: "string"
|
|
35
|
+
},
|
|
36
|
+
port: {
|
|
37
|
+
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.",
|
|
38
|
+
type: "number"
|
|
39
|
+
}
|
|
40
|
+
}).epilogue([
|
|
41
|
+
"",
|
|
42
|
+
"Functions run with the linked branch's Neon env injected, the same set the",
|
|
43
|
+
"deployed runtime gives them: DATABASE_URL, plus Neon Auth, the Data API,",
|
|
44
|
+
"object storage and the AI Gateway where the branch has them. A neon.ts in",
|
|
45
|
+
"this directory decides instead, exactly as it does for `env pull`.",
|
|
46
|
+
"",
|
|
47
|
+
"`dev` reads your .env / .env.local to reuse the branch credential behind the",
|
|
48
|
+
"AI Gateway and object storage, and never writes to them. With no such file it",
|
|
49
|
+
"issues a credential on every start, so run `env pull` once if you restart often."
|
|
50
|
+
].join("\n")).strict();
|
|
48
51
|
/**
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
52
|
+
* The resolver context for a `neon dev` run.
|
|
53
|
+
*
|
|
54
|
+
* Two things here are what make local dev match the deployed runtime, which injects a
|
|
55
|
+
* branch's whole env into a function:
|
|
56
|
+
*
|
|
57
|
+
* - **The AI Gateway is asked for**, like `env pull` does, because nothing can detect it.
|
|
58
|
+
* Without this, a function that works deployed fails locally with no `NEON_AI_GATEWAY_*`,
|
|
59
|
+
* which is exactly the difference `dev` exists to eliminate.
|
|
60
|
+
* - **The local dotenv file is layered in**, so the branch credential behind the gateway and
|
|
61
|
+
* object storage is *reused* rather than re-minted. `dev` writes no file of its own, so
|
|
62
|
+
* without a source of persisted secrets every start would mint a credential and leave the
|
|
63
|
+
* last one live — one orphan per restart. `neon-env run` already reads the file for this
|
|
64
|
+
* reason; `dev` was the one that didn't.
|
|
65
|
+
*/
|
|
66
|
+
const devEnvContext = (props, branchId, cwd) => {
|
|
67
|
+
const envFile = resolveEnvFilePath(cwd);
|
|
68
|
+
return {
|
|
69
|
+
cwd,
|
|
70
|
+
implyAiGateway: true,
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
...existsSync(envFile) ? readEnvFile(envFile) : {}
|
|
74
|
+
},
|
|
75
|
+
...props.projectId ? { projectId: props.projectId } : {},
|
|
76
|
+
...branchId ? { branchId } : {},
|
|
77
|
+
...props.apiKey ? { apiKey: props.apiKey } : {},
|
|
78
|
+
...props.apiHost ? { apiHost: props.apiHost } : {}
|
|
79
|
+
};
|
|
77
80
|
};
|
|
78
81
|
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
log.warning("Issued a branch credential for this run (%s) and left any previous one live — a dev " +
|
|
95
|
-
`server has nowhere to keep it. Run \`${getCliName()} env pull\` once to write it to ` +
|
|
96
|
-
"your .env, and restarts will reuse it instead of issuing another.", credential.keys.join(", "));
|
|
82
|
+
* Say when a run issued a branch credential.
|
|
83
|
+
*
|
|
84
|
+
* `dev` has nowhere to persist one — it writes no file — so on a branch with nothing to reuse
|
|
85
|
+
* it mints per start and cannot name the previous one to revoke it. Every other command that
|
|
86
|
+
* mints says so; this is the one that runs dozens of times a day, and the server banner listing
|
|
87
|
+
* `NEON_AI_GATEWAY_TOKEN` reads as "fetched", not "just created, and the last one is still
|
|
88
|
+
* live". The note names the one action that stops it, so it disappears once followed.
|
|
89
|
+
*/
|
|
90
|
+
const reportDevCredential = (credential) => {
|
|
91
|
+
if (!credential?.issued) return;
|
|
92
|
+
if (credential.revoked.length > 0) {
|
|
93
|
+
log.info("Issued a new branch credential — %s changed. Revoked the one it replaced (%s).", credential.keys.join(", "), credential.revoked.join(", "));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
log.warning(`Issued a branch credential for this run (%s) and left any previous one live — a dev server has nowhere to keep it. Run \`${getCliName()} env pull\` once to write it to your .env, and restarts will reuse it instead of issuing another.`, credential.keys.join(", "));
|
|
97
97
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (props.port !== undefined) {
|
|
106
|
-
throw new Error("--port can only be used with --source. To set ports for the functions " +
|
|
107
|
-
"in neon.ts, give each one a `dev.port` in its config.");
|
|
108
|
-
}
|
|
109
|
-
await runFromConfig(props);
|
|
98
|
+
const handler = async (props) => {
|
|
99
|
+
if (props.source !== void 0) {
|
|
100
|
+
await runSingleSource(props);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
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.");
|
|
104
|
+
await runFromConfig(props);
|
|
110
105
|
};
|
|
111
106
|
/** Single-function mode: serve exactly the `--source` path (legacy behavior). */
|
|
112
107
|
const runSingleSource = async (props) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
await runSupervisor([unit], {
|
|
131
|
-
...(skipped ? { envNote: skipped.reason } : {}),
|
|
132
|
-
});
|
|
108
|
+
const source = resolve(process.cwd(), props.source);
|
|
109
|
+
if (!existsSync(source)) throw new Error(`Source file not found: ${source}`);
|
|
110
|
+
const branchId = await resolveBranchId(props);
|
|
111
|
+
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd()));
|
|
112
|
+
reportDevCredential(credential);
|
|
113
|
+
const unit = {
|
|
114
|
+
slug: null,
|
|
115
|
+
source,
|
|
116
|
+
bundleDir: devBundleDir(process.cwd()),
|
|
117
|
+
childEnv: buildChildEnv(neonEnv, portFromProps(props.port)),
|
|
118
|
+
label: null,
|
|
119
|
+
envSummary: {
|
|
120
|
+
neon: Object.keys(neonEnv),
|
|
121
|
+
fn: []
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
await runSupervisor([unit], { ...skipped ? { envNote: skipped.reason } : {} });
|
|
133
125
|
};
|
|
134
126
|
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
* Multi-function mode: serve every function declared in neon.ts. Requires a neon.ts
|
|
128
|
+
* (there is no single source to fall back on), one dev server per function.
|
|
129
|
+
*/
|
|
138
130
|
const runFromConfig = async (props) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const re = await resolveFunctionsFromConfig(process.cwd());
|
|
160
|
-
if (re === null)
|
|
161
|
-
return null;
|
|
162
|
-
return planFunctionsToUnits(re.functions, neonEnv, searchBase);
|
|
163
|
-
};
|
|
164
|
-
await runSupervisor(units, {
|
|
165
|
-
reload: { configPath, replan },
|
|
166
|
-
...(skipped ? { envNote: skipped.reason } : {}),
|
|
167
|
-
});
|
|
131
|
+
const branchId = await resolveBranchId(props);
|
|
132
|
+
const resolved = await resolveFunctionsFromConfig(process.cwd());
|
|
133
|
+
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 `preview.functions`.");
|
|
134
|
+
const { configPath, functions } = resolved;
|
|
135
|
+
if (functions.length === 0) throw new Error("neon.ts has no functions to serve. Add at least one under `preview.functions`, or pass --source <path>.");
|
|
136
|
+
const { vars: neonEnv, skipped, credential } = await resolveDevEnv(devEnvContext(props, branchId, process.cwd()));
|
|
137
|
+
reportDevCredential(credential);
|
|
138
|
+
const units = planFunctionsToUnits(functions, neonEnv, DEFAULT_PORT_BASE);
|
|
139
|
+
const replan = async (searchBase) => {
|
|
140
|
+
const re = await resolveFunctionsFromConfig(process.cwd());
|
|
141
|
+
if (re === null) return null;
|
|
142
|
+
return planFunctionsToUnits(re.functions, neonEnv, searchBase);
|
|
143
|
+
};
|
|
144
|
+
await runSupervisor(units, {
|
|
145
|
+
reload: {
|
|
146
|
+
configPath,
|
|
147
|
+
replan
|
|
148
|
+
},
|
|
149
|
+
...skipped ? { envNote: skipped.reason } : {}
|
|
150
|
+
});
|
|
168
151
|
};
|
|
169
152
|
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
153
|
+
* Map a list of {@link PlannedFunction}s to {@link ServedUnit}s, coordinating the search
|
|
154
|
+
* base across them so search-mode functions don't all probe the same starting port.
|
|
155
|
+
*
|
|
156
|
+
* Each search-mode (no `dev.port`) function gets a distinct base starting at
|
|
157
|
+
* `searchBase`; the runtime still walks upward from its base, so an occupied base
|
|
158
|
+
* self-resolves and this never fails — the offset just makes startup deterministic.
|
|
159
|
+
*/
|
|
177
160
|
const planFunctionsToUnits = (functions, neonEnv, searchBase) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
161
|
+
let searchOffset = 0;
|
|
162
|
+
return functions.map((fn) => {
|
|
163
|
+
const base = searchBase + searchOffset;
|
|
164
|
+
if (fn.port === void 0) searchOffset += 1;
|
|
165
|
+
return plannedToUnit(fn, neonEnv, base);
|
|
166
|
+
});
|
|
185
167
|
};
|
|
186
168
|
/**
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
169
|
+
* Resolve the selected branch id from props, if any. Best-effort: a failure here only
|
|
170
|
+
* means env injection is skipped, so it never throws.
|
|
171
|
+
*/
|
|
190
172
|
const resolveBranchId = async (props) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
log.debug("dev: could not resolve branch id: %s", err instanceof Error ? err.message : String(err));
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
173
|
+
if (!props.apiClient || !props.projectId) return void 0;
|
|
174
|
+
const branch = props.branch ?? props.id;
|
|
175
|
+
if (!branch) return void 0;
|
|
176
|
+
try {
|
|
177
|
+
return await branchIdResolve({
|
|
178
|
+
branch,
|
|
179
|
+
apiClient: props.apiClient,
|
|
180
|
+
projectId: props.projectId
|
|
181
|
+
});
|
|
182
|
+
} catch (err) {
|
|
183
|
+
log.debug("dev: could not resolve branch id: %s", err instanceof Error ? err.message : String(err));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
207
186
|
};
|
|
208
187
|
const DEFAULT_PORT_BASE = 8787;
|
|
209
188
|
const portFromProps = (port) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
189
|
+
if (port !== void 0) return {
|
|
190
|
+
mode: "explicit",
|
|
191
|
+
port
|
|
192
|
+
};
|
|
193
|
+
if (process.env.PORT !== void 0 && process.env.PORT !== "") return {
|
|
194
|
+
mode: "explicit",
|
|
195
|
+
port: Number(process.env.PORT)
|
|
196
|
+
};
|
|
197
|
+
return {
|
|
198
|
+
mode: "search",
|
|
199
|
+
from: DEFAULT_PORT_BASE
|
|
200
|
+
};
|
|
216
201
|
};
|
|
217
202
|
/**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
? join(cwd, "node_modules", ".neon-dev")
|
|
229
|
-
: join(cwd, "node_modules", ".neon-dev", slug);
|
|
203
|
+
* Where a locally-served function's bundle is written.
|
|
204
|
+
*
|
|
205
|
+
* The location inside the project's own `node_modules` is load-bearing, not a tidiness
|
|
206
|
+
* choice. A function's `externalPackages` are left unbundled, and Node resolves an unbundled
|
|
207
|
+
* import by walking up from the importing file — so from here it reaches the project's real
|
|
208
|
+
* `node_modules` and finds them, at the host architecture that can actually run locally.
|
|
209
|
+
* Moving this directory anywhere outside `node_modules` breaks `neon dev` for those
|
|
210
|
+
* functions with a `Cannot find module`, so the path is pinned by a test.
|
|
211
|
+
*/
|
|
212
|
+
const devBundleDir = (cwd, slug) => slug === void 0 ? join(cwd, "node_modules", ".neon-dev") : join(cwd, "node_modules", ".neon-dev", slug);
|
|
230
213
|
/**
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
214
|
+
* Translate a {@link PlannedFunction} into a {@link ServedUnit}. Port rules:
|
|
215
|
+
* - explicit `dev.port`: bind exactly, fail if taken.
|
|
216
|
+
* - no `dev.port`: search for a free port (base coordinated by the caller).
|
|
217
|
+
* Per-function neon.ts env layers over the shared branch env.
|
|
218
|
+
*/
|
|
236
219
|
const plannedToUnit = (fn, branchEnv, searchBase) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
220
|
+
const port = fn.port !== void 0 ? {
|
|
221
|
+
mode: "explicit",
|
|
222
|
+
port: fn.port
|
|
223
|
+
} : {
|
|
224
|
+
mode: "search",
|
|
225
|
+
from: searchBase
|
|
226
|
+
};
|
|
227
|
+
const childEnv = buildChildEnv({
|
|
228
|
+
...branchEnv,
|
|
229
|
+
...fn.env
|
|
230
|
+
}, port);
|
|
231
|
+
return {
|
|
232
|
+
slug: fn.slug,
|
|
233
|
+
source: fn.source,
|
|
234
|
+
bundleDir: devBundleDir(process.cwd(), fn.slug),
|
|
235
|
+
childEnv,
|
|
236
|
+
label: fn.slug,
|
|
237
|
+
envSummary: {
|
|
238
|
+
neon: Object.keys(branchEnv),
|
|
239
|
+
fn: Object.keys(fn.env)
|
|
240
|
+
},
|
|
241
|
+
...fn.externalPackages ? { externalPackages: fn.externalPackages } : {},
|
|
242
|
+
configKey: JSON.stringify({
|
|
243
|
+
source: fn.source,
|
|
244
|
+
port: fn.port ?? null,
|
|
245
|
+
env: fn.env,
|
|
246
|
+
externalPackages: fn.externalPackages ?? null
|
|
247
|
+
})
|
|
248
|
+
};
|
|
262
249
|
};
|
|
263
250
|
/**
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
251
|
+
* Build a child's env. Neon vars layer over the inherited environment (so the branch's
|
|
252
|
+
* DATABASE_URL wins over a stale inherited value); a function that loads its own `.env`
|
|
253
|
+
* at runtime still overrides them. The port spec is encoded for the runtime via
|
|
254
|
+
* NEON_DEV_PORT (explicit) or NEON_DEV_PORT_BASE (search).
|
|
255
|
+
*/
|
|
269
256
|
const buildChildEnv = (neonEnv, port) => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return env;
|
|
257
|
+
const env = {
|
|
258
|
+
...process.env,
|
|
259
|
+
...neonEnv
|
|
260
|
+
};
|
|
261
|
+
delete env.NEON_DEV_PORT;
|
|
262
|
+
delete env.NEON_DEV_PORT_BASE;
|
|
263
|
+
if (port.mode === "explicit") env.NEON_DEV_PORT = String(port.port);
|
|
264
|
+
else if (port.mode === "search") env.NEON_DEV_PORT_BASE = String(port.from);
|
|
265
|
+
return env;
|
|
280
266
|
};
|
|
281
267
|
const READY_PATTERN = /neon-dev:ready (\d+)/;
|
|
282
268
|
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
269
|
+
* Supervise one or more {@link ServedUnit}s: bundle + start each in its own child, watch
|
|
270
|
+
* its inputs for hot reload, and tear everything down cleanly on shutdown. Units are
|
|
271
|
+
* independent — one crashing or failing to start does not stop the others (it is shown
|
|
272
|
+
* as errored and recovered on the next edit). A single SIGINT/SIGTERM shuts all of them
|
|
273
|
+
* down, tree-killing each child so no descendant it spawned is orphaned.
|
|
274
|
+
*
|
|
275
|
+
* In config mode, `reload` lets the supervisor watch `neon.ts` and reconcile the live set
|
|
276
|
+
* of units when it changes: a newly-declared function is hot-added (its own child, watcher,
|
|
277
|
+
* and port) and a removed one is torn down — all without disturbing the functions that
|
|
278
|
+
* stayed the same. A function whose config (env/port/source) changed is restarted
|
|
279
|
+
* in place; siblings are untouched.
|
|
280
|
+
*/
|
|
295
281
|
const runSupervisor = async (units, options = {}) => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
pending = false;
|
|
407
|
-
await reconcileOnce(running, reload.replan, ops);
|
|
408
|
-
} while (pending && !shuttingDown);
|
|
409
|
-
})().finally(() => {
|
|
410
|
-
inFlight = null;
|
|
411
|
-
});
|
|
412
|
-
};
|
|
413
|
-
configWatcher = await startConfigWatcher(reload.configPath, drive);
|
|
414
|
-
}
|
|
415
|
-
await new Promise((resolveRun) => {
|
|
416
|
-
const shutdown = () => {
|
|
417
|
-
if (shuttingDown)
|
|
418
|
-
return;
|
|
419
|
-
shuttingDown = true;
|
|
420
|
-
void (async () => {
|
|
421
|
-
await configWatcher?.close();
|
|
422
|
-
await Promise.all(running.map((r) => stopUnit(r)));
|
|
423
|
-
log.info(chalk.dim("Stopped the dev server."));
|
|
424
|
-
resolveRun();
|
|
425
|
-
})();
|
|
426
|
-
};
|
|
427
|
-
process.on("SIGINT", shutdown);
|
|
428
|
-
process.on("SIGTERM", shutdown);
|
|
429
|
-
});
|
|
282
|
+
const { reload, envNote } = options;
|
|
283
|
+
const runtimePath = resolveRuntimePath();
|
|
284
|
+
let shuttingDown = false;
|
|
285
|
+
const running = units.map(makeRunningUnit);
|
|
286
|
+
const bundleAndStart = async (r) => {
|
|
287
|
+
let bundlePath;
|
|
288
|
+
try {
|
|
289
|
+
bundlePath = await writeBundle(r.unit.source, r.unit.bundleDir, r.unit.externalPackages, r.unit.slug ?? void 0);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
r.status = "error";
|
|
292
|
+
logUnit(r.unit, chalk.red("bundle failed: ") + (err instanceof Error ? err.message : String(err)));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (r.watcher) await r.watcher.sync();
|
|
296
|
+
const next = spawnChild(r.unit, runtimePath, bundlePath);
|
|
297
|
+
r.child = next;
|
|
298
|
+
const ready = waitForReady(next);
|
|
299
|
+
pipeChildOutput(next, r.unit.label);
|
|
300
|
+
next.on("exit", (code, signal) => {
|
|
301
|
+
if (shuttingDown || r.child !== next) return;
|
|
302
|
+
if (signal) {
|
|
303
|
+
log.debug("runtime for %s exited via %s", r.unit.slug ?? "(source)", signal);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (code && code !== 0 && r.everReady) {
|
|
307
|
+
r.status = "error";
|
|
308
|
+
logUnit(r.unit, chalk.red(`exited with code ${code} (waiting for a change)`));
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
const port = await ready;
|
|
312
|
+
if (port !== null) {
|
|
313
|
+
r.boundPort = port;
|
|
314
|
+
r.everReady = true;
|
|
315
|
+
r.status = "ready";
|
|
316
|
+
} else r.status = "error";
|
|
317
|
+
};
|
|
318
|
+
const restart = (r) => {
|
|
319
|
+
if (shuttingDown) return;
|
|
320
|
+
if (r.restartTimer) clearTimeout(r.restartTimer);
|
|
321
|
+
r.restartTimer = setTimeout(() => {
|
|
322
|
+
(async () => {
|
|
323
|
+
logUnit(r.unit, chalk.dim("change detected, restarting…"));
|
|
324
|
+
if (r.child) await killTree(r.child);
|
|
325
|
+
if (shuttingDown) return;
|
|
326
|
+
await bundleAndStart(r);
|
|
327
|
+
if (r.status === "ready") logUnit(r.unit, chalk.green("ready") + ` ${urlFor(r.boundPort)}`);
|
|
328
|
+
})();
|
|
329
|
+
}, 150);
|
|
330
|
+
};
|
|
331
|
+
const startUnit = async (r) => {
|
|
332
|
+
r.watcher = await startWatcher(r.unit.source, () => {
|
|
333
|
+
restart(r);
|
|
334
|
+
});
|
|
335
|
+
await bundleAndStart(r);
|
|
336
|
+
};
|
|
337
|
+
const stopUnit = async (r) => {
|
|
338
|
+
if (r.restartTimer) clearTimeout(r.restartTimer);
|
|
339
|
+
await r.watcher?.close();
|
|
340
|
+
if (r.child) await killTree(r.child);
|
|
341
|
+
rmSync(r.unit.bundleDir, {
|
|
342
|
+
recursive: true,
|
|
343
|
+
force: true
|
|
344
|
+
});
|
|
345
|
+
};
|
|
346
|
+
await Promise.all(running.map((r) => startUnit(r)));
|
|
347
|
+
if (running.every((r) => r.status === "error")) {
|
|
348
|
+
await Promise.all(running.map((r) => stopUnit(r)));
|
|
349
|
+
throw new Error("No function started. See the output above for details.");
|
|
350
|
+
}
|
|
351
|
+
printBanner(running, envNote);
|
|
352
|
+
let configWatcher = null;
|
|
353
|
+
if (reload) {
|
|
354
|
+
const ops = {
|
|
355
|
+
isShuttingDown: () => shuttingDown,
|
|
356
|
+
startUnit,
|
|
357
|
+
stopUnit,
|
|
358
|
+
restartUnit: restart
|
|
359
|
+
};
|
|
360
|
+
let inFlight = null;
|
|
361
|
+
let pending = false;
|
|
362
|
+
const drive = () => {
|
|
363
|
+
if (inFlight) {
|
|
364
|
+
pending = true;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
inFlight = (async () => {
|
|
368
|
+
do {
|
|
369
|
+
pending = false;
|
|
370
|
+
await reconcileOnce(running, reload.replan, ops);
|
|
371
|
+
} while (pending && !shuttingDown);
|
|
372
|
+
})().finally(() => {
|
|
373
|
+
inFlight = null;
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
configWatcher = await startConfigWatcher(reload.configPath, drive);
|
|
377
|
+
}
|
|
378
|
+
await new Promise((resolveRun) => {
|
|
379
|
+
const shutdown = () => {
|
|
380
|
+
if (shuttingDown) return;
|
|
381
|
+
shuttingDown = true;
|
|
382
|
+
(async () => {
|
|
383
|
+
await configWatcher?.close();
|
|
384
|
+
await Promise.all(running.map((r) => stopUnit(r)));
|
|
385
|
+
log.info(chalk.dim("Stopped the dev server."));
|
|
386
|
+
resolveRun();
|
|
387
|
+
})();
|
|
388
|
+
};
|
|
389
|
+
process.on("SIGINT", shutdown);
|
|
390
|
+
process.on("SIGTERM", shutdown);
|
|
391
|
+
});
|
|
430
392
|
};
|
|
431
393
|
const makeRunningUnit = (unit) => ({
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
394
|
+
unit,
|
|
395
|
+
child: null,
|
|
396
|
+
boundPort: null,
|
|
397
|
+
everReady: false,
|
|
398
|
+
restartTimer: null,
|
|
399
|
+
watcher: null,
|
|
400
|
+
status: "starting"
|
|
439
401
|
});
|
|
440
402
|
/**
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
plan.restart.push(r);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
plan.add.push(want);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return plan;
|
|
403
|
+
* Pure slug-keyed diff of the live units against the freshly-resolved desired set:
|
|
404
|
+
* - a slug present now but not before → **add** (new child + watcher + port),
|
|
405
|
+
* - a slug gone from neon.ts → **remove** (torn down),
|
|
406
|
+
* - a slug whose config (source/port/env) changed → **restart** in place,
|
|
407
|
+
* - an unchanged slug → left out of the plan entirely (never touched).
|
|
408
|
+
* Functions that stayed the same never die, so an edit that only adds a function is
|
|
409
|
+
* non-disruptive. `desired === null` (neon.ts deleted) is treated as "no functions".
|
|
410
|
+
*/
|
|
411
|
+
const diffUnits = (running, desired) => {
|
|
412
|
+
const desiredBySlug = /* @__PURE__ */ new Map();
|
|
413
|
+
for (const u of desired ?? []) if (u.slug !== null) desiredBySlug.set(u.slug, u);
|
|
414
|
+
const runningBySlug = /* @__PURE__ */ new Map();
|
|
415
|
+
for (const r of running) if (r.unit.slug !== null) runningBySlug.set(r.unit.slug, r);
|
|
416
|
+
const plan = {
|
|
417
|
+
remove: [],
|
|
418
|
+
restart: [],
|
|
419
|
+
add: []
|
|
420
|
+
};
|
|
421
|
+
for (const [slug, r] of runningBySlug) if (!desiredBySlug.has(slug)) plan.remove.push(r);
|
|
422
|
+
for (const [slug, want] of desiredBySlug) {
|
|
423
|
+
const r = runningBySlug.get(slug);
|
|
424
|
+
if (r) {
|
|
425
|
+
if (r.unit.configKey !== want.configKey) {
|
|
426
|
+
r.unit = want;
|
|
427
|
+
plan.restart.push(r);
|
|
428
|
+
}
|
|
429
|
+
} else plan.add.push(want);
|
|
430
|
+
}
|
|
431
|
+
return plan;
|
|
478
432
|
};
|
|
479
433
|
/**
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
434
|
+
* Run one reconcile: re-resolve neon.ts (ignoring the change with a clear message if it no
|
|
435
|
+
* longer loads), {@link diffUnits} against the live set, then apply the plan — tearing down
|
|
436
|
+
* removed functions, restarting changed ones in place, and hot-adding new ones. Mutates
|
|
437
|
+
* `running` in place so the surrounding supervisor sees the converged set.
|
|
438
|
+
*/
|
|
485
439
|
const reconcileOnce = async (running, replan, ops) => {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
});
|
|
517
|
-
await Promise.all(added.map((r) => ops.startUnit(r)));
|
|
518
|
-
for (const r of added) {
|
|
519
|
-
if (r.status === "ready") {
|
|
520
|
-
const env = formatEnvSummary(r.unit.envSummary);
|
|
521
|
-
logUnit(r.unit, chalk.green("ready") +
|
|
522
|
-
` ${urlFor(r.boundPort)}` +
|
|
523
|
-
(env ? chalk.dim(` ${env}`) : ""));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
440
|
+
if (ops.isShuttingDown()) return;
|
|
441
|
+
let desired;
|
|
442
|
+
try {
|
|
443
|
+
desired = await replan(nextSearchBase(running));
|
|
444
|
+
} catch (err) {
|
|
445
|
+
log.info(chalk.red("neon.ts change ignored: ") + (err instanceof Error ? err.message : String(err)) + chalk.dim(" (fix it and save again)"));
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (ops.isShuttingDown()) return;
|
|
449
|
+
const plan = diffUnits(running, desired);
|
|
450
|
+
for (const r of plan.remove) {
|
|
451
|
+
logUnit(r.unit, chalk.dim("removed from neon.ts, stopping…"));
|
|
452
|
+
await ops.stopUnit(r);
|
|
453
|
+
const idx = running.indexOf(r);
|
|
454
|
+
if (idx !== -1) running.splice(idx, 1);
|
|
455
|
+
}
|
|
456
|
+
for (const r of plan.restart) ops.restartUnit(r);
|
|
457
|
+
if (plan.add.length > 0) {
|
|
458
|
+
const added = plan.add.map((unit) => {
|
|
459
|
+
const r = makeRunningUnit(unit);
|
|
460
|
+
running.push(r);
|
|
461
|
+
logUnit(unit, chalk.dim("added in neon.ts, starting…"));
|
|
462
|
+
return r;
|
|
463
|
+
});
|
|
464
|
+
await Promise.all(added.map((r) => ops.startUnit(r)));
|
|
465
|
+
for (const r of added) if (r.status === "ready") {
|
|
466
|
+
const env = formatEnvSummary(r.unit.envSummary);
|
|
467
|
+
logUnit(r.unit, chalk.green("ready") + ` ${urlFor(r.boundPort)}` + (env ? chalk.dim(` ${env}`) : ""));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
527
470
|
};
|
|
528
471
|
/**
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
472
|
+
* Choose a port search base above every port the live units already bound, so a hot-added
|
|
473
|
+
* search-mode function starts probing where there's room. The runtime still walks upward
|
|
474
|
+
* from here, so it never fails even if this guess is taken — it just keeps things tidy.
|
|
475
|
+
*/
|
|
533
476
|
const nextSearchBase = (running) => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
max = r.boundPort;
|
|
538
|
-
}
|
|
539
|
-
return max + 1;
|
|
477
|
+
let max = 8786;
|
|
478
|
+
for (const r of running) if (r.boundPort !== null && r.boundPort > max) max = r.boundPort;
|
|
479
|
+
return max + 1;
|
|
540
480
|
};
|
|
541
481
|
/**
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
482
|
+
* Spawn the child for a unit: the bundled output run directly under `node`.
|
|
483
|
+
*
|
|
484
|
+
* Spawned detached (own process group) so killTree can reap the whole group.
|
|
485
|
+
*/
|
|
546
486
|
const spawnChild = (unit, runtimePath, bundlePath) => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
487
|
+
return spawn(process.execPath, [runtimePath, bundlePath], {
|
|
488
|
+
stdio: [
|
|
489
|
+
"ignore",
|
|
490
|
+
"pipe",
|
|
491
|
+
"pipe"
|
|
492
|
+
],
|
|
493
|
+
env: unit.childEnv,
|
|
494
|
+
detached: true
|
|
495
|
+
});
|
|
552
496
|
};
|
|
553
497
|
/**
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const reportedFindings = new Map();
|
|
498
|
+
* Findings already reported for a served unit, so an advisory that cannot change between
|
|
499
|
+
* saves is not reprinted on every one. A dev session on a project with a standing false
|
|
500
|
+
* positive would otherwise repeat the whole block for its lifetime.
|
|
501
|
+
*/
|
|
502
|
+
const reportedFindings = /* @__PURE__ */ new Map();
|
|
559
503
|
const writeBundle = async (source, bundleDir, externalPackages, label) => {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const signature = findings.map((f) => f.name).join(",");
|
|
576
|
-
if (reportedFindings.get(source) !== signature) {
|
|
577
|
-
reportedFindings.set(source, signature);
|
|
578
|
-
for (const finding of findings) {
|
|
579
|
-
log.warning(describeNativeFinding(label ?? basename(source), finding));
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
mkdirSync(bundleDir, { recursive: true });
|
|
583
|
-
// bundleEntry emits a single `index.mjs` (no source map). The `.mjs` extension makes Node
|
|
584
|
-
// load it as ESM directly, so no `package.json` `"type": "module"` marker is needed.
|
|
585
|
-
for (const [name, contents] of Object.entries(files)) {
|
|
586
|
-
writeFileSync(join(bundleDir, name), contents);
|
|
587
|
-
}
|
|
588
|
-
return join(bundleDir, "index.mjs");
|
|
504
|
+
const { files, metafile, warnings } = await bundleEntry(source, { ...externalPackages ? { externalPackages } : {} });
|
|
505
|
+
for (const warning of warnings) log.warning(warning);
|
|
506
|
+
const findings = findUndeclaredNativePackages({
|
|
507
|
+
metafile,
|
|
508
|
+
declared: externalPackages ?? [],
|
|
509
|
+
projectDir: dirname(source)
|
|
510
|
+
});
|
|
511
|
+
const signature = findings.map((f) => f.name).join(",");
|
|
512
|
+
if (reportedFindings.get(source) !== signature) {
|
|
513
|
+
reportedFindings.set(source, signature);
|
|
514
|
+
for (const finding of findings) log.warning(describeNativeFinding(label ?? basename(source), finding));
|
|
515
|
+
}
|
|
516
|
+
mkdirSync(bundleDir, { recursive: true });
|
|
517
|
+
for (const [name, contents] of Object.entries(files)) writeFileSync(join(bundleDir, name), contents);
|
|
518
|
+
return join(bundleDir, "index.mjs");
|
|
589
519
|
};
|
|
590
520
|
const urlFor = (port) => port === null ? chalk.red("not running") : `http://localhost:${port}`;
|
|
591
521
|
const waitForReady = (child) => new Promise((resolveReady) => {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
522
|
+
let settled = false;
|
|
523
|
+
let buffer = "";
|
|
524
|
+
const onData = (chunk) => {
|
|
525
|
+
buffer += chunk.toString();
|
|
526
|
+
const match = READY_PATTERN.exec(buffer);
|
|
527
|
+
if (match && !settled) {
|
|
528
|
+
settled = true;
|
|
529
|
+
child.stdout?.off("data", onData);
|
|
530
|
+
resolveReady(Number(match[1]));
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
child.stdout?.on("data", onData);
|
|
534
|
+
child.once("exit", () => {
|
|
535
|
+
if (!settled) {
|
|
536
|
+
settled = true;
|
|
537
|
+
resolveReady(null);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
610
540
|
});
|
|
611
541
|
/**
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
542
|
+
* Forward the child's stdout/stderr to the parent, swallowing the machine-readable
|
|
543
|
+
* `neon-dev:ready` line. When `label` is set (multi-function mode), every line is
|
|
544
|
+
* prefixed with `[slug]` so concurrent servers' output stays readable.
|
|
545
|
+
*/
|
|
616
546
|
const pipeChildOutput = (child, label) => {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
forward("stderr");
|
|
547
|
+
const prefix = label ? chalk.dim(`[${label}] `) : "";
|
|
548
|
+
const forward = (stream) => {
|
|
549
|
+
let buffer = "";
|
|
550
|
+
child[stream]?.on("data", (chunk) => {
|
|
551
|
+
buffer += chunk.toString();
|
|
552
|
+
const lines = buffer.split("\n");
|
|
553
|
+
buffer = lines.pop() ?? "";
|
|
554
|
+
for (const line of lines) {
|
|
555
|
+
if (READY_PATTERN.test(line)) continue;
|
|
556
|
+
process[stream].write(`${prefix}${line}\n`);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
};
|
|
560
|
+
forward("stdout");
|
|
561
|
+
forward("stderr");
|
|
633
562
|
};
|
|
634
563
|
const printBanner = (running, envNote) => {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
log.info("");
|
|
564
|
+
log.info("");
|
|
565
|
+
log.info(chalk.green.bold(" Neon Functions dev server"));
|
|
566
|
+
log.info("");
|
|
567
|
+
for (const r of running) {
|
|
568
|
+
const name = r.unit.label ?? "function";
|
|
569
|
+
const url = urlFor(r.boundPort);
|
|
570
|
+
log.info(` ${chalk.dim(name.padEnd(20))} ${url}`);
|
|
571
|
+
const env = formatEnvSummary(r.unit.envSummary);
|
|
572
|
+
if (env) log.info(` ${" ".repeat(20)} ${chalk.dim(env)}`);
|
|
573
|
+
}
|
|
574
|
+
if (envNote) {
|
|
575
|
+
log.info("");
|
|
576
|
+
log.info(` ${chalk.yellow("!")} ${chalk.dim(`Neon env: ${envNote}`)}`);
|
|
577
|
+
}
|
|
578
|
+
log.info("");
|
|
651
579
|
};
|
|
652
580
|
/**
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
if (summary.fn.length > 0) {
|
|
666
|
-
parts.push(`neon.ts: ${[...summary.fn].sort().join(", ")}`);
|
|
667
|
-
}
|
|
668
|
-
return parts.join(" · ");
|
|
581
|
+
* Render a unit's injected env into one transparent line for the banner, e.g.
|
|
582
|
+
* `env: DATABASE_URL, DATABASE_URL_UNPOOLED · neon.ts: RESEND_API_KEY`. Var **names** only
|
|
583
|
+
* (never values — they're secrets). Returns `''` when nothing is injected, so the caller can
|
|
584
|
+
* skip the line. Exported for unit testing.
|
|
585
|
+
*/
|
|
586
|
+
const formatEnvSummary = (summary) => {
|
|
587
|
+
if (!summary) return "";
|
|
588
|
+
const parts = [];
|
|
589
|
+
if (summary.neon.length > 0) parts.push(`env: ${[...summary.neon].sort().join(", ")}`);
|
|
590
|
+
if (summary.fn.length > 0) parts.push(`neon.ts: ${[...summary.fn].sort().join(", ")}`);
|
|
591
|
+
return parts.join(" · ");
|
|
669
592
|
};
|
|
670
593
|
const logUnit = (unit, message) => {
|
|
671
|
-
|
|
672
|
-
|
|
594
|
+
const prefix = unit.label ? chalk.dim(`[${unit.label}] `) : "";
|
|
595
|
+
log.info(`${prefix}${message}`);
|
|
673
596
|
};
|
|
674
597
|
const startWatcher = async (source, restart) => {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
return startInputWatcher(chokidar, source, initialInputs, restart);
|
|
598
|
+
const { default: chokidar } = await import("chokidar");
|
|
599
|
+
const initialInputs = await resolveWatchInputs(source);
|
|
600
|
+
if (initialInputs === null) return startDirectoryWatcher(chokidar, source, restart);
|
|
601
|
+
return startInputWatcher(chokidar, source, initialInputs, restart);
|
|
681
602
|
};
|
|
682
603
|
/**
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
604
|
+
* Watch the neon.ts file itself for changes, firing `onChange` on every save. Used by the
|
|
605
|
+
* supervisor (config mode only) to hot-add/remove/restart functions when the declared set
|
|
606
|
+
* changes. We watch the single config file rather than its import graph: editing neon.ts to
|
|
607
|
+
* add or remove a function is the case that matters, and a plain file watch is robust where
|
|
608
|
+
* the esbuild-based input resolution (built for function bundles) is not a fit for a
|
|
609
|
+
* jiti-loaded config.
|
|
610
|
+
*/
|
|
690
611
|
const startConfigWatcher = async (configPath, onChange) => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
612
|
+
const { default: chokidar } = await import("chokidar");
|
|
613
|
+
const watcher = chokidar.watch(configPath, { ignoreInitial: true });
|
|
614
|
+
await once(watcher, "ready");
|
|
615
|
+
watcher.on("all", () => {
|
|
616
|
+
onChange();
|
|
617
|
+
});
|
|
618
|
+
return {
|
|
619
|
+
sync: () => Promise.resolve(),
|
|
620
|
+
close: () => watcher.close()
|
|
621
|
+
};
|
|
698
622
|
};
|
|
699
623
|
const startInputWatcher = async (chokidar, source, initialInputs, restart) => {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
};
|
|
724
|
-
return { sync, close: () => watcher.close() };
|
|
624
|
+
const watched = /* @__PURE__ */ new Set([source, ...initialInputs]);
|
|
625
|
+
const watcher = chokidar.watch([...watched], { ignoreInitial: true });
|
|
626
|
+
await once(watcher, "ready");
|
|
627
|
+
watcher.on("all", () => {
|
|
628
|
+
restart();
|
|
629
|
+
});
|
|
630
|
+
const sync = async () => {
|
|
631
|
+
const next = await resolveWatchInputs(source);
|
|
632
|
+
if (next === null) return;
|
|
633
|
+
const desired = /* @__PURE__ */ new Set([source, ...next]);
|
|
634
|
+
for (const file of desired) if (!watched.has(file)) {
|
|
635
|
+
watcher.add(file);
|
|
636
|
+
watched.add(file);
|
|
637
|
+
}
|
|
638
|
+
for (const file of watched) if (!desired.has(file)) {
|
|
639
|
+
watcher.unwatch(file);
|
|
640
|
+
watched.delete(file);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
sync,
|
|
645
|
+
close: () => watcher.close()
|
|
646
|
+
};
|
|
725
647
|
};
|
|
726
648
|
const startDirectoryWatcher = async (chokidar, source, restart) => {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
649
|
+
const watchedDir = dirname(source);
|
|
650
|
+
const isIgnored = (p) => {
|
|
651
|
+
const segments = p.split(/[/\\]/);
|
|
652
|
+
return segments.includes("node_modules") || segments.includes(".git") || segments.includes("dist");
|
|
653
|
+
};
|
|
654
|
+
const watcher = chokidar.watch(watchedDir, {
|
|
655
|
+
ignoreInitial: true,
|
|
656
|
+
ignored: (path) => isIgnored(path)
|
|
657
|
+
});
|
|
658
|
+
await once(watcher, "ready");
|
|
659
|
+
watcher.on("all", () => {
|
|
660
|
+
restart();
|
|
661
|
+
});
|
|
662
|
+
return {
|
|
663
|
+
sync: () => Promise.resolve(),
|
|
664
|
+
close: () => watcher.close()
|
|
665
|
+
};
|
|
743
666
|
};
|
|
744
667
|
/**
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
668
|
+
* Terminate a child and every descendant it spawned. The child is started `detached`, so
|
|
669
|
+
* on POSIX it leads its own process group and a negative-PID signal reaps the group
|
|
670
|
+
* (covering the runtime and anything it spawned). On Windows there are no POSIX groups, so we
|
|
671
|
+
* shell out to `taskkill /T` to kill the tree. Escalates SIGTERM -> SIGKILL after 2s.
|
|
672
|
+
*/
|
|
750
673
|
const killTree = (child) => {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
catch {
|
|
769
|
-
// Fall through to a direct kill if the group is already gone.
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
child.kill("SIGTERM");
|
|
773
|
-
});
|
|
674
|
+
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
|
|
675
|
+
const pid = child.pid;
|
|
676
|
+
return new Promise((resolveKill) => {
|
|
677
|
+
const timeout = setTimeout(() => {
|
|
678
|
+
forceKill(child, pid);
|
|
679
|
+
}, 2e3);
|
|
680
|
+
child.once("exit", () => {
|
|
681
|
+
clearTimeout(timeout);
|
|
682
|
+
resolveKill();
|
|
683
|
+
});
|
|
684
|
+
if (pid !== void 0 && process.platform !== "win32") try {
|
|
685
|
+
process.kill(-pid, "SIGTERM");
|
|
686
|
+
return;
|
|
687
|
+
} catch {}
|
|
688
|
+
child.kill("SIGTERM");
|
|
689
|
+
});
|
|
774
690
|
};
|
|
775
691
|
const forceKill = (child, pid) => {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
692
|
+
if (pid === void 0) {
|
|
693
|
+
child.kill("SIGKILL");
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (process.platform === "win32") {
|
|
697
|
+
spawnSync("taskkill", [
|
|
698
|
+
"/pid",
|
|
699
|
+
String(pid),
|
|
700
|
+
"/T",
|
|
701
|
+
"/F"
|
|
702
|
+
]);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
process.kill(-pid, "SIGKILL");
|
|
707
|
+
} catch {
|
|
708
|
+
child.kill("SIGKILL");
|
|
709
|
+
}
|
|
790
710
|
};
|
|
791
711
|
const resolveRuntimePath = () => {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
return candidate;
|
|
712
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
713
|
+
const candidate = join(here, "..", "dev", "runtime.js");
|
|
714
|
+
if (!existsSync(candidate)) throw new Error(`Could not locate the dev runtime at ${candidate}`);
|
|
715
|
+
return candidate;
|
|
798
716
|
};
|
|
717
|
+
//#endregion
|
|
718
|
+
export { builder, command, describe, devBundleDir, devEnvContext, diffUnits, formatEnvSummary, handler, reportDevCredential, dev_exports as t };
|