Compare commits

..

No commits in common. "80ece66a0f412411a17064ff2060aa6d145ee078" and "cacfdd3bfd1a9072508ee46a8243ad13b2188b18" have entirely different histories.

10 changed files with 141 additions and 96 deletions

View File

@ -6,32 +6,31 @@ import { svg } from "../lib/tasks/svg.ts";
import { argv, exit } from "node:process"; import { argv, exit } from "node:process";
type Task = () => Promise<void>; const wrapTask = (name: string, task: () => Promise<void>) => async () => {
const wrapTask = (name: string, task: Task) => async () => {
console.log("[start]", name); console.log("[start]", name);
await task(); await task();
console.log("[end]", name); console.log("[end]", name);
}; };
const TASKS = new Map<string, Task>(Object.entries({ const TASKS: { [task: string]: () => Promise<void> } = {
articles: wrapTask("articles", articles), articles: wrapTask("articles", articles),
css: wrapTask("css", css), css: wrapTask("css", css),
fonts: wrapTask("fonts", fonts), fonts: wrapTask("fonts", fonts),
images: wrapTask("images", images), images: wrapTask("images", images),
svg: wrapTask("svg", svg), svg: wrapTask("svg", svg),
})); };
const ALL_TASKS = ["fonts", "images", "svg", "css", "articles"]; const ALL_TASKS = ["fonts", "images", "svg", "css", "articles"];
const args = argv.slice(2); const args = argv.slice(2);
await (args.length ? args : ALL_TASKS) console.log("args", args);
(args.length ? args : ALL_TASKS)
.map((task: string) => { .map((task: string) => {
if (!TASKS.has(task)) { if (!TASKS.hasOwnProperty(task)) {
console.error("Unknown task", task); console.error("Unknown task", task);
exit(1); exit(1);
} }
return TASKS.get(task)!; return TASKS[task];
}) })
.reduce((prev: Promise<void>, cur: Task) => prev.then(cur), Promise.resolve()); .reduce((prev: Promise<void>, cur: () => Promise<void>) => prev.then(cur), Promise.resolve());

View File

@ -19,10 +19,9 @@
"rehype-stringify": "npm:rehype-stringify@^10.0.0", "rehype-stringify": "npm:rehype-stringify@^10.0.0",
"rxjs": "npm:rxjs@^7.8.1", "rxjs": "npm:rxjs@^7.8.1",
"tmp": "npm:tmp@^0.2.3", "tmp": "npm:tmp@^0.2.3",
"to-vfile": "https://esm.sh/to-vfile@^8.0.0",
"unified": "npm:unified@^11.0.4", "unified": "npm:unified@^11.0.4",
"unist-util-visit": "npm:unist-util-visit@^5.0.0", "unist-util-visit": "npm:unist-util-visit@^5.0.0",
"vfile": "https://esm.sh/vfile@^6.0.1" "vinyl": "npm:vinyl@^3.0.0"
}, },
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",

View File

