From 80c596b3b57aaed12ad1f1ec5f2bf33e22237b14 Mon Sep 17 00:00:00 2001 From: Gold Okpa Date: Sat, 22 Aug 2026 15:16:30 +0100 Subject: [PATCH] ci(docs): add static Cloudflare Pages build --- docs-site/package-lock.json | 28 +++++++++++ docs-site/package.json | 12 +++++ docs-site/scripts/build.mjs | 96 +++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 docs-site/package-lock.json create mode 100644 docs-site/package.json create mode 100644 docs-site/scripts/build.mjs diff --git a/docs-site/package-lock.json b/docs-site/package-lock.json new file mode 100644 index 00000000..88209114 --- /dev/null +++ b/docs-site/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "identark-docs", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "identark-docs", + "version": "0.1.0", + "devDependencies": { + "marked": "^15.0.6" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + } + } +} diff --git a/docs-site/package.json b/docs-site/package.json new file mode 100644 index 00000000..ca67cad1 --- /dev/null +++ b/docs-site/package.json @@ -0,0 +1,12 @@ +{ + "name": "identark-docs", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "build": "node scripts/build.mjs" + }, + "devDependencies": { + "marked": "^15.0.6" + } +} diff --git a/docs-site/scripts/build.mjs b/docs-site/scripts/build.mjs new file mode 100644 index 00000000..5b703fbe --- /dev/null +++ b/docs-site/scripts/build.mjs @@ -0,0 +1,96 @@ +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { marked } from "marked"; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); +const output = join(root, "site"); +const config = JSON.parse(await readFile(join(root, "docs.json"), "utf8")); + +function pageUrl(page) { + return page === "introduction" ? "/" : `/${page}/`; +} + +function titleFromPage(page) { + return page.split("/").at(-1).replace(/-/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()); +} + +function pagesFromNavigation() { + return config.navigation.tabs.flatMap((tab) => + tab.groups.flatMap((group) => (group.pages ?? []).map((page) => ({ ...group, page }))), + ); +} + +function parseDocument(source) { + const match = source.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/); + if (!match) return { title: "IdentArk", description: "", body: source }; + const title = match[1].match(/^title:\s*["']?(.*?)["']?\s*$/m)?.[1] ?? "IdentArk"; + const description = match[1].match(/^description:\s*["']?(.*?)["']?\s*$/m)?.[1] ?? ""; + return { title, description, body: match[2] }; +} + +function escapeHtml(value) { + return value.replace(/[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character]); +} + +function transformMintlifyMdx(source) { + return source + .replace(/]*>/g, "") + .replace(/<\/CodeGroup>/g, "") + .replace(/]*>/g, '
') + .replace(/<\/CardGroup>/g, "
") + .replace(/]*href="([^"]+)"[^>]*>/g, '

$1

') + .replace(/]*>/g, '

$1

') + .replace(/<\/Card>/g, "
") + .replace(/<(Note|Tip|Info|Warning)>/g, (_, kind) => `") + .replace(//g, '
') + .replace(/<\/Steps>/g, "
") + .replace(/]*>/g, '

$1

') + .replace(/<\/Step>/g, "
") + .replace(/]*)\/>/g, (_, attributes) => { + const name = attributes.match(/name="([^"]+)"/)?.[1] ?? "Parameter"; + const type = attributes.match(/type="([^"]+)"/)?.[1]; + return `
${name}${type ? ` ${type}` : ""}
`; + }); +} + +function sidebar(activePage) { + return config.navigation.tabs.map((tab) => ` + `).join(""); +} + +function layout({ page, title, description, content }) { + const navbar = (config.navbar.links ?? []).map((link) => `${escapeHtml(link.label)}`).join(""); + return ` + +${escapeHtml(title)} | IdentArk + +
IdentArk
+
${content}
`; +} + +await rm(output, { recursive: true, force: true }); +await mkdir(output, { recursive: true }); +await cp(join(root, "images"), join(output, "images"), { recursive: true }); +await cp(join(root, "favicon.svg"), join(output, "favicon.svg")); + +for (const { page } of pagesFromNavigation()) { + const source = await readFile(join(root, `${page}.mdx`), "utf8"); + const document = parseDocument(source); + const html = marked.parse(transformMintlifyMdx(document.body)); + const destination = page === "introduction" ? join(output, "index.html") : join(output, page, "index.html"); + await mkdir(dirname(destination), { recursive: true }); + await writeFile(destination, layout({ page, ...document, content: html })); +} + +const spec = JSON.parse(await readFile(join(root, "api-reference", "openapi.json"), "utf8")); +const endpoints = Object.entries(spec.paths ?? {}).flatMap(([path, methods]) => Object.keys(methods).map((method) => `
  • ${method.toUpperCase()} ${escapeHtml(path)}
  • `)).join(""); +const apiDestination = join(output, "api-reference", "endpoints", "index.html"); +await mkdir(dirname(apiDestination), { recursive: true }); +await writeFile(apiDestination, layout({ page: "api-reference/introduction", title: "API endpoints", description: "IdentArk public API endpoints.", content: `

    API endpoints

    Generated from the public IdentArk OpenAPI contract.

      ${endpoints}
    ` })); + +console.log(`Built ${pagesFromNavigation().length} documentation pages in ${output}`);