kit.ts
4.3 kB · typescript · 124 lines
1import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";2import { dirname, join, resolve } from "node:path";34const org = resolve(import.meta.dir, "..");5const home = join(org, "data", "kit");6const lock = join(org, "kit.lock");7const REPO = "mrlyprod/mrlyprod";8const KEEP = "sites/kit/";9const HEAD = `https://api.github.com/repos/${REPO}/commits/main`;1011/* TAR */1213const text = (data: Uint8Array, from: number, to: number) => {14 const end = data.indexOf(0, from);15 return new TextDecoder().decode(data.subarray(from, end < 0 || end > to ? to : end));16};1718const octal = (data: Uint8Array, from: number, to: number) => parseInt(text(data, from, to).trim() || "0", 8);1920function pax(block: Uint8Array) {21 const out: Record<string, string> = {};22 for (const line of new TextDecoder().decode(block).split("\n")) {23 const m = line.match(/^\d+ ([^=]+)=(.*)$/);24 if (m) out[m[1]] = m[2];25 }26 return out;27}2829function untar(tar: Uint8Array, into: string) {30 let at = 0;31 let next: Record<string, string> = {};32 let files = 0;33 while (at + 512 <= tar.length) {34 const head = tar.subarray(at, at + 512);35 if (head.every((b) => b === 0)) break;36 const size = octal(head, 124, 136);37 const kind = String.fromCharCode(head[156]);38 const prefix = text(head, 345, 500);39 let name = next.path ?? (prefix ? `${prefix}/${text(head, 0, 100)}` : text(head, 0, 100));40 const body = tar.subarray(at + 512, at + 512 + size);41 at += 512 + Math.ceil(size / 512) * 512;42 if (kind === "x") {43 next = pax(body);44 continue;45 }46 next = {};47 if (kind === "g" || kind === "L") continue;48 name = name.replace(/^[^/]+\//, "");49 if (!name.startsWith(KEEP)) continue;50 const path = join(into, name.slice(KEEP.length));51 if (kind === "5" || name.endsWith("/")) mkdirSync(path, { recursive: true });52 else if (kind === "0" || kind === "\0") {53 mkdirSync(dirname(path), { recursive: true });54 writeFileSync(path, body);55 files++;56 }57 }58 return files;59}6061/* FETCH */6263const sha = () => readFileSync(lock, "utf8").trim();6465async function head() {66 const reply = await fetch(HEAD, { headers: { accept: "application/vnd.github+json" } });67 if (!reply.ok) throw new Error(`HTTP ${reply.status}`);68 return ((await reply.json()) as { sha: string }).sha;69}7071async function pull(at: string) {72 const reply = await fetch(`https://codeload.github.com/${REPO}/tar.gz/${at}`);73 if (!reply.ok) throw new Error(`HTTP ${reply.status}`);74 const tar = Bun.gunzipSync(new Uint8Array(await reply.arrayBuffer()));75 const fresh = join(org, "data", "kit.next");76 rmSync(fresh, { recursive: true, force: true });77 const files = untar(tar, fresh);78 if (!existsSync(join(fresh, "ui", "chrome.jsx"))) throw new Error(`${at} carries no sites/kit/ui`);79 rmSync(home, { recursive: true, force: true });80 renameSync(fresh, home);81 return files;82}8384/* DEPS */8586function deps() {87 if (!existsSync(join(home, "package.json"))) return;88 const done = Bun.spawnSync([process.execPath, "install", "--production", "--frozen-lockfile"], { cwd: home, stdout: "pipe", stderr: "pipe" });89 if (done.exitCode !== 0) throw new Error(`kit: bun install in ${home} failed\n${done.stderr.toString().trim().slice(-500)}`);90}9192/* KIT */9394export async function kit(): Promise<string> {95 const local = process.env.KIT;96 if (local) {97 const from = resolve(local);98 if (!existsSync(join(from, "ui", "chrome.jsx"))) throw new Error(`kit: KIT=${from} holds no ui/chrome.jsx`);99 rmSync(home, { recursive: true, force: true });100 mkdirSync(dirname(home), { recursive: true });101 cpSync(from, home, { recursive: true });102 deps();103 console.log(`kit: copied from ${from}`);104 return home;105 }106 try {107 const files = await pull(sha());108 console.log(`kit: fetched ${files} files at ${sha().slice(0, 8)}`);109 } catch (reason) {110 if (!existsSync(join(home, "ui", "chrome.jsx"))) throw new Error(`kit: fetch failed (${reason}) and no cache at ${home}; set KIT to a local checkout`);111 console.warn(`kit: fetch failed (${reason}), building from the cached copy`);112 }113 deps();114 return home;115}116117if (import.meta.main) {118 if (!process.argv.includes("--hold")) {119 const at = await head();120 writeFileSync(lock, `${at}\n`);121 console.log(`kit: kit.lock now ${at}`);122 }123 await kit();124}