@ -20,7 +20,8 @@
"npm:rxjs@^7.8.1": "npm:rxjs@7.8.1", "npm:rxjs@^7.8.1": "npm:rxjs@7.8.1",
"npm:tmp@^0.2.3": "npm:tmp@0.2.3", "npm:tmp@^0.2.3": "npm:tmp@0.2.3",
"npm:unified@^11.0.4": "npm:unified@11.0.5", "npm:unified@^11.0.4": "npm:unified@11.0.5",
"npm:unist-util-visit@^5.0.0": "npm:unist-util-visit@5.0.0" "npm:unist-util-visit@^5.0.0": "npm:unist-util-visit@5.0.0",
"npm:vinyl@^3.0.0": "npm:vinyl@3.0.0"
}, },
"npm": { "npm": {
"@csstools/selector-resolve-nested@1.1.0_postcss-selector-parser@6.1.0": { "@csstools/selector-resolve-nested@1.1.0_postcss-selector-parser@6.1.0": {
@ -121,6 +122,10 @@
"picomatch": "picomatch@2.3.1" "picomatch": "picomatch@2.3.1"
} }
}, },
"b4a@1.6.6": {
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==",
"dependencies": {}
},
"bail@2.0.2": { "bail@2.0.2": {
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
"dependencies": {} "dependencies": {}
@ -129,6 +134,10 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dependencies": {} "dependencies": {}
}, },
"bare-events@2.4.2": {
"integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==",
"dependencies": {}
},
"bcp-47-match@2.0.3": { "bcp-47-match@2.0.3": {
"integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==",
"dependencies": {} "dependencies": {}
@ -233,6 +242,14 @@
"source-map": "source-map@0.6.1" "source-map": "source-map@0.6.1"
} }
}, },
"clone-stats@1.0.0": {
"integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==",
"dependencies": {}
},
"clone@2.1.2": {
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dependencies": {}
},
"collapse-white-space@2.1.0": { "collapse-white-space@2.1.0": {
"integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==",
"dependencies": {} "dependencies": {}
@ -437,6 +454,10 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dependencies": {} "dependencies": {}
}, },
"fast-fifo@1.3.2": {
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"dependencies": {}
},
"fill-range@7.1.1": { "fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
@ -1183,6 +1204,10 @@
"integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
"dependencies": {} "dependencies": {}
}, },
"queue-tick@1.0.1": {
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
"dependencies": {}
},
"read-cache@1.0.0": { "read-cache@1.0.0": {
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dependencies": { "dependencies": {
@ -1461,6 +1486,14 @@
"unified": "unified@11.0.5" "unified": "unified@11.0.5"
} }
}, },
"remove-trailing-separator@1.1.0": {
"integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==",
"dependencies": {}
},
"replace-ext@2.0.0": {
"integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==",
"dependencies": {}
},
"resolve@1.22.8": { "resolve@1.22.8": {
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": { "dependencies": {
@ -1505,6 +1538,15 @@
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
"dependencies": {} "dependencies": {}
}, },
"streamx@2.18.0": {
"integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==",
"dependencies": {
"bare-events": "bare-events@2.4.2",
"fast-fifo": "fast-fifo@1.3.2",
"queue-tick": "queue-tick@1.0.1",
"text-decoder": "text-decoder@1.1.0"
}
},
"string-width@4.2.3": { "string-width@4.2.3": {
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": { "dependencies": {
@ -1564,6 +1606,18 @@
"picocolors": "picocolors@1.0.1" "picocolors": "picocolors@1.0.1"
} }
}, },
"teex@1.0.1": {
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"dependencies": {
"streamx": "streamx@2.18.0"
}
},
"text-decoder@1.1.0": {
"integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==",
"dependencies": {
"b4a": "b4a@1.6.6"
}
},
"tmp@0.2.3": { "tmp@0.2.3": {
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"dependencies": {} "dependencies": {}
@ -1688,6 +1742,16 @@
"vfile-message": "vfile-message@4.0.2" "vfile-message": "vfile-message@4.0.2"
} }
}, },
"vinyl@3.0.0": {
"integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==",
"dependencies": {
"clone": "clone@2.1.2",
"clone-stats": "clone-stats@1.0.0",
"remove-trailing-separator": "remove-trailing-separator@1.1.0",
"replace-ext": "replace-ext@2.0.0",
"teex": "teex@1.0.1"
}
},
"web-namespaces@2.0.1": { "web-namespaces@2.0.1": {
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
"dependencies": {} "dependencies": {}
@ -1725,23 +1789,12 @@
} }
}, },
"redirects": { "redirects": {
"https://esm.sh/asciidoctor@^3.0.4": "https://esm.sh/asciidoctor@3.0.4", "https://esm.sh/asciidoctor@^3.0.4": "https://esm.sh/asciidoctor@3.0.4"
"https://esm.sh/to-vfile@^8.0.0": "https://esm.sh/to-vfile@8.0.0",
"https://esm.sh/vfile@^6.0.1": "https://esm.sh/vfile@6.0.1"
}, },
"remote": { "remote": {
"https://esm.sh/asciidoctor@3.0.4": "23f6b6ab844b5295074b6dd139b0153f4b6424df7f41aaa2fcec5004359bb094", "https://esm.sh/asciidoctor@3.0.4": "23f6b6ab844b5295074b6dd139b0153f4b6424df7f41aaa2fcec5004359bb094",
"https://esm.sh/to-vfile@8.0.0": "05aa989433514d267833c5057b53935c837c1373824ed8b60ed38cae67655af5",
"https://esm.sh/v135/@asciidoctor/core@3.0.4/denonext/core.mjs": "fbb624c9375ac40a70e586d84f6986ff7e8c1ff572e006284a08709e5118bbf7", "https://esm.sh/v135/@asciidoctor/core@3.0.4/denonext/core.mjs": "fbb624c9375ac40a70e586d84f6986ff7e8c1ff572e006284a08709e5118bbf7",
"https://esm.sh/v135/asciidoctor@3.0.4/denonext/asciidoctor.mjs": "00c21f42422684a4bc5e35647d9495a6631632fdc4c97ddb045a1a3b46d58dba", "https://esm.sh/v135/asciidoctor@3.0.4/denonext/asciidoctor.mjs": "00c21f42422684a4bc5e35647d9495a6631632fdc4c97ddb045a1a3b46d58dba"
"https://esm.sh/v135/to-vfile@8.0.0/denonext/to-vfile.mjs": "e9e39c7791eb0ea7055105ffd3eb6ccf3d36a1a4ddf6c5a3824bc3838544ad40",
"https://esm.sh/v135/unist-util-stringify-position@4.0.0/denonext/unist-util-stringify-position.mjs": "dabd32cb2b590bbb077fc6f6591a2e065cffd6c55646ba383455926a27ea64d7",
"https://esm.sh/v135/vfile-message@4.0.2/denonext/vfile-message.mjs": "efc85b18bedda337fb1c20cdc452fac3addac32ee55948cebf2845396ae641ac",
"https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minpath.js": "9a7ca0443aa0dff4d6a74822ba54cd1a5077a75d2f740d4d99cd8897a7f62a3c",
"https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minproc.js": "34e683b50c7d1e17a90a522afafcdb032ceaeff2e0eb5dbca28f9b4783de73ba",
"https://esm.sh/v135/vfile@6.0.1/denonext/do-not-use-conditional-minurl.js": "79e90ed8915870371874d7ca17f8cbb3a8cab5b4e054bbcff85f7c91df32d0ba",
"https://esm.sh/v135/vfile@6.0.1/denonext/vfile.mjs": "31065c960cf79824afc0bc6890688c540f09d067eb5415effb7fafac6ef33c13",
"https://esm.sh/vfile@6.0.1": "a28180cc6d7bfad90237eddd9dbc8c840af0bc50e1220bb9b8dc670e96fe49aa"
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [
@ -1764,7 +1817,8 @@
"npm:rxjs@^7.8.1", "npm:rxjs@^7.8.1",
"npm:tmp@^0.2.3", "npm:tmp@^0.2.3",
"npm:unified@^11.0.4", "npm:unified@^11.0.4",
"npm:unist-util-visit@^5.0.0" "npm:unist-util-visit@^5.0.0",
"npm:vinyl@^3.0.0"
] ]
} }
} }

