@hyperframes/studio-server 0.8.82 → 0.8.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/{chunk-G6IIFKXY.js → chunk-2YEGCDJW.js} +39 -20
  2. package/dist/chunk-2YEGCDJW.js.map +1 -0
  3. package/dist/{chunk-TJQ76IEA.js → chunk-I3DRHG52.js} +23 -71
  4. package/dist/chunk-I3DRHG52.js.map +1 -0
  5. package/dist/chunk-IG4DRZI2.js +1571 -0
  6. package/dist/chunk-IG4DRZI2.js.map +1 -0
  7. package/dist/chunk-PMXUKOC3.js +22 -0
  8. package/dist/chunk-PMXUKOC3.js.map +1 -0
  9. package/dist/chunk-TTWLTYSQ.js +79 -0
  10. package/dist/chunk-TTWLTYSQ.js.map +1 -0
  11. package/dist/chunk-WUHW3MWG.js +100 -0
  12. package/dist/chunk-WUHW3MWG.js.map +1 -0
  13. package/dist/{chunk-FTDTO3QP.js → chunk-YUPEBATT.js} +3 -3
  14. package/dist/{chunk-FTDTO3QP.js.map → chunk-YUPEBATT.js.map} +1 -1
  15. package/dist/helpers/hfIdPersist.d.ts +23 -0
  16. package/dist/helpers/hfIdPersist.js +13 -0
  17. package/dist/helpers/hfIdPersist.js.map +1 -0
  18. package/dist/helpers/mediaCodecMap.js +2 -1
  19. package/dist/helpers/mediaProxyPreview.d.ts +2 -1
  20. package/dist/helpers/mediaProxyPreview.js +4 -3
  21. package/dist/helpers/proxyTranscoder.d.ts +5 -2
  22. package/dist/helpers/proxyTranscoder.js +7 -2
  23. package/dist/helpers/safePath.d.ts +22 -0
  24. package/dist/helpers/safePath.js +27 -0
  25. package/dist/helpers/safePath.js.map +1 -0
  26. package/dist/history/index.d.ts +49 -0
  27. package/dist/history/index.js +29 -0
  28. package/dist/history/index.js.map +1 -0
  29. package/dist/index.d.ts +12 -54
  30. package/dist/index.js +357 -1915
  31. package/dist/index.js.map +1 -1
  32. package/dist/{mediaProxyPreview-BMHd9G3N.d.ts → mediaProxyPreview-CLu9FdIr.d.ts} +2 -139
  33. package/dist/projectHistory-Cm8Mdmmv.d.ts +139 -0
  34. package/package.json +15 -3
  35. package/dist/chunk-G6IIFKXY.js.map +0 -1
  36. package/dist/chunk-TJQ76IEA.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/history/projectHistory.ts","../src/helpers/fileVersion.ts","../src/helpers/projectSignature.ts","../src/history/blobStore.ts","../src/history/pruneHistories.ts","../src/history/historyId.ts","../src/history/ownerLock.ts","../src/history/historyLog.ts","../src/history/historyCache.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { readdir, rm, rmdir, writeFile } from \"node:fs/promises\";\nimport {\n type Dirent,\n type Stats,\n existsSync,\n lstatSync,\n statSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { replaceFileAtomically } from \"../helpers/atomicFile.js\";\nimport {\n bytesOverwrittenBy,\n DELETED_VERSION,\n fileContentVersion,\n forgetOverwrittenBytes,\n hashOfVersion,\n hashVersion,\n recordFileWriteReceipt,\n} from \"../helpers/fileVersion.js\";\nimport { affectsProjectSignature, listProjectFiles } from \"../helpers/projectSignature.js\";\nimport { openBlobStore, type BlobStore } from \"./blobStore.js\";\nimport { pruneGoneProjectHistoriesDaily } from \"./pruneHistories.js\";\nimport {\n ID_PATH,\n isRecordedFolder,\n sameFolder,\n projectHistoryId,\n readId,\n recordProject,\n type FolderIdentity,\n} from \"./historyId.js\";\nimport { takeHistoryOwnership } from \"./ownerLock.js\";\nimport {\n START,\n foldOldest,\n manifestAround,\n manifestAt,\n readLog,\n saveRecord,\n referencedHashes,\n stepTarget,\n undoneIds,\n writeLog,\n type HistoryEntry,\n type HistoryEntrySide,\n type HistoryFileChange,\n type HistoryLog,\n type HistoryWho,\n type LogRecord,\n type Manifest,\n} from \"./historyLog.js\";\n\n/** Where hosts keep project histories unless told otherwise: outside every project, so no tidy-up takes one away. */\nexport const DEFAULT_HISTORY_ROOT = join(homedir(), \".cache\", \"hyperframes\", \"history\");\n\nexport interface ProjectHistoryOptions {\n projectDir: string;\n /** Kept outside the project, so nothing tidying the project can take its history with it. */\n historyRoot: string;\n now?: () => number;\n /** Writes with no window open group into one entry until this long without another (default 2 s)... */\n quietMs?: number;\n /** ...or this long after the first (default 30 s). */\n maxGroupMs?: number;\n /** Past this many stored bytes the oldest unpinned entries are folded away (default 2 GB). */\n budgetBytes?: number;\n /** A sweep or commit that a watcher or timer started failed. */\n onError?: (error: unknown) => void;\n /** How long an open waits for another process to close the same history (default 5 s). */\n ownerWaitMs?: number;\n /** A CLI turn's window from an earlier open: writes since, within its idle limit, become the entry with its id. */\n closedWindow?: ClosedWindow;\n undoScope?: \"own\" | \"everyone\";\n pruneGoneProjectsBudgetMs?: number;\n}\n\ninterface ClaimOptions {\n coalesceKey?: string;\n idleMs?: number;\n /** Per path, the version (fileContentVersion) the claimer overwrote: earlier changes stay their writer's. */\n overwrote?: Readonly<Record<string, string>>;\n}\n\ninterface Writing {\n writeToken?: string;\n}\n\nexport interface ClosedWindow {\n id: string;\n who: HistoryWho;\n label: string;\n startedAt: number;\n lastWriteAt: number;\n idleMs: number;\n}\n\nexport const MAX_WINDOW_IDLE_MS = 10 * 60_000;\n\nexport const UNDO_MODES = [\"just-this\", \"back-to-before\", \"keep-later-edits\"] as const;\nexport type UndoMode = (typeof UNDO_MODES)[number];\n\nexport interface HistoryListItem extends HistoryEntry {\n pinned: boolean;\n undone: boolean;\n}\n\nexport type HistoryResult =\n | { ok: true; entry: HistoryEntry | null }\n | { ok: false; conflict: { files: string[]; newer: string[] } };\n\nexport interface HistoryWindow {\n readonly id: string;\n readonly startedAt: number;\n /** Records everything written since the window opened as one entry (null when nothing changed); after the window\n * ended by itself or by flush, returns the entry it became. */\n close(): Promise<HistoryEntry | null>;\n}\n\nexport interface ProjectHistory {\n readonly projectId: string;\n /** Writes until close() are one entry with the window's id; another writer or an operation meanwhile commits the\n * part so far first (fresh id), returned by close() if nothing else remained. Overlaps go to the newest. */\n beginWindow(\n who: HistoryWho,\n label: string,\n options?: { idleMs?: number },\n ): Promise<HistoryWindow>;\n /**\n * For a writer that records after writing (Studio): moves the uncommitted changes to `paths` into one entry of\n * `who`'s, each cut at the `overwrote` version so earlier parts stay their writer's. Same-key claims merge until\n * another key, idle, an operation, a window, or another write. Null when nothing was claimed or a drag nets to 0.\n */\n claim(\n who: HistoryWho,\n label: string,\n paths: readonly string[],\n options?: ClaimOptions,\n ): Promise<{ id: string } | null>;\n /** A watcher saw `path` change (project-relative or absolute). */\n noteChange(path: string): void;\n list(): HistoryListItem[];\n /** Cmd+Z (back) and Cmd+Shift+Z (forward) over `who`'s own and outside changes, or all (undoScope everyone). */\n step(direction: \"back\" | \"forward\", who: HistoryWho, options?: Writing): Promise<HistoryResult>;\n /** The entry `who`'s next step reverts, pending changes included, as of the last scan (a step scans first). */\n next(direction: \"back\" | \"forward\", who: HistoryWho): HistoryEntry | undefined;\n /** A file changed since returns a conflict; `mode` takes a choice (keep-later-edits: null when none is left). */\n undo(id: string, options: { who: HistoryWho; mode?: UndoMode } & Writing): Promise<HistoryResult>;\n /** Makes the files equal what they were right after `point` (an entry id, or START). */\n restore(point: string, who: HistoryWho, options?: Writing): Promise<HistoryEntry | null>;\n /** The files at `point` without writing anything: path to hash, read through readBlob. */\n peek(point: string): Record<string, string> | null;\n checkout(entryId: string, side: HistoryEntrySide, emptyDir: string): Promise<void>;\n readBlob(hash: string): Promise<Buffer>;\n pin(id: string, pinned: boolean): void;\n onEntry(listener: (entry: HistoryEntry) => void): () => void;\n /** Takes in every pending write and commits every open window and the outside group. */\n flush(): Promise<void>;\n /** True once another project stands at the folder's path: open that one's history instead. */\n replacedAtPath(): boolean;\n close(): Promise<void>;\n}\n\nconst OUTSIDE: HistoryWho = { kind: \"outside\", name: \"Outside\" };\nconst OUTSIDE_LABEL = \"Changed outside the app\";\n\ninterface Tracked {\n hash: string;\n stat: string;\n}\n\ninterface Group {\n id: string;\n who: HistoryWho;\n label: string;\n startedAt: number;\n changes: Map<string, HistoryFileChange>;\n /** Windows only: the idle lifetime, its timer, the last write's time, and the entry it became once ended. */\n idleMs?: number;\n lastWriteAt?: number;\n idleTimer?: NodeJS.Timeout;\n entry?: HistoryEntry | null;\n parts?: Set<string>;\n}\n\n/**\n * A stat is only trusted once the file is older than any file system's timestamp tick (git's \"racily clean\"): a\n * same-size rewrite inside one tick keeps its size, mtime and ctime. \"\" is never trusted, so the file is re-hashed.\n */\nconst RACY_MS = 2000;\nconst statKey = (file: { size: number; mtimeMs: number; ctimeMs: number }, sweptAt: number) =>\n sweptAt - Math.max(file.mtimeMs, file.ctimeMs) < RACY_MS\n ? \"\"\n : `${file.size}:${file.mtimeMs}:${file.ctimeMs}`;\n\nconst sameWho = (a: HistoryWho, b: HistoryWho) => a.kind === b.kind && a.name === b.name;\n\n/** Cuts a change at the overwritten `at`: [kept before it, claimed after]; `at` unknown or not kept claims all. */\nfunction splitAt(\n change: HistoryFileChange,\n at: string | undefined,\n kept: boolean,\n): [HistoryFileChange | null, HistoryFileChange | null] {\n if (at === change.after) return [change, null];\n if (!at || at === change.before || !kept) return [null, change];\n return [\n { ...change, after: at },\n { ...change, before: at },\n ];\n}\n\nconst isMissingFolder = (error: unknown) =>\n [\"ENOENT\", \"ENOTDIR\"].includes((error as NodeJS.ErrnoException).code ?? \"\");\n\n/** Removes a folder at `dir` that holds only empty folders (inTheWay checked), so a file can take its place. */\nasync function removeEmptyFolders(dir: string): Promise<void> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch (error) {\n if (isMissingFolder(error)) return;\n throw error;\n }\n for (const entry of entries)\n if (entry.isDirectory()) await removeEmptyFolders(join(dir, entry.name));\n await rmdir(dir);\n}\n\n/** The first file or link under folder `dir` (project path `at`) not in `deleted`; links are not followed. */\nfunction fileLeftIn(dir: string, at: string, deleted: ReadonlySet<string>): string | undefined {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const path = `${at}/${entry.name}`;\n const left = entry.isDirectory()\n ? fileLeftIn(join(dir, entry.name), path, deleted)\n : deleted.has(path)\n ? undefined\n : path;\n if (left) return left;\n }\n return undefined;\n}\n\n/** Whether the project's disk treats `A` and `a` as one name: its history-id file answers when upper-cased. */\nfunction ignoresCase(dir: string): boolean {\n const self = statSync(join(dir, ID_PATH), { throwIfNoEntry: false });\n const other = statSync(join(dir, ID_PATH.toUpperCase()), { throwIfNoEntry: false });\n return self !== undefined && other?.ino === self.ino && other.dev === self.dev;\n}\n\nconst blocks = (removed: string, added: string) =>\n added.startsWith(`${removed}/`) || removed.startsWith(`${added}/`);\n\n/** A window takes a write within idleMs of its last one; past that it has ended, even before its timer commits it. */\nconst takesWrite = (window: Group, at: number) =>\n window.idleMs === undefined || at - (window.lastWriteAt ?? at) <= window.idleMs;\n\n/** Files one change to a group; a later change to the same path keeps the group's first \"before\". */\nfunction addChange(group: Group, path: string, before: string | null, after: string | null): void {\n const earlier = group.changes.get(path);\n const from = earlier ? earlier.before : before;\n if (from === after) group.changes.delete(path);\n else group.changes.set(path, { path, before: from, after });\n}\n\nclass Engine {\n readonly dir: string;\n readonly foldsCase: boolean;\n readonly folder: FolderIdentity & { dev: number };\n readonly home: string;\n log: HistoryLog = { baseline: new Map(), entries: [], pins: new Set() };\n tracked = new Map<string, Tracked>();\n windows: Group[] = [];\n outside: Group | null = null;\n /** A coalescing claim, open until another key, its idle timer, an operation, a window, or another write. */\n claimed: { group: Group; key: string; timer?: NodeJS.Timeout } | null = null;\n writeToken: string | undefined;\n quietTimer: NodeJS.Timeout | undefined;\n maxTimer: NodeJS.Timeout | undefined;\n notedTimer: NodeJS.Timeout | null = null;\n listeners = new Set<(entry: HistoryEntry) => void>();\n tail: Promise<unknown> = Promise.resolve();\n closed = false;\n closing: Promise<void> | undefined;\n\n constructor(\n readonly options: ProjectHistoryOptions,\n readonly projectId: string,\n readonly blobs: BlobStore,\n ) {\n this.dir = resolve(options.projectDir);\n this.home = join(options.historyRoot, projectId);\n this.foldsCase = ignoresCase(this.dir);\n const folder = statSync(this.dir, { throwIfNoEntry: false });\n // Checked after the ownership wait, so a folder swapped for a copy meanwhile is refused.\n if (!folder || readId(this.dir) !== projectId || !isRecordedFolder(this.home, folder))\n throw this.replaced();\n this.folder = { dev: folder.dev, ino: folder.ino, birthtimeMs: folder.birthtimeMs };\n }\n\n /** A path as the project's disk compares names: folded where `A` and `a` are one name. */\n nameKey(path: string): string {\n return this.foldsCase ? path.toLowerCase() : path;\n }\n\n get logFile() {\n return join(this.home, \"log.jsonl\");\n }\n\n now(): number {\n return (this.options.now ?? Date.now)();\n }\n\n /** One operation at a time, in order: sweeps, windows and undos never interleave. */\n queue<T>(task: () => Promise<T>): Promise<T> {\n if (this.closed)\n return Promise.reject(new HistoryClosedError(\"This project's history is closed.\"));\n const run = this.tail.then(task);\n this.tail = run.catch(() => undefined);\n return run;\n }\n\n /** Another project if the folder or its id changed, or the id is gone: a recreated folder may reuse the inode. */\n whereFolder(): \"here\" | \"gone\" | \"replaced\" {\n const now = statSync(this.dir, { throwIfNoEntry: false });\n if (!now) return \"gone\";\n const same = now.dev === this.folder.dev && sameFolder(now, this.folder);\n return same && readId(this.dir) === this.projectId ? \"here\" : \"replaced\";\n }\n\n assertWritable(): void {\n const folder = this.whereFolder();\n if (folder === \"gone\") throw new Error(`The project folder is gone: ${this.dir}`);\n if (folder === \"replaced\") throw this.replaced();\n }\n\n persistLog(record?: LogRecord): void {\n if (record) saveRecord(this.logFile, this.log, record);\n else writeLog(this.logFile, this.log);\n if (!existsSync(join(this.home, \"project.json\")))\n recordProject(this.home, this.dir, this.folder);\n }\n\n replaced(): HistoryClosedError {\n return new HistoryClosedError(`${this.dir} is now another project.`);\n }\n\n assertOpen(): void {\n if (this.closed) throw new HistoryClosedError(\"This project's history is closed.\");\n if (this.whereFolder() === \"replaced\") throw this.replaced();\n }\n\n async start(): Promise<void> {\n const log = readLog(this.logFile, (line) =>\n this.options.onError?.(\n new Error(`History log line ${line} could not be read and was skipped.`),\n ),\n );\n if (!log) return this.firstOpen();\n this.log = log;\n const cache = this.readStatCache();\n const last = this.log.entries.at(-1)?.id ?? START;\n for (const [path, hash] of manifestAt(this.log, last) ?? []) {\n const cached = cache.get(path);\n this.tracked.set(path, { hash, stat: cached?.hash === hash ? cached.stat : \"\" });\n }\n // What changed while the project was closed is one outside entry, or the closed window's.\n this.reopenClosedWindow();\n await this.settleAll();\n }\n\n reopenClosedWindow(): void {\n const closed = this.options.closedWindow;\n if (!closed || this.log.entries.some((entry) => entry.id === closed.id)) return;\n const { id, who, label, startedAt, lastWriteAt, idleMs } = closed;\n this.windows.push({ id, who, label, startedAt, lastWriteAt, idleMs, changes: new Map() });\n }\n\n async firstOpen(): Promise<void> {\n const sweptAt = Date.now();\n for (const file of listProjectFiles(this.dir)) {\n const hash = await this.storeIfPresent(file.path);\n if (this.whereFolder() !== \"here\") throw this.replaced();\n if (hash !== null) this.tracked.set(file.path, { hash, stat: statKey(file, sweptAt) });\n }\n this.log.baseline = this.manifest();\n this.persistLog();\n this.saveStatCache();\n }\n\n manifest(): Manifest {\n return new Map([...this.tracked].map(([path, file]) => [path, file.hash]));\n }\n\n readStatCache(): Map<string, Tracked> {\n try {\n const files = JSON.parse(readFileSync(join(this.home, \"stat.json\"), \"utf-8\"));\n return new Map(Object.entries(files as Record<string, Tracked>));\n } catch {\n return new Map();\n }\n }\n\n saveStatCache(): void {\n const files = JSON.stringify(Object.fromEntries(this.tracked));\n mkdirSync(this.home, { recursive: true });\n replaceFileAtomically(join(this.home, \"stat.json\"), files, 0o644);\n }\n\n /**\n * Takes in every write since the last sweep and files each change to its writer. ponytail: always the whole\n * project (a stat per file, a hash only when the stat moved); per-path sweeps if projects reach tens of thousands.\n */\n async sweep(): Promise<void> {\n // A folder moved or removed was not emptied, and another project at its path is none of this history's.\n if (this.whereFolder() !== \"here\") return;\n const sweptAt = Date.now();\n const changedAt = (file: { mtimeMs: number; ctimeMs: number }) =>\n Math.min(sweptAt, Math.max(file.mtimeMs, file.ctimeMs));\n const seen = listProjectFiles(this.dir);\n const present = new Set(seen.map((file) => file.path));\n const removed = [...this.tracked.keys()]\n .filter((path) => !present.has(path))\n .map((path) => {\n const standing = this.standingAt(path);\n return { at: standing ? changedAt(standing.stat) : sweptAt, path, file: null };\n });\n // A file in the way of a removal (a/b over file a, or the reverse) appeared no earlier than that removal.\n const appearedAt = (file: (typeof seen)[number]) =>\n Math.max(\n changedAt(file),\n ...removed\n .filter((gone) => blocks(this.nameKey(gone.path), this.nameKey(file.path)))\n .map((gone) => gone.at),\n );\n const events = [\n ...removed,\n ...seen.map((file) => ({ at: appearedAt(file), path: file.path, file })),\n ].sort((a, b) => a.at - b.at);\n let changed = false;\n for (const { at, path, file } of events) {\n if (file) {\n changed = (await this.observe(path, statKey(file, sweptAt), at)) || changed;\n continue;\n }\n const known = this.tracked.get(path)!;\n this.tracked.delete(path);\n await this.record(path, known.hash, null, at);\n changed = true;\n }\n if (changed) this.saveStatCache();\n }\n\n /** The file's hash once copied in; null when it was removed before the copy (the next sweep records that). */\n async storeIfPresent(path: string): Promise<string | null> {\n try {\n return await this.blobs.put(join(this.dir, path));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n }\n\n async observe(path: string, stat: string, at: number): Promise<boolean> {\n const known = this.tracked.get(path) ?? { hash: null, stat: null };\n if (stat && known.stat === stat) return false;\n const hash = await this.storeIfPresent(path);\n if (hash === null || this.whereFolder() !== \"here\") return false;\n this.tracked.set(path, { hash, stat });\n if (known.hash !== hash) await this.record(path, known.hash, hash, at);\n return known.hash !== hash || known.stat !== stat;\n }\n\n async record(\n path: string,\n before: string | null,\n after: string | null,\n at: number,\n ): Promise<void> {\n const endedBeforeThisWrite = this.windows.filter((open) => !takesWrite(open, at));\n if (endedBeforeThisWrite.length) await this.commitOutside();\n for (const window of endedBeforeThisWrite) await this.endWindow(window);\n const window = this.windows.at(-1);\n if (window) this.touch(window, at);\n addChange(window ?? this.outsideGroup(), path, before, after);\n }\n\n /** ponytail: another's write between Studio's write and its sweep folds into Studio's entry (per-write tokens). */\n async claimNow(\n who: HistoryWho,\n label: string,\n paths: readonly string[],\n { coalesceKey, idleMs, overwrote = {} }: ClaimOptions,\n ): Promise<{ id: string } | null> {\n await this.sweep();\n const taken = await this.takeClaimed(who, paths, overwrote);\n if (!taken.length) return this.claimNothing(coalesceKey);\n const held = this.heldFor(coalesceKey, taken);\n if (!held) await this.commitClaim();\n await this.commitPending();\n const group = held ?? this.newGroup(who, label);\n for (const change of taken) addChange(group, change.path, change.before, change.after);\n if (coalesceKey) return this.holdClaim(group, coalesceKey, idleMs);\n const entry = await this.commit(group);\n return entry && { id: entry.id };\n }\n\n /** A claim that took nothing still ends a held claim under another key. */\n async claimNothing(coalesceKey: string | undefined): Promise<null> {\n if (coalesceKey !== this.claimed?.key) await this.commitClaim();\n return null;\n }\n\n /** Commits every other writer's pending changes, oldest first; open windows stay open. */\n async commitPending(): Promise<void> {\n await this.commitOutside();\n for (const window of [...this.windows])\n if (window.changes.size) await this.commitCut(window, [...window.changes.keys()]);\n }\n\n pendingElsewhere(): boolean {\n return (\n Boolean(this.outside?.changes.size) || this.windows.some((open) => open.changes.size > 0)\n );\n }\n\n /** The held claim `taken` continues: same key, each file from its own last change, no held file changed since. */\n heldFor(key: string | undefined, taken: readonly HistoryFileChange[]): Group | null {\n const group =\n key && this.claimed?.key === key && !this.pendingElsewhere() ? this.claimed.group : null;\n const after = (path: string, before: string | null) => {\n const own = group?.changes.get(path);\n return own ? own.after : before;\n };\n const takenPaths = new Set(taken.map((change) => change.path));\n const untouched = (change: HistoryFileChange) =>\n takenPaths.has(change.path) || (this.tracked.get(change.path)?.hash ?? null) === change.after;\n return group &&\n taken.every((change) => after(change.path, change.before) === change.before) &&\n [...group.changes.values()].every(untouched)\n ? group\n : null;\n }\n\n logPath(path: string): string {\n return relative(this.dir, resolve(this.dir, path)).split(sep).join(\"/\");\n }\n\n /** Takes `paths`' uncommitted changes from others, cut at what `who` overwrote. */\n async takeClaimed(\n who: HistoryWho,\n paths: readonly string[],\n overwrote: Readonly<Record<string, string>>,\n ): Promise<HistoryFileChange[]> {\n const wanted = new Set(paths.map((path) => this.logPath(path)));\n const at = new Map(\n Object.entries(overwrote).map(([path, version]) => [\n this.logPath(path),\n hashOfVersion(version),\n ]),\n );\n const others = this.windows.filter((open) => !sameWho(open.who, who));\n const groups = this.outside ? [this.outside, ...others] : others;\n const hits = groups.flatMap((group) =>\n [...group.changes.values()]\n .filter((change) => wanted.has(change.path))\n .map((change) => ({ group, change })),\n );\n const cuts: Array<string | undefined> = [];\n const walked = hits.map(() => new Set<string>());\n for (const [i, { change }] of hits.entries()) {\n const told = at.get(change.path);\n cuts.push((await this.overwrittenBy(change, told, walked[i]!)) ?? told);\n }\n hits.forEach(({ change }, i) =>\n forgetOverwrittenBytes(\n join(this.dir, change.path),\n new Set([...walked[i]!].map(hashVersion)),\n ),\n );\n return hits.flatMap(({ group, change }, i) => this.cutOut(group, change, cuts[i]) ?? []);\n }\n\n /** Walks the server's writes back from `change.after` to the bytes they replaced, stored so an undo restores them. */\n async overwrittenBy(change: HistoryFileChange, told: string | undefined, seen: Set<string>) {\n const absPath = join(this.dir, change.path);\n let bytes: string | Uint8Array | undefined;\n for (let hash = change.after; hash; ) {\n // A loop (X, Y, back to X) says nothing about what was there first: the client's word stands.\n if (seen.has(hash)) return undefined;\n seen.add(hash);\n const replaced = bytesOverwrittenBy(absPath, hashVersion(hash));\n if (replaced === undefined) break;\n bytes = replaced;\n hash = hashOfVersion(fileContentVersion(replaced))!;\n if (hash === change.before || hash === told) return hash;\n }\n if (bytes === undefined) return undefined;\n const staged = join(this.home, `overwrote-${randomUUID()}`);\n await writeFile(staged, bytes);\n try {\n return await this.blobs.put(staged);\n } finally {\n await rm(staged, { force: true });\n }\n }\n\n cutOut(group: Group, change: HistoryFileChange, cut: string | undefined) {\n const [kept, claimed] = splitAt(change, cut, cut !== undefined && this.blobs.has(cut));\n group.changes.delete(change.path);\n if (kept) group.changes.set(kept.path, kept);\n return claimed;\n }\n\n /** Commits the window's cut `paths` as their own entry, other files staying: a window holds one change per file,\n * so the writer's next write to a cut file would otherwise span the claimer's edit. */\n async commitCut(window: Group, paths: readonly string[]): Promise<void> {\n const part = { ...this.newGroup(window.who, window.label), startedAt: window.startedAt };\n for (const path of paths) {\n part.changes.set(path, window.changes.get(path)!);\n window.changes.delete(path);\n }\n (window.parts ??= new Set()).add(part.id);\n window.entry = await this.commit(part);\n }\n\n holdClaim(\n group: Group,\n key: string,\n idleMs = this.options.quietMs ?? 2000,\n ): { id: string } | null {\n clearTimeout(this.claimed?.timer);\n const timer = Number.isFinite(idleMs)\n ? setTimeout(() => this.background(() => this.commitClaim()), idleMs)\n : undefined;\n timer?.unref?.();\n this.claimed = { group, key, timer };\n return group.changes.size ? { id: group.id } : null;\n }\n\n async commitClaim(): Promise<void> {\n const held = this.claimed;\n this.claimed = null;\n if (!held) return;\n clearTimeout(held.timer);\n await this.commit(held.group);\n }\n\n outsideGroup(): Group {\n const { quietMs = 2000, maxGroupMs = 30_000 } = this.options;\n const closeIn = (ms: number) => {\n const timer = setTimeout(() => this.background(() => this.commitOutside()), ms);\n timer.unref?.();\n return timer;\n };\n if (!this.outside) {\n this.outside = this.newGroup(OUTSIDE, OUTSIDE_LABEL);\n this.maxTimer = closeIn(maxGroupMs);\n }\n clearTimeout(this.quietTimer);\n this.quietTimer = closeIn(quietMs);\n return this.outside;\n }\n\n newGroup(who: HistoryWho, label: string): Group {\n return { id: randomUUID(), who, label, startedAt: this.now(), changes: new Map() };\n }\n\n async commitOutside(): Promise<void> {\n if (this.outside?.changes.size) await this.commitClaim();\n clearTimeout(this.quietTimer);\n clearTimeout(this.maxTimer);\n const group = this.outside;\n this.outside = null;\n if (group) await this.commit(group);\n }\n\n async commit(group: Group, extra: Partial<HistoryEntry> = {}): Promise<HistoryEntry | null> {\n if (!group.changes.size) return null;\n const pending = this.pendingEntry(group);\n const endedAt = Math.max(pending.endedAt, this.log.entries.at(-1)?.endedAt ?? 0);\n const entry: HistoryEntry = { ...pending, endedAt, ...extra };\n this.log.entries.push(entry);\n try {\n this.persistLog({ type: \"entry\", entry });\n } catch (error) {\n this.log.entries.pop();\n throw error;\n }\n for (const listener of this.listeners) listener(entry);\n await this.keepWithinBudget();\n return entry;\n }\n\n /** Stored bytes beyond what the current files need: a project larger than the budget still keeps its history. */\n historyBytes(): number {\n let current = 0;\n for (const hash of new Set([...this.tracked.values()].map((file) => file.hash)))\n current += this.blobs.size(hash);\n return this.blobs.bytes() - current;\n }\n\n async keepWithinBudget(): Promise<void> {\n const budget = this.options.budgetBytes ?? 2 * 1024 ** 3;\n let folded = false;\n while (this.historyBytes() > budget && foldOldest(this.log)) {\n folded = true;\n await this.blobs.prune(\n new Set([...referencedHashes(this.log, this.manifest()), ...this.pendingHashes()]),\n );\n }\n if (folded) this.persistLog();\n }\n\n /** Hashes only pending changes point at yet, such as a claim's stored cut. */\n pendingHashes(): string[] {\n const changes = [...this.windows, this.outside, this.claimed?.group].flatMap((group) => [\n ...(group?.changes.values() ?? []),\n ]);\n return changes\n .flatMap((change) => [change.before, change.after])\n .filter((hash): hash is string => hash !== null);\n }\n\n /** Before an operation or a window: every write is filed and every pending change committed, in write order. */\n async settle(): Promise<void> {\n await this.sweep();\n await this.commitClaim();\n await this.commitPending();\n }\n\n /** A watcher saw a write: one sweep per burst takes it in (a deleted folder is reported by its name alone). */\n noteChange(path: string): void {\n if (this.notedTimer || !affectsProjectSignature(this.dir, resolve(this.dir, path))) return;\n this.notedTimer = setTimeout(() => {\n this.notedTimer = null;\n this.background(() => this.sweep());\n }, 20);\n this.notedTimer.unref?.();\n }\n\n background(task: () => Promise<unknown>): void {\n this.queue(task).catch((error) => {\n if (!(error instanceof HistoryClosedError)) this.options.onError?.(error);\n });\n }\n\n beginWindow(who: HistoryWho, label: string, idleMs: number): Promise<HistoryWindow> {\n return this.queue(async () => {\n // Writes before the window opened are not this writer's, and are logged before it.\n await this.settle();\n const window = { ...this.newGroup(who, label), idleMs };\n this.windows.push(window);\n this.touch(window, Date.now());\n const close = () => this.queue(() => this.sweepAndEnd(window));\n return { id: window.id, startedAt: window.startedAt, close };\n });\n }\n\n /** A window with no write for its idleMs ends, so a close that never comes cannot hold every later write. */\n touch(window: Group, at: number): void {\n window.lastWriteAt = Math.max(window.lastWriteAt ?? at, at);\n clearTimeout(window.idleTimer);\n if (window.idleMs === undefined || !Number.isFinite(window.idleMs)) return;\n window.idleTimer = setTimeout(\n () => this.background(() => this.sweepAndEnd(window)),\n window.idleMs,\n );\n window.idleTimer.unref?.();\n }\n\n async sweepAndEnd(window: Group): Promise<HistoryEntry | null> {\n await this.sweep();\n return this.endWindow(window);\n }\n\n /** Commits an open window once; ending it again returns the entry it became. */\n async endWindow(window: Group): Promise<HistoryEntry | null> {\n if (!this.windows.includes(window)) return window.entry ?? null;\n clearTimeout(window.idleTimer);\n await this.commitClaim();\n this.windows = this.windows.filter((open) => open !== window);\n window.entry = (await this.commit(window)) ?? window.entry ?? null;\n return window.entry;\n }\n\n /** Every pending write, open window and outside group, committed: for flush and close. */\n async settleAll(): Promise<void> {\n await this.sweep();\n await this.commitClaim();\n for (const window of [...this.windows]) await this.endWindow(window);\n await this.commitOutside();\n }\n\n /** What took a removed path's place, dating its removal: an entry at the path, or a file at a folder above it. */\n standingAt(path: string): { at: string; stat: Stats } | undefined {\n const parts = path.split(\"/\");\n for (let depth = 1; depth <= parts.length; depth += 1) {\n const at = parts.slice(0, depth).join(\"/\");\n const stat = lstatSync(join(this.dir, at), { throwIfNoEntry: false });\n if (!stat) return undefined;\n if (at === path || !stat.isDirectory()) return { at, stat };\n }\n return undefined;\n }\n\n /** What a write of `path` would have to delete: a file or link at or above it, or a file in a folder at it. */\n inTheWay(path: string, deleted: ReadonlySet<string>): string | undefined {\n const standing = this.standingAt(path);\n if (!standing) return undefined;\n if (standing.stat.isSymbolicLink()) return standing.at;\n if (standing.at === path && !standing.stat.isDirectory()) return undefined;\n // On-disk names, so an entry `a` found at `A` on a case-insensitive disk matches its tracked paths.\n const real = realpathSync.native(join(this.dir, standing.at));\n const at = relative(realpathSync.native(this.dir), real).split(sep).join(\"/\");\n if (standing.at !== path) return deleted.has(at) ? undefined : at;\n return fileLeftIn(real, at, deleted);\n }\n\n /** Writes `target` (path to hash, null deletes) as one entry of `who`'s. */\n async writeAs(\n who: HistoryWho,\n label: string,\n target: Map<string, string | null>,\n extra: Partial<HistoryEntry>,\n ): Promise<HistoryEntry | null> {\n this.assertWritable();\n const changes = [...target]\n .filter(([path, hash]) => (this.tracked.get(path)?.hash ?? null) !== hash)\n .sort(([, a], [, b]) => Number(a !== null) - Number(b !== null));\n const deleted = new Set(changes.filter(([, hash]) => hash === null).map(([path]) => path));\n const blocked = changes\n .filter(([, hash]) => hash !== null)\n .map(([path]) => this.inTheWay(path, deleted))\n .find(Boolean);\n if (blocked) throw new Error(`${blocked} is in the way; move or delete it, then try again.`);\n const group = this.newGroup(who, label);\n this.windows.push(group);\n try {\n for (const [path, hash] of changes) await this.writeProjectFile(path, hash);\n await this.sweep();\n } finally {\n this.windows = this.windows.filter((open) => open !== group);\n }\n return this.commit(group, extra);\n }\n\n /** Writes one project file (null deletes it), first leaving the running operation's receipt for its echo. */\n async writeProjectFile(path: string, hash: string | null): Promise<void> {\n this.assertWritable();\n const absPath = join(this.dir, path);\n const { writeToken } = this;\n if (writeToken) {\n const version = hash === null ? DELETED_VERSION : hashVersion(hash);\n recordFileWriteReceipt(absPath, { path, version, writeToken });\n }\n if (hash === null) {\n this.assertWritable();\n rmSync(absPath, { force: true });\n } else {\n await removeEmptyFolders(absPath);\n await this.blobs.writeTo(hash, absPath, () => this.assertWritable());\n }\n }\n\n entry(id: string): HistoryEntry {\n const entry = this.log.entries.find((candidate) => candidate.id === id);\n if (!entry) throw new Error(\"That change is no longer kept in this project's history.\");\n return entry;\n }\n\n undoLabel(entry: HistoryEntry): string {\n if (!entry.undoes) return `Undid: ${entry.label}`;\n const original = this.log.entries.find((candidate) => candidate.id === entry.undoes);\n return `Redid: ${original?.label ?? entry.label}`;\n }\n\n async undoNow(id: string, who: HistoryWho, mode?: UndoMode): Promise<HistoryResult> {\n const entry = this.entry(id);\n const changed = this.movedOn(entry);\n if (changed.length && !mode) return { ok: false, conflict: this.conflict(entry, changed) };\n if (mode === \"back-to-before\") {\n const index = this.log.entries.indexOf(entry);\n const point = index > 0 ? this.log.entries[index - 1]!.id : START;\n return {\n ok: true,\n entry: await this.restoreNow(point, who, `Went back to before: ${entry.label}`),\n };\n }\n const target = new Map(\n entry.files\n .filter((file) => mode !== \"keep-later-edits\" || !changed.includes(file))\n .map((file) => [file.path, file.before]),\n );\n return {\n ok: true,\n entry: await this.writeAs(who, this.undoLabel(entry), target, { undoes: id }),\n };\n }\n\n movedOn(entry: HistoryEntry): HistoryFileChange[] {\n return entry.files.filter((file) => (this.tracked.get(file.path)?.hash ?? null) !== file.after);\n }\n\n next(direction: \"back\" | \"forward\", who: HistoryWho): HistoryEntry | undefined {\n // An outside change has no known author, so it is everyone's; a step commits it after the held claim.\n const mine = (author: HistoryWho) => sameWho(author, who) || sameWho(author, OUTSIDE);\n const pending = [...[...this.windows].reverse(), this.outside, this.claimed?.group].find(\n (group) => group?.changes.size && mine(group.who),\n );\n if (pending) return direction === \"back\" ? this.pendingEntry(pending) : undefined;\n const everyone = this.options.undoScope === \"everyone\";\n const ofOpenTurn = (entry: HistoryEntry) =>\n this.windows.some((open) => open.parts?.has(entry.id));\n return stepTarget(\n this.log.entries,\n direction,\n (entry) => mine(entry.who) || (everyone && !ofOpenTurn(entry)),\n );\n }\n\n /** A pending group as the entry it becomes once committed (a window's part gets a fresh id). */\n pendingEntry(group: Group): HistoryEntry {\n const files = [...group.changes.values()].sort((a, b) => a.path.localeCompare(b.path));\n const { id, who, label, startedAt, lastWriteAt } = group;\n return { id, who, label, startedAt, endedAt: lastWriteAt ?? this.now(), files };\n }\n\n conflict(\n entry: HistoryEntry,\n changed: HistoryFileChange[],\n ): { files: string[]; newer: string[] } {\n const paths = new Set(changed.map((file) => file.path));\n const after = this.log.entries.slice(this.log.entries.indexOf(entry) + 1);\n const newer = after.filter((later) => later.files.some((file) => paths.has(file.path)));\n return { files: [...paths], newer: newer.map((later) => later.id) };\n }\n\n async restoreNow(point: string, who: HistoryWho, label: string): Promise<HistoryEntry | null> {\n const files = manifestAt(this.log, point);\n if (!files) throw new Error(\"That point is no longer kept in this project's history.\");\n const target = new Map<string, string | null>(\n [...this.tracked.keys()].map((path) => [path, null]),\n );\n for (const [path, hash] of files) target.set(path, hash);\n return this.writeAs(who, label, target, { restoredTo: point });\n }\n\n /** Settles, then runs `task` with its writes labelled `writeToken`. */\n operation<T>(writeToken: string | undefined, task: () => Promise<T>): Promise<T> {\n return this.queue(async () => {\n await this.settle();\n this.writeToken = writeToken;\n try {\n return await task();\n } finally {\n this.writeToken = undefined;\n }\n });\n }\n\n pointLabel(point: string): string {\n return point === START ? \"the start\" : this.entry(point).label;\n }\n\n api(): ProjectHistory {\n return {\n projectId: this.projectId,\n beginWindow: (who, label, options = {}) =>\n this.beginWindow(who, label, options.idleMs ?? this.options.maxGroupMs ?? 30_000),\n claim: (who, label, paths, options = {}) =>\n this.queue(() => this.claimNow(who, label, paths, options)),\n noteChange: (path) => {\n if (!this.closed) this.noteChange(path);\n },\n list: () => {\n this.assertOpen();\n const undone = undoneIds(this.log.entries);\n return this.log.entries.map((entry) => ({\n ...entry,\n pinned: this.log.pins.has(entry.id),\n undone: undone.has(entry.id),\n }));\n },\n step: (direction, who, { writeToken } = {}) =>\n this.operation(writeToken, async () => {\n const target = this.next(direction, who);\n return target ? this.undoNow(target.id, who) : { ok: true, entry: null };\n }),\n undo: (id, { who, mode, writeToken }) =>\n this.operation(writeToken, () => this.undoNow(id, who, mode)),\n restore: (point, who, { writeToken } = {}) =>\n this.operation(writeToken, () =>\n this.restoreNow(point, who, `Restored: ${this.pointLabel(point)}`),\n ),\n peek: (point) => {\n this.assertOpen();\n const files = manifestAt(this.log, point);\n return files && Object.fromEntries(files);\n },\n checkout: (entryId, side, emptyDir) =>\n this.queue(async () => {\n this.entry(entryId);\n if (!existsSync(this.dir)) throw new Error(`The project folder is gone: ${this.dir}`);\n const dest = resolve(emptyDir);\n if (isWithin(this.dir, dest))\n throw new Error(`Checkout writes outside the project only: ${dest}`);\n if ((await readdir(dest).catch(missingIsEmpty)).length > 0)\n throw new Error(`Checkout writes into an empty folder only: ${dest}`);\n const files = manifestAround(this.log, entryId, side)!;\n try {\n for (const [path, hash] of files) await this.blobs.writeTo(hash, join(dest, path));\n } catch (error) {\n for (const name of await readdir(dest).catch(() => []))\n await rm(join(dest, name), { recursive: true, force: true });\n throw error;\n }\n }),\n next: (direction, who) => {\n this.assertOpen();\n return this.next(direction, who);\n },\n readBlob: async (hash) => {\n this.assertOpen();\n return this.blobs.read(hash);\n },\n pin: (id, pinned) => {\n this.assertOpen();\n this.entry(id);\n if (pinned) this.log.pins.add(id);\n else this.log.pins.delete(id);\n this.persistLog({ type: \"pin\", id, pinned });\n },\n onEntry: (listener) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n },\n flush: () => this.queue(() => this.settleAll()),\n replacedAtPath: () => this.whereFolder() === \"replaced\",\n close: () =>\n (this.closing ??= (async () => {\n if (this.notedTimer) clearTimeout(this.notedTimer);\n const settled = this.queue(() => this.settleAll());\n this.closed = true;\n await settled;\n })()),\n };\n }\n}\n\n/** Compares real on-disk paths; `.native` gives a case-insensitive disk's real letter case, so no alias gets in. */\nfunction isWithin(dir: string, path: string): boolean {\n let probe = resolve(path);\n while (!existsSync(probe) && dirname(probe) !== probe) probe = dirname(probe);\n const rel = relative(realpathSync.native(dir), realpathSync.native(probe));\n return rel === \"\" || (rel !== \"..\" && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));\n}\n\nfunction missingIsEmpty(error: NodeJS.ErrnoException): string[] {\n if (error.code === \"ENOENT\") return [];\n throw error;\n}\n\n/** A call on a history that was closed, or whose folder is now another project. */\nexport class HistoryClosedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HistoryClosedError\";\n }\n}\n\n/** Opens a project's history: every write to its files becomes an entry that can be undone or restored. */\nexport async function openProjectHistory(options: ProjectHistoryOptions): Promise<ProjectHistory> {\n if (!existsSync(options.projectDir))\n throw new HistoryClosedError(`${options.projectDir} is gone or now another project.`);\n const projectId = projectHistoryId(options.projectDir, options.historyRoot);\n const release = await takeHistoryOwnership(\n join(options.historyRoot, projectId),\n options.ownerWaitMs ?? 5000,\n );\n try {\n const blobs = await openBlobStore(join(options.historyRoot, projectId, \"blobs\"));\n const engine = new Engine(options, projectId, blobs);\n await engine.queue(() => engine.start());\n pruneGoneProjectHistoriesDaily(options.historyRoot, engine.now(), {\n onError: options.onError,\n budgetMs: options.pruneGoneProjectsBudgetMs,\n });\n const api = engine.api();\n return {\n ...api,\n close: () => api.close().finally(release),\n };\n } catch (error) {\n release();\n throw error;\n }\n}\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { realFilePath } from \"./safePath.js\";\n\nexport interface FileWriteReceipt {\n path: string;\n version: string;\n writeToken: string;\n}\n\ninterface StoredReceipt extends FileWriteReceipt {\n recordedAt: number;\n /** The bytes this write replaced, so the project history can keep a save that landed just before it. */\n overwrote?: string | Uint8Array;\n}\n\nconst RECEIPT_TTL_MS = 10_000;\nconst receipts = new Map<string, StoredReceipt[]>();\n\n/** Strong content version used as both the JSON version and HTTP ETag. */\nexport function fileContentVersion(content: string | Uint8Array): string {\n return hashVersion(createHash(\"sha256\").update(content).digest(\"hex\"));\n}\n\n/** The version a deletion's receipt is kept under: a deleted file has no bytes to hash. */\nexport const DELETED_VERSION = '\"deleted\"';\n\nexport function hashVersion(hex: string): string {\n return `\"sha256:${hex}\"`;\n}\n\n/** The sha256 a version names, or undefined when it is not a content version. */\nexport function hashOfVersion(version: string): string | undefined {\n return /^\"sha256:([0-9a-f]{64})\"$/.exec(version)?.[1];\n}\n\n/** A validator from a file's inode, change time and size, or null while the change is under three\n * seconds old. Change time, not mtime: copy tools (`cp -p`, rsync, robocopy) set mtime back but not\n * ctime, which Node reads from NTFS ChangeTime on Windows. A same-size rewrite within one tick keeps\n * both, so only a settled file is tagged. */\nexport function settledFileTag(\n stat: { ino: number; ctimeMs: number; size: number },\n now = Date.now(),\n): string | null {\n if (now - stat.ctimeMs < 3000) return null;\n return [stat.ino, stat.ctimeMs, stat.size].map((n) => n.toString(36)).join(\"-\");\n}\n\nexport function createWriteToken(requestToken?: string): string {\n const token = requestToken?.trim();\n return token && token.length <= 200 ? token : randomUUID();\n}\n\nexport function recordFileWriteReceipt(\n filePath: string,\n receipt: Omit<StoredReceipt, \"recordedAt\">,\n): void {\n const absPath = realFilePath(filePath);\n const now = Date.now();\n // Every path's expired receipts go, not just this one's: a receipt can hold a whole file's bytes.\n for (const [path, list] of receipts) {\n const live = list.filter((entry) => now - entry.recordedAt < RECEIPT_TTL_MS);\n if (live.length > 0) receipts.set(path, live);\n else receipts.delete(path);\n }\n receipts.set(absPath, [...(receipts.get(absPath) ?? []), { ...receipt, recordedAt: now }]);\n}\n\nexport function clearFileWriteReceipt(filePath: string, version: string, writeToken: string): void {\n const absPath = realFilePath(filePath);\n const current = (receipts.get(absPath) ?? []).filter(\n (entry) => entry.version !== version || entry.writeToken !== writeToken,\n );\n if (current.length > 0) receipts.set(absPath, current);\n else receipts.delete(absPath);\n}\n\n/**\n * Attach one API write's identity to the watcher echo for its exact bytes.\n *\n * Reading is non-destructive: one watcher event fans out to every open SSE\n * subscriber, and a receipt removed by the first reader leaves the rest seeing\n * an unlabelled change and reloading the preview on Studio's own edit. Only the\n * TTL removes a receipt.\n */\nexport function identifyFileWrite(\n filePath: string,\n expectedVersion: string,\n): FileWriteReceipt | null {\n const receipt = newestReceipt(realFilePath(filePath), expectedVersion);\n if (!receipt) return null;\n const { path, version, writeToken } = receipt;\n return { path, version, writeToken };\n}\n\n/** The bytes the API write of `version` replaced, while its receipt lives. */\nexport function bytesOverwrittenBy(\n filePath: string,\n version: string,\n): string | Uint8Array | undefined {\n return newestReceipt(realFilePath(filePath), version)?.overwrote;\n}\n\n/** Drops the replaced bytes a claim walked through, so a later claim can't walk back through them. */\nexport function forgetOverwrittenBytes(filePath: string, versions: ReadonlySet<string>): void {\n for (const receipt of receipts.get(realFilePath(filePath)) ?? [])\n if (versions.has(receipt.version)) delete receipt.overwrote;\n}\n\nfunction newestReceipt(absPath: string, expectedVersion: string): StoredReceipt | undefined {\n const now = Date.now();\n const current = (receipts.get(absPath) ?? []).filter(\n (entry) => now - entry.recordedAt < RECEIPT_TTL_MS,\n );\n if (current.length > 0) receipts.set(absPath, current);\n else receipts.delete(absPath);\n // Newest match, not oldest: identical bytes written twice inside the TTL\n // (undo, retyping a value) share a version, and the older token was already\n // spent by the client on its own echo. The destructive read used to advance\n // past it; scanning from the end keeps that cursor without the eviction.\n let receipt: StoredReceipt | undefined;\n for (let i = current.length - 1; i >= 0 && !receipt; i -= 1) {\n if (current[i]?.version === expectedVersion) receipt = current[i];\n }\n return receipt;\n}\n\n/**\n * @deprecated Renamed to {@link identifyFileWrite}, which despite this alias's\n * name does NOT consume the receipt: one watcher event fans out to every SSE\n * subscriber, so a destructive read left all but the first reloading on Studio's\n * own write. Kept for one release; call `identifyFileWrite` instead.\n */\nexport const consumeFileWriteReceipt = identifyFileWrite;\n\nexport function resetFileWriteReceipts(): void {\n receipts.clear();\n}\n","import { createHash } from \"node:crypto\";\nimport { lstatSync, readFileSync, readdirSync } from \"node:fs\";\nimport { extname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport type { ResolvedProject, StudioApiAdapter } from \"../types.js\";\n\nconst SIGNATURE_TEXT_EXTENSIONS = new Set([\n \".cjs\",\n \".css\",\n \".html\",\n \".js\",\n \".json\",\n \".jsx\",\n \".mjs\",\n \".svg\",\n \".ts\",\n \".tsx\",\n]);\nconst SIGNATURE_EXCLUDED_DIRS = new Set([\n \".cache\",\n \".git\",\n \".hyperframes\",\n \".next\",\n \".thumbnails\",\n \".transcode-cache\",\n \".vite\",\n \".waveform-cache\",\n \"build\",\n \"coverage\",\n \"dist\",\n \"node_modules\",\n \"outputs\",\n \"renders\",\n]);\nconst MAX_SIGNATURE_TEXT_BYTES = 2_000_000;\nexport const STUDIO_SIGNATURE_MANIFEST_PATHS = [\n \".hyperframes/studio-manual-edits.json\",\n \".hyperframes/studio-motion.json\",\n] as const;\n\n/**\n * Whether a write at `changedPath` can change `createProjectSignature(projectDir)`.\n *\n * Owned here because it is the exact complement of what the walk below collects,\n * and a second copy of that reasoning drifts the moment either set changes. A\n * watcher that invalidates on everything is not merely wasteful: `.thumbnails/`\n * is written by the thumbnail route on every capture, and each capture reads the\n * preview, so an unfiltered watcher discards the memo on roughly every request of\n * the one workload the memo exists for.\n *\n * Note this is not `WATCHER_EXCLUDED_DIRS`, which is character-identical but\n * excludes all of `.hyperframes/` — the signature deliberately reads two manifest\n * files from inside it, so filtering with that set would stop motion-state saves\n * from ever invalidating.\n *\n * Every segment is tested, not just the parents, so a directory event on an\n * excluded dir itself (`unlinkDir .thumbnails`) is filtered too. The cost is that\n * a *file* literally named `dist` at the project root reads as excluded; the walk\n * would collect it, so it is a false negative, and no real project has one.\n */\nexport function affectsProjectSignature(projectDir: string, changedPath: string): boolean {\n const relativePath = relative(resolve(projectDir), resolve(changedPath));\n if (relativePath === \"\" || relativePath.startsWith(\"..\") || isAbsolute(relativePath)) {\n return false;\n }\n const segments = relativePath.split(sep);\n if (STUDIO_SIGNATURE_MANIFEST_PATHS.includes(segments.join(\"/\") as never)) return true;\n return !segments.some((segment) => SIGNATURE_EXCLUDED_DIRS.has(segment));\n}\n\ninterface ProjectSignatureFile {\n file: string;\n mtimeMs: number;\n ctimeMs: number;\n size: number;\n textContentEligible: boolean;\n}\n\ninterface ProjectSignatureCacheEntry {\n fingerprint: string;\n signature: string;\n}\n\nconst projectSignatureCache = new Map<string, ProjectSignatureCacheEntry>();\n\nfunction isPathWithin(parentDir: string, childPath: string): boolean {\n const childRelativePath = relative(parentDir, childPath);\n return (\n childRelativePath === \"\" ||\n (!childRelativePath.startsWith(\"..\") && !isAbsolute(childRelativePath))\n );\n}\n\nfunction isTextContentEligible(file: string, size: number): boolean {\n return (\n SIGNATURE_TEXT_EXTENSIONS.has(extname(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES\n );\n}\n\nfunction collectProjectSignatureFiles(\n projectDir: string,\n dir: string,\n files: ProjectSignatureFile[],\n): void {\n let entries: string[];\n try {\n entries = readdirSync(dir).sort();\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (SIGNATURE_EXCLUDED_DIRS.has(entry)) continue;\n const file = resolve(dir, entry);\n if (!isPathWithin(projectDir, file)) continue;\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(file);\n } catch {\n continue;\n }\n if (stat.isSymbolicLink()) continue;\n if (stat.isDirectory()) {\n collectProjectSignatureFiles(projectDir, file, files);\n } else if (stat.isFile()) {\n files.push({\n file,\n mtimeMs: stat.mtimeMs,\n ctimeMs: stat.ctimeMs,\n size: stat.size,\n textContentEligible: isTextContentEligible(file, stat.size),\n });\n }\n }\n}\n\nfunction collectProjectSignatureManifestFiles(\n projectDir: string,\n files: ProjectSignatureFile[],\n): void {\n const seen = new Set(files.map((entry) => entry.file));\n for (const manifestPath of STUDIO_SIGNATURE_MANIFEST_PATHS) {\n const file = resolve(projectDir, manifestPath);\n if (seen.has(file) || !isPathWithin(projectDir, file)) continue;\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(file);\n } catch {\n continue;\n }\n if (stat.isSymbolicLink() || !stat.isFile()) continue;\n files.push({\n file,\n mtimeMs: stat.mtimeMs,\n ctimeMs: stat.ctimeMs,\n size: stat.size,\n textContentEligible: isTextContentEligible(file, stat.size),\n });\n seen.add(file);\n }\n}\n\nfunction createProjectFingerprint(projectDir: string, files: ProjectSignatureFile[]): string {\n const hash = createHash(\"sha256\");\n for (const entry of files) {\n hash.update(relative(projectDir, entry.file));\n hash.update(\"\\0\");\n hash.update(String(entry.size));\n hash.update(\"\\0\");\n hash.update(String(entry.mtimeMs));\n hash.update(\"\\0\");\n hash.update(String(entry.ctimeMs));\n hash.update(\"\\0\");\n hash.update(entry.textContentEligible ? \"text\" : \"binary\");\n hash.update(\"\\0\");\n }\n return hash.digest(\"hex\").slice(0, 24);\n}\n\nfunction collectProjectFiles(normalizedProjectDir: string): ProjectSignatureFile[] {\n const collected: ProjectSignatureFile[] = [];\n collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir, collected);\n collectProjectSignatureManifestFiles(normalizedProjectDir, collected);\n return collected;\n}\n\n/** The files a project is made of (the signature's set: source plus Studio's two manifests), paths with `/`. */\nexport function listProjectFiles(\n projectDir: string,\n): Array<{ path: string; size: number; mtimeMs: number; ctimeMs: number }> {\n const normalizedProjectDir = resolve(projectDir);\n return collectProjectFiles(normalizedProjectDir).map((entry) => ({\n path: relative(normalizedProjectDir, entry.file).split(sep).join(\"/\"),\n size: entry.size,\n mtimeMs: entry.mtimeMs,\n ctimeMs: entry.ctimeMs,\n }));\n}\n\n/**\n * Resolve the project signature through the adapter's cached path when the host\n * provides one (the CLI invalidates its cache from the file watcher), falling\n * back to a direct computation.\n */\nexport function resolveProjectSignature(adapter: StudioApiAdapter, projectDir: string): string {\n return adapter.getProjectSignature?.(projectDir) ?? createProjectSignature(projectDir);\n}\n\n/** The shared route opening: resolve the project (null → caller 404s) with its signature. */\nexport async function resolveProjectAndSignature(\n adapter: StudioApiAdapter,\n projectId: string,\n): Promise<{ project: ResolvedProject; signature: string } | null> {\n const project = await adapter.resolveProject(projectId);\n if (!project) return null;\n return { project, signature: resolveProjectSignature(adapter, project.dir) };\n}\n\n/**\n * Creates a stable preview cache-busting signature for project source plus Studio manifests.\n * `excluding` (project-relative paths) leaves those files out.\n */\nexport function createProjectSignature(\n projectDir: string,\n excluding: ReadonlySet<string> = new Set(),\n): string {\n const normalizedProjectDir = resolve(projectDir);\n const collected = collectProjectFiles(normalizedProjectDir);\n const files = collected.filter(\n (entry) => !excluding.has(relative(normalizedProjectDir, entry.file).split(sep).join(\"/\")),\n );\n files.sort((a, b) => a.file.localeCompare(b.file));\n\n const fingerprint = createProjectFingerprint(normalizedProjectDir, files);\n const cacheKey = excluding.size\n ? `${normalizedProjectDir}\\0${createHash(\"sha256\")\n .update([...excluding].sort().join(\"\\0\"))\n .digest(\"hex\")}`\n : normalizedProjectDir;\n const cached = projectSignatureCache.get(cacheKey);\n if (cached?.fingerprint === fingerprint) return cached.signature;\n\n const hash = createHash(\"sha256\");\n for (const entry of files) {\n const relativePath = relative(normalizedProjectDir, entry.file);\n hash.update(relativePath);\n hash.update(\"\\0\");\n hash.update(String(entry.size));\n hash.update(\"\\0\");\n if (entry.textContentEligible) {\n try {\n hash.update(readFileSync(entry.file));\n } catch {\n hash.update(String(entry.mtimeMs));\n }\n } else {\n hash.update(String(entry.mtimeMs));\n }\n hash.update(\"\\0\");\n }\n const signature = hash.digest(\"hex\").slice(0, 24);\n projectSignatureCache.set(cacheKey, { fingerprint, signature });\n return signature;\n}\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { constants, createReadStream, renameSync } from \"node:fs\";\nimport { copyFile, mkdir, readdir, readFile, rename, rm, stat } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\n/** File contents stored once by sha256, text and binary alike. */\nexport interface BlobStore {\n /** Copies the file in (a clone where the file system can) and returns the copy's hash. */\n put(absPath: string): Promise<string>;\n has(hash: string): boolean;\n read(hash: string): Promise<Buffer>;\n /** Writes the blob's bytes to `absPath` by clone-or-copy and rename, so a reader never sees half a file. */\n writeTo(hash: string, absPath: string, beforeReplace?: () => void): Promise<void>;\n bytes(): number;\n size(hash: string): number;\n prune(keep: ReadonlySet<string>): Promise<void>;\n}\n\nconst BLOB_HASH = /^[0-9a-f]{64}$/;\n\nasync function hashFile(path: string): Promise<string> {\n const hash = createHash(\"sha256\");\n for await (const chunk of createReadStream(path)) hash.update(chunk);\n return hash.digest(\"hex\");\n}\n\nasync function cloneOrCopy(from: string, to: string, beforeReplace?: () => void): Promise<void> {\n await mkdir(dirname(to), { recursive: true });\n const temp = `${to}.${randomUUID()}.tmp`;\n try {\n await copyFile(from, temp, constants.COPYFILE_FICLONE);\n beforeReplace?.();\n renameSync(temp, to);\n } catch (error) {\n await rm(temp, { force: true });\n throw error;\n }\n}\n\nexport async function openBlobStore(dir: string): Promise<BlobStore> {\n const sizes = new Map<string, number>();\n await mkdir(dir, { recursive: true });\n for (const shard of await readdir(dir))\n for (const hash of await readdir(join(dir, shard)).catch(() => []))\n sizes.set(hash, (await stat(join(dir, shard, hash))).size);\n const pathOf = (hash: string) => {\n // Checked where the path is joined, so no caller can read or write outside the store.\n if (!BLOB_HASH.test(hash)) throw new Error(\"That is not a history blob.\");\n return join(dir, hash.slice(0, 2), hash);\n };\n let total = [...sizes.values()].reduce((sum, size) => sum + size, 0);\n\n return {\n async put(absPath) {\n // Hash the copy, not the source, so a write racing the copy can never file bytes under the wrong hash.\n const temp = join(dir, `incoming-${randomUUID()}`);\n // Recreated if removed while open, so a missing folder is never mistaken for a deleted project file.\n await mkdir(dir, { recursive: true });\n await copyFile(absPath, temp, constants.COPYFILE_FICLONE);\n const hash = await hashFile(temp);\n if (sizes.has(hash)) {\n await rm(temp, { force: true });\n return hash;\n }\n await mkdir(dirname(pathOf(hash)), { recursive: true });\n const size = (await stat(temp)).size;\n await rename(temp, pathOf(hash));\n sizes.set(hash, size);\n total += size;\n return hash;\n },\n has: (hash) => sizes.has(hash),\n read: async (hash) => readFile(pathOf(hash)),\n writeTo: async (hash, absPath, beforeReplace) =>\n cloneOrCopy(pathOf(hash), absPath, beforeReplace),\n bytes: () => total,\n size: (hash) => sizes.get(hash) ?? 0,\n async prune(keep) {\n for (const [hash, size] of sizes) {\n if (keep.has(hash)) continue;\n await rm(pathOf(hash), { force: true });\n sizes.delete(hash);\n total -= size;\n }\n },\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport { readdirSync, readFileSync, renameSync, statSync, writeFileSync } from \"node:fs\";\nimport { lstat, readdir, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { isSafePath } from \"../helpers/safePath.js\";\nimport { ID_PATH, isHistoryId, readId, readRecord } from \"./historyId.js\";\nimport { HistoryBusyError, takeHistoryOwnership } from \"./ownerLock.js\";\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** A gone project's history stays this long after its last use, so a moved project reopened in time keeps it. */\nexport const KEEP_GONE_PROJECT_HISTORY_MS = 14 * DAY_MS;\nconst PRUNED_PREFIX = \".pruned-\";\nconst LAST_PRUNE_FILE = \".last-prune\";\n\ninterface PruneOptions {\n now?: number;\n /** Reports what would go, with the same checks, and removes nothing. */\n dryRun?: boolean;\n /** Projects under this folder are scratch: their history goes as soon as they do. */\n tempDir?: string;\n /** One history that could not be checked or removed; the rest are still pruned. */\n onError?: (error: unknown) => void;\n /** Starts no further history after this long; the rest wait for the next run. */\n budgetMs?: number;\n}\n\nexport interface ProjectHistoryRecord {\n id: string;\n projectDir: string;\n lastUsedMs: number;\n}\n\n/** Every history whose project is still in the folder it was recorded for; reads only. */\nexport function listProjectHistories(historyRoot: string): ProjectHistoryRecord[] {\n return namesIn(historyRoot)\n .filter(isHistoryId)\n .flatMap((id) => {\n const home = join(historyRoot, id);\n const projectDir = readRecord(home)?.dir;\n if (typeof projectDir !== \"string\" || readId(projectDir) !== id) return [];\n return [{ id, projectDir, lastUsedMs: lastUsed(home) }];\n });\n}\n\nfunction namesIn(historyRoot: string): string[] {\n try {\n return readdirSync(historyRoot);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw error;\n }\n}\n\nexport interface PrunedHistory {\n id: string;\n projectDir: string;\n bytes: number;\n}\n\n/** Removes the histories whose project is gone and that no live process holds. */\nexport async function pruneGoneProjectHistories(\n historyRoot: string,\n options: PruneOptions = {},\n): Promise<PrunedHistory[]> {\n return (await pruneWithin(historyRoot, options)).pruned;\n}\n\nasync function pruneWithin(\n historyRoot: string,\n {\n now = Date.now(),\n dryRun = false,\n tempDir = tmpdir(),\n onError = (error) => console.warn(\"[history] could not prune a history:\", error),\n budgetMs = Infinity,\n measure = true,\n }: PruneOptions & { measure?: boolean },\n): Promise<{ pruned: PrunedHistory[]; finished: boolean }> {\n const started = performance.now();\n const names = namesIn(historyRoot);\n const pruned: PrunedHistory[] = [];\n for (const name of names) {\n if (performance.now() - started >= budgetMs) return { pruned, finished: false };\n try {\n const gone = await pruneEntry(historyRoot, name, { now, dryRun, tempDir, measure });\n if (gone) pruned.push(gone);\n } catch (error) {\n onError(error);\n }\n }\n return { pruned, finished: true };\n}\n\nasync function pruneEntry(historyRoot: string, name: string, run: PruneRun) {\n if (name.startsWith(PRUNED_PREFIX)) {\n if (!run.dryRun) await rm(join(historyRoot, name), { recursive: true, force: true });\n return null;\n }\n return isHistoryId(name) ? pruneOne(historyRoot, name, run) : null;\n}\n\ntype PruneRun = { now: number; dryRun: boolean; tempDir: string; measure: boolean };\n\nasync function pruneOne(\n historyRoot: string,\n id: string,\n run: PruneRun,\n): Promise<PrunedHistory | null> {\n const home = join(historyRoot, id);\n if (abandonedProject(home, id, run.now, run.tempDir) === null) return null;\n const release = await takeUnlessBusy(home);\n if (!release) return null;\n let taken: { projectDir: string; trash: string | null } | null;\n try {\n taken = recheckAndMoveAway(historyRoot, id, run);\n } catch (error) {\n release();\n throw error;\n }\n if (!taken?.trash) release();\n if (!taken) return null;\n const bytes = await clearAway(taken.trash, home, run.measure);\n return { id, projectDir: taken.projectDir, bytes };\n}\n\n/** Removes the moved-away history, if any; its size when asked, measured first. */\nasync function clearAway(trash: string | null, home: string, measure: boolean): Promise<number> {\n const bytes = measure ? await bytesIn(trash ?? home) : 0;\n if (trash) await rm(trash, { recursive: true, force: true });\n return bytes;\n}\n\nasync function takeUnlessBusy(home: string): Promise<(() => void) | null> {\n try {\n return await takeHistoryOwnership(home, 0);\n } catch (error) {\n if (error instanceof HistoryBusyError) return null;\n throw error;\n }\n}\n\nfunction recheckAndMoveAway(historyRoot: string, id: string, { now, dryRun, tempDir }: PruneRun) {\n const home = join(historyRoot, id);\n // Re-read under the lock: an open that recorded this project again meanwhile keeps it.\n const projectDir = abandonedProject(home, id, now, tempDir);\n if (projectDir === null) return null;\n if (dryRun) return { projectDir, trash: null };\n // One rename takes the lock along, so an interrupted delete leaves a `.pruned-` folder the next run finishes.\n const trash = join(historyRoot, `${PRUNED_PREFIX}${id}-${randomUUID()}`);\n renameSync(home, trash);\n return { projectDir, trash };\n}\n\n/** The folder a history was recorded for, when that project is gone; null while it may still need its history. */\nfunction abandonedProject(home: string, id: string, now: number, tempDir: string): string | null {\n const record = readRecord(home);\n const dir = record?.dir;\n if (typeof dir !== \"string\") return null;\n if (now - lastUsed(home) < KEEP_GONE_PROJECT_HISTORY_MS && !isSafePath(tempDir, dir)) return null;\n return projectGone(dir, id, record?.dev, tempDir) ? dir : null;\n}\n\nfunction projectGone(dir: string, id: string, dev: unknown, tempDir: string): boolean {\n if (!statSync(join(dir, ID_PATH), { throwIfNoEntry: false }))\n return diskStillHere(dir, dev, tempDir);\n // An id file this version cannot read is refused, never replaced, so its history is still the project's.\n const there = readId(dir);\n return there !== null && there !== id;\n}\n\n/** A project without its id is gone only if the disk that held it is here: an unmounted drive or share keeps it. */\nfunction diskStillHere(dir: string, dev: unknown, tempDir: string): boolean {\n let at = dir;\n let stat = statSync(at, { throwIfNoEntry: false });\n while (!stat) {\n if (dirname(at) === at) return false;\n at = dirname(at);\n stat = statSync(at, { throwIfNoEntry: false });\n }\n if (typeof dev === \"number\") return stat.dev === dev;\n // Recorded before `dev`: a missing folder may sit on an unmounted disk, so only an emptied folder or temp counts.\n return at === dir || isSafePath(tempDir, at);\n}\n\nfunction lastUsed(home: string): number {\n const mtime = (file: string) =>\n statSync(join(home, file), { throwIfNoEntry: false })?.mtimeMs ?? 0;\n return Math.max(mtime(\"project.json\"), mtime(\"log.jsonl\"));\n}\n\nasync function bytesIn(dir: string): Promise<number> {\n let total = 0;\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const path = join(dir, entry.name);\n total += entry.isDirectory() ? await bytesIn(path) : (await lstat(path)).size;\n }\n return total;\n}\n\nfunction prunedWithinADay(stamp: string, now: number): boolean {\n try {\n const last = Number(readFileSync(stamp, \"utf-8\"));\n return last <= now && now - last < DAY_MS;\n } catch {\n return false;\n }\n}\n\nconst pruning = new Set<string>();\n\n/** Prunes `historyRoot` in the background until one run finishes a day, whichever process opens a history first. */\nexport function pruneGoneProjectHistoriesDaily(\n historyRoot: string,\n now: number,\n { onError, budgetMs }: Pick<PruneOptions, \"onError\" | \"budgetMs\"> = {},\n): void {\n const stamp = join(historyRoot, LAST_PRUNE_FILE);\n if (pruning.has(historyRoot) || prunedWithinADay(stamp, now)) return;\n pruning.add(historyRoot);\n setImmediate(() => {\n pruneWithin(historyRoot, { now, onError, budgetMs, measure: false })\n .then(({ finished }) => finished && writeFileSync(stamp, String(now)))\n .catch((error) => onError?.(error))\n .finally(() => pruning.delete(historyRoot));\n });\n}\n","import { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { replaceFileAtomically } from \"../helpers/atomicFile.js\";\nimport { mkdirWithinProject } from \"../helpers/safePath.js\";\n\nexport const ID_PATH = join(\".hyperframes\", \"history-id\");\n/** The only shape minted here; the id is project content and becomes a path, so nothing else is trusted. */\nconst ID_SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\nexport const isHistoryId = (name: string) => ID_SHAPE.test(name);\n\nexport function readId(projectDir: string): string | null {\n try {\n const id = readFileSync(join(projectDir, ID_PATH), \"utf-8\").trim();\n return ID_SHAPE.test(id) ? id : null;\n } catch {\n return null;\n }\n}\n\nexport type FolderIdentity = { ino: number; birthtimeMs: number };\n\nexport const sameFolder = (a: FolderIdentity, b: FolderIdentity) =>\n a.ino === b.ino && a.birthtimeMs === b.birthtimeMs;\n\nexport function isRecordedFolder(historyDir: string, folder: FolderIdentity): boolean {\n const was = readRecord(historyDir);\n if (typeof was?.ino === \"number\")\n return sameFolder({ ino: was.ino, birthtimeMs: was.born ?? NaN }, folder);\n const unreadableSo0878KeptTheId = typeof was?.dir !== \"string\";\n if (unreadableSo0878KeptTheId) return true;\n return !isCopyOf0878Folder(was!.dir as string, basename(historyDir), folder);\n}\n\nexport function readRecord(\n historyDir: string,\n): { dir?: unknown; ino?: unknown; born?: number; dev?: unknown } | null {\n try {\n return JSON.parse(readFileSync(join(historyDir, \"project.json\"), \"utf-8\"));\n } catch {\n return null;\n }\n}\n\nfunction isCopyOf0878Folder(recordedDir: string, id: string, folder: FolderIdentity): boolean {\n let there;\n try {\n there = statSync(recordedDir, { throwIfNoEntry: false });\n } catch {\n return false;\n }\n return !!there && !sameFolder(there, folder) && readId(recordedDir) === id;\n}\n\n/**\n * The project's history id, kept in the project so a rename or move keeps its history; only the folder its history\n * was recorded for keeps it (isRecordedFolder). An id file that cannot be read is refused, never replaced.\n */\nexport function projectHistoryId(projectDir: string, historyRoot: string): string {\n const dir = resolve(projectDir);\n const folder = statSync(dir);\n let id = readId(dir);\n if (!id && existsSync(join(dir, ID_PATH)))\n throw new HistoryIdError(\n `${join(dir, ID_PATH)} holds no history id this version can read; move it aside to start anew.`,\n );\n if (\n !id ||\n (existsSync(join(historyRoot, id)) && !isRecordedFolder(join(historyRoot, id), folder))\n ) {\n id = randomUUID();\n mkdirWithinProject(dir, join(dir, \".hyperframes\"));\n writeFileSync(join(dir, ID_PATH), `${id}\\n`);\n }\n recordProject(join(historyRoot, id), dir, folder);\n return id;\n}\n\nexport class HistoryIdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"HistoryIdError\";\n }\n}\n\nexport function recordProject(\n historyDir: string,\n dir: string,\n folder: FolderIdentity & { dev: number },\n): void {\n const record = { dir, ino: folder.ino, born: folder.birthtimeMs, dev: folder.dev };\n mkdirSync(historyDir, { recursive: true });\n replaceFileAtomically(join(historyDir, \"project.json\"), JSON.stringify(record), 0o644);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { linkSync, mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport class HistoryBusyError extends Error {\n constructor(readonly pid: number) {\n super(`This project's history is open in another process (pid ${pid}).`);\n this.name = \"HistoryBusyError\";\n }\n}\n\nfunction alive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** The pid in `file`; null when there is no file, NaN when it holds no pid (so it reads as dead). */\nfunction ownerOf(file: string): number | null {\n try {\n const text = readFileSync(file, \"utf-8\");\n return /^\\d+$/.test(text) ? Number(text) : Number.NaN;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n\n/** Takes `file` if nobody holds it: written aside and linked in, so a reader never sees it without its pid. */\nfunction claim(file: string): boolean {\n const draft = `${file}-${randomUUID()}.tmp`;\n writeFileSync(draft, String(process.pid));\n try {\n linkSync(draft, file);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n return false;\n } finally {\n rmSync(draft, { force: true });\n }\n}\n\n/** Removes `file` only while it names this process, so a release never takes a later owner's lock. */\nfunction releaseOwn(file: string): void {\n if (ownerOf(file) === process.pid) rmSync(file, { force: true });\n}\n\n/**\n * Removes a dead owner's lock under an evict lock, re-reading the owner, so a live owner's lock survives. False when\n * another evictor holds it. ponytail: a crashed evictor's lock is cleared unguarded; racing that can give two owners.\n */\nfunction evictDeadOwner(file: string): boolean {\n const evictor = `${file}.evict`;\n if (!claim(evictor)) {\n const holder = ownerOf(evictor);\n if (holder !== null && !alive(holder)) rmSync(evictor, { force: true });\n return false;\n }\n try {\n const pid = ownerOf(file);\n if (pid !== null && !alive(pid)) rmSync(file, { force: true });\n return true;\n } finally {\n releaseOwn(evictor);\n }\n}\n\n/**\n * One process at a time keeps a project's history open (a second opener would fork the log). Waits up to `waitMs`\n * for the owner to close, takes over a dead owner's lock.\n */\nexport async function takeHistoryOwnership(home: string, waitMs: number): Promise<() => void> {\n const file = join(home, \"owner.pid\");\n const deadline = Date.now() + waitMs;\n mkdirSync(home, { recursive: true });\n for (;;) {\n if (claim(file)) {\n let held = true;\n return () => {\n if (held) releaseOwn(file);\n held = false;\n };\n }\n const pid = ownerOf(file);\n if (pid === null) continue;\n if (!alive(pid) && evictDeadOwner(file)) continue;\n if (Date.now() >= deadline) throw new HistoryBusyError(pid);\n await new Promise((settle) => setTimeout(settle, 50));\n }\n}\n","import { closeSync, constants, mkdirSync, openSync, readFileSync, writeSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { replaceFileAtomically } from \"../helpers/atomicFile.js\";\n\nexport interface HistoryWho {\n kind: \"person\" | \"agent\" | \"outside\";\n /** Shown in the History list: \"You\", the agent's name, \"Outside\". */\n name: string;\n}\n\n/** A file's sha256 before and after; null is \"did not exist\", so a create or delete undoes like any edit. */\nexport interface HistoryFileChange {\n path: string;\n before: string | null;\n after: string | null;\n}\n\nexport type HistoryEntrySide = \"before\" | \"after\";\n\nexport interface HistoryEntry {\n id: string;\n who: HistoryWho;\n label: string;\n startedAt: number;\n endedAt: number;\n files: HistoryFileChange[];\n /** Set on an undo: the entry it reverted. Redo is undoing the undo. */\n undoes?: string;\n /** Set on a restore: the point (an entry id, or START) the files were made equal to. */\n restoredTo?: string;\n}\n\n/** The point before the first kept entry. */\nexport const START = \"start\";\n\n/** Path to hash. */\nexport type Manifest = Map<string, string>;\n\nexport interface HistoryLog {\n baseline: Manifest;\n entries: HistoryEntry[];\n pins: Set<string>;\n}\n\nexport type LogRecord =\n | { type: \"baseline\"; files: Record<string, string> }\n | { type: \"entry\"; entry: HistoryEntry }\n | { type: \"pin\"; id: string; pinned: boolean };\n\nfunction parseRecord(line: string): LogRecord | null {\n try {\n return JSON.parse(line);\n } catch {\n return null;\n }\n}\n\n/** `onUnreadable` hears of a damaged line; a torn last line (a crash mid-append) is expected and dropped quietly. */\nexport function readLog(file: string, onUnreadable: (line: number) => void): HistoryLog | null {\n let text: string;\n try {\n text = readFileSync(file, \"utf-8\");\n } catch {\n return null;\n }\n const log: HistoryLog = { baseline: new Map(), entries: [], pins: new Set() };\n const lines = text.split(\"\\n\");\n lines.forEach((line, index) => {\n const record = line ? parseRecord(line) : null;\n if (record) applyRecord(log, record);\n else if (line && index < lines.length - 1) onUnreadable(index + 1);\n });\n return log;\n}\n\nfunction applyRecord(log: HistoryLog, record: LogRecord): void {\n if (record.type === \"baseline\") log.baseline = new Map(Object.entries(record.files));\n else if (record.type === \"entry\") log.entries.push(record.entry);\n else if (record.pinned) log.pins.add(record.id);\n else log.pins.delete(record.id);\n}\n\n/** Appends `record`, already applied to `log`; a log file that is gone is written whole, so a restart replays it. */\nexport function saveRecord(file: string, log: HistoryLog, record: LogRecord): void {\n let fd: number;\n try {\n // No O_CREAT: an append never creates a log that would lack its baseline.\n fd = openSync(file, constants.O_WRONLY | constants.O_APPEND);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n return writeLog(file, log);\n }\n try {\n writeSync(fd, `${JSON.stringify(record)}\\n`);\n } finally {\n closeSync(fd);\n }\n}\n\nexport function writeLog(file: string, log: HistoryLog): void {\n const records: LogRecord[] = [\n { type: \"baseline\", files: Object.fromEntries(log.baseline) },\n ...log.entries.map((entry) => ({ type: \"entry\" as const, entry })),\n ...[...log.pins].map((id) => ({ type: \"pin\" as const, id, pinned: true })),\n ];\n mkdirSync(dirname(file), { recursive: true });\n replaceFileAtomically(file, records.map((r) => `${JSON.stringify(r)}\\n`).join(\"\"), 0o644);\n}\n\nfunction applyEntry(manifest: Manifest, entry: HistoryEntry, side: HistoryEntrySide): void {\n for (const file of entry.files) {\n const hash = file[side];\n if (hash === null) manifest.delete(file.path);\n else manifest.set(file.path, hash);\n }\n}\n\n/** The files as they were right after `point`. Null when that point is no longer kept. */\nexport function manifestAt(log: HistoryLog, point: string): Manifest | null {\n return point === START ? new Map(log.baseline) : manifestAround(log, point, \"after\");\n}\n\n/** Entry `id`'s own files as it found (`before`) or left (`after`) them; the rest as earlier entries left them. */\nexport function manifestAround(\n log: HistoryLog,\n id: string,\n side: HistoryEntrySide,\n): Manifest | null {\n const manifest = new Map(log.baseline);\n for (const entry of log.entries) {\n applyEntry(manifest, entry, entry.id === id ? side : \"after\");\n if (entry.id === id) return manifest;\n }\n return null;\n}\n\n/** Entries currently reverted: an undo that is itself in effect reverts its target. */\nexport function undoneIds(entries: readonly HistoryEntry[]): Set<string> {\n const undone = new Set<string>();\n for (let i = entries.length - 1; i >= 0; i -= 1) {\n const entry = entries[i]!;\n if (entry.undoes && !undone.has(entry.id)) undone.add(entry.undoes);\n }\n return undone;\n}\n\n/**\n * What Cmd+Z (back) or Cmd+Shift+Z (forward) reverts for `mine`. Back: the newest change `mine` owns that is still\n * in effect. Forward: the newest of `mine`'s undos still in effect, while `mine` has made no change since.\n */\nexport function stepTarget(\n entries: readonly HistoryEntry[],\n direction: \"back\" | \"forward\",\n mine: (entry: HistoryEntry) => boolean,\n): HistoryEntry | undefined {\n const undone = undoneIds(entries);\n const byId = new Map(entries.map((entry) => [entry.id, entry]));\n const owns = ownerOf(entries, byId, mine);\n const newestFirst = [...entries].reverse();\n if (direction === \"back\")\n return newestFirst.find((entry) => !entry.undoes && !undone.has(entry.id) && owns(entry));\n for (const entry of newestFirst) {\n if (!entry.undoes) {\n if (owns(entry)) return undefined;\n continue;\n }\n const target = byId.get(entry.undoes);\n if (mine(entry) && !undone.has(entry.id) && target && !target.undoes) return entry;\n }\n return undefined;\n}\n\n/** A change is yours to step over if you made it, or your redo is what brought it back. */\nfunction ownerOf(\n entries: readonly HistoryEntry[],\n byId: ReadonlyMap<string, HistoryEntry>,\n mine: (entry: HistoryEntry) => boolean,\n): (entry: HistoryEntry) => boolean {\n const redoneBy = new Map<string, HistoryEntry>();\n for (const entry of entries) {\n const target = entry.undoes ? byId.get(entry.undoes) : undefined;\n if (target?.undoes) redoneBy.set(target.undoes, entry);\n }\n return (entry) => mine(entry) || mine(redoneBy.get(entry.id) ?? entry);\n}\n\n/**\n * Folds the oldest entry into the baseline unless it is pinned. ponytail: an old pin therefore holds everything\n * after it; per-point snapshots would lift that. Returns whether one was folded.\n */\nexport function foldOldest(log: HistoryLog): boolean {\n const oldest = log.entries[0];\n if (!oldest || log.pins.has(oldest.id)) return false;\n applyEntry(log.baseline, oldest, \"after\");\n log.entries.shift();\n return true;\n}\n\n/** Every hash a kept point can still need. */\nexport function referencedHashes(log: HistoryLog, current: Manifest): Set<string> {\n const keep = new Set([...log.baseline.values(), ...current.values()]);\n for (const entry of log.entries)\n for (const file of entry.files) {\n if (file.before) keep.add(file.before);\n if (file.after) keep.add(file.after);\n }\n return keep;\n}\n","import type { ProjectHistory } from \"./projectHistory.js\";\n\ntype Opened = Promise<ProjectHistory | null>;\n\n/** A host's histories, one per project folder, opened on first use; a new project at the path gets its own. */\nexport function historyCache(open: (projectDir: string) => Opened) {\n const histories = new Map<string, Opened>();\n const get = async (projectDir: string): Promise<ProjectHistory | null> => {\n let cached = histories.get(projectDir);\n if (!cached) histories.set(projectDir, (cached = open(projectDir)));\n const opened = await cached;\n if (!opened?.replacedAtPath()) return opened;\n const reopen = () => open(projectDir);\n if (histories.get(projectDir) === cached)\n histories.set(projectDir, opened.close().then(reopen, reopen));\n return get(projectDir);\n };\n return {\n get,\n /** The history if one was opened, without opening it. */\n peek: (projectDir: string) => histories.get(projectDir),\n forget: (projectDir: string) => histories.delete(projectDir),\n closeAll: () =>\n Promise.all(\n [...histories.values()].map((opened) => opened.then((history) => history?.close())),\n ),\n };\n}\n"],"mappings":"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","names":["randomUUID","readdir","rm","existsSync","lstatSync","statSync","mkdirSync","readdirSync","readFileSync","rmSync","dirname","isAbsolute","join","relative","resolve","sep","stat","createHash","stat","createHash","randomUUID","randomUUID","readdirSync","readFileSync","renameSync","statSync","writeFileSync","readdir","rm","dirname","join","randomUUID","readFileSync","join","resolve","join","readFileSync","resolve","randomUUID","randomUUID","mkdirSync","readFileSync","writeFileSync","join","join","readdirSync","rm","randomUUID","renameSync","statSync","stat","dirname","readdir","readFileSync","writeFileSync","constants","mkdirSync","readFileSync","dirname","readFileSync","constants","mkdirSync","dirname","ownerOf","join","readdir","readdirSync","statSync","resolve","existsSync","readFileSync","mkdirSync","stat","window","relative","sep","randomUUID","rm","lstatSync","rmSync","dirname","isAbsolute"]}
