@t8/serve 0.1.37 → 0.1.39

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Simple static file server + bundler, primarily for demo apps and tests, manual or automated
4
4
 
5
- ## Use cases
5
+ Use cases:
6
6
 
7
7
  - Use the CLI-based server to launch a demo or test app with a single CLI command.
8
8
  - Use the code-based setup to launch an own server from multiple tests, each with its own app.
@@ -12,19 +12,29 @@ Simple static file server + bundler, primarily for demo apps and tests, manual o
12
12
  ```sh
13
13
  npx @t8/serve [url|port] [*] [app_dir] [...assets_dirs] [-b [bundle_input_path] [bundle_output_path] [bundle_output_dir]] [--watch] [--minify]
14
14
  # * = SPA mode: serve all unmatched paths as "/"
15
+ ```
16
+
17
+ Examples:
15
18
 
19
+ ```sh
16
20
  npx @t8/serve 3000 app
17
- npx @t8/serve 3000 app -b
18
- npx @t8/serve 3000 * app
21
+ # - serve files from './app' at 'localhost:3000'
22
+
19
23
  npx @t8/serve 3000 * app -b
20
- npx @t8/serve 3000 app public dist
21
- npx @t8/serve 127.0.0.1:3000 app public dist
22
- npx @t8/serve 3000 app public dist -b
23
- npx @t8/serve 3000 app public dist -b src/index.ts
24
+ # - bundle './app/index.ts' to './app/dist/index.js' [-b]
25
+ # - serve files from './app' at 'localhost:3000' in the SPA mode [*] (handling all unmatched paths as "/")
26
+
27
+ npx @t8/serve 3000 * app -b src/index.ts
28
+ # - bundle './app/src/index.ts' to './app/dist/index.js'
29
+ # - serve files from './app' at 'localhost:3000' in the SPA mode
30
+
31
+ npx @t8/serve 127.0.0.1:3000 app public dist -b
32
+ # - bundle './app/index.ts' to './app/dist/index.js'
33
+ # - serve files from './app/public' and './app/dist' at '127.0.0.1:3000'
24
34
  ```
25
35
 
26
36
  <details>
27
- <summary>Example 1 (flat)</summary>
37
+ <summary>Example with Playwright #1 (flat)</summary>
28
38
 
29
39
  ```
30
40
  // package.json
@@ -47,7 +57,6 @@ npm run play
47
57
  ```
48
58
 
49
59
  ```
50
- // With Playwright:
51
60
  // playwright.config.ts
52
61
  ...
53
62
  use: {
@@ -62,7 +71,7 @@ webServer: {
62
71
  </details>
63
72
 
64
73
  <details>
65
- <summary>Example 2 (nested)</summary>
74
+ <summary>Example with Playwright #2 (nested)</summary>
66
75
 
67
76
  ```
68
77
  // package.json
@@ -87,7 +96,6 @@ npm run play
87
96
  ```
88
97
 
89
98
  ```
90
- // With Playwright:
91
99
  // playwright.config.ts
92
100
  ...
