httpath 1.0.0 → 1.1.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.cjs CHANGED
@@ -1,532 +1,71 @@
1
1
  #!/usr/bin/env node
2
- let e=require(`node:util`),t=require(`node:path`),n=require(`node:http`),r=require(`node:fs/promises`),i=require(`node:fs`),a=require(`node:child_process`),o=require(`node:events`);const s=e=>({success:!0,data:e}),c=e=>({success:!1,error:e}),l=e=>e.success===!0,u=(e,t)=>{try{return s(e())}catch(e){return c(t?t(e):e)}},d=async(e,t)=>{try{return s(await e())}catch(e){return c(t?t(e):e)}};var f=class extends Error{constructor(e,t,n,r){super(e),this.code=t,this.statusCode=n,this.path=r,this.name=`HTTPathError`}},p=class extends f{constructor(e,t){super(e,`FS_ERROR`,500,t),this.name=`FileSystemError`}},m=class extends f{constructor(e,t){super(e,`SECURITY_ERROR`,403,t),this.name=`SecurityError`}},h=class extends f{constructor(e){super(e,`CONFIG_ERROR`,500),this.name=`ConfigurationError`}},g=class extends f{constructor(e){super(e,`NETWORK_ERROR`,500),this.name=`NetworkError`}};const _=e=>(t,...n)=>t instanceof e?t:new e(t instanceof Error?t.message:String(t),...n),v=_(p),y=_(m),ee=_(h),b=_(g);globalThis.success=s,globalThis.failure=c,globalThis.isSuccess=l;const x={port:8080,rootPath:process.cwd(),reload:!1,ignorePatterns:[`node_modules`,`.git`,`.DS_Store`],enableDirectoryListing:!0,restartOnChange:!1,logLevel:`info`,debounceMs:500},te={port:{type:`string`,short:`p`,default:x.port.toString()},path:{type:`string`,short:`d`,default:x.rootPath},reload:{type:`boolean`,short:`r`,default:x.reload},ignore:{type:`string`,short:`i`,default:(x.ignorePatterns||[]).join(`,`)},"no-listing":{type:`boolean`,default:!x.enableDirectoryListing},"restart-on-change":{type:`boolean`,default:x.restartOnChange},log:{type:`string`,default:x.logLevel},help:{type:`boolean`,short:`h`,default:!1},version:{type:`boolean`,short:`v`,default:!1}},S=`
3
- HTTPath - A minimalist Node.js file server with hot-reload capabilities
2
+ let e=require(`node:util`),t=require(`node:path`),n=require(`node:http`),r=require(`node:fs`),i=require(`node:fs/promises`),a=require(`node:url`),o=require(`node:child_process`);const s=(0,t.dirname)((0,a.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),c={html:`text/html`,css:`text/css`,js:`text/javascript`,mjs:`text/javascript`,json:`application/json`,png:`image/png`,jpg:`image/jpeg`,jpeg:`image/jpeg`,gif:`image/gif`,svg:`image/svg+xml`,ico:`image/x-icon`,txt:`text/plain`,md:`text/markdown`,ts:`video/mp2t`},l={directory:s,port:8080,ignorePatterns:[`.git`,`node_modules`,`.DS_Store`],enableDirectoryListing:!0,logLevel:`info`,enableLiveReload:!0,restartOnChange:!1};let u=null,d=!1;const f=new Set,p=n=>{let{values:r}=(0,e.parseArgs)({args:n,options:{dir:{type:`string`,short:`d`,default:l.directory},port:{type:`string`,short:`p`,default:l.port.toString()},ignore:{type:`string`,short:`i`,default:l.ignorePatterns.join(`,`)},"no-listing":{type:`boolean`,default:!1},"no-live-reload":{type:`boolean`,default:!1},"restart-on-change":{type:`boolean`,default:!1},log:{type:`string`,default:l.logLevel},help:{type:`boolean`,short:`h`,default:!1}},allowPositionals:!0});r.help&&(console.log(`
3
+ Static File Server with Auto-Reload (Node.js)
4
4
 
5
- Usage: httpath [options]
5
+ Usage: httpreload [OPTIONS]
6
6
 
7
7
  Options:
8
- -p, --port <number> Port number to listen on (default: 8080)
9
- -d, --path <directory> Directory to serve files from (default: current directory)
10
- -r, --reload Enable hot-reload functionality (default: false)
11
- -i, --ignore <patterns> Comma-separated patterns to ignore (default: node_modules,.git,.DS_Store)
12
- --no-listing Disable directory listing (default: false)
13
- --restart-on-change Restart server process on file changes (default: false)
14
- --log <level> Log level: debug, info, warn, error (default: info)
15
- -h, --help Show this help message
16
- -v, --version Show version number
17
-
18
- Examples:
19
- httpath # Start server on port 8080 in current directory
20
- httpath --port 3000 # Start server on port 3000
21
- httpath --path ./public # Serve files from ./public directory
22
- httpath --reload # Enable hot-reload for development
23
- httpath -p 3000 -d ./dist -r # Combined options
24
-
25
- Documentation: https://github.com/MetalbolicX/httpath
26
- `,C={name:`HTTPath`,version:`0.1.0`,description:`A minimalist Node.js file server with hot-reload capabilities`,author:`José Martínez Santana`,license:`MIT`},w=(n=process.argv.slice(2))=>{let r=u(()=>{let{values:r}=(0,e.parseArgs)({args:n,options:te,allowPositionals:!0});r.help&&(console.log(S),process.exit(0)),r.version&&(console.log(`${C.name} v${C.version}\n${C.description}`),process.exit(0));let i=parseInt(r.port,10);if(isNaN(i)||i<1||i>65535)throw Error(`Invalid port number: ${r.port}. Port must be between 1 and 65535.`);let a=(0,t.resolve)(r.path),o=r.ignore?r.ignore.split(`,`).map(e=>e.trim()).filter(Boolean):x.ignorePatterns;return{port:i,rootPath:a,reload:!!r.reload,ignorePatterns:o,enableDirectoryListing:!r[`no-listing`],restartOnChange:!!r[`restart-on-change`],logLevel:r.log||x.logLevel}},ee);return r.success||(console.error(`❌ Error parsing arguments: ${r.error.message}`),console.log(S),process.exit(1)),r},T=e=>!Number.isInteger(e.port)||e.port<1||e.port>65535?(console.error(`❌ Invalid port: ${e.port}. Port must be between 1 and 65535.`),!1):typeof e.rootPath!=`string`||e.rootPath.length===0?(console.error(`❌ Invalid root path: ${e.rootPath}`),!1):typeof e.reload==`boolean`?!0:(console.error(`❌ Invalid reload flag: ${e.reload}. Must be boolean.`),!1),E={allowDotFiles:!1,maxPathLength:1e3,blockedPatterns:`../,..\\,%2e%2e%2f,%2e%2e%5c,..%2f,..%5c,\0,%00,\r,
27
- , ,CON,PRN,AUX,NUL,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9`.split(`,`)},D=new Set([`windows`,`system32`,`program files`,`program files (x86)`,`users`,`documents and settings`,`boot`,`etc`,`bin`,`sbin`,`usr`,`var`,`proc`,`sys`,`dev`,`root`,`home`]),ne=(e,n,r={})=>{let i={...E,...r};if(!e||typeof e!=`string`)return failure(y(Error({isValid:!1,resolvedPath:``,error:`Invalid path: path must be a non-empty string`}.error)));if(!n||typeof n!=`string`)return failure(y(Error({isValid:!1,resolvedPath:``,error:`Invalid root path: root path must be a non-empty string`}.error)));if(e.length>i.maxPathLength){let e={isValid:!1,resolvedPath:``,error:`Path too long: exceeds ${i.maxPathLength} characters`};return failure(y(Error(e.error)))}let a=u(()=>decodeURIComponent(e),()=>y(Error(`Invalid URL encoding in path`)));if(!a.success)return failure(a.error);let o=a.data,s=o.toLowerCase();for(let e of i.blockedPatterns)if(s.includes(e.toLowerCase())){let t={isValid:!1,resolvedPath:``,error:`Blocked pattern detected: ${e}`};return failure(y(Error(t.error)))}let c=o.replace(/\\/g,`/`),l=u(()=>(0,t.resolve)(n,c.startsWith(`/`)?c.slice(1):c),y);if(!l.success)return failure(l.error);let d=l.data,f=(0,t.normalize)(n),p=(0,t.normalize)(d);if(!p.startsWith(f+t.sep)&&p!==f)return failure(y(Error({isValid:!1,resolvedPath:``,error:`Path traversal detected: path is outside root directory`}.error)));if(!i.allowDotFiles){let e=c.split(`/`);for(let t of e)if(t.startsWith(`.`)&&t!==`.`&&t!==`..`)return failure(y(Error({isValid:!1,resolvedPath:``,error:`Dot files not allowed`}.error)))}let m=p.toLowerCase().split(t.sep);for(let e of m)if(D.has(e)){let t={isValid:!1,resolvedPath:``,error:`Access to protected directory denied: ${e}`};return failure(y(Error(t.error)))}let h={isValid:!0,resolvedPath:p};return success(h)},re={".html":`text/html`,".htm":`text/html`,".css":`text/css`,".js":`text/javascript`,".mjs":`text/javascript`,".ts":`text/typescript`,".jsx":`text/jsx`,".tsx":`text/tsx`,".json":`application/json`,".xml":`application/xml`,".rss":`application/rss+xml`,".atom":`application/atom+xml`,".png":`image/png`,".jpg":`image/jpeg`,".jpeg":`image/jpeg`,".gif":`image/gif`,".svg":`image/svg+xml`,".ico":`image/x-icon`,".webp":`image/webp`,".bmp":`image/bmp`,".tiff":`image/tiff`,".tif":`image/tiff`,".woff":`font/woff`,".woff2":`font/woff2`,".ttf":`font/ttf`,".otf":`font/otf`,".eot":`application/vnd.ms-fontobject`,".pdf":`application/pdf`,".doc":`application/msword`,".docx":`application/vnd.openxmlformats-officedocument.wordprocessingml.document`,".xls":`application/vnd.ms-excel`,".xlsx":`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`,".ppt":`application/vnd.ms-powerpoint`,".pptx":`application/vnd.openxmlformats-officedocument.presentationml.presentation`,".txt":`text/plain`,".md":`text/markdown`,".csv":`text/csv`,".log":`text/plain`,".yaml":`text/yaml`,".yml":`text/yaml`,".toml":`text/plain`,".ini":`text/plain`,".conf":`text/plain`,".cfg":`text/plain`,".zip":`application/zip`,".rar":`application/vnd.rar`,".7z":`application/x-7z-compressed`,".tar":`application/x-tar`,".gz":`application/gzip`,".bz2":`application/x-bzip2`,".mp3":`audio/mpeg`,".wav":`audio/wav`,".ogg":`audio/ogg`,".m4a":`audio/mp4`,".aac":`audio/aac`,".flac":`audio/flac`,".mp4":`video/mp4`,".avi":`video/x-msvideo`,".mov":`video/quicktime`,".wmv":`video/x-ms-wmv`,".flv":`video/x-flv`,".webm":`video/webm`,".mkv":`video/x-matroska`,".exe":`application/octet-stream`,".msi":`application/x-msdownload`,".deb":`application/vnd.debian.binary-package`,".rpm":`application/x-rpm`,".dmg":`application/x-apple-diskimage`,".iso":`application/x-iso9660-image`,".map":`application/json`,".lock":`text/plain`,".gitignore":`text/plain`,".env":`text/plain`,".dockerfile":`text/plain`,".makefile":`text/plain`},ie=new Set([`text/html`,`text/css`,`text/javascript`,`text/plain`,`text/markdown`,`text/xml`,`application/json`,`application/xml`,`image/svg+xml`]),ae=(e,t={})=>{let{defaultType:n=`application/octet-stream`,customMappings:r={}}=t,i=e.startsWith(`.`)?e.toLowerCase():`.${e.toLowerCase()}`;return r[i]||re[i]||n},oe=e=>ie.has(e)||e.startsWith(`text/`),se=e=>{let t=e.lastIndexOf(`.`);return t===-1||t===0?``:e.slice(t).toLowerCase()},O=(e,t={})=>ae(se(e),t),k={showHidden:!1,sortBy:`name`,sortOrder:`asc`},ce={customCSS:``,customJS:``,favicon:`data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📁</text></svg>`,theme:`auto`},le=async(e,n={})=>{let i={...k,...n},a=await d(()=>(0,r.readdir)(e),v);if(!a.success)return c(a.error);let o=[];for(let n of a.data){if(!i.showHidden&&n.startsWith(`.`))continue;let a=(0,t.join)(e,n),s=await d(()=>(0,r.stat)(a),v);s.success?o=[...o,{name:n,isDir:s.data.isDirectory(),size:s.data.size,lastModified:s.data.mtime}]:console.warn(`Warning: Cannot access file ${n}:`,s.error)}return o.sort((e,t)=>{if(e.isDir&&!t.isDir)return-1;if(!e.isDir&&t.isDir)return 1;let n=0;switch(i.sortBy){case`size`:n=(e.size||0)-(t.size||0);break;case`date`:n=(e.lastModified?.getTime()||0)-(t.lastModified?.getTime()||0);break;default:n=e.name.localeCompare(t.name);break}return i.sortOrder===`desc`?-n:n}),s(o)},A=async(e,t,n={})=>{let r={...k,...n},i={...ce,...n},a=await le(e,r);return a.success?s(F({title:`Directory listing for ${t}`,path:t,files:a.data,parentPath:t===`/`?void 0:j(t),serverInfo:{name:`HTTPath`,version:`0.1.0`,uptime:process.uptime()}},i)):s(I(`Error reading directory`,a.error.message))},j=e=>{let t=e.split(`/`).filter(e=>e);return t.pop(),t.length>0?`/`+t.join(`/`):`/`},M=e=>{if(e===0)return`0 B`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=Math.floor(Math.log(e)/Math.log(1024));return`${(e/1024**n).toFixed(n===0?0:1)} ${t[n]}`},N=e=>e.toLocaleString(`en-US`,{year:`numeric`,month:`short`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`}),P=e=>{if(e.isDir)return`📁`;let n=(0,t.extname)(e.name).toLowerCase();return{".html":`🌐`,".htm":`🌐`,".css":`🎨`,".js":`⚡`,".mjs":`⚡`,".ts":`📘`,".jsx":`⚛️`,".tsx":`⚛️`,".json":`📋`,".xml":`📋`,".txt":`📄`,".md":`📝`,".pdf":`📕`,".doc":`📘`,".docx":`📘`,".xls":`📗`,".xlsx":`📗`,".png":`🖼️`,".jpg":`🖼️`,".jpeg":`🖼️`,".gif":`🖼️`,".svg":`🎨`,".mp3":`🎵`,".wav":`🎵`,".mp4":`🎬`,".avi":`🎬`,".zip":`📦`,".tar":`📦`,".gz":`📦`}[n]||`📄`},F=(e,n)=>{let{title:r,path:i,files:a,parentPath:o}=e,s=o?`<tr><td><a href="${o}" class="parent-link">📁 ..</a></td><td>-</td><td>-</td></tr>`:``,c=a.map(e=>{let n=(0,t.join)(i,e.name).replace(/\\/g,`/`),r=e.isDir?`${e.name}/`:e.name,a=P(e),o=e.isDir?`-`:M(e.size||0),s=e.lastModified?N(e.lastModified):`-`;return`
28
- <tr>
29
- <td><a href="${n}" class="${e.isDir?`directory`:`file`}">${a} ${r}</a></td>
30
- <td>${o}</td>
31
- <td>${s}</td>
32
- </tr>`}).join(``);return`
33
- <!DOCTYPE html>
34
- <html lang="en" class="${n.theme===`dark`?`theme-dark`:n.theme===`light`?`theme-light`:``}">
35
- <head>
36
- <meta charset="utf-8">
37
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
38
- <title>${r}</title>
39
- <link rel="icon" href="${n.favicon}">
40
- <style>
41
- :root {
42
- --bg-color: #ffffff;
43
- --text-color: #333333;
44
- --border-color: #e1e5e9;
45
- --hover-color: #f8f9fa;
46
- --link-color: #0066cc;
47
- --header-bg: #f8f9fa;
48
- }
49
-
50
- .theme-dark {
51
- --bg-color: #1a1a1a;
52
- --text-color: #e1e1e1;
53
- --border-color: #333333;
54
- --hover-color: #2a2a2a;
55
- --link-color: #4da6ff;
56
- --header-bg: #2a2a2a;
57
- }
58
-
59
- @media (prefers-color-scheme: dark) {
60
- :root {
61
- --bg-color: #1a1a1a;
62
- --text-color: #e1e1e1;
63
- --border-color: #333333;
64
- --hover-color: #2a2a2a;
65
- --link-color: #4da6ff;
66
- --header-bg: #2a2a2a;
67
- }
68
- }
69
-
70
- body {
71
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
72
- line-height: 1.6;
73
- margin: 0;
74
- padding: 20px;
75
- background-color: var(--bg-color);
76
- color: var(--text-color);
77
- }
78
-
79
- .container {
80
- max-width: 1200px;
81
- margin: 0 auto;
82
- }
83
-
84
- h1 {
85
- border-bottom: 2px solid var(--border-color);
86
- padding-bottom: 10px;
87
- margin-bottom: 20px;
88
- display: flex;
89
- align-items: center;
90
- gap: 10px;
91
- }
92
-
93
- .breadcrumb {
94
- font-size: 0.9em;
95
- color: var(--link-color);
96
- margin-bottom: 15px;
97
- }
98
-
99
- .breadcrumb a {
100
- color: var(--link-color);
101
- text-decoration: none;
102
- }
103
-
104
- .breadcrumb a:hover {
105
- text-decoration: underline;
106
- }
107
-
108
- table {
109
- width: 100%;
110
- border-collapse: collapse;
111
- margin-bottom: 20px;
112
- background: var(--bg-color);
113
- border-radius: 8px;
114
- overflow: hidden;
115
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
116
- }
117
-
118
- th {
119
- background-color: var(--header-bg);
120
- padding: 12px 15px;
121
- text-align: left;
122
- font-weight: 600;
123
- border-bottom: 1px solid var(--border-color);
124
- }
125
-
126
- td {
127
- padding: 10px 15px;
128
- border-bottom: 1px solid var(--border-color);
129
- }
130
-
131
- tr:hover {
132
- background-color: var(--hover-color);
133
- }
134
-
135
- tr:last-child td {
136
- border-bottom: none;
137
- }
138
-
139
- a {
140
- color: var(--link-color);
141
- text-decoration: none;
142
- display: flex;
143
- align-items: center;
144
- gap: 8px;
145
- }
146
-
147
- a:hover {
148
- text-decoration: underline;
149
- }
150
-
151
- .parent-link {
152
- font-weight: 600;
153
- opacity: 0.8;
154
- }
155
-
156
- .directory {
157
- font-weight: 500;
158
- }
159
-
160
- .footer {
161
- margin-top: 30px;
162
- padding-top: 20px;
163
- border-top: 1px solid var(--border-color);
164
- text-align: center;
165
- color: var(--text-color);
166
- opacity: 0.7;
167
- font-size: 0.9em;
168
- }
169
-
170
- .stats {
171
- display: flex;
172
- justify-content: space-between;
173
- margin-bottom: 20px;
174
- font-size: 0.9em;
175
- color: var(--text-color);
176
- opacity: 0.8;
177
- }
178
-
179
- @media (max-width: 768px) {
180
- body {
181
- padding: 10px;
182
- }
183
-
184
- table {
185
- font-size: 0.9em;
186
- }
187
-
188
- .stats {
189
- flex-direction: column;
190
- gap: 5px;
191
- }
192
- }
193
-
194
- ${n.customCSS}
195
- </style>
196
- </head>
197
- <body>
198
- <div class="container">
199
- <h1>📁 Directory listing for ${i}</h1>
200
-
201
- <div class="stats">
202
- <span>${a.length} items</span>
203
- <span>Served by HTTPath v${e.serverInfo?.version}</span>
204
- </div>
205
-
206
- <table>
207
- <thead>
208
- <tr>
209
- <th>Name</th>
210
- <th>Size</th>
211
- <th>Modified</th>
212
- </tr>
213
- </thead>
214
- <tbody>
215
- ${s}
216
- ${c}
217
- </tbody>
218
- </table>
219
-
220
- <div class="footer">
221
- <p>HTTPath - A minimalist Node.js file server</p>
222
- </div>
223
- </div>
224
-
225
- ${n.customJS?`<script>${n.customJS}<\/script>`:``}
226
- </body>
227
- </html>`},I=(e,t)=>`
228
- <!DOCTYPE html>
229
- <html lang="en">
230
- <head>
231
- <meta charset="utf-8">
232
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
233
- <title>Error - HTTPath</title>
234
- <style>
235
- body {
236
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
237
- display: flex;
238
- justify-content: center;
239
- align-items: center;
240
- min-height: 100vh;
241
- margin: 0;
242
- background-color: #f8f9fa;
243
- color: #333;
244
- }
245
- .error-container {
246
- text-align: center;
247
- padding: 40px;
248
- background: white;
249
- border-radius: 8px;
250
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
251
- }
252
- .error-icon {
253
- font-size: 4em;
254
- margin-bottom: 20px;
255
- }
256
- h1 {
257
- color: #dc3545;
258
- margin-bottom: 10px;
259
- }
260
- p {
261
- color: #666;
262
- line-height: 1.6;
263
- }
264
- </style>
265
- </head>
266
- <body>
267
- <div class="error-container">
268
- <div class="error-icon">❌</div>
269
- <h1>${e}</h1>
270
- <p>${t}</p>
271
- </div>
272
- </body>
273
- </html>`,L=async e=>s((await d(()=>(0,r.access)(e),v)).success),R=async e=>await d(()=>(0,r.stat)(e),v),z=async e=>await d(()=>(0,r.readFile)(e,`utf8`),v),B=e=>(0,i.createReadStream)(e),V=e=>oe(O(e))?`buffer`:`stream`,H={watchPath:process.cwd(),ignored:[`node_modules`,`.git`,`.vscode`,`.idea`,`dist`,`build`,`.next`,`.nuxt`,`coverage`,`.nyc_output`,`*.log`,`.DS_Store`,`Thumbs.db`],debounceMs:500,restartOnChange:!1},U=`
8
+ -d, --dir <directory> Directory to serve (default: current directory)
9
+ -p, --port <port> Port to listen on (default: 8080)
10
+ -i, --ignore <patterns> Comma-separated patterns to ignore
11
+ --no-listing Disable directory listing
12
+ --no-live-reload Disable live reload feature
13
+ --restart-on-change Restart server process on file changes
14
+ --log <level> Log level: info, debug, error
15
+ -h, --help Show this help message
16
+ `),process.exit(0));let i=parseInt(r.port||`8080`,10);if(isNaN(i)||i<1||i>65535)throw Error(`Port must be a valid number between 1 and 65535`);return{directory:(0,t.resolve)(r.dir||`.`),port:i,ignorePatterns:(r.ignore||``).split(`,`).map(e=>e.trim()).filter(Boolean),enableDirectoryListing:!r[`no-listing`],logLevel:r.log||l.logLevel,enableLiveReload:!r[`no-live-reload`],restartOnChange:!!r[`restart-on-change`]}},m=(e,t=`info`)=>{let n=new Date().toISOString(),r=t.toUpperCase().padEnd(5);console.log(`[${n}] ${r} ${e}`)},h=(e,t)=>t.some(t=>e.includes(t)||e.endsWith(t)),g=e=>new Promise(t=>{u&&clearTimeout(u),u=setTimeout(()=>{u=null,t()},e)}),_=e=>c[(0,t.extname)(e).slice(1).toLowerCase()]||`application/octet-stream`,v=e=>`
274
17
  <script>
275
- (function() {
276
- 'use strict';
277
-
278
- let eventSource;
279
- let reconnectAttempts = 0;
280
- const maxReconnectAttempts = 10;
281
- const baseReconnectDelay = 1000;
282
-
283
- function connect() {
284
- console.log('[HTTPath] Connecting to hot-reload server...');
18
+ (() => {
19
+ const sseUrl = '/livereload';
20
+ let source;
285
21
 
286
- eventSource = new EventSource('/__reload__');
22
+ const connect = () => {
23
+ console.log('[Live Reload] Connecting...');
24
+ source = new EventSource(sseUrl);
287
25
 
288
- eventSource.onopen = function() {
289
- console.log('[HTTPath] Hot-reload connected');
290
- reconnectAttempts = 0;
291
-
292
- // Show connection indicator
293
- showConnectionStatus('connected');
26
+ source.onopen = () => {
27
+ console.log('[Live Reload] Connected');
294
28
  };
295
29
 
296
- eventSource.onmessage = function(event) {
297
- const data = event.data;
298
- console.log('[HTTPath] Received reload signal:', data);
299
-
300
- if (data === 'reload') {
301
- console.log('[HTTPath] Reloading page...');
302
- showReloadNotification();
303
-
304
- // Small delay to show notification
305
- setTimeout(() => {
306
- window.location.reload();
307
- }, 200);
30
+ source.onmessage = (event) => {
31
+ if (event.data === 'reload') {
32
+ console.log('[Live Reload] Reloading page...');
33
+ window.location.reload();
308
34
  }
309
35
  };
310
36
 
311
- eventSource.onerror = function() {
312
- console.warn('[HTTPath] Hot-reload connection lost');
313
- eventSource.close();
314
-
315
- showConnectionStatus('disconnected');
316
-
317
- if (reconnectAttempts < maxReconnectAttempts) {
318
- reconnectAttempts++;
319
- const delay = baseReconnectDelay * Math.pow(1.5, reconnectAttempts - 1);
320
-
321
- console.log(\`[HTTPath] Reconnecting in \${delay}ms... (attempt \${reconnectAttempts})\`);
322
-
323
- setTimeout(connect, delay);
324
- } else {
325
- console.error('[HTTPath] Max reconnection attempts reached. Please refresh the page.');
326
- showConnectionStatus('failed');
327
- }
37
+ source.onerror = () => {
38
+ console.log('[Live Reload] Connection error, reconnecting...');
39
+ source.close();
40
+ setTimeout(connect, 1000);
328
41
  };
329
- }
42
+ };
330
43
 
331
- function showConnectionStatus(status) {
332
- const indicator = getOrCreateIndicator();
333
-
334
- switch (status) {
335
- case 'connected':
336
- indicator.style.background = '#28a745';
337
- indicator.title = 'Hot-reload connected';
338
- indicator.textContent = '🔄';
339
- break;
340
- case 'disconnected':
341
- indicator.style.background = '#ffc107';
342
- indicator.title = 'Hot-reload disconnected - attempting to reconnect...';
343
- indicator.textContent = '⏳';
344
- break;
345
- case 'failed':
346
- indicator.style.background = '#dc3545';
347
- indicator.title = 'Hot-reload failed - refresh page to reconnect';
348
- indicator.textContent = '❌';
349
- break;
350
- }
351
- }
352
-
353
- function showReloadNotification() {
354
- const notification = document.createElement('div');
355
- notification.style.cssText = \`
356
- position: fixed;
357
- top: 20px;
358
- right: 20px;
359
- background: #007bff;
360
- color: white;
361
- padding: 12px 20px;
362
- border-radius: 6px;
363
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
364
- font-size: 14px;
365
- font-weight: 500;
366
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
367
- z-index: 10000;
368
- animation: slideIn 0.3s ease-out;
369
- \`;
370
-
371
- notification.textContent = '🔄 Reloading...';
372
-
373
- const style = document.createElement('style');
374
- style.textContent = \`
375
- @keyframes slideIn {
376
- from { transform: translateX(100%); opacity: 0; }
377
- to { transform: translateX(0); opacity: 1; }
378
- }
379
- \`;
380
-
381
- document.head.appendChild(style);
382
- document.body.appendChild(notification);
383
- }
384
-
385
- function getOrCreateIndicator() {
386
- let indicator = document.getElementById('httpath-reload-indicator');
387
-
388
- if (!indicator) {
389
- indicator = document.createElement('div');
390
- indicator.id = 'httpath-reload-indicator';
391
- indicator.style.cssText = \`
392
- position: fixed;
393
- bottom: 20px;
394
- right: 20px;
395
- width: 40px;
396
- height: 40px;
397
- border-radius: 50%;
398
- display: flex;
399
- align-items: center;
400
- justify-content: center;
401
- font-size: 16px;
402
- color: white;
403
- font-weight: bold;
404
- cursor: pointer;
405
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
406
- z-index: 9999;
407
- transition: all 0.3s ease;
408
- user-select: none;
409
- \`;
410
-
411
- indicator.addEventListener('click', () => {
412
- if (eventSource && eventSource.readyState === EventSource.OPEN) {
413
- window.location.reload();
414
- } else {
415
- connect();
416
- }
417
- });
418
-
419
- document.body.appendChild(indicator);
420
- }
421
-
422
- return indicator;
423
- }
424
-
425
- // Start connection when DOM is ready
426
- if (document.readyState === 'loading') {
427
- document.addEventListener('DOMContentLoaded', connect);
428
- } else {
429
- connect();
430
- }
431
-
432
- // Cleanup on page unload
433
- window.addEventListener('beforeunload', () => {
434
- if (eventSource) {
435
- eventSource.close();
436
- }
437
- });
44
+ connect();
438
45
  })();
439
- <\/script>`;var W=class extends o.EventEmitter{constructor(e={}){super(),this.watcher=null,this.clients=new Map,this.debounceTimer=null,this.options={...H,...e}}start(){this.watcher&&this.stop();let e=u(()=>{this.watcher=(0,i.watch)(this.options.watchPath,{recursive:!0},(e,t)=>{t&&this.handleFileChange(e,t)}),console.log(`🔄 Hot-reload watching: ${this.options.watchPath}`),process.on(`SIGINT`,()=>this.stop()),process.on(`SIGTERM`,()=>this.stop())},v);return e.success?s(void 0):(console.warn(`File watching not supported on this system:`,e.error),c(e.error))}stop(){this.watcher&&=(this.watcher.close(),null);for(let e of this.clients.values())this.removeClient(e.id);this.debounceTimer&&=(clearTimeout(this.debounceTimer),null),console.log(`🔄 Hot-reload stopped`)}handleSSEConnection(e,t){let n=this.generateClientId();t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`,"Access-Control-Allow-Origin":`*`,"Access-Control-Allow-Headers":`Cache-Control`,"Access-Control-Allow-Methods":`GET, OPTIONS`}),t.write(`data: connected
440
-
441
- `);let r={response:t,id:n,connectedAt:new Date};this.clients.set(n,r),e.on(`close`,()=>{this.removeClient(n)}),e.on(`aborted`,()=>{this.removeClient(n)}),t.on(`error`,e=>{console.warn(`SSE client error for ${n}:`,e.message),this.removeClient(n)}),console.log(`🔗 Hot-reload client connected: ${n} (${this.clients.size} total)`),this.emit(`client-connected`,n)}broadcastReload(e){if(this.clients.size===0)return;let t=[];for(let[e,n]of this.clients)try{n.response.write(`data: reload
442
-
443
- `)}catch(n){console.warn(`Failed to send reload signal to client ${e}:`,n),t=[...t,e]}for(let e of t)this.removeClient(e);console.log(`📡 Reload signal sent to ${this.clients.size} clients`),e&&this.emit(`reload-triggered`,e)}injectScript(e){return e.includes(`</body>`)?e.replace(`</body>`,`${U}</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${U}</html>`):e+U}getClientCount(){return this.clients.size}getClientInfo(){return Array.from(this.clients.values()).map(e=>({id:e.id,connectedAt:e.connectedAt}))}handleFileChange(e,t){this.shouldIgnoreFile(t)||(this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{console.log(`📝 File changed: ${t}`);let n={type:this.getEventType(e),path:t,timestamp:new Date},r=this.shouldRestartServer([t]),i=this.shouldTriggerBrowserReload([t]);if(this.options.restartOnChange||r){console.log(`🔁 Restart requested due to change: ${t}`);try{this.restartProcess()}catch(e){console.warn(`Failed to restart process:`,e)}return}i&&this.broadcastReload(n),this.emit(`file-changed`,n)},this.options.debounceMs))}shouldIgnoreFile(e){return this.options.ignored.some(t=>t.includes(`*`)?new RegExp(t.replace(/\*/g,`.*`),`i`).test(e):e.includes(t))}getEventType(e){switch(e){case`change`:return`file-changed`;case`rename`:return`file-added`;default:return`file-changed`}}shouldRestartServer(e){let t=[/\.ts$/i,/\.js$/i,/\.mjs$/i,/\.json$/i,/\.toml$/i,/\.ya?ml$/i,/deno\.json/i,/deno\.lock/i,/package\.json/i];return e.some(e=>t.some(t=>t.test(e)))}shouldTriggerBrowserReload(e){let t=[/\.html?$/i,/\.css$/i,/\.s[ac]ss$/i,/\.less$/i,/\.js$/i,/\.jsx$/i,/\.ts$/i,/\.tsx$/i,/\.vue$/i,/\.svelte$/i,/\.md$/i,/\.(png|jpe?g|gif|svg|webp|ico)$/i,/\.(woff2?|ttf|eot)$/i,/\.json$/i];return e.some(e=>t.some(t=>t.test(e)))}restartProcess(){let e=process.argv.slice(1);(0,a.spawn)(process.execPath,e,{detached:!0,stdio:`inherit`}).unref(),console.log(`🔁 Spawned replacement process, exiting current process...`),process.exit(0)}generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}removeClient(e){let t=this.clients.get(e);t&&(u(()=>t.response.end(),()=>Error(`Error closing connection`)),this.clients.delete(e),console.log(`🔗 Hot-reload client disconnected: ${e} (${this.clients.size} remaining)`),this.emit(`client-disconnected`,e))}};const ue=e=>new W(e),de={level:`info`,format:`simple`,includeTimestamp:!0,colorize:!0},G={reset:`\x1B[0m`,bright:`\x1B[1m`,dim:`\x1B[2m`,red:`\x1B[31m`,green:`\x1B[32m`,yellow:`\x1B[33m`,blue:`\x1B[34m`,magenta:`\x1B[35m`,cyan:`\x1B[36m`,white:`\x1B[37m`,gray:`\x1B[90m`},K={debug:0,info:1,warn:2,error:3},fe={debug:G.gray,info:G.blue,warn:G.yellow,error:G.red};var pe=class{constructor(e={}){this.options={...de,...e},this.startTime=Date.now()}debug(e,...t){this.log(`debug`,e,...t)}info(e,...t){this.log(`info`,e,...t)}warn(e,...t){this.log(`warn`,e,...t)}error(e,...t){this.log(`error`,e,...t)}logRequest(e,t,n){let r=this.formatTimestamp(),i=`${this.colorize(e,G.cyan)} ${this.colorize(t,G.white)}`;this.options.format===`detailed`&&n&&(i+=this.colorize(` - ${n}`,G.gray)),this.writeLog(`info`,i,r)}logResponse(e,t){let n=this.formatTimestamp(),r=this.getStatusColor(e),i=`Response: ${this.colorize(e.toString(),r)}`;if(t!==void 0){let e=t>1e3?G.yellow:t>500?G.cyan:G.green,n=this.colorize(`${t}ms`,e);i+=` in ${n}`}this.writeLog(`info`,i,n)}createLogEntry(e,t,n){return{level:e,message:t,timestamp:new Date,...n}}setLevel(e){this.options.level=e}getLevel(){return this.options.level}shouldLog(e){return K[e]>=K[this.options.level]}getUptime(){return Date.now()-this.startTime}formatUptime(){let e=this.getUptime(),t=Math.floor(e/1e3)%60,n=Math.floor(e/(1e3*60))%60,r=Math.floor(e/(1e3*60*60));return r>0?`${r}h ${n}m ${t}s`:n>0?`${n}m ${t}s`:`${t}s`}log(e,t,...n){if(!this.shouldLog(e))return;let r=this.formatTimestamp(),i=this.formatMessage(t,n);this.writeLog(e,i,r)}writeLog(e,t,n){let r=``;if(this.options.includeTimestamp&&n&&(r+=this.colorize(`[${n}] `,G.gray)),this.options.format!==`simple`){let t=e.toUpperCase().padEnd(5);r+=this.colorize(`${t} `,fe[e])}r+=t,(e===`error`?process.stderr:process.stdout).write(r+`
444
- `)}formatMessage(e,t){if(t.length===0)return e;let n=e;for(let e of t)typeof e==`object`?n+=` `+JSON.stringify(e,null,2):n+=` `+String(e);return n}formatTimestamp(){let e=new Date;return this.options.format===`json`?e.toISOString():e.toLocaleTimeString(`en-US`,{hour12:!1})}colorize(e,t){return!this.options.colorize||!process.stdout.isTTY?e:t+e+G.reset}getStatusColor(e){return e>=200&&e<300?G.green:e>=300&&e<400?G.cyan:e>=400&&e<500?G.yellow:e>=500?G.red:G.white}};const q=e=>new pe(e);q();const me={startPort:8080,endPort:8180,timeout:2e3},he=(e,t=2e3)=>new Promise(r=>{let i=(0,n.createServer)(),a=!1,o=setTimeout(()=>{a||(a=!0,i.close(),r(s(!1)))},t);i.once(`error`,e=>{a||(a=!0,clearTimeout(o),i.close(),e.code===`EADDRINUSE`?r(s(!1)):r(c(b(e))))}),i.once(`listening`,()=>{a||(a=!0,clearTimeout(o),i.close(()=>{r(s(!0))}))}),i.listen(e)}),J=async(e={})=>{let t={...me,...e};if(t.startPort<1||t.startPort>65535)return c(b(Error(`Invalid start port: ${t.startPort}. Must be between 1 and 65535.`)));if(t.endPort<t.startPort||t.endPort>65535)return c(b(Error(`Invalid end port: ${t.endPort}. Must be between ${t.startPort} and 65535.`)));for(let e=t.startPort;e<=t.endPort;e++){let n=await he(e,t.timeout);if(l(n)&&n.data)return s(e)}return c(b(Error(`No available ports found in range ${t.startPort}-${t.endPort}`)))};var ge=class{constructor(e){this.server=null,this.hotReload=null,this.logger=q(),this.isRunning=!1,this.config=e,e.reload&&(this.hotReload=new W({watchPath:e.rootPath,ignored:e.ignorePatterns||void 0,debounceMs:e.debounceMs||void 0,restartOnChange:e.restartOnChange||!1}))}async start(){if(this.isRunning)throw Error(`Server is already running`);let e=await J({startPort:this.config.port});if(!l(e))throw Error(`Failed to find available port: ${e.error.message}`);let t=e.data;if(this.server=(0,n.createServer)((e,t)=>{this.handleRequest(e,t)}),this.hotReload){let e=this.hotReload.start();l(e)?this.logger.info(`🔄 Hot-reload enabled`):this.logger.warn(`Hot-reload failed to start:`,e.error)}return new Promise((e,n)=>{this.server.listen(t,()=>{this.isRunning=!0,this.logger.info(`🚀 Server running at http://localhost:${t}`),this.logger.info(`📁 Serving files from: ${this.config.rootPath}`),this.logger.info(`
445
- Press Ctrl+C to stop the server
446
- `),e({port:t,server:this.server,config:this.config,stop:()=>this.stop()})}),this.server.on(`error`,e=>{e.code===`EADDRINUSE`?n(Error(`Port ${t} is already in use`)):n(e)})})}async stop(){if(!(!this.isRunning||!this.server))return this.logger.info(`
447
-
448
- 👋 Shutting down gracefully...`),this.hotReload&&this.hotReload.stop(),new Promise(e=>{this.server.close(()=>{this.isRunning=!1,this.logger.info(`✅ Server stopped`),e()})})}async handleRequest(e,t){let n=Date.now(),r=e.url||`/`,i=e.method||`GET`,a=500;try{if(this.logger.logRequest(i,r),r===`/__reload__`&&this.hotReload){this.hotReload.handleSSEConnection(e,t),a=200;return}if(i!==`GET`){this.sendError(t,405,`Method Not Allowed`),a=405;return}let n=ne(r,this.config.rootPath);if(!l(n)){this.logger.warn(`Security violation: ${n.error.message} - ${r}`),this.sendError(t,403,`Forbidden - Access denied`),a=403;return}let o=n.data;if(!o.isValid){this.logger.warn(`Security violation: ${o.error} - ${r}`),this.sendError(t,403,`Forbidden - Access denied`),a=403;return}let s=o.resolvedPath,c=await L(s);if(!l(c)||!c.data){this.sendError(t,404,`Not Found`),a=404;return}let u=await R(s);if(!l(u)){this.sendError(t,500,`Internal Server Error`),a=500;return}u.data.isDirectory()?await this.handleDirectoryRequest(s,r,t):await this.handleFileRequest(s,t),a=200}catch(e){let n=e;n instanceof Error?this.logger.error(`Unhandled exception during request handling:`,n.message,n.stack):this.logger.error(`Unhandled exception during request handling:`,n),t.headersSent||this.sendError(t,500,`Internal Server Error`),a=500}finally{let e=Date.now()-n;this.logger.logResponse(a,e)}}async handleDirectoryRequest(e,n,r){let i=(0,t.join)(e,`index.html`),a=await L(i);if(l(a)&&a.data){await this.handleFileRequest(i,r);return}if(this.config&&this.config.enableDirectoryListing===!1){this.sendError(r,403,`Directory listing disabled`);return}let o=await A(e,n);if(!l(o)){this.sendError(r,500,`Error generating directory listing`);return}let s=o.data;this.hotReload&&(s=this.hotReload.injectScript(s)),r.writeHead(200,{"Content-Type":`text/html; charset=utf-8`,"Content-Length":Buffer.byteLength(s,`utf8`).toString()}),r.end(s)}async handleFileRequest(e,n){let r=await R(e);if(!l(r)){this.sendError(n,500,`Internal Server Error`);return}let i=r.data,a=(0,t.extname)(e).toLowerCase(),o=O(e),s=V(e),c={"Content-Type":o,"Content-Length":i.size.toString(),"Last-Modified":i.mtime.toUTCString(),"Cache-Control":`public, max-age=0`};if(s===`buffer`&&(a===`.html`||a===`.htm`)&&this.hotReload){let t=await z(e);if(l(t)){let e=this.hotReload.injectScript(t.data);c[`Content-Length`]=Buffer.byteLength(e,`utf8`).toString(),n.writeHead(200,c),n.end(e);return}else this.logger.warn(`Failed to read HTML file for script injection, falling back to streaming`)}n.writeHead(200,c);let u=B(e);u.pipe(n),u.on(`error`,e=>{this.logger.error(`File stream error:`,e),n.headersSent||this.sendError(n,500,`Internal Server Error`)}),n.on(`error`,e=>{this.logger.error(`Response error:`,e)})}sendError(e,t,n){if(e.headersSent)return;let r=this.generateErrorPage(t,n);e.writeHead(t,{"Content-Type":`text/html; charset=utf-8`,"Content-Length":Buffer.byteLength(r,`utf8`).toString()}),e.end(r)}generateErrorPage(e,t){return`
46
+ <\/script>`,y=(e,t)=>{let n=v(t);return e.includes(`</body>`)?e.replace(`</body>`,`${n}\n</body>`):e.includes(`</html>`)?e.replace(`</html>`,`${n}\n</html>`):e+n},b=async(e,t)=>{t.writeHead(200,{"Content-Type":`text/event-stream`,"Cache-Control":`no-cache`,Connection:`keep-alive`}),t.write(`data: connected
47
+
48
+ `),f.add(t),e.on(`close`,()=>{f.delete(t)})},x=async(e=`change`)=>{if(f.size!==0){m(`Reloading ${f.size} clients (${e})`,`debug`);for(let e of f)try{e.write(`data: reload
49
+
50
+ `)}catch{}}},S=()=>`
51
+ :root { --bg-page: #f2f2f2; --bg-article: #bbc3db; --color-title: #333; --color-paragraph: #333; --link-color: #1a0dab; --link-hover-color: #d93025; --toggle-color: #0f172b; --fill-icons: white; }
52
+ :root:has(#dark:checked) { --bg-page: #333; --bg-article: #444; --color-title: #eee; --color-paragraph: #ddd; --link-color: #bb86fc; --link-hover-color: #ff79c6; }
53
+ body { font-family: monospace; font-size: 1.3em; margin: 0.5em; padding: 1em; background-color: var(--bg-page); color: var(--color-paragraph); &:has(#dark:checked) { background-color: var(--bg-article); color: var(--color-title); } }
54
+ h1 { font-size: 2em; margin-bottom: 0.5em; }
55
+ a { text-decoration: none; color: var(--link-color); &:hover { text-decoration: underline; color: var(--link-hover-color); } }
56
+ .toggle { --width: 3em; --height: calc(var(--width) / 2); --border-radius: calc(var(--height) / 2); display: inline-block; cursor: pointer; .toggle__input { display: none; &:checked + .toggle__fill { background: #009578; } &:checked + .toggle__fill::after { transform: translateX(var(--height)); } } .toggle__fill { position: relative; width: var(--width); height: var(--height); border-radius: var(--border-radius); background-color: var(--toggle-color); transition: background-color 0.3s ease-in-out; &::after { content: ""; position: absolute; top: 0; left: 0; width: var(--height); height: var(--height); border-radius: var(--border-radius); background-color: var(--fill-icons); box-shadow: 0 0 0.2em rgba(0, 0, 0, 0.2); transition: transform 0.3s ease-in-out; } } }
57
+ `,C=(e,t)=>{let n=t===`/`?``:`<a href="../">../</a><br>`,r=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?0:e.isDirectory?-1:1).sort((e,t)=>e.isDirectory===t.isDirectory?e.name.localeCompare(t.name):0).map(e=>{let t=e.isDirectory?`📁`:`📄`;return`<a href="${e.url}">${t} ${e.name}</a>`}).join(`<br>`);return`
449
58
  <!DOCTYPE html>
450
- <html lang="en">
59
+ <html>
451
60
  <head>
452
- <meta charset="utf-8">
453
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
454
- <title>${e} ${t} - HTTPath</title>
455
- <style>
456
- body {
457
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
458
- display: flex;
459
- justify-content: center;
460
- align-items: center;
461
- min-height: 100vh;
462
- margin: 0;
463
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
464
- color: white;
465
- }
466
- .error-container {
467
- text-align: center;
468
- padding: 60px 40px;
469
- background: rgba(255, 255, 255, 0.1);
470
- backdrop-filter: blur(10px);
471
- border-radius: 16px;
472
- border: 1px solid rgba(255, 255, 255, 0.2);
473
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
474
- max-width: 500px;
475
- }
476
- .error-code {
477
- font-size: 6rem;
478
- font-weight: 700;
479
- margin-bottom: 20px;
480
- text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
481
- }
482
- .error-title {
483
- font-size: 2rem;
484
- margin-bottom: 16px;
485
- font-weight: 600;
486
- }
487
- .error-message {
488
- font-size: 1.1rem;
489
- margin-bottom: 30px;
490
- opacity: 0.9;
491
- line-height: 1.6;
492
- }
493
- .error-footer {
494
- font-size: 0.9rem;
495
- opacity: 0.7;
496
- border-top: 1px solid rgba(255, 255, 255, 0.2);
497
- padding-top: 20px;
498
- margin-top: 30px;
499
- }
500
- .back-link {
501
- display: inline-block;
502
- margin-top: 20px;
503
- padding: 12px 24px;
504
- background: rgba(255, 255, 255, 0.2);
505
- border: 1px solid rgba(255, 255, 255, 0.3);
506
- border-radius: 8px;
507
- color: white;
508
- text-decoration: none;
509
- font-weight: 500;
510
- transition: all 0.3s ease;
511
- }
512
- .back-link:hover {
513
- background: rgba(255, 255, 255, 0.3);
514
- transform: translateY(-2px);
515
- }
516
- </style>
61
+ <meta charset="utf-8">
62
+ <title>Listing of ${t}</title>
63
+ <style>${S()}</style>
517
64
  </head>
518
65
  <body>
519
- <div class="error-container">
520
- <div class="error-code">${e}</div>
521
- <h1 class="error-title">${t}</h1>
522
- <p class="error-message">${{403:`You do not have permission to access this resource.`,404:`The requested file or directory was not found.`,405:`The requested method is not allowed for this resource.`,500:`An internal server error occurred.`}[e]||`An error occurred.`}</p>
523
- <a href="/" class="back-link">← Go Home</a>
524
- <div class="error-footer">
525
- HTTPath Server
526
- </div>
527
- </div>
66
+ <label class="toggle" for="dark"><input type="checkbox" id="dark" class="toggle__input" checked><span class="toggle__fill"></span></label>
67
+ <h1>Listing of ${t}</h1>
68
+ ${n}
69
+ ${r}
528
70
  </body>
529
- </html>`}};const Y=e=>new ge(e),X=q({level:`info`,format:`simple`,colorize:!0});let Z=null;const Q=async()=>{$();let e=w();l(e)||(X.error(`❌ Failed to parse CLI arguments:`,e.error.message),process.exit(1));let t=e.data;T(t)||process.exit(1);try{Z=await Y(t).start(),_e()}catch(e){X.error(`❌ Failed to start server:`,e instanceof Error?e.message:String(e)),process.exit(1)}},$=()=>{console.log(`
530
- 🚀 ${C.name} v${C.version}
531
- ${C.description}
532
- `)},_e=()=>{for(let e of[`SIGINT`,`SIGTERM`])process.on(e,async()=>{if(X.info(`\n\n👋 Received ${e}, shutting down gracefully...`),Z)try{await Z.stop(),X.info(`✅ Server stopped successfully`),process.exit(0)}catch(e){X.error(`❌ Error stopping server:`,e),process.exit(1)}else process.exit(0)});process.on(`uncaughtException`,e=>{X.error(`💥 Uncaught Exception:`,e),Z?Z.stop().finally(()=>process.exit(1)):process.exit(1)}),process.on(`unhandledRejection`,(e,t)=>{X.error(`💥 Unhandled Promise Rejection:`,e),X.debug(`Promise:`,t),Z?Z.stop().finally(()=>process.exit(1)):process.exit(1)})};process.argv[1]&&(process.argv[1].endsWith(`/index.mjs`)||process.argv[1].endsWith(`\\index.mjs`)||process.argv[1].includes(`dist`))&&Q().catch(e=>{X.error(`💥 Application crashed:`,e),process.exit(1)}),exports.VERSION_INFO=C,exports.createHTTPServer=Y,exports.createHotReloadService=ue,exports.createLogger=q,exports.findAvailablePort=J,exports.main=Q,exports.parseCliArgs=w,exports.validateConfig=T;
71
+ </html>`},w=async(e,t,n)=>{let a=_(e);if(n.enableLiveReload&&a.includes(`text/html`))try{let r=await(0,i.open)(e);try{let e=y(await r.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":a}),t.end(e);return}finally{await r.close()}}catch{}t.writeHead(200,{"Content-Type":a});let o=(0,r.createReadStream)(e);o.pipe(t),o.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},T=async(e,n,r,a)=>{try{let o=C((await(0,i.readdir)(e,{withFileTypes:!0})).filter(e=>!h(e.name,a.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:(0,t.join)(n,e.name).replace(/\\/g,`/`)})),n);a.enableLiveReload&&(o=y(o,a.port)),r.writeHead(200,{"Content-Type":`text/html`}),r.end(o)}catch{r.writeHead(500),r.end(`Error reading directory`)}},E=e=>async(n,r)=>{let a=n.headers.host||`localhost:${e.port}`,o=new URL(n.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return b(n,r);let c=(0,t.resolve)(e.directory,`.${s}`);if(!c.startsWith(e.directory))return r.writeHead(403),r.end(`Forbidden`);try{let n=await(0,i.stat)(c);if(n.isFile())return await w(c,r,e);if(n.isDirectory()){if(e.enableDirectoryListing)return await T(c,s,r,e);{let n=(0,t.join)(c,`index.html`);try{return await(0,i.stat)(n),await w(n,r,e)}catch{return r.writeHead(403),r.end(`Listing disabled`)}}}else return r.writeHead(404),r.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return r.writeHead(404),r.end(`Not Found`);r.writeHead(500),r.end(e?.message||`Server error`)}},D=()=>{m(`Reloading server process...`),(0,o.spawn)(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},O=async e=>{m(`Watching: ${e.directory}`);try{(0,r.watch)(e.directory,{recursive:!0},async(t,n)=>{if(!n||h(n,e.ignorePatterns)||d)return;d=!0,m(`Change detected: ${n} (${t})`),await g(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&x(`restart`),D()):e.enableLiveReload&&i&&x(`asset change`),setTimeout(()=>{d=!1},1e3)})}catch(e){m(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=p(process.argv.slice(2));(0,n.createServer)(E(e)).listen(e.port,()=>{m(`Server running at http://localhost:${e.port}`),m(`Root: ${e.directory}`),O(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{m(`Shutting down...`),process.exit(0)})})}catch(e){m(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();