@@ -0,0 +1,22 @@
1
+ // src/helpers/atomicFile.ts
2
+ import * as fs from "fs";
3
+ import { randomUUID } from "crypto";
4
+ function replaceFileAtomically(filePath, content, mode, operations = fs) {
5
+ const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
6
+ try {
7
+ operations.writeFileSync(tempPath, content, { encoding: "utf-8", mode });
8
+ operations.chmodSync(tempPath, mode);
9
+ operations.renameSync(tempPath, filePath);
10
+ } catch (error) {
11
+ try {
12
+ operations.unlinkSync(tempPath);
13
+ } catch {
14
+ }
15
+ throw error;
16
+ }
17
+ }
18
+
19
+ export {
20
+ replaceFileAtomically
21
+ };
22
+ //# sourceMappingURL=chunk-PMXUKOC3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/atomicFile.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { randomUUID } from \"node:crypto\";\n\ntype AtomicFileSystem = Pick<\n typeof fs,\n \"writeFileSync\" | \"chmodSync\" | \"renameSync\" | \"unlinkSync\"\n>;\n\n/** Replace a file only after the complete sibling temp file is written. */\nexport function replaceFileAtomically(\n filePath: string,\n content: string | Uint8Array,\n mode: number,\n operations: AtomicFileSystem = fs,\n): void {\n const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n operations.writeFileSync(tempPath, content, { encoding: \"utf-8\", mode });\n operations.chmodSync(tempPath, mode);\n // Node fs.rename uses libuv uv_fs_rename; win32 calls MoveFileExW with MOVEFILE_REPLACE_EXISTING.\n operations.renameSync(tempPath, filePath);\n } catch (error) {\n try {\n operations.unlinkSync(tempPath);\n } catch {\n // Preserve the write error; cleanup is best effort.\n }\n throw error;\n }\n}\n"],"mappings":";AAAA,YAAY,QAAQ;AACpB,SAAS,kBAAkB;AAQpB,SAAS,sBACd,UACA,SACA,MACA,aAA+B,IACzB;AACN,QAAM,WAAW,GAAG,QAAQ,IAAI,QAAQ,GAAG,IAAI,WAAW,CAAC;AAC3D,MAAI;AACF,eAAW,cAAc,UAAU,SAAS,EAAE,UAAU,SAAS,KAAK,CAAC;AACvE,eAAW,UAAU,UAAU,IAAI;AAEnC,eAAW,WAAW,UAAU,QAAQ;AAAA,EAC1C,SAAS,OAAO;AACd,QAAI;AACF,iBAAW,WAAW,QAAQ;AAAA,IAChC,QAAQ;AAAA,IAER;AACA,UAAM;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,79 @@
1
+ // src/helpers/safePath.ts
2
+ import { basename, dirname, isAbsolute, join, relative, sep } from "path";
3
+ import { readdirSync } from "fs";
4
+ import { realpath, resolveWithinProject } from "@hyperframes/core/safe-path";
5
+ import {
6
+ folderGone,
7
+ isProjectRootMissing,
8
+ isSafePath,
9
+ mkdirWithinProject,
10
+ realpath as realpath2,
11
+ realProjectRoot,
12
+ resolveWithinProject as resolveWithinProject2
13
+ } from "@hyperframes/core/safe-path";
14
+ function realFilePath(filePath) {
15
+ try {
16
+ return realpath(filePath);
17
+ } catch {
18
+ const dir = dirname(filePath);
19
+ return dir === filePath ? filePath : join(realFilePath(dir), basename(filePath));
20
+ }
21
+ }
22
+ function pinWithinProject(base, relativePath) {
23
+ const checked = resolveWithinProject(base, relativePath);
24
+ if (!checked) return null;
25
+ const inside = relative(realFilePath(base), realFilePath(checked));
26
+ const escapes = inside === ".." || inside.startsWith(`..${sep}`) || isAbsolute(inside);
27
+ return escapes ? null : join(base, inside);
28
+ }
29
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
30
+ ".thumbnails",
31
+ ".transcode-cache",
32
+ ".waveform-cache",
33
+ "node_modules",
34
+ ".git"
35
+ ]);
36
+ function shouldIgnoreDir(rel) {
37
+ return rel === ".hyperframes/backup";
38
+ }
39
+ function isInHiddenOrVendorDir(relPath) {
40
+ const segments = relPath.split("/");
41
+ return segments.slice(0, -1).some((seg) => seg.startsWith(".") || seg === "node_modules");
42
+ }
43
+ var UNREADABLE_DIR_CODES = /* @__PURE__ */ new Set(["EACCES", "EPERM", "ENOENT", "ENOTDIR"]);
44
+ function walkDir(dir, prefix = "") {
45
+ let entries;
46
+ try {
47
+ entries = readdirSync(dir, { withFileTypes: true });
48
+ } catch (err) {
49
+ const code = err.code;
50
+ if (!prefix || !code || !UNREADABLE_DIR_CODES.has(code)) throw err;
51
+ return [];
52
+ }
53
+ const files = [];
54
+ for (const entry of entries) {
55
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
56
+ if (IGNORE_DIRS.has(entry.name) || shouldIgnoreDir(rel)) continue;
57
+ if (entry.isDirectory()) {
58
+ files.push(...walkDir(join(dir, entry.name), rel));
59
+ } else {
60
+ files.push(rel);
61
+ }
62
+ }
63
+ return files;
64
+ }
65
+
66
+ export {
67
+ realFilePath,
68
+ pinWithinProject,
69
+ isInHiddenOrVendorDir,
70
+ walkDir,
71
+ folderGone,
72
+ isProjectRootMissing,
73
+ isSafePath,
74
+ mkdirWithinProject,
75
+ realpath2 as realpath,
76
+ realProjectRoot,
77
+ resolveWithinProject2 as resolveWithinProject
78
+ };
79
+ //# sourceMappingURL=chunk-TTWLTYSQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/safePath.ts"],"sourcesContent":["import { basename, dirname, isAbsolute, join, relative, sep } from \"node:path\";\nimport { readdirSync, type Dirent } from \"node:fs\";\nimport { realpath, resolveWithinProject } from \"@hyperframes/core/safe-path\";\n\n// `isSafePath` lives at the package root so non-studio-api layers (compiler,\n// CLI, engine) can share it without a backwards dependency on studio-api.\n// Re-exported here for back-compat with existing `../helpers/safePath.js` imports.\nexport {\n folderGone,\n isProjectRootMissing,\n isSafePath,\n mkdirWithinProject,\n realpath,\n realProjectRoot,\n resolveWithinProject,\n} from \"@hyperframes/core/safe-path\";\n\n/** The real path; for a path not there (yet, or any more), the nearest existing folder's real path plus the rest. */\nexport function realFilePath(filePath: string): string {\n try {\n return realpath(filePath);\n } catch {\n const dir = dirname(filePath);\n return dir === filePath ? filePath : join(realFilePath(dir), basename(filePath));\n }\n}\n\n/**\n * `resolveWithinProject` with every link inside the project resolved now, so a write to the result lands on the\n * file checked here even if a link on the way is retargeted before the write.\n */\nexport function pinWithinProject(base: string, relativePath: string): string | null {\n const checked = resolveWithinProject(base, relativePath);\n if (!checked) return null;\n const inside = relative(realFilePath(base), realFilePath(checked));\n const escapes = inside === \"..\" || inside.startsWith(`..${sep}`) || isAbsolute(inside);\n return escapes ? null : join(base, inside);\n}\n\nconst IGNORE_DIRS = new Set([\n \".thumbnails\",\n \".transcode-cache\",\n \".waveform-cache\",\n \"node_modules\",\n \".git\",\n]);\n\nfunction shouldIgnoreDir(rel: string): boolean {\n return rel === \".hyperframes/backup\";\n}\n\n/**\n * True when any directory segment of a relative path is a dot-directory or\n * node_modules. Projects that vendor tooling assets under dot-directories\n * (.hyperframes/, .cache/, …) ship example/preset HTML that must not surface\n * as project compositions or studio lint targets (#1384). The file tree is\n * deliberately not filtered — this only gates discovery.\n */\nexport function isInHiddenOrVendorDir(relPath: string): boolean {\n const segments = relPath.split(\"/\");\n return segments.slice(0, -1).some((seg) => seg.startsWith(\".\") || seg === \"node_modules\");\n}\n\nconst UNREADABLE_DIR_CODES = new Set([\"EACCES\", \"EPERM\", \"ENOENT\", \"ENOTDIR\"]);\n\n/** Recursively walk a directory and return relative file paths. A subfolder that is unreadable\n * or removed mid-walk is skipped; any other failure, or an unreadable `dir` itself, throws. */\nexport function walkDir(dir: string, prefix = \"\"): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (!prefix || !code || !UNREADABLE_DIR_CODES.has(code)) throw err;\n return [];\n }\n const files: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (IGNORE_DIRS.has(entry.name) || shouldIgnoreDir(rel)) continue;\n if (entry.isDirectory()) {\n files.push(...walkDir(join(dir, entry.name), rel));\n } else {\n files.push(rel);\n }\n }\n return files;\n}\n"],"mappings":";AAAA,SAAS,UAAU,SAAS,YAAY,MAAM,UAAU,WAAW;AACnE,SAAS,mBAAgC;AACzC,SAAS,UAAU,4BAA4B;AAK/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAAA;AAAA,EACA;AAAA,EACA,wBAAAC;AAAA,OACK;AAGA,SAAS,aAAa,UAA0B;AACrD,MAAI;AACF,WAAO,SAAS,QAAQ;AAAA,EAC1B,QAAQ;AACN,UAAM,MAAM,QAAQ,QAAQ;AAC5B,WAAO,QAAQ,WAAW,WAAW,KAAK,aAAa,GAAG,GAAG,SAAS,QAAQ,CAAC;AAAA,EACjF;AACF;AAMO,SAAS,iBAAiB,MAAc,cAAqC;AAClF,QAAM,UAAU,qBAAqB,MAAM,YAAY;AACvD,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAAS,SAAS,aAAa,IAAI,GAAG,aAAa,OAAO,CAAC;AACjE,QAAM,UAAU,WAAW,QAAQ,OAAO,WAAW,KAAK,GAAG,EAAE,KAAK,WAAW,MAAM;AACrF,SAAO,UAAU,OAAO,KAAK,MAAM,MAAM;AAC3C;AAEA,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,gBAAgB,KAAsB;AAC7C,SAAO,QAAQ;AACjB;AASO,SAAS,sBAAsB,SAA0B;AAC9D,QAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,SAAO,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK,CAAC,QAAQ,IAAI,WAAW,GAAG,KAAK,QAAQ,cAAc;AAC1F;AAEA,IAAM,uBAAuB,oBAAI,IAAI,CAAC,UAAU,SAAS,UAAU,SAAS,CAAC;AAItE,SAAS,QAAQ,KAAa,SAAS,IAAc;AAC1D,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACpD,SAAS,KAAK;AACZ,UAAM,OAAQ,IAA8B;AAC5C,QAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,qBAAqB,IAAI,IAAI,EAAG,OAAM;AAC/D,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AACvD,QAAI,YAAY,IAAI,MAAM,IAAI,KAAK,gBAAgB,GAAG,EAAG;AACzD,QAAI,MAAM,YAAY,GAAG;AACvB,YAAM,KAAK,GAAG,QAAQ,KAAK,KAAK,MAAM,IAAI,GAAG,GAAG,CAAC;AAAA,IACnD,OAAO;AACL,YAAM,KAAK,GAAG;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;","names":["realpath","resolveWithinProject"]}
@@ -0,0 +1,100 @@
1
+ import {
2
+ replaceFileAtomically
3
+ } from "./chunk-PMXUKOC3.js";
4
+ import {
5
+ isInHiddenOrVendorDir,
6
+ mkdirWithinProject,
7
+ walkDir
8
+ } from "./chunk-TTWLTYSQ.js";
9
+
10
+ // src/helpers/hfIdPersist.ts
11
+ import { ensureHfIds } from "@hyperframes/parsers/hf-ids";
12
+ import { createHash } from "crypto";
13
+ import { closeSync, constants, fstatSync, openSync, readFileSync, writeFileSync } from "fs";
14
+ import { dirname, join } from "path";
15
+ var isCompositionSource = (html) => /data-composition-id\s*=/.test(html);
16
+ var STAMP_RECORD = join(".hyperframes", "hf-ids-stamped.json");
17
+ var contentHash = (text) => createHash("sha1").update(text).digest("base64url");
18
+ function stampProjectHfIds(projectDir) {
19
+ const recordPath = join(projectDir, STAMP_RECORD);
20
+ let last = {};
21
+ try {
22
+ last = JSON.parse(readFileSync(recordPath, "utf-8"));
23
+ } catch {
24
+ }
25
+ const next = {};
26
+ for (const file of walkDir(projectDir)) {
27
+ if (!file.endsWith(".html") || isInHiddenOrVendorDir(file)) continue;
28
+ const absPath = join(projectDir, file);
29
+ let html;
30
+ try {
31
+ html = readFileSync(absPath, "utf-8");
32
+ } catch {
33
+ continue;
34
+ }
35
+ let hash = contentHash(html);
36
+ if (last[file] !== hash && isCompositionSource(html)) {
37
+ const stamped = stampFileHfIds(absPath);
38
+ if (stamped === null) continue;
39
+ hash = contentHash(stamped);
40
+ }
41
+ next[file] = hash;
42
+ }
43
+ try {
44
+ mkdirWithinProject(projectDir, dirname(recordPath));
45
+ writeFileSync(recordPath, JSON.stringify(next));
46
+ } catch {
47
+ }
48
+ }
49
+ function openNoFollow(filePath, flags) {
50
+ const noFollow = constants.O_NOFOLLOW ?? 0;
51
+ try {
52
+ return openSync(filePath, flags | noFollow);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function isUnchanged(filePath, expected) {
58
+ const fd = openNoFollow(filePath, constants.O_RDONLY);
59
+ if (fd === null) return false;
60
+ try {
61
+ return readFileSync(fd, "utf-8") === expected;
62
+ } finally {
63
+ closeSync(fd);
64
+ }
65
+ }
66
+ function stampFileHfIds(filePath) {
67
+ let fd = openNoFollow(filePath, constants.O_RDWR);
68
+ let writable = true;
69
+ if (fd === null) {
70
+ fd = openNoFollow(filePath, constants.O_RDONLY);
71
+ writable = false;
72
+ }
73
+ if (fd === null) return null;
74
+ try {
75
+ if (!fstatSync(fd).isFile()) return null;
76
+ const html = readFileSync(fd, "utf-8");
77
+ const normalized = ensureHfIds(html);
78
+ const idsBefore = (html.match(/\bdata-hf-id=/g) ?? []).length;
79
+ const idsAfter = (normalized.match(/\bdata-hf-id=/g) ?? []).length;
80
+ if (writable && idsAfter > idsBefore) {
81
+ const mode = fstatSync(fd).mode;
82
+ closeSync(fd);
83
+ fd = null;
84
+ if (isUnchanged(filePath, html)) replaceFileAtomically(filePath, normalized, mode);
85
+ }
86
+ return normalized;
87
+ } catch (err) {
88
+ console.warn("[hyperframes] stampFileHfIds: failed to stamp ids:", err);
89
+ return null;
90
+ } finally {
91
+ if (fd !== null) closeSync(fd);
92
+ }
93
+ }
94
+
95
+ export {
96
+ isCompositionSource,
97
+ stampProjectHfIds,
98
+ stampFileHfIds
99
+ };
100
+ //# sourceMappingURL=chunk-WUHW3MWG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/hfIdPersist.ts"],"sourcesContent":["import { ensureHfIds } from \"@hyperframes/parsers/hf-ids\";\nimport { createHash } from \"node:crypto\";\nimport { closeSync, constants, fstatSync, openSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { replaceFileAtomically } from \"./atomicFile.js\";\nimport { isInHiddenOrVendorDir, mkdirWithinProject, walkDir } from \"./safePath.js\";\n\nexport const isCompositionSource = (html: string): boolean => /data-composition-id\\s*=/.test(html);\n\nconst STAMP_RECORD = join(\".hyperframes\", \"hf-ids-stamped.json\");\n\nconst contentHash = (text: string): string => createHash(\"sha1\").update(text).digest(\"base64url\");\n\n/** Pins hf-ids into every composition in the project. A host runs it once before it serves or\n * watches the project: an id write during a session reaches Studio as an outside edit and reloads it.\n * A file whose content hash matches the last run's (kept in `.hyperframes`) is not parsed again. */\nexport function stampProjectHfIds(projectDir: string): void {\n const recordPath = join(projectDir, STAMP_RECORD);\n let last: Record<string, string> = {};\n try {\n last = JSON.parse(readFileSync(recordPath, \"utf-8\"));\n } catch {\n // first run, or an unreadable record: parse everything\n }\n const next: Record<string, string> = {};\n for (const file of walkDir(projectDir)) {\n if (!file.endsWith(\".html\") || isInHiddenOrVendorDir(file)) continue;\n const absPath = join(projectDir, file);\n let html: string;\n try {\n html = readFileSync(absPath, \"utf-8\");\n } catch {\n continue; // unreadable now; the preview route stamps it when it is served\n }\n let hash = contentHash(html);\n if (last[file] !== hash && isCompositionSource(html)) {\n const stamped = stampFileHfIds(absPath);\n if (stamped === null) continue;\n hash = contentHash(stamped);\n }\n next[file] = hash;\n }\n try {\n mkdirWithinProject(projectDir, dirname(recordPath));\n writeFileSync(recordPath, JSON.stringify(next));\n } catch {\n // read-only project: the next start parses again\n }\n}\n\nfunction openNoFollow(filePath: string, flags: number): number | null {\n // O_NOFOLLOW is undefined on Windows; opening without it is the platform norm there.\n const noFollow = constants.O_NOFOLLOW ?? 0;\n try {\n return openSync(filePath, flags | noFollow);\n } catch {\n return null;\n }\n}\n\nfunction isUnchanged(filePath: string, expected: string): boolean {\n const fd = openNoFollow(filePath, constants.O_RDONLY);\n if (fd === null) return false;\n try {\n return readFileSync(fd, \"utf-8\") === expected;\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Read `filePath`, mint any missing `data-hf-id`s, write the stamped content\n * back if new ids were added, and return the stamped content. Every open of the path is\n * no-follow (POSIX) and the write renames a sibling temp file over it, so a symlink swapped\n * in at the path is never read or written through (CodeQL js/file-system-race).\n *\n * Falls back to read-only stamping when the file isn't writable (read-only\n * fs, sandbox) — serving stamped content without persisting is still correct;\n * ids are content-keyed so the SDK mints the same ones from the same bytes.\n *\n * Returns null when the file is missing, unreadable, or not a regular file.\n *\n * A write that lands while ids are minted is kept: the file is replaced only if\n * it still holds the bytes that were stamped, else it is left for the next open or save.\n */\nexport function stampFileHfIds(filePath: string): string | null {\n let fd: number | null = openNoFollow(filePath, constants.O_RDWR);\n let writable = true;\n if (fd === null) {\n fd = openNoFollow(filePath, constants.O_RDONLY);\n writable = false;\n }\n if (fd === null) return null;\n try {\n if (!fstatSync(fd).isFile()) return null;\n const html = readFileSync(fd, \"utf-8\");\n const normalized = ensureHfIds(html);\n // Attribute count, not string equality — linkedom serialization normalizes\n // quote style/whitespace even when no ids were minted.\n const idsBefore = (html.match(/\\bdata-hf-id=/g) ?? []).length;\n const idsAfter = (normalized.match(/\\bdata-hf-id=/g) ?? []).length;\n if (writable && idsAfter > idsBefore) {\n const mode = fstatSync(fd).mode;\n closeSync(fd);\n fd = null;\n if (isUnchanged(filePath, html)) replaceFileAtomically(filePath, normalized, mode);\n }\n return normalized;\n } catch (err) {\n console.warn(\"[hyperframes] stampFileHfIds: failed to stamp ids:\", err);\n return null;\n } finally {\n if (fd !== null) closeSync(fd);\n }\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,WAAW,WAAW,WAAW,UAAU,cAAc,qBAAqB;AACvF,SAAS,SAAS,YAAY;AAIvB,IAAM,sBAAsB,CAAC,SAA0B,0BAA0B,KAAK,IAAI;AAEjG,IAAM,eAAe,KAAK,gBAAgB,qBAAqB;AAE/D,IAAM,cAAc,CAAC,SAAyB,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,WAAW;AAKzF,SAAS,kBAAkB,YAA0B;AAC1D,QAAM,aAAa,KAAK,YAAY,YAAY;AAChD,MAAI,OAA+B,CAAC;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC;AAAA,EACrD,QAAQ;AAAA,EAER;AACA,QAAM,OAA+B,CAAC;AACtC,aAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,QAAI,CAAC,KAAK,SAAS,OAAO,KAAK,sBAAsB,IAAI,EAAG;AAC5D,UAAM,UAAU,KAAK,YAAY,IAAI;AACrC,QAAI;AACJ,QAAI;AACF,aAAO,aAAa,SAAS,OAAO;AAAA,IACtC,QAAQ;AACN;AAAA,IACF;AACA,QAAI,OAAO,YAAY,IAAI;AAC3B,QAAI,KAAK,IAAI,MAAM,QAAQ,oBAAoB,IAAI,GAAG;AACpD,YAAM,UAAU,eAAe,OAAO;AACtC,UAAI,YAAY,KAAM;AACtB,aAAO,YAAY,OAAO;AAAA,IAC5B;AACA,SAAK,IAAI,IAAI;AAAA,EACf;AACA,MAAI;AACF,uBAAmB,YAAY,QAAQ,UAAU,CAAC;AAClD,kBAAc,YAAY,KAAK,UAAU,IAAI,CAAC;AAAA,EAChD,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,aAAa,UAAkB,OAA8B;AAEpE,QAAM,WAAW,UAAU,cAAc;AACzC,MAAI;AACF,WAAO,SAAS,UAAU,QAAQ,QAAQ;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,YAAY,UAAkB,UAA2B;AAChE,QAAM,KAAK,aAAa,UAAU,UAAU,QAAQ;AACpD,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI;AACF,WAAO,aAAa,IAAI,OAAO,MAAM;AAAA,EACvC,UAAE;AACA,cAAU,EAAE;AAAA,EACd;AACF;AAiBO,SAAS,eAAe,UAAiC;AAC9D,MAAI,KAAoB,aAAa,UAAU,UAAU,MAAM;AAC/D,MAAI,WAAW;AACf,MAAI,OAAO,MAAM;AACf,SAAK,aAAa,UAAU,UAAU,QAAQ;AAC9C,eAAW;AAAA,EACb;AACA,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI;AACF,QAAI,CAAC,UAAU,EAAE,EAAE,OAAO,EAAG,QAAO;AACpC,UAAM,OAAO,aAAa,IAAI,OAAO;AACrC,UAAM,aAAa,YAAY,IAAI;AAGnC,UAAM,aAAa,KAAK,MAAM,gBAAgB,KAAK,CAAC,GAAG;AACvD,UAAM,YAAY,WAAW,MAAM,gBAAgB,KAAK,CAAC,GAAG;AAC5D,QAAI,YAAY,WAAW,WAAW;AACpC,YAAM,OAAO,UAAU,EAAE,EAAE;AAC3B,gBAAU,EAAE;AACZ,WAAK;AACL,UAAI,YAAY,UAAU,IAAI,EAAG,uBAAsB,UAAU,YAAY,IAAI;AAAA,IACnF;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,sDAAsD,GAAG;AACtE,WAAO;AAAA,EACT,UAAE;AACA,QAAI,OAAO,KAAM,WAAU,EAAE;AAAA,EAC/B;AACF;","names":[]}
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  PROXY_PARAMS_VERSION,
3
3
  resolveProxy
4
- } from "./chunk-G6IIFKXY.js";
4
+ } from "./chunk-2YEGCDJW.js";
5
5
  import {
6
6
  createMediaCodecProbeCache,
7
7
  proxyVariantFor,
8
8
  recordProxyPrewarm,
9
9
  scanProjectMediaCodecMap,
10
10
  shouldPrewarmProxy
11
- } from "./chunk-TJQ76IEA.js";
11
+ } from "./chunk-I3DRHG52.js";
12
12
 
