httpath 1.0.0 → 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.cjs +115 -510
- package/dist/index.d.cts +14 -377
- package/dist/index.d.mts +14 -377
- package/dist/index.mjs +115 -510
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,532 +1,137 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
let e=require(`node:util`),t=require(`node:path`),n=require(`node:http`),r=require(`node:fs
|
|
3
|
-
|
|
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`,short:`r`,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:
|
|
5
|
+
Usage: httpreload [OPTIONS]
|
|
6
6
|
|
|
7
7
|
Options:
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
--no-
|
|
13
|
-
--restart-on-change
|
|
14
|
-
--log <level>
|
|
15
|
-
-h, --help
|
|
16
|
-
|
|
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
|
+
-r --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
|
-
(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
let eventSource;
|
|
279
|
-
let reconnectAttempts = 0;
|
|
280
|
-
const maxReconnectAttempts = 10;
|
|
281
|
-
const baseReconnectDelay = 1000;
|
|
18
|
+
(() => {
|
|
19
|
+
const sseUrl = '/livereload';
|
|
20
|
+
let source;
|
|
282
21
|
|
|
283
|
-
|
|
284
|
-
console.log('[
|
|
22
|
+
const connect = () => {
|
|
23
|
+
console.log('[Live Reload] Connecting...');
|
|
24
|
+
source = new EventSource(sseUrl);
|
|
285
25
|
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
312
|
-
console.
|
|
313
|
-
|
|
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
|
-
}
|
|
330
|
-
|
|
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
|
-
\`;
|
|
42
|
+
};
|
|
370
43
|
|
|
371
|
-
|
|
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
|
|
440
|
-
|
|
441
|
-
`)
|
|
442
|
-
|
|
443
|
-
`)}catch
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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{
|
|
52
|
+
--bg: #f6f8fa;
|
|
53
|
+
--card: #ffffff;
|
|
54
|
+
--muted: #6b7280;
|
|
55
|
+
--accent: #2563eb;
|
|
56
|
+
--text: #0f172a;
|
|
57
|
+
--elev: 0 6px 18px rgba(15,23,42,0.06);
|
|
58
|
+
--btn-border: rgba(15,23,42,0.06);
|
|
59
|
+
}
|
|
60
|
+
/* Respect user's system preference */
|
|
61
|
+
@media (prefers-color-scheme: dark){
|
|
62
|
+
:root{
|
|
63
|
+
--bg: #0b1220;
|
|
64
|
+
--card: #0f1724;
|
|
65
|
+
--muted: #9ca3af;
|
|
66
|
+
--accent: #60a5fa;
|
|
67
|
+
--text: #e6eef8;
|
|
68
|
+
--elev: 0 6px 22px rgba(2,6,23,0.6);
|
|
69
|
+
--btn-border: rgba(255,255,255,0.08);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/* Toggle using hidden checkbox (works without :has()) */
|
|
73
|
+
#dark-toggle:checked ~ .container{
|
|
74
|
+
--bg: #0b1220;
|
|
75
|
+
--card: #0f1724;
|
|
76
|
+
--muted: #9ca3af;
|
|
77
|
+
--accent: #60a5fa;
|
|
78
|
+
--text: #e6eef8;
|
|
79
|
+
--elev: 0 6px 22px rgba(2,6,23,0.6);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.box-sizing{box-sizing:border-box}
|
|
83
|
+
*{box-sizing:border-box}
|
|
84
|
+
html{color-scheme: light dark; background-color:var(--bg)}
|
|
85
|
+
body{margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,'Helvetica Neue',Arial,sans-serif;font-size:14px;background:var(--bg);color:var(--text)}
|
|
86
|
+
.container{max-width:980px;margin:28px auto;padding:20px}
|
|
87
|
+
.card{background:var(--card);border-radius:10px;padding:18px;box-shadow:var(--elev)}
|
|
88
|
+
.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;gap:12px}
|
|
89
|
+
.title{font-size:18px;font-weight:600;color:var(--text);margin:0}
|
|
90
|
+
.path{font-size:12px;color:var(--muted)}
|
|
91
|
+
.controls{display:flex;align-items:center;gap:8px}
|
|
92
|
+
.toggle-btn{background:transparent;border:1px solid var(--btn-border);padding:6px 8px;border-radius:8px;cursor:pointer;font-size:14px;color:var(--text);backdrop-filter: blur(4px);transition:all 180ms ease}
|
|
93
|
+
.toggle-btn:hover{opacity:0.95}
|
|
94
|
+
|
|
95
|
+
/* Visible active state when dark mode is enabled */
|
|
96
|
+
#dark-toggle:checked ~ .container .toggle-btn{background:var(--accent);color:#fff;border-color:transparent;transform:translateY(-1px)}
|
|
97
|
+
.file-list{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px}
|
|
98
|
+
.file{display:flex;align-items:center;padding:10px;border-radius:8px;border:1px solid rgba(15,23,42,0.04);background:linear-gradient(180deg,rgba(0,0,0,0.01),transparent)}
|
|
99
|
+
.file a{display:flex;align-items:center;gap:12px;width:100%;color:inherit;text-decoration:none}
|
|
100
|
+
.icon{width:44px;height:44px;border-radius:8px;display:flex;align-items:center;justify-content:center;background:rgba(37,99,235,0.08);font-size:20px}
|
|
101
|
+
.name{font-weight:500}
|
|
102
|
+
.meta{margin-left:auto;font-size:12px;color:var(--muted)}
|
|
103
|
+
.parent{grid-column:1/-1;padding:0 4px}
|
|
104
|
+
.empty{padding:18px;text-align:center;color:var(--muted)}
|
|
105
|
+
@media (max-width:600px){.file-list{grid-template-columns:1fr}}
|
|
106
|
+
`,C=(e,n)=>{let r=n===`/`?null:{name:`..`,url:(0,t.join)(n,`../`).replace(/\\/g,`/`),isDirectory:!0},i=e.slice().sort((e,t)=>e.isDirectory===t.isDirectory?e.name.toLowerCase().localeCompare(t.name.toLowerCase()):e.isDirectory?-1:1).map(e=>{let t=e.isDirectory?`📁`:`📄`;return`<li class="file"><a href="${e.url}"><span class="icon">${t}</span><span class="name">${e.name}</span><span class="meta">${e.isDirectory?`Dir`:`File`}</span></a></li>`}).join(`
|
|
107
|
+
`),a=r?`<li class="file parent"><a href="../"><span class="icon">⬆️</span><span class="name">..</span><span class="meta">Parent</span></a></li>`:``,o=i||`<div class="empty">This folder is empty</div>`;return`
|
|
108
|
+
<!doctype html>
|
|
450
109
|
<html lang="en">
|
|
451
110
|
<head>
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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>
|
|
111
|
+
<meta charset="utf-8" />
|
|
112
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
113
|
+
<title>Listing of ${n}</title>
|
|
114
|
+
<style>${S()}</style>
|
|
115
|
+
<style>/* small reset for injected scripts */ body > script{display:none}</style>
|
|
517
116
|
</head>
|
|
518
117
|
<body>
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
<
|
|
524
|
-
|
|
525
|
-
|
|
118
|
+
<input type="checkbox" id="dark-toggle" aria-hidden style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;border:0;padding:0;margin:0" />
|
|
119
|
+
<div class="container">
|
|
120
|
+
<div class="card">
|
|
121
|
+
<div class="header">
|
|
122
|
+
<div>
|
|
123
|
+
<h1 class="title">Listing of ${n}</h1>
|
|
124
|
+
<div class="path">${n}</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="controls">
|
|
127
|
+
<label for="dark-toggle" class="toggle-btn" title="Toggle dark mode">🌓</label>
|
|
526
128
|
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<ul class="file-list">
|
|
131
|
+
${a}
|
|
132
|
+
${o}
|
|
133
|
+
</ul>
|
|
527
134
|
</div>
|
|
135
|
+
</div>
|
|
528
136
|
</body>
|
|
529
|
-
</html>`}};
|
|
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;
|
|
137
|
+
</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)}})();
|