@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.
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Document</title>
8
+ </head>
9
+
10
+ <body>
11
+ hello edwin
12
+ </body>
13
+
14
+ </html>
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 = "0.1.4";
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
- try {
83
- server = serve({
84
- port,
85
- hostname,
86
- async fetch(req) {
87
- const url = new URL(req.url);
88
- const reqPath = formatPath(url);
89
- if (url.pathname === "/__reload") {
90
- let controllerRef = null;
91
- const stream = new ReadableStream({
92
- start(controller) {
93
- controllerRef = controller;
94
- clients.add(controller);
95
- controller.enqueue(encoder.encode(`retry: 1000
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
- log(`SSE connected (clients=${clients.size})`);
99
- },
100
- cancel() {
101
- if (controllerRef)
102
- clients.delete(controllerRef);
103
- log(`SSE disconnected (clients=${clients.size})`);
104
- }
105
- });
106
- const res2 = new Response(stream, {
107
- headers: {
108
- "Content-Type": "text/event-stream; charset=utf-8",
109
- "Cache-Control": "no-cache, no-transform",
110
- Connection: "keep-alive"
111
- }
112
- });
113
- log(`${req.method} ${reqPath} -> ${res2.status}`);
114
- return res2;
115
- }
116
- let pathname;
117
- try {
118
- pathname = decodeURIComponent(url.pathname);
119
- } catch {
120
- const res2 = new Response("Bad Request", { status: 400 });
121
- log(`${req.method} ${reqPath} -> ${res2.status}`);
122
- return res2;
123
- }
124
- if (pathname === "/index.html") {
125
- const res2 = Response.redirect(url.origin + "/" + url.search, 301);
126
- log(`${req.method} ${reqPath} -> ${res2.status} (redirect to /)`);
127
- return res2;
128
- }
129
- if (pathname === "/")
130
- pathname = "/index.html";
131
- const rootPrefix = rootDir.endsWith(sep) ? rootDir : rootDir + sep;
132
- function isInsideRoot(p) {
133
- return p === rootDir || p.startsWith(rootPrefix);
134
- }
135
- let resolvedPath = resolve(rootDir, `.${pathname}`);
136
- if (!isInsideRoot(resolvedPath)) {
137
- const res2 = new Response("Forbidden", { status: 403 });
138
- log(`${req.method} ${reqPath} -> ${res2.status}`);
139
- return res2;
140
- }
141
- let file = Bun.file(resolvedPath);
142
- if (!await file.exists()) {
143
- const hasExtension = pathname.includes(".") && !pathname.endsWith("/");
144
- if (!hasExtension) {
145
- const htmlPath = resolve(rootDir, `.${pathname}.html`);
146
- if (isInsideRoot(htmlPath)) {
147
- const htmlFile = Bun.file(htmlPath);
148
- if (await htmlFile.exists()) {
149
- resolvedPath = htmlPath;
150
- file = htmlFile;
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
- if (!await file.exists()) {
156
- const res2 = new Response("Not Found", { status: 404 });
157
- log(`${req.method} ${reqPath} -> ${res2.status}`);
158
- return res2;
159
- }
160
- if (file.type.startsWith("text/html")) {
161
- const t = await file.text();
162
- const injected = `<script>
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
- const body = t.includes("</body>") ? t.replace("</body>", `${injected}</body>`) : `${t}
207
+ const body = t.includes("</body>") ? t.replace("</body>", `${injected}</body>`) : `${t}
167
208
  ${injected}
168
209
  `;
169
- const res2 = new Response(body, {
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
- "Content-Type": file.type || "text/html; charset=utf-8"
221
+ "Access-Control-Allow-Origin": "*"
172
222
  }
173
223
  });
174
- log(`${req.method} ${reqPath} -> ${res2.status} (${resolvedPath})`);
175
- return res2;
224
+ log(`${req.method} ${reqPath} -> ${res.status} (${resolvedPath})`);
225
+ return res;
176
226
  }
177
- const res = new Response(file);
178
- log(`${req.method} ${reqPath} -> ${res.status} (${resolvedPath})`);
179
- return res;
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("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edwinencomienda/live-reloader",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "A fast, simple live-reload development server with file watching",
5
5
  "author": "Edwin Encomienda",
6
6
  "license": "MIT",