93
101
  use: {
@@ -102,7 +110,7 @@ webServer: {
102
110
  </details>
103
111
 
104
112
  <details>
105
- <summary>Example 3 (index.html)</summary>
113
+ <summary>Example with the watch mode</summary>
106
114
 
107
115
  ```
108
116
  /app
@@ -137,9 +145,10 @@ import { serve } from "@t8/serve";
137
145
 
138
146
  // Start
139
147
  let server = await serve({
140
- port: 3000,
148
+ host: "localhost", // default
149
+ port: 3000, // default
141
150
  path: "app",
142
- bundle: true, // or input path, or `{ input, output, dir }`
151
+ bundle: true, // or input path, or { input, output, dir }
143
152
  spa: true,
144
153
  });
145
154
 
@@ -148,7 +157,7 @@ server.close();
148
157
  ```
149
158
 
150
159
  <details>
151
- <summary>Example 1 (flat)</summary>
160
+ <summary>Example with Playwright 1 (flat)</summary>
152
161
 
153
162
  ```
154
163
  /playground
@@ -182,7 +191,7 @@ test.afterAll(() => {
182
191
  </details>
183
192
 
184
193
  <details>
185
- <summary>Example 2 (nested)</summary>
194
+ <summary>Example with Playwright 2 (nested)</summary>
186
195
 
187
196
  ```
188
197
  /tests/x
@@ -220,7 +229,7 @@ test.afterAll(() => {
220
229
  </details>
221
230
 
222
231
  <details>
223
- <summary>Example 3 (simple API)</summary>
232
+ <summary>Example with an API mock</summary>
224
233
 
225
234
  ```ts
226
235
  import { serve } from "@t8/serve";
package/dist/index.cjs ADDED
@@ -0,0 +1,127 @@
1
+ let node_fs = require("node:fs");
2
+ let node_http = require("node:http");
3
+ let node_path = require("node:path");
4
+ let node_fs_promises = require("node:fs/promises");
5
+ let esbuild = require("esbuild");
6
+
7
+ async function bundle({ path = "", bundle: options, watch, minify } = {}) {
8
+ if (!options) return;
9
+ let normalizedOptions;
10
+ if (typeof options === "boolean") normalizedOptions = {};
11
+ else if (typeof options === "string") normalizedOptions = { input: options };
12
+ else normalizedOptions = options;
13
+ let dir = normalizedOptions.dir ?? "dist";
14
+ let inputFile = (0, node_path.join)(path, normalizedOptions.input ?? "index.ts");
15
+ await (0, node_fs_promises.rm)((0, node_path.join)(path, dir), {
16
+ recursive: true,
17
+ force: true
18
+ });
19
+ let buildOptions = {
20
+ entryPoints: [inputFile],
21
+ bundle: true,
22
+ format: "esm",
23
+ platform: "browser",
24
+ logLevel: "warning",
25
+ minify
26
+ };
27
+ if (normalizedOptions.output) buildOptions.outfile = (0, node_path.join)(path, dir, normalizedOptions.output);
28
+ else {
29
+ buildOptions.outdir = (0, node_path.join)(path, dir);
30
+ buildOptions.splitting = true;
31
+ }
32
+ if (watch) {
33
+ let ctx = await (0, esbuild.context)(buildOptions);
34
+ await ctx.watch();
35
+ return async () => {
36
+ await ctx.dispose();
37
+ };
38
+ }
39
+ await (0, esbuild.build)(buildOptions);
40
+ }
41
+
42
+ async function isValidFilePath(filePath, dirPath) {
43
+ if (!filePath.startsWith(dirPath)) return false;
44
+ try {
45
+ await (0, node_fs_promises.access)(filePath);
46
+ return !(await (0, node_fs_promises.lstat)(filePath)).isDirectory();
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ const cwd = process.cwd();
53
+ async function getFilePath(url = "", { path = "", dirs = [], spa }) {
54
+ let urlPath = url.replace(/[?#].*$/, "");
55
+ for (let dir of dirs.length === 0 ? [""] : dirs) {
56
+ let dirPath = (0, node_path.join)(cwd, path, dir);
57
+ let filePath = (0, node_path.join)(dirPath, urlPath);
58
+ if (!urlPath.endsWith("/") && await isValidFilePath(filePath, dirPath)) return filePath;
59
+ filePath = (0, node_path.join)(dirPath, spa ? "" : urlPath, "index.html");
60
+ if (await isValidFilePath(filePath, dirPath)) return filePath;
61
+ }
62
+ }
63
+
64
+ const defaultHost = "localhost";
65
+ const defaultPort = 3e3;
66
+ function getTarget(config = {}) {
67
+ let { host, port, url } = config;
68
+ let [, , urlHost, , urlPort] = url?.match(/^(https?:\/\/)?([^:/]+)(:(\d+))?\/?/) ?? [];
69
+ if (!urlPort && /^\d+$/.test(urlHost)) {
70
+ urlPort = urlHost;
71
+ urlHost = "";
72
+ }
73
+ return {
74
+ port: port || Number(urlPort) || defaultPort,
75
+ host: host || urlHost || defaultHost
76
+ };
77
+ }
78
+
79
+ const mimeTypes = {
80
+ html: "text/html; charset=utf-8",
81
+ js: "text/javascript",
82
+ json: "application/json",
83
+ css: "text/css",
84
+ svg: "image/svg+xml",
85
+ png: "image/png",
86
+ jpg: "image/jpeg",
87
+ gif: "image/gif",
88
+ ico: "image/x-icon",
89
+ txt: "text/plain",
90
+ md: "text/markdown"
91
+ };
92
+
93
+ /**
94
+ * Starts a static server as configured by the `config` parameter and
95
+ * returns the `Server` object.
96
+ *
97
+ * Bundles the code before starting the server, if configured with
98
+ * `config.bundle` to do so.
99
+ */
100
+ async function serve(config = {}) {
101
+ let stop = await bundle(config);
102
+ return new Promise((resolve) => {
103
+ let server = (0, node_http.createServer)(async (req, res) => {
104
+ await config.onRequest?.(req, res);
105
+ if (res.headersSent) return;
106
+ let filePath = await getFilePath(req.url, config);
107
+ if (filePath === void 0) {
108
+ res.writeHead(404, { "content-type": "text/plain" });
109
+ res.end("Not found");
110
+ return;
111
+ }
112
+ let mimeType = mimeTypes[(0, node_path.extname)(filePath).slice(1).toLowerCase()] ?? "application/octet-stream";
113
+ res.writeHead(200, { "content-type": mimeType });
114
+ (0, node_fs.createReadStream)(filePath).pipe(res);
115
+ });
116
+ if (stop) server.on("close", async () => {
117
+ await stop();
118
+ });
119
+ let { host, port } = getTarget(config);
120
+ server.listen(port, host, () => {
121
+ if (config.log) console.log(`Server running at http://${host}:${port}`);
122
+ resolve(server);
123
+ });
124
+ });
125
+ }
126
+
127
+ exports.serve = serve;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { IncomingMessage, ServerResponse, createServer } from "node:http";
1
+ import { IncomingMessage, ServerResponse, createServer } from "node:http";
2
2
 
