Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465import {revisionSummary} from './revision-summary.mjs';// Complete piece snapshots stay on this machine; rollback appends, never erases.import { createHash, randomUUID } from "node:crypto";import { mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";import { historyDir } from "./paths.mjs";import { extname, join, resolve } from "node:path";
const digest = (source) => createHash("sha256").update(source).digest("hex");
// Parse JavaScript without importing it: user code must never execute in aesel.export async function validatePieceSource(source, file) { if (typeof source !== "string" || !source.trim()) throw new Error("The piece is empty."); if (extname(file) !== ".mjs") return; // Other runtimes retain their own loader validation. // Bun does not implement Node's stdin --check contract. Use the bundled // parser on both runtimes; parsing neither imports nor executes the piece. const { parse } = await import("./vendor/acorn.mjs"); try { parse(source, { ecmaVersion: "latest", sourceType: "module" }); } catch (error) { throw new Error(`Incomplete or invalid JavaScript; previous preview kept. ${error.message}`); }}
export class PieceRevisions { constructor(file, { root = historyDir() } = {}) { this.file = resolve(file); this.directory = join(root, digest(this.file)); } list() { let names; try { names = readdirSync(this.directory); } catch (error) { if (error.code === "ENOENT") return []; throw error; } const entries=names.filter((name) => /^v\d+\.json$/.test(name)).map((name) => JSON.parse(readFileSync(join(this.directory, name), "utf8"))) .sort((a, b) => a.version - b.version); return entries.map((entry,index)=>({...entry,summary:entry.summary||revisionSummary(entries[index-1]?.source,entry.source,entry)})); } capture(source, { restoredFrom } = {}) { const entries = this.list(); const revision = digest(source); const previous = entries.at(-1); if (previous?.revision === revision) return previous; const entry = { version: previous ? previous.version + 1 : 0, revision, updatedAt: new Date().toISOString(), source, summary:revisionSummary(previous?.source,source,{restoredFrom}), ...(restoredFrom !== undefined ? { restoredFrom } : {}) }; mkdirSync(this.directory, { recursive: true, mode: 0o700 }); // Exclusive final creation prevents two sessions silently overwriting a version. writeFileSync(join(this.directory, `v${entry.version}.json`), `${JSON.stringify(entry)}\n`, { flag: "wx", mode: 0o600 }); return entry; } annotate(version,summary,expectedRevision) { const file=join(this.directory,`v${Number(version)}.json`); const entry=JSON.parse(readFileSync(file,'utf8')); if(entry.revision!==expectedRevision)return false; const clean=String(summary).replace(/https?:\/\/\S+/g,'').replace(/[`*_#]/g,'').replace(/\s+/g,' ').trim().slice(0,160); if(!clean)return false; const temporary=file+'.'+randomUUID()+'.tmp'; writeFileSync(temporary,JSON.stringify({...entry,summary:clean})+'\n',{flag:'wx',mode:0o600});renameSync(temporary,file);return true; } async restore(version) { const entry = this.list().find((item) => item.version === Number(version)); if (!entry) throw new Error(`No saved v${version} for this piece.`); const before = readFileSync(this.file, "utf8"); await validatePieceSource(entry.source, this.file); if (readFileSync(this.file, "utf8") !== before) throw new Error("The piece changed while preparing rollback. Try again when editing stops."); const temporary = `${this.file}.${randomUUID()}.tmp`; writeFileSync(temporary, entry.source); renameSync(temporary, this.file); return this.capture(entry.source, { restoredFrom: entry.version }); }}