@edwinencomienda/live-reloader 1.0.3 → 1.2.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/dist/index.html +14 -0
- package/dist/index.js +152 -95
- package/package.json +1 -1
package/dist/index.html
ADDED
package/dist/index.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
// index.ts
|
|
5
5
|
var {serve } = globalThis.Bun;
|
|
6
|
-
import { watch } from "fs";
|
|
6
|
+
import { existsSync, watch } from "fs";
|
|
7
7
|
import { networkInterfaces } from "os";
|
|
8
8
|
import { resolve, sep } from "path";
|
|
9
|
-
var VERSION = "
|
|
9
|
+
var VERSION = "1.2.0";
|
|
10
10
|
var clients = new Set;
|
|
11
11
|
var encoder = new TextEncoder;
|
|
12
12
|
function parseArgs() {
|
|
@@ -55,6 +55,18 @@ function parseArgs() {
|
|
|
55
55
|
return { port, hostname, rootDir: resolve(rootDir ?? process.cwd()) };
|
|
56
56
|
}
|
|
57
57
|
var { port, hostname, rootDir } = parseArgs();
|
|
58
|
+
if (!existsSync(rootDir)) {
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log(`\x1B[31m\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\x1B[0m`);
|
|
61
|
+
console.log(`\x1B[31m \u274C Directory Not Found\x1B[0m`);
|
|
62
|
+
console.log(`\x1B[31m\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\x1B[0m`);
|
|
63
|
+
console.log(`\x1B[33m\uD83D\uDCC1 Path:\x1B[0m ${rootDir}`);
|
|
64
|
+
console.log("");
|
|
65
|
+
console.log(` The specified directory does not exist.`);
|
|
66
|
+
console.log(` Please check the path and try again.`);
|
|
67
|
+
console.log("");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
58
70
|
function now() {
|
|
59
71
|
return new Date().toISOString();
|
|
60
72
|
}
|
|
@@ -79,115 +91,157 @@ function getLocalIP() {
|
|
|
79
91
|
return;
|
|
80
92
|
}
|
|
81
93
|
var server;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
var finalPort = port;
|
|
95
|
+
var attempts = 0;
|
|
96
|
+
var maxAttempts = 10;
|
|
97
|
+
while (attempts < maxAttempts) {
|
|
98
|
+
try {
|
|
99
|
+
server = serve({
|
|
100
|
+
port: finalPort,
|
|
101
|
+
hostname,
|
|
102
|
+
async fetch(req) {
|
|
103
|
+
const url = new URL(req.url);
|
|
104
|
+
const reqPath = formatPath(url);
|
|
105
|
+
if (req.method === "OPTIONS") {
|
|
106
|
+
const res2 = new Response(null, {
|
|
107
|
+
status: 204,
|
|
108
|
+
headers: {
|
|
109
|
+
"Access-Control-Allow-Origin": "*",
|
|
110
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
111
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
112
|
+
"Access-Control-Max-Age": "86400"
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
log(`${req.method} ${reqPath} -> ${res2.status}`);
|
|
116
|
+
return res2;
|
|
117
|
+
}
|
|
118
|
+
if (url.pathname === "/__reload") {
|
|
119
|
+
let controllerRef = null;
|
|
120
|
+
const stream = new ReadableStream({
|
|
121
|
+
start(controller) {
|
|
122
|
+
controllerRef = controller;
|
|
123
|
+
clients.add(controller);
|
|
124
|
+
controller.enqueue(encoder.encode(`retry: 1000
|
|
96
125
|
|
|
97
126
|
`));
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
pathname
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
127
|
+
log(`SSE connected (clients=${clients.size})`);
|
|
128
|
+
},
|
|
129
|
+
cancel() {
|
|
130
|
+
if (controllerRef)
|
|
131
|
+
clients.delete(controllerRef);
|
|
132
|
+
log(`SSE disconnected (clients=${clients.size})`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const res2 = new Response(stream, {
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
138
|
+
"Cache-Control": "no-cache, no-transform",
|
|
139
|
+
Connection: "keep-alive",
|
|
140
|
+
"Access-Control-Allow-Origin": "*",
|
|
141
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
142
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
log(`${req.method} ${reqPath} -> ${res2.status}`);
|
|
146
|
+
return res2;
|
|
147
|
+
}
|
|
148
|
+
let pathname;
|
|
149
|
+
try {
|
|
150
|
+
pathname = decodeURIComponent(url.pathname);
|
|
151
|
+
} catch {
|
|
152
|
+
const res2 = new Response("Bad Request", {
|
|
153
|
+
status: 400,
|
|
154
|
+
headers: { "Access-Control-Allow-Origin": "*" }
|
|
155
|
+
});
|
|
156
|
+
log(`${req.method} ${reqPath} -> ${res2.status}`);
|
|
157
|
+
return res2;
|
|
158
|
+
}
|
|
159
|
+
if (pathname === "/index.html") {
|
|
160
|
+
const res2 = Response.redirect(url.origin + "/" + url.search, 301);
|
|
161
|
+
log(`${req.method} ${reqPath} -> ${res2.status} (redirect to /)`);
|
|
162
|
+
return res2;
|
|
163
|
+
}
|
|
164
|
+
if (pathname === "/")
|
|
165
|
+
pathname = "/index.html";
|
|
166
|
+
const rootPrefix = rootDir.endsWith(sep) ? rootDir : rootDir + sep;
|
|
167
|
+
function isInsideRoot(p) {
|
|
168
|
+
return p === rootDir || p.startsWith(rootPrefix);
|
|
169
|
+
}
|
|
170
|
+
let resolvedPath = resolve(rootDir, `.${pathname}`);
|
|
171
|
+
if (!isInsideRoot(resolvedPath)) {
|
|
172
|
+
const res2 = new Response("Forbidden", {
|
|
173
|
+
status: 403,
|
|
174
|
+
headers: { "Access-Control-Allow-Origin": "*" }
|
|
175
|
+
});
|
|
176
|
+
log(`${req.method} ${reqPath} -> ${res2.status}`);
|
|
177
|
+
return res2;
|
|
178
|
+
}
|
|
179
|
+
let file = Bun.file(resolvedPath);
|
|
180
|
+
if (!await file.exists()) {
|
|
181
|
+
const hasExtension = pathname.includes(".") && !pathname.endsWith("/");
|
|
182
|
+
if (!hasExtension) {
|
|
183
|
+
const htmlPath = resolve(rootDir, `.${pathname}.html`);
|
|
184
|
+
if (isInsideRoot(htmlPath)) {
|
|
185
|
+
const htmlFile = Bun.file(htmlPath);
|
|
186
|
+
if (await htmlFile.exists()) {
|
|
187
|
+
resolvedPath = htmlPath;
|
|
188
|
+
file = htmlFile;
|
|
189
|
+
}
|
|
151
190
|
}
|
|
152
191
|
}
|
|
153
192
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
if (!await file.exists()) {
|
|
194
|
+
const res2 = new Response("Not Found", {
|
|
195
|
+
status: 404,
|
|
196
|
+
headers: { "Access-Control-Allow-Origin": "*" }
|
|
197
|
+
});
|
|
198
|
+
log(`${req.method} ${reqPath} -> ${res2.status}`);
|
|
199
|
+
return res2;
|
|
200
|
+
}
|
|
201
|
+
if (file.type.startsWith("text/html")) {
|
|
202
|
+
const t = await file.text();
|
|
203
|
+
const injected = `<script>
|
|
163
204
|
const es=new EventSource('/__reload');
|
|
164
205
|
es.onmessage=()=>location.reload();
|
|
165
206
|
</script>`;
|
|
166
|
-
|
|
207
|
+
const body = t.includes("</body>") ? t.replace("</body>", `${injected}</body>`) : `${t}
|
|
167
208
|
${injected}
|
|
168
209
|
`;
|
|
169
|
-
|
|
210
|
+
const res2 = new Response(body, {
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": file.type || "text/html; charset=utf-8",
|
|
213
|
+
"Access-Control-Allow-Origin": "*"
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
log(`${req.method} ${reqPath} -> ${res2.status} (${resolvedPath})`);
|
|
217
|
+
return res2;
|
|
218
|
+
}
|
|
219
|
+
const res = new Response(file, {
|
|
170
220
|
headers: {
|
|
171
|
-
"
|
|
221
|
+
"Access-Control-Allow-Origin": "*"
|
|
172
222
|
}
|
|
173
223
|
});
|
|
174
|
-
log(`${req.method} ${reqPath} -> ${
|
|
175
|
-
return
|
|
224
|
+
log(`${req.method} ${reqPath} -> ${res.status} (${resolvedPath})`);
|
|
225
|
+
return res;
|
|
176
226
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
227
|
+
});
|
|
228
|
+
break;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
231
|
+
if (msg.includes("EADDRINUSE") || msg.toLowerCase().includes("in use")) {
|
|
232
|
+
attempts++;
|
|
233
|
+
if (attempts >= maxAttempts) {
|
|
234
|
+
log(`Unable to find an available port after trying ports ${port}-${finalPort}.`);
|
|
235
|
+
console.log(` All ports are in use. Please specify a different port with --port.`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
finalPort++;
|
|
239
|
+
log(`Port ${finalPort - 1} is in use, trying port ${finalPort}...`);
|
|
240
|
+
} else {
|
|
241
|
+
log(`Failed to start server: ${msg}`);
|
|
242
|
+
process.exit(1);
|
|
180
243
|
}
|
|
181
|
-
});
|
|
182
|
-
} catch (err) {
|
|
183
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
184
|
-
if (msg.includes("EADDRINUSE") || msg.toLowerCase().includes("in use")) {
|
|
185
|
-
log(`Port ${port} is already in use.`);
|
|
186
|
-
console.log(` Try using a different port: live-reloader --port ${port + 1}`);
|
|
187
|
-
} else {
|
|
188
|
-
log(`Failed to start server: ${msg}`);
|
|
189
244
|
}
|
|
190
|
-
process.exit(1);
|
|
191
245
|
}
|
|
192
246
|
console.log("");
|
|
193
247
|
console.log(`\x1B[36m\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\x1B[0m`);
|
|
@@ -199,6 +253,9 @@ var localIP = getLocalIP();
|
|
|
199
253
|
if (localIP) {
|
|
200
254
|
console.log(`\x1B[33m\uD83D\uDD17 Network:\x1B[0m \x1B[1mhttp://${localIP}:${server.port}\x1B[0m`);
|
|
201
255
|
}
|
|
256
|
+
if (finalPort !== port) {
|
|
257
|
+
console.log(`\x1B[33m\u26A0\uFE0F Note:\x1B[0m Port ${port} was in use, using port ${finalPort} instead`);
|
|
258
|
+
}
|
|
202
259
|
console.log(`\x1B[33m\uD83D\uDC40 Watching for changes...\x1B[0m`);
|
|
203
260
|
console.log(`\x1B[36m\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\x1B[0m`);
|
|
204
261
|
console.log("");
|