3
- export type BundleConfig = {
3
+ type BundleConfig = {
4
4
  /**
5
5
  * Output directory path relative to the server config `path`.
6
6
  *
@@ -20,7 +20,8 @@ export type BundleConfig = {
20
20
  */
21
21
  output?: string | undefined;
22
22
  };
23
- export type Config = {
23
+
24
+ type Config = {
24
25
  /** Server URL. */
25
26
  url?: string;
26
27
  /**
@@ -65,7 +66,15 @@ export type Config = {
65
66
  /** Custom request handler. */
66
67
  onRequest?: (req?: IncomingMessage, res?: ServerResponse<IncomingMessage>) => void | Promise<void>;
67
68
  };
68
- export type Server = ReturnType<typeof createServer>;
69
- export declare function serve(config?: Config): Promise<Server>;
70
69
 
71
- export {};
70
+ type Server = ReturnType<typeof createServer>;
71
+ /**
72
+ * Starts a static server as configured by the `config` parameter and
73
+ * returns the `Server` object.
74
+ *
75
+ * Bundles the code before starting the server, if configured with
76
+ * `config.bundle` to do so.
77
+ */
78
+ declare function serve(config?: Config): Promise<Server>;
79
+
80
+ export { BundleConfig, Config, Server, serve };
@@ -1,29 +1,21 @@
1
- // src/serve.ts
2
1
  import { createReadStream } from "node:fs";
3
2
  import { createServer } from "node:http";
4
- import { extname } from "node:path";
5
-
6
- // src/bundle.ts
7
- import { rm } from "node:fs/promises";
8
- import { join } from "node:path";
3
+ import { extname, join } from "node:path";
4
+ import { access, lstat, rm } from "node:fs/promises";
9
5
  import { build, context } from "esbuild";
10
- async function bundle({
11
- path = "",
12
- bundle: options,
13
- watch,
14
- minify
15
- } = {}) {
6
+
7
+ async function bundle({ path = "", bundle: options, watch, minify } = {}) {
16
8
  if (!options) return;
17
9
  let normalizedOptions;
18
10
  if (typeof options === "boolean") normalizedOptions = {};
19
- else if (typeof options === "string")
20
- normalizedOptions = {
21
- input: options
22
- };
11
+ else if (typeof options === "string") normalizedOptions = { input: options };
23
12
  else normalizedOptions = options;
24
13
  let dir = normalizedOptions.dir ?? "dist";
25
14
  let inputFile = join(path, normalizedOptions.input ?? "index.ts");
26
- await rm(join(path, dir), { recursive: true, force: true });
15
+ await rm(join(path, dir), {
16
+ recursive: true,
17
+ force: true
18
+ });
27
19
  let buildOptions = {
28
20
  entryPoints: [inputFile],
29
21
  bundle: true,
@@ -32,8 +24,7 @@ async function bundle({
32
24
  logLevel: "warning",
33
25
  minify
34
26
  };
35
- if (normalizedOptions.output)
36
- buildOptions.outfile = join(path, dir, normalizedOptions.output);
27
+ if (normalizedOptions.output) buildOptions.outfile = join(path, dir, normalizedOptions.output);
37
28
  else {
38
29
  buildOptions.outdir = join(path, dir);
39
30
  buildOptions.splitting = true;
@@ -48,11 +39,6 @@ async function bundle({
48
39
  await build(buildOptions);
49
40
  }
50
41
 
51
- // src/getFilePath.ts
52
- import { join as join2 } from "node:path";
53
-
54
- // src/isValidFilePath.ts
55
- import { access, lstat } from "node:fs/promises";
56
42
  async function isValidFilePath(filePath, dirPath) {
57
43
  if (!filePath.startsWith(dirPath)) return false;
58
44
  try {
@@ -63,23 +49,20 @@ async function isValidFilePath(filePath, dirPath) {
63
49
  }
64
50
  }
65
51
 
66
- // src/getFilePath.ts
67
- var cwd = process.cwd();
52
+ const cwd = process.cwd();
68
53
  async function getFilePath(url = "", { path = "", dirs = [], spa }) {
69
54
  let urlPath = url.replace(/[?#].*$/, "");
70
55
  for (let dir of dirs.length === 0 ? [""] : dirs) {
71
- let dirPath = join2(cwd, path, dir);
72
- let filePath = join2(dirPath, urlPath);
73
- if (!urlPath.endsWith("/") && await isValidFilePath(filePath, dirPath))
74
- return filePath;
75
- filePath = join2(dirPath, spa ? "" : urlPath, "index.html");
56
+ let dirPath = join(cwd, path, dir);
57
+ let filePath = join(dirPath, urlPath);
58
+ if (!urlPath.endsWith("/") && await isValidFilePath(filePath, dirPath)) return filePath;
59
+ filePath = join(dirPath, spa ? "" : urlPath, "index.html");
76
60
  if (await isValidFilePath(filePath, dirPath)) return filePath;
77
61
  }
78
62
  }
79
63
 
80
- // src/getTarget.ts
81
- var defaultHost = "localhost";
82
- var defaultPort = 3e3;
64
+ const defaultHost = "localhost";
65
+ const defaultPort = 3e3;
83
66
  function getTarget(config = {}) {
84
67
  let { host, port, url } = config;
85
68
  let [, , urlHost, , urlPort] = url?.match(/^(https?:\/\/)?([^:/]+)(:(\d+))?\/?/) ?? [];
@@ -93,8 +76,7 @@ function getTarget(config = {}) {
93
76
  };
94
77
  }
95
78
 
96
- // src/mimeTypes.ts
97
- var mimeTypes = {
79
+ const mimeTypes = {
98
80
  html: "text/html; charset=utf-8",
99
81
  js: "text/javascript",
100
82
  json: "application/json",
@@ -108,7 +90,13 @@ var mimeTypes = {
108
90
  md: "text/markdown"
109
91
  };
110
92
 
111
- // src/serve.ts
93
+ /**
94
+ * Starts a static server as configured by the `config` parameter and
95
+ * returns the `Server` object.
96
+ *
97
+ * Bundles the code before starting the server, if configured with
98
+ * `config.bundle` to do so.
99
+ */
112
100
  async function serve(config = {}) {
113
101
  let stop = await bundle(config);
114
102
  return new Promise((resolve) => {
@@ -121,16 +109,13 @@ async function serve(config = {}) {
121
109
  res.end("Not found");
122
110
  return;
123
111
  }
124
- let ext = extname(filePath).slice(1).toLowerCase();
125
- let mimeType = mimeTypes[ext] ?? "application/octet-stream";
112
+ let mimeType = mimeTypes[extname(filePath).slice(1).toLowerCase()] ?? "application/octet-stream";
126
113
  res.writeHead(200, { "content-type": mimeType });
127
114
  createReadStream(filePath).pipe(res);
128
115
  });
129
- if (stop) {
130
- server.on("close", async () => {
131
- await stop();
132
- });
133
- }
116
+ if (stop) server.on("close", async () => {
117
+ await stop();
118
+ });
134
119
  let { host, port } = getTarget(config);
135
120
  server.listen(port, host, () => {
136
121
  if (config.log) console.log(`Server running at http://${host}:${port}`);
@@ -138,6 +123,5 @@ async function serve(config = {}) {
138
123
  });
139
124
  });
140
125
  }
141
- export {
142
- serve
143
- };
126
+
127
+ export { serve };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t8/serve",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "Simple static file server + bundler, primarily for demo apps and tests, manual or automated",
5
5
  "keywords": [
6
6
  "node",
@@ -9,7 +9,7 @@
9
9
  "bundler"
10
10
  ],
11
11
  "bin": {
12
- "serve": "dist/run.js"
12
+ "serve": "./dist/run.mjs"
13
13
  },
14
14
  "repository": {
15
15
  "type": "git",
@@ -18,14 +18,13 @@
18
18
  "license": "MIT",
19
19
  "author": "axtk",
20
20
  "type": "module",
21
- "main": "dist/index.js",
22
- "types": "dist/index.d.ts",
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.ts",
23
24
  "scripts": {
24
- "clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
25
- "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=node --format=esm --external:esbuild",
26
- "compile-bin": "npx esbuild src/run.ts --bundle --outfile=dist/run.js --platform=node --format=esm --external:esbuild",
27
- "preversion": "npx npm-run-all clean shape -p compile compile-bin",
28
- "shape": "npx codeshape --typecheck --emit-types"
25
+ "compile-bin": "npx esbuild src/run.ts --bundle --outfile=dist/run.mjs --platform=node --format=esm --external:esbuild",
26
+ "preversion": "npx npm-run-all shape compile-bin",
27
+ "shape": "npx codeshape"
29
28
  },
30
29
  "devDependencies": {
31
30
  "@types/node": "^24.5.2"
package/src/serve.ts CHANGED
@@ -9,6 +9,13 @@ import { mimeTypes } from "./mimeTypes.ts";
9
9
 
10
10
  export type Server = ReturnType<typeof createServer>;
11
11
 
12
+ /**
13
+ * Starts a static server as configured by the `config` parameter and
14
+ * returns the `Server` object.
15
+ *
16
+ * Bundles the code before starting the server, if configured with
17
+ * `config.bundle` to do so.
18
+ */
12
19
  export async function serve(config: Config = {}): Promise<Server> {
13
20
  let stop = await bundle(config);
14
21
 
package/tsconfig.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "include": ["index.ts", "src"],
2
+ "include": ["./index.ts", "./src"],
3
3
  "compilerOptions": {
4
4
  "declaration": true,
5
5
  "emitDeclarationOnly": true,
File without changes