authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-06-08 12:38:25-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-10-14 02:40:44-07:00
log6832e061d3ab21c081e593dc308f5087eacf377d
tree1d5a07d4463a1df48d1c827a82b9ccdbb992faa6
parent8f9a9d4e99fa9e1b759ae0f7a63151deae720585
signature Commit is signed but in an unrecognized format.

add content type library


12 files changed, 153 insertions(+), 40 deletions(-)

deno.jsonc+1-1
......@@ -1,6 +1,6 @@
11{
22 "lint": {
3 "exclude": ["framework/meta"], // OLD
3 "exclude": ["src"], // OLD
44 "rules": {
55 "exclude": [
66 "no-explicit-any" // TODO
framework/assets.ts+1
......@@ -97,3 +97,4 @@ import * as fs from "./fs.ts";
9797import type { Context, Next } from "hono";
9898import type { StatusCode } from "hono/utils/http-status";
9999import type { BuiltAsset, BuiltAssetMap, View } from "./incremental.ts";
100import { Buffer } from "node:buffer";
framework/bundle.ts+2-2
......@@ -1,5 +1,4 @@
11// This file implements client-side bundling, mostly wrapping esbuild.
2import process from "node:process";
32const plugins: esbuild.Plugin[] = [
43 // There are currently no plugins needed by 'paperclover.net'
54];
......@@ -80,6 +79,7 @@ export async function bundleClientJavaScript(
8079 }
8180}
8281
83import * as path from "node:path";
8482import * as esbuild from "esbuild";
83import * as path from "node:path";
84import process from "node:process";
8585import { Incremental } from "./incremental.ts";
framework/engine/jsx-runtime.ts+1-1
......@@ -35,7 +35,7 @@ declare global {
3535 [name: string]: Record<string, unknown>;
3636 }
3737 interface ElementChildrenAttribute {
38 children: {};
38 children: unknown;
3939 }
4040 type Element = engine.Node;
4141 type ElementType = keyof IntrinsicElements | engine.Component;
framework/hot.ts+4-2
......@@ -157,11 +157,13 @@ function loadMarko(module: NodeJS.Module, filepath: string) {
157157 // bare client import statements to it's own usage.
158158 if (src.match(/^\s*client\s+import\s+["']/m)) {
159159 src = src.replace(
160 /^\s*client\s+import\s+("[^"]+|'[^']+)[^\n]+/m,
160 /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]+/m,
161161 "<CloverScriptInclude src=$1 />",
162 ) + '\nimport { Script as CloverScriptInclude } from "#sitegen";';
162 ) + '\nimport { Script as CloverScriptInclude } from "#sitegen";\n';
163163 }
164164
165 console.log(src);
166 console.log("---");
165167 src = marko.compileSync(src, filepath).code;
166168 src = src.replace("marko/debug/html", "#ssr/marko");
167169 return loadEsbuildCode(module, filepath, src);
framework/mime.ts+19-18
......@@ -1,29 +1,30 @@
1const db = new Map(
2 fs.readFileSync(path.join(import.meta.dirname, "mime.txt"), "utf8")
3 .split("\n").filter(Boolean).map((line) =>
4 line.split(/\s+/) as [string, string]
5 ),
6);
1const entries = fs.readFileSync(
2 path.join(import.meta.dirname, "mime.txt"),
3 "utf8",
4)
5 .split("\n")
6 .map((line) => line.trim())
7 .filter((line) => line && !line.startsWith("#"))
8 .map((line) => line.split(/\s+/, 2) as [string, string]);
9const extensions = new Map(entries.filter((x) => x[0].startsWith(".")));
10const fullNames = new Map(entries.filter((x) => !x[0].startsWith(".")));
711
812/**
913 * Accepts:
10 * - Full file path
14 * - Full file path or basename
1115 * - Extension (with or without dot)
1216 */
1317export function contentTypeFor(file: string) {
14 if (file.includes("/") || file.includes("\\")) {
15 // Some file names are special cased.
16 switch (path.basename(file)) {
17 case "rss.xml":
18 return "application/rss+xml";
19 }
20
21 file = path.extname(file);
22 }
18 const slash = file.indexOf("/");
19 if (slash !== -1) file = file.slice(slash + 1);
2320 const dot = file.indexOf(".");
2421 if (dot === -1) file = "." + file;
25 else if (dot > 0) file = file.slice(dot);
26 return db.get(file) ?? "application/octet-stream";
22 else if (dot > 0) {
23 let entry = fullNames.get(file);
24 if (entry) return entry;
25 file = file.slice(dot);
26 }
27 return extensions.get(file) ?? "application/octet-stream";
2728}
2829
2930import * as fs from "./fs.ts";
framework/mime.txt created+99
......@@ -0,0 +1,99 @@
1# media types
2# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types
3.aac audio/x-aac
4.aif audio/x-aiff
5.aifc audio/x-aiff
6.aiff audio/x-aiff
7.asm text/x-asm
8.avi video/x-msvideo
9.bat application/x-msdownload
10.c text/x-c
11.chat text/x-clover-chatlog
12.class application/java-vm
13.cmd application/x-msdownload
14.com application/x-msdownload
15.conf text/plain
16.cpp text/x-c
17.css text/css
18.csv text/csv
19.cxx text/x-c
20.def text/plain
21.diff text/plain
22.dll application/x-msdownload
23.dmg application/octet-stream
24.doc application/msword
25.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
26.epub application/epub+zip
27.exe application/x-msdownload
28.flv video/x-flv
29.fbx application/fbx
30.gz application/x-gzip
31.h text/x-c
32.h264 video/h264
33.hh text/x-c
34.htm text/html;charset=utf-8
35.html text/html;charset=utf-8
36.ico image/x-icon
37.ics text/calendar
38.in text/plain
39.jar application/java-archive
40.java text/x-java-source
41.jpeg image/jpeg
42.jpg image/jpeg
43.jpgv video/jpeg
44.jxl image/jxl
45.js application/javascript
46.json application/json
47.latex application/x-latex
48.list text/plain
49.log text/plain
50.m4a audio/mp4
51.man text/troff
52.mid audio/midi
53.midi audio/midi
54.mov video/quicktime
55.mp3 audio/mpeg
56.mp4 video/mp4
57.msh model/mesh
58.msi application/x-msdownload
59.obj application/octet-stream
60.ogg audio/ogg
61.otf application/x-font-otf
62.pdf application/pdf
63.png image/png
64.ppt application/vnd.ms-powerpoint
65.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
66.psd image/vnd.adobe.photoshop
67.py text/x-python
68.rar application/x-rar-compressed
69.rss application/rss+xml
70.rtf application/rtf
71.rtx text/richtext
72.s text/x-asm
73.pem application/x-pem-file"
74.ser application/java-serialized-object
75.sh application/x-sh
76.sig application/pgp-signature
77.silo model/mesh
78.svg image/svg+xml
79.t text/troff
80.tar application/x-tar
81.text text/plain
82.tgz application/x-gzip
83.tif image/tiff
84.tiff image/tiff
85.torrent application/x-bittorrent
86.ttc application/x-font-ttf
87.ttf application/x-font-ttf
88.txt text/plain
89.urls text/uri-list
90.v text/x-v
91.wav audio/x-wav
92.wmv video/x-ms-wmv
93.xls application/vnd.ms-excel
94.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
95.xml application/xml
96.xps application/vnd.ms-xpsdocument
97
98# special cased based on file name
99rss.xml application/rss+xml
framework/queue.ts+5-5
......@@ -1,8 +1,3 @@
1import { Progress } from "@paperclover/console/Progress";
2import { Spinner } from "@paperclover/console/Spinner";
3import * as path from "node:path";
4import process from "node:process";
5
61interface QueueOptions<T, R> {
72 name: string;
83 fn: (item: T, spin: Spinner) => Promise<R>;
......@@ -204,3 +199,8 @@ export class OnceMap<T> {
204199 return result;
205200 }
206201}
202
203import { Progress } from "@paperclover/console/Progress";
204import { Spinner } from "@paperclover/console/Spinner";
205import * as path from "node:path";
206import process from "node:process";
package-lock.json+3-3
......@@ -1027,9 +1027,9 @@
10271027 "license": "ISC"
10281028 },
10291029 "node_modules/acorn": {
1030 "version": "8.14.1",
1031 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
1032 "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
1030 "version": "8.15.0",
1031 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
1032 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
10331033 "license": "MIT",
10341034 "bin": {
10351035 "acorn": "bin/acorn"
package.json+3-4
......@@ -1,5 +1,4 @@
11{
2 "private": true,
32 "type": "module",
43 "dependencies": {
54 "@hono/node-server": "^1.14.3",
......@@ -23,9 +22,9 @@
2322 "#ssr/jsx-runtime": "./framework/engine/jsx-runtime.ts",
2423 "#ssr/marko": "./framework/engine/marko-runtime.ts",
2524 "#marko/html": {
26 "development": "marko/debug/html",
27 "production": "marko/html",
28 "types": "marko/html"
25 "types": "marko/html",
26 "production": "marko/production",
27 "node": "marko/debug/html"
2928 },
3029 "#hono/platform": {
3130 "bun": "hono/bun",
repl.js+1-1
......@@ -18,7 +18,7 @@ hot.load("node:repl").start({
1818 .catch((err) => {
1919 // TODO: improve @paperclover/console's ability to print AggregateError
2020 // and errors with extra random properties
21 console.error(inspect(err));
21 console.error(util.inspect(err));
2222 })
2323 .then((result) => done(null, result));
2424 },
run.js+14-3
......@@ -1,6 +1,5 @@
11// This file allows using Node.js in combination with
22// all available plugins. Usage: "node run <script>"
3import * as path from "node:path";
43import * as util from "node:util";
54import process from "node:process";
65
......@@ -47,9 +46,21 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) {
4746 console.error("usage: node run <script> [...args]");
4847 process.exit(1);
4948 }
50 const file = path.resolve(process.argv[2]);
49 let found;
50 for (const dir of ["./", "./src/", "./framework/"]) {
51 try {
52 found = hot.resolveFrom(import.meta.filename, dir + process.argv[2]);
53 break;
54 } catch (e) {
55 continue;
56 }
57 }
58 if (!found) {
59 console.error("Cannot find script: " + process.argv[2]);
60 process.exit(1);
61 }
5162 process.argv = [process.argv[0], ...process.argv.slice(2)];
52 hot.load(file).main?.();
63 hot.load(found).main?.();
5364}
5465
5566export { hot };