@web-ts-toolkit/express-runtime 0.8.0 → 0.10.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 +47 -18
- package/cli.js +245 -14
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ pnpm add @web-ts-toolkit/express-runtime express
|
|
|
25
25
|
shutdown that drains in-flight requests, and a configurable timeout.
|
|
26
26
|
- CLI binary with three subcommands:
|
|
27
27
|
- `dev` — run an Express app as a local dev server
|
|
28
|
-
- `build` — bundle the app as a serverless handler
|
|
28
|
+
- `build` — bundle the app as a serverless handler
|
|
29
29
|
- `start` — smoke-test the bundled handler locally by translating HTTP ↔ serverless events
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
@@ -103,6 +103,29 @@ For TypeScript app modules, run the CLI through `tsx`:
|
|
|
103
103
|
npx tsx ./node_modules/@web-ts-toolkit/express-runtime/dist/cli.js dev ./src/app.ts
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
#### CLI — dev with env, require, and watch
|
|
107
|
+
|
|
108
|
+
`--env` loads `.env` files into `process.env` (existing vars are not
|
|
109
|
+
overridden). `--require` preloads modules (e.g. `tsconfig-paths/register` for
|
|
110
|
+
TS path aliases) before the app module is loaded. `--watch` forks a child
|
|
111
|
+
process running the server and restarts it on file changes:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npx tsx ./node_modules/@web-ts-toolkit/express-runtime/dist/cli.js dev ./src/app.ts \
|
|
115
|
+
--env .env \
|
|
116
|
+
--require tsconfig-paths/register \
|
|
117
|
+
--watch ./src,./shared \
|
|
118
|
+
--ext ts,json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> `--env` parses `KEY=VALUE` lines (supports `export` prefix, quoted values,
|
|
122
|
+
> `#` comments). For advanced dotenv features (multiline, variable expansion),
|
|
123
|
+
> use `--require dotenv/config` instead.
|
|
124
|
+
>
|
|
125
|
+
> `--watch` uses Node 20+'s `fs.watch` with `{ recursive: true }` and forks the
|
|
126
|
+
> same CLI as a child process. On file change, the child receives `SIGTERM`,
|
|
127
|
+
> waits for it to exit, and respawns after the debounce delay (`--delay`).
|
|
128
|
+
|
|
106
129
|
> The `dev` command evaluates arbitrary code from `<app-module>` in the current
|
|
107
130
|
> process and inherits its privileges. Init logic (e.g. DB connections) should
|
|
108
131
|
> be placed at the top level of your app module since `dev` does not expose an
|
|
@@ -111,8 +134,7 @@ npx tsx ./node_modules/@web-ts-toolkit/express-runtime/dist/cli.js dev ./src/app
|
|
|
111
134
|
### CLI — build (serverless bundle)
|
|
112
135
|
|
|
113
136
|
The `build` command generates a temporary serverless entry that wraps the app
|
|
114
|
-
with `createServerlessHandler`, then bundles it
|
|
115
|
-
deployment-ready file:
|
|
137
|
+
with `createServerlessHandler`, then bundles it into a deployment-ready file:
|
|
116
138
|
|
|
117
139
|
```sh
|
|
118
140
|
npx wtt-express-runtime build ./src/app.ts --out-dir netlify/functions
|
|
@@ -135,8 +157,8 @@ This produces `netlify/functions/handler.js` (configurable via `--out-name`)
|
|
|
135
157
|
that exports a `handler` function compatible with Netlify, Vercel, AWS Lambda,
|
|
136
158
|
and any platform that calls `(event, context)`.
|
|
137
159
|
|
|
138
|
-
> `
|
|
139
|
-
>
|
|
160
|
+
> `express` is always external; additional externals can be added via
|
|
161
|
+
> `--external`.
|
|
140
162
|
|
|
141
163
|
### CLI — start (run a bundled handler locally)
|
|
142
164
|
|
|
@@ -147,7 +169,7 @@ serverless platform:
|
|
|
147
169
|
|
|
148
170
|
```sh
|
|
149
171
|
npx wtt-express-runtime build ./src/app.ts --out-dir dist
|
|
150
|
-
npx wtt-express-runtime start ./dist/handler.js --port 9000
|
|
172
|
+
npx wtt-express-runtime start ./dist/handler.js --port 9000 --env .env
|
|
151
173
|
```
|
|
152
174
|
|
|
153
175
|
The handler module must export a `handler` function (named or default) that
|
|
@@ -279,7 +301,7 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
|
279
301
|
| Command | Description |
|
|
280
302
|
| ------- | ----------------------------------------------------------------------------------- |
|
|
281
303
|
| `dev` | Run the Express app as a local dev server (`http.createServer` + graceful shutdown) |
|
|
282
|
-
| `build` | Bundle the Express app as a serverless handler
|
|
304
|
+
| `build` | Bundle the Express app as a serverless handler |
|
|
283
305
|
| `start` | Run a bundled serverless handler locally (HTTP ↔ serverless event adapter) |
|
|
284
306
|
|
|
285
307
|
#### dev options
|
|
@@ -291,6 +313,11 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
|
291
313
|
| `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
|
|
292
314
|
| `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
|
|
293
315
|
| `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
|
|
316
|
+
| `--require <module>` | Module(s) to preload before app load (repeatable; comma-separated values supported) |
|
|
317
|
+
| `--env <path>` | Env file(s) to load before app load (repeatable; existing env vars are not overridden) |
|
|
318
|
+
| `--watch <paths>` | Comma-separated paths to watch for restart (repeatable; forks a child process) |
|
|
319
|
+
| `--ext <extensions>` | Comma-separated extensions to watch (default: `ts,js,mjs,cjs,json`) |
|
|
320
|
+
| `--delay <ms>` | Debounce ms before restarting on change (default: `500`) |
|
|
294
321
|
|
|
295
322
|
#### build options
|
|
296
323
|
|
|
@@ -307,13 +334,15 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
|
307
334
|
|
|
308
335
|
#### start options
|
|
309
336
|
|
|
310
|
-
| Option | Description
|
|
311
|
-
| ------------------------- |
|
|
312
|
-
| `<handler-module>` | JS/CJS module path exporting `handler` (named or default) — the output of `build`
|
|
313
|
-
| `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`)
|
|
314
|
-
| `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`)
|
|
315
|
-
| `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration
|
|
316
|
-
| `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`)
|
|
337
|
+
| Option | Description |
|
|
338
|
+
| ------------------------- | ------------------------------------------------------------------------------------------ |
|
|
339
|
+
| `<handler-module>` | JS/CJS module path exporting `handler` (named or default) — the output of `build` |
|
|
340
|
+
| `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
|
|
341
|
+
| `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
|
|
342
|
+
| `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
|
|
343
|
+
| `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
|
|
344
|
+
| `--require <module>` | Module(s) to preload before handler load (repeatable; comma-separated values supported) |
|
|
345
|
+
| `--env <path>` | Env file(s) to load before handler load (repeatable; existing env vars are not overridden) |
|
|
317
346
|
|
|
318
347
|
#### global options
|
|
319
348
|
|
|
@@ -327,10 +356,10 @@ exit the process after the server drains. TypeScript app modules require a TS
|
|
|
327
356
|
loader (see the Quick Start CLI section for a `tsx` invocation).
|
|
328
357
|
|
|
329
358
|
The `build` command generates a temporary entry file that imports the app
|
|
330
|
-
module and wraps it with `createServerlessHandler`, then
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
module and wraps it with `createServerlessHandler`, then produces a
|
|
360
|
+
self-contained bundle. `express` is always external; all other dependencies
|
|
361
|
+
(including `@web-ts-toolkit/express-runtime` and `serverless-http`) are
|
|
362
|
+
bundled into the output unless marked external via `--external`.
|
|
334
363
|
|
|
335
364
|
## License
|
|
336
365
|
|
package/cli.js
CHANGED
|
@@ -27,6 +27,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_node_url = require("url");
|
|
28
28
|
var import_node_path = require("path");
|
|
29
29
|
var import_node_fs = require("fs");
|
|
30
|
+
var import_node_module = require("module");
|
|
31
|
+
var import_node_child_process = require("child_process");
|
|
30
32
|
|
|
31
33
|
// src/index.ts
|
|
32
34
|
var import_node_http = __toESM(require("http"));
|
|
@@ -194,6 +196,7 @@ function startLocalServer(app, options = {}) {
|
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
// src/cli-utils.ts
|
|
199
|
+
var import_meta = {};
|
|
197
200
|
var CLI_VERSION = "0.0.0-PLACEHOLDER";
|
|
198
201
|
function readValue(argv, index, name) {
|
|
199
202
|
const value = argv[index + 1];
|
|
@@ -221,6 +224,11 @@ Dev options:
|
|
|
221
224
|
--host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
|
|
222
225
|
--no-signals Disable SIGINT/SIGTERM handler registration
|
|
223
226
|
--shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
|
|
227
|
+
--require <module> Module(s) to preload before app load (repeatable)
|
|
228
|
+
--env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
|
|
229
|
+
--watch <paths> Comma-separated paths to watch for restart (repeatable; dev only)
|
|
230
|
+
--ext <extensions> Comma-separated extensions to watch (default: ts,js,mjs,cjs,json)
|
|
231
|
+
--delay <ms> Debounce ms before restarting on change (default: 500)
|
|
224
232
|
|
|
225
233
|
Build options:
|
|
226
234
|
--init <path> Init hook module (default export, async function)
|
|
@@ -236,6 +244,8 @@ Start options:
|
|
|
236
244
|
--host <hostname> Hostname to bind (default: process.env.HOST or 0.0.0.0)
|
|
237
245
|
--no-signals Disable SIGINT/SIGTERM handler registration
|
|
238
246
|
--shutdown-timeout <ms> Max ms to wait for in-flight requests (default: 5000)
|
|
247
|
+
--require <module> Module(s) to preload before handler load (repeatable)
|
|
248
|
+
--env <path> Env file(s) to load (repeatable; existing env vars are not overridden)
|
|
239
249
|
|
|
240
250
|
Global options:
|
|
241
251
|
-V, --version Show version
|
|
@@ -244,16 +254,22 @@ Global options:
|
|
|
244
254
|
Examples:
|
|
245
255
|
wtt-express-runtime dev ./dist/app.js
|
|
246
256
|
wtt-express-runtime dev ./dist/app.js --port 3000 --host localhost
|
|
257
|
+
wtt-express-runtime dev ./src/app.ts --env .env --require tsconfig-paths/register --watch ./src,./shared
|
|
247
258
|
wtt-express-runtime build ./src/app.ts --out-dir netlify/functions
|
|
248
259
|
wtt-express-runtime build ./src/app.ts --init ./src/init.ts --format esm
|
|
249
|
-
wtt-express-runtime start ./netlify/functions/handler.js --port 9000
|
|
260
|
+
wtt-express-runtime start ./netlify/functions/handler.js --port 9000 --env .env
|
|
250
261
|
wtt-express-runtime build ./src/app.ts && wtt-express-runtime start ./dist/handler.js
|
|
251
262
|
|
|
252
263
|
Notes:
|
|
253
264
|
- In dev mode, the CLI evaluates arbitrary code from <app-module> in the current process.
|
|
254
265
|
- TypeScript app modules in dev mode require a TS loader. Run via tsx:
|
|
255
266
|
npx tsx ./node_modules/@web-ts-toolkit/express-runtime/dist/cli.js dev ./src/app.ts
|
|
256
|
-
|
|
267
|
+
Or use --require with a TS-aware loader module.
|
|
268
|
+
- --env files are parsed as KEY=VALUE; existing process.env entries are never overridden.
|
|
269
|
+
For advanced dotenv features (multiline, expansion), --require dotenv/config instead.
|
|
270
|
+
- --watch forks a child process running the same CLI without --watch. On file change,
|
|
271
|
+
the child is killed (SIGTERM) and respawned after the debounce delay.
|
|
272
|
+
- In build mode, express is always external. Add more externals with --external.
|
|
257
273
|
- In start mode, the bundled handler file must be a JS/CJS module whose "handler"
|
|
258
274
|
export (or default export) is a function: (event, context) => Promise<result>.
|
|
259
275
|
- Init logic for dev mode (DB connections, etc.): add at the top level of your app module.
|
|
@@ -268,8 +284,23 @@ function isHelp(arg) {
|
|
|
268
284
|
function isSubcommand(arg) {
|
|
269
285
|
return arg === "dev" || arg === "build" || arg === "start";
|
|
270
286
|
}
|
|
287
|
+
var DEFAULT_WATCH_EXTENSIONS = ["ts", "js", "mjs", "cjs", "json"];
|
|
288
|
+
var DEFAULT_WATCH_DELAY = 500;
|
|
289
|
+
function parseRepeatable(argv, index, arg, list) {
|
|
290
|
+
const value = readValue(argv, index, arg);
|
|
291
|
+
for (const part of value.split(",")) {
|
|
292
|
+
const trimmed = part.trim();
|
|
293
|
+
if (trimmed) list.push(trimmed);
|
|
294
|
+
}
|
|
295
|
+
return index + 1;
|
|
296
|
+
}
|
|
271
297
|
function parseDevArgs(argv) {
|
|
272
298
|
const options = {};
|
|
299
|
+
const requireModules = [];
|
|
300
|
+
const envFiles = [];
|
|
301
|
+
const watchPaths = [];
|
|
302
|
+
let watchExt;
|
|
303
|
+
let watchDelay;
|
|
273
304
|
let appPath;
|
|
274
305
|
for (let index = 0; index < argv.length; index += 1) {
|
|
275
306
|
const arg = argv[index];
|
|
@@ -314,6 +345,61 @@ function parseDevArgs(argv) {
|
|
|
314
345
|
options.shutdownTimeout = Number(arg.slice("--shutdown-timeout=".length));
|
|
315
346
|
continue;
|
|
316
347
|
}
|
|
348
|
+
if (arg === "--require") {
|
|
349
|
+
index = parseRepeatable(argv, index, arg, requireModules);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (arg.startsWith("--require=")) {
|
|
353
|
+
for (const part of arg.slice("--require=".length).split(",")) {
|
|
354
|
+
const trimmed = part.trim();
|
|
355
|
+
if (trimmed) requireModules.push(trimmed);
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (arg === "--env") {
|
|
360
|
+
index = parseRepeatable(argv, index, arg, envFiles);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (arg.startsWith("--env=")) {
|
|
364
|
+
for (const part of arg.slice("--env=".length).split(",")) {
|
|
365
|
+
const trimmed = part.trim();
|
|
366
|
+
if (trimmed) envFiles.push(trimmed);
|
|
367
|
+
}
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (arg === "--watch") {
|
|
371
|
+
index = parseRepeatable(argv, index, arg, watchPaths);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (arg.startsWith("--watch=")) {
|
|
375
|
+
for (const part of arg.slice("--watch=".length).split(",")) {
|
|
376
|
+
const trimmed = part.trim();
|
|
377
|
+
if (trimmed) watchPaths.push(trimmed);
|
|
378
|
+
}
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (arg === "--ext") {
|
|
382
|
+
watchExt = [];
|
|
383
|
+
index = parseRepeatable(argv, index, arg, watchExt);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (arg.startsWith("--ext=")) {
|
|
387
|
+
watchExt = [];
|
|
388
|
+
for (const part of arg.slice("--ext=".length).split(",")) {
|
|
389
|
+
const trimmed = part.trim();
|
|
390
|
+
if (trimmed) watchExt.push(trimmed);
|
|
391
|
+
}
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (arg === "--delay") {
|
|
395
|
+
watchDelay = Number(readValue(argv, index, arg));
|
|
396
|
+
index += 1;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (arg.startsWith("--delay=")) {
|
|
400
|
+
watchDelay = Number(arg.slice("--delay=".length));
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
317
403
|
if (!arg.startsWith("--")) {
|
|
318
404
|
if (appPath) {
|
|
319
405
|
throw new Error(`Unexpected positional argument: ${arg}. App module already set to ${appPath}`);
|
|
@@ -327,11 +413,29 @@ function parseDevArgs(argv) {
|
|
|
327
413
|
printHelp();
|
|
328
414
|
throw new Error("Missing required argument: <app-module>");
|
|
329
415
|
}
|
|
330
|
-
return {
|
|
416
|
+
return {
|
|
417
|
+
appPath,
|
|
418
|
+
options,
|
|
419
|
+
require: requireModules,
|
|
420
|
+
env: envFiles,
|
|
421
|
+
watch: watchPaths,
|
|
422
|
+
watchExt: watchExt ?? DEFAULT_WATCH_EXTENSIONS,
|
|
423
|
+
watchDelay: watchDelay ?? DEFAULT_WATCH_DELAY
|
|
424
|
+
};
|
|
331
425
|
}
|
|
332
426
|
function parseStartArgs(argv) {
|
|
427
|
+
for (const arg of argv) {
|
|
428
|
+
if (arg === "--watch" || arg.startsWith("--watch=") || arg === "--ext" || arg.startsWith("--ext=") || arg === "--delay" || arg.startsWith("--delay=")) {
|
|
429
|
+
throw new Error(`--watch/--ext/--delay are not supported with the start subcommand`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
333
432
|
const result = parseDevArgs(argv);
|
|
334
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
handlerPath: result.appPath,
|
|
435
|
+
options: result.options,
|
|
436
|
+
require: result.require,
|
|
437
|
+
env: result.env
|
|
438
|
+
};
|
|
335
439
|
}
|
|
336
440
|
function parseBuildArgs(argv) {
|
|
337
441
|
let appPath;
|
|
@@ -494,6 +598,124 @@ async function loadApp(appPath) {
|
|
|
494
598
|
}
|
|
495
599
|
return resolveExport(exported, appPath);
|
|
496
600
|
}
|
|
601
|
+
function parseEnvFile(content) {
|
|
602
|
+
const result = {};
|
|
603
|
+
for (const line of content.split("\n")) {
|
|
604
|
+
let trimmed = line.trim();
|
|
605
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
606
|
+
if (trimmed.startsWith("export ")) trimmed = trimmed.slice("export ".length).trim();
|
|
607
|
+
const eqIndex = trimmed.indexOf("=");
|
|
608
|
+
if (eqIndex === -1) continue;
|
|
609
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
610
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
611
|
+
if (value.length >= 2) {
|
|
612
|
+
const first = value[0];
|
|
613
|
+
const last = value[value.length - 1];
|
|
614
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
615
|
+
value = value.slice(1, -1);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
result[key] = value;
|
|
619
|
+
}
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
function loadEnvFiles(paths) {
|
|
623
|
+
for (const p of paths) {
|
|
624
|
+
const absPath = (0, import_node_path.resolve)(process.cwd(), p);
|
|
625
|
+
if (!(0, import_node_fs.existsSync)(absPath)) {
|
|
626
|
+
throw new Error(`Env file not found: ${p}`);
|
|
627
|
+
}
|
|
628
|
+
const content = (0, import_node_fs.readFileSync)(absPath, "utf8");
|
|
629
|
+
const parsed = parseEnvFile(content);
|
|
630
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
631
|
+
if (process.env[key] === void 0) {
|
|
632
|
+
process.env[key] = value;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
var moduleRequire = (0, import_node_module.createRequire)(
|
|
638
|
+
typeof import_meta !== "undefined" && import_meta.url || (0, import_node_path.resolve)(process.cwd(), "x").replace(/x$/, "")
|
|
639
|
+
);
|
|
640
|
+
async function preloadModules(modules) {
|
|
641
|
+
for (const mod of modules) {
|
|
642
|
+
moduleRequire(mod);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function buildChildArgs(args) {
|
|
646
|
+
const result = ["dev", args.appPath];
|
|
647
|
+
if (args.options.port !== void 0) result.push("--port", String(args.options.port));
|
|
648
|
+
if (args.options.host !== void 0) result.push("--host", args.options.host);
|
|
649
|
+
if (args.options.signals === false) result.push("--no-signals");
|
|
650
|
+
if (args.options.shutdownTimeout !== void 0)
|
|
651
|
+
result.push("--shutdown-timeout", String(args.options.shutdownTimeout));
|
|
652
|
+
for (const r of args.require) result.push("--require", r);
|
|
653
|
+
for (const e of args.env) result.push("--env", e);
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
function runWithWatch(args) {
|
|
657
|
+
const cliPath = process.argv[1];
|
|
658
|
+
const childArgv = buildChildArgs(args);
|
|
659
|
+
let child = null;
|
|
660
|
+
let restartTimer = null;
|
|
661
|
+
let isShuttingDown = false;
|
|
662
|
+
const restartDelay = args.watchDelay;
|
|
663
|
+
const spawnChild = () => {
|
|
664
|
+
child = (0, import_node_child_process.fork)(cliPath, childArgv, { stdio: "inherit" });
|
|
665
|
+
child.on("exit", (code) => {
|
|
666
|
+
child = null;
|
|
667
|
+
if (!isShuttingDown && code !== null && code !== 0) {
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
const killChild = () => {
|
|
672
|
+
return new Promise((resolve) => {
|
|
673
|
+
if (!child || !child.pid) {
|
|
674
|
+
resolve();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
child.once("exit", () => resolve());
|
|
678
|
+
child.kill("SIGTERM");
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
const restart = async () => {
|
|
682
|
+
await killChild();
|
|
683
|
+
spawnChild();
|
|
684
|
+
};
|
|
685
|
+
const debouncedRestart = () => {
|
|
686
|
+
if (restartTimer) clearTimeout(restartTimer);
|
|
687
|
+
restartTimer = setTimeout(() => {
|
|
688
|
+
restartTimer = null;
|
|
689
|
+
void restart();
|
|
690
|
+
}, restartDelay);
|
|
691
|
+
};
|
|
692
|
+
for (const watchPath of args.watch) {
|
|
693
|
+
const absPath = (0, import_node_path.resolve)(process.cwd(), watchPath);
|
|
694
|
+
if (!(0, import_node_fs.existsSync)(absPath)) {
|
|
695
|
+
throw new Error(`Watch path not found: ${watchPath}`);
|
|
696
|
+
}
|
|
697
|
+
(0, import_node_fs.watch)(absPath, { recursive: true }, (_eventType, filename) => {
|
|
698
|
+
if (!filename) return;
|
|
699
|
+
const ext = (0, import_node_path.extname)(filename).slice(1).toLowerCase();
|
|
700
|
+
if (args.watchExt.includes(ext)) {
|
|
701
|
+
debouncedRestart();
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
const shutdown = () => {
|
|
706
|
+
isShuttingDown = true;
|
|
707
|
+
if (restartTimer) clearTimeout(restartTimer);
|
|
708
|
+
if (child && child.pid) {
|
|
709
|
+
child.once("exit", () => process.exit(0));
|
|
710
|
+
child.kill("SIGTERM");
|
|
711
|
+
} else {
|
|
712
|
+
process.exit(0);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
process.on("SIGINT", shutdown);
|
|
716
|
+
process.on("SIGTERM", shutdown);
|
|
717
|
+
spawnChild();
|
|
718
|
+
}
|
|
497
719
|
var TEMP_ENTRY_FILENAME = ".express-runtime-build-entry.ts";
|
|
498
720
|
function generateServerlessEntry(appPath, initPath) {
|
|
499
721
|
const absAppPath = (0, import_node_path.resolve)(process.cwd(), appPath);
|
|
@@ -513,12 +735,7 @@ function generateServerlessEntry(appPath, initPath) {
|
|
|
513
735
|
return lines.join("\n") + "\n";
|
|
514
736
|
}
|
|
515
737
|
async function buildServerless(args) {
|
|
516
|
-
|
|
517
|
-
try {
|
|
518
|
-
tsupModule = await import("tsup");
|
|
519
|
-
} catch {
|
|
520
|
-
throw new Error("`tsup` is required for the `build` command. Install it as a dev dependency:\n pnpm add -D tsup");
|
|
521
|
-
}
|
|
738
|
+
const tsupModule = await import("tsup");
|
|
522
739
|
const { build } = tsupModule;
|
|
523
740
|
const entryContent = generateServerlessEntry(args.appPath, args.initPath);
|
|
524
741
|
const tempEntryPath = (0, import_node_path.resolve)(process.cwd(), TEMP_ENTRY_FILENAME);
|
|
@@ -619,12 +836,26 @@ async function main() {
|
|
|
619
836
|
return;
|
|
620
837
|
}
|
|
621
838
|
if (parsedArgs.subcommand === "dev") {
|
|
622
|
-
const
|
|
623
|
-
|
|
839
|
+
const { dev } = parsedArgs;
|
|
840
|
+
if (dev.watch.length > 0) {
|
|
841
|
+
runWithWatch(dev);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (dev.env.length > 0) {
|
|
845
|
+
loadEnvFiles(dev.env);
|
|
846
|
+
}
|
|
847
|
+
await preloadModules(dev.require);
|
|
848
|
+
const app = await loadApp(dev.appPath);
|
|
849
|
+
startLocalServer(app, { ...dev.options, exitAfterShutdown: true });
|
|
624
850
|
} else if (parsedArgs.subcommand === "start") {
|
|
625
|
-
const
|
|
851
|
+
const { start } = parsedArgs;
|
|
852
|
+
if (start.env.length > 0) {
|
|
853
|
+
loadEnvFiles(start.env);
|
|
854
|
+
}
|
|
855
|
+
await preloadModules(start.require);
|
|
856
|
+
const handler = await loadHandler(start.handlerPath);
|
|
626
857
|
const app = createServerlessAdapterApp(handler);
|
|
627
|
-
startLocalServer(app, { ...
|
|
858
|
+
startLocalServer(app, { ...start.options, exitAfterShutdown: true });
|
|
628
859
|
} else {
|
|
629
860
|
await buildServerless(parsedArgs.build);
|
|
630
861
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/express-runtime",
|
|
3
3
|
"description": "Express app factory plus serverless handler and local dev server helpers",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-runtime",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.10.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
8
8
|
"express",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"node": ">=20"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"serverless-http": "^4.0.0"
|
|
33
|
+
"serverless-http": "^4.0.0",
|
|
34
|
+
"tsup": "^8.5.1"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"express": ">=5.0.0"
|