httpath 1.1.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 +83 -17
- package/dist/index.mjs +82 -16
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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(`
|
|
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
3
|
Static File Server with Auto-Reload (Node.js)
|
|
4
4
|
|
|
5
5
|
Usage: httpreload [OPTIONS]
|
|
@@ -10,7 +10,7 @@ Options:
|
|
|
10
10
|
-i, --ignore <patterns> Comma-separated patterns to ignore
|
|
11
11
|
--no-listing Disable directory listing
|
|
12
12
|
--no-live-reload Disable live reload feature
|
|
13
|
-
--restart-on-change Restart server process on file changes
|
|
13
|
+
-r --restart-on-change Restart server process on file changes
|
|
14
14
|
--log <level> Log level: info, debug, error
|
|
15
15
|
-h, --help Show this help message
|
|
16
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=>`
|
|
@@ -48,24 +48,90 @@ Options:
|
|
|
48
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
49
|
|
|
50
50
|
`)}catch{}}},S=()=>`
|
|
51
|
-
:root
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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>
|
|
109
|
+
<html lang="en">
|
|
60
110
|
<head>
|
|
61
|
-
<meta charset="utf-8"
|
|
62
|
-
<
|
|
111
|
+
<meta charset="utf-8" />
|
|
112
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
113
|
+
<title>Listing of ${n}</title>
|
|
63
114
|
<style>${S()}</style>
|
|
115
|
+
<style>/* small reset for injected scripts */ body > script{display:none}</style>
|
|
64
116
|
</head>
|
|
65
117
|
<body>
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
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>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<ul class="file-list">
|
|
131
|
+
${a}
|
|
132
|
+
${o}
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
70
136
|
</body>
|
|
71
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)}})();
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{parseArgs as e}from"node:util";import{dirname as t,extname as n,join as r,resolve as i}from"node:path";import{createServer as a}from"node:http";import{createReadStream as o,watch as s}from"node:fs";import{open as c,readdir as l,stat as u}from"node:fs/promises";import{fileURLToPath as d}from"node:url";import{spawn as f}from"node:child_process";const p=t(d(import.meta.url)),m={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`},h={directory:p,port:8080,ignorePatterns:[`.git`,`node_modules`,`.DS_Store`],enableDirectoryListing:!0,logLevel:`info`,enableLiveReload:!0,restartOnChange:!1};let g=null,_=!1;const v=new Set,y=t=>{let{values:n}=e({args:t,options:{dir:{type:`string`,short:`d`,default:h.directory},port:{type:`string`,short:`p`,default:h.port.toString()},ignore:{type:`string`,short:`i`,default:h.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:h.logLevel},help:{type:`boolean`,short:`h`,default:!1}},allowPositionals:!0});n.help&&(console.log(`
|
|
2
|
+
import{parseArgs as e}from"node:util";import{dirname as t,extname as n,join as r,resolve as i}from"node:path";import{createServer as a}from"node:http";import{createReadStream as o,watch as s}from"node:fs";import{open as c,readdir as l,stat as u}from"node:fs/promises";import{fileURLToPath as d}from"node:url";import{spawn as f}from"node:child_process";const p=t(d(import.meta.url)),m={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`},h={directory:p,port:8080,ignorePatterns:[`.git`,`node_modules`,`.DS_Store`],enableDirectoryListing:!0,logLevel:`info`,enableLiveReload:!0,restartOnChange:!1};let g=null,_=!1;const v=new Set,y=t=>{let{values:n}=e({args:t,options:{dir:{type:`string`,short:`d`,default:h.directory},port:{type:`string`,short:`p`,default:h.port.toString()},ignore:{type:`string`,short:`i`,default:h.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:h.logLevel},help:{type:`boolean`,short:`h`,default:!1}},allowPositionals:!0});n.help&&(console.log(`
|
|
3
3
|
Static File Server with Auto-Reload (Node.js)
|
|
4
4
|
|
|
5
5
|
Usage: httpreload [OPTIONS]
|
|
@@ -10,7 +10,7 @@ Options:
|
|
|
10
10
|
-i, --ignore <patterns> Comma-separated patterns to ignore
|
|
11
11
|
--no-listing Disable directory listing
|
|
12
12
|
--no-live-reload Disable live reload feature
|
|
13
|
-
--restart-on-change Restart server process on file changes
|
|
13
|
+
-r --restart-on-change Restart server process on file changes
|
|
14
14
|
--log <level> Log level: info, debug, error
|
|
15
15
|
-h, --help Show this help message
|
|
16
16
|
`),process.exit(0));let r=parseInt(n.port||`8080`,10);if(isNaN(r)||r<1||r>65535)throw Error(`Port must be a valid number between 1 and 65535`);return{directory:i(n.dir||`.`),port:r,ignorePatterns:(n.ignore||``).split(`,`).map(e=>e.trim()).filter(Boolean),enableDirectoryListing:!n[`no-listing`],logLevel:n.log||h.logLevel,enableLiveReload:!n[`no-live-reload`],restartOnChange:!!n[`restart-on-change`]}},b=(e,t=`info`)=>{let n=new Date().toISOString(),r=t.toUpperCase().padEnd(5);console.log(`[${n}] ${r} ${e}`)},x=(e,t)=>t.some(t=>e.includes(t)||e.endsWith(t)),S=e=>new Promise(t=>{g&&clearTimeout(g),g=setTimeout(()=>{g=null,t()},e)}),C=e=>m[n(e).slice(1).toLowerCase()]||`application/octet-stream`,w=e=>`
|
|
@@ -48,24 +48,90 @@ Options:
|
|
|
48
48
|
`),v.add(t),e.on(`close`,()=>{v.delete(t)})},D=async(e=`change`)=>{if(v.size!==0){b(`Reloading ${v.size} clients (${e})`,`debug`);for(let e of v)try{e.write(`data: reload
|
|
49
49
|
|
|
50
50
|
`)}catch{}}},O=()=>`
|
|
51
|
-
:root
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
`,k=(e,t)=>{let n=t===`/`?null:{name:`..`,url:r(t,`../`).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=n?`<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>
|
|
109
|
+
<html lang="en">
|
|
60
110
|
<head>
|
|
61
|
-
<meta charset="utf-8"
|
|
111
|
+
<meta charset="utf-8" />
|
|
112
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
62
113
|
<title>Listing of ${t}</title>
|
|
63
114
|
<style>${O()}</style>
|
|
115
|
+
<style>/* small reset for injected scripts */ body > script{display:none}</style>
|
|
64
116
|
</head>
|
|
65
117
|
<body>
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
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 ${t}</h1>
|
|
124
|
+
<div class="path">${t}</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="controls">
|
|
127
|
+
<label for="dark-toggle" class="toggle-btn" title="Toggle dark mode">🌓</label>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<ul class="file-list">
|
|
131
|
+
${a}
|
|
132
|
+
${o}
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
70
136
|
</body>
|
|
71
137
|
</html>`},A=async(e,t,n)=>{let r=C(e);if(n.enableLiveReload&&r.includes(`text/html`))try{let i=await c(e);try{let e=T(await i.readFile({encoding:`utf-8`}),n.port);t.writeHead(200,{"Content-Type":r}),t.end(e);return}finally{await i.close()}}catch{}t.writeHead(200,{"Content-Type":r});let i=o(e);i.pipe(t),i.on(`error`,e=>{t.writeHead(500),t.end(e.message)})},j=async(e,t,n,i)=>{try{let a=k((await l(e,{withFileTypes:!0})).filter(e=>!x(e.name,i.ignorePatterns)).map(e=>({name:e.name,isDirectory:e.isDirectory(),url:r(t,e.name).replace(/\\/g,`/`)})),t);i.enableLiveReload&&(a=T(a,i.port)),n.writeHead(200,{"Content-Type":`text/html`}),n.end(a)}catch{n.writeHead(500),n.end(`Error reading directory`)}},M=e=>async(t,n)=>{let a=t.headers.host||`localhost:${e.port}`,o=new URL(t.url||`/`,`http://${a}`),s=decodeURIComponent(o.pathname);if(e.enableLiveReload&&s===`/livereload`)return E(t,n);let c=i(e.directory,`.${s}`);if(!c.startsWith(e.directory))return n.writeHead(403),n.end(`Forbidden`);try{let t=await u(c);if(t.isFile())return await A(c,n,e);if(t.isDirectory()){if(e.enableDirectoryListing)return await j(c,s,n,e);{let t=r(c,`index.html`);try{return await u(t),await A(t,n,e)}catch{return n.writeHead(403),n.end(`Listing disabled`)}}}else return n.writeHead(404),n.end(`Not Found`)}catch(e){if(e&&e.code===`ENOENT`)return n.writeHead(404),n.end(`Not Found`);n.writeHead(500),n.end(e?.message||`Server error`)}},N=()=>{b(`Reloading server process...`),f(process.argv[0],process.argv.slice(1),{stdio:`inherit`,detached:!0}).unref(),process.exit(0)},P=async e=>{b(`Watching: ${e.directory}`);try{s(e.directory,{recursive:!0},async(t,n)=>{if(!n||x(n,e.ignorePatterns)||_)return;_=!0,b(`Change detected: ${n} (${t})`),await S(500);let r=/\.(json|js|mjs|ts)$/.test(n),i=/\.(html|css|png|jpg|jpeg|gif|svg)$/.test(n);e.restartOnChange||r?(e.enableLiveReload&&D(`restart`),N()):e.enableLiveReload&&i&&D(`asset change`),setTimeout(()=>{_=!1},1e3)})}catch(e){b(`Watcher error: ${e?.message}`,`error`)}};(()=>{try{let e=y(process.argv.slice(2));a(M(e)).listen(e.port,()=>{b(`Server running at http://localhost:${e.port}`),b(`Root: ${e.directory}`),P(e)}),[`SIGINT`,`SIGTERM`].forEach(e=>{process.on(e,()=>{b(`Shutting down...`),process.exit(0)})})}catch(e){b(`Startup Error: ${e?.message}`,`error`),process.exit(1)}})();export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "httpath",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A lightweight, feature-rich static file server similar to Python's `python -m http.server` but with modern Node.js features and more.",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.mts",
|