@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 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 three subcommands:
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 serverless handler
29
- - `start` — smoke-test the bundled handler locally by translating HTTP ↔ serverless events
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 (serverless bundle)
136
+ ### CLI — build (local runtime bundle)
135
137
 
136
- The `build` command generates a temporary serverless entry that wraps the app
137
- with `createServerlessHandler`, then bundles it into a deployment-ready file:
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 netlify/functions
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 netlify/functions
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 `netlify/functions/handler.js` (configurable via `--out-name`)
157
- that exports a `handler` function compatible with Netlify, Vercel, AWS Lambda,
158
- and any platform that calls `(event, context)`.
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 handler locally)
165
+ ### CLI — start (run a bundled app locally)
164
166
 
165
- The `start` command runs a bundled serverless handler locally by translating
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/handler.js --port 9000 --env .env
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
- The handler module must export a `handler` function (named or default) that
176
- accepts `(event, context)` and returns a result with `statusCode`, `headers`,
177
- and `body` — the same shape produced by `createServerlessHandler` via
178
- `serverless-http`.
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 | Description |
302
- | ------- | ----------------------------------------------------------------------------------- |
303
- | `dev` | Run the Express app as a local dev server (`http.createServer` + graceful shutdown) |
304
- | `build` | Bundle the Express app as a serverless handler |
305
- | `start` | Run a bundled serverless handler locally (HTTP ↔ serverless event adapter) |
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: `handler`) |
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
- | `<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) |
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 imports the app
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`.
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 };