View File

@ -1,8 +1,8 @@
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import File from "vinyl";
import { PRODUCTION } from "./environment.ts"; import { PRODUCTION } from "./environment.ts";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { Buffer } from "node:buffer"; import { Buffer } from "node:buffer";
import { VFile } from "vfile";
function fileHash(buffer: Buffer) { function fileHash(buffer: Buffer) {
const hash = createHash("sha256"); const hash = createHash("sha256");
@ -10,22 +10,22 @@ function fileHash(buffer: Buffer) {
return hash.digest("hex"); return hash.digest("hex");
} }
const hashPath = (mappings: Map<string, string>) => (file: VFile): VFile => { const hashPath = (mappings: Map<string, string>) => (file: File): File => {
const hash = PRODUCTION ? fileHash(file.value) : "00000000"; const hash = PRODUCTION ? fileHash(file.contents as Buffer) : "00000000";
const newName = [ const newName = [
file.stem!, file.basename.substring(0, file.basename.length - file.extname.length),
hash.substring(0, 8), hash.substring(0, 8),
file.extname!.substring(1), file.extname.substring(1),
].join("."); ].join(".");
mappings.set(file.basename!, newName); mappings.set(file.basename, newName);
file.basename = newName; file.basename = newName;
return file; return file;
}; };
export default function (manifestName: string) { export default function (manifestName: string) {
return (observable: Observable<VFile>): Observable<VFile> => return (observable: Observable<File>): Observable<File> =>
new Observable((subscriber) => { new Observable((subscriber) => {
const mappings = new Map<string, string>(); const mappings = new Map<string, string>();
const hash = hashPath(mappings); const hash = hashPath(mappings);
@ -38,9 +38,9 @@ export default function (manifestName: string) {
subscriber.error(e); subscriber.error(e);
}, },
complete() { complete() {
const mappingFile = new VFile({ const mappingFile = new File({
path: manifestName, path: manifestName,
value: JSON.stringify(Object.fromEntries(mappings)), contents: Buffer.from(JSON.stringify(Object.fromEntries(mappings))),
}); });
subscriber.next(mappingFile); subscriber.next(mappingFile);

View File

@ -1,18 +1,18 @@
import { from, mergeMap, Observable, Subscriber } from "rxjs"; import { from, mergeMap, Observable, Subscriber } from "rxjs";
import File from "vinyl";
import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { existsSync } from "node:fs"; import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises"; import { mkdir, writeFile } from "node:fs/promises";
import { Glob } from "glob"; import { Glob } from "glob";
import { VFile } from "vfile";
import { read } from "to-vfile";
export function dest(prefix: string) { export function dest(prefix: string) {
return async (file: VFile) => { return async (file: File) => {
const actualPath = join(prefix, file.path); const actualPath = join(prefix, file.path);
if (!existsSync(dirname(actualPath))) { if (!existsSync(dirname(actualPath))) {
await mkdir(dirname(actualPath), { recursive: true }); await mkdir(dirname(actualPath), { recursive: true });
} }
await writeFile(actualPath, file.value); await writeFile(actualPath, file.contents as Buffer);
console.log("[-] Written", actualPath); console.log("[-] Written", actualPath);
return file; return file;
@ -37,11 +37,7 @@ export function onComplete<T>(f: (sink: Subscriber<T>) => Promise<void>) {
); );
} }
const loadFile = (path: string): Promise<VFile> => const loadFile = async (path: string): Promise<File> => new File({ path, contents: await readFile(path) });
read(join(Deno.cwd(), path)).then((vfile) => {
vfile.path = path;
return vfile;
});
export const fromGlob = (paths: string | string[]): Observable<VFile> => export const fromGlob = (paths: string | string[]): Observable<File> =>
from(new Glob(paths, {})).pipe(mergeMap(loadFile)); from(new Glob(paths, {})).pipe(mergeMap(loadFile));

View File

@ -1,5 +1,7 @@
import { readFileSync } from "node:fs";
import { basename, dirname, join } from "node:path"; import { basename, dirname, join } from "node:path";
import asciidoctor from "asciidoctor"; import asciidoctor from "asciidoctor";
import File from "vinyl";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { unified } from "unified"; import { unified } from "unified";
import rehypeParse from "rehype-parse"; import rehypeParse from "rehype-parse";
@ -18,11 +20,7 @@ import { JSX } from "preact/jsx-runtime";
import { reloadAssets } from "../assets.ts"; import { reloadAssets } from "../assets.ts";
import { lastValueFrom, mergeMap, Observable, Subscriber } from "rxjs"; import { lastValueFrom, mergeMap, Observable, Subscriber } from "rxjs";
import { dest, fromGlob, onComplete } from "../rx-utils.ts"; import { dest, fromGlob, onComplete } from "../rx-utils.ts";
import { VFile } from "vfile"; import { Buffer } from "node:buffer";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const DOCTYPE = encoder.encode("<!doctype html>");
const Asciidoctor = asciidoctor(); const Asciidoctor = asciidoctor();
const EXTENSION_REGISTRY = Asciidoctor.Extensions.create(); const EXTENSION_REGISTRY = Asciidoctor.Extensions.create();
@ -36,7 +34,7 @@ EXTENSION_REGISTRY.inlineMacro("abbr", function () {
}); });
}); });
function extractImages(sink: Subscriber<VFile>, articlePath: string) { function extractImages(sink: (image: File) => void, articlePath: string) {
return function () { return function () {
return function (tree: any) { return function (tree: any) {
visit(tree, "element", function (node: any) { visit(tree, "element", function (node: any) {
@ -45,30 +43,24 @@ function extractImages(sink: Subscriber<VFile>, articlePath: string) {
} }
const imagePath = join(dirname(articlePath)); const imagePath = join(dirname(articlePath));
const image = new VFile({ const image = new File({
path: join(basename(articlePath, ".asciidoc"), node.properties.src), path: join(basename(articlePath, ".asciidoc"), node.properties.src),
value: Deno.readFileSync(imagePath), contents: readFileSync(imagePath),
}); });
sink.next(image); sink(image);
}); });
}; };
}; };
} }
function renderDocument(root: JSX.Element): Uint8Array { function renderDocument(root: JSX.Element): Buffer {
const result = encoder.encode(renderToStaticMarkup(root)); return Buffer.concat([Buffer.from("<!doctype html>"), Buffer.from(renderToStaticMarkup(root))]);
const dest = new Uint8Array(DOCTYPE.length + result.length);
dest.set(DOCTYPE);
dest.set(result, DOCTYPE.length);
return dest;
} }
const transformArticle = (sink: Subscriber<VFile>, articles: Article[]) => async (file: VFile) => { const transformArticle = (sink: (file: File) => void, articles: Article[]) => async (file: File) => {
const slug = basename(file.path, ".asciidoc"); const slug = basename(file.path, ".asciidoc");
const document = Asciidoctor.load(decoder.decode(file.value as Uint8Array), { const document = Asciidoctor.load(file.contents!.toString(), {
extension_registry: EXTENSION_REGISTRY, extension_registry: EXTENSION_REGISTRY,
}); });
const date = DateTime.fromISO(document.getAttribute("docdate", { zone: "UTC" })); const date = DateTime.fromISO(document.getAttribute("docdate", { zone: "UTC" }));
@ -102,13 +94,13 @@ const transformArticle = (sink: Subscriber<VFile>, articles: Article[]) => async
}); });
file.path = join(slug, "index.html"); file.path = join(slug, "index.html");
file.value = renderDocument(content); file.contents = renderDocument(content);
articles.push(article); articles.push(article);
sink.next(file); sink(file);
}; };
const finalizeArticles = (articles: Article[]) => (sink: Subscriber<VFile>) => { const finalizeArticles = (articles: Article[]) => (sink: Subscriber<File>) => {
articles.sort(({ date: a }, { date: b }) => b.diff(a).toMillis()); articles.sort(({ date: a }, { date: b }) => b.diff(a).toMillis());
const contents = renderLayout({ const contents = renderLayout({
meta: { meta: {
@ -120,7 +112,7 @@ const finalizeArticles = (articles: Article[]) => (sink: Subscriber<VFile>) => {
Content: () => renderIndex({ articles }), Content: () => renderIndex({ articles }),
}); });
sink.next(new VFile({ path: "index.html", value: renderDocument(contents) })); sink.next(new File({ path: "index.html", contents: renderDocument(contents) }));
return Promise.resolve(); return Promise.resolve();
}; };
@ -132,9 +124,9 @@ export const articles = async () => {
fromGlob("articles/**/*.asciidoc").pipe( fromGlob("articles/**/*.asciidoc").pipe(
mergeMap( mergeMap(
(file) => (file) =>
new Observable<VFile>((subscriber) => { new Observable<File>((subscriber) => {
transformArticle( transformArticle(
subscriber, (f) => subscriber.next(f),
articles, articles,
)(file).then(() => subscriber.complete()); )(file).then(() => subscriber.complete());
}), }),

View File

@ -3,20 +3,19 @@ import config from "../../postcss.config.ts";
import { dest, fromGlob } from "../rx-utils.ts"; import { dest, fromGlob } from "../rx-utils.ts";
import { lastValueFrom, mergeMap } from "rxjs"; import { lastValueFrom, mergeMap } from "rxjs";
import hashPaths from "../hash.ts"; import hashPaths from "../hash.ts";
import { VFile } from "vfile"; import File from "vinyl";
import { Buffer } from "node:buffer";
export const css = async () => { export const css = async () => {
await lastValueFrom( await lastValueFrom(
fromGlob("src/index.css").pipe( fromGlob("src/index.css").pipe(
mergeMap( mergeMap(
(file) => (file) =>
new Promise<VFile>((resolve) => new Promise<File>((resolve) =>
postcss(config.plugins) postcss(config.plugins)
.process(file.value, { from: file.path }) .process(file.contents, { from: file.path })
.then((result: Result) => { .then((result: Result) => {
const encoder = new TextEncoder(); file.contents = Buffer.from(result.css);
file.value = encoder.encode(result.css);
file.path = "index.css"; file.path = "index.css";
resolve(file); resolve(file);

View File

@ -1,33 +1,39 @@
import File from "vinyl";
import tmp from "tmp"; import tmp from "tmp";
import { execFile } from "node:child_process";
import { readFileSync, unlinkSync } from "node:fs";
import hashPaths from "../hash.ts"; import hashPaths from "../hash.ts";
import { dest, fromGlob } from "../rx-utils.ts"; import { dest, fromGlob } from "../rx-utils.ts";
import { lastValueFrom, mergeMap } from "rxjs"; import { mergeMap } from "rxjs";
import { VFile } from "vfile";
const FONT_PRESETS: { [variant: string]: { ranges: string[] } } = { const FONT_PRESETS: { [variant: string]: { ranges: string[] } } = {
mono: { ranges: ["20-7F", "2205", "2E22-2E25", "2713", "2717"] }, mono: { ranges: ["20-7F", "2205", "2E22-2E25", "2713", "2717"] },
text: { ranges: ["20-7F", "A0-FF", "2000-206F", "20AC"] }, text: { ranges: ["20-7F", "A0-FF", "2000-206F", "20AC"] },
}; };
async function compileFont(font: VFile): Promise<VFile> { function compileFont(font: File): Promise<File> {
const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(font.basename!) as string[]; const [, variant, weight] = /([A-Z][a-z]+)-(\w+)\.ttf$/.exec(font.basename) as string[];
const tmpOutput = tmp.fileSync({ discardDescriptor: true }); const tmpOutput = tmp.fileSync({ discardDescriptor: true });
const unicodes = FONT_PRESETS[variant.toLowerCase()].ranges; const unicodes = FONT_PRESETS[variant.toLowerCase()].ranges;
await new Deno.Command("pyftsubset", { return new Promise((resolve) => {
args: [font.path, `--unicodes=${unicodes.join(",")}`, `--output-file=${tmpOutput.name}`, "--flavor=woff2"], execFile("pyftsubset", [
}).output(); font.path,
`--unicodes=${unicodes.join(",")}`,
`--output-file=${tmpOutput.name}`,
"--flavor=woff2",
]).once("exit", () => {
font.path = `iosevka-adaedra-${variant.toLowerCase()}-${weight.toLowerCase()}.woff2`; font.path = `iosevka-adaedra-${variant.toLowerCase()}-${weight.toLowerCase()}.woff2`;
font.value = await Deno.readFile(tmpOutput.name); font.contents = readFileSync(tmpOutput.name);
(font as any).base = null;
await Deno.remove(tmpOutput.name); unlinkSync(tmpOutput.name);
return font; resolve(font);
});
});
} }
export const fonts = async () => { export const fonts = () =>
await lastValueFrom(
fromGlob("vendor/*.ttf") fromGlob("vendor/*.ttf")
.pipe(mergeMap(compileFont), hashPaths("fonts.manifest"), mergeMap(dest("dist/_assets"))), .pipe(mergeMap(compileFont), hashPaths("fonts.manifest"))
); .forEach(dest("dist/_assets"));
};

View File

@ -5,7 +5,7 @@ import { tap } from "rxjs";
export const images = () => export const images = () =>
fromGlob("src/*.avif") fromGlob("src/*.avif")
.pipe( .pipe(
tap((f) => (f.path = f.basename!)), tap((f) => (f.path = f.path.substring(4))),
hashPaths("images.manifest"), hashPaths("images.manifest"),
) )
.forEach(dest("dist/_assets")); .forEach(dest("dist/_assets"));

View File

@ -4,5 +4,5 @@ import { dest, fromGlob } from "../rx-utils.ts";
// SVG `use` has no way of allowing cross-origin, so we need to keep them with the HTML files. // SVG `use` has no way of allowing cross-origin, so we need to keep them with the HTML files.
export const svg = () => export const svg = () =>
fromGlob("src/*.svg") fromGlob("src/*.svg")
.pipe(tap((f) => (f.path = f.basename!))) .pipe(tap((f) => (f.path = f.path.substring(4))))
.forEach(dest("dist")); .forEach(dest("dist"));