serve-reload 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 serve-reload contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # serve-reload
2
+
3
+ Zero-dependency static file server with live reload for development.
4
+
5
+ Most static dev servers with live reload are either abandoned — accumulating security vulnerabilities flagged by `npm audit` — or bloated with features and dependencies far beyond what a simple dev server needs. serve-reload takes a different approach: it just serves and reloads, with zero dependencies, eliminating the problem of transitive security issues at the root.
6
+
7
+ ## Features
8
+
9
+ - **Zero dependencies** — only Node.js built-ins
10
+ - **Live reload** — via Server-Sent Events (no WebSocket library needed)
11
+ - **CSS hot inject** — CSS changes apply instantly without a full page reload
12
+ - **SPA mode** — fallback routing for single-page apps
13
+ - **Proxy** — forward API requests to another server
14
+ - **CORS enabled** — by default
15
+ - **Glob ignore patterns** — `*.map`, `src/temp`, `**/test`, etc.
16
+ - **Fast startup** — nothing to install beyond Node.js
17
+ - **CLI + API** — use from the terminal or programmatically
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install -g serve-reload
23
+ ```
24
+
25
+ Or use directly with npx:
26
+
27
+ ```bash
28
+ npx serve-reload ./public
29
+ ```
30
+
31
+ ## CLI Usage
32
+
33
+ ```bash
34
+ serve-reload [root] [options]
35
+ ```
36
+
37
+ | Option | Description | Default |
38
+ |---|---|---|
39
+ | `-p, --port <n>` | Port number (auto-fallback if busy) | `3000` |
40
+ | `--host <addr>` | Bind address | `0.0.0.0` |
41
+ | `-o, --open` | Open browser on start | off |
42
+ | `--no-reload` | Disable live reload | enabled |
43
+ | `--no-cors` | Disable CORS headers | enabled |
44
+ | `--spa <file>` | SPA fallback file | — |
45
+ | `--quiet` | Suppress request logs | off |
46
+ | `--ignore <patterns>` | Comma-separated ignore patterns | — |
47
+ | `--proxy <route:url>` | Proxy route to URL (repeatable) | — |
48
+
49
+ ### Examples
50
+
51
+ ```bash
52
+ # Serve current directory on port 3000
53
+ serve-reload
54
+
55
+ # Serve ./dist on port 8080, open browser
56
+ serve-reload ./dist -p 8080 --open
57
+
58
+ # SPA mode (all unknown routes → index.html)
59
+ serve-reload ./build --spa index.html
60
+
61
+ # Ignore source maps and temp files
62
+ serve-reload --ignore "*.map,*.min.*,temp"
63
+
64
+ # Proxy API requests
65
+ serve-reload --proxy /api:https://api.example.com
66
+
67
+ # Multiple proxies
68
+ serve-reload --proxy /api:https://api.example.com --proxy /auth:http://localhost:4000
69
+
70
+ # Disable live reload (just a static server)
71
+ serve-reload ./public --no-reload
72
+ ```
73
+
74
+ ## Ignore Patterns
75
+
76
+ The `--ignore` option controls which file changes **do not trigger a reload**. Ignored files are still served normally over HTTP — they just won't cause the browser to refresh.
77
+
78
+ Supported patterns:
79
+
80
+ | Pattern | Example | Matches |
81
+ |---|---|---|
82
+ | `node_modules` | Plain name | Any path segment named `node_modules` |
83
+ | `*.map` | Extension glob | `dist/bundle.js.map` |
84
+ | `*.min.*` | Multi-extension | `bundle.min.js` |
85
+ | `src/temp` | Path prefix | `src/temp` and `src/temp/file.js` |
86
+ | `**/test` | Recursive glob | `test`, `src/test`, `deep/nested/test` |
87
+ | `?.js` | Single character | `a.js` but not `ab.js` |
88
+
89
+ Default ignored: `node_modules`, `.git`, `.DS_Store`
90
+
91
+ ## Programmatic API
92
+
93
+ ```ts
94
+ import { ServeReload } from "serve-reload";
95
+
96
+ const server = new ServeReload({
97
+ root: "./public",
98
+ port: 8080,
99
+ open: true,
100
+ spa: "index.html",
101
+ ignore: ["*.map", "temp"],
102
+ proxy: {
103
+ "/api": "https://api.example.com",
104
+ },
105
+ });
106
+
107
+ server.start();
108
+
109
+ // Later...
110
+ await server.stop();
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ 1. **Static serving** — Node's built-in `http` module serves files with correct MIME types
116
+ 2. **HTML injection** — A tiny `<script>` is injected into HTML responses that opens an SSE connection
117
+ 3. **File watching** — `fs.watch` (recursive) monitors the served directory for changes
118
+ 4. **Reload signal** — When a non-CSS file changes, all connected browsers reload. When a CSS file changes, stylesheets are hot-swapped without a full reload
119
+ 5. **Reconnect** — If the server restarts, the browser automatically reconnects and reloads
120
+
121
+ ## Requirements
122
+
123
+ Node.js ≥ 18
124
+
125
+ ## License
126
+
127
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * serve-reload CLI
4
+ *
5
+ * Usage:
6
+ * serve-reload [root] [options]
7
+ *
8
+ * Options:
9
+ * -p, --port <n> Port number (default: 3000)
10
+ * --host <addr> Bind address (default: 0.0.0.0)
11
+ * -o, --open Open browser on start
12
+ * --no-reload Disable live reload
13
+ * --no-cors Disable CORS headers
14
+ * --spa <file> SPA fallback file (e.g. index.html)
15
+ * --quiet Suppress request logging
16
+ * --ignore <dirs> Comma-separated dirs to ignore
17
+ * --proxy <r:url> Proxy route to URL (repeatable)
18
+ * --help Show this help
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;GAiBG"}
package/dist/cli.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * serve-reload CLI
5
+ *
6
+ * Usage:
7
+ * serve-reload [root] [options]
8
+ *
9
+ * Options:
10
+ * -p, --port <n> Port number (default: 3000)
11
+ * --host <addr> Bind address (default: 0.0.0.0)
12
+ * -o, --open Open browser on start
13
+ * --no-reload Disable live reload
14
+ * --no-cors Disable CORS headers
15
+ * --spa <file> SPA fallback file (e.g. index.html)
16
+ * --quiet Suppress request logging
17
+ * --ignore <dirs> Comma-separated dirs to ignore
18
+ * --proxy <r:url> Proxy route to URL (repeatable)
19
+ * --help Show this help
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const server_js_1 = require("./server.js");
23
+ const args = process.argv.slice(2);
24
+ if (args.includes("--help")) {
25
+ console.log(`
26
+ serve-reload — Zero-dependency static file server with live reload
27
+
28
+ Usage:
29
+ serve-reload [root] [options]
30
+
31
+ Options:
32
+ -p, --port <n> Port number (default: 3000)
33
+ --host <addr> Bind address (default: 0.0.0.0)
34
+ -o, --open Open browser on start
35
+ --no-reload Disable live reload
36
+ --no-cors Disable CORS headers
37
+ --spa <file> SPA fallback file (e.g. index.html)
38
+ --quiet Suppress request logging
39
+ --ignore <dirs> Comma-separated directory names to ignore
40
+ --proxy <r:url> Proxy route to URL (repeatable, e.g. /api:https://api.example.com)
41
+ --help Show this help
42
+
43
+ Examples:
44
+ serve-reload
45
+ serve-reload ./dist -p 8080 --open
46
+ serve-reload ./public --spa index.html
47
+ serve-reload --proxy /api:https://api.example.com --proxy /auth:http://localhost:4000
48
+ `);
49
+ process.exit(0);
50
+ }
51
+ function getArg(flags) {
52
+ for (const flag of flags) {
53
+ const idx = args.indexOf(flag);
54
+ if (idx !== -1 && idx + 1 < args.length) {
55
+ return args[idx + 1];
56
+ }
57
+ }
58
+ return undefined;
59
+ }
60
+ function hasFlag(flags) {
61
+ return flags.some((f) => args.includes(f));
62
+ }
63
+ // The first non-flag argument is the root directory
64
+ let root = ".";
65
+ for (const arg of args) {
66
+ if (!arg.startsWith("-")) {
67
+ root = arg;
68
+ break;
69
+ }
70
+ }
71
+ const server = new server_js_1.ServeReload({
72
+ root,
73
+ port: parseInt(getArg(["-p", "--port"]) ?? "3000", 10),
74
+ host: getArg(["--host"]) ?? "0.0.0.0",
75
+ open: hasFlag(["-o", "--open"]),
76
+ reload: !hasFlag(["--no-reload"]),
77
+ cors: !hasFlag(["--no-cors"]),
78
+ quiet: hasFlag(["--quiet"]),
79
+ spa: getArg(["--spa"]) ?? null,
80
+ ignore: getArg(["--ignore"])?.split(",").map((s) => s.trim()) ?? [],
81
+ proxy: parseProxy(),
82
+ });
83
+ function parseProxy() {
84
+ const result = {};
85
+ for (let i = 0; i < args.length; i++) {
86
+ if (args[i] === "--proxy" && i + 1 < args.length) {
87
+ const val = args[i + 1];
88
+ const sep = val.indexOf(":", val.startsWith("/") ? 1 : 0);
89
+ if (sep === -1) {
90
+ console.error(` Invalid proxy format: ${val} (expected /route:url)`);
91
+ process.exit(1);
92
+ }
93
+ const route = val.slice(0, sep);
94
+ const target = val.slice(sep + 1);
95
+ result[route] = target;
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+ server.start();
101
+ // Graceful shutdown
102
+ process.on("SIGINT", async () => {
103
+ console.log("\n Shutting down...");
104
+ await server.stop();
105
+ process.exit(0);
106
+ });
107
+ process.on("SIGTERM", async () => {
108
+ await server.stop();
109
+ process.exit(0);
110
+ });
111
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/glob.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal glob-to-regex converter. Zero dependencies.
3
+ *
4
+ * Supported patterns:
5
+ * "node_modules" → matches any path segment named node_modules
6
+ * "*.map" → matches any file ending in .map
7
+ * "src/temp" → matches exact relative prefix
8
+ * "**​/test" → matches "test" at any depth
9
+ * "*.min.*" → matches e.g. bundle.min.js
10
+ * ".git" → matches .git directory/file
11
+ */
12
+ export declare function createMatcher(patterns: string[]): (filePath: string) => boolean;
13
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAU/E"}
package/dist/glob.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal glob-to-regex converter. Zero dependencies.
4
+ *
5
+ * Supported patterns:
6
+ * "node_modules" → matches any path segment named node_modules
7
+ * "*.map" → matches any file ending in .map
8
+ * "src/temp" → matches exact relative prefix
9
+ * "**​/test" → matches "test" at any depth
10
+ * "*.min.*" → matches e.g. bundle.min.js
11
+ * ".git" → matches .git directory/file
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.createMatcher = createMatcher;
15
+ function createMatcher(patterns) {
16
+ if (patterns.length === 0)
17
+ return () => false;
18
+ const matchers = patterns.map((p) => compileSingle(p));
19
+ return (filePath) => {
20
+ // Normalize to forward slashes for matching
21
+ const normalized = filePath.replace(/\\/g, "/");
22
+ return matchers.some((m) => m(normalized));
23
+ };
24
+ }
25
+ function compileSingle(pattern) {
26
+ const p = pattern.replace(/\\/g, "/");
27
+ // Plain name without glob chars or slashes → match any segment
28
+ if (!p.includes("*") && !p.includes("/") && !p.includes("?")) {
29
+ return (filePath) => {
30
+ const segments = filePath.split("/");
31
+ return segments.includes(p);
32
+ };
33
+ }
34
+ // Patterns with slashes but no globs → match as prefix (dir) or exact
35
+ if (!p.includes("*") && !p.includes("?") && p.includes("/")) {
36
+ return (filePath) => filePath === p || filePath.startsWith(p + "/");
37
+ }
38
+ // Convert glob to regex
39
+ const regexStr = globToRegex(p);
40
+ const regex = new RegExp(regexStr);
41
+ return (filePath) => regex.test(filePath);
42
+ }
43
+ function globToRegex(glob) {
44
+ let result = "";
45
+ let i = 0;
46
+ // If pattern doesn't start with ** or a path separator, allow matching
47
+ // against any segment boundary (e.g. "*.map" matches "dir/foo.map")
48
+ const matchAnywhere = !glob.startsWith("/") && !glob.startsWith("**/");
49
+ if (matchAnywhere) {
50
+ result += "(?:^|/)";
51
+ }
52
+ else {
53
+ result += "^";
54
+ }
55
+ while (i < glob.length) {
56
+ const ch = glob[i];
57
+ if (ch === "*" && glob[i + 1] === "*") {
58
+ // ** matches any depth (including zero segments)
59
+ if (glob[i + 2] === "/") {
60
+ result += "(?:.+/)?";
61
+ i += 3;
62
+ }
63
+ else {
64
+ result += ".*";
65
+ i += 2;
66
+ }
67
+ }
68
+ else if (ch === "*") {
69
+ // * matches anything except /
70
+ result += "[^/]*";
71
+ i++;
72
+ }
73
+ else if (ch === "?") {
74
+ // ? matches single char except /
75
+ result += "[^/]";
76
+ i++;
77
+ }
78
+ else {
79
+ // Escape regex special chars
80
+ result += escapeRegex(ch);
81
+ i++;
82
+ }
83
+ }
84
+ result += "$";
85
+ return result;
86
+ }
87
+ function escapeRegex(ch) {
88
+ return ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
89
+ }
90
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAEH,sCAUC;AAVD,SAAgB,aAAa,CAAC,QAAkB;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC;IAE9C,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvD,OAAO,CAAC,QAAgB,EAAE,EAAE;QAC1B,4CAA4C;QAC5C,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEtC,+DAA+D;IAC/D,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,CAAC,QAAQ,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IACtE,CAAC;IAED,wBAAwB;IACxB,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEnC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,uEAAuE;IACvE,oEAAoE;IACpE,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,SAAS,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnB,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACtC,iDAAiD;YACjD,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,IAAI,UAAU,CAAC;gBACrB,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,IAAI,CAAC;gBACf,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;QACH,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,8BAA8B;YAC9B,MAAM,IAAI,OAAO,CAAC;YAClB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACtB,iCAAiC;YACjC,MAAM,IAAI,MAAM,CAAC;YACjB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,CAAC;YACN,6BAA6B;YAC7B,MAAM,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;YAC1B,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,CAAC;IACd,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,EAAU;IAC7B,OAAO,EAAE,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * serve-reload — Zero-dependency static file server with live reload.
3
+ *
4
+ * Usage (programmatic):
5
+ *
6
+ * import { ServeReload } from "serve-reload";
7
+ * const server = new ServeReload({ root: "./public", port: 8080 });
8
+ * server.start();
9
+ */
10
+ export { ServeReload } from "./server.js";
11
+ export type { ServeReloadOptions } from "./server.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * serve-reload — Zero-dependency static file server with live reload.
4
+ *
5
+ * Usage (programmatic):
6
+ *
7
+ * import { ServeReload } from "serve-reload";
8
+ * const server = new ServeReload({ root: "./public", port: 8080 });
9
+ * server.start();
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ServeReload = void 0;
13
+ var server_js_1 = require("./server.js");
14
+ Object.defineProperty(exports, "ServeReload", { enumerable: true, get: function () { return server_js_1.ServeReload; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,yCAA0C;AAAjC,wGAAA,WAAW,OAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The tiny client-side script injected into HTML pages.
3
+ * Uses Server-Sent Events (SSE) to listen for reload signals.
4
+ * Automatically reconnects if the connection drops (server restart).
5
+ */
6
+ /**
7
+ * Inject the live-reload script into an HTML string.
8
+ * Inserts right before </body> if present, otherwise appends to the end.
9
+ */
10
+ export declare function inject(html: string, sseEndpoint: string): string;
11
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAShE"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * The tiny client-side script injected into HTML pages.
4
+ * Uses Server-Sent Events (SSE) to listen for reload signals.
5
+ * Automatically reconnects if the connection drops (server restart).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.inject = inject;
9
+ function clientScript(sseEndpoint) {
10
+ return `<script>/*serve-reload*/(function(){var c=!1;function n(){var e=new EventSource("${sseEndpoint}");e.onmessage=function(ev){var d=ev.data;if(d==="1")return location.reload();var t=Date.now();document.querySelectorAll('link[rel="stylesheet"]').forEach(function(l){var h=l.getAttribute("href");if(h&&h.split("?")[0].endsWith(d)){l.setAttribute("href",h.split("?")[0]+"?t="+t)}})};e.onopen=function(){if(c)location.reload();c=!0};e.onerror=function(){e.close();setTimeout(n,1e3)}}n()})()</script>`;
11
+ }
12
+ /**
13
+ * Inject the live-reload script into an HTML string.
14
+ * Inserts right before </body> if present, otherwise appends to the end.
15
+ */
16
+ function inject(html, sseEndpoint) {
17
+ const script = clientScript(sseEndpoint);
18
+ if (html.includes("</body>")) {
19
+ return html.replace("</body>", script + "\n</body>");
20
+ }
21
+ if (html.includes("</head>")) {
22
+ return html.replace("</head>", script + "\n</head>");
23
+ }
24
+ return html + script;
25
+ }
26
+ //# sourceMappingURL=injector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.js","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,wBASC;AAjBD,SAAS,YAAY,CAAC,WAAmB;IACvC,OAAO,oFAAoF,WAAW,+YAA+Y,CAAC;AACxf,CAAC;AAED;;;GAGG;AACH,SAAgB,MAAM,CAAC,IAAY,EAAE,WAAmB;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,IAAI,GAAG,MAAM,CAAC;AACvB,CAAC"}
package/dist/mime.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal MIME type lookup by file extension.
3
+ * Covers the most common static file types served during development.
4
+ */
5
+ export declare function lookup(filePath: string): string;
6
+ //# sourceMappingURL=mime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgDH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG/C"}
package/dist/mime.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal MIME type lookup by file extension.
4
+ * Covers the most common static file types served during development.
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.lookup = lookup;
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const MIME_TYPES = {
13
+ // Text / markup
14
+ ".html": "text/html; charset=utf-8",
15
+ ".htm": "text/html; charset=utf-8",
16
+ ".css": "text/css; charset=utf-8",
17
+ ".js": "application/javascript; charset=utf-8",
18
+ ".mjs": "application/javascript; charset=utf-8",
19
+ ".json": "application/json; charset=utf-8",
20
+ ".xml": "application/xml; charset=utf-8",
21
+ ".txt": "text/plain; charset=utf-8",
22
+ ".md": "text/markdown; charset=utf-8",
23
+ ".csv": "text/csv; charset=utf-8",
24
+ // Images
25
+ ".png": "image/png",
26
+ ".jpg": "image/jpeg",
27
+ ".jpeg": "image/jpeg",
28
+ ".gif": "image/gif",
29
+ ".svg": "image/svg+xml",
30
+ ".ico": "image/x-icon",
31
+ ".webp": "image/webp",
32
+ ".avif": "image/avif",
33
+ // Fonts
34
+ ".woff": "font/woff",
35
+ ".woff2": "font/woff2",
36
+ ".ttf": "font/ttf",
37
+ ".otf": "font/otf",
38
+ ".eot": "application/vnd.ms-fontobject",
39
+ // Media
40
+ ".mp4": "video/mp4",
41
+ ".webm": "video/webm",
42
+ ".ogg": "audio/ogg",
43
+ ".mp3": "audio/mpeg",
44
+ ".wav": "audio/wav",
45
+ // Other
46
+ ".pdf": "application/pdf",
47
+ ".zip": "application/zip",
48
+ ".wasm": "application/wasm",
49
+ ".map": "application/json",
50
+ };
51
+ function lookup(filePath) {
52
+ const ext = node_path_1.default.extname(filePath).toLowerCase();
53
+ return MIME_TYPES[ext] || "application/octet-stream";
54
+ }
55
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;AAgDH,wBAGC;AAjDD,0DAA6B;AAE7B,MAAM,UAAU,GAA2B;IACzC,gBAAgB;IAChB,OAAO,EAAE,0BAA0B;IACnC,MAAM,EAAE,0BAA0B;IAClC,MAAM,EAAE,yBAAyB;IACjC,KAAK,EAAE,uCAAuC;IAC9C,MAAM,EAAE,uCAAuC;IAC/C,OAAO,EAAE,iCAAiC;IAC1C,MAAM,EAAE,gCAAgC;IACxC,MAAM,EAAE,2BAA2B;IACnC,KAAK,EAAE,8BAA8B;IACrC,MAAM,EAAE,yBAAyB;IAEjC,SAAS;IACT,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,cAAc;IACtB,OAAO,EAAE,YAAY;IACrB,OAAO,EAAE,YAAY;IAErB,QAAQ;IACR,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,+BAA+B;IAEvC,QAAQ;IACR,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IAEnB,QAAQ;IACR,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,kBAAkB;CAC3B,CAAC;AAEF,SAAgB,MAAM,CAAC,QAAgB;IACrC,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACvD,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Core static file server with SSE-based live reload.
3
+ */
4
+ import http from "node:http";
5
+ export interface ServeReloadOptions {
6
+ /** Directory to serve (default ".") */
7
+ root?: string;
8
+ /** Port number (default 3000) */
9
+ port?: number;
10
+ /** Host to bind (default "0.0.0.0") */
11
+ host?: string;
12
+ /** Open browser on start (default false) */
13
+ open?: boolean;
14
+ /** Enable live reload (default true) */
15
+ reload?: boolean;
16
+ /** Enable permissive CORS (default true) */
17
+ cors?: boolean;
18
+ /** Suppress request logging (default false) */
19
+ quiet?: boolean;
20
+ /** Extra directory names to ignore when watching */
21
+ ignore?: string[];
22
+ /** Serve this file for 404s — SPA mode (e.g. "index.html") */
23
+ spa?: string | null;
24
+ /** Proxy rules: { route: targetURL } (e.g. { "/api": "https://api.example.com" }) */
25
+ proxy?: Record<string, string>;
26
+ }
27
+ export declare class ServeReload {
28
+ root: string;
29
+ port: number;
30
+ host: string;
31
+ open: boolean;
32
+ reload: boolean;
33
+ cors: boolean;
34
+ quiet: boolean;
35
+ spa: string | null;
36
+ ignore: string[];
37
+ proxy: Map<string, string>;
38
+ private requestedPort;
39
+ private clients;
40
+ private connections;
41
+ _server: http.Server | null;
42
+ private watcher;
43
+ constructor(opts?: ServeReloadOptions);
44
+ start(): this;
45
+ stop(): Promise<void>;
46
+ handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void;
47
+ private serveFile;
48
+ private handle404;
49
+ private handleSSE;
50
+ private broadcast;
51
+ private matchProxy;
52
+ private handleProxy;
53
+ private log;
54
+ private openBrowser;
55
+ }
56
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,wCAAwC;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,qBAAa,WAAW;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,WAAW,CAAyB;IAE5C,OAAO,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAQ;IACnC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,IAAI,GAAE,kBAAuB;IAczC,KAAK,IAAI,IAAI;IAmDP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB3B,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI;IA6CxE,OAAO,CAAC,SAAS;IAuDjB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,WAAW;IAoCnB,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,WAAW;CASpB"}
package/dist/server.js ADDED
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * Core static file server with SSE-based live reload.
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ServeReload = void 0;
10
+ const node_http_1 = __importDefault(require("node:http"));
11
+ const node_https_1 = __importDefault(require("node:https"));
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const node_url_1 = require("node:url");
15
+ const node_child_process_1 = require("node:child_process");
16
+ const mime_js_1 = require("./mime.js");
17
+ const injector_js_1 = require("./injector.js");
18
+ const watcher_js_1 = require("./watcher.js");
19
+ const SSE_ENDPOINT = "/__serve_reload_sse__";
20
+ class ServeReload {
21
+ root;
22
+ port;
23
+ host;
24
+ open;
25
+ reload;
26
+ cors;
27
+ quiet;
28
+ spa;
29
+ ignore;
30
+ proxy;
31
+ requestedPort;
32
+ clients = new Set();
33
+ connections = new Set();
34
+ /* @internal – exposed for testing */
35
+ _server = null;
36
+ watcher = null;
37
+ constructor(opts = {}) {
38
+ this.root = node_path_1.default.resolve(opts.root ?? ".");
39
+ this.port = opts.port ?? 3000;
40
+ this.host = opts.host ?? "0.0.0.0";
41
+ this.open = opts.open ?? false;
42
+ this.reload = opts.reload ?? true;
43
+ this.cors = opts.cors ?? true;
44
+ this.quiet = opts.quiet ?? false;
45
+ this.spa = opts.spa ?? null;
46
+ this.ignore = ["node_modules", ".git", ".DS_Store", ...(opts.ignore ?? [])];
47
+ this.proxy = new Map(Object.entries(opts.proxy ?? {}));
48
+ this.requestedPort = this.port;
49
+ }
50
+ start() {
51
+ this._server = node_http_1.default.createServer((req, res) => this.handleRequest(req, res));
52
+ // Track open connections so we can destroy them on stop
53
+ this._server.on("connection", (socket) => {
54
+ this.connections.add(socket);
55
+ socket.on("close", () => this.connections.delete(socket));
56
+ });
57
+ this._server.on("error", (err) => {
58
+ if (err.code === "EADDRINUSE" && this.port === this.requestedPort) {
59
+ console.log(` Port ${this.port} is busy, using a random port...`);
60
+ this.port = 0;
61
+ this._server.listen(0, this.host);
62
+ }
63
+ else {
64
+ throw err;
65
+ }
66
+ });
67
+ this._server.listen(this.port, this.host, () => {
68
+ const addr = this._server.address();
69
+ this.port = addr.port;
70
+ const url = `http://localhost:${this.port}`;
71
+ console.log(`\n serve-reload\n`);
72
+ console.log(` Serving ${this.root}`);
73
+ console.log(` Local ${url}`);
74
+ if (this.reload)
75
+ console.log(` Reload enabled`);
76
+ if (this.spa)
77
+ console.log(` SPA fallback → ${this.spa}`);
78
+ for (const [route, target] of this.proxy) {
79
+ console.log(` Proxy ${route} → ${target}`);
80
+ }
81
+ console.log();
82
+ if (this.open)
83
+ this.openBrowser(url);
84
+ });
85
+ if (this.reload) {
86
+ this.watcher = new watcher_js_1.Watcher(this.root, {
87
+ ignore: this.ignore,
88
+ onChange: (filePath) => {
89
+ const isCSS = filePath.endsWith(".css");
90
+ if (!this.quiet)
91
+ console.log(` ${isCSS ? "css" : "changed"} ${filePath}`);
92
+ this.broadcast(filePath);
93
+ },
94
+ });
95
+ this.watcher.start();
96
+ }
97
+ return this;
98
+ }
99
+ async stop() {
100
+ if (this.watcher)
101
+ this.watcher.stop();
102
+ for (const res of this.clients) {
103
+ res.end();
104
+ }
105
+ this.clients.clear();
106
+ // Destroy all open sockets so server.close() doesn't hang
107
+ for (const socket of this.connections) {
108
+ socket.destroy();
109
+ }
110
+ this.connections.clear();
111
+ if (this._server) {
112
+ await new Promise((resolve) => this._server.close(() => resolve()));
113
+ }
114
+ }
115
+ /* ---- request handling ---- */
116
+ handleRequest(req, res) {
117
+ // SSE endpoint
118
+ if (req.url === SSE_ENDPOINT) {
119
+ return this.handleSSE(req, res);
120
+ }
121
+ // Proxy
122
+ const proxyTarget = this.matchProxy(req.url);
123
+ if (proxyTarget) {
124
+ return this.handleProxy(req, res, proxyTarget.target, proxyTarget.rewrittenPath);
125
+ }
126
+ // CORS headers
127
+ if (this.cors) {
128
+ res.setHeader("Access-Control-Allow-Origin", "*");
129
+ res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
130
+ res.setHeader("Access-Control-Allow-Headers", "*");
131
+ if (req.method === "OPTIONS") {
132
+ res.writeHead(204);
133
+ return void res.end();
134
+ }
135
+ }
136
+ // Only GET / HEAD
137
+ if (req.method !== "GET" && req.method !== "HEAD") {
138
+ res.writeHead(405, { "Content-Type": "text/plain" });
139
+ return void res.end("Method Not Allowed");
140
+ }
141
+ // Decode & sanitize URL
142
+ let urlPath;
143
+ try {
144
+ urlPath = decodeURIComponent(req.url.split("?")[0]);
145
+ }
146
+ catch {
147
+ res.writeHead(400);
148
+ return void res.end("Bad Request");
149
+ }
150
+ // Prevent path traversal
151
+ const safePath = node_path_1.default.normalize(urlPath).replace(/^(\.\.[/\\])+/, "");
152
+ const filePath = node_path_1.default.join(this.root, safePath);
153
+ this.serveFile(req, res, filePath, urlPath);
154
+ }
155
+ serveFile(req, res, filePath, urlPath) {
156
+ node_fs_1.default.stat(filePath, (err, stats) => {
157
+ if (err || !stats || (!stats.isFile() && !stats.isDirectory())) {
158
+ return this.handle404(req, res, urlPath);
159
+ }
160
+ // Directory → try index.html
161
+ if (stats.isDirectory()) {
162
+ if (!urlPath.endsWith("/")) {
163
+ res.writeHead(301, { Location: urlPath + "/" });
164
+ return void res.end();
165
+ }
166
+ return this.serveFile(req, res, node_path_1.default.join(filePath, "index.html"), urlPath);
167
+ }
168
+ const mimeType = (0, mime_js_1.lookup)(filePath);
169
+ const isHTML = mimeType.startsWith("text/html");
170
+ if (isHTML && this.reload) {
171
+ // Read entirely to inject script
172
+ node_fs_1.default.readFile(filePath, "utf-8", (readErr, content) => {
173
+ if (readErr) {
174
+ res.writeHead(500);
175
+ return void res.end("Internal Server Error");
176
+ }
177
+ const injected = (0, injector_js_1.inject)(content, SSE_ENDPOINT);
178
+ const buf = Buffer.from(injected, "utf-8");
179
+ res.writeHead(200, {
180
+ "Content-Type": mimeType,
181
+ "Content-Length": buf.length,
182
+ "Cache-Control": "no-cache, no-store, must-revalidate",
183
+ });
184
+ if (req.method === "HEAD")
185
+ return void res.end();
186
+ res.end(buf);
187
+ this.log(200, urlPath);
188
+ });
189
+ }
190
+ else {
191
+ // Stream the file
192
+ res.writeHead(200, {
193
+ "Content-Type": mimeType,
194
+ "Content-Length": stats.size,
195
+ "Cache-Control": "no-cache",
196
+ });
197
+ if (req.method === "HEAD")
198
+ return void res.end();
199
+ node_fs_1.default.createReadStream(filePath).pipe(res);
200
+ this.log(200, urlPath);
201
+ }
202
+ });
203
+ }
204
+ handle404(req, res, urlPath) {
205
+ // SPA fallback
206
+ if (this.spa) {
207
+ const spaPath = node_path_1.default.join(this.root, this.spa);
208
+ if (node_fs_1.default.existsSync(spaPath)) {
209
+ return this.serveFile(req, res, spaPath, urlPath);
210
+ }
211
+ }
212
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
213
+ res.end(`<!DOCTYPE html><html><head><title>404</title></head><body><h1>404 — Not Found</h1><p>${urlPath}</p></body></html>`);
214
+ this.log(404, urlPath);
215
+ }
216
+ /* ---- SSE ---- */
217
+ handleSSE(_req, res) {
218
+ res.writeHead(200, {
219
+ "Content-Type": "text/event-stream",
220
+ "Cache-Control": "no-cache",
221
+ Connection: "keep-alive",
222
+ });
223
+ res.write(":\n\n"); // comment to keep alive
224
+ this.clients.add(res);
225
+ _req.on("close", () => {
226
+ this.clients.delete(res);
227
+ });
228
+ }
229
+ broadcast(filePath) {
230
+ const isCSS = filePath.endsWith(".css");
231
+ const msg = isCSS ? `data: ${filePath}\n\n` : `data: 1\n\n`;
232
+ for (const client of this.clients) {
233
+ client.write(msg);
234
+ }
235
+ }
236
+ /* ---- proxy ---- */
237
+ matchProxy(url) {
238
+ const urlPath = url.split("?")[0];
239
+ for (const [route, target] of this.proxy) {
240
+ if (urlPath === route || urlPath.startsWith(route + "/") || urlPath.startsWith(route + "?")) {
241
+ // Keep the full original URL (path + query) for forwarding
242
+ return { target, rewrittenPath: url };
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ handleProxy(req, res, target, forwardPath) {
248
+ const targetUrl = new node_url_1.URL(forwardPath, target);
249
+ const lib = targetUrl.protocol === "https:" ? node_https_1.default : node_http_1.default;
250
+ const proxyReq = lib.request(targetUrl, {
251
+ method: req.method,
252
+ headers: {
253
+ ...req.headers,
254
+ host: targetUrl.host,
255
+ },
256
+ }, (proxyRes) => {
257
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
258
+ proxyRes.pipe(res);
259
+ this.log(proxyRes.statusCode ?? 502, `⇄ ${forwardPath}`);
260
+ });
261
+ proxyReq.on("error", (err) => {
262
+ res.writeHead(502, { "Content-Type": "text/plain" });
263
+ res.end(`Proxy error: ${err.message}`);
264
+ this.log(502, `⇄ ${forwardPath}`);
265
+ });
266
+ req.pipe(proxyReq);
267
+ }
268
+ /* ---- helpers ---- */
269
+ log(status, urlPath) {
270
+ if (this.quiet)
271
+ return;
272
+ const color = status < 400 ? "\x1b[32m" : "\x1b[33m";
273
+ const reset = "\x1b[0m";
274
+ console.log(` ${color}${status}${reset} ${urlPath}`);
275
+ }
276
+ openBrowser(url) {
277
+ const cmd = process.platform === "darwin"
278
+ ? "open"
279
+ : process.platform === "win32"
280
+ ? "start"
281
+ : "xdg-open";
282
+ (0, node_child_process_1.exec)(`${cmd} ${url}`);
283
+ }
284
+ }
285
+ exports.ServeReload = ServeReload;
286
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * File watcher built on top of fs.watch (recursive mode).
3
+ *
4
+ * fs.watch with { recursive: true } is supported on macOS and Windows.
5
+ * On Linux (kernel ≥ 5.9, Node ≥ 19.1) it is also supported.
6
+ * For older Linux, we fall back to walking the directory tree and
7
+ * watching each subdirectory individually.
8
+ */
9
+ export interface WatcherOptions {
10
+ /** Patterns to ignore (directory names, globs, paths). e.g. ["node_modules", "*.map", "src/temp"] */
11
+ ignore?: string[];
12
+ /** Milliseconds to debounce rapid changes (default 150) */
13
+ debounce?: number;
14
+ /** Callback invoked with the relative file path that changed */
15
+ onChange?: (filePath: string) => void;
16
+ }
17
+ export declare class Watcher {
18
+ readonly root: string;
19
+ private readonly ignoreSet;
20
+ private readonly isIgnored;
21
+ private readonly debounce;
22
+ private readonly onChange;
23
+ private watchers;
24
+ private timers;
25
+ constructor(root: string, opts?: WatcherOptions);
26
+ start(): void;
27
+ stop(): void;
28
+ private tryRecursive;
29
+ private walkAndWatch;
30
+ private handle;
31
+ }
32
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,cAAc;IAC7B,qGAAqG;IACrG,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,qBAAa,OAAO;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,MAAM,CAAoD;gBAEtD,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB;IAWnD,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,MAAM;CAef"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ /**
3
+ * File watcher built on top of fs.watch (recursive mode).
4
+ *
5
+ * fs.watch with { recursive: true } is supported on macOS and Windows.
6
+ * On Linux (kernel ≥ 5.9, Node ≥ 19.1) it is also supported.
7
+ * For older Linux, we fall back to walking the directory tree and
8
+ * watching each subdirectory individually.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.Watcher = void 0;
15
+ const node_fs_1 = __importDefault(require("node:fs"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const glob_js_1 = require("./glob.js");
18
+ class Watcher {
19
+ root;
20
+ ignoreSet;
21
+ isIgnored;
22
+ debounce;
23
+ onChange;
24
+ watchers = [];
25
+ timers = new Map();
26
+ constructor(root, opts = {}) {
27
+ this.root = node_path_1.default.resolve(root);
28
+ const patterns = opts.ignore ?? ["node_modules", ".git", ".DS_Store"];
29
+ // Simple names go into a Set for fast directory-walk filtering
30
+ this.ignoreSet = new Set(patterns.filter((p) => !p.includes("*") && !p.includes("/") && !p.includes("?")));
31
+ // Full glob matcher for all patterns
32
+ this.isIgnored = (0, glob_js_1.createMatcher)(patterns);
33
+ this.debounce = opts.debounce ?? 150;
34
+ this.onChange = opts.onChange ?? (() => { });
35
+ }
36
+ start() {
37
+ try {
38
+ this.tryRecursive();
39
+ }
40
+ catch {
41
+ this.walkAndWatch(this.root);
42
+ }
43
+ }
44
+ stop() {
45
+ for (const w of this.watchers) {
46
+ w.close();
47
+ }
48
+ this.watchers = [];
49
+ for (const t of this.timers.values()) {
50
+ clearTimeout(t);
51
+ }
52
+ this.timers.clear();
53
+ }
54
+ /* ---- internals ---- */
55
+ tryRecursive() {
56
+ const w = node_fs_1.default.watch(this.root, { recursive: true }, (_, filename) => {
57
+ if (filename)
58
+ this.handle(filename);
59
+ });
60
+ this.watchers.push(w);
61
+ }
62
+ walkAndWatch(dir) {
63
+ if (this.ignoreSet.has(node_path_1.default.basename(dir)))
64
+ return;
65
+ const w = node_fs_1.default.watch(dir, (_, filename) => {
66
+ if (filename)
67
+ this.handle(node_path_1.default.relative(this.root, node_path_1.default.join(dir, filename)));
68
+ });
69
+ this.watchers.push(w);
70
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
71
+ if (entry.isDirectory() && !this.ignoreSet.has(entry.name)) {
72
+ this.walkAndWatch(node_path_1.default.join(dir, entry.name));
73
+ }
74
+ }
75
+ }
76
+ handle(relative) {
77
+ if (this.isIgnored(relative))
78
+ return;
79
+ // debounce per file
80
+ const existing = this.timers.get(relative);
81
+ if (existing)
82
+ clearTimeout(existing);
83
+ this.timers.set(relative, setTimeout(() => {
84
+ this.timers.delete(relative);
85
+ this.onChange(relative);
86
+ }, this.debounce));
87
+ }
88
+ }
89
+ exports.Watcher = Watcher;
90
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "serve-reload",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency static file server with live reload for development",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "serve-reload": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch & node --watch-path=dist dist/cli.js",
13
+ "start": "node dist/cli.js",
14
+ "test": "node test/test.js && node test/glob.test.js",
15
+ "prepublishOnly": "tsc && npm test"
16
+ },
17
+ "keywords": [
18
+ "static",
19
+ "server",
20
+ "live-reload",
21
+ "dev-server",
22
+ "zero-dependency"
23
+ ],
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=18"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^25.5.0",
30
+ "typescript": "^5.8.0"
31
+ },
32
+ "files": [
33
+ "dist/",
34
+ "README.md"
35
+ ]
36
+ }