104 lines · 3.9 KB · TypeScript
RawDownload
import { mkdtemp, readFile, rm, mkdir, stat, readdir, copyFile } from "node:fs/promises"import path from "node:path"import os from "node:os"import { $ } from "bun"import { runtimeRegexPass, classifierInjectionRegexPass, type Warning } from "./review"import type { SkillEntry } from "./fetcher" interface FetchPinnedParams {  repo_url: string  pinned_sha: string  namespace: string  skillName: string  destDir: string} /** Sync-time git fetch: clone the pinned SHA, copy one skill's tree into *  destDir, run Layers 1 & 2 regex as paranoid defense-in-depth (the SHA *  is content-addressed so contents are immutable, but if they ever match *  a Tier-1 pattern we want to refuse — could mean the original install's *  Layer-3 was bypassed somehow). * *  Skips Layer 3 — the first install's classifier verdict is authoritative. *  Server-side `archived_at` is the kill-switch: an archived row simply *  doesn't appear in fetchInstalledSkills, so sync never even tries. */export async function gitFetchPinned(params: FetchPinnedParams): Promise<void> {  const tmpDir = await mkdtemp(path.join(os.tmpdir(), "openscience-skill-sync-"))  try {    // Clone full repo at default branch, then checkout the pinned SHA.    // Shallow `--depth 1` won't work because we need an arbitrary SHA.    const clone = await $`git clone --quiet ${params.repo_url} ${tmpDir}`      .env({        ...process.env,        GIT_TERMINAL_PROMPT: "0",        GCM_INTERACTIVE: "Never",      })      .quiet()      .nothrow()    if (clone.exitCode !== 0) {      throw new Error(`git clone failed: ${clone.stderr.toString()}`)    }    const checkout = await $`git checkout --quiet ${params.pinned_sha}`.cwd(tmpDir).quiet().nothrow()    if (checkout.exitCode !== 0) {      throw new Error(`pinned SHA ${params.pinned_sha} unreachable: ${checkout.stderr.toString()}`)    }     const srcSkill = path.join(tmpDir, "skills", params.skillName)    const srcSkillMd = path.join(srcSkill, "SKILL.md")    try {      await stat(srcSkillMd)    } catch {      throw new Error(`skill ${params.namespace}/${params.skillName} not found in repo at this SHA`)    }     // Paranoid local defense-in-depth (cheap, fast)    const content = await readFile(srcSkillMd, "utf-8")    const fakeEntry: SkillEntry = {      namespace: params.namespace,      name: params.skillName,      description: "",      content,      scripts: [],      references: [],    }    const l1 = runtimeRegexPass([fakeEntry])    if (l1.rejected.length > 0) {      throw new Error(`Layer-1 reject on sync: ${l1.rejected[0]!.reason}`)    }    const l2 = classifierInjectionRegexPass([fakeEntry])    if (l2.rejected.length > 0) {      throw new Error(`Layer-2 reject on sync: ${l2.rejected[0]!.reason}`)    }     // Copy the skill directory tree to destDir    await mkdir(params.destDir, { recursive: true })    await copyTree(srcSkill, params.destDir)     // Also copy the namespace's openscience-skills.json (entries manifest) if    // present. This is per-namespace, not per-skill — only the first sync    // per namespace needs to write it.    const nsDir = path.dirname(path.dirname(params.destDir))    const nsManifest = path.join(nsDir, "openscience-skills.json")    const upstreamManifest = path.join(tmpDir, "openscience-skills.json")    if ((await Bun.file(upstreamManifest).exists()) && !(await Bun.file(nsManifest).exists())) {      await copyFile(upstreamManifest, nsManifest)    }  } finally {    await rm(tmpDir, { recursive: true, force: true }).catch(() => {})  }} async function copyTree(src: string, dest: string): Promise<void> {  const entries = await readdir(src, { withFileTypes: true })  await mkdir(dest, { recursive: true })  for (const entry of entries) {    const s = path.join(src, entry.name)    const d = path.join(dest, entry.name)    if (entry.isDirectory()) {      await copyTree(s, d)    } else if (entry.isFile()) {      await copyFile(s, d)    }  }}