13
13
  // src/helpers/mediaProxyPreview.ts
14
14
  import { resolve } from "path";
@@ -63,4 +63,4 @@ export {
63
63
  injectMediaCodecMapIntoHtml,
64
64
  injectMediaCodecMap
65
65
  };
66
- //# sourceMappingURL=chunk-FTDTO3QP.js.map
66
+ //# sourceMappingURL=chunk-YUPEBATT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers/mediaProxyPreview.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { insertBeforeCloseTag } from \"@hyperframes/core/compiler/html-document\";\nimport type { StudioApiAdapter } from \"../types.js\";\nimport {\n createMediaCodecProbeCache,\n proxyVariantFor,\n recordProxyPrewarm,\n scanProjectMediaCodecMap,\n shouldPrewarmProxy,\n type HtmlSourceLike,\n type MediaCodecMap,\n type MediaCodecProbeCache,\n} from \"./mediaCodecMap.js\";\nimport { resolveProxy, PROXY_PARAMS_VERSION } from \"./proxyTranscoder.js\";\n\n/**\n * Transparent-media-proxy wiring shared by `routes/preview.ts`\n * (docs/plans/2026-07-14-002-feat-transparent-media-proxies-plan.md, unit U3).\n * Split out of the route module to keep it under the repo's 600-line file cap.\n */\n\n/**\n * Preview-route-local adapter surface for the auto-proxy feature. Both\n * fields are optional so any existing `StudioApiAdapter` value remains\n * structurally assignable without editing the shared interface:\n * `autoProxy` defaults to true (on) when omitted — a later unit wires the\n * CLI `--no-proxy` flag / `hyperframes.json` setting through it;\n * `mediaCodecProbeCache` lets a host share one probe cache across\n * preview/play/static-server surfaces instead of each constructing its own.\n */\nexport type PreviewApiAdapter = StudioApiAdapter & {\n autoProxy?: boolean;\n mediaCodecProbeCache?: MediaCodecProbeCache;\n /** Keeps built preview documents across restarts, keyed by project id and preview ETag. */\n previewDocuments?: PreviewDocumentStore;\n};\n\nexport interface PreviewDocumentStore {\n read(key: string): string | null;\n write(key: string, html: string): void;\n}\n\nexport function isAutoProxyEnabled(adapter: PreviewApiAdapter): boolean {\n return adapter.autoProxy !== false;\n}\n\n/** One probe cache per server instance — construct once in `registerPreviewRoutes`\n * and reuse across every request so the mtime-cache benefit in\n * `scanProjectMediaCodecMap` actually applies. A host that wants to share the\n * cache across other surfaces (play, static project server) can pass its own\n * via `adapter.mediaCodecProbeCache`. */\nexport function resolvePreviewMediaCodecProbeCache(\n adapter: PreviewApiAdapter,\n): MediaCodecProbeCache {\n return adapter.mediaCodecProbeCache ?? createMediaCodecProbeCache();\n}\n\n/**\n * ETag salt for `?hf-proxy=` asset requests, mirroring `variablesEtagSalt` in\n * preview.ts: salted by the raw param value plus the transcoder's params\n * version, so a future proxy-recipe change (which bumps `PROXY_PARAMS_VERSION`)\n * or a different proxy variant invalidates cached 304s without needing to\n * touch the proxy file itself.\n */\nexport function proxyEtagSalt(raw: string | undefined): string {\n if (raw === undefined) return \"\";\n return `:proxy:${raw}:${PROXY_PARAMS_VERSION}`;\n}\n\n/**\n * Injects `window.__HF_MEDIA_CODEC_MAP__` (the U1 codec-facts scan) into\n * served composition HTML, and fire-and-forget pre-warms `resolveProxy` for\n * every entry whose codec no browser decodes, so an element's proactive swap\n * usually hits a warm cache (KTD: protects the per-origin connection budget\n * under held responses). Conditionally hostile codecs are injected but NOT\n * pre-warmed: the requesting browser usually plays them, and the transcode\n * runs concurrently with its first layout. No second concurrency limiter here\n * — the transcoder's own global bound throttles both pre-warm and\n * element-triggered calls. Pre-warm failures are swallowed; an actual\n * `?hf-proxy=` request surfaces them as a 502 and transcodes lazily.\n * Alpha-bearing entries pre-warm their VP8/WebM variant.\n *\n * The single shared implementation for every auto-proxy surface — the studio\n * preview route (via `injectMediaCodecMap` below) and the CLI's composition /\n * static project servers (via the `./media-proxy-preview` subpath export).\n * Empty maps leave HTML untouched, preserving the normal no-hostile-media\n * preview path. On-demand proxy requests enforce the same eligibility gate.\n */\nexport async function injectMediaCodecMapIntoHtml(\n html: string,\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n probeCache?: MediaCodecProbeCache,\n): Promise<string> {\n let map: MediaCodecMap;\n try {\n map = await scanProjectMediaCodecMap(\n projectDir,\n htmlSources,\n probeCache ? { cache: probeCache } : {},\n );\n } catch {\n // Best-effort: a scan failure must never block serving the page.\n return html;\n }\n if (Object.keys(map).length === 0) return html;\n for (const [rootRelativePathname, facts] of Object.entries(map)) {\n if (!shouldPrewarmProxy(facts)) continue;\n recordProxyPrewarm();\n resolveProxy(\n projectDir,\n resolve(projectDir, rootRelativePathname.replace(/^\\/+/, \"\")),\n proxyVariantFor(facts),\n ).catch(() => {\n // Swallowed: the pre-warm is best-effort. A real `?hf-proxy=` request\n // for this asset re-attempts the transcode and reports failure (502).\n });\n }\n // <-escape prevents a src path containing \"</script>\" from breaking out of\n // the injected tag, mirroring injectPreviewVariables in helpers/previewVariables.ts.\n const json = JSON.stringify(map)\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n const tag = `<script data-hf-media-codec-map>window.__HF_MEDIA_CODEC_MAP__=${json};</script>`;\n return insertBeforeCloseTag(html, \"head\", `${tag}\\n`) ?? `${tag}\\n${html}`;\n}\n\n/**\n * Adapter-aware wrapper used by the studio preview routes: skipped entirely\n * (no scan, no injection) when auto-proxy is off for this adapter.\n */\nexport async function injectMediaCodecMap(\n html: string,\n adapter: PreviewApiAdapter,\n projectDir: string,\n compSrcPath: string,\n probeCache: MediaCodecProbeCache,\n): Promise<string> {\n if (!isAutoProxyEnabled(adapter)) return html;\n return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,4BAA4B;AAyC9B,SAAS,mBAAmB,SAAqC;AACtE,SAAO,QAAQ,cAAc;AAC/B;AAOO,SAAS,mCACd,SACsB;AACtB,SAAO,QAAQ,wBAAwB,2BAA2B;AACpE;AASO,SAAS,cAAc,KAAiC;AAC7D,MAAI,QAAQ,OAAW,QAAO;AAC9B,SAAO,UAAU,GAAG,IAAI,oBAAoB;AAC9C;AAqBA,eAAsB,4BACpB,MACA,YACA,aACA,YACiB;AACjB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,GAAG,EAAE,WAAW,EAAG,QAAO;AAC1C,aAAW,CAAC,sBAAsB,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/D,QAAI,CAAC,mBAAmB,KAAK,EAAG;AAChC,uBAAmB;AACnB;AAAA,MACE;AAAA,MACA,QAAQ,YAAY,qBAAqB,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAC5D,gBAAgB,KAAK;AAAA,IACvB,EAAE,MAAM,MAAM;AAAA,IAGd,CAAC;AAAA,EACH;AAGA,QAAM,OAAO,KAAK,UAAU,GAAG,EAC5B,QAAQ,MAAM,SAAS,EACvB,QAAQ,WAAW,SAAS,EAC5B,QAAQ,WAAW,SAAS;AAC/B,QAAM,MAAM,iEAAiE,IAAI;AACjF,SAAO,qBAAqB,MAAM,QAAQ,GAAG,GAAG;AAAA,CAAI,KAAK,GAAG,GAAG;AAAA,EAAK,IAAI;AAC1E;AAMA,eAAsB,oBACpB,MACA,SACA,YACA,aACA,YACiB;AACjB,MAAI,CAAC,mBAAmB,OAAO,EAAG,QAAO;AACzC,SAAO,4BAA4B,MAAM,YAAY,CAAC,EAAE,MAAM,YAAY,CAAC,GAAG,UAAU;AAC1F;","names":[]}
1
+ {"version":3,"sources":["../src/helpers/mediaProxyPreview.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { insertBeforeCloseTag } from \"@hyperframes/core/compiler/html-document\";\nimport type { StudioApiAdapter } from \"../types.js\";\nimport {\n createMediaCodecProbeCache,\n proxyVariantFor,\n recordProxyPrewarm,\n scanProjectMediaCodecMap,\n shouldPrewarmProxy,\n type HtmlSourceLike,\n type MediaCodecMap,\n type MediaCodecProbeCache,\n} from \"./mediaCodecMap.js\";\nimport { resolveProxy, PROXY_PARAMS_VERSION } from \"./proxyTranscoder.js\";\n\n/**\n * Transparent-media-proxy wiring shared by `routes/preview.ts`\n * (docs/plans/2026-07-14-002-feat-transparent-media-proxies-plan.md, unit U3).\n * Split out of the route module to keep it under the repo's 600-line file cap.\n */\n\n/**\n * Preview-route-local adapter surface for the auto-proxy feature. Both\n * fields are optional so any existing `StudioApiAdapter` value remains\n * structurally assignable without editing the shared interface:\n * `autoProxy` defaults to true (on) when omitted — a later unit wires the\n * CLI `--no-proxy` flag / `hyperframes.json` setting through it;\n * `mediaCodecProbeCache` lets a host share one probe cache across\n * preview/play/static-server surfaces instead of each constructing its own.\n */\nexport type PreviewApiAdapter = StudioApiAdapter & {\n autoProxy?: boolean;\n mediaCodecProbeCache?: MediaCodecProbeCache;\n /** Keeps built preview documents across restarts, keyed by project id and preview ETag. */\n previewDocuments?: PreviewDocumentStore;\n};\n\nexport interface PreviewDocumentStore {\n read(key: string): string | null;\n write(key: string, html: string): void;\n}\n\nexport function isAutoProxyEnabled(adapter: PreviewApiAdapter): boolean {\n return adapter.autoProxy !== false;\n}\n\n/** One probe cache per server instance — construct once in `registerPreviewRoutes`\n * and reuse across every request so the mtime-cache benefit in\n * `scanProjectMediaCodecMap` actually applies. A host that wants to share the\n * cache across other surfaces (play, static project server) can pass its own\n * via `adapter.mediaCodecProbeCache`. */\nexport function resolvePreviewMediaCodecProbeCache(\n adapter: PreviewApiAdapter,\n): MediaCodecProbeCache {\n return adapter.mediaCodecProbeCache ?? createMediaCodecProbeCache();\n}\n\n/**\n * ETag salt for `?hf-proxy=` asset requests, mirroring `variablesEtagSalt` in\n * preview.ts: salted by the raw param value plus the transcoder's params\n * version, so a future proxy-recipe change (which bumps `PROXY_PARAMS_VERSION`)\n * or a different proxy variant invalidates cached 304s without needing to\n * touch the proxy file itself.\n */\nexport function proxyEtagSalt(raw: string | undefined): string {\n if (raw === undefined) return \"\";\n return `:proxy:${raw}:${PROXY_PARAMS_VERSION}`;\n}\n\n/**\n * Injects `window.__HF_MEDIA_CODEC_MAP__` (the U1 codec-facts scan) into\n * served composition HTML, and fire-and-forget pre-warms `resolveProxy` for\n * every entry whose codec no browser decodes, so an element's proactive swap\n * usually hits a warm cache (KTD: protects the per-origin connection budget\n * under held responses). Conditionally hostile codecs are injected but NOT\n * pre-warmed: the requesting browser usually plays them, and the transcode\n * runs concurrently with its first layout. No second concurrency limiter here\n * — the transcoder's own global bound throttles both pre-warm and\n * element-triggered calls. Pre-warm failures are swallowed; an actual\n * `?hf-proxy=` request surfaces them as a 502 and transcodes lazily.\n * Alpha-bearing entries pre-warm their VP8/WebM variant.\n *\n * The single shared implementation for every auto-proxy surface — the studio\n * preview route (via `injectMediaCodecMap` below) and the CLI's composition /\n * static project servers (via the `./media-proxy-preview` subpath export).\n * Empty maps leave HTML untouched, preserving the normal no-hostile-media\n * preview path. On-demand proxy requests enforce the same eligibility gate.\n */\nexport async function injectMediaCodecMapIntoHtml(\n html: string,\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n probeCache?: MediaCodecProbeCache,\n): Promise<string> {\n let map: MediaCodecMap;\n try {\n map = await scanProjectMediaCodecMap(\n projectDir,\n htmlSources,\n probeCache ? { cache: probeCache } : {},\n );\n } catch {\n // Best-effort: a scan failure must never block serving the page.\n return html;\n }\n if (Object.keys(map).length === 0) return html;\n for (const [rootRelativePathname, facts] of Object.entries(map)) {\n if (!shouldPrewarmProxy(facts)) continue;\n recordProxyPrewarm();\n resolveProxy(\n projectDir,\n resolve(projectDir, rootRelativePathname.replace(/^\\/+/, \"\")),\n proxyVariantFor(facts),\n ).catch(() => {\n // Swallowed: the pre-warm is best-effort. A real `?hf-proxy=` request\n // for this asset hears the remembered failure (502) or re-attempts it.\n });\n }\n // <-escape prevents a src path containing \"</script>\" from breaking out of\n // the injected tag, mirroring injectPreviewVariables in helpers/previewVariables.ts.\n const json = JSON.stringify(map)\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n const tag = `<script data-hf-media-codec-map>window.__HF_MEDIA_CODEC_MAP__=${json};</script>`;\n return insertBeforeCloseTag(html, \"head\", `${tag}\\n`) ?? `${tag}\\n${html}`;\n}\n\n/**\n * Adapter-aware wrapper used by the studio preview routes: skipped entirely\n * (no scan, no injection) when auto-proxy is off for this adapter.\n */\nexport async function injectMediaCodecMap(\n html: string,\n adapter: PreviewApiAdapter,\n projectDir: string,\n compSrcPath: string,\n probeCache: MediaCodecProbeCache,\n): Promise<string> {\n if (!isAutoProxyEnabled(adapter)) return html;\n return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,eAAe;AACxB,SAAS,4BAA4B;AAyC9B,SAAS,mBAAmB,SAAqC;AACtE,SAAO,QAAQ,cAAc;AAC/B;AAOO,SAAS,mCACd,SACsB;AACtB,SAAO,QAAQ,wBAAwB,2BAA2B;AACpE;AASO,SAAS,cAAc,KAAiC;AAC7D,MAAI,QAAQ,OAAW,QAAO;AAC9B,SAAO,UAAU,GAAG,IAAI,oBAAoB;AAC9C;AAqBA,eAAsB,4BACpB,MACA,YACA,aACA,YACiB;AACjB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,GAAG,EAAE,WAAW,EAAG,QAAO;AAC1C,aAAW,CAAC,sBAAsB,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/D,QAAI,CAAC,mBAAmB,KAAK,EAAG;AAChC,uBAAmB;AACnB;AAAA,MACE;AAAA,MACA,QAAQ,YAAY,qBAAqB,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAC5D,gBAAgB,KAAK;AAAA,IACvB,EAAE,MAAM,MAAM;AAAA,IAGd,CAAC;AAAA,EACH;AAGA,QAAM,OAAO,KAAK,UAAU,GAAG,EAC5B,QAAQ,MAAM,SAAS,EACvB,QAAQ,WAAW,SAAS,EAC5B,QAAQ,WAAW,SAAS;AAC/B,QAAM,MAAM,iEAAiE,IAAI;AACjF,SAAO,qBAAqB,MAAM,QAAQ,GAAG,GAAG;AAAA,CAAI,KAAK,GAAG,GAAG;AAAA,EAAK,IAAI;AAC1E;AAMA,eAAsB,oBACpB,MACA,SACA,YACA,aACA,YACiB;AACjB,MAAI,CAAC,mBAAmB,OAAO,EAAG,QAAO;AACzC,SAAO,4BAA4B,MAAM,YAAY,CAAC,EAAE,MAAM,YAAY,CAAC,GAAG,UAAU;AAC1F;","names":[]}