scripts/workspace.mjs
scripts/workspace.mjsBrowse 22 files
946 tokens
3,707 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env node2/**3 * Resolve the scrollcraft workspace: the one directory that holds this user's4 * builds and their fingerprint registry.5 *6 * The skill used to hardcode one author's repo layout, which meant the7 * fingerprint gate dead-ended on every other machine. Nothing is hardcoded now.8 * Resolution order, first hit wins:9 *10 * 1. SCROLLCRAFT_HOME env var, absolute or relative to cwd11 * 2. .scrollcraft.json nearest one walking up from cwd,12 * { "workspace": "some/path" } relative to13 * the file that declares it14 * 3. <project root>/scrollcraft project root = nearest ancestor with .git,15 * otherwise cwd16 *17 * USAGE18 * node scripts/workspace.mjs print the workspace path19 * node scripts/workspace.mjs --json print every resolved path20 * node scripts/workspace.mjs --ensure create it and seed the registry21 */22 23import fs from "node:fs";24import path from "node:path";25import { fileURLToPath } from "node:url";26 27const HERE = path.dirname(fileURLToPath(import.meta.url));28const SKILL = path.resolve(HERE, "..");29 30function findUp(startDir, predicate) {31 let dir = path.resolve(startDir);32 for (let i = 0; i < 12; i++) {33 const hit = predicate(dir);34 if (hit) return { dir, hit };35 const up = path.dirname(dir);36 if (up === dir) break;37 dir = up;38 }39 return null;40}41 42export function resolveWorkspace(cwd = process.cwd()) {43 if (process.env.SCROLLCRAFT_HOME) {44 return { root: path.resolve(cwd, process.env.SCROLLCRAFT_HOME), via: "SCROLLCRAFT_HOME" };45 }46 47 const cfg = findUp(cwd, (d) => {48 const p = path.join(d, ".scrollcraft.json");49 return fs.existsSync(p) ? p : null;50 });51 if (cfg) {52 let parsed = {};53 try {54 parsed = JSON.parse(fs.readFileSync(cfg.hit, "utf8"));55 } catch (e) {56 throw new Error(`.scrollcraft.json is not valid JSON: ${cfg.hit}\n ${e.message}`);57 }58 if (parsed.workspace) {59 return { root: path.resolve(cfg.dir, parsed.workspace), via: cfg.hit };60 }61 }62 63 const git = findUp(cwd, (d) => (fs.existsSync(path.join(d, ".git")) ? d : null));64 const base = git ? git.hit : path.resolve(cwd);65 return { root: path.join(base, "scrollcraft"), via: git ? "project root (.git)" : "cwd" };66}67 68export function paths(cwd = process.cwd()) {69 const { root, via } = resolveWorkspace(cwd);70 return {71 via,72 workspace: root,73 builds: path.join(root, "builds"),74 lab: path.join(root, "lab"),75 fingerprints: path.join(root, "FINGERPRINTS.md"),76 };77}78 79export function ensure(cwd = process.cwd()) {80 const p = paths(cwd);81 fs.mkdirSync(p.builds, { recursive: true });82 fs.mkdirSync(p.lab, { recursive: true });83 let seeded = false;84 if (!fs.existsSync(p.fingerprints)) {85 const seed = path.join(SKILL, "templates", "FINGERPRINTS.md");86 fs.copyFileSync(seed, p.fingerprints);87 seeded = true;88 }89 return { ...p, seeded };90}91 92// ------------------------------------------------------------------- cli ----93if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {94 const argv = process.argv.slice(2);95 const p = argv.includes("--ensure") ? ensure() : paths();96 if (argv.includes("--json")) {97 console.log(JSON.stringify(p, null, 2));98 } else if (argv.includes("--ensure")) {99 console.log(p.workspace);100 console.error(` builds ${p.builds}`);101 console.error(` registry ${p.fingerprints}${p.seeded ? " (seeded, empty)" : " (already present)"}`);102 console.error(` resolved via ${p.via}`);103 } else {104 console.log(p.workspace);105 }106}107