@web-ts-toolkit/express-runtime 0.40.1 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1494 @@
1
+ import {
2
+ MAX_INTEGER_OPTION_VALUE,
3
+ createExpressApp,
4
+ parsePortValue,
5
+ validateFiniteInteger
6
+ } from "./chunk-VPFBKM2K.mjs";
7
+
8
+ // src/cli-utils.ts
9
+ import { pathToFileURL } from "url";
10
+ import {
11
+ dirname,
12
+ resolve as pathResolve,
13
+ extname,
14
+ join as pathJoin,
15
+ normalize as pathNormalize,
16
+ parse as pathParse,
17
+ sep as pathSep
18
+ } from "path";
19
+ import {
20
+ writeFileSync,
21
+ rmSync,
22
+ readFileSync,
23
+ existsSync,
24
+ realpathSync,
25
+ watch,
26
+ mkdtempSync,
27
+ lstatSync,
28
+ chmodSync
29
+ } from "fs";
30
+ import { createRequire } from "module";
31
+ import { fork } from "child_process";
32
+ import { validateHeaderName, validateHeaderValue } from "http";
33
+ function readPackageVersion(candidatePath) {
34
+ try {
35
+ const manifest = JSON.parse(readFileSync(candidatePath, "utf8"));
36
+ return typeof manifest.version === "string" && manifest.version.length > 0 ? manifest.version : void 0;
37
+ } catch (_error) {
38
+ void _error;
39
+ return void 0;
40
+ }
41
+ }
42
+ function resolveCliVersion(executablePath = process.argv[1]) {
43
+ let resolvedExecutable;
44
+ try {
45
+ resolvedExecutable = executablePath ? realpathSync(executablePath) : void 0;
46
+ } catch (_error) {
47
+ void _error;
48
+ }
49
+ const executableDir = resolvedExecutable ? dirname(resolvedExecutable) : void 0;
50
+ const candidates = executableDir ? [pathJoin(executableDir, "package.json"), pathJoin(executableDir, "..", "package.json")] : [];
51
+ for (const candidate of candidates) {
52
+ const version = readPackageVersion(candidate);
53
+ if (version) return version;
54
+ }
55
+ return "0.0.0-dev";
56
+ }
57
+ var CLI_VERSION = resolveCliVersion();
58
+ function readValue(argv, index, name) {
59
+ const value = argv[index + 1];
60
+ if (value === void 0 || value === "" || value.startsWith("--")) {
61
+ throw new Error(`Missing value for argument: ${name}`);
62
+ }
63
+ return value;
64
+ }
65
+ function readInlineValue(arg, prefix, name) {
66
+ const value = arg.slice(prefix.length);
67
+ if (value === "") {
68
+ throw new Error(`Missing value for argument: ${name}`);
69
+ }
70
+ return value;
71
+ }
72
+ function parseIntegerFlag(raw, name, min = 0, max = MAX_INTEGER_OPTION_VALUE) {
73
+ if (!/^(0|[1-9]\d*)$/.test(raw)) {
74
+ throw new Error(`Invalid ${name}: ${raw}. Must be a finite integer in ${min}..${max}.`);
75
+ }
76
+ return validateFiniteInteger(Number(raw), { name, min, max });
77
+ }
78
+ function parsePortFlag(raw, name = "--port") {
79
+ try {
80
+ return parsePortValue(raw, name);
81
+ } catch (error) {
82
+ if (error instanceof Error && error.message.startsWith(`Invalid ${name}:`)) {
83
+ throw error;
84
+ }
85
+ throw new Error(`Invalid ${name}: ${raw}. Must be a port number in 0..65535 or a named pipe path.`, {
86
+ cause: error
87
+ });
88
+ }
89
+ }
90
+ function parseCsvFlagValue(raw, name) {
91
+ const values = raw.split(",").map((part) => part.trim()).filter(Boolean);
92
+ if (values.length === 0) {
93
+ throw new Error(`Missing value for argument: ${name}`);
94
+ }
95
+ return values;
96
+ }
97
+ function printHelp() {
98
+ console.log(`wtt-express-runtime
99
+
100
+ Run an Express app locally, bundle it for local or serverless runtimes, or run the bundle.
101
+
102
+ Usage:
103
+ wtt-express-runtime <command> <app-module> [options]
104
+ wtt-express-runtime <app-module> [options] (alias for dev)
105
+
106
+ Commands:
107
+ dev Run the Express app as a local dev server
108
+ build Bundle the Express app as a local app module
109
+ start Run a bundled local app module
110
+ build-serverless Bundle the Express app as a serverless handler
111
+ start-serverless Run a bundled serverless handler locally
112
+
113
+ Dev options:
114
+ --port <number> Port or named pipe (default: process.env.PORT or 8080)
115
+ --host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
116
+ --no-signals Disable SIGINT/SIGTERM handler registration
117
+ --shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
118
+ --require <module> Module(s) to preload before app load (repeatable)
119
+ --env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
120
+ --tsconfig <path> Tsconfig used by config-aware consumers for TS path resolution
121
+ --watch <paths> Comma-separated paths to watch for restart (repeatable; dev only)
122
+ --ext <extensions> Comma-separated extensions to watch (default: ts,js,mjs,cjs,json)
123
+ --delay <ms> Debounce ms before restarting on change (default: 500)
124
+
125
+ Build options:
126
+ --init <path> Init hook module (default export, async function)
127
+ --tsconfig <path> Use a custom tsconfig for bundling
128
+ --out-dir <path> Output directory (default: dist)
129
+ --out-name <name> Output filename without extension (default: app)
130
+ --format <cjs|esm> Output format (default: cjs)
131
+ --target <target> Compilation target (default: node22)
132
+ --external <pkg> Mark package as external (repeatable; express always external)
133
+ --no-clean Don't clean the output directory before building
134
+
135
+ Start options:
136
+ --port <number> Port or named pipe (default: process.env.PORT or 8080)
137
+ --host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
138
+ --no-signals Disable SIGINT/SIGTERM handler registration
139
+ --shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
140
+ --require <module> Module(s) to preload before app load (repeatable)
141
+ --env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
142
+
143
+ Build-serverless options:
144
+ --init <path> Init hook module (default export, async function)
145
+ --tsconfig <path> Use a custom tsconfig for bundling
146
+ --out-dir <path> Output directory (default: dist)
147
+ --out-name <name> Output filename without extension (default: handler)
148
+ --format <cjs|esm> Output format (default: cjs)
149
+ --target <target> Compilation target (default: node22)
150
+ --external <pkg> Mark package as external (repeatable; express always external)
151
+ --no-clean Don't clean the output directory before building
152
+
153
+ Start-serverless options:
154
+ --port <number> Port or named pipe (default: process.env.PORT or 8080)
155
+ --host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
156
+ --no-signals Disable SIGINT/SIGTERM handler registration
157
+ --shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
158
+ --max-body-bytes <bytes> Max request body bytes for adapter (default: 1048576; 0 disallows bodies)
159
+ --require <module> Module(s) to preload before handler load (repeatable)
160
+ --env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
161
+
162
+ Global options:
163
+ -V, --version Show version
164
+ -h, --help Show this help message
165
+
166
+ Examples:
167
+ wtt-express-runtime dev ./dist/app.js
168
+ wtt-express-runtime dev ./dist/app.js --port 3000 --host localhost
169
+ wtt-express-runtime dev ./src/app.ts --env .env --require tsconfig-paths/register --watch ./src,./shared
170
+ wtt-express-runtime build ./src/app.ts --out-dir dist
171
+ wtt-express-runtime start ./dist/app.js --port 3000 --env .env
172
+ wtt-express-runtime build-serverless ./src/app.ts --out-dir netlify/functions
173
+ wtt-express-runtime build-serverless ./src/app.ts --init ./src/init.ts --format esm
174
+ wtt-express-runtime start-serverless ./netlify/functions/handler.js --port 9000 --env .env
175
+ wtt-express-runtime build-serverless ./src/app.ts && wtt-express-runtime start-serverless ./dist/handler.js
176
+
177
+ Notes:
178
+ - In dev mode, the CLI evaluates arbitrary code from <app-module> in the current process.
179
+ - TypeScript app modules in dev mode require a TS loader. Run via tsx:
180
+ npx tsx ./node_modules/@web-ts-toolkit/express-runtime/cli.js dev ./src/app.ts
181
+ Or use --require with a TS-aware loader module.
182
+ - --env files are parsed as KEY=VALUE; existing process.env entries are never overridden.
183
+ For advanced dotenv features (multiline, expansion), --require dotenv/config instead.
184
+ - --watch forks one child process running the same CLI without --watch. File changes
185
+ are serialized into one restart at a time: SIGTERM, SIGKILL after 5000 ms if needed,
186
+ then respawn after the debounce delay. Shutdown closes owned watchers and signal handlers.
187
+ - In build/build-serverless mode, express is always external. Add more externals with --external.
188
+ - In start mode, the bundled app file must default-export an Express app (or export it as "app").
189
+ If the bundle exports "init", it runs before the server starts listening.
190
+ - In start-serverless mode, the bundled handler file must be a JS/CJS module whose
191
+ "handler" export (or default export) is a function: (event, context) => Promise<result>.
192
+ - The start-serverless adapter buffers at most --max-body-bytes per request (default 1 MiB, 0 = empty bodies only);
193
+ larger declared Content-Length or chunked bodies receive 413 without invoking the handler.
194
+ - Use -- before a positional path that starts with a dash, e.g. dev -- --app.js.
195
+ - Numeric flag values are validated before env/preload/app loading, watching, or binding.
196
+ - Init logic for dev mode (DB connections, etc.): add at the top level of your app module.
197
+ `);
198
+ }
199
+ function isVersion(arg) {
200
+ return arg === "-V" || arg === "--version";
201
+ }
202
+ function isHelp(arg) {
203
+ return arg === "-h" || arg === "--help";
204
+ }
205
+ function isSubcommand(arg) {
206
+ return arg === "dev" || arg === "build" || arg === "start" || arg === "build-serverless" || arg === "start-serverless";
207
+ }
208
+ var DEFAULT_WATCH_EXTENSIONS = ["ts", "js", "mjs", "cjs", "json"];
209
+ var DEFAULT_WATCH_DELAY = 500;
210
+ function parseRepeatable(argv, index, arg, list) {
211
+ list.push(...parseCsvFlagValue(readValue(argv, index, arg), arg));
212
+ return index + 1;
213
+ }
214
+ function addPositional(arg, current, label) {
215
+ if (current) {
216
+ throw new Error(`Unexpected positional argument: ${arg}. ${label} already set to ${current}`);
217
+ }
218
+ return arg;
219
+ }
220
+ function setTsconfigPath(current, next) {
221
+ if (current !== void 0 && current !== next) {
222
+ throw new Error(`Conflicting --tsconfig values: ${current} and ${next}`);
223
+ }
224
+ return next;
225
+ }
226
+ function optionArgs(argv) {
227
+ const terminator = argv.indexOf("--");
228
+ return terminator === -1 ? argv : argv.slice(0, terminator);
229
+ }
230
+ function parseDevArgs(argv) {
231
+ const options = {};
232
+ const requireModules = [];
233
+ const envFiles = [];
234
+ const watchPaths = [];
235
+ let watchExt;
236
+ let watchDelay;
237
+ let tsconfigPath;
238
+ let appPath;
239
+ for (let index = 0; index < argv.length; index += 1) {
240
+ const arg = argv[index];
241
+ if (arg === "--") {
242
+ for (const positional of argv.slice(index + 1)) {
243
+ appPath = addPositional(positional, appPath, "App module");
244
+ }
245
+ break;
246
+ }
247
+ if (isHelp(arg) || isVersion(arg)) {
248
+ continue;
249
+ }
250
+ if (arg === "--port") {
251
+ options.port = parsePortFlag(readValue(argv, index, arg));
252
+ index += 1;
253
+ continue;
254
+ }
255
+ if (arg.startsWith("--port=")) {
256
+ options.port = parsePortFlag(readInlineValue(arg, "--port=", "--port"));
257
+ continue;
258
+ }
259
+ if (arg === "--host") {
260
+ options.host = readValue(argv, index, arg);
261
+ index += 1;
262
+ continue;
263
+ }
264
+ if (arg.startsWith("--host=")) {
265
+ options.host = readInlineValue(arg, "--host=", "--host");
266
+ continue;
267
+ }
268
+ if (arg === "--no-signals") {
269
+ options.signals = false;
270
+ continue;
271
+ }
272
+ if (arg === "--shutdown-timeout") {
273
+ options.shutdownTimeout = parseIntegerFlag(readValue(argv, index, arg), "--shutdown-timeout");
274
+ index += 1;
275
+ continue;
276
+ }
277
+ if (arg.startsWith("--shutdown-timeout=")) {
278
+ options.shutdownTimeout = parseIntegerFlag(
279
+ readInlineValue(arg, "--shutdown-timeout=", "--shutdown-timeout"),
280
+ "--shutdown-timeout"
281
+ );
282
+ continue;
283
+ }
284
+ if (arg === "--require") {
285
+ index = parseRepeatable(argv, index, arg, requireModules);
286
+ continue;
287
+ }
288
+ if (arg.startsWith("--require=")) {
289
+ requireModules.push(...parseCsvFlagValue(readInlineValue(arg, "--require=", "--require"), "--require"));
290
+ continue;
291
+ }
292
+ if (arg === "--env") {
293
+ index = parseRepeatable(argv, index, arg, envFiles);
294
+ continue;
295
+ }
296
+ if (arg.startsWith("--env=")) {
297
+ envFiles.push(...parseCsvFlagValue(readInlineValue(arg, "--env=", "--env"), "--env"));
298
+ continue;
299
+ }
300
+ if (arg === "--tsconfig") {
301
+ tsconfigPath = setTsconfigPath(tsconfigPath, readValue(argv, index, arg));
302
+ index += 1;
303
+ continue;
304
+ }
305
+ if (arg.startsWith("--tsconfig=")) {
306
+ tsconfigPath = setTsconfigPath(tsconfigPath, readInlineValue(arg, "--tsconfig=", "--tsconfig"));
307
+ continue;
308
+ }
309
+ if (arg === "--watch") {
310
+ index = parseRepeatable(argv, index, arg, watchPaths);
311
+ continue;
312
+ }
313
+ if (arg.startsWith("--watch=")) {
314
+ watchPaths.push(...parseCsvFlagValue(readInlineValue(arg, "--watch=", "--watch"), "--watch"));
315
+ continue;
316
+ }
317
+ if (arg === "--ext") {
318
+ watchExt = [];
319
+ index = parseRepeatable(argv, index, arg, watchExt);
320
+ continue;
321
+ }
322
+ if (arg.startsWith("--ext=")) {
323
+ watchExt = [];
324
+ watchExt.push(...parseCsvFlagValue(readInlineValue(arg, "--ext=", "--ext"), "--ext"));
325
+ continue;
326
+ }
327
+ if (arg === "--delay") {
328
+ watchDelay = parseIntegerFlag(readValue(argv, index, arg), "--delay");
329
+ index += 1;
330
+ continue;
331
+ }
332
+ if (arg.startsWith("--delay=")) {
333
+ watchDelay = parseIntegerFlag(readInlineValue(arg, "--delay=", "--delay"), "--delay");
334
+ continue;
335
+ }
336
+ if (!arg.startsWith("--")) {
337
+ appPath = addPositional(arg, appPath, "App module");
338
+ continue;
339
+ }
340
+ throw new Error(`Unknown argument: ${arg}`);
341
+ }
342
+ if (!appPath) {
343
+ printHelp();
344
+ throw new Error("Missing required argument: <app-module>");
345
+ }
346
+ return {
347
+ appPath,
348
+ options,
349
+ tsconfigPath,
350
+ require: requireModules,
351
+ env: envFiles,
352
+ watch: watchPaths,
353
+ watchExt: watchExt ?? DEFAULT_WATCH_EXTENSIONS,
354
+ watchDelay: watchDelay ?? DEFAULT_WATCH_DELAY
355
+ };
356
+ }
357
+ function parseStartLikeArgs(argv, subcommandName) {
358
+ for (const arg of optionArgs(argv)) {
359
+ if (arg === "--watch" || arg.startsWith("--watch=") || arg === "--tsconfig" || arg.startsWith("--tsconfig=") || arg === "--ext" || arg.startsWith("--ext=") || arg === "--delay" || arg.startsWith("--delay=")) {
360
+ throw new Error(`--watch/--tsconfig/--ext/--delay are not supported with the ${subcommandName} subcommand`);
361
+ }
362
+ }
363
+ return parseDevArgs(argv);
364
+ }
365
+ function parseStartArgs(argv) {
366
+ const result = parseStartLikeArgs(argv, "start");
367
+ return {
368
+ appPath: result.appPath,
369
+ options: result.options,
370
+ require: result.require,
371
+ env: result.env
372
+ };
373
+ }
374
+ function parseStartServerlessArgs(argv) {
375
+ let maxBodyBytes;
376
+ const filtered = [];
377
+ for (let i = 0; i < argv.length; i += 1) {
378
+ const arg = argv[i];
379
+ if (arg === "--") {
380
+ filtered.push(...argv.slice(i));
381
+ break;
382
+ }
383
+ if (arg === "--max-body-bytes") {
384
+ maxBodyBytes = parseIntegerFlag(readValue(argv, i, arg), "--max-body-bytes");
385
+ validateMaxBodyBytes(maxBodyBytes);
386
+ i += 1;
387
+ continue;
388
+ }
389
+ if (arg.startsWith("--max-body-bytes=")) {
390
+ maxBodyBytes = parseIntegerFlag(
391
+ readInlineValue(arg, "--max-body-bytes=", "--max-body-bytes"),
392
+ "--max-body-bytes"
393
+ );
394
+ validateMaxBodyBytes(maxBodyBytes);
395
+ continue;
396
+ }
397
+ filtered.push(arg);
398
+ }
399
+ const result = parseStartLikeArgs(filtered, "start-serverless");
400
+ return {
401
+ handlerPath: result.appPath,
402
+ options: result.options,
403
+ maxBodyBytes,
404
+ require: result.require,
405
+ env: result.env
406
+ };
407
+ }
408
+ function parseBuildArgs(argv, outNameDefault) {
409
+ let appPath;
410
+ const external = [];
411
+ const result = {
412
+ initPath: void 0,
413
+ tsconfigPath: void 0,
414
+ outDir: "dist",
415
+ outName: outNameDefault,
416
+ format: "cjs",
417
+ target: "node22",
418
+ external,
419
+ clean: true
420
+ };
421
+ for (let index = 0; index < argv.length; index += 1) {
422
+ const arg = argv[index];
423
+ if (arg === "--") {
424
+ for (const positional of argv.slice(index + 1)) {
425
+ appPath = addPositional(positional, appPath, "App module");
426
+ }
427
+ break;
428
+ }
429
+ if (isHelp(arg) || isVersion(arg)) {
430
+ continue;
431
+ }
432
+ if (arg === "--init") {
433
+ result.initPath = readValue(argv, index, arg);
434
+ index += 1;
435
+ continue;
436
+ }
437
+ if (arg.startsWith("--init=")) {
438
+ result.initPath = readInlineValue(arg, "--init=", "--init");
439
+ continue;
440
+ }
441
+ if (arg === "--tsconfig") {
442
+ result.tsconfigPath = setTsconfigPath(result.tsconfigPath, readValue(argv, index, arg));
443
+ index += 1;
444
+ continue;
445
+ }
446
+ if (arg.startsWith("--tsconfig=")) {
447
+ result.tsconfigPath = setTsconfigPath(result.tsconfigPath, readInlineValue(arg, "--tsconfig=", "--tsconfig"));
448
+ continue;
449
+ }
450
+ if (arg === "--out-dir") {
451
+ result.outDir = readValue(argv, index, arg);
452
+ index += 1;
453
+ continue;
454
+ }
455
+ if (arg.startsWith("--out-dir=")) {
456
+ result.outDir = readInlineValue(arg, "--out-dir=", "--out-dir");
457
+ continue;
458
+ }
459
+ if (arg === "--out-name") {
460
+ result.outName = readValue(argv, index, arg);
461
+ index += 1;
462
+ continue;
463
+ }
464
+ if (arg.startsWith("--out-name=")) {
465
+ result.outName = readInlineValue(arg, "--out-name=", "--out-name");
466
+ continue;
467
+ }
468
+ if (arg === "--format") {
469
+ const fmt = readValue(argv, index, arg);
470
+ if (fmt !== "cjs" && fmt !== "esm") {
471
+ throw new Error(`Invalid --format: ${fmt}. Must be 'cjs' or 'esm'.`);
472
+ }
473
+ result.format = fmt;
474
+ index += 1;
475
+ continue;
476
+ }
477
+ if (arg.startsWith("--format=")) {
478
+ const fmt = readInlineValue(arg, "--format=", "--format");
479
+ if (fmt !== "cjs" && fmt !== "esm") {
480
+ throw new Error(`Invalid --format: ${fmt}. Must be 'cjs' or 'esm'.`);
481
+ }
482
+ result.format = fmt;
483
+ continue;
484
+ }
485
+ if (arg === "--target") {
486
+ result.target = readValue(argv, index, arg);
487
+ index += 1;
488
+ continue;
489
+ }
490
+ if (arg.startsWith("--target=")) {
491
+ result.target = readInlineValue(arg, "--target=", "--target");
492
+ continue;
493
+ }
494
+ if (arg === "--external") {
495
+ external.push(readValue(argv, index, arg));
496
+ index += 1;
497
+ continue;
498
+ }
499
+ if (arg.startsWith("--external=")) {
500
+ external.push(readInlineValue(arg, "--external=", "--external"));
501
+ continue;
502
+ }
503
+ if (arg === "--no-clean") {
504
+ result.clean = false;
505
+ continue;
506
+ }
507
+ if (!arg.startsWith("--")) {
508
+ appPath = addPositional(arg, appPath, "App module");
509
+ continue;
510
+ }
511
+ throw new Error(`Unknown argument: ${arg}`);
512
+ }
513
+ if (!appPath) {
514
+ printHelp();
515
+ throw new Error("Missing required argument: <app-module>");
516
+ }
517
+ return { appPath, ...result };
518
+ }
519
+ function parseLocalBuildArgs(argv) {
520
+ return parseBuildArgs(argv, "app");
521
+ }
522
+ function parseBuildServerlessArgs(argv) {
523
+ return parseBuildArgs(argv, "handler");
524
+ }
525
+ function parseArgs(argv) {
526
+ if (argv.length === 0) {
527
+ printHelp();
528
+ return null;
529
+ }
530
+ const globalArgs = optionArgs(argv);
531
+ if (globalArgs.some((a) => isHelp(a))) {
532
+ printHelp();
533
+ return null;
534
+ }
535
+ if (globalArgs.some((a) => isVersion(a))) {
536
+ console.log(CLI_VERSION);
537
+ return null;
538
+ }
539
+ const first = argv[0];
540
+ if (isSubcommand(first)) {
541
+ const rest = argv.slice(1);
542
+ if (first === "dev") {
543
+ return { subcommand: "dev", dev: parseDevArgs(rest) };
544
+ }
545
+ if (first === "build") {
546
+ return { subcommand: "build", build: parseLocalBuildArgs(rest) };
547
+ }
548
+ if (first === "start") {
549
+ return { subcommand: "start", start: parseStartArgs(rest) };
550
+ }
551
+ if (first === "build-serverless") {
552
+ return { subcommand: "build-serverless", buildServerless: parseBuildServerlessArgs(rest) };
553
+ }
554
+ return { subcommand: "start-serverless", startServerless: parseStartServerlessArgs(rest) };
555
+ }
556
+ return { subcommand: "dev", dev: parseDevArgs(argv) };
557
+ }
558
+ function isExpressApp(x) {
559
+ if (x === null || x === void 0) return false;
560
+ const t = typeof x;
561
+ if (t !== "object" && t !== "function") return false;
562
+ return typeof x.listen === "function" && typeof x.use === "function";
563
+ }
564
+ function extractExport(mod) {
565
+ return mod.default ?? mod.app;
566
+ }
567
+ async function resolveExport(exported, appPath) {
568
+ if (isExpressApp(exported)) {
569
+ return exported;
570
+ }
571
+ if (typeof exported === "function") {
572
+ const result = await exported();
573
+ if (!isExpressApp(result)) {
574
+ throw new Error(`Function in "${appPath}" did not return an Express app.`);
575
+ }
576
+ return result;
577
+ }
578
+ throw new Error(`Default export of "${appPath}" is not an Express app or an async function returning one.`);
579
+ }
580
+ async function loadApp(appPath) {
581
+ const fullPath = pathResolve(process.cwd(), appPath);
582
+ const moduleUrl = pathToFileURL(fullPath).href;
583
+ const mod = await import(moduleUrl);
584
+ const exported = extractExport(mod);
585
+ if (!exported) {
586
+ throw new Error(
587
+ `Module "${appPath}" must default-export an Express app or an async function returning one. Exports: ${Object.keys(mod).join(", ")}`
588
+ );
589
+ }
590
+ return resolveExport(exported, appPath);
591
+ }
592
+ function parseEnvFile(content) {
593
+ const result = {};
594
+ for (const line of content.split("\n")) {
595
+ let trimmed = line.trim();
596
+ if (!trimmed || trimmed.startsWith("#")) continue;
597
+ if (trimmed.startsWith("export ")) trimmed = trimmed.slice("export ".length).trim();
598
+ const eqIndex = trimmed.indexOf("=");
599
+ if (eqIndex === -1) continue;
600
+ const key = trimmed.slice(0, eqIndex).trim();
601
+ let value = trimmed.slice(eqIndex + 1).trim();
602
+ if (value.length >= 2) {
603
+ const first = value[0];
604
+ const last = value[value.length - 1];
605
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
606
+ value = value.slice(1, -1);
607
+ }
608
+ }
609
+ result[key] = value;
610
+ }
611
+ return result;
612
+ }
613
+ function loadEnvFiles(paths) {
614
+ for (const p of paths) {
615
+ const absPath = pathResolve(process.cwd(), p);
616
+ if (!existsSync(absPath)) {
617
+ throw new Error(`Env file not found: ${p}`);
618
+ }
619
+ const content = readFileSync(absPath, "utf8");
620
+ const parsed = parseEnvFile(content);
621
+ for (const [key, value] of Object.entries(parsed)) {
622
+ if (process.env[key] === void 0) {
623
+ process.env[key] = value;
624
+ }
625
+ }
626
+ }
627
+ }
628
+ var moduleRequire = createRequire(
629
+ pathToFileURL(pathResolve(process.cwd(), "__wtt_runtime_preload__.js"))
630
+ );
631
+ async function preloadModules(modules) {
632
+ for (const mod of modules) {
633
+ moduleRequire(mod);
634
+ }
635
+ }
636
+ var DEFAULT_WATCH_KILL_TIMEOUT_MS = 5e3;
637
+ function toDiagnosticMessage(prefix, error) {
638
+ const message = error instanceof Error ? error.message : String(error);
639
+ return `${prefix}: ${message}`;
640
+ }
641
+ function createWatchSupervisor(args, deps = {}) {
642
+ const forkImpl = deps.fork ?? fork;
643
+ const watchImpl = deps.watch ?? watch;
644
+ const existsSyncImpl = deps.existsSync ?? existsSync;
645
+ const logger = deps.logger ?? console;
646
+ const setTimeoutImpl = deps.setTimeout ?? setTimeout;
647
+ const clearTimeoutImpl = deps.clearTimeout ?? clearTimeout;
648
+ const killTimeoutMs = deps.killTimeoutMs ?? DEFAULT_WATCH_KILL_TIMEOUT_MS;
649
+ const cliPath = process.argv[1];
650
+ const childArgv = buildChildArgs(args);
651
+ let child = null;
652
+ let restartTimer = null;
653
+ let killTimer = null;
654
+ let isShuttingDown = false;
655
+ let shutdownPromise = null;
656
+ let restartInFlight = null;
657
+ let terminatingChild = null;
658
+ let failureHandled = false;
659
+ const restartDelay = args.watchDelay;
660
+ const watchers = [];
661
+ const clearRestartTimer = () => {
662
+ if (restartTimer) {
663
+ clearTimeoutImpl(restartTimer);
664
+ restartTimer = null;
665
+ }
666
+ };
667
+ const clearKillTimer = () => {
668
+ if (killTimer) {
669
+ clearTimeoutImpl(killTimer);
670
+ killTimer = null;
671
+ }
672
+ };
673
+ const closeWatchers = () => {
674
+ for (const watcher of watchers.splice(0)) {
675
+ try {
676
+ watcher.close();
677
+ } catch (_error) {
678
+ void _error;
679
+ }
680
+ }
681
+ };
682
+ const completeWithExit = async (code) => {
683
+ await shutdown();
684
+ deps.exit?.(code);
685
+ };
686
+ const fail = (message, code = 1) => {
687
+ if (failureHandled) return;
688
+ failureHandled = true;
689
+ logger.error(message);
690
+ void completeWithExit(code).catch(() => {
691
+ deps.exit?.(code);
692
+ });
693
+ };
694
+ const spawnChild = () => {
695
+ if (isShuttingDown) return;
696
+ let nextChild;
697
+ try {
698
+ nextChild = forkImpl(cliPath, childArgv, { stdio: "inherit" });
699
+ } catch (error) {
700
+ fail(toDiagnosticMessage("Watch child failed to spawn", error));
701
+ return;
702
+ }
703
+ child = nextChild;
704
+ nextChild.once("error", (error) => {
705
+ if (child === nextChild) {
706
+ child = null;
707
+ }
708
+ fail(toDiagnosticMessage("Watch child process error", error));
709
+ });
710
+ nextChild.once("exit", (code, signal) => {
711
+ if (child === nextChild) {
712
+ child = null;
713
+ }
714
+ if (terminatingChild === nextChild || isShuttingDown || failureHandled) {
715
+ return;
716
+ }
717
+ const exitCode = typeof code === "number" && code > 0 ? code : 1;
718
+ fail(`Watch child exited unexpectedly${signal ? ` from ${signal}` : ` with code ${String(code)}`}`, exitCode);
719
+ });
720
+ };
721
+ const killChild = async (target = child) => {
722
+ if (!target || !target.pid) {
723
+ if (target && child === target) child = null;
724
+ return;
725
+ }
726
+ terminatingChild = target;
727
+ await new Promise((resolve, reject) => {
728
+ let settled = false;
729
+ const settle = (error) => {
730
+ if (settled) return;
731
+ settled = true;
732
+ clearKillTimer();
733
+ target.removeListener("exit", onExit);
734
+ target.removeListener("error", onError);
735
+ if (child === target) child = null;
736
+ if (terminatingChild === target) terminatingChild = null;
737
+ if (error) reject(error);
738
+ else resolve();
739
+ };
740
+ const onExit = () => settle();
741
+ const onError = (error) => settle(error);
742
+ target.once("exit", onExit);
743
+ target.once("error", onError);
744
+ try {
745
+ const signaled = target.kill("SIGTERM");
746
+ if (!signaled) {
747
+ throw new Error('child.kill("SIGTERM") returned false');
748
+ }
749
+ } catch (error) {
750
+ settle(error instanceof Error ? error : new Error(String(error)));
751
+ return;
752
+ }
753
+ if (!settled) {
754
+ killTimer = setTimeoutImpl(() => {
755
+ try {
756
+ const signaled = target.kill("SIGKILL");
757
+ if (!signaled) {
758
+ settle(new Error('child.kill("SIGKILL") returned false'));
759
+ }
760
+ } catch (error) {
761
+ settle(error instanceof Error ? error : new Error(String(error)));
762
+ }
763
+ }, killTimeoutMs);
764
+ }
765
+ });
766
+ };
767
+ const restart = async () => {
768
+ if (isShuttingDown) return;
769
+ if (restartInFlight) {
770
+ await restartInFlight;
771
+ return;
772
+ }
773
+ restartInFlight = (async () => {
774
+ if (isShuttingDown) return;
775
+ await killChild();
776
+ if (isShuttingDown) return;
777
+ spawnChild();
778
+ })();
779
+ try {
780
+ await restartInFlight;
781
+ } finally {
782
+ restartInFlight = null;
783
+ }
784
+ };
785
+ const debouncedRestart = () => {
786
+ if (isShuttingDown) return;
787
+ clearRestartTimer();
788
+ restartTimer = setTimeoutImpl(() => {
789
+ restartTimer = null;
790
+ void restart().catch((error) => {
791
+ fail(toDiagnosticMessage("Watch restart failed", error));
792
+ });
793
+ }, restartDelay);
794
+ };
795
+ for (const watchPath of args.watch) {
796
+ const absPath = pathResolve(process.cwd(), watchPath);
797
+ if (!existsSyncImpl(absPath)) {
798
+ throw new Error(`Watch path not found: ${watchPath}`);
799
+ }
800
+ }
801
+ const shutdown = async () => {
802
+ if (shutdownPromise) return shutdownPromise;
803
+ isShuttingDown = true;
804
+ clearRestartTimer();
805
+ closeWatchers();
806
+ shutdownPromise = (async () => {
807
+ const activeRestart = restartInFlight;
808
+ if (activeRestart) {
809
+ await activeRestart.catch(() => void 0);
810
+ }
811
+ const activeChild = child;
812
+ if (activeChild) {
813
+ await killChild(activeChild).catch((error) => {
814
+ if (!failureHandled) {
815
+ fail(toDiagnosticMessage("Watch child termination failed", error));
816
+ }
817
+ });
818
+ }
819
+ })();
820
+ return shutdownPromise;
821
+ };
822
+ try {
823
+ for (const watchPath of args.watch) {
824
+ const absPath = pathResolve(process.cwd(), watchPath);
825
+ const watchListener = (_eventType, filename) => {
826
+ if (isShuttingDown) return;
827
+ if (!filename) return;
828
+ const ext = extname(filename).slice(1).toLowerCase();
829
+ if (args.watchExt.includes(ext)) {
830
+ debouncedRestart();
831
+ }
832
+ };
833
+ let watcher;
834
+ try {
835
+ watcher = watchImpl(absPath, { recursive: true }, watchListener);
836
+ } catch (error) {
837
+ if (error.code !== "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM") {
838
+ throw error;
839
+ }
840
+ watcher = watchImpl(absPath, watchListener);
841
+ }
842
+ watcher.on?.(
843
+ "error",
844
+ (error) => {
845
+ fail(toDiagnosticMessage("Watch path runtime error", error));
846
+ }
847
+ );
848
+ watchers.push(watcher);
849
+ }
850
+ spawnChild();
851
+ } catch (error) {
852
+ clearRestartTimer();
853
+ closeWatchers();
854
+ if (child) {
855
+ void killChild(child).catch(() => void 0);
856
+ }
857
+ throw error;
858
+ }
859
+ return {
860
+ shutdown,
861
+ getChild: () => child,
862
+ getWatchers: () => [...watchers],
863
+ isShuttingDown: () => isShuttingDown
864
+ };
865
+ }
866
+ function buildChildArgs(args) {
867
+ const result = ["dev", args.appPath];
868
+ if (args.options.port !== void 0) result.push("--port", String(args.options.port));
869
+ if (args.options.host !== void 0) result.push("--host", args.options.host);
870
+ if (args.options.signals === false) result.push("--no-signals");
871
+ if (args.options.shutdownTimeout !== void 0)
872
+ result.push("--shutdown-timeout", String(args.options.shutdownTimeout));
873
+ if (args.tsconfigPath !== void 0) result.push("--tsconfig", args.tsconfigPath);
874
+ for (const r of args.require) result.push("--require", r);
875
+ for (const e of args.env) result.push("--env", e);
876
+ return result;
877
+ }
878
+ function runWithWatch(args, deps = {}) {
879
+ const usingInjectedDeps = Object.keys(deps).length > 0;
880
+ const installSignalHandlers = deps.installSignalHandlers ?? !usingInjectedDeps;
881
+ const exitImpl = deps.exit ?? (usingInjectedDeps ? void 0 : (code) => process.exit(code));
882
+ const controller = createWatchSupervisor(args, {
883
+ ...deps,
884
+ exit: exitImpl
885
+ });
886
+ let shutdownStarted = false;
887
+ const ownedHandlers = [];
888
+ const removeOwnedHandlers = () => {
889
+ for (const [signal, handler] of ownedHandlers.splice(0)) {
890
+ process.removeListener(signal, handler);
891
+ }
892
+ };
893
+ const shutdown = async () => {
894
+ removeOwnedHandlers();
895
+ await controller.shutdown();
896
+ };
897
+ const wrappedController = {
898
+ shutdown,
899
+ getChild: controller.getChild,
900
+ getWatchers: controller.getWatchers,
901
+ isShuttingDown: controller.isShuttingDown
902
+ };
903
+ if (installSignalHandlers) {
904
+ const shutdownAndExit = () => {
905
+ if (shutdownStarted) return;
906
+ shutdownStarted = true;
907
+ void shutdown().then(() => exitImpl?.(0));
908
+ };
909
+ ownedHandlers.push(["SIGINT", shutdownAndExit], ["SIGTERM", shutdownAndExit]);
910
+ process.on("SIGINT", shutdownAndExit);
911
+ process.on("SIGTERM", shutdownAndExit);
912
+ }
913
+ return wrappedController;
914
+ }
915
+ var TEMP_BUILD_ENTRY_FILENAME = ".express-runtime-build-entry.ts";
916
+ var TEMP_SERVERLESS_ENTRY_FILENAME = ".express-runtime-build-serverless-entry.ts";
917
+ var STAGING_DIR_PREFIX = ".wtt-build-";
918
+ function generateServerlessEntry(appPath, initPath) {
919
+ const absAppPath = pathResolve(process.cwd(), appPath);
920
+ const absInitPath = initPath ? pathResolve(process.cwd(), initPath) : void 0;
921
+ const lines = [
922
+ "// Auto-generated by @web-ts-toolkit/express-runtime CLI \u2014 do not edit.",
923
+ `import { createServerlessHandler } from '@web-ts-toolkit/express-runtime';`,
924
+ `import app from ${JSON.stringify(absAppPath)};`
925
+ ];
926
+ if (absInitPath) {
927
+ lines.push(`import init from ${JSON.stringify(absInitPath)};`);
928
+ lines.push(`const handler = createServerlessHandler(app, { init });`);
929
+ } else {
930
+ lines.push(`const handler = createServerlessHandler(app);`);
931
+ }
932
+ lines.push(`export { handler };`);
933
+ return lines.join("\n") + "\n";
934
+ }
935
+ function generateRuntimeEntry(appPath, initPath) {
936
+ const absAppPath = pathResolve(process.cwd(), appPath);
937
+ const absInitPath = initPath ? pathResolve(process.cwd(), initPath) : void 0;
938
+ const lines = [
939
+ "// Auto-generated by @web-ts-toolkit/express-runtime CLI \u2014 do not edit.",
940
+ `import app from ${JSON.stringify(absAppPath)};`,
941
+ "export default app;",
942
+ "export { app };"
943
+ ];
944
+ if (absInitPath) {
945
+ lines.push(`export { default as init } from ${JSON.stringify(absInitPath)};`);
946
+ }
947
+ return lines.join("\n") + "\n";
948
+ }
949
+ function validateOutDirForClean(outDir, clean, appPath, initPath) {
950
+ if (!clean) return;
951
+ const cwd = process.cwd();
952
+ const outAbs = pathResolve(cwd, outDir);
953
+ const normalized = pathNormalize(outAbs);
954
+ const root = pathParse(normalized).root;
955
+ if (normalized === root) {
956
+ throw new Error(`Refusing to clean filesystem root: ${outDir} resolves to ${normalized}`);
957
+ }
958
+ if (normalized === pathNormalize(cwd)) {
959
+ throw new Error(`Refusing to clean project directory: ${outDir} resolves to cwd ${cwd}`);
960
+ }
961
+ if (cwd !== root && (cwd === normalized || cwd.startsWith(normalized + pathSep))) {
962
+ throw new Error(
963
+ `Refusing to clean ancestor of project directory: ${outDir} resolves to ${normalized} which contains cwd ${cwd}`
964
+ );
965
+ }
966
+ try {
967
+ if (existsSync(outAbs)) {
968
+ const st = lstatSync(outAbs);
969
+ if (st.isSymbolicLink()) {
970
+ throw new Error(`Refusing to clean symlinked outDir: ${outDir} resolves to symlink ${outAbs}`);
971
+ }
972
+ }
973
+ } catch (e) {
974
+ if (e.message.startsWith("Refusing to clean")) throw e;
975
+ }
976
+ const checkOverlap = (inputPath, label) => {
977
+ if (!inputPath) return;
978
+ const inputAbs = pathResolve(cwd, inputPath);
979
+ const inputNorm = pathNormalize(inputAbs);
980
+ if (inputNorm === normalized) {
981
+ throw new Error(`Refusing to clean outDir that is the same as ${label}: ${outDir} == ${inputPath}`);
982
+ }
983
+ if (inputNorm.startsWith(normalized + pathSep)) {
984
+ throw new Error(`Refusing to clean outDir that contains ${label}: ${outDir} contains ${inputPath}`);
985
+ }
986
+ };
987
+ checkOverlap(appPath, "appPath");
988
+ checkOverlap(initPath, "initPath");
989
+ }
990
+ function createUniqueStagingDir() {
991
+ const cwd = process.cwd();
992
+ const prefix = pathJoin(cwd, STAGING_DIR_PREFIX);
993
+ const dir = mkdtempSync(prefix);
994
+ try {
995
+ const st = lstatSync(dir);
996
+ if (st.isSymbolicLink()) {
997
+ rmSync(dir, { recursive: true, force: true });
998
+ throw new Error(`Staging directory is a symlink: ${dir}`);
999
+ }
1000
+ } catch (e) {
1001
+ if (e.message.includes("Staging directory is a symlink")) throw e;
1002
+ throw e;
1003
+ }
1004
+ try {
1005
+ chmodSync(dir, 448);
1006
+ } catch {
1007
+ }
1008
+ return dir;
1009
+ }
1010
+ function writeStagingEntry(dir, content) {
1011
+ const entryPath = pathJoin(dir, "entry.ts");
1012
+ try {
1013
+ if (existsSync(entryPath)) {
1014
+ const st = lstatSync(entryPath);
1015
+ if (st.isSymbolicLink()) {
1016
+ throw new Error(`Refusing to overwrite symlink at staging path: ${entryPath}`);
1017
+ }
1018
+ throw new Error(`Staging file already exists: ${entryPath}`);
1019
+ }
1020
+ } catch (e) {
1021
+ if (e.message.startsWith("Refusing to") || e.message.startsWith("Staging file already exists"))
1022
+ throw e;
1023
+ }
1024
+ writeFileSync(entryPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
1025
+ try {
1026
+ const st = lstatSync(entryPath);
1027
+ if (st.isSymbolicLink()) {
1028
+ rmSync(entryPath, { force: true });
1029
+ throw new Error(`Staging file is a symlink after write: ${entryPath}`);
1030
+ }
1031
+ } catch (e) {
1032
+ if (e.message.includes("Staging file is a symlink")) throw e;
1033
+ }
1034
+ return entryPath;
1035
+ }
1036
+ async function buildBundleFromEntryContent(args) {
1037
+ validateOutDirForClean(args.outDir, args.clean);
1038
+ const tsupModule = await import("tsup");
1039
+ const { build } = tsupModule;
1040
+ const stagingDir = createUniqueStagingDir();
1041
+ const tempEntryPath = writeStagingEntry(stagingDir, args.entryContent);
1042
+ const absOutDir = pathResolve(process.cwd(), args.outDir);
1043
+ try {
1044
+ await build({
1045
+ config: false,
1046
+ entry: { [args.outName]: tempEntryPath },
1047
+ tsconfig: args.tsconfigPath,
1048
+ format: [args.format],
1049
+ target: args.target,
1050
+ outDir: absOutDir,
1051
+ clean: args.clean,
1052
+ external: ["express", "@web-ts-toolkit/express-runtime", ...args.external],
1053
+ sourcemap: false,
1054
+ dts: false,
1055
+ splitting: false
1056
+ });
1057
+ } finally {
1058
+ rmSync(stagingDir, { recursive: true, force: true });
1059
+ }
1060
+ }
1061
+ async function buildRuntime(args) {
1062
+ const { runBuildEntryCommand } = await import("./cli-api.mjs");
1063
+ await runBuildEntryCommand(args, {
1064
+ generateEntry: generateRuntimeEntry
1065
+ });
1066
+ }
1067
+ async function buildServerless(args) {
1068
+ const { runBuildEntryCommand } = await import("./cli-api.mjs");
1069
+ await runBuildEntryCommand(args, {
1070
+ generateEntry: generateServerlessEntry
1071
+ });
1072
+ }
1073
+ var DEFAULT_ADAPTER_MAX_BODY_BYTES = 1024 * 1024;
1074
+ function validateMaxBodyBytes(value) {
1075
+ try {
1076
+ return validateFiniteInteger(value, { name: "--max-body-bytes", min: 0, max: MAX_INTEGER_OPTION_VALUE });
1077
+ } catch (_error) {
1078
+ void _error;
1079
+ throw new Error(
1080
+ `Invalid --max-body-bytes: ${String(value)}. Must be a finite integer in 0..${MAX_INTEGER_OPTION_VALUE}. Use 0 to disallow bodies (empty bodies only).`,
1081
+ { cause: _error }
1082
+ );
1083
+ }
1084
+ }
1085
+ function collectBody(req, maxBytes) {
1086
+ validateMaxBodyBytes(maxBytes);
1087
+ return new Promise((resolve, reject) => {
1088
+ const rawLength = req.headers["content-length"];
1089
+ if (rawLength !== void 0) {
1090
+ const raw = Array.isArray(rawLength) ? rawLength[0] : rawLength;
1091
+ const parsed = Number(raw);
1092
+ if (Number.isFinite(parsed) && Number.isInteger(parsed) && parsed >= 0) {
1093
+ if (parsed > maxBytes) {
1094
+ try {
1095
+ req.resume?.();
1096
+ } catch (_e) {
1097
+ void _e;
1098
+ }
1099
+ const err = new Error(
1100
+ `Request body too large: Content-Length ${parsed} exceeds limit ${maxBytes}`
1101
+ );
1102
+ err.code = "LIMIT_EXCEEDED";
1103
+ err.statusCode = 413;
1104
+ reject(err);
1105
+ return;
1106
+ }
1107
+ }
1108
+ }
1109
+ const chunks = [];
1110
+ let total = 0;
1111
+ let finished = false;
1112
+ const cleanup = () => {
1113
+ req.removeListener("data", onData);
1114
+ req.removeListener("end", onEnd);
1115
+ req.removeListener("error", onError);
1116
+ req.removeListener("close", onClose);
1117
+ try {
1118
+ req.removeListener?.(
1119
+ "aborted",
1120
+ onClose
1121
+ );
1122
+ } catch (_e) {
1123
+ void _e;
1124
+ }
1125
+ };
1126
+ const fail = (err) => {
1127
+ if (finished) return;
1128
+ finished = true;
1129
+ cleanup();
1130
+ if (err.code === "LIMIT_EXCEEDED") {
1131
+ try {
1132
+ req.resume?.();
1133
+ } catch (_e) {
1134
+ void _e;
1135
+ }
1136
+ }
1137
+ reject(err);
1138
+ };
1139
+ const onData = (chunk) => {
1140
+ if (finished) return;
1141
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1142
+ total += buf.length;
1143
+ if (total > maxBytes) {
1144
+ const err = new Error(
1145
+ `Request body too large: received ${total} bytes exceeds limit ${maxBytes}`
1146
+ );
1147
+ err.code = "LIMIT_EXCEEDED";
1148
+ err.statusCode = 413;
1149
+ fail(err);
1150
+ return;
1151
+ }
1152
+ chunks.push(buf);
1153
+ };
1154
+ const onEnd = () => {
1155
+ if (finished) return;
1156
+ finished = true;
1157
+ cleanup();
1158
+ try {
1159
+ resolve(Buffer.concat(chunks, total));
1160
+ } catch (e) {
1161
+ fail(e);
1162
+ }
1163
+ };
1164
+ const onError = (err) => {
1165
+ const e = err;
1166
+ if (!e.code) e.code = "STREAM_ERROR";
1167
+ fail(e);
1168
+ };
1169
+ const onClose = () => {
1170
+ if (finished) return;
1171
+ const e = new Error("Request aborted by client");
1172
+ e.code = "CLIENT_ABORT";
1173
+ fail(e);
1174
+ };
1175
+ req.on("data", onData);
1176
+ req.on("end", onEnd);
1177
+ req.on("error", onError);
1178
+ req.on("close", onClose);
1179
+ try {
1180
+ req.on?.("aborted", onClose);
1181
+ } catch (_e) {
1182
+ void _e;
1183
+ }
1184
+ });
1185
+ }
1186
+ function toServerlessEvent(method, url, headers, body) {
1187
+ const parsedUrl = new URL(url, "http://localhost");
1188
+ const { queryStringParameters, multiValueQueryStringParameters } = parseAwsRestQuery(parsedUrl.search);
1189
+ const { singleValueHeaders, multiValueHeaders } = normalizeAwsRestHeaders(headers);
1190
+ return {
1191
+ httpMethod: method,
1192
+ path: parsedUrl.pathname,
1193
+ headers: singleValueHeaders,
1194
+ multiValueHeaders,
1195
+ queryStringParameters,
1196
+ multiValueQueryStringParameters,
1197
+ body: body.length > 0 ? body.toString("base64") : "",
1198
+ isBase64Encoded: body.length > 0,
1199
+ requestContext: {
1200
+ identity: {
1201
+ // Minimal field required by serverless-http's AWS v1 request adapter.
1202
+ sourceIp: ""
1203
+ }
1204
+ }
1205
+ };
1206
+ }
1207
+ function parseAwsRestQuery(search) {
1208
+ if (search === "" || search === "?") {
1209
+ return { queryStringParameters: null, multiValueQueryStringParameters: null };
1210
+ }
1211
+ const single = {};
1212
+ const multi = {};
1213
+ const query = search.startsWith("?") ? search.slice(1) : search;
1214
+ for (const pair of query.split("&")) {
1215
+ if (pair === "") continue;
1216
+ const separator = pair.indexOf("=");
1217
+ const rawKey = separator === -1 ? pair : pair.slice(0, separator);
1218
+ const rawValue = separator === -1 ? "" : pair.slice(separator + 1);
1219
+ const key = decodeQueryComponent(rawKey);
1220
+ const value = decodeQueryComponent(rawValue);
1221
+ single[key] = value;
1222
+ (multi[key] ??= []).push(value);
1223
+ }
1224
+ return {
1225
+ queryStringParameters: Object.keys(single).length > 0 ? single : null,
1226
+ multiValueQueryStringParameters: Object.keys(multi).length > 0 ? multi : null
1227
+ };
1228
+ }
1229
+ function decodeQueryComponent(value) {
1230
+ try {
1231
+ return decodeURIComponent(value);
1232
+ } catch (_error) {
1233
+ void _error;
1234
+ return value;
1235
+ }
1236
+ }
1237
+ function normalizeAwsRestHeaders(headers) {
1238
+ const singleValueHeaders = {};
1239
+ const multiValueHeaders = {};
1240
+ for (const [key, value] of Object.entries(headers)) {
1241
+ if (value === void 0) continue;
1242
+ const values = Array.isArray(value) ? value.map(String) : [String(value)];
1243
+ multiValueHeaders[key] = values;
1244
+ singleValueHeaders[key] = values.join(", ");
1245
+ }
1246
+ return { singleValueHeaders, multiValueHeaders };
1247
+ }
1248
+ function applyServerlessResult(result, res) {
1249
+ const response = validateServerlessResult(result);
1250
+ res.status(response.statusCode);
1251
+ for (const [key, value] of Object.entries(response.headers)) {
1252
+ res.setHeader(key, value);
1253
+ }
1254
+ for (const [key, values] of Object.entries(response.multiValueHeaders)) {
1255
+ if (key.toLowerCase() === "set-cookie") {
1256
+ res.setHeader(key, values);
1257
+ } else {
1258
+ res.setHeader(key, values.join(","));
1259
+ }
1260
+ }
1261
+ if (response.isBase64Encoded) {
1262
+ res.end(response.decodedBody);
1263
+ } else {
1264
+ res.end(response.body);
1265
+ }
1266
+ }
1267
+ function validateServerlessResult(result) {
1268
+ if (!isPlainRecord(result)) {
1269
+ throw new Error(
1270
+ "Invalid serverless result: expected an object with an optional statusCode, headers, multiValueHeaders, body, and isBase64Encoded."
1271
+ );
1272
+ }
1273
+ const rawStatus = result.statusCode;
1274
+ if (rawStatus !== void 0 && (typeof rawStatus !== "number" || !Number.isInteger(rawStatus) || rawStatus < 100 || rawStatus > 599)) {
1275
+ throw new Error(`Invalid serverless result statusCode: ${String(rawStatus)}. Expected an integer in 100..599.`);
1276
+ }
1277
+ const rawIsBase64Encoded = result.isBase64Encoded;
1278
+ if (rawIsBase64Encoded !== void 0 && typeof rawIsBase64Encoded !== "boolean") {
1279
+ throw new Error("Invalid serverless result isBase64Encoded: expected a boolean when provided.");
1280
+ }
1281
+ const rawBody = result.body;
1282
+ if (rawBody !== void 0 && typeof rawBody !== "string") {
1283
+ throw new Error(`Invalid serverless result body: expected a string when provided, received ${typeof rawBody}.`);
1284
+ }
1285
+ const headers = validateSingleValueHeaders(result.headers, "headers");
1286
+ const multiValueHeaders = validateMultiValueHeaders(result.multiValueHeaders, "multiValueHeaders");
1287
+ const multiHeaderKeys = new Set(Object.keys(multiValueHeaders).map((key) => key.toLowerCase()));
1288
+ for (const key of Object.keys(headers)) {
1289
+ if (multiHeaderKeys.has(key.toLowerCase())) {
1290
+ delete headers[key];
1291
+ }
1292
+ }
1293
+ const isBase64Encoded = rawIsBase64Encoded ?? false;
1294
+ const body = rawBody ?? "";
1295
+ let decodedBody;
1296
+ if (isBase64Encoded) {
1297
+ if (!isValidBase64(body)) {
1298
+ throw new Error("Invalid serverless result body: isBase64Encoded is true but body is not valid standard base64.");
1299
+ }
1300
+ decodedBody = Buffer.from(body, "base64");
1301
+ }
1302
+ return {
1303
+ statusCode: rawStatus ?? 200,
1304
+ headers,
1305
+ multiValueHeaders,
1306
+ body,
1307
+ decodedBody,
1308
+ isBase64Encoded
1309
+ };
1310
+ }
1311
+ function validateSingleValueHeaders(value, name) {
1312
+ if (value === void 0) return {};
1313
+ if (!isPlainRecord(value)) {
1314
+ throw new Error(`Invalid serverless result ${name}: expected an object of string header values.`);
1315
+ }
1316
+ const headers = {};
1317
+ for (const [key, headerValue] of Object.entries(value)) {
1318
+ if (headerValue === void 0) continue;
1319
+ if (typeof headerValue !== "string") {
1320
+ throw new Error(`Invalid serverless result ${name}.${key}: expected a string header value.`);
1321
+ }
1322
+ validateServerlessHeader(key, headerValue, `${name}.${key}`);
1323
+ headers[key] = headerValue;
1324
+ }
1325
+ return headers;
1326
+ }
1327
+ function validateMultiValueHeaders(value, name) {
1328
+ if (value === void 0) return {};
1329
+ if (!isPlainRecord(value)) {
1330
+ throw new Error(`Invalid serverless result ${name}: expected an object of string-array header values.`);
1331
+ }
1332
+ const headers = {};
1333
+ for (const [key, headerValue] of Object.entries(value)) {
1334
+ if (headerValue === void 0) continue;
1335
+ if (!Array.isArray(headerValue) || headerValue.some((entry) => typeof entry !== "string")) {
1336
+ throw new Error(`Invalid serverless result ${name}.${key}: expected an array of string header values.`);
1337
+ }
1338
+ for (const entry of headerValue) {
1339
+ validateServerlessHeader(key, entry, `${name}.${key}`);
1340
+ }
1341
+ headers[key] = headerValue;
1342
+ }
1343
+ return headers;
1344
+ }
1345
+ function validateServerlessHeader(key, value, label) {
1346
+ try {
1347
+ validateHeaderName(key);
1348
+ validateHeaderValue(key, value);
1349
+ } catch (error) {
1350
+ throw new Error(`Invalid serverless result header ${label}: ${error.message}`, { cause: error });
1351
+ }
1352
+ }
1353
+ function isValidBase64(value) {
1354
+ if (value === "") return true;
1355
+ if (value.length % 4 !== 0) return false;
1356
+ return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
1357
+ }
1358
+ function isPlainRecord(value) {
1359
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1360
+ }
1361
+ function createServerlessAdapterApp(handler, options = {}) {
1362
+ const maxBytes = options.maxBodyBytes ?? DEFAULT_ADAPTER_MAX_BODY_BYTES;
1363
+ validateMaxBodyBytes(maxBytes);
1364
+ return createExpressApp({
1365
+ json: false,
1366
+ urlencoded: false,
1367
+ finalize: (app) => {
1368
+ app.use(async (req, res) => {
1369
+ let body;
1370
+ try {
1371
+ body = await collectBody(req, maxBytes);
1372
+ } catch (err) {
1373
+ const e = err;
1374
+ if (e?.code === "LIMIT_EXCEEDED" || e?.statusCode === 413) {
1375
+ if (!res.headersSent && !res.writableEnded) {
1376
+ res.status(413).end("Payload Too Large");
1377
+ } else {
1378
+ try {
1379
+ res.end();
1380
+ } catch (_e) {
1381
+ void _e;
1382
+ }
1383
+ }
1384
+ return;
1385
+ }
1386
+ if (e?.code === "CLIENT_ABORT") {
1387
+ return;
1388
+ }
1389
+ console.error("Serverless adapter error:", e);
1390
+ if (!res.headersSent && !res.writableEnded) {
1391
+ res.status(500).end("Internal server error");
1392
+ } else {
1393
+ try {
1394
+ res.end();
1395
+ } catch (_e) {
1396
+ void _e;
1397
+ }
1398
+ }
1399
+ return;
1400
+ }
1401
+ let result;
1402
+ try {
1403
+ const event = toServerlessEvent(req.method, req.url, req.headers, body);
1404
+ result = await handler(event, {});
1405
+ } catch (e) {
1406
+ console.error("Serverless adapter error:", e);
1407
+ if (!res.headersSent && !res.writableEnded) res.status(500).end("Internal server error");
1408
+ return;
1409
+ }
1410
+ try {
1411
+ applyServerlessResult(result, res);
1412
+ } catch (e) {
1413
+ console.error("Invalid serverless handler result:", e);
1414
+ if (!res.headersSent && !res.writableEnded) res.status(500).end("Internal server error");
1415
+ }
1416
+ });
1417
+ },
1418
+ errorHandler: (error, _req, res, _next) => {
1419
+ console.error("Serverless adapter error:", error);
1420
+ res.status(500).end("Internal server error");
1421
+ }
1422
+ });
1423
+ }
1424
+ async function loadBuiltApp(appPath) {
1425
+ const fullPath = pathResolve(process.cwd(), appPath);
1426
+ const moduleUrl = pathToFileURL(fullPath).href;
1427
+ const mod = await import(moduleUrl);
1428
+ const exported = mod.app ?? mod.default;
1429
+ if (!exported) {
1430
+ throw new Error(
1431
+ `Module "${appPath}" must default-export an Express app or export it as "app". Exports: ${Object.keys(mod).join(", ")}`
1432
+ );
1433
+ }
1434
+ const init = mod.init;
1435
+ if (init !== void 0 && typeof init !== "function") {
1436
+ throw new Error(`Module "${appPath}" must export "init" as a function when present.`);
1437
+ }
1438
+ const shutdown = mod.shutdown;
1439
+ if (shutdown !== void 0 && typeof shutdown !== "function") {
1440
+ throw new Error(`Module "${appPath}" must export "shutdown" as a function when present.`);
1441
+ }
1442
+ return {
1443
+ app: await resolveExport(exported, appPath),
1444
+ init,
1445
+ shutdown
1446
+ };
1447
+ }
1448
+ async function loadHandler(handlerPath) {
1449
+ const fullPath = pathResolve(process.cwd(), handlerPath);
1450
+ const moduleUrl = pathToFileURL(fullPath).href;
1451
+ const mod = await import(moduleUrl);
1452
+ const exported = mod.handler ?? mod.default;
1453
+ if (typeof exported !== "function") {
1454
+ throw new Error(
1455
+ `Module "${handlerPath}" must export a "handler" function. Exports: ${Object.keys(mod).join(", ")}`
1456
+ );
1457
+ }
1458
+ return exported;
1459
+ }
1460
+
1461
+ export {
1462
+ resolveCliVersion,
1463
+ CLI_VERSION,
1464
+ readValue,
1465
+ printHelp,
1466
+ parseArgs,
1467
+ isExpressApp,
1468
+ extractExport,
1469
+ resolveExport,
1470
+ loadApp,
1471
+ parseEnvFile,
1472
+ loadEnvFiles,
1473
+ preloadModules,
1474
+ DEFAULT_WATCH_KILL_TIMEOUT_MS,
1475
+ createWatchSupervisor,
1476
+ buildChildArgs,
1477
+ runWithWatch,
1478
+ TEMP_BUILD_ENTRY_FILENAME,
1479
+ TEMP_SERVERLESS_ENTRY_FILENAME,
1480
+ generateServerlessEntry,
1481
+ generateRuntimeEntry,
1482
+ validateOutDirForClean,
1483
+ buildBundleFromEntryContent,
1484
+ buildRuntime,
1485
+ buildServerless,
1486
+ DEFAULT_ADAPTER_MAX_BODY_BYTES,
1487
+ validateMaxBodyBytes,
1488
+ collectBody,
1489
+ toServerlessEvent,
1490
+ applyServerlessResult,
1491
+ createServerlessAdapterApp,
1492
+ loadBuiltApp,
1493
+ loadHandler
1494
+ };