@web-ts-toolkit/express-runtime 0.40.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -66
- package/chunk-UPFG3S34.mjs +1494 -0
- package/chunk-VPFBKM2K.mjs +487 -0
- package/cli-api.d.mts +133 -29
- package/cli-api.d.ts +133 -29
- package/cli-api.js +1150 -196
- package/cli-api.mjs +51 -771
- package/cli-utils-4POUMJN7.mjs +69 -0
- package/cli.js +1143 -195
- package/index.d.mts +74 -33
- package/index.d.ts +74 -33
- package/index.js +314 -47
- package/index.mjs +7 -3
- package/package.json +12 -3
- package/chunk-SOW7OLVN.mjs +0 -221
package/cli-api.js
CHANGED
|
@@ -30,6 +30,45 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/numeric-validation.ts
|
|
34
|
+
function validateFiniteInteger(value, options) {
|
|
35
|
+
const min = options.min ?? Number.MIN_SAFE_INTEGER;
|
|
36
|
+
const max = options.max ?? MAX_INTEGER_OPTION_VALUE;
|
|
37
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value < min || value > max) {
|
|
38
|
+
throw new Error(`Invalid ${options.name}: ${String(value)}. Must be a finite integer in ${min}..${max}.`);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function parsePortValue(value, name) {
|
|
43
|
+
if (typeof value === "number") {
|
|
44
|
+
return validateFiniteInteger(value, { name, min: 0, max: 65535 });
|
|
45
|
+
}
|
|
46
|
+
if (value.trim() === "") {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Invalid ${name}: ${JSON.stringify(value)}. Must be a port number in 0..65535 or a named pipe path.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (value.trim() !== value) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid ${name}: ${JSON.stringify(value)}. Numeric ports must not contain surrounding whitespace.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (/^(0|[1-9]\d*)$/.test(value)) {
|
|
57
|
+
return validateFiniteInteger(Number(value), { name, min: 0, max: 65535 });
|
|
58
|
+
}
|
|
59
|
+
if (/^[+-]?(?:\d+|\d*\.\d+)(?:e[+-]?\d+)?$/i.test(value) || /^[+-]?(?:infinity|nan)$/i.test(value)) {
|
|
60
|
+
throw new Error(`Invalid ${name}: ${value}. Numeric ports must be canonical decimal integers in 0..65535.`);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
var MAX_INTEGER_OPTION_VALUE;
|
|
65
|
+
var init_numeric_validation = __esm({
|
|
66
|
+
"src/numeric-validation.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
MAX_INTEGER_OPTION_VALUE = Number.MAX_SAFE_INTEGER;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
33
72
|
// src/index.ts
|
|
34
73
|
function applySettings(app, options) {
|
|
35
74
|
if (options.disablePoweredBy !== false) {
|
|
@@ -54,8 +93,15 @@ function applyRouters(app, options) {
|
|
|
54
93
|
app.use(path, mount.handler);
|
|
55
94
|
}
|
|
56
95
|
}
|
|
96
|
+
function createDefaultErrorHandler(logger) {
|
|
97
|
+
return (error, _req, _res, next) => {
|
|
98
|
+
logger.error("Unhandled Express error:", error);
|
|
99
|
+
next(error);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
57
102
|
function createExpressApp(options = {}) {
|
|
58
103
|
const app = (0, import_express.default)();
|
|
104
|
+
const logger = options.logger ?? defaultLogger;
|
|
59
105
|
applySettings(app, options);
|
|
60
106
|
applyMiddlewareList(app, options.preMiddleware);
|
|
61
107
|
if (options.json !== false) {
|
|
@@ -72,10 +118,12 @@ function createExpressApp(options = {}) {
|
|
|
72
118
|
}
|
|
73
119
|
if (options.errorHandler) {
|
|
74
120
|
app.use(options.errorHandler);
|
|
121
|
+
} else {
|
|
122
|
+
app.use(createDefaultErrorHandler(logger));
|
|
75
123
|
}
|
|
76
124
|
return app;
|
|
77
125
|
}
|
|
78
|
-
function normalizePort(val) {
|
|
126
|
+
function normalizePort(val, name = "port") {
|
|
79
127
|
if (val === void 0 || val === "") {
|
|
80
128
|
const envPort = process.env.PORT;
|
|
81
129
|
if (envPort === void 0 || envPort === "") {
|
|
@@ -83,17 +131,7 @@ function normalizePort(val) {
|
|
|
83
131
|
}
|
|
84
132
|
val = envPort;
|
|
85
133
|
}
|
|
86
|
-
|
|
87
|
-
const parsed = Number(val);
|
|
88
|
-
if (Number.isNaN(parsed)) {
|
|
89
|
-
return val;
|
|
90
|
-
}
|
|
91
|
-
val = parsed;
|
|
92
|
-
}
|
|
93
|
-
if (!Number.isFinite(val) || val < 0 || val > 65535) {
|
|
94
|
-
throw new Error(`Invalid port: ${String(val)}`);
|
|
95
|
-
}
|
|
96
|
-
return val;
|
|
134
|
+
return parsePortValue(val, name);
|
|
97
135
|
}
|
|
98
136
|
function defaultOnError(error, port, logger) {
|
|
99
137
|
if (error.syscall !== "listen") {
|
|
@@ -114,54 +152,217 @@ function startLocalServer(app, options = {}) {
|
|
|
114
152
|
const logger = options.logger ?? defaultLogger;
|
|
115
153
|
const port = normalizePort(options.port);
|
|
116
154
|
const host = options.host ?? process.env.HOST ?? "0.0.0.0";
|
|
117
|
-
const shutdownTimeout = options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT
|
|
155
|
+
const shutdownTimeout = validateFiniteInteger(options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT, {
|
|
156
|
+
name: "shutdownTimeout",
|
|
157
|
+
min: 0,
|
|
158
|
+
max: MAX_INTEGER_OPTION_VALUE
|
|
159
|
+
});
|
|
118
160
|
const server = import_node_http.default.createServer(app);
|
|
119
161
|
app.set("port", port);
|
|
120
|
-
|
|
162
|
+
let state = "initializing";
|
|
163
|
+
let shutdownPromise = null;
|
|
164
|
+
let readySettled = false;
|
|
165
|
+
let readyResolve;
|
|
166
|
+
let readyReject;
|
|
167
|
+
const ready = new Promise((resolve, reject) => {
|
|
168
|
+
readyResolve = () => {
|
|
169
|
+
if (!readySettled) {
|
|
170
|
+
readySettled = true;
|
|
171
|
+
resolve();
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
readyReject = (err) => {
|
|
175
|
+
if (!readySettled) {
|
|
176
|
+
readySettled = true;
|
|
177
|
+
reject(err);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
ready.catch(() => {
|
|
182
|
+
});
|
|
183
|
+
const ownedSignalHandlers = /* @__PURE__ */ new Map();
|
|
184
|
+
const cleanupSignalHandlers = () => {
|
|
185
|
+
for (const [sig, handler] of ownedSignalHandlers.entries()) {
|
|
186
|
+
process.removeListener(sig, handler);
|
|
187
|
+
}
|
|
188
|
+
ownedSignalHandlers.clear();
|
|
189
|
+
};
|
|
190
|
+
const handleListenError = (error) => {
|
|
191
|
+
if (state === "stopping" || state === "stopped" || state === "failed") {
|
|
192
|
+
logger.error("Server error after terminal state:", error);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (state === "initializing") {
|
|
196
|
+
state = "failed";
|
|
197
|
+
cleanupSignalHandlers();
|
|
198
|
+
readyReject(error);
|
|
199
|
+
if (options.onError) {
|
|
200
|
+
try {
|
|
201
|
+
options.onError(error);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
logger.error(e);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
try {
|
|
207
|
+
defaultOnError(error, port, logger);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
logger.error(e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
server.close();
|
|
214
|
+
} catch (_err) {
|
|
215
|
+
void _err;
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
121
219
|
if (options.onError) {
|
|
122
|
-
|
|
220
|
+
try {
|
|
221
|
+
options.onError(error);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
logger.error(e);
|
|
224
|
+
}
|
|
123
225
|
} else {
|
|
124
|
-
|
|
226
|
+
try {
|
|
227
|
+
defaultOnError(error, port, logger);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
logger.error(e);
|
|
230
|
+
}
|
|
125
231
|
}
|
|
126
232
|
};
|
|
127
233
|
const onListening = () => {
|
|
234
|
+
if (state === "stopping" || state === "stopped" || state === "failed") {
|
|
235
|
+
try {
|
|
236
|
+
server.close();
|
|
237
|
+
} catch (_err) {
|
|
238
|
+
void _err;
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
state = "listening";
|
|
128
243
|
const addr = server.address();
|
|
129
244
|
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr?.port}`;
|
|
130
|
-
|
|
131
|
-
|
|
245
|
+
const actualPort = typeof addr === "object" && addr !== null ? addr.port : port;
|
|
246
|
+
if (typeof addr === "object" && addr !== null) {
|
|
247
|
+
logger.log(`Server running at http://${host}:${actualPort}/ (${bind})`);
|
|
248
|
+
} else if (typeof addr === "string") {
|
|
249
|
+
logger.log(`Server running at pipe ${addr} (${bind})`);
|
|
250
|
+
} else {
|
|
251
|
+
logger.log(`Server running at http://${host}:${port}/ (${bind})`);
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
options.onListening?.();
|
|
255
|
+
} catch (e) {
|
|
256
|
+
logger.error("onListening hook failed:", e);
|
|
257
|
+
}
|
|
258
|
+
readyResolve();
|
|
132
259
|
};
|
|
133
|
-
|
|
260
|
+
const handleClose = () => {
|
|
261
|
+
if (state === "stopping") {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (state === "listening") {
|
|
265
|
+
state = "stopped";
|
|
266
|
+
cleanupSignalHandlers();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (state === "initializing" && !readySettled) {
|
|
270
|
+
state = "stopped";
|
|
271
|
+
cleanupSignalHandlers();
|
|
272
|
+
readyReject(new Error("Server closed before listening"));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (state === "initializing") {
|
|
276
|
+
state = "stopped";
|
|
277
|
+
cleanupSignalHandlers();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
server.on("error", handleListenError);
|
|
134
281
|
server.on("listening", onListening);
|
|
135
|
-
|
|
282
|
+
server.on("close", handleClose);
|
|
283
|
+
const doShutdown = async () => {
|
|
284
|
+
if (state === "stopping" || state === "stopped") {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (state === "failed") {
|
|
288
|
+
state = "stopped";
|
|
289
|
+
cleanupSignalHandlers();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
state = "stopping";
|
|
293
|
+
if (!readySettled) {
|
|
294
|
+
readyReject(new Error("Server shutdown before listening"));
|
|
295
|
+
}
|
|
296
|
+
cleanupSignalHandlers();
|
|
136
297
|
logger.log("Shutting down...");
|
|
298
|
+
await new Promise((resolve) => {
|
|
299
|
+
if (!server.listening) {
|
|
300
|
+
resolve();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
let settled = false;
|
|
304
|
+
const done = () => {
|
|
305
|
+
if (settled) return;
|
|
306
|
+
settled = true;
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
resolve();
|
|
309
|
+
};
|
|
310
|
+
const timer = setTimeout(() => {
|
|
311
|
+
try {
|
|
312
|
+
server.closeAllConnections?.();
|
|
313
|
+
} catch (_err) {
|
|
314
|
+
void _err;
|
|
315
|
+
}
|
|
316
|
+
done();
|
|
317
|
+
}, shutdownTimeout);
|
|
318
|
+
timer.unref?.();
|
|
319
|
+
try {
|
|
320
|
+
server.close((err) => {
|
|
321
|
+
if (err) {
|
|
322
|
+
logger.error("Server close error:", err);
|
|
323
|
+
}
|
|
324
|
+
done();
|
|
325
|
+
});
|
|
326
|
+
} catch (err) {
|
|
327
|
+
logger.error("Server close error:", err);
|
|
328
|
+
done();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
let shutdownError;
|
|
137
332
|
try {
|
|
138
333
|
if (options.onShutdown) {
|
|
139
334
|
await options.onShutdown();
|
|
140
335
|
}
|
|
141
336
|
} catch (err) {
|
|
142
337
|
logger.error("onShutdown hook failed:", err);
|
|
338
|
+
shutdownError = err;
|
|
143
339
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
logger.error("Server close error:", err);
|
|
153
|
-
}
|
|
154
|
-
resolve();
|
|
155
|
-
});
|
|
156
|
-
});
|
|
340
|
+
if (shutdownError) {
|
|
341
|
+
state = "failed";
|
|
342
|
+
if (options.exitAfterShutdown) {
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
throw shutdownError;
|
|
346
|
+
}
|
|
347
|
+
state = "stopped";
|
|
157
348
|
if (options.exitAfterShutdown) {
|
|
158
349
|
process.exit(0);
|
|
159
350
|
}
|
|
160
351
|
};
|
|
352
|
+
const shutdown = () => {
|
|
353
|
+
if (shutdownPromise) return shutdownPromise;
|
|
354
|
+
shutdownPromise = doShutdown();
|
|
355
|
+
return shutdownPromise;
|
|
356
|
+
};
|
|
161
357
|
if (options.signals !== false) {
|
|
162
358
|
const list = options.signals === void 0 || options.signals === true ? DEFAULT_SIGNALS : options.signals;
|
|
163
359
|
for (const sig of list) {
|
|
164
|
-
|
|
360
|
+
const handler = () => {
|
|
361
|
+
void shutdown().catch(() => {
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
ownedSignalHandlers.set(sig, handler);
|
|
365
|
+
process.once(sig, handler);
|
|
165
366
|
}
|
|
166
367
|
}
|
|
167
368
|
const start = async () => {
|
|
@@ -169,19 +370,44 @@ function startLocalServer(app, options = {}) {
|
|
|
169
370
|
if (options.init) {
|
|
170
371
|
await options.init();
|
|
171
372
|
}
|
|
373
|
+
if (state === "stopping" || state === "stopped" || state === "failed") {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
172
376
|
if (typeof port === "number") {
|
|
173
377
|
server.listen(port, host);
|
|
174
378
|
} else {
|
|
175
379
|
server.listen(port);
|
|
176
380
|
}
|
|
177
381
|
} catch (err) {
|
|
178
|
-
|
|
382
|
+
if (state === "stopping" || state === "stopped") {
|
|
383
|
+
logger.error("Init failed after shutdown started:", err);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
state = "failed";
|
|
387
|
+
cleanupSignalHandlers();
|
|
388
|
+
const error = err;
|
|
389
|
+
readyReject(error);
|
|
390
|
+
if (options.onError) {
|
|
391
|
+
try {
|
|
392
|
+
options.onError(error);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
logger.error(e);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
logger.error("Init failed:", error);
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
server.close();
|
|
401
|
+
} catch (_err) {
|
|
402
|
+
void _err;
|
|
403
|
+
}
|
|
179
404
|
}
|
|
180
405
|
};
|
|
181
406
|
void start();
|
|
182
407
|
return {
|
|
183
408
|
server,
|
|
184
|
-
shutdown
|
|
409
|
+
shutdown,
|
|
410
|
+
ready
|
|
185
411
|
};
|
|
186
412
|
}
|
|
187
413
|
var import_node_http, import_express, import_serverless_http, defaultLogger, DEFAULT_SIGNALS, DEFAULT_SHUTDOWN_TIMEOUT;
|
|
@@ -191,6 +417,7 @@ var init_index = __esm({
|
|
|
191
417
|
import_node_http = __toESM(require("http"));
|
|
192
418
|
import_express = __toESM(require("express"));
|
|
193
419
|
import_serverless_http = __toESM(require("serverless-http"));
|
|
420
|
+
init_numeric_validation();
|
|
194
421
|
defaultLogger = {
|
|
195
422
|
log: (...args) => console.log(...args),
|
|
196
423
|
error: (...args) => console.error(...args),
|
|
@@ -202,13 +429,104 @@ var init_index = __esm({
|
|
|
202
429
|
});
|
|
203
430
|
|
|
204
431
|
// src/cli-utils.ts
|
|
432
|
+
var cli_utils_exports = {};
|
|
433
|
+
__export(cli_utils_exports, {
|
|
434
|
+
CLI_VERSION: () => CLI_VERSION,
|
|
435
|
+
DEFAULT_ADAPTER_MAX_BODY_BYTES: () => DEFAULT_ADAPTER_MAX_BODY_BYTES,
|
|
436
|
+
DEFAULT_WATCH_KILL_TIMEOUT_MS: () => DEFAULT_WATCH_KILL_TIMEOUT_MS,
|
|
437
|
+
TEMP_BUILD_ENTRY_FILENAME: () => TEMP_BUILD_ENTRY_FILENAME,
|
|
438
|
+
TEMP_SERVERLESS_ENTRY_FILENAME: () => TEMP_SERVERLESS_ENTRY_FILENAME,
|
|
439
|
+
applyServerlessResult: () => applyServerlessResult,
|
|
440
|
+
buildBundleFromEntryContent: () => buildBundleFromEntryContent,
|
|
441
|
+
buildChildArgs: () => buildChildArgs,
|
|
442
|
+
buildRuntime: () => buildRuntime,
|
|
443
|
+
buildServerless: () => buildServerless,
|
|
444
|
+
collectBody: () => collectBody,
|
|
445
|
+
createServerlessAdapterApp: () => createServerlessAdapterApp,
|
|
446
|
+
createWatchSupervisor: () => createWatchSupervisor,
|
|
447
|
+
extractExport: () => extractExport,
|
|
448
|
+
generateRuntimeEntry: () => generateRuntimeEntry,
|
|
449
|
+
generateServerlessEntry: () => generateServerlessEntry,
|
|
450
|
+
isExpressApp: () => isExpressApp,
|
|
451
|
+
loadApp: () => loadApp,
|
|
452
|
+
loadBuiltApp: () => loadBuiltApp,
|
|
453
|
+
loadEnvFiles: () => loadEnvFiles,
|
|
454
|
+
loadHandler: () => loadHandler,
|
|
455
|
+
parseArgs: () => parseArgs,
|
|
456
|
+
parseEnvFile: () => parseEnvFile,
|
|
457
|
+
preloadModules: () => preloadModules,
|
|
458
|
+
printHelp: () => printHelp,
|
|
459
|
+
readValue: () => readValue,
|
|
460
|
+
resolveCliVersion: () => resolveCliVersion,
|
|
461
|
+
resolveExport: () => resolveExport,
|
|
462
|
+
runWithWatch: () => runWithWatch,
|
|
463
|
+
toServerlessEvent: () => toServerlessEvent,
|
|
464
|
+
validateMaxBodyBytes: () => validateMaxBodyBytes,
|
|
465
|
+
validateOutDirForClean: () => validateOutDirForClean
|
|
466
|
+
});
|
|
467
|
+
function readPackageVersion(candidatePath) {
|
|
468
|
+
try {
|
|
469
|
+
const manifest = JSON.parse((0, import_node_fs.readFileSync)(candidatePath, "utf8"));
|
|
470
|
+
return typeof manifest.version === "string" && manifest.version.length > 0 ? manifest.version : void 0;
|
|
471
|
+
} catch (_error) {
|
|
472
|
+
void _error;
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function resolveCliVersion(executablePath = process.argv[1]) {
|
|
477
|
+
let resolvedExecutable;
|
|
478
|
+
try {
|
|
479
|
+
resolvedExecutable = executablePath ? (0, import_node_fs.realpathSync)(executablePath) : void 0;
|
|
480
|
+
} catch (_error) {
|
|
481
|
+
void _error;
|
|
482
|
+
}
|
|
483
|
+
const executableDir = resolvedExecutable ? (0, import_node_path.dirname)(resolvedExecutable) : void 0;
|
|
484
|
+
const candidates = executableDir ? [(0, import_node_path.join)(executableDir, "package.json"), (0, import_node_path.join)(executableDir, "..", "package.json")] : [];
|
|
485
|
+
for (const candidate of candidates) {
|
|
486
|
+
const version = readPackageVersion(candidate);
|
|
487
|
+
if (version) return version;
|
|
488
|
+
}
|
|
489
|
+
return "0.0.0-dev";
|
|
490
|
+
}
|
|
205
491
|
function readValue(argv, index, name) {
|
|
206
492
|
const value = argv[index + 1];
|
|
207
|
-
if (value === void 0 || value.startsWith("--")) {
|
|
493
|
+
if (value === void 0 || value === "" || value.startsWith("--")) {
|
|
494
|
+
throw new Error(`Missing value for argument: ${name}`);
|
|
495
|
+
}
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
function readInlineValue(arg, prefix, name) {
|
|
499
|
+
const value = arg.slice(prefix.length);
|
|
500
|
+
if (value === "") {
|
|
208
501
|
throw new Error(`Missing value for argument: ${name}`);
|
|
209
502
|
}
|
|
210
503
|
return value;
|
|
211
504
|
}
|
|
505
|
+
function parseIntegerFlag(raw, name, min = 0, max = MAX_INTEGER_OPTION_VALUE) {
|
|
506
|
+
if (!/^(0|[1-9]\d*)$/.test(raw)) {
|
|
507
|
+
throw new Error(`Invalid ${name}: ${raw}. Must be a finite integer in ${min}..${max}.`);
|
|
508
|
+
}
|
|
509
|
+
return validateFiniteInteger(Number(raw), { name, min, max });
|
|
510
|
+
}
|
|
511
|
+
function parsePortFlag(raw, name = "--port") {
|
|
512
|
+
try {
|
|
513
|
+
return parsePortValue(raw, name);
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (error instanceof Error && error.message.startsWith(`Invalid ${name}:`)) {
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
throw new Error(`Invalid ${name}: ${raw}. Must be a port number in 0..65535 or a named pipe path.`, {
|
|
519
|
+
cause: error
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function parseCsvFlagValue(raw, name) {
|
|
524
|
+
const values = raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
525
|
+
if (values.length === 0) {
|
|
526
|
+
throw new Error(`Missing value for argument: ${name}`);
|
|
527
|
+
}
|
|
528
|
+
return values;
|
|
529
|
+
}
|
|
212
530
|
function printHelp() {
|
|
213
531
|
console.log(`wtt-express-runtime
|
|
214
532
|
|
|
@@ -270,6 +588,7 @@ Start-serverless options:
|
|
|
270
588
|
--host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
|
|
271
589
|
--no-signals Disable SIGINT/SIGTERM handler registration
|
|
272
590
|
--shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
|
|
591
|
+
--max-body-bytes <bytes> Max request body bytes for adapter (default: 1048576; 0 disallows bodies)
|
|
273
592
|
--require <module> Module(s) to preload before handler load (repeatable)
|
|
274
593
|
--env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
|
|
275
594
|
|
|
@@ -291,17 +610,22 @@ Examples:
|
|
|
291
610
|
Notes:
|
|
292
611
|
- In dev mode, the CLI evaluates arbitrary code from <app-module> in the current process.
|
|
293
612
|
- TypeScript app modules in dev mode require a TS loader. Run via tsx:
|
|
294
|
-
npx tsx ./node_modules/@web-ts-toolkit/express-runtime/
|
|
613
|
+
npx tsx ./node_modules/@web-ts-toolkit/express-runtime/cli.js dev ./src/app.ts
|
|
295
614
|
Or use --require with a TS-aware loader module.
|
|
296
615
|
- --env files are parsed as KEY=VALUE; existing process.env entries are never overridden.
|
|
297
616
|
For advanced dotenv features (multiline, expansion), --require dotenv/config instead.
|
|
298
|
-
- --watch forks
|
|
299
|
-
|
|
617
|
+
- --watch forks one child process running the same CLI without --watch. File changes
|
|
618
|
+
are serialized into one restart at a time: SIGTERM, SIGKILL after 5000 ms if needed,
|
|
619
|
+
then respawn after the debounce delay. Shutdown closes owned watchers and signal handlers.
|
|
300
620
|
- In build/build-serverless mode, express is always external. Add more externals with --external.
|
|
301
621
|
- In start mode, the bundled app file must default-export an Express app (or export it as "app").
|
|
302
622
|
If the bundle exports "init", it runs before the server starts listening.
|
|
303
623
|
- In start-serverless mode, the bundled handler file must be a JS/CJS module whose
|
|
304
624
|
"handler" export (or default export) is a function: (event, context) => Promise<result>.
|
|
625
|
+
- The start-serverless adapter buffers at most --max-body-bytes per request (default 1 MiB, 0 = empty bodies only);
|
|
626
|
+
larger declared Content-Length or chunked bodies receive 413 without invoking the handler.
|
|
627
|
+
- Use -- before a positional path that starts with a dash, e.g. dev -- --app.js.
|
|
628
|
+
- Numeric flag values are validated before env/preload/app loading, watching, or binding.
|
|
305
629
|
- Init logic for dev mode (DB connections, etc.): add at the top level of your app module.
|
|
306
630
|
`);
|
|
307
631
|
}
|
|
@@ -315,13 +639,25 @@ function isSubcommand(arg) {
|
|
|
315
639
|
return arg === "dev" || arg === "build" || arg === "start" || arg === "build-serverless" || arg === "start-serverless";
|
|
316
640
|
}
|
|
317
641
|
function parseRepeatable(argv, index, arg, list) {
|
|
318
|
-
|
|
319
|
-
for (const part of value.split(",")) {
|
|
320
|
-
const trimmed = part.trim();
|
|
321
|
-
if (trimmed) list.push(trimmed);
|
|
322
|
-
}
|
|
642
|
+
list.push(...parseCsvFlagValue(readValue(argv, index, arg), arg));
|
|
323
643
|
return index + 1;
|
|
324
644
|
}
|
|
645
|
+
function addPositional(arg, current, label) {
|
|
646
|
+
if (current) {
|
|
647
|
+
throw new Error(`Unexpected positional argument: ${arg}. ${label} already set to ${current}`);
|
|
648
|
+
}
|
|
649
|
+
return arg;
|
|
650
|
+
}
|
|
651
|
+
function setTsconfigPath(current, next) {
|
|
652
|
+
if (current !== void 0 && current !== next) {
|
|
653
|
+
throw new Error(`Conflicting --tsconfig values: ${current} and ${next}`);
|
|
654
|
+
}
|
|
655
|
+
return next;
|
|
656
|
+
}
|
|
657
|
+
function optionArgs(argv) {
|
|
658
|
+
const terminator = argv.indexOf("--");
|
|
659
|
+
return terminator === -1 ? argv : argv.slice(0, terminator);
|
|
660
|
+
}
|
|
325
661
|
function parseDevArgs(argv) {
|
|
326
662
|
const options = {};
|
|
327
663
|
const requireModules = [];
|
|
@@ -334,22 +670,21 @@ function parseDevArgs(argv) {
|
|
|
334
670
|
for (let index = 0; index < argv.length; index += 1) {
|
|
335
671
|
const arg = argv[index];
|
|
336
672
|
if (arg === "--") {
|
|
337
|
-
|
|
673
|
+
for (const positional of argv.slice(index + 1)) {
|
|
674
|
+
appPath = addPositional(positional, appPath, "App module");
|
|
675
|
+
}
|
|
676
|
+
break;
|
|
338
677
|
}
|
|
339
678
|
if (isHelp(arg) || isVersion(arg)) {
|
|
340
679
|
continue;
|
|
341
680
|
}
|
|
342
681
|
if (arg === "--port") {
|
|
343
|
-
|
|
344
|
-
const portNum = Number(port);
|
|
345
|
-
options.port = Number.isNaN(portNum) ? port : portNum;
|
|
682
|
+
options.port = parsePortFlag(readValue(argv, index, arg));
|
|
346
683
|
index += 1;
|
|
347
684
|
continue;
|
|
348
685
|
}
|
|
349
686
|
if (arg.startsWith("--port=")) {
|
|
350
|
-
|
|
351
|
-
const portNum = Number(port);
|
|
352
|
-
options.port = Number.isNaN(portNum) ? port : portNum;
|
|
687
|
+
options.port = parsePortFlag(readInlineValue(arg, "--port=", "--port"));
|
|
353
688
|
continue;
|
|
354
689
|
}
|
|
355
690
|
if (arg === "--host") {
|
|
@@ -358,7 +693,7 @@ function parseDevArgs(argv) {
|
|
|
358
693
|
continue;
|
|
359
694
|
}
|
|
360
695
|
if (arg.startsWith("--host=")) {
|
|
361
|
-
options.host = arg
|
|
696
|
+
options.host = readInlineValue(arg, "--host=", "--host");
|
|
362
697
|
continue;
|
|
363
698
|
}
|
|
364
699
|
if (arg === "--no-signals") {
|
|
@@ -366,12 +701,15 @@ function parseDevArgs(argv) {
|
|
|
366
701
|
continue;
|
|
367
702
|
}
|
|
368
703
|
if (arg === "--shutdown-timeout") {
|
|
369
|
-
options.shutdownTimeout =
|
|
704
|
+
options.shutdownTimeout = parseIntegerFlag(readValue(argv, index, arg), "--shutdown-timeout");
|
|
370
705
|
index += 1;
|
|
371
706
|
continue;
|
|
372
707
|
}
|
|
373
708
|
if (arg.startsWith("--shutdown-timeout=")) {
|
|
374
|
-
options.shutdownTimeout =
|
|
709
|
+
options.shutdownTimeout = parseIntegerFlag(
|
|
710
|
+
readInlineValue(arg, "--shutdown-timeout=", "--shutdown-timeout"),
|
|
711
|
+
"--shutdown-timeout"
|
|
712
|
+
);
|
|
375
713
|
continue;
|
|
376
714
|
}
|
|
377
715
|
if (arg === "--require") {
|
|
@@ -379,10 +717,7 @@ function parseDevArgs(argv) {
|
|
|
379
717
|
continue;
|
|
380
718
|
}
|
|
381
719
|
if (arg.startsWith("--require=")) {
|
|
382
|
-
|
|
383
|
-
const trimmed = part.trim();
|
|
384
|
-
if (trimmed) requireModules.push(trimmed);
|
|
385
|
-
}
|
|
720
|
+
requireModules.push(...parseCsvFlagValue(readInlineValue(arg, "--require=", "--require"), "--require"));
|
|
386
721
|
continue;
|
|
387
722
|
}
|
|
388
723
|
if (arg === "--env") {
|
|
@@ -390,19 +725,16 @@ function parseDevArgs(argv) {
|
|
|
390
725
|
continue;
|
|
391
726
|
}
|
|
392
727
|
if (arg.startsWith("--env=")) {
|
|
393
|
-
|
|
394
|
-
const trimmed = part.trim();
|
|
395
|
-
if (trimmed) envFiles.push(trimmed);
|
|
396
|
-
}
|
|
728
|
+
envFiles.push(...parseCsvFlagValue(readInlineValue(arg, "--env=", "--env"), "--env"));
|
|
397
729
|
continue;
|
|
398
730
|
}
|
|
399
731
|
if (arg === "--tsconfig") {
|
|
400
|
-
tsconfigPath = readValue(argv, index, arg);
|
|
732
|
+
tsconfigPath = setTsconfigPath(tsconfigPath, readValue(argv, index, arg));
|
|
401
733
|
index += 1;
|
|
402
734
|
continue;
|
|
403
735
|
}
|
|
404
736
|
if (arg.startsWith("--tsconfig=")) {
|
|
405
|
-
tsconfigPath = arg
|
|
737
|
+
tsconfigPath = setTsconfigPath(tsconfigPath, readInlineValue(arg, "--tsconfig=", "--tsconfig"));
|
|
406
738
|
continue;
|
|
407
739
|
}
|
|
408
740
|
if (arg === "--watch") {
|
|
@@ -410,10 +742,7 @@ function parseDevArgs(argv) {
|
|
|
410
742
|
continue;
|
|
411
743
|
}
|
|
412
744
|
if (arg.startsWith("--watch=")) {
|
|
413
|
-
|
|
414
|
-
const trimmed = part.trim();
|
|
415
|
-
if (trimmed) watchPaths.push(trimmed);
|
|
416
|
-
}
|
|
745
|
+
watchPaths.push(...parseCsvFlagValue(readInlineValue(arg, "--watch=", "--watch"), "--watch"));
|
|
417
746
|
continue;
|
|
418
747
|
}
|
|
419
748
|
if (arg === "--ext") {
|
|
@@ -423,26 +752,20 @@ function parseDevArgs(argv) {
|
|
|
423
752
|
}
|
|
424
753
|
if (arg.startsWith("--ext=")) {
|
|
425
754
|
watchExt = [];
|
|
426
|
-
|
|
427
|
-
const trimmed = part.trim();
|
|
428
|
-
if (trimmed) watchExt.push(trimmed);
|
|
429
|
-
}
|
|
755
|
+
watchExt.push(...parseCsvFlagValue(readInlineValue(arg, "--ext=", "--ext"), "--ext"));
|
|
430
756
|
continue;
|
|
431
757
|
}
|
|
432
758
|
if (arg === "--delay") {
|
|
433
|
-
watchDelay =
|
|
759
|
+
watchDelay = parseIntegerFlag(readValue(argv, index, arg), "--delay");
|
|
434
760
|
index += 1;
|
|
435
761
|
continue;
|
|
436
762
|
}
|
|
437
763
|
if (arg.startsWith("--delay=")) {
|
|
438
|
-
watchDelay =
|
|
764
|
+
watchDelay = parseIntegerFlag(readInlineValue(arg, "--delay=", "--delay"), "--delay");
|
|
439
765
|
continue;
|
|
440
766
|
}
|
|
441
767
|
if (!arg.startsWith("--")) {
|
|
442
|
-
|
|
443
|
-
throw new Error(`Unexpected positional argument: ${arg}. App module already set to ${appPath}`);
|
|
444
|
-
}
|
|
445
|
-
appPath = arg;
|
|
768
|
+
appPath = addPositional(arg, appPath, "App module");
|
|
446
769
|
continue;
|
|
447
770
|
}
|
|
448
771
|
throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -463,7 +786,7 @@ function parseDevArgs(argv) {
|
|
|
463
786
|
};
|
|
464
787
|
}
|
|
465
788
|
function parseStartLikeArgs(argv, subcommandName) {
|
|
466
|
-
for (const arg of argv) {
|
|
789
|
+
for (const arg of optionArgs(argv)) {
|
|
467
790
|
if (arg === "--watch" || arg.startsWith("--watch=") || arg === "--tsconfig" || arg.startsWith("--tsconfig=") || arg === "--ext" || arg.startsWith("--ext=") || arg === "--delay" || arg.startsWith("--delay=")) {
|
|
468
791
|
throw new Error(`--watch/--tsconfig/--ext/--delay are not supported with the ${subcommandName} subcommand`);
|
|
469
792
|
}
|
|
@@ -480,10 +803,35 @@ function parseStartArgs(argv) {
|
|
|
480
803
|
};
|
|
481
804
|
}
|
|
482
805
|
function parseStartServerlessArgs(argv) {
|
|
483
|
-
|
|
806
|
+
let maxBodyBytes;
|
|
807
|
+
const filtered = [];
|
|
808
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
809
|
+
const arg = argv[i];
|
|
810
|
+
if (arg === "--") {
|
|
811
|
+
filtered.push(...argv.slice(i));
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
if (arg === "--max-body-bytes") {
|
|
815
|
+
maxBodyBytes = parseIntegerFlag(readValue(argv, i, arg), "--max-body-bytes");
|
|
816
|
+
validateMaxBodyBytes(maxBodyBytes);
|
|
817
|
+
i += 1;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (arg.startsWith("--max-body-bytes=")) {
|
|
821
|
+
maxBodyBytes = parseIntegerFlag(
|
|
822
|
+
readInlineValue(arg, "--max-body-bytes=", "--max-body-bytes"),
|
|
823
|
+
"--max-body-bytes"
|
|
824
|
+
);
|
|
825
|
+
validateMaxBodyBytes(maxBodyBytes);
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
filtered.push(arg);
|
|
829
|
+
}
|
|
830
|
+
const result = parseStartLikeArgs(filtered, "start-serverless");
|
|
484
831
|
return {
|
|
485
832
|
handlerPath: result.appPath,
|
|
486
833
|
options: result.options,
|
|
834
|
+
maxBodyBytes,
|
|
487
835
|
require: result.require,
|
|
488
836
|
env: result.env
|
|
489
837
|
};
|
|
@@ -504,7 +852,10 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
504
852
|
for (let index = 0; index < argv.length; index += 1) {
|
|
505
853
|
const arg = argv[index];
|
|
506
854
|
if (arg === "--") {
|
|
507
|
-
|
|
855
|
+
for (const positional of argv.slice(index + 1)) {
|
|
856
|
+
appPath = addPositional(positional, appPath, "App module");
|
|
857
|
+
}
|
|
858
|
+
break;
|
|
508
859
|
}
|
|
509
860
|
if (isHelp(arg) || isVersion(arg)) {
|
|
510
861
|
continue;
|
|
@@ -515,16 +866,16 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
515
866
|
continue;
|
|
516
867
|
}
|
|
517
868
|
if (arg.startsWith("--init=")) {
|
|
518
|
-
result.initPath = arg
|
|
869
|
+
result.initPath = readInlineValue(arg, "--init=", "--init");
|
|
519
870
|
continue;
|
|
520
871
|
}
|
|
521
872
|
if (arg === "--tsconfig") {
|
|
522
|
-
result.tsconfigPath = readValue(argv, index, arg);
|
|
873
|
+
result.tsconfigPath = setTsconfigPath(result.tsconfigPath, readValue(argv, index, arg));
|
|
523
874
|
index += 1;
|
|
524
875
|
continue;
|
|
525
876
|
}
|
|
526
877
|
if (arg.startsWith("--tsconfig=")) {
|
|
527
|
-
result.tsconfigPath =
|
|
878
|
+
result.tsconfigPath = setTsconfigPath(result.tsconfigPath, readInlineValue(arg, "--tsconfig=", "--tsconfig"));
|
|
528
879
|
continue;
|
|
529
880
|
}
|
|
530
881
|
if (arg === "--out-dir") {
|
|
@@ -533,7 +884,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
533
884
|
continue;
|
|
534
885
|
}
|
|
535
886
|
if (arg.startsWith("--out-dir=")) {
|
|
536
|
-
result.outDir = arg
|
|
887
|
+
result.outDir = readInlineValue(arg, "--out-dir=", "--out-dir");
|
|
537
888
|
continue;
|
|
538
889
|
}
|
|
539
890
|
if (arg === "--out-name") {
|
|
@@ -542,7 +893,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
542
893
|
continue;
|
|
543
894
|
}
|
|
544
895
|
if (arg.startsWith("--out-name=")) {
|
|
545
|
-
result.outName = arg
|
|
896
|
+
result.outName = readInlineValue(arg, "--out-name=", "--out-name");
|
|
546
897
|
continue;
|
|
547
898
|
}
|
|
548
899
|
if (arg === "--format") {
|
|
@@ -555,7 +906,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
555
906
|
continue;
|
|
556
907
|
}
|
|
557
908
|
if (arg.startsWith("--format=")) {
|
|
558
|
-
const fmt = arg
|
|
909
|
+
const fmt = readInlineValue(arg, "--format=", "--format");
|
|
559
910
|
if (fmt !== "cjs" && fmt !== "esm") {
|
|
560
911
|
throw new Error(`Invalid --format: ${fmt}. Must be 'cjs' or 'esm'.`);
|
|
561
912
|
}
|
|
@@ -568,7 +919,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
568
919
|
continue;
|
|
569
920
|
}
|
|
570
921
|
if (arg.startsWith("--target=")) {
|
|
571
|
-
result.target = arg
|
|
922
|
+
result.target = readInlineValue(arg, "--target=", "--target");
|
|
572
923
|
continue;
|
|
573
924
|
}
|
|
574
925
|
if (arg === "--external") {
|
|
@@ -577,7 +928,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
577
928
|
continue;
|
|
578
929
|
}
|
|
579
930
|
if (arg.startsWith("--external=")) {
|
|
580
|
-
external.push(arg
|
|
931
|
+
external.push(readInlineValue(arg, "--external=", "--external"));
|
|
581
932
|
continue;
|
|
582
933
|
}
|
|
583
934
|
if (arg === "--no-clean") {
|
|
@@ -585,10 +936,7 @@ function parseBuildArgs(argv, outNameDefault) {
|
|
|
585
936
|
continue;
|
|
586
937
|
}
|
|
587
938
|
if (!arg.startsWith("--")) {
|
|
588
|
-
|
|
589
|
-
throw new Error(`Unexpected positional argument: ${arg}. App module already set to ${appPath}`);
|
|
590
|
-
}
|
|
591
|
-
appPath = arg;
|
|
939
|
+
appPath = addPositional(arg, appPath, "App module");
|
|
592
940
|
continue;
|
|
593
941
|
}
|
|
594
942
|
throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -610,11 +958,12 @@ function parseArgs(argv) {
|
|
|
610
958
|
printHelp();
|
|
611
959
|
return null;
|
|
612
960
|
}
|
|
613
|
-
|
|
961
|
+
const globalArgs = optionArgs(argv);
|
|
962
|
+
if (globalArgs.some((a) => isHelp(a))) {
|
|
614
963
|
printHelp();
|
|
615
964
|
return null;
|
|
616
965
|
}
|
|
617
|
-
if (
|
|
966
|
+
if (globalArgs.some((a) => isVersion(a))) {
|
|
618
967
|
console.log(CLI_VERSION);
|
|
619
968
|
return null;
|
|
620
969
|
}
|
|
@@ -712,80 +1061,283 @@ async function preloadModules(modules) {
|
|
|
712
1061
|
moduleRequire(mod);
|
|
713
1062
|
}
|
|
714
1063
|
}
|
|
715
|
-
function
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
if (args.options.host !== void 0) result.push("--host", args.options.host);
|
|
719
|
-
if (args.options.signals === false) result.push("--no-signals");
|
|
720
|
-
if (args.options.shutdownTimeout !== void 0)
|
|
721
|
-
result.push("--shutdown-timeout", String(args.options.shutdownTimeout));
|
|
722
|
-
if (args.tsconfigPath !== void 0) result.push("--tsconfig", args.tsconfigPath);
|
|
723
|
-
for (const r of args.require) result.push("--require", r);
|
|
724
|
-
for (const e of args.env) result.push("--env", e);
|
|
725
|
-
return result;
|
|
1064
|
+
function toDiagnosticMessage(prefix, error) {
|
|
1065
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1066
|
+
return `${prefix}: ${message}`;
|
|
726
1067
|
}
|
|
727
|
-
function
|
|
1068
|
+
function createWatchSupervisor(args, deps = {}) {
|
|
1069
|
+
const forkImpl = deps.fork ?? import_node_child_process.fork;
|
|
1070
|
+
const watchImpl = deps.watch ?? import_node_fs.watch;
|
|
1071
|
+
const existsSyncImpl = deps.existsSync ?? import_node_fs.existsSync;
|
|
1072
|
+
const logger = deps.logger ?? console;
|
|
1073
|
+
const setTimeoutImpl = deps.setTimeout ?? setTimeout;
|
|
1074
|
+
const clearTimeoutImpl = deps.clearTimeout ?? clearTimeout;
|
|
1075
|
+
const killTimeoutMs = deps.killTimeoutMs ?? DEFAULT_WATCH_KILL_TIMEOUT_MS;
|
|
728
1076
|
const cliPath = process.argv[1];
|
|
729
1077
|
const childArgv = buildChildArgs(args);
|
|
730
1078
|
let child = null;
|
|
731
1079
|
let restartTimer = null;
|
|
1080
|
+
let killTimer = null;
|
|
732
1081
|
let isShuttingDown = false;
|
|
1082
|
+
let shutdownPromise = null;
|
|
1083
|
+
let restartInFlight = null;
|
|
1084
|
+
let terminatingChild = null;
|
|
1085
|
+
let failureHandled = false;
|
|
733
1086
|
const restartDelay = args.watchDelay;
|
|
1087
|
+
const watchers = [];
|
|
1088
|
+
const clearRestartTimer = () => {
|
|
1089
|
+
if (restartTimer) {
|
|
1090
|
+
clearTimeoutImpl(restartTimer);
|
|
1091
|
+
restartTimer = null;
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
const clearKillTimer = () => {
|
|
1095
|
+
if (killTimer) {
|
|
1096
|
+
clearTimeoutImpl(killTimer);
|
|
1097
|
+
killTimer = null;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
const closeWatchers = () => {
|
|
1101
|
+
for (const watcher of watchers.splice(0)) {
|
|
1102
|
+
try {
|
|
1103
|
+
watcher.close();
|
|
1104
|
+
} catch (_error) {
|
|
1105
|
+
void _error;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
const completeWithExit = async (code) => {
|
|
1110
|
+
await shutdown();
|
|
1111
|
+
deps.exit?.(code);
|
|
1112
|
+
};
|
|
1113
|
+
const fail = (message, code = 1) => {
|
|
1114
|
+
if (failureHandled) return;
|
|
1115
|
+
failureHandled = true;
|
|
1116
|
+
logger.error(message);
|
|
1117
|
+
void completeWithExit(code).catch(() => {
|
|
1118
|
+
deps.exit?.(code);
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
734
1121
|
const spawnChild = () => {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1122
|
+
if (isShuttingDown) return;
|
|
1123
|
+
let nextChild;
|
|
1124
|
+
try {
|
|
1125
|
+
nextChild = forkImpl(cliPath, childArgv, { stdio: "inherit" });
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
fail(toDiagnosticMessage("Watch child failed to spawn", error));
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
child = nextChild;
|
|
1131
|
+
nextChild.once("error", (error) => {
|
|
1132
|
+
if (child === nextChild) {
|
|
1133
|
+
child = null;
|
|
1134
|
+
}
|
|
1135
|
+
fail(toDiagnosticMessage("Watch child process error", error));
|
|
1136
|
+
});
|
|
1137
|
+
nextChild.once("exit", (code, signal) => {
|
|
1138
|
+
if (child === nextChild) {
|
|
1139
|
+
child = null;
|
|
1140
|
+
}
|
|
1141
|
+
if (terminatingChild === nextChild || isShuttingDown || failureHandled) {
|
|
1142
|
+
return;
|
|
739
1143
|
}
|
|
1144
|
+
const exitCode = typeof code === "number" && code > 0 ? code : 1;
|
|
1145
|
+
fail(`Watch child exited unexpectedly${signal ? ` from ${signal}` : ` with code ${String(code)}`}`, exitCode);
|
|
740
1146
|
});
|
|
741
1147
|
};
|
|
742
|
-
const killChild = () => {
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
1148
|
+
const killChild = async (target = child) => {
|
|
1149
|
+
if (!target || !target.pid) {
|
|
1150
|
+
if (target && child === target) child = null;
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
terminatingChild = target;
|
|
1154
|
+
await new Promise((resolve, reject) => {
|
|
1155
|
+
let settled = false;
|
|
1156
|
+
const settle = (error) => {
|
|
1157
|
+
if (settled) return;
|
|
1158
|
+
settled = true;
|
|
1159
|
+
clearKillTimer();
|
|
1160
|
+
target.removeListener("exit", onExit);
|
|
1161
|
+
target.removeListener("error", onError);
|
|
1162
|
+
if (child === target) child = null;
|
|
1163
|
+
if (terminatingChild === target) terminatingChild = null;
|
|
1164
|
+
if (error) reject(error);
|
|
1165
|
+
else resolve();
|
|
1166
|
+
};
|
|
1167
|
+
const onExit = () => settle();
|
|
1168
|
+
const onError = (error) => settle(error);
|
|
1169
|
+
target.once("exit", onExit);
|
|
1170
|
+
target.once("error", onError);
|
|
1171
|
+
try {
|
|
1172
|
+
const signaled = target.kill("SIGTERM");
|
|
1173
|
+
if (!signaled) {
|
|
1174
|
+
throw new Error('child.kill("SIGTERM") returned false');
|
|
1175
|
+
}
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
746
1178
|
return;
|
|
747
1179
|
}
|
|
748
|
-
|
|
749
|
-
|
|
1180
|
+
if (!settled) {
|
|
1181
|
+
killTimer = setTimeoutImpl(() => {
|
|
1182
|
+
try {
|
|
1183
|
+
const signaled = target.kill("SIGKILL");
|
|
1184
|
+
if (!signaled) {
|
|
1185
|
+
settle(new Error('child.kill("SIGKILL") returned false'));
|
|
1186
|
+
}
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
1189
|
+
}
|
|
1190
|
+
}, killTimeoutMs);
|
|
1191
|
+
}
|
|
750
1192
|
});
|
|
751
1193
|
};
|
|
752
1194
|
const restart = async () => {
|
|
753
|
-
|
|
754
|
-
|
|
1195
|
+
if (isShuttingDown) return;
|
|
1196
|
+
if (restartInFlight) {
|
|
1197
|
+
await restartInFlight;
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
restartInFlight = (async () => {
|
|
1201
|
+
if (isShuttingDown) return;
|
|
1202
|
+
await killChild();
|
|
1203
|
+
if (isShuttingDown) return;
|
|
1204
|
+
spawnChild();
|
|
1205
|
+
})();
|
|
1206
|
+
try {
|
|
1207
|
+
await restartInFlight;
|
|
1208
|
+
} finally {
|
|
1209
|
+
restartInFlight = null;
|
|
1210
|
+
}
|
|
755
1211
|
};
|
|
756
1212
|
const debouncedRestart = () => {
|
|
757
|
-
if (
|
|
758
|
-
|
|
1213
|
+
if (isShuttingDown) return;
|
|
1214
|
+
clearRestartTimer();
|
|
1215
|
+
restartTimer = setTimeoutImpl(() => {
|
|
759
1216
|
restartTimer = null;
|
|
760
|
-
void restart()
|
|
1217
|
+
void restart().catch((error) => {
|
|
1218
|
+
fail(toDiagnosticMessage("Watch restart failed", error));
|
|
1219
|
+
});
|
|
761
1220
|
}, restartDelay);
|
|
762
1221
|
};
|
|
763
1222
|
for (const watchPath of args.watch) {
|
|
764
1223
|
const absPath = (0, import_node_path.resolve)(process.cwd(), watchPath);
|
|
765
|
-
if (!(
|
|
1224
|
+
if (!existsSyncImpl(absPath)) {
|
|
766
1225
|
throw new Error(`Watch path not found: ${watchPath}`);
|
|
767
1226
|
}
|
|
768
|
-
(0, import_node_fs.watch)(absPath, { recursive: true }, (_eventType, filename) => {
|
|
769
|
-
if (!filename) return;
|
|
770
|
-
const ext = (0, import_node_path.extname)(filename).slice(1).toLowerCase();
|
|
771
|
-
if (args.watchExt.includes(ext)) {
|
|
772
|
-
debouncedRestart();
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
1227
|
}
|
|
776
|
-
const shutdown = () => {
|
|
1228
|
+
const shutdown = async () => {
|
|
1229
|
+
if (shutdownPromise) return shutdownPromise;
|
|
777
1230
|
isShuttingDown = true;
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1231
|
+
clearRestartTimer();
|
|
1232
|
+
closeWatchers();
|
|
1233
|
+
shutdownPromise = (async () => {
|
|
1234
|
+
const activeRestart = restartInFlight;
|
|
1235
|
+
if (activeRestart) {
|
|
1236
|
+
await activeRestart.catch(() => void 0);
|
|
1237
|
+
}
|
|
1238
|
+
const activeChild = child;
|
|
1239
|
+
if (activeChild) {
|
|
1240
|
+
await killChild(activeChild).catch((error) => {
|
|
1241
|
+
if (!failureHandled) {
|
|
1242
|
+
fail(toDiagnosticMessage("Watch child termination failed", error));
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
})();
|
|
1247
|
+
return shutdownPromise;
|
|
1248
|
+
};
|
|
1249
|
+
try {
|
|
1250
|
+
for (const watchPath of args.watch) {
|
|
1251
|
+
const absPath = (0, import_node_path.resolve)(process.cwd(), watchPath);
|
|
1252
|
+
const watchListener = (_eventType, filename) => {
|
|
1253
|
+
if (isShuttingDown) return;
|
|
1254
|
+
if (!filename) return;
|
|
1255
|
+
const ext = (0, import_node_path.extname)(filename).slice(1).toLowerCase();
|
|
1256
|
+
if (args.watchExt.includes(ext)) {
|
|
1257
|
+
debouncedRestart();
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
let watcher;
|
|
1261
|
+
try {
|
|
1262
|
+
watcher = watchImpl(absPath, { recursive: true }, watchListener);
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
if (error.code !== "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM") {
|
|
1265
|
+
throw error;
|
|
1266
|
+
}
|
|
1267
|
+
watcher = watchImpl(absPath, watchListener);
|
|
1268
|
+
}
|
|
1269
|
+
watcher.on?.(
|
|
1270
|
+
"error",
|
|
1271
|
+
(error) => {
|
|
1272
|
+
fail(toDiagnosticMessage("Watch path runtime error", error));
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
watchers.push(watcher);
|
|
784
1276
|
}
|
|
1277
|
+
spawnChild();
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
clearRestartTimer();
|
|
1280
|
+
closeWatchers();
|
|
1281
|
+
if (child) {
|
|
1282
|
+
void killChild(child).catch(() => void 0);
|
|
1283
|
+
}
|
|
1284
|
+
throw error;
|
|
1285
|
+
}
|
|
1286
|
+
return {
|
|
1287
|
+
shutdown,
|
|
1288
|
+
getChild: () => child,
|
|
1289
|
+
getWatchers: () => [...watchers],
|
|
1290
|
+
isShuttingDown: () => isShuttingDown
|
|
785
1291
|
};
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1292
|
+
}
|
|
1293
|
+
function buildChildArgs(args) {
|
|
1294
|
+
const result = ["dev", args.appPath];
|
|
1295
|
+
if (args.options.port !== void 0) result.push("--port", String(args.options.port));
|
|
1296
|
+
if (args.options.host !== void 0) result.push("--host", args.options.host);
|
|
1297
|
+
if (args.options.signals === false) result.push("--no-signals");
|
|
1298
|
+
if (args.options.shutdownTimeout !== void 0)
|
|
1299
|
+
result.push("--shutdown-timeout", String(args.options.shutdownTimeout));
|
|
1300
|
+
if (args.tsconfigPath !== void 0) result.push("--tsconfig", args.tsconfigPath);
|
|
1301
|
+
for (const r of args.require) result.push("--require", r);
|
|
1302
|
+
for (const e of args.env) result.push("--env", e);
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
1305
|
+
function runWithWatch(args, deps = {}) {
|
|
1306
|
+
const usingInjectedDeps = Object.keys(deps).length > 0;
|
|
1307
|
+
const installSignalHandlers = deps.installSignalHandlers ?? !usingInjectedDeps;
|
|
1308
|
+
const exitImpl = deps.exit ?? (usingInjectedDeps ? void 0 : (code) => process.exit(code));
|
|
1309
|
+
const controller = createWatchSupervisor(args, {
|
|
1310
|
+
...deps,
|
|
1311
|
+
exit: exitImpl
|
|
1312
|
+
});
|
|
1313
|
+
let shutdownStarted = false;
|
|
1314
|
+
const ownedHandlers = [];
|
|
1315
|
+
const removeOwnedHandlers = () => {
|
|
1316
|
+
for (const [signal, handler] of ownedHandlers.splice(0)) {
|
|
1317
|
+
process.removeListener(signal, handler);
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
const shutdown = async () => {
|
|
1321
|
+
removeOwnedHandlers();
|
|
1322
|
+
await controller.shutdown();
|
|
1323
|
+
};
|
|
1324
|
+
const wrappedController = {
|
|
1325
|
+
shutdown,
|
|
1326
|
+
getChild: controller.getChild,
|
|
1327
|
+
getWatchers: controller.getWatchers,
|
|
1328
|
+
isShuttingDown: controller.isShuttingDown
|
|
1329
|
+
};
|
|
1330
|
+
if (installSignalHandlers) {
|
|
1331
|
+
const shutdownAndExit = () => {
|
|
1332
|
+
if (shutdownStarted) return;
|
|
1333
|
+
shutdownStarted = true;
|
|
1334
|
+
void shutdown().then(() => exitImpl?.(0));
|
|
1335
|
+
};
|
|
1336
|
+
ownedHandlers.push(["SIGINT", shutdownAndExit], ["SIGTERM", shutdownAndExit]);
|
|
1337
|
+
process.on("SIGINT", shutdownAndExit);
|
|
1338
|
+
process.on("SIGTERM", shutdownAndExit);
|
|
1339
|
+
}
|
|
1340
|
+
return wrappedController;
|
|
789
1341
|
}
|
|
790
1342
|
function generateServerlessEntry(appPath, initPath) {
|
|
791
1343
|
const absAppPath = (0, import_node_path.resolve)(process.cwd(), appPath);
|
|
@@ -818,11 +1370,100 @@ function generateRuntimeEntry(appPath, initPath) {
|
|
|
818
1370
|
}
|
|
819
1371
|
return lines.join("\n") + "\n";
|
|
820
1372
|
}
|
|
1373
|
+
function validateOutDirForClean(outDir, clean, appPath, initPath) {
|
|
1374
|
+
if (!clean) return;
|
|
1375
|
+
const cwd = process.cwd();
|
|
1376
|
+
const outAbs = (0, import_node_path.resolve)(cwd, outDir);
|
|
1377
|
+
const normalized = (0, import_node_path.normalize)(outAbs);
|
|
1378
|
+
const root = (0, import_node_path.parse)(normalized).root;
|
|
1379
|
+
if (normalized === root) {
|
|
1380
|
+
throw new Error(`Refusing to clean filesystem root: ${outDir} resolves to ${normalized}`);
|
|
1381
|
+
}
|
|
1382
|
+
if (normalized === (0, import_node_path.normalize)(cwd)) {
|
|
1383
|
+
throw new Error(`Refusing to clean project directory: ${outDir} resolves to cwd ${cwd}`);
|
|
1384
|
+
}
|
|
1385
|
+
if (cwd !== root && (cwd === normalized || cwd.startsWith(normalized + import_node_path.sep))) {
|
|
1386
|
+
throw new Error(
|
|
1387
|
+
`Refusing to clean ancestor of project directory: ${outDir} resolves to ${normalized} which contains cwd ${cwd}`
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
try {
|
|
1391
|
+
if ((0, import_node_fs.existsSync)(outAbs)) {
|
|
1392
|
+
const st = (0, import_node_fs.lstatSync)(outAbs);
|
|
1393
|
+
if (st.isSymbolicLink()) {
|
|
1394
|
+
throw new Error(`Refusing to clean symlinked outDir: ${outDir} resolves to symlink ${outAbs}`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
} catch (e) {
|
|
1398
|
+
if (e.message.startsWith("Refusing to clean")) throw e;
|
|
1399
|
+
}
|
|
1400
|
+
const checkOverlap = (inputPath, label) => {
|
|
1401
|
+
if (!inputPath) return;
|
|
1402
|
+
const inputAbs = (0, import_node_path.resolve)(cwd, inputPath);
|
|
1403
|
+
const inputNorm = (0, import_node_path.normalize)(inputAbs);
|
|
1404
|
+
if (inputNorm === normalized) {
|
|
1405
|
+
throw new Error(`Refusing to clean outDir that is the same as ${label}: ${outDir} == ${inputPath}`);
|
|
1406
|
+
}
|
|
1407
|
+
if (inputNorm.startsWith(normalized + import_node_path.sep)) {
|
|
1408
|
+
throw new Error(`Refusing to clean outDir that contains ${label}: ${outDir} contains ${inputPath}`);
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
checkOverlap(appPath, "appPath");
|
|
1412
|
+
checkOverlap(initPath, "initPath");
|
|
1413
|
+
}
|
|
1414
|
+
function createUniqueStagingDir() {
|
|
1415
|
+
const cwd = process.cwd();
|
|
1416
|
+
const prefix = (0, import_node_path.join)(cwd, STAGING_DIR_PREFIX);
|
|
1417
|
+
const dir = (0, import_node_fs.mkdtempSync)(prefix);
|
|
1418
|
+
try {
|
|
1419
|
+
const st = (0, import_node_fs.lstatSync)(dir);
|
|
1420
|
+
if (st.isSymbolicLink()) {
|
|
1421
|
+
(0, import_node_fs.rmSync)(dir, { recursive: true, force: true });
|
|
1422
|
+
throw new Error(`Staging directory is a symlink: ${dir}`);
|
|
1423
|
+
}
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
if (e.message.includes("Staging directory is a symlink")) throw e;
|
|
1426
|
+
throw e;
|
|
1427
|
+
}
|
|
1428
|
+
try {
|
|
1429
|
+
(0, import_node_fs.chmodSync)(dir, 448);
|
|
1430
|
+
} catch {
|
|
1431
|
+
}
|
|
1432
|
+
return dir;
|
|
1433
|
+
}
|
|
1434
|
+
function writeStagingEntry(dir, content) {
|
|
1435
|
+
const entryPath = (0, import_node_path.join)(dir, "entry.ts");
|
|
1436
|
+
try {
|
|
1437
|
+
if ((0, import_node_fs.existsSync)(entryPath)) {
|
|
1438
|
+
const st = (0, import_node_fs.lstatSync)(entryPath);
|
|
1439
|
+
if (st.isSymbolicLink()) {
|
|
1440
|
+
throw new Error(`Refusing to overwrite symlink at staging path: ${entryPath}`);
|
|
1441
|
+
}
|
|
1442
|
+
throw new Error(`Staging file already exists: ${entryPath}`);
|
|
1443
|
+
}
|
|
1444
|
+
} catch (e) {
|
|
1445
|
+
if (e.message.startsWith("Refusing to") || e.message.startsWith("Staging file already exists"))
|
|
1446
|
+
throw e;
|
|
1447
|
+
}
|
|
1448
|
+
(0, import_node_fs.writeFileSync)(entryPath, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1449
|
+
try {
|
|
1450
|
+
const st = (0, import_node_fs.lstatSync)(entryPath);
|
|
1451
|
+
if (st.isSymbolicLink()) {
|
|
1452
|
+
(0, import_node_fs.rmSync)(entryPath, { force: true });
|
|
1453
|
+
throw new Error(`Staging file is a symlink after write: ${entryPath}`);
|
|
1454
|
+
}
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
if (e.message.includes("Staging file is a symlink")) throw e;
|
|
1457
|
+
}
|
|
1458
|
+
return entryPath;
|
|
1459
|
+
}
|
|
821
1460
|
async function buildBundleFromEntryContent(args) {
|
|
1461
|
+
validateOutDirForClean(args.outDir, args.clean);
|
|
822
1462
|
const tsupModule = await import("tsup");
|
|
823
1463
|
const { build } = tsupModule;
|
|
824
|
-
const
|
|
825
|
-
|
|
1464
|
+
const stagingDir = createUniqueStagingDir();
|
|
1465
|
+
const tempEntryPath = writeStagingEntry(stagingDir, args.entryContent);
|
|
1466
|
+
const absOutDir = (0, import_node_path.resolve)(process.cwd(), args.outDir);
|
|
826
1467
|
try {
|
|
827
1468
|
await build({
|
|
828
1469
|
config: false,
|
|
@@ -830,85 +1471,371 @@ async function buildBundleFromEntryContent(args) {
|
|
|
830
1471
|
tsconfig: args.tsconfigPath,
|
|
831
1472
|
format: [args.format],
|
|
832
1473
|
target: args.target,
|
|
833
|
-
outDir:
|
|
1474
|
+
outDir: absOutDir,
|
|
834
1475
|
clean: args.clean,
|
|
835
|
-
external: ["express", ...args.external],
|
|
1476
|
+
external: ["express", "@web-ts-toolkit/express-runtime", ...args.external],
|
|
836
1477
|
sourcemap: false,
|
|
837
1478
|
dts: false,
|
|
838
1479
|
splitting: false
|
|
839
1480
|
});
|
|
840
1481
|
} finally {
|
|
841
|
-
(0, import_node_fs.rmSync)(
|
|
1482
|
+
(0, import_node_fs.rmSync)(stagingDir, { recursive: true, force: true });
|
|
842
1483
|
}
|
|
843
1484
|
}
|
|
844
1485
|
async function buildRuntime(args) {
|
|
845
1486
|
const { runBuildEntryCommand: runBuildEntryCommand2 } = await Promise.resolve().then(() => (init_cli_api(), cli_api_exports));
|
|
846
1487
|
await runBuildEntryCommand2(args, {
|
|
847
|
-
generateEntry: generateRuntimeEntry
|
|
848
|
-
tempEntryFilename: TEMP_BUILD_ENTRY_FILENAME
|
|
1488
|
+
generateEntry: generateRuntimeEntry
|
|
849
1489
|
});
|
|
850
1490
|
}
|
|
851
1491
|
async function buildServerless(args) {
|
|
852
1492
|
const { runBuildEntryCommand: runBuildEntryCommand2 } = await Promise.resolve().then(() => (init_cli_api(), cli_api_exports));
|
|
853
1493
|
await runBuildEntryCommand2(args, {
|
|
854
|
-
generateEntry: generateServerlessEntry
|
|
855
|
-
tempEntryFilename: TEMP_SERVERLESS_ENTRY_FILENAME
|
|
1494
|
+
generateEntry: generateServerlessEntry
|
|
856
1495
|
});
|
|
857
1496
|
}
|
|
858
|
-
function
|
|
1497
|
+
function validateMaxBodyBytes(value) {
|
|
1498
|
+
try {
|
|
1499
|
+
return validateFiniteInteger(value, { name: "--max-body-bytes", min: 0, max: MAX_INTEGER_OPTION_VALUE });
|
|
1500
|
+
} catch (_error) {
|
|
1501
|
+
void _error;
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`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).`,
|
|
1504
|
+
{ cause: _error }
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
function collectBody(req, maxBytes) {
|
|
1509
|
+
validateMaxBodyBytes(maxBytes);
|
|
859
1510
|
return new Promise((resolve, reject) => {
|
|
1511
|
+
const rawLength = req.headers["content-length"];
|
|
1512
|
+
if (rawLength !== void 0) {
|
|
1513
|
+
const raw = Array.isArray(rawLength) ? rawLength[0] : rawLength;
|
|
1514
|
+
const parsed = Number(raw);
|
|
1515
|
+
if (Number.isFinite(parsed) && Number.isInteger(parsed) && parsed >= 0) {
|
|
1516
|
+
if (parsed > maxBytes) {
|
|
1517
|
+
try {
|
|
1518
|
+
req.resume?.();
|
|
1519
|
+
} catch (_e) {
|
|
1520
|
+
void _e;
|
|
1521
|
+
}
|
|
1522
|
+
const err = new Error(
|
|
1523
|
+
`Request body too large: Content-Length ${parsed} exceeds limit ${maxBytes}`
|
|
1524
|
+
);
|
|
1525
|
+
err.code = "LIMIT_EXCEEDED";
|
|
1526
|
+
err.statusCode = 413;
|
|
1527
|
+
reject(err);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
860
1532
|
const chunks = [];
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1533
|
+
let total = 0;
|
|
1534
|
+
let finished = false;
|
|
1535
|
+
const cleanup = () => {
|
|
1536
|
+
req.removeListener("data", onData);
|
|
1537
|
+
req.removeListener("end", onEnd);
|
|
1538
|
+
req.removeListener("error", onError);
|
|
1539
|
+
req.removeListener("close", onClose);
|
|
1540
|
+
try {
|
|
1541
|
+
req.removeListener?.(
|
|
1542
|
+
"aborted",
|
|
1543
|
+
onClose
|
|
1544
|
+
);
|
|
1545
|
+
} catch (_e) {
|
|
1546
|
+
void _e;
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
const fail = (err) => {
|
|
1550
|
+
if (finished) return;
|
|
1551
|
+
finished = true;
|
|
1552
|
+
cleanup();
|
|
1553
|
+
if (err.code === "LIMIT_EXCEEDED") {
|
|
1554
|
+
try {
|
|
1555
|
+
req.resume?.();
|
|
1556
|
+
} catch (_e) {
|
|
1557
|
+
void _e;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
reject(err);
|
|
1561
|
+
};
|
|
1562
|
+
const onData = (chunk) => {
|
|
1563
|
+
if (finished) return;
|
|
1564
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1565
|
+
total += buf.length;
|
|
1566
|
+
if (total > maxBytes) {
|
|
1567
|
+
const err = new Error(
|
|
1568
|
+
`Request body too large: received ${total} bytes exceeds limit ${maxBytes}`
|
|
1569
|
+
);
|
|
1570
|
+
err.code = "LIMIT_EXCEEDED";
|
|
1571
|
+
err.statusCode = 413;
|
|
1572
|
+
fail(err);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
chunks.push(buf);
|
|
1576
|
+
};
|
|
1577
|
+
const onEnd = () => {
|
|
1578
|
+
if (finished) return;
|
|
1579
|
+
finished = true;
|
|
1580
|
+
cleanup();
|
|
1581
|
+
try {
|
|
1582
|
+
resolve(Buffer.concat(chunks, total));
|
|
1583
|
+
} catch (e) {
|
|
1584
|
+
fail(e);
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
const onError = (err) => {
|
|
1588
|
+
const e = err;
|
|
1589
|
+
if (!e.code) e.code = "STREAM_ERROR";
|
|
1590
|
+
fail(e);
|
|
1591
|
+
};
|
|
1592
|
+
const onClose = () => {
|
|
1593
|
+
if (finished) return;
|
|
1594
|
+
const e = new Error("Request aborted by client");
|
|
1595
|
+
e.code = "CLIENT_ABORT";
|
|
1596
|
+
fail(e);
|
|
1597
|
+
};
|
|
1598
|
+
req.on("data", onData);
|
|
1599
|
+
req.on("end", onEnd);
|
|
1600
|
+
req.on("error", onError);
|
|
1601
|
+
req.on("close", onClose);
|
|
1602
|
+
try {
|
|
1603
|
+
req.on?.("aborted", onClose);
|
|
1604
|
+
} catch (_e) {
|
|
1605
|
+
void _e;
|
|
1606
|
+
}
|
|
864
1607
|
});
|
|
865
1608
|
}
|
|
866
1609
|
function toServerlessEvent(method, url, headers, body) {
|
|
1610
|
+
const parsedUrl = new URL(url, "http://localhost");
|
|
1611
|
+
const { queryStringParameters, multiValueQueryStringParameters } = parseAwsRestQuery(parsedUrl.search);
|
|
1612
|
+
const { singleValueHeaders, multiValueHeaders } = normalizeAwsRestHeaders(headers);
|
|
867
1613
|
return {
|
|
868
1614
|
httpMethod: method,
|
|
869
|
-
path:
|
|
870
|
-
headers,
|
|
871
|
-
|
|
1615
|
+
path: parsedUrl.pathname,
|
|
1616
|
+
headers: singleValueHeaders,
|
|
1617
|
+
multiValueHeaders,
|
|
1618
|
+
queryStringParameters,
|
|
1619
|
+
multiValueQueryStringParameters,
|
|
1620
|
+
body: body.length > 0 ? body.toString("base64") : "",
|
|
1621
|
+
isBase64Encoded: body.length > 0,
|
|
1622
|
+
requestContext: {
|
|
1623
|
+
identity: {
|
|
1624
|
+
// Minimal field required by serverless-http's AWS v1 request adapter.
|
|
1625
|
+
sourceIp: ""
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function parseAwsRestQuery(search) {
|
|
1631
|
+
if (search === "" || search === "?") {
|
|
1632
|
+
return { queryStringParameters: null, multiValueQueryStringParameters: null };
|
|
1633
|
+
}
|
|
1634
|
+
const single = {};
|
|
1635
|
+
const multi = {};
|
|
1636
|
+
const query = search.startsWith("?") ? search.slice(1) : search;
|
|
1637
|
+
for (const pair of query.split("&")) {
|
|
1638
|
+
if (pair === "") continue;
|
|
1639
|
+
const separator = pair.indexOf("=");
|
|
1640
|
+
const rawKey = separator === -1 ? pair : pair.slice(0, separator);
|
|
1641
|
+
const rawValue = separator === -1 ? "" : pair.slice(separator + 1);
|
|
1642
|
+
const key = decodeQueryComponent(rawKey);
|
|
1643
|
+
const value = decodeQueryComponent(rawValue);
|
|
1644
|
+
single[key] = value;
|
|
1645
|
+
(multi[key] ??= []).push(value);
|
|
1646
|
+
}
|
|
1647
|
+
return {
|
|
1648
|
+
queryStringParameters: Object.keys(single).length > 0 ? single : null,
|
|
1649
|
+
multiValueQueryStringParameters: Object.keys(multi).length > 0 ? multi : null
|
|
872
1650
|
};
|
|
873
1651
|
}
|
|
1652
|
+
function decodeQueryComponent(value) {
|
|
1653
|
+
try {
|
|
1654
|
+
return decodeURIComponent(value);
|
|
1655
|
+
} catch (_error) {
|
|
1656
|
+
void _error;
|
|
1657
|
+
return value;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function normalizeAwsRestHeaders(headers) {
|
|
1661
|
+
const singleValueHeaders = {};
|
|
1662
|
+
const multiValueHeaders = {};
|
|
1663
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1664
|
+
if (value === void 0) continue;
|
|
1665
|
+
const values = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
1666
|
+
multiValueHeaders[key] = values;
|
|
1667
|
+
singleValueHeaders[key] = values.join(", ");
|
|
1668
|
+
}
|
|
1669
|
+
return { singleValueHeaders, multiValueHeaders };
|
|
1670
|
+
}
|
|
874
1671
|
function applyServerlessResult(result, res) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1672
|
+
const response = validateServerlessResult(result);
|
|
1673
|
+
res.status(response.statusCode);
|
|
1674
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
1675
|
+
res.setHeader(key, value);
|
|
878
1676
|
}
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
for (const [key, value] of Object.entries(r.headers)) {
|
|
885
|
-
if (value !== void 0) {
|
|
886
|
-
if (Array.isArray(value) && key.toLowerCase() === "set-cookie") {
|
|
887
|
-
res.setHeader(key, value);
|
|
888
|
-
} else {
|
|
889
|
-
res.setHeader(key, Array.isArray(value) ? value.join(",") : value);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
1677
|
+
for (const [key, values] of Object.entries(response.multiValueHeaders)) {
|
|
1678
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
1679
|
+
res.setHeader(key, values);
|
|
1680
|
+
} else {
|
|
1681
|
+
res.setHeader(key, values.join(","));
|
|
892
1682
|
}
|
|
893
1683
|
}
|
|
894
|
-
if (
|
|
895
|
-
res.end(
|
|
896
|
-
} else if (typeof r.body === "string") {
|
|
897
|
-
res.end(r.body);
|
|
1684
|
+
if (response.isBase64Encoded) {
|
|
1685
|
+
res.end(response.decodedBody);
|
|
898
1686
|
} else {
|
|
899
|
-
res.end();
|
|
1687
|
+
res.end(response.body);
|
|
900
1688
|
}
|
|
901
1689
|
}
|
|
902
|
-
function
|
|
1690
|
+
function validateServerlessResult(result) {
|
|
1691
|
+
if (!isPlainRecord(result)) {
|
|
1692
|
+
throw new Error(
|
|
1693
|
+
"Invalid serverless result: expected an object with an optional statusCode, headers, multiValueHeaders, body, and isBase64Encoded."
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
const rawStatus = result.statusCode;
|
|
1697
|
+
if (rawStatus !== void 0 && (typeof rawStatus !== "number" || !Number.isInteger(rawStatus) || rawStatus < 100 || rawStatus > 599)) {
|
|
1698
|
+
throw new Error(`Invalid serverless result statusCode: ${String(rawStatus)}. Expected an integer in 100..599.`);
|
|
1699
|
+
}
|
|
1700
|
+
const rawIsBase64Encoded = result.isBase64Encoded;
|
|
1701
|
+
if (rawIsBase64Encoded !== void 0 && typeof rawIsBase64Encoded !== "boolean") {
|
|
1702
|
+
throw new Error("Invalid serverless result isBase64Encoded: expected a boolean when provided.");
|
|
1703
|
+
}
|
|
1704
|
+
const rawBody = result.body;
|
|
1705
|
+
if (rawBody !== void 0 && typeof rawBody !== "string") {
|
|
1706
|
+
throw new Error(`Invalid serverless result body: expected a string when provided, received ${typeof rawBody}.`);
|
|
1707
|
+
}
|
|
1708
|
+
const headers = validateSingleValueHeaders(result.headers, "headers");
|
|
1709
|
+
const multiValueHeaders = validateMultiValueHeaders(result.multiValueHeaders, "multiValueHeaders");
|
|
1710
|
+
const multiHeaderKeys = new Set(Object.keys(multiValueHeaders).map((key) => key.toLowerCase()));
|
|
1711
|
+
for (const key of Object.keys(headers)) {
|
|
1712
|
+
if (multiHeaderKeys.has(key.toLowerCase())) {
|
|
1713
|
+
delete headers[key];
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
const isBase64Encoded = rawIsBase64Encoded ?? false;
|
|
1717
|
+
const body = rawBody ?? "";
|
|
1718
|
+
let decodedBody;
|
|
1719
|
+
if (isBase64Encoded) {
|
|
1720
|
+
if (!isValidBase64(body)) {
|
|
1721
|
+
throw new Error("Invalid serverless result body: isBase64Encoded is true but body is not valid standard base64.");
|
|
1722
|
+
}
|
|
1723
|
+
decodedBody = Buffer.from(body, "base64");
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
statusCode: rawStatus ?? 200,
|
|
1727
|
+
headers,
|
|
1728
|
+
multiValueHeaders,
|
|
1729
|
+
body,
|
|
1730
|
+
decodedBody,
|
|
1731
|
+
isBase64Encoded
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
function validateSingleValueHeaders(value, name) {
|
|
1735
|
+
if (value === void 0) return {};
|
|
1736
|
+
if (!isPlainRecord(value)) {
|
|
1737
|
+
throw new Error(`Invalid serverless result ${name}: expected an object of string header values.`);
|
|
1738
|
+
}
|
|
1739
|
+
const headers = {};
|
|
1740
|
+
for (const [key, headerValue] of Object.entries(value)) {
|
|
1741
|
+
if (headerValue === void 0) continue;
|
|
1742
|
+
if (typeof headerValue !== "string") {
|
|
1743
|
+
throw new Error(`Invalid serverless result ${name}.${key}: expected a string header value.`);
|
|
1744
|
+
}
|
|
1745
|
+
validateServerlessHeader(key, headerValue, `${name}.${key}`);
|
|
1746
|
+
headers[key] = headerValue;
|
|
1747
|
+
}
|
|
1748
|
+
return headers;
|
|
1749
|
+
}
|
|
1750
|
+
function validateMultiValueHeaders(value, name) {
|
|
1751
|
+
if (value === void 0) return {};
|
|
1752
|
+
if (!isPlainRecord(value)) {
|
|
1753
|
+
throw new Error(`Invalid serverless result ${name}: expected an object of string-array header values.`);
|
|
1754
|
+
}
|
|
1755
|
+
const headers = {};
|
|
1756
|
+
for (const [key, headerValue] of Object.entries(value)) {
|
|
1757
|
+
if (headerValue === void 0) continue;
|
|
1758
|
+
if (!Array.isArray(headerValue) || headerValue.some((entry) => typeof entry !== "string")) {
|
|
1759
|
+
throw new Error(`Invalid serverless result ${name}.${key}: expected an array of string header values.`);
|
|
1760
|
+
}
|
|
1761
|
+
for (const entry of headerValue) {
|
|
1762
|
+
validateServerlessHeader(key, entry, `${name}.${key}`);
|
|
1763
|
+
}
|
|
1764
|
+
headers[key] = headerValue;
|
|
1765
|
+
}
|
|
1766
|
+
return headers;
|
|
1767
|
+
}
|
|
1768
|
+
function validateServerlessHeader(key, value, label) {
|
|
1769
|
+
try {
|
|
1770
|
+
(0, import_node_http2.validateHeaderName)(key);
|
|
1771
|
+
(0, import_node_http2.validateHeaderValue)(key, value);
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
throw new Error(`Invalid serverless result header ${label}: ${error.message}`, { cause: error });
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function isValidBase64(value) {
|
|
1777
|
+
if (value === "") return true;
|
|
1778
|
+
if (value.length % 4 !== 0) return false;
|
|
1779
|
+
return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
1780
|
+
}
|
|
1781
|
+
function isPlainRecord(value) {
|
|
1782
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1783
|
+
}
|
|
1784
|
+
function createServerlessAdapterApp(handler, options = {}) {
|
|
1785
|
+
const maxBytes = options.maxBodyBytes ?? DEFAULT_ADAPTER_MAX_BODY_BYTES;
|
|
1786
|
+
validateMaxBodyBytes(maxBytes);
|
|
903
1787
|
return createExpressApp({
|
|
904
1788
|
json: false,
|
|
905
1789
|
urlencoded: false,
|
|
906
1790
|
finalize: (app) => {
|
|
907
1791
|
app.use(async (req, res) => {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1792
|
+
let body;
|
|
1793
|
+
try {
|
|
1794
|
+
body = await collectBody(req, maxBytes);
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
const e = err;
|
|
1797
|
+
if (e?.code === "LIMIT_EXCEEDED" || e?.statusCode === 413) {
|
|
1798
|
+
if (!res.headersSent && !res.writableEnded) {
|
|
1799
|
+
res.status(413).end("Payload Too Large");
|
|
1800
|
+
} else {
|
|
1801
|
+
try {
|
|
1802
|
+
res.end();
|
|
1803
|
+
} catch (_e) {
|
|
1804
|
+
void _e;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (e?.code === "CLIENT_ABORT") {
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
console.error("Serverless adapter error:", e);
|
|
1813
|
+
if (!res.headersSent && !res.writableEnded) {
|
|
1814
|
+
res.status(500).end("Internal server error");
|
|
1815
|
+
} else {
|
|
1816
|
+
try {
|
|
1817
|
+
res.end();
|
|
1818
|
+
} catch (_e) {
|
|
1819
|
+
void _e;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
let result;
|
|
1825
|
+
try {
|
|
1826
|
+
const event = toServerlessEvent(req.method, req.url, req.headers, body);
|
|
1827
|
+
result = await handler(event, {});
|
|
1828
|
+
} catch (e) {
|
|
1829
|
+
console.error("Serverless adapter error:", e);
|
|
1830
|
+
if (!res.headersSent && !res.writableEnded) res.status(500).end("Internal server error");
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
try {
|
|
1834
|
+
applyServerlessResult(result, res);
|
|
1835
|
+
} catch (e) {
|
|
1836
|
+
console.error("Invalid serverless handler result:", e);
|
|
1837
|
+
if (!res.headersSent && !res.writableEnded) res.status(500).end("Internal server error");
|
|
1838
|
+
}
|
|
912
1839
|
});
|
|
913
1840
|
},
|
|
914
1841
|
errorHandler: (error, _req, res, _next) => {
|
|
@@ -921,7 +1848,7 @@ async function loadBuiltApp(appPath) {
|
|
|
921
1848
|
const fullPath = (0, import_node_path.resolve)(process.cwd(), appPath);
|
|
922
1849
|
const moduleUrl = (0, import_node_url.pathToFileURL)(fullPath).href;
|
|
923
1850
|
const mod = await import(moduleUrl);
|
|
924
|
-
const exported =
|
|
1851
|
+
const exported = mod.app ?? mod.default;
|
|
925
1852
|
if (!exported) {
|
|
926
1853
|
throw new Error(
|
|
927
1854
|
`Module "${appPath}" must default-export an Express app or export it as "app". Exports: ${Object.keys(mod).join(", ")}`
|
|
@@ -931,9 +1858,14 @@ async function loadBuiltApp(appPath) {
|
|
|
931
1858
|
if (init !== void 0 && typeof init !== "function") {
|
|
932
1859
|
throw new Error(`Module "${appPath}" must export "init" as a function when present.`);
|
|
933
1860
|
}
|
|
1861
|
+
const shutdown = mod.shutdown;
|
|
1862
|
+
if (shutdown !== void 0 && typeof shutdown !== "function") {
|
|
1863
|
+
throw new Error(`Module "${appPath}" must export "shutdown" as a function when present.`);
|
|
1864
|
+
}
|
|
934
1865
|
return {
|
|
935
1866
|
app: await resolveExport(exported, appPath),
|
|
936
|
-
init
|
|
1867
|
+
init,
|
|
1868
|
+
shutdown
|
|
937
1869
|
};
|
|
938
1870
|
}
|
|
939
1871
|
async function loadHandler(handlerPath) {
|
|
@@ -948,7 +1880,7 @@ async function loadHandler(handlerPath) {
|
|
|
948
1880
|
}
|
|
949
1881
|
return exported;
|
|
950
1882
|
}
|
|
951
|
-
var import_node_url, import_node_path, import_node_fs, import_node_module, import_node_child_process, CLI_VERSION, DEFAULT_WATCH_EXTENSIONS, DEFAULT_WATCH_DELAY, moduleRequire, TEMP_BUILD_ENTRY_FILENAME, TEMP_SERVERLESS_ENTRY_FILENAME;
|
|
1883
|
+
var import_node_url, import_node_path, import_node_fs, import_node_module, import_node_child_process, import_node_http2, CLI_VERSION, DEFAULT_WATCH_EXTENSIONS, DEFAULT_WATCH_DELAY, moduleRequire, DEFAULT_WATCH_KILL_TIMEOUT_MS, TEMP_BUILD_ENTRY_FILENAME, TEMP_SERVERLESS_ENTRY_FILENAME, STAGING_DIR_PREFIX, DEFAULT_ADAPTER_MAX_BODY_BYTES;
|
|
952
1884
|
var init_cli_utils = __esm({
|
|
953
1885
|
"src/cli-utils.ts"() {
|
|
954
1886
|
"use strict";
|
|
@@ -957,15 +1889,20 @@ var init_cli_utils = __esm({
|
|
|
957
1889
|
import_node_fs = require("fs");
|
|
958
1890
|
import_node_module = require("module");
|
|
959
1891
|
import_node_child_process = require("child_process");
|
|
1892
|
+
import_node_http2 = require("http");
|
|
960
1893
|
init_index();
|
|
961
|
-
|
|
1894
|
+
init_numeric_validation();
|
|
1895
|
+
CLI_VERSION = resolveCliVersion();
|
|
962
1896
|
DEFAULT_WATCH_EXTENSIONS = ["ts", "js", "mjs", "cjs", "json"];
|
|
963
1897
|
DEFAULT_WATCH_DELAY = 500;
|
|
964
1898
|
moduleRequire = (0, import_node_module.createRequire)(
|
|
965
1899
|
(0, import_node_url.pathToFileURL)((0, import_node_path.resolve)(process.cwd(), "__wtt_runtime_preload__.js"))
|
|
966
1900
|
);
|
|
1901
|
+
DEFAULT_WATCH_KILL_TIMEOUT_MS = 5e3;
|
|
967
1902
|
TEMP_BUILD_ENTRY_FILENAME = ".express-runtime-build-entry.ts";
|
|
968
1903
|
TEMP_SERVERLESS_ENTRY_FILENAME = ".express-runtime-build-serverless-entry.ts";
|
|
1904
|
+
STAGING_DIR_PREFIX = ".wtt-build-";
|
|
1905
|
+
DEFAULT_ADAPTER_MAX_BODY_BYTES = 1024 * 1024;
|
|
969
1906
|
}
|
|
970
1907
|
});
|
|
971
1908
|
|
|
@@ -973,11 +1910,15 @@ var init_cli_utils = __esm({
|
|
|
973
1910
|
var cli_api_exports = {};
|
|
974
1911
|
__export(cli_api_exports, {
|
|
975
1912
|
CLI_VERSION: () => CLI_VERSION,
|
|
1913
|
+
DEFAULT_ADAPTER_MAX_BODY_BYTES: () => DEFAULT_ADAPTER_MAX_BODY_BYTES,
|
|
1914
|
+
TEMP_BUILD_ENTRY_FILENAME: () => TEMP_BUILD_ENTRY_FILENAME,
|
|
1915
|
+
TEMP_SERVERLESS_ENTRY_FILENAME: () => TEMP_SERVERLESS_ENTRY_FILENAME,
|
|
976
1916
|
applyServerlessResult: () => applyServerlessResult,
|
|
977
1917
|
buildBundleFromEntryContent: () => buildBundleFromEntryContent,
|
|
978
1918
|
buildChildArgs: () => buildChildArgs,
|
|
979
1919
|
buildRuntime: () => buildRuntime,
|
|
980
1920
|
buildServerless: () => buildServerless,
|
|
1921
|
+
collectBody: () => collectBody,
|
|
981
1922
|
createServerlessAdapterApp: () => createServerlessAdapterApp,
|
|
982
1923
|
extractExport: () => extractExport,
|
|
983
1924
|
generateRuntimeEntry: () => generateRuntimeEntry,
|
|
@@ -998,7 +1939,9 @@ __export(cli_api_exports, {
|
|
|
998
1939
|
runDevCommand: () => runDevCommand,
|
|
999
1940
|
runExpressDevCommand: () => runExpressDevCommand,
|
|
1000
1941
|
runWithWatch: () => runWithWatch,
|
|
1001
|
-
toServerlessEvent: () => toServerlessEvent
|
|
1942
|
+
toServerlessEvent: () => toServerlessEvent,
|
|
1943
|
+
validateMaxBodyBytes: () => validateMaxBodyBytes,
|
|
1944
|
+
validateOutDirForClean: () => validateOutDirForClean
|
|
1002
1945
|
});
|
|
1003
1946
|
module.exports = __toCommonJS(cli_api_exports);
|
|
1004
1947
|
async function runDevCommand(args, runner) {
|
|
@@ -1011,7 +1954,8 @@ async function runDevCommand(args, runner) {
|
|
|
1011
1954
|
}
|
|
1012
1955
|
await preloadModules(args.require);
|
|
1013
1956
|
const loaded = await runner.load(args.appPath);
|
|
1014
|
-
runner.start(loaded, { ...args.options, exitAfterShutdown: true });
|
|
1957
|
+
const server = runner.start(loaded, { ...args.options, exitAfterShutdown: true });
|
|
1958
|
+
await server?.ready;
|
|
1015
1959
|
}
|
|
1016
1960
|
async function runExpressDevCommand(args) {
|
|
1017
1961
|
await runDevCommand(args, {
|
|
@@ -1025,9 +1969,10 @@ async function runBuildEntryCommand(args, options) {
|
|
|
1025
1969
|
if (options.allowInit === false && args.initPath) {
|
|
1026
1970
|
throw new Error(options.initErrorMessage ?? "This build command manages init automatically. Remove --init.");
|
|
1027
1971
|
}
|
|
1972
|
+
const { validateOutDirForClean: validateOutDirForClean2 } = await Promise.resolve().then(() => (init_cli_utils(), cli_utils_exports));
|
|
1973
|
+
validateOutDirForClean2(args.outDir, args.clean, args.appPath, args.initPath);
|
|
1028
1974
|
await buildBundleFromEntryContent({
|
|
1029
1975
|
entryContent: options.generateEntry(args.appPath, args.initPath),
|
|
1030
|
-
tempEntryFilename: options.tempEntryFilename,
|
|
1031
1976
|
tsconfigPath: args.tsconfigPath,
|
|
1032
1977
|
outDir: args.outDir,
|
|
1033
1978
|
outName: args.outName,
|
|
@@ -1048,14 +1993,17 @@ async function runCliCommand(parsedArgs) {
|
|
|
1048
1993
|
loadEnvFiles(start.env);
|
|
1049
1994
|
}
|
|
1050
1995
|
await preloadModules(start.require);
|
|
1051
|
-
const { app, init } = await loadBuiltApp(start.appPath);
|
|
1052
|
-
startLocalServer(app, {
|
|
1996
|
+
const { app, init, shutdown } = await loadBuiltApp(start.appPath);
|
|
1997
|
+
await startLocalServer(app, {
|
|
1053
1998
|
...start.options,
|
|
1054
1999
|
init: init ? async () => {
|
|
1055
2000
|
await init();
|
|
1056
2001
|
} : void 0,
|
|
2002
|
+
onShutdown: shutdown ? async () => {
|
|
2003
|
+
await shutdown();
|
|
2004
|
+
} : void 0,
|
|
1057
2005
|
exitAfterShutdown: true
|
|
1058
|
-
});
|
|
2006
|
+
}).ready;
|
|
1059
2007
|
return;
|
|
1060
2008
|
}
|
|
1061
2009
|
if (parsedArgs.subcommand === "build") {
|
|
@@ -1069,8 +2017,8 @@ async function runCliCommand(parsedArgs) {
|
|
|
1069
2017
|
}
|
|
1070
2018
|
await preloadModules(startServerless.require);
|
|
1071
2019
|
const handler = await loadHandler(startServerless.handlerPath);
|
|
1072
|
-
const app = createServerlessAdapterApp(handler);
|
|
1073
|
-
startLocalServer(app, { ...startServerless.options, exitAfterShutdown: true });
|
|
2020
|
+
const app = createServerlessAdapterApp(handler, { maxBodyBytes: startServerless.maxBodyBytes });
|
|
2021
|
+
await startLocalServer(app, { ...startServerless.options, exitAfterShutdown: true }).ready;
|
|
1074
2022
|
return;
|
|
1075
2023
|
}
|
|
1076
2024
|
await buildServerless(parsedArgs.buildServerless);
|
|
@@ -1085,11 +2033,15 @@ init_cli_api();
|
|
|
1085
2033
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1086
2034
|
0 && (module.exports = {
|
|
1087
2035
|
CLI_VERSION,
|
|
2036
|
+
DEFAULT_ADAPTER_MAX_BODY_BYTES,
|
|
2037
|
+
TEMP_BUILD_ENTRY_FILENAME,
|
|
2038
|
+
TEMP_SERVERLESS_ENTRY_FILENAME,
|
|
1088
2039
|
applyServerlessResult,
|
|
1089
2040
|
buildBundleFromEntryContent,
|
|
1090
2041
|
buildChildArgs,
|
|
1091
2042
|
buildRuntime,
|
|
1092
2043
|
buildServerless,
|
|
2044
|
+
collectBody,
|
|
1093
2045
|
createServerlessAdapterApp,
|
|
1094
2046
|
extractExport,
|
|
1095
2047
|
generateRuntimeEntry,
|
|
@@ -1110,5 +2062,7 @@ init_cli_api();
|
|
|
1110
2062
|
runDevCommand,
|
|
1111
2063
|
runExpressDevCommand,
|
|
1112
2064
|
runWithWatch,
|
|
1113
|
-
toServerlessEvent
|
|
2065
|
+
toServerlessEvent,
|
|
2066
|
+
validateMaxBodyBytes,
|
|
2067
|
+
validateOutDirForClean
|
|
1114
2068
|
});
|