From 92de3438eb32c98914737971665b20fa14d66f7a Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Mon, 10 Jun 2024 01:22:08 -0400 Subject: [PATCH] feat: serve versioned matcha (#32) --- README.md | 5 +++- app/build/ssg.ts | 71 +++++++++++++++++++++++++++++++++++++++++++++++- app/mod.html | 6 +++- app/mod.ts | 5 +++- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4dfa32a..f46e657 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,10 @@ To utilize **matcha.css**, just include the following line in the `` secti ``` Assets are hosted on [Vercel](https://vercel.com) but _matcha.css_ is also available on -[![npm](https://img.shields.io/npm/v/@lowlighter%2Fmatcha?logo=npm&labelColor=cb0000&color=black)](https://www.npmjs.com/package/@lowlighter/matcha) and CDN services that distributes npm packages. +[![npm](https://img.shields.io/npm/v/@lowlighter%2Fmatcha?logo=npm&labelColor=cb0000&color=black)](https://www.npmjs.com/package/@lowlighter/matcha) and CDN services that distributes npm packages such +as [JSdelivr](https://www.jsdelivr.com/package/npm/@lowlighter/matcha). + +All published versions are available in the [`/v/`](https://matcha.mizu.sh/v/) directory. By default, the `main` branch is served. ### 🍴 À la carte diff --git a/app/build/ssg.ts b/app/build/ssg.ts index 7281daa..de5626d 100644 --- a/app/build/ssg.ts +++ b/app/build/ssg.ts @@ -1,9 +1,10 @@ // Imports import { copy, emptyDir, ensureDir, expandGlob } from "jsr:@std/fs@0.229.1" -import { dirname, fromFileUrl } from "jsr:@std/path@0.225.1" +import { basename, dirname, fromFileUrl } from "jsr:@std/path@0.225.1" import { root } from "./root.ts" import { html, html_builder, html_builder_demo } from "./html.ts" import { compatibility } from "jsr:@libs/bundle@5/css" +import { DOMParser } from " https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm.ts" /** Highlight.js CDN */ export const highlight = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" @@ -24,9 +25,13 @@ export async function ssg() { console.log("Created .pages/mod.css") // Generate CSS const dist = fromFileUrl(new URL("dist", root)).replaceAll("\\", "/") + await ensureDir(new URL(".pages/v/main", root)) + console.log("Created .pages/v/main") for await (const { path, name } of expandGlob("**/*.css", { root: dist })) { await copy(path, new URL(`.pages/${name}`, root)) console.log(`Created .pages/${name}`) + await copy(path, new URL(`.pages/v/main/${name}`, root)) + console.log(`Created .pages/v/main/${name}`) } // Generate compatibility table const table = await compatibility(new URL(".pages/matcha.css", root), { output: "html", style: false, verbose: true }) @@ -53,10 +58,74 @@ export async function ssg() { const alias = `${dirname(subpath)}.css` await copy(path, new URL(`.pages/${alias}`, root)) console.log(`Created .pages/${alias}`) + await copy(path, new URL(`.pages/v/main/${alias}`, root)) + console.log(`Created .pages/v/main/${alias}`) } } + await copy(new URL(".pages/styles", root), new URL(".pages/v/main/styles", root)) + console.log("Created .pages/v/main/styles") // Download highlight.js await Deno.writeTextFile(new URL(".pages/highlight.js", root), await fetch(highlight).then((response) => response.text())) console.log("Created .pages/highlight.js") + // Download previous versions + const { tags: { latest }, versions } = await fetch("https://data.jsdelivr.com/v1/package/npm/@lowlighter/matcha").then((response) => response.json()) + for (const version of versions.reverse()) { + await ensureDir(new URL(`.pages/v/${version}`, root)) + console.log(`Created .pages/v/${version}`) + const url = `https://cdn.jsdelivr.net/npm/@lowlighter/matcha@${version}` + const dist = Array.from( + new DOMParser().parseFromString(await fetch(`${url}/dist/`).then((response) => response.text()), "text/html")!.querySelectorAll(`.listing a[href^="/npm/@lowlighter/matcha@${version}"]`), + ) + await Promise.all(dist.map(async (_file) => { + const file = _file as unknown as HTMLAnchorElement + const href = file.getAttribute("href")! + await Deno.writeTextFile(new URL(`.pages/v/${version}/${basename(href)}`, root), await fetch(new URL(href, url)).then((response) => response.text())) + console.log(`Created .pages/v/${version}/${basename(href)}`) + })) + const styles = Array.from( + new DOMParser().parseFromString(await fetch(`${url}/styles/`).then((response) => response.text()), "text/html")!.querySelectorAll(`.listing a[href^="/npm/@lowlighter/matcha@${version}"]`), + ) + await Promise.all(styles.map(async (_directory) => { + const directory = _directory as unknown as HTMLAnchorElement + const href = directory.getAttribute("href")! + const files = Array.from( + new DOMParser().parseFromString(await fetch(new URL(href, url)).then((response) => response.text()), "text/html")!.querySelectorAll(`.listing a[href^="/npm/@lowlighter/matcha@${version}"]`), + ) + await Promise.all(Array.from(files.map(async (_file) => { + const file = _file as unknown as HTMLAnchorElement + const href = file.getAttribute("href")! + if (!href.endsWith(".css")) { + return + } + const subpath = `styles/${basename(directory.getAttribute("href")!)}/${basename(href)}` + const content = await fetch(new URL(href, url)).then((response) => response.text()) + await ensureDir(new URL(`.pages/v/${version}/${dirname(subpath)}`, root)) + await Deno.writeTextFile(new URL(`.pages/v/${version}/${subpath}`, root), content) + console.log(`Created .pages/v/${version}/${subpath}`) + if (basename(href) === "mod.css") { + await Deno.writeTextFile(new URL(`.pages/v/${version}/${basename(dirname(subpath))}.css`, root), content) + console.log(`Created .pages/v/${version}/${basename(dirname(subpath))}.css`) + } + }))) + })) + } + await copy(new URL(`.pages/v/${latest}`, root), new URL(".pages/v/latest", root)) + console.log(`Created .pages/v/latest from version ${latest}`) + for (const major of new Set(versions.map((version: string) => version.split(".")[0]))) { + if (major === "0") { + continue + } + const version = versions.reverse().filter((version: string) => version.startsWith(`${major}.`))[0] + await copy(new URL(`.pages/v/${version}`, root), new URL(`.pages/v/${major}`, root)) + console.log(`Created .pages/v/${major} from version ${version}`) + } + for (const minor of new Set(versions.map((version: string) => `${version.split(".")[0]}.${version.split(".")[1]}`))) { + if (minor.startsWith("0.")) { + continue + } + const version = versions.reverse().filter((version: string) => version.startsWith(`${minor}.`))[0] + await copy(new URL(`.pages/v/${version}`, root), new URL(`.pages/v/${minor}`, root)) + console.log(`Created .pages/v/${minor} from version ${version}`) + } console.log("Done!") } diff --git a/app/mod.html b/app/mod.html index 7151e6f..5723a0a 100644 --- a/app/mod.html +++ b/app/mod.html @@ -110,7 +110,11 @@

Usage

<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css">

Assets are hosted on Vercel but matcha.css is also available on npm - and CDN services that distributes npm packages. + and CDN services that distributes npm packages such as JSdelivr. +

+

+ All published versions are available in the /v/ directory. + By default, the main branch is served.

À la carte

diff --git a/app/mod.ts b/app/mod.ts index 122397f..be0b70f 100644 --- a/app/mod.ts +++ b/app/mod.ts @@ -4,10 +4,11 @@ import { css } from "./build/css.ts" import { html, html_builder, html_builder_demo } from "./build/html.ts" import { highlight, ssg } from "./build/ssg.ts" import { dist } from "./build/dist.ts" -import { STATUS_CODE, STATUS_TEXT } from "jsr:@std/http@0.224.1" +import { serveDir, STATUS_CODE, STATUS_TEXT } from "jsr:@std/http@0.224.1" import { root } from "./build/root.ts" import api_minify from "../api/brew.ts" import api_preview from "../api/preview.ts" +import { fromFileUrl } from "jsr:@std/path" // Serve files switch (Deno.args[0]) { @@ -40,6 +41,8 @@ switch (Deno.args[0]) { return api_preview(request) case new URLPattern("/highlight.js", url.origin).test(url.href): return fetch(highlight) + case new URLPattern("/v/*", url.origin).test(url.href): + return serveDir(request, { fsRoot: fromFileUrl(new URL(".pages/v", root)), urlRoot: "v", showDirListing: true, quiet: true }) default: return new Response(STATUS_TEXT[STATUS_CODE.NotFound], { status: STATUS_CODE.NotFound }) }