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}