@web-ts-toolkit/express-runtime 0.25.0 → 0.27.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 +98 -41
- package/chunk-SOW7OLVN.mjs +221 -0
- package/cli-api.d.mts +237 -0
- package/cli-api.d.ts +237 -0
- package/cli-api.js +1084 -0
- package/cli-api.mjs +839 -0
- package/cli.js +269 -68
- package/index.mjs +7 -213
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -23,10 +23,12 @@ pnpm add @web-ts-toolkit/express-runtime express
|
|
|
23
23
|
- `startLocalServer()` — `http.createServer` + `listen` with friendly
|
|
24
24
|
`EADDRINUSE` / `EACCES` errors, optional graceful `SIGINT` / `SIGTERM`
|
|
25
25
|
shutdown that drains in-flight requests, and a configurable timeout.
|
|
26
|
-
- CLI binary with
|
|
26
|
+
- CLI binary with five subcommands:
|
|
27
27
|
- `dev` — run an Express app as a local dev server
|
|
28
|
-
- `build` — bundle the app as a
|
|
29
|
-
- `start` —
|
|
28
|
+
- `build` — bundle the app as a local runtime module
|
|
29
|
+
- `start` — start the bundled local app module
|
|
30
|
+
- `build-serverless` — bundle the app as a serverless handler
|
|
31
|
+
- `start-serverless` — smoke-test the bundled handler locally by translating HTTP ↔ serverless events
|
|
30
32
|
|
|
31
33
|
## Quick Start
|
|
32
34
|
|
|
@@ -131,19 +133,19 @@ npx tsx ./node_modules/@web-ts-toolkit/express-runtime/dist/cli.js dev ./src/app
|
|
|
131
133
|
> be placed at the top level of your app module since `dev` does not expose an
|
|
132
134
|
> `init` hook.
|
|
133
135
|
|
|
134
|
-
### CLI — build (
|
|
136
|
+
### CLI — build (local runtime bundle)
|
|
135
137
|
|
|
136
|
-
The `build` command generates a temporary
|
|
137
|
-
|
|
138
|
+
The `build` command generates a temporary runtime entry that re-exports the app
|
|
139
|
+
and optional `init` hook, then bundles it into a local runtime file:
|
|
138
140
|
|
|
139
141
|
```sh
|
|
140
|
-
npx wtt-express-runtime build ./src/app.ts --out-dir
|
|
142
|
+
npx wtt-express-runtime build ./src/app.ts --out-dir dist
|
|
141
143
|
```
|
|
142
144
|
|
|
143
145
|
With an optional init hook (DB connections, cache warmup, etc.):
|
|
144
146
|
|
|
145
147
|
```sh
|
|
146
|
-
npx wtt-express-runtime build ./src/app.ts --init ./src/init.ts --out-dir
|
|
148
|
+
npx wtt-express-runtime build ./src/app.ts --init ./src/init.ts --out-dir dist
|
|
147
149
|
```
|
|
148
150
|
|
|
149
151
|
```ts
|
|
@@ -153,29 +155,54 @@ export default async () => {
|
|
|
153
155
|
};
|
|
154
156
|
```
|
|
155
157
|
|
|
156
|
-
This produces `
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
This produces `dist/app.js` (configurable via `--out-name`) that default-exports
|
|
159
|
+
the Express app and, when `--init` is used, also exports `init` for the `start`
|
|
160
|
+
command to run before listening.
|
|
159
161
|
|
|
160
162
|
> `express` is always external; additional externals can be added via
|
|
161
163
|
> `--external`.
|
|
162
164
|
|
|
163
|
-
### CLI — start (run a bundled
|
|
165
|
+
### CLI — start (run a bundled app locally)
|
|
164
166
|
|
|
165
|
-
The `start` command runs
|
|
166
|
-
HTTP requests into serverless events and the handler's results back into HTTP
|
|
167
|
-
responses — letting you smoke-test the exact `build` output without a
|
|
168
|
-
serverless platform:
|
|
167
|
+
The `start` command runs the `build` output locally with `startLocalServer()`:
|
|
169
168
|
|
|
170
169
|
```sh
|
|
171
170
|
npx wtt-express-runtime build ./src/app.ts --out-dir dist
|
|
172
|
-
npx wtt-express-runtime start ./dist/
|
|
171
|
+
npx wtt-express-runtime start ./dist/app.js --port 9000 --env .env
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The bundled app module must default-export an Express app (or export it as
|
|
175
|
+
`app`). If it exports `init`, that hook runs once before the server starts
|
|
176
|
+
listening.
|
|
177
|
+
|
|
178
|
+
### CLI — build-serverless (serverless bundle)
|
|
179
|
+
|
|
180
|
+
The `build-serverless` command preserves the previous serverless bundling flow:
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
npx wtt-express-runtime build-serverless ./src/app.ts --out-dir netlify/functions
|
|
173
184
|
```
|
|
174
185
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
With an optional init hook:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
npx wtt-express-runtime build-serverless ./src/app.ts --init ./src/init.ts --out-dir netlify/functions
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This produces `netlify/functions/handler.js` (configurable via `--out-name`)
|
|
193
|
+
that exports a `handler` function compatible with Netlify, Vercel, AWS Lambda,
|
|
194
|
+
and any platform that calls `(event, context)`.
|
|
195
|
+
|
|
196
|
+
### CLI — start-serverless (run a bundled handler locally)
|
|
197
|
+
|
|
198
|
+
The `start-serverless` command runs a bundled serverless handler locally by
|
|
199
|
+
translating HTTP requests into serverless events and the handler's results back
|
|
200
|
+
into HTTP responses:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
npx wtt-express-runtime build-serverless ./src/app.ts --out-dir dist
|
|
204
|
+
npx wtt-express-runtime start-serverless ./dist/handler.js --port 9000 --env .env
|
|
205
|
+
```
|
|
179
206
|
|
|
180
207
|
> The adapter passes the raw request body as a Buffer (no body parsing) so the
|
|
181
208
|
> handler's request hook, including the serverless-http #305 workaround, runs
|
|
@@ -294,15 +321,19 @@ interface Logger {
|
|
|
294
321
|
|
|
295
322
|
## CLI
|
|
296
323
|
|
|
324
|
+
Programmatic CLI helpers are also available from the public subpath `@web-ts-toolkit/express-runtime/cli` when another package wants to reuse the same parsing, build, watch, env-loading, or start logic without shelling out to the `wtt-express-runtime` binary.
|
|
325
|
+
|
|
297
326
|
### `wtt-express-runtime <command> <app-module> [options]`
|
|
298
327
|
|
|
299
328
|
Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
300
329
|
|
|
301
|
-
| Command
|
|
302
|
-
|
|
|
303
|
-
| `dev`
|
|
304
|
-
| `build`
|
|
305
|
-
| `start`
|
|
330
|
+
| Command | Description |
|
|
331
|
+
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
332
|
+
| `dev` | Run the Express app as a local dev server (`http.createServer` + graceful shutdown) |
|
|
333
|
+
| `build` | Bundle the Express app as a local runtime module |
|
|
334
|
+
| `start` | Run a bundled local app module with `startLocalServer()` |
|
|
335
|
+
| `build-serverless` | Bundle the Express app as a serverless handler |
|
|
336
|
+
| `start-serverless` | Run a bundled serverless handler locally (HTTP ↔ serverless event adapter) |
|
|
306
337
|
|
|
307
338
|
#### dev options
|
|
308
339
|
|
|
@@ -326,7 +357,7 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
|
326
357
|
| `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
|
|
327
358
|
| `--init <path>` | Init hook module (default export, async function) called once per cold start |
|
|
328
359
|
| `--out-dir <path>` | Output directory (default: `dist`) |
|
|
329
|
-
| `--out-name <name>` | Output filename without extension (default: `
|
|
360
|
+
| `--out-name <name>` | Output filename without extension (default: `app`) |
|
|
330
361
|
| `--format <cjs\|esm>` | Output format (default: `cjs`) |
|
|
331
362
|
| `--target <target>` | Compilation target (default: `node22`) |
|
|
332
363
|
| `--external <pkg>` | Mark package as external (repeatable; `express` is always external) |
|
|
@@ -334,15 +365,40 @@ Omitting `<command>` defaults to `dev` for backward compatibility.
|
|
|
334
365
|
|
|
335
366
|
#### start options
|
|
336
367
|
|
|
337
|
-
| Option | Description
|
|
338
|
-
| ------------------------- |
|
|
339
|
-
| `<
|
|
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
|
|
345
|
-
| `--env <path>` | Env file(s) to load before
|
|
368
|
+
| Option | Description |
|
|
369
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
370
|
+
| `<app-module>` | JS/CJS module path default-exporting an Express app (or exporting `app`) — the output of `build` |
|
|
371
|
+
| `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
|
|
372
|
+
| `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
|
|
373
|
+
| `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
|
|
374
|
+
| `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
|
|
375
|
+
| `--require <module>` | Module(s) to preload before app load (repeatable; comma-separated values supported) |
|
|
376
|
+
| `--env <path>` | Env file(s) to load before app load (repeatable; existing env vars are not overridden) |
|
|
377
|
+
|
|
378
|
+
#### build-serverless options
|
|
379
|
+
|
|
380
|
+
| Option | Description |
|
|
381
|
+
| --------------------- | -------------------------------------------------------------------------------- |
|
|
382
|
+
| `<app-module>` | Module path whose **default export** is an Express app (sync, not async factory) |
|
|
383
|
+
| `--init <path>` | Init hook module (default export, async function) called once per cold start |
|
|
384
|
+
| `--out-dir <path>` | Output directory (default: `dist`) |
|
|
385
|
+
| `--out-name <name>` | Output filename without extension (default: `handler`) |
|
|
386
|
+
| `--format <cjs\|esm>` | Output format (default: `cjs`) |
|
|
387
|
+
| `--target <target>` | Compilation target (default: `node22`) |
|
|
388
|
+
| `--external <pkg>` | Mark package as external (repeatable; `express` is always external) |
|
|
389
|
+
| `--no-clean` | Don't clean the output directory before building |
|
|
390
|
+
|
|
391
|
+
#### start-serverless options
|
|
392
|
+
|
|
393
|
+
| Option | Description |
|
|
394
|
+
| ------------------------- | -------------------------------------------------------------------------------------------- |
|
|
395
|
+
| `<handler-module>` | JS/CJS module path exporting `handler` (named or default) — the output of `build-serverless` |
|
|
396
|
+
| `--port <number>` | Port or named pipe (default: `process.env.PORT` or `8080`) |
|
|
397
|
+
| `--host <hostname>` | Hostname to bind (default: `process.env.HOST` or `0.0.0.0`) |
|
|
398
|
+
| `--no-signals` | Disable `SIGINT` / `SIGTERM` handler registration |
|
|
399
|
+
| `--shutdown-timeout <ms>` | Max ms to wait for in-flight requests (default: `5000`) |
|
|
400
|
+
| `--require <module>` | Module(s) to preload before handler load (repeatable; comma-separated values supported) |
|
|
401
|
+
| `--env <path>` | Env file(s) to load before handler load (repeatable; existing env vars are not overridden) |
|
|
346
402
|
|
|
347
403
|
#### global options
|
|
348
404
|
|
|
@@ -355,11 +411,12 @@ The `dev` command sets `exitAfterShutdown: true` so `SIGINT` / `SIGTERM` cleanly
|
|
|
355
411
|
exit the process after the server drains. TypeScript app modules require a TS
|
|
356
412
|
loader (see the Quick Start CLI section for a `tsx` invocation).
|
|
357
413
|
|
|
358
|
-
The `build` command generates a temporary entry file that
|
|
359
|
-
module and
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
bundled into the output unless marked external via
|
|
414
|
+
The `build` command generates a temporary entry file that re-exports the app
|
|
415
|
+
module and optional `init` hook, then produces a local runtime bundle. The
|
|
416
|
+
`build-serverless` command instead wraps the app with `createServerlessHandler`
|
|
417
|
+
and bundles the serverless runtime. `express` is always external; all other
|
|
418
|
+
dependencies are bundled into the output unless marked external via
|
|
419
|
+
`--external`.
|
|
363
420
|
|
|
364
421
|
## License
|
|
365
422
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import http from "http";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import serverless from "serverless-http";
|
|
5
|
+
var defaultLogger = {
|
|
6
|
+
log: (...args) => console.log(...args),
|
|
7
|
+
error: (...args) => console.error(...args),
|
|
8
|
+
debug: (...args) => console.debug(...args)
|
|
9
|
+
};
|
|
10
|
+
function applySettings(app, options) {
|
|
11
|
+
if (options.disablePoweredBy !== false) {
|
|
12
|
+
app.disable("x-powered-by");
|
|
13
|
+
}
|
|
14
|
+
app.set("etag", options.etag ?? false);
|
|
15
|
+
app.set("trust proxy", options.trustProxy ?? false);
|
|
16
|
+
}
|
|
17
|
+
function applyMiddlewareList(app, list) {
|
|
18
|
+
if (list) {
|
|
19
|
+
for (const mw of list) {
|
|
20
|
+
app.use(mw);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function applyRouters(app, options) {
|
|
25
|
+
const mounts = [];
|
|
26
|
+
if (options.router) mounts.push(options.router);
|
|
27
|
+
if (options.routers) mounts.push(...options.routers);
|
|
28
|
+
for (const mount of mounts) {
|
|
29
|
+
const path = typeof mount.path === "function" ? mount.path() : mount.path;
|
|
30
|
+
app.use(path, mount.handler);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function createExpressApp(options = {}) {
|
|
34
|
+
const app = express();
|
|
35
|
+
applySettings(app, options);
|
|
36
|
+
applyMiddlewareList(app, options.preMiddleware);
|
|
37
|
+
if (options.json !== false) {
|
|
38
|
+
app.use(express.json(options.json ?? { limit: "1mb" }));
|
|
39
|
+
}
|
|
40
|
+
if (options.urlencoded !== false) {
|
|
41
|
+
app.use(express.urlencoded(options.urlencoded ?? { extended: false, limit: "1mb" }));
|
|
42
|
+
}
|
|
43
|
+
applyMiddlewareList(app, options.middleware);
|
|
44
|
+
applyRouters(app, options);
|
|
45
|
+
applyMiddlewareList(app, options.postMiddleware);
|
|
46
|
+
if (options.finalize) {
|
|
47
|
+
options.finalize(app);
|
|
48
|
+
}
|
|
49
|
+
if (options.errorHandler) {
|
|
50
|
+
app.use(options.errorHandler);
|
|
51
|
+
}
|
|
52
|
+
return app;
|
|
53
|
+
}
|
|
54
|
+
function defaultRequestHook(req, maxBodyBytes = 1024 * 1024, logger = defaultLogger) {
|
|
55
|
+
if (!req.body || !Buffer.isBuffer(req.body)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (req.body.length > maxBodyBytes) {
|
|
59
|
+
logger.debug?.(" Skipping oversized serverless body for content-type parsing");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const bodyStr = req.body.toString("utf8");
|
|
64
|
+
const contentType = (req.headers?.["content-type"] ?? "").toLowerCase();
|
|
65
|
+
if (contentType.startsWith("application/json")) {
|
|
66
|
+
req.body = JSON.parse(bodyStr);
|
|
67
|
+
} else {
|
|
68
|
+
req.body = bodyStr;
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error("Failed to parse serverless request body:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function createServerlessHandler(app, options = {}) {
|
|
75
|
+
const logger = options.logger ?? defaultLogger;
|
|
76
|
+
const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024;
|
|
77
|
+
const requestHook = options.request ?? ((req) => defaultRequestHook(req, maxBodyBytes, logger));
|
|
78
|
+
const baseOptions = {
|
|
79
|
+
...options.serverlessOptions ?? {},
|
|
80
|
+
request: requestHook
|
|
81
|
+
};
|
|
82
|
+
if (options.response) {
|
|
83
|
+
baseOptions.response = options.response;
|
|
84
|
+
}
|
|
85
|
+
const apiHandler = serverless(app, baseOptions);
|
|
86
|
+
let initialized = null;
|
|
87
|
+
const ensureInit = () => {
|
|
88
|
+
if (!initialized) {
|
|
89
|
+
logger.debug?.("Serverless cold start: running init");
|
|
90
|
+
initialized = options.init ? options.init() : Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
return initialized;
|
|
93
|
+
};
|
|
94
|
+
const handler = async (event, context) => {
|
|
95
|
+
await ensureInit();
|
|
96
|
+
return apiHandler(event, context);
|
|
97
|
+
};
|
|
98
|
+
handler.reset = () => {
|
|
99
|
+
initialized = null;
|
|
100
|
+
};
|
|
101
|
+
return handler;
|
|
102
|
+
}
|
|
103
|
+
var DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
104
|
+
var DEFAULT_SHUTDOWN_TIMEOUT = 5e3;
|
|
105
|
+
function normalizePort(val) {
|
|
106
|
+
if (val === void 0 || val === "") {
|
|
107
|
+
const envPort = process.env.PORT;
|
|
108
|
+
if (envPort === void 0 || envPort === "") {
|
|
109
|
+
return 8080;
|
|
110
|
+
}
|
|
111
|
+
val = envPort;
|
|
112
|
+
}
|
|
113
|
+
if (typeof val === "string") {
|
|
114
|
+
const parsed = Number(val);
|
|
115
|
+
if (Number.isNaN(parsed)) {
|
|
116
|
+
return val;
|
|
117
|
+
}
|
|
118
|
+
val = parsed;
|
|
119
|
+
}
|
|
120
|
+
if (!Number.isFinite(val) || val < 0 || val > 65535) {
|
|
121
|
+
throw new Error(`Invalid port: ${String(val)}`);
|
|
122
|
+
}
|
|
123
|
+
return val;
|
|
124
|
+
}
|
|
125
|
+
function defaultOnError(error, port, logger) {
|
|
126
|
+
if (error.syscall !== "listen") {
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
const bind = typeof port === "string" ? `Pipe ${port}` : `Port ${port}`;
|
|
130
|
+
if (error.code === "EACCES") {
|
|
131
|
+
logger.error(`${bind} requires elevated privileges`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
} else if (error.code === "EADDRINUSE") {
|
|
134
|
+
logger.error(`${bind} is already in use`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
} else {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function startLocalServer(app, options = {}) {
|
|
141
|
+
const logger = options.logger ?? defaultLogger;
|
|
142
|
+
const port = normalizePort(options.port);
|
|
143
|
+
const host = options.host ?? process.env.HOST ?? "0.0.0.0";
|
|
144
|
+
const shutdownTimeout = options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT;
|
|
145
|
+
const server = http.createServer(app);
|
|
146
|
+
app.set("port", port);
|
|
147
|
+
const onError = (error) => {
|
|
148
|
+
if (options.onError) {
|
|
149
|
+
options.onError(error);
|
|
150
|
+
} else {
|
|
151
|
+
defaultOnError(error, port, logger);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const onListening = () => {
|
|
155
|
+
const addr = server.address();
|
|
156
|
+
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr?.port}`;
|
|
157
|
+
logger.log(`Server running at http://${host}:${port}/ (${bind})`);
|
|
158
|
+
options.onListening?.();
|
|
159
|
+
};
|
|
160
|
+
server.on("error", onError);
|
|
161
|
+
server.on("listening", onListening);
|
|
162
|
+
const shutdown = async () => {
|
|
163
|
+
logger.log("Shutting down...");
|
|
164
|
+
try {
|
|
165
|
+
if (options.onShutdown) {
|
|
166
|
+
await options.onShutdown();
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
logger.error("onShutdown hook failed:", err);
|
|
170
|
+
}
|
|
171
|
+
await new Promise((resolve) => {
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
server.closeAllConnections?.();
|
|
174
|
+
resolve();
|
|
175
|
+
}, shutdownTimeout);
|
|
176
|
+
server.close((err) => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
if (err) {
|
|
179
|
+
logger.error("Server close error:", err);
|
|
180
|
+
}
|
|
181
|
+
resolve();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
if (options.exitAfterShutdown) {
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
if (options.signals !== false) {
|
|
189
|
+
const list = options.signals === void 0 || options.signals === true ? DEFAULT_SIGNALS : options.signals;
|
|
190
|
+
for (const sig of list) {
|
|
191
|
+
process.once(sig, () => void shutdown());
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const start = async () => {
|
|
195
|
+
try {
|
|
196
|
+
if (options.init) {
|
|
197
|
+
await options.init();
|
|
198
|
+
}
|
|
199
|
+
if (typeof port === "number") {
|
|
200
|
+
server.listen(port, host);
|
|
201
|
+
} else {
|
|
202
|
+
server.listen(port);
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
server.emit("error", err);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
void start();
|
|
209
|
+
return {
|
|
210
|
+
server,
|
|
211
|
+
shutdown
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
createExpressApp,
|
|
217
|
+
defaultRequestHook,
|
|
218
|
+
createServerlessHandler,
|
|
219
|
+
normalizePort,
|
|
220
|
+
startLocalServer
|
|
221
|
+
};
|
package/cli-api.d.mts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Response, Express } from 'express';
|
|
2
|
+
import { LocalServerOptions } from './index.mjs';
|
|
3
|
+
import 'node:http';
|
|
4
|
+
import 'serverless-http';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Version placeholder rewritten at publish time by `@repo-toolkit/publish-package`.
|
|
8
|
+
*/
|
|
9
|
+
declare const CLI_VERSION = "0.0.0-PLACEHOLDER";
|
|
10
|
+
/**
|
|
11
|
+
* Read the next argv value after a flag, throwing if it is missing or looks
|
|
12
|
+
* like another flag.
|
|
13
|
+
*/
|
|
14
|
+
declare function readValue(argv: string[], index: number, name: string): string;
|
|
15
|
+
type Subcommand = 'dev' | 'build' | 'start' | 'build-serverless' | 'start-serverless';
|
|
16
|
+
interface DevArgs {
|
|
17
|
+
appPath: string;
|
|
18
|
+
options: Omit<LocalServerOptions, 'init' | 'onShutdown'>;
|
|
19
|
+
/** Modules to preload before loading the app (repeatable `--require`). */
|
|
20
|
+
require: string[];
|
|
21
|
+
/** Env files to load before loading the app (repeatable `--env`). */
|
|
22
|
+
env: string[];
|
|
23
|
+
/** Directories to watch for changes (repeatable `--watch`). */
|
|
24
|
+
watch: string[];
|
|
25
|
+
/** File extensions to watch (default: ts,js,mjs,cjs,json). */
|
|
26
|
+
watchExt: string[];
|
|
27
|
+
/** Debounce delay (ms) before restarting on file change (default: 500). */
|
|
28
|
+
watchDelay: number;
|
|
29
|
+
}
|
|
30
|
+
interface BuildArgs {
|
|
31
|
+
appPath: string;
|
|
32
|
+
initPath?: string;
|
|
33
|
+
outDir: string;
|
|
34
|
+
outName: string;
|
|
35
|
+
format: 'cjs' | 'esm';
|
|
36
|
+
target: string;
|
|
37
|
+
external: string[];
|
|
38
|
+
clean: boolean;
|
|
39
|
+
}
|
|
40
|
+
interface BuildEntryContentArgs {
|
|
41
|
+
entryContent: string;
|
|
42
|
+
tempEntryFilename: string;
|
|
43
|
+
outDir: string;
|
|
44
|
+
outName: string;
|
|
45
|
+
format: 'cjs' | 'esm';
|
|
46
|
+
target: string;
|
|
47
|
+
external: string[];
|
|
48
|
+
clean: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface StartArgs {
|
|
51
|
+
appPath: string;
|
|
52
|
+
options: Omit<LocalServerOptions, 'onShutdown'>;
|
|
53
|
+
/** Modules to preload before loading the app bundle (repeatable `--require`). */
|
|
54
|
+
require: string[];
|
|
55
|
+
/** Env files to load before loading the app bundle (repeatable `--env`). */
|
|
56
|
+
env: string[];
|
|
57
|
+
}
|
|
58
|
+
interface StartServerlessArgs {
|
|
59
|
+
handlerPath: string;
|
|
60
|
+
options: Omit<LocalServerOptions, 'init' | 'onShutdown'>;
|
|
61
|
+
/** Modules to preload before loading the handler (repeatable `--require`). */
|
|
62
|
+
require: string[];
|
|
63
|
+
/** Env files to load before loading the handler (repeatable `--env`). */
|
|
64
|
+
env: string[];
|
|
65
|
+
}
|
|
66
|
+
type ParsedArgs = {
|
|
67
|
+
subcommand: 'dev';
|
|
68
|
+
dev: DevArgs;
|
|
69
|
+
} | {
|
|
70
|
+
subcommand: 'build';
|
|
71
|
+
build: BuildArgs;
|
|
72
|
+
} | {
|
|
73
|
+
subcommand: 'build-serverless';
|
|
74
|
+
buildServerless: BuildArgs;
|
|
75
|
+
} | {
|
|
76
|
+
subcommand: 'start';
|
|
77
|
+
start: StartArgs;
|
|
78
|
+
} | {
|
|
79
|
+
subcommand: 'start-serverless';
|
|
80
|
+
startServerless: StartServerlessArgs;
|
|
81
|
+
} | null;
|
|
82
|
+
declare function printHelp(): void;
|
|
83
|
+
declare function parseArgs(argv: string[]): ParsedArgs;
|
|
84
|
+
/**
|
|
85
|
+
* Type-guard: an Express app is a function with `listen` and `use` methods.
|
|
86
|
+
*/
|
|
87
|
+
declare function isExpressApp(x: unknown): x is Express;
|
|
88
|
+
/**
|
|
89
|
+
* Extract the primary export from a loaded module: prefer `default`, fall back
|
|
90
|
+
* to a named `app`.
|
|
91
|
+
*/
|
|
92
|
+
declare function extractExport(mod: Record<string, unknown>): unknown;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a raw export into an Express app, awaiting an async factory if
|
|
95
|
+
* needed. Throws with a friendly message on incompatible exports.
|
|
96
|
+
*/
|
|
97
|
+
declare function resolveExport(exported: unknown, appPath: string): Promise<Express>;
|
|
98
|
+
/**
|
|
99
|
+
* Dynamically import a module and resolve its primary export to an Express app.
|
|
100
|
+
*/
|
|
101
|
+
declare function loadApp(appPath: string): Promise<Express>;
|
|
102
|
+
/**
|
|
103
|
+
* Parse env file content as KEY=VALUE lines. Supports `export` prefix,
|
|
104
|
+
* single/double-quoted values, and `#` comments. Returns parsed entries.
|
|
105
|
+
*
|
|
106
|
+
* Exported for direct unit testing.
|
|
107
|
+
*/
|
|
108
|
+
declare function parseEnvFile(content: string): Record<string, string>;
|
|
109
|
+
/**
|
|
110
|
+
* Load env files into `process.env`. Existing environment variables are
|
|
111
|
+
* **not** overridden (consistent with dotenv's default behavior). Missing
|
|
112
|
+
* files throw with a friendly message.
|
|
113
|
+
*
|
|
114
|
+
* Exported for direct unit testing.
|
|
115
|
+
*/
|
|
116
|
+
declare function loadEnvFiles(paths: string[]): void;
|
|
117
|
+
/**
|
|
118
|
+
* Preload modules (e.g. `tsconfig-paths/register`, `dotenv/config`) before
|
|
119
|
+
* loading the app module. Each module is `require()`-ed, running its
|
|
120
|
+
* side effects (registering hooks, loading configs, etc.).
|
|
121
|
+
*
|
|
122
|
+
* Exported for direct unit testing.
|
|
123
|
+
*/
|
|
124
|
+
declare function preloadModules(modules: string[]): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Reconstruct the argv for the child process, stripping --watch/--ext/--delay
|
|
127
|
+
* flags (the child runs without watch mode).
|
|
128
|
+
*
|
|
129
|
+
* Exported for direct unit testing.
|
|
130
|
+
*/
|
|
131
|
+
declare function buildChildArgs(args: DevArgs): string[];
|
|
132
|
+
/**
|
|
133
|
+
* Run the CLI in watch mode. Forks a child process running the same CLI
|
|
134
|
+
* without --watch, watches the specified paths for file changes, and
|
|
135
|
+
* restarts the child (SIGTERM → respawn) on changes matching the given
|
|
136
|
+
* extensions. Uses Node 20+'s `fs.watch` with `{ recursive: true }`.
|
|
137
|
+
*/
|
|
138
|
+
declare function runWithWatch(args: DevArgs): void;
|
|
139
|
+
type RuntimeModuleInit = () => Promise<void> | void;
|
|
140
|
+
/**
|
|
141
|
+
* Generate the temporary entry file content that wires the user's app and
|
|
142
|
+
* optional init hook into a serverless handler.
|
|
143
|
+
*
|
|
144
|
+
* Exported for direct unit testing.
|
|
145
|
+
*/
|
|
146
|
+
declare function generateServerlessEntry(appPath: string, initPath?: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* Generate the temporary entry file content that wires the user's app and
|
|
149
|
+
* optional init hook into a local runtime bundle.
|
|
150
|
+
*
|
|
151
|
+
* Exported for direct unit testing.
|
|
152
|
+
*/
|
|
153
|
+
declare function generateRuntimeEntry(appPath: string, initPath?: string): string;
|
|
154
|
+
declare function buildBundleFromEntryContent(args: BuildEntryContentArgs): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Bundle an Express app as a local runtime module. The output default-exports
|
|
157
|
+
* the app and may additionally export an `init` hook for the `start` command.
|
|
158
|
+
*/
|
|
159
|
+
declare function buildRuntime(args: BuildArgs): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Bundle an Express app as a serverless handler. Writes a temporary entry file
|
|
162
|
+
* to the user's cwd (for node_modules resolution), lazy-loads the bundled
|
|
163
|
+
* build tool, then cleans up.
|
|
164
|
+
*
|
|
165
|
+
* `express` is always external; additional externals can be passed via
|
|
166
|
+
* `BuildArgs.external`.
|
|
167
|
+
*/
|
|
168
|
+
declare function buildServerless(args: BuildArgs): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* A platform-agnostic serverless handler function (the output of
|
|
171
|
+
* `build-serverless`).
|
|
172
|
+
*/
|
|
173
|
+
type GenericHandler = (event: unknown, context: unknown) => Promise<unknown>;
|
|
174
|
+
/**
|
|
175
|
+
* The result shape returned by `serverless-http` (and the `build` output).
|
|
176
|
+
*/
|
|
177
|
+
interface ServerlessResult {
|
|
178
|
+
statusCode?: number;
|
|
179
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
180
|
+
body?: string;
|
|
181
|
+
isBase64Encoded?: boolean;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build a serverless event from HTTP request components.
|
|
185
|
+
*
|
|
186
|
+
* Exported for direct unit testing.
|
|
187
|
+
*/
|
|
188
|
+
declare function toServerlessEvent(method: string, url: string, headers: Record<string, string | string[] | undefined>, body: Buffer): Record<string, unknown>;
|
|
189
|
+
/**
|
|
190
|
+
* Write a serverless handler result to an Express response.
|
|
191
|
+
*
|
|
192
|
+
* Exported for direct unit testing.
|
|
193
|
+
*/
|
|
194
|
+
declare function applyServerlessResult(result: unknown, res: Response): void;
|
|
195
|
+
/**
|
|
196
|
+
* Create an Express app that proxies all requests to a serverless handler.
|
|
197
|
+
* Each HTTP request is translated into a serverless event, the handler is
|
|
198
|
+
* invoked, and the result is written back to the response.
|
|
199
|
+
*
|
|
200
|
+
* Express body parsers are disabled; the raw request body is read directly
|
|
201
|
+
* from the stream and passed as a Buffer (so the serverless handler's request
|
|
202
|
+
* hook — including the #305 workaround — works identically to production).
|
|
203
|
+
*/
|
|
204
|
+
declare function createServerlessAdapterApp(handler: GenericHandler): Express;
|
|
205
|
+
/**
|
|
206
|
+
* Load a bundled app module from the `build` output.
|
|
207
|
+
*/
|
|
208
|
+
declare function loadBuiltApp(appPath: string): Promise<{
|
|
209
|
+
app: Express;
|
|
210
|
+
init?: RuntimeModuleInit;
|
|
211
|
+
}>;
|
|
212
|
+
/**
|
|
213
|
+
* Load a bundled serverless handler from a JS/CJS module. The module must
|
|
214
|
+
* export a `handler` function (or use `default` export).
|
|
215
|
+
*/
|
|
216
|
+
declare function loadHandler(handlerPath: string): Promise<GenericHandler>;
|
|
217
|
+
|
|
218
|
+
type RuntimeCliCommand = Exclude<ParsedArgs, null>;
|
|
219
|
+
interface DevCommandRunner<TLoaded> {
|
|
220
|
+
load: (appPath: string) => Promise<TLoaded> | TLoaded;
|
|
221
|
+
start: (loaded: TLoaded, options: DevArgs['options'] & {
|
|
222
|
+
exitAfterShutdown: true;
|
|
223
|
+
}) => void;
|
|
224
|
+
watch?: (args: DevArgs) => void;
|
|
225
|
+
}
|
|
226
|
+
interface BuildEntryCommandOptions {
|
|
227
|
+
generateEntry: (appPath: string, initPath?: string) => string;
|
|
228
|
+
tempEntryFilename: string;
|
|
229
|
+
allowInit?: boolean;
|
|
230
|
+
initErrorMessage?: string;
|
|
231
|
+
}
|
|
232
|
+
declare function runDevCommand<TLoaded>(args: DevArgs, runner: DevCommandRunner<TLoaded>): Promise<void>;
|
|
233
|
+
declare function runExpressDevCommand(args: DevArgs): Promise<void>;
|
|
234
|
+
declare function runBuildEntryCommand(args: BuildArgs, options: BuildEntryCommandOptions): Promise<void>;
|
|
235
|
+
declare function runCliCommand(parsedArgs: RuntimeCliCommand): Promise<void>;
|
|
236
|
+
|
|
237
|
+
export { type BuildArgs, type BuildEntryCommandOptions, type BuildEntryContentArgs, CLI_VERSION, type DevArgs, type DevCommandRunner, type GenericHandler, type ParsedArgs, type RuntimeCliCommand, type RuntimeModuleInit, type ServerlessResult, type StartArgs, type StartServerlessArgs, type Subcommand, applyServerlessResult, buildBundleFromEntryContent, buildChildArgs, buildRuntime, buildServerless, createServerlessAdapterApp, extractExport, generateRuntimeEntry, generateServerlessEntry, isExpressApp, loadApp, loadBuiltApp, loadEnvFiles, loadHandler, parseArgs, parseEnvFile, preloadModules, printHelp, readValue, resolveExport, runBuildEntryCommand, runCliCommand, runDevCommand, runExpressDevCommand, runWithWatch, toServerlessEvent };
|