@web-ts-toolkit/express-runtime 0.26.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
@@ -321,6 +321,8 @@ interface Logger {
321
321
 
322
322
  ## CLI
323
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
+
324
326
  ### `wtt-express-runtime <command> <app-module> [options]`
325
327
 
326
328
  Omitting `<command>` defaults to `dev` for backward compatibility.
@@ -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 };
package/cli-api.d.ts ADDED
@@ -0,0 +1,237 @@
1
+ import { Response, Express } from 'express';
2
+ import { LocalServerOptions } from './index.js';
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 };