// deno-lint-ignore-file no-unversioned-import no-import-prefix
import { Args } from "./modules/args.ts";
import html_base from "./src/file_tree.html" with { type: "text" };
import css_base from "./src/file_browser.css" with { type: "text" };
import help_text from "./src/help_text.txt" with { type: "text" };
import help_colors from "./src/help_colors.txt" with { type: "text" };
import local_deno_json from "./deno.json" with { type: "json" };
const local_version = local_deno_json.version;
import { join } from "jsr:@std/path";
import { existsSync } from "jsr:@std/fs";
import { serveFile } from "jsr:@std/http/file-server";
import { GridRenderer } from "./modules/renderers/grid.ts";
import { ListRenderer } from "./modules/renderers/list.ts";
import { Renderer } from "./modules/renderers/_renderer.ts";
ListRenderer;
GridRenderer;
if(Deno.args.includes('void')) Deno.exit(0);
const args = Args.Parse({
port: {
type: "number",
long: "--port",
short: "-p",
default: 8080
},
help: {
type: 'flag',
long: '--help',
short: '-h'
},
hidden: {
type: 'flag',
long: '--show-hidden',
short: '-H'
}
});
const get_cloud_version = async () => {
try {
const cloud_deno_json = await (await fetch('https://viapak.xyz/@utils/http-server/deno.json?direct=true')).json() as typeof local_deno_json;
return cloud_deno_json.version;
} catch(_) {
return local_deno_json.version;
}
}
const check_for_updates = async () => {
const cloud_version = await get_cloud_version();
if(local_version !== cloud_version) {
do_upgrade().then(() => {
console.log(`%chttp-server%c has been upgraded to %c${cloud_version}%c!\nRestart %chttp-server%c to use the new version.`, 'color: orange', 'color: skyblue', 'color: orange', 'color: skyblue', 'color: orange', 'color: skyblue');
})
}
}
const do_upgrade = () => {
return new Deno.Command("deno", {
args: "run --unstable-raw-imports -Arq https://viapak.xyz/@utils/http-server void".split(' ')
}).output()
}
const upgrade = async (force: boolean = false) => {
console.log("%cFetching latest %chttp-server%c version...", 'color: skyblue', 'color: orange', 'color: skyblue');
const cloud_version = await get_cloud_version();
console.log(`%cLatest version: %c${cloud_version}`, 'color: skyblue', 'color: orange');
if(local_version == cloud_version) {
if(!force) {
console.log(`%cAlready on latest version.\n`);
return false;
}
console.log('%cAlready on latest version. Validating...', 'color: skyblue');
await do_upgrade();
console.log('Done\n');
return true;
}
await do_upgrade();
console.log(`%chttp-server%c has been updated to version %c${cloud_version}%c\n`, 'color: orange', 'color: skyblue', 'color: orange', '');
}
if(Deno.args.includes('upgrade')) {
await upgrade(true);
Deno.exit(0);
}
if(args.help || Deno.args.includes('help')) {
console.log(help_text, ...help_colors.split(/[\r\n]/).map(c => c.length ? `color: ${c}` : ''));
Deno.exit(0);
}
export interface TreeItem {
name: string
path: string
is_directory: boolean
href: string
}
let renderer: Renderer = ListRenderer;
export const is_image = (file_name: string) => !!file_name.match(/\.(jpe?g|png|bmp|webp|gif|svg|ico)$/);
setTimeout(check_for_updates, 5000); // 5 seconds after booting
setInterval(check_for_updates, 1000 * 60 * 60 * 2); // Every 2 hours
Deno.serve({
port: args.port
}, req => {
const url = new URL(req.url);
if(url.pathname == '/http-server-mode-switch') {
renderer = Renderer.ByName[url.searchParams.get('mode') || ''] || ListRenderer;
return new Response(null, {
headers: new Headers({
Location: req.headers.get('referer') || '/'
}),
status: 307
})
}
const path = join(Deno.cwd(), url.pathname).replace(/\%20/g, ' ');
if(!existsSync(path)) {
return new Response('404 Not Found', { status: 404 });
}
const { isDirectory } = Deno.statSync(path);
if(isDirectory) {
const index_path = join(path, 'index.html');
if(existsSync(index_path) && Deno.statSync(index_path).isFile) return serveFile(req, index_path);
} else {
if(Deno.statSync(path).isFile) return serveFile(req, path);
return new Response('404 Not Found', { status: 404 });
}
const is_root = url.pathname == '/';
let items: TreeItem[] = [];
for(const entry of Deno.readDirSync(path)) {
if(!args.hidden && entry.name.startsWith('.')) continue;
items.push({
is_directory: entry.isDirectory,
name: entry.name,
path: join(url.pathname, entry.name),
href: `${join(url.pathname, entry.name)}${ entry.isDirectory ? '/' : ''}`
})
}
items = items.sort((a, b) => a.is_directory == b.is_directory ? 0 : a.is_directory ? -1 : 1);
const directories = items.filter(item => item.is_directory);
const files = items.filter(item => !item.is_directory);
const content = `
<div class="topbar">
<div class="title">http-server</div>
<a href="/" style="color: white" class="title">${url.protocol}//${url.host}</a>
</div>
<div style="padding: 2rem 4rem; margin-top: 80px;">
<div class="navbar">
<div>
${ is_root ? '' : `<a class="button" href='${url.pathname.replace(/\/[\.\w]*\/?$/, '') || '/'}'>Up</a>` }
</div>
<div>Current path: <span class="path">${ url.pathname }</span></div>
<div class="flex">
<a class="button" href="/http-server-mode-switch?mode=grid">Grid</a>
<a class="button" href="/http-server-mode-switch?mode=list">List</a>
</div>
</div>
<div class="content">
${items.length ? `
${directories.length ? `
${renderer.render(directories)}
` : ''}
${files.length ? `
${renderer.render(files)}
` : ''}
` : '<h3 style="padding: 1rem 2rem;">This directory is empty</h3>'}
</div>
</div>
`;
const html = html_base
.replace(/\<Title\s*\/\>/, `http-server:${url.pathname}`)
.replace(/\<Content\s*\/\>/, content)
.replace(/\<style\s+id="root-styles"\s*\>/, `<style>${css_base}`);
return new Response(html, {
headers: new Headers({
'Content-Type': 'text/html'
})
});
})