@hyperframes/studio-server 0.8.158 → 0.8.160
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.
- package/dist/{chunk-66HD2HFT.js → chunk-SZF3L5MT.js} +339 -65
- package/dist/history/index.d.ts +5 -1
- package/dist/history/index.js +7 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/package.json +3 -3
|
@@ -10,16 +10,16 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
10
10
|
import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
|
|
11
11
|
import {
|
|
12
12
|
existsSync as existsSync3,
|
|
13
|
-
lstatSync as
|
|
13
|
+
lstatSync as lstatSync4,
|
|
14
14
|
statSync as statSync4,
|
|
15
|
-
mkdirSync as
|
|
16
|
-
readdirSync as
|
|
17
|
-
readFileSync as
|
|
15
|
+
mkdirSync as mkdirSync5,
|
|
16
|
+
readdirSync as readdirSync4,
|
|
17
|
+
readFileSync as readFileSync10,
|
|
18
18
|
realpathSync,
|
|
19
|
-
rmSync as
|
|
19
|
+
rmSync as rmSync3
|
|
20
20
|
} from "fs";
|
|
21
21
|
import { homedir } from "os";
|
|
22
|
-
import { dirname as
|
|
22
|
+
import { dirname as dirname5, isAbsolute as isAbsolute5, join as join8, relative as relative5, resolve as resolve7, sep as sep5 } from "path";
|
|
23
23
|
import { replaceFileAtomically as replaceFileAtomically5 } from "@hyperframes/core/atomic-file";
|
|
24
24
|
|
|
25
25
|
// src/helpers/fileVersion.ts
|
|
@@ -411,9 +411,10 @@ function createProjectSignature(projectDir, excluding = /* @__PURE__ */ new Set(
|
|
|
411
411
|
|
|
412
412
|
// src/history/blobStore.ts
|
|
413
413
|
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
414
|
-
import { constants, createReadStream, renameSync } from "fs";
|
|
414
|
+
import { constants, createReadStream, createWriteStream, renameSync } from "fs";
|
|
415
415
|
import { copyFile, mkdir, readdir, readFile, rename, rm, stat } from "fs/promises";
|
|
416
416
|
import { dirname, join as join2 } from "path";
|
|
417
|
+
import { pipeline } from "stream/promises";
|
|
417
418
|
import { atomicTempPath } from "@hyperframes/core/atomic-file";
|
|
418
419
|
var BLOB_HASH = /^[0-9a-f]{64}$/;
|
|
419
420
|
function blobPath(dir, hash) {
|
|
@@ -426,6 +427,14 @@ async function hashFile(path) {
|
|
|
426
427
|
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
427
428
|
return hash.digest("hex");
|
|
428
429
|
}
|
|
430
|
+
async function cloneOrStream(from, to) {
|
|
431
|
+
try {
|
|
432
|
+
await copyFile(from, to, constants.COPYFILE_FICLONE_FORCE);
|
|
433
|
+
} catch {
|
|
434
|
+
const { mode } = await stat(from);
|
|
435
|
+
await pipeline(createReadStream(from), createWriteStream(to, { mode }));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
429
438
|
async function cloneOrCopy(from, to, beforeReplace) {
|
|
430
439
|
await mkdir(dirname(to), { recursive: true });
|
|
431
440
|
const temp = atomicTempPath(to);
|
|
@@ -450,7 +459,10 @@ async function openBlobStore(dir) {
|
|
|
450
459
|
async put(absPath) {
|
|
451
460
|
const temp = join2(dir, `incoming-${randomUUID3()}`);
|
|
452
461
|
await mkdir(dir, { recursive: true });
|
|
453
|
-
await
|
|
462
|
+
await cloneOrStream(absPath, temp).catch(async (error) => {
|
|
463
|
+
await rm(temp, { force: true });
|
|
464
|
+
throw error;
|
|
465
|
+
});
|
|
454
466
|
const hash = await hashFile(temp);
|
|
455
467
|
if (sizes.has(hash)) {
|
|
456
468
|
await rm(temp, { force: true });
|
|
@@ -704,8 +716,8 @@ function pruneGoneProjectHistoriesDaily(historyRoot, now, { onError, budgetMs }
|
|
|
704
716
|
|
|
705
717
|
// src/history/videoState.ts
|
|
706
718
|
import { createHash as createHash5, randomUUID as randomUUID6 } from "crypto";
|
|
707
|
-
import { createReadStream as createReadStream2, existsSync as existsSync2, readFileSync as
|
|
708
|
-
import { isAbsolute as isAbsolute4, join as
|
|
719
|
+
import { createReadStream as createReadStream2, existsSync as existsSync2, readFileSync as readFileSync8, renameSync as renameSync3, statSync as statSync3 } from "fs";
|
|
720
|
+
import { isAbsolute as isAbsolute4, join as join7, resolve as resolve6 } from "path";
|
|
709
721
|
import { replaceFileAtomically as replaceFileAtomically3 } from "@hyperframes/core/atomic-file";
|
|
710
722
|
|
|
711
723
|
// src/history/artifactCleanup.ts
|
|
@@ -824,6 +836,253 @@ function finishArtifactCleanup(projectDir, state) {
|
|
|
824
836
|
updateLatest(state);
|
|
825
837
|
}
|
|
826
838
|
|
|
839
|
+
// src/history/snapshotRuns.ts
|
|
840
|
+
import {
|
|
841
|
+
closeSync as closeSync2,
|
|
842
|
+
constants as constants3,
|
|
843
|
+
fstatSync as fstatSync2,
|
|
844
|
+
lstatSync as lstatSync3,
|
|
845
|
+
openSync as openSync2,
|
|
846
|
+
mkdirSync as mkdirSync3,
|
|
847
|
+
readFileSync as readFileSync7,
|
|
848
|
+
readdirSync as readdirSync3,
|
|
849
|
+
rmSync as rmSync2,
|
|
850
|
+
ftruncateSync,
|
|
851
|
+
writeSync,
|
|
852
|
+
writeFileSync as writeFileSync4
|
|
853
|
+
} from "fs";
|
|
854
|
+
import { dirname as dirname3, join as join6, relative as relative4, resolve as resolve5, sep as sep4 } from "path";
|
|
855
|
+
var SNAPSHOT_RUNS_TO_KEEP = 10;
|
|
856
|
+
var SNAPSHOT_GRACE_MS = 24 * 60 * 60 * 1e3;
|
|
857
|
+
var OWNER = ".capture-owner.json";
|
|
858
|
+
var ownedMarkers = /* @__PURE__ */ new Map();
|
|
859
|
+
var UUID = /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i;
|
|
860
|
+
var rootOf = (project) => join6(resolve5(project), ".hyperframes", "artifacts", "snapshots");
|
|
861
|
+
function ordinaryPath(project, path) {
|
|
862
|
+
const root = resolve5(project);
|
|
863
|
+
const rel = relative4(root, path);
|
|
864
|
+
if (rel === ".." || rel.startsWith(`..${sep4}`)) return false;
|
|
865
|
+
let current = root;
|
|
866
|
+
for (const part of rel.split(sep4).filter(Boolean)) {
|
|
867
|
+
current = join6(current, part);
|
|
868
|
+
try {
|
|
869
|
+
if (lstatSync3(current).isSymbolicLink()) return false;
|
|
870
|
+
} catch {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
function createSnapshotParents(project, path) {
|
|
877
|
+
let parent = resolve5(project);
|
|
878
|
+
for (const part of relative4(parent, path).split(sep4).filter(Boolean)) {
|
|
879
|
+
parent = join6(parent, part);
|
|
880
|
+
try {
|
|
881
|
+
mkdirSync3(parent);
|
|
882
|
+
} catch (error) {
|
|
883
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
884
|
+
}
|
|
885
|
+
if (!ordinaryPath(project, parent)) throw new Error("Snapshot storage must not use symlinks.");
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function ownSnapshotRun(project, path) {
|
|
889
|
+
const root = rootOf(project);
|
|
890
|
+
if (dirname3(path) !== root || !UUID.test(path.slice(root.length + 1))) return;
|
|
891
|
+
createSnapshotParents(project, root);
|
|
892
|
+
mkdirSync3(path);
|
|
893
|
+
const marker = join6(path, OWNER);
|
|
894
|
+
const fd = openSync2(marker, "wx");
|
|
895
|
+
try {
|
|
896
|
+
writeFileSync4(fd, JSON.stringify({ version: 1, pid: process.pid }));
|
|
897
|
+
ownedMarkers.set(marker, fstatSync2(fd));
|
|
898
|
+
} finally {
|
|
899
|
+
closeSync2(fd);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function missingFile(error) {
|
|
903
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
904
|
+
}
|
|
905
|
+
function retainedOutput(path) {
|
|
906
|
+
try {
|
|
907
|
+
lstatSync3(join6(path, ".user-output"));
|
|
908
|
+
return true;
|
|
909
|
+
} catch (error) {
|
|
910
|
+
return !missingFile(error);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function processIsAlive(pid) {
|
|
914
|
+
try {
|
|
915
|
+
process.kill(pid, 0);
|
|
916
|
+
return true;
|
|
917
|
+
} catch (error) {
|
|
918
|
+
return !(error instanceof Error && "code" in error && error.code === "ESRCH");
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function ownerPid(owner) {
|
|
922
|
+
if (!owner || typeof owner !== "object" || !("pid" in owner)) return null;
|
|
923
|
+
const pid = owner.pid;
|
|
924
|
+
return typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
925
|
+
}
|
|
926
|
+
function ordinaryOwner(stat2) {
|
|
927
|
+
return stat2.isFile() && stat2.nlink === 1;
|
|
928
|
+
}
|
|
929
|
+
function sameOwnerFile(before, current) {
|
|
930
|
+
return current.dev === before.dev && current.ino === before.ino;
|
|
931
|
+
}
|
|
932
|
+
function openOwner(marker, access) {
|
|
933
|
+
const fd = openSync2(marker, access | (constants3.O_NOFOLLOW ?? 0) | (constants3.O_NONBLOCK ?? 0));
|
|
934
|
+
try {
|
|
935
|
+
const current = fstatSync2(fd);
|
|
936
|
+
const named = lstatSync3(marker);
|
|
937
|
+
if (!ordinaryOwner(current) || !ordinaryOwner(named) || !sameOwnerFile(named, current))
|
|
938
|
+
throw new Error("Snapshot owner changed while opening.");
|
|
939
|
+
return fd;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
closeSync2(fd);
|
|
942
|
+
throw error;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function readOwner(marker) {
|
|
946
|
+
const fd = openOwner(marker, constants3.O_RDONLY);
|
|
947
|
+
try {
|
|
948
|
+
return JSON.parse(readFileSync7(fd, "utf8"));
|
|
949
|
+
} finally {
|
|
950
|
+
closeSync2(fd);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function releasedOwner(owner) {
|
|
954
|
+
return !!owner && typeof owner === "object" && "released" in owner && owner.released === true && ownerPid(owner) !== null;
|
|
955
|
+
}
|
|
956
|
+
function liveOwner(path) {
|
|
957
|
+
if (retainedOutput(path)) return true;
|
|
958
|
+
try {
|
|
959
|
+
const marker = join6(path, OWNER);
|
|
960
|
+
const owner = readOwner(marker);
|
|
961
|
+
if (releasedOwner(owner)) return false;
|
|
962
|
+
const pid = ownerPid(owner);
|
|
963
|
+
return pid === null || processIsAlive(pid);
|
|
964
|
+
} catch (error) {
|
|
965
|
+
return !missingFile(error);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function recordsOf(state) {
|
|
969
|
+
return [...Object.values(state.snapshots.revisions).flat(), ...state.snapshots.unversioned];
|
|
970
|
+
}
|
|
971
|
+
function containingRun(root, path) {
|
|
972
|
+
if (!path) return null;
|
|
973
|
+
const name = relative4(root, resolve5(path)).split(sep4)[0];
|
|
974
|
+
return name && UUID.test(name) ? join6(root, name) : null;
|
|
975
|
+
}
|
|
976
|
+
function keepContainingRun(keep, root, path) {
|
|
977
|
+
const run = containingRun(root, path);
|
|
978
|
+
if (run) keep.add(run);
|
|
979
|
+
}
|
|
980
|
+
function protectedRuns(root, state, records2) {
|
|
981
|
+
const keep = /* @__PURE__ */ new Set();
|
|
982
|
+
for (const revision of [state.currentRevision, ...state.renders?.retainedRevisions ?? []]) {
|
|
983
|
+
const latest2 = records2.filter((record) => record.revision === revision && record.status !== "cleaned").sort((a, b) => b.createdAt - a.createdAt)[0];
|
|
984
|
+
keepContainingRun(keep, root, latest2?.image);
|
|
985
|
+
}
|
|
986
|
+
const latest = records2.find((record) => record.id === state.snapshots.latest?.id);
|
|
987
|
+
keepContainingRun(keep, root, latest?.image);
|
|
988
|
+
for (const item of state.pendingArtifactCleanup ?? []) keepContainingRun(keep, root, item.path);
|
|
989
|
+
return keep;
|
|
990
|
+
}
|
|
991
|
+
function runsOnDisk(root) {
|
|
992
|
+
let entries;
|
|
993
|
+
try {
|
|
994
|
+
entries = readdirSync3(root, { withFileTypes: true });
|
|
995
|
+
} catch {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
return entries.filter((entry) => entry.isDirectory() && UUID.test(entry.name)).flatMap((entry) => {
|
|
999
|
+
const path = join6(root, entry.name);
|
|
1000
|
+
try {
|
|
1001
|
+
const stat2 = lstatSync3(path);
|
|
1002
|
+
return [{ path, stat: stat2 }];
|
|
1003
|
+
} catch {
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
}).sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs || a.path.localeCompare(b.path));
|
|
1007
|
+
}
|
|
1008
|
+
function planSnapshotCleanup(project, state, now = Date.now()) {
|
|
1009
|
+
const root = rootOf(project);
|
|
1010
|
+
if (!ordinaryPath(project, root)) return [];
|
|
1011
|
+
const records2 = recordsOf(state);
|
|
1012
|
+
const keep = protectedRuns(root, state, records2);
|
|
1013
|
+
const runs = runsOnDisk(root);
|
|
1014
|
+
for (const run of runs.slice(0, SNAPSHOT_RUNS_TO_KEEP)) keep.add(run.path);
|
|
1015
|
+
const pending = [];
|
|
1016
|
+
for (const { path, stat: stat2 } of runs) {
|
|
1017
|
+
if (keep.has(path) || now - stat2.mtimeMs < SNAPSHOT_GRACE_MS || liveOwner(path)) continue;
|
|
1018
|
+
try {
|
|
1019
|
+
const run = { path, dev: stat2.dev, ino: stat2.ino, mtimeMs: stat2.mtimeMs };
|
|
1020
|
+
if (!unchangedRun(project, run)) continue;
|
|
1021
|
+
pending.push(run);
|
|
1022
|
+
retireReferences(records2, root, path);
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return pending;
|
|
1027
|
+
}
|
|
1028
|
+
function retireReferences(records2, root, path) {
|
|
1029
|
+
for (const record of records2) {
|
|
1030
|
+
if (containingRun(root, record.image) !== path) continue;
|
|
1031
|
+
delete record.image;
|
|
1032
|
+
record.status = "cleaned";
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function unchangedRun(project, run) {
|
|
1036
|
+
if (!ordinaryPath(project, run.path)) return false;
|
|
1037
|
+
const current = lstatSync3(run.path);
|
|
1038
|
+
return current.isDirectory() && current.dev === run.dev && current.ino === run.ino && current.mtimeMs === run.mtimeMs;
|
|
1039
|
+
}
|
|
1040
|
+
function finishSnapshotCleanup(project, runs) {
|
|
1041
|
+
for (const run of runs) {
|
|
1042
|
+
try {
|
|
1043
|
+
if (!unchangedRun(project, run) || liveOwner(run.path)) continue;
|
|
1044
|
+
rmSync2(run.path, { recursive: true });
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function releaseSnapshotRun(project, path) {
|
|
1050
|
+
const marker = join6(path, OWNER);
|
|
1051
|
+
const expected = ownedMarkers.get(marker);
|
|
1052
|
+
if (!expected) return;
|
|
1053
|
+
try {
|
|
1054
|
+
if (dirname3(path) !== rootOf(project) || !ordinaryPath(project, path)) return;
|
|
1055
|
+
const fd = openOwner(marker, constants3.O_RDWR);
|
|
1056
|
+
try {
|
|
1057
|
+
if (!sameOwnerFile(expected, fstatSync2(fd))) return;
|
|
1058
|
+
const owner = JSON.parse(readFileSync7(fd, "utf8"));
|
|
1059
|
+
if (ownerPid(owner) !== process.pid) return;
|
|
1060
|
+
ftruncateSync(fd, 0);
|
|
1061
|
+
writeSync(fd, JSON.stringify({ version: 1, pid: process.pid, released: true }), 0, "utf8");
|
|
1062
|
+
} finally {
|
|
1063
|
+
closeSync2(fd);
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
} finally {
|
|
1067
|
+
ownedMarkers.delete(marker);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function retainSnapshotOutput(project, path) {
|
|
1071
|
+
const root = rootOf(project);
|
|
1072
|
+
const run = containingRun(root, path);
|
|
1073
|
+
if (!run) return;
|
|
1074
|
+
createSnapshotParents(project, resolve5(path));
|
|
1075
|
+
try {
|
|
1076
|
+
writeFileSync4(
|
|
1077
|
+
join6(run, ".user-output"),
|
|
1078
|
+
"Explicit --output; retained until manual cleanup.\n",
|
|
1079
|
+
{ flag: "wx" }
|
|
1080
|
+
);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
827
1086
|
// src/history/videoState.ts
|
|
828
1087
|
function statePath(dir) {
|
|
829
1088
|
const path = pinProjectRecord(dir, ".hyperframes/video-state.json");
|
|
@@ -843,7 +1102,7 @@ var emptyState = () => ({
|
|
|
843
1102
|
});
|
|
844
1103
|
function readState(dir) {
|
|
845
1104
|
if (!existsSync2(statePath(dir))) return emptyState();
|
|
846
|
-
const state = JSON.parse(
|
|
1105
|
+
const state = JSON.parse(readFileSync8(statePath(dir), "utf8"));
|
|
847
1106
|
if (state.version !== 1 || !state.checks || !state.snapshots || !state.revisions || !state.files)
|
|
848
1107
|
throw new Error("Unsupported video-state schema.");
|
|
849
1108
|
return state;
|
|
@@ -868,7 +1127,7 @@ async function updateState(dir, update) {
|
|
|
868
1127
|
}
|
|
869
1128
|
}
|
|
870
1129
|
async function writeHistoryState(projectDir, historyId, historyDirectory, entries, currentFiles, committedFiles) {
|
|
871
|
-
const blob = (hash) => hash === null ? null :
|
|
1130
|
+
const blob = (hash) => hash === null ? null : resolve6(historyDirectory, "blobs", hash.slice(0, 2), hash);
|
|
872
1131
|
await updateState(projectDir, (state) => {
|
|
873
1132
|
if (state.historyId && state.historyId !== historyId) {
|
|
874
1133
|
state.checks = collection();
|
|
@@ -920,7 +1179,7 @@ async function fileHashes(dir) {
|
|
|
920
1179
|
const files = {};
|
|
921
1180
|
for (const { path } of listProjectFiles(dir).sort((a, b) => a.path.localeCompare(b.path))) {
|
|
922
1181
|
const hash = createHash5("sha256");
|
|
923
|
-
for await (const chunk of createReadStream2(
|
|
1182
|
+
for await (const chunk of createReadStream2(join7(dir, path))) hash.update(chunk);
|
|
924
1183
|
files[path] = hash.digest("hex");
|
|
925
1184
|
}
|
|
926
1185
|
return files;
|
|
@@ -943,10 +1202,11 @@ async function recordArtifacts(projectDir, capture, artifactsToRecord) {
|
|
|
943
1202
|
for (const artifact of artifactsToRecord) {
|
|
944
1203
|
const path = artifact.kind === "check" ? artifact.report : artifact.image;
|
|
945
1204
|
if (!path || !isAbsolute4(path)) throw new Error("Artifact needs an absolute output path.");
|
|
946
|
-
hashes.set(artifact.id, createHash5("sha256").update(
|
|
1205
|
+
hashes.set(artifact.id, createHash5("sha256").update(readFileSync8(path)).digest("hex"));
|
|
947
1206
|
}
|
|
948
1207
|
const after = await beginArtifactCapture(projectDir);
|
|
949
1208
|
const unchanged = after.fingerprint === capture.fingerprint;
|
|
1209
|
+
let cleanup = [];
|
|
950
1210
|
await updateState(projectDir, (state) => {
|
|
951
1211
|
const revision = unchanged ? knownRevision(state, capture.revision) : null;
|
|
952
1212
|
const attribution = attributionOf(unchanged, revision);
|
|
@@ -968,7 +1228,9 @@ async function recordArtifacts(projectDir, capture, artifactsToRecord) {
|
|
|
968
1228
|
records2.push(record);
|
|
969
1229
|
artifacts.latest = { revision, id: record.id };
|
|
970
1230
|
}
|
|
1231
|
+
cleanup = planSnapshotCleanup(projectDir, state);
|
|
971
1232
|
});
|
|
1233
|
+
finishSnapshotCleanup(projectDir, cleanup);
|
|
972
1234
|
return records2;
|
|
973
1235
|
}
|
|
974
1236
|
function knownRevision(state, revision) {
|
|
@@ -996,7 +1258,7 @@ function listsOf(artifacts) {
|
|
|
996
1258
|
return [...Object.values(artifacts.revisions), artifacts.unversioned];
|
|
997
1259
|
}
|
|
998
1260
|
async function forgetRecordedArtifacts(projectDir, paths) {
|
|
999
|
-
const gone = new Set(paths.map((path) =>
|
|
1261
|
+
const gone = new Set(paths.map((path) => resolve6(path)));
|
|
1000
1262
|
if (gone.size === 0) return;
|
|
1001
1263
|
await updateState(projectDir, (state) => {
|
|
1002
1264
|
for (const artifacts of [state.checks, state.snapshots]) {
|
|
@@ -1004,7 +1266,7 @@ async function forgetRecordedArtifacts(projectDir, paths) {
|
|
|
1004
1266
|
for (let i = list.length - 1; i >= 0; i--) {
|
|
1005
1267
|
const record = list[i];
|
|
1006
1268
|
const path = record.image ?? record.report;
|
|
1007
|
-
if (path && gone.has(
|
|
1269
|
+
if (path && gone.has(resolve6(path))) list.splice(i, 1);
|
|
1008
1270
|
}
|
|
1009
1271
|
}
|
|
1010
1272
|
if (artifacts.latest && !listsOf(artifacts).some(
|
|
@@ -1016,7 +1278,9 @@ async function forgetRecordedArtifacts(projectDir, paths) {
|
|
|
1016
1278
|
});
|
|
1017
1279
|
}
|
|
1018
1280
|
function artifactDirectory(projectDir, kind) {
|
|
1019
|
-
|
|
1281
|
+
const path = join7(resolve6(projectDir), ".hyperframes", "artifacts", kind, randomUUID6());
|
|
1282
|
+
if (kind === "snapshots") ownSnapshotRun(projectDir, path);
|
|
1283
|
+
return path;
|
|
1020
1284
|
}
|
|
1021
1285
|
function retainRevision(retained, revision) {
|
|
1022
1286
|
if (!revision) return [...retained];
|
|
@@ -1030,7 +1294,7 @@ async function recordRenderSuccess(projectDir, output, status = "completed", cap
|
|
|
1030
1294
|
const record = {
|
|
1031
1295
|
id: randomUUID6(),
|
|
1032
1296
|
completedAt: Date.now(),
|
|
1033
|
-
output:
|
|
1297
|
+
output: resolve6(output),
|
|
1034
1298
|
status
|
|
1035
1299
|
};
|
|
1036
1300
|
const unchanged = Boolean(capture && after?.fingerprint === capture.fingerprint);
|
|
@@ -1048,10 +1312,18 @@ async function recordRenderSuccess(projectDir, output, status = "completed", cap
|
|
|
1048
1312
|
});
|
|
1049
1313
|
return record;
|
|
1050
1314
|
}
|
|
1315
|
+
async function finishSnapshotRun(projectDir, path) {
|
|
1316
|
+
releaseSnapshotRun(projectDir, path);
|
|
1317
|
+
let cleanup = [];
|
|
1318
|
+
await updateState(projectDir, (state) => {
|
|
1319
|
+
cleanup = planSnapshotCleanup(projectDir, state);
|
|
1320
|
+
});
|
|
1321
|
+
finishSnapshotCleanup(projectDir, cleanup);
|
|
1322
|
+
}
|
|
1051
1323
|
|
|
1052
1324
|
// src/history/historyLog.ts
|
|
1053
|
-
import { closeSync as
|
|
1054
|
-
import { dirname as
|
|
1325
|
+
import { closeSync as closeSync3, constants as constants4, mkdirSync as mkdirSync4, openSync as openSync3, readFileSync as readFileSync9, writeSync as writeSync2 } from "fs";
|
|
1326
|
+
import { dirname as dirname4 } from "path";
|
|
1055
1327
|
import { replaceFileAtomically as replaceFileAtomically4 } from "@hyperframes/core/atomic-file";
|
|
1056
1328
|
var START = "start";
|
|
1057
1329
|
function parseRecord(line) {
|
|
@@ -1064,7 +1336,7 @@ function parseRecord(line) {
|
|
|
1064
1336
|
function readLog(file, onUnreadable) {
|
|
1065
1337
|
let text;
|
|
1066
1338
|
try {
|
|
1067
|
-
text =
|
|
1339
|
+
text = readFileSync9(file, "utf-8");
|
|
1068
1340
|
} catch {
|
|
1069
1341
|
return null;
|
|
1070
1342
|
}
|
|
@@ -1088,16 +1360,16 @@ function applyRecord(log, record) {
|
|
|
1088
1360
|
function saveRecord(file, log, record) {
|
|
1089
1361
|
let fd;
|
|
1090
1362
|
try {
|
|
1091
|
-
fd =
|
|
1363
|
+
fd = openSync3(file, constants4.O_WRONLY | constants4.O_APPEND);
|
|
1092
1364
|
} catch (error) {
|
|
1093
1365
|
if (error.code !== "ENOENT") throw error;
|
|
1094
1366
|
return writeLog(file, log);
|
|
1095
1367
|
}
|
|
1096
1368
|
try {
|
|
1097
|
-
|
|
1369
|
+
writeSync2(fd, `${JSON.stringify(record)}
|
|
1098
1370
|
`);
|
|
1099
1371
|
} finally {
|
|
1100
|
-
|
|
1372
|
+
closeSync3(fd);
|
|
1101
1373
|
}
|
|
1102
1374
|
}
|
|
1103
1375
|
function baselineRecord(log) {
|
|
@@ -1113,7 +1385,7 @@ function writeLog(file, log) {
|
|
|
1113
1385
|
...log.entries.map((entry) => ({ type: "entry", entry })),
|
|
1114
1386
|
...[...log.pins].map((id) => ({ type: "pin", id, pinned: true }))
|
|
1115
1387
|
];
|
|
1116
|
-
|
|
1388
|
+
mkdirSync4(dirname4(file), { recursive: true });
|
|
1117
1389
|
replaceFileAtomically4(file, records2.map((r) => `${JSON.stringify(r)}
|
|
1118
1390
|
`).join(""), 420);
|
|
1119
1391
|
}
|
|
@@ -1186,7 +1458,7 @@ function referencedHashes(log, current) {
|
|
|
1186
1458
|
}
|
|
1187
1459
|
|
|
1188
1460
|
// src/history/projectHistory.ts
|
|
1189
|
-
var DEFAULT_HISTORY_ROOT =
|
|
1461
|
+
var DEFAULT_HISTORY_ROOT = join8(homedir(), ".cache", "hyperframes", "history");
|
|
1190
1462
|
var MAX_WINDOW_IDLE_MS = 10 * 6e4;
|
|
1191
1463
|
var UNDO_MODES = ["just-this", "back-to-before", "keep-later-edits"];
|
|
1192
1464
|
var OUTSIDE = { kind: "outside", name: "Outside" };
|
|
@@ -1199,7 +1471,7 @@ function isHistoryPath(path) {
|
|
|
1199
1471
|
if (KEPT_HIDDEN_PATHS.has(path)) return true;
|
|
1200
1472
|
return !path.split("/").some((segment) => segment.startsWith("."));
|
|
1201
1473
|
}
|
|
1202
|
-
var logFileIn = (home) =>
|
|
1474
|
+
var logFileIn = (home) => join8(home, "log.jsonl");
|
|
1203
1475
|
function listOf(log) {
|
|
1204
1476
|
const undone = undoneIds(log.entries);
|
|
1205
1477
|
return log.entries.map((entry) => ({
|
|
@@ -1239,20 +1511,20 @@ async function removeEmptyFolders(dir) {
|
|
|
1239
1511
|
throw error;
|
|
1240
1512
|
}
|
|
1241
1513
|
for (const entry of entries)
|
|
1242
|
-
if (entry.isDirectory()) await removeEmptyFolders(
|
|
1514
|
+
if (entry.isDirectory()) await removeEmptyFolders(join8(dir, entry.name));
|
|
1243
1515
|
await rmdir(dir);
|
|
1244
1516
|
}
|
|
1245
1517
|
function fileLeftIn(dir, at, deleted) {
|
|
1246
|
-
for (const entry of
|
|
1518
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
1247
1519
|
const path = `${at}/${entry.name}`;
|
|
1248
|
-
const left = entry.isDirectory() ? fileLeftIn(
|
|
1520
|
+
const left = entry.isDirectory() ? fileLeftIn(join8(dir, entry.name), path, deleted) : deleted.has(path) ? void 0 : path;
|
|
1249
1521
|
if (left) return left;
|
|
1250
1522
|
}
|
|
1251
1523
|
return void 0;
|
|
1252
1524
|
}
|
|
1253
1525
|
function ignoresCase(dir) {
|
|
1254
|
-
const self = statSync4(
|
|
1255
|
-
const other = statSync4(
|
|
1526
|
+
const self = statSync4(join8(dir, ID_PATH), { throwIfNoEntry: false });
|
|
1527
|
+
const other = statSync4(join8(dir, ID_PATH.toUpperCase()), { throwIfNoEntry: false });
|
|
1256
1528
|
return self !== void 0 && other?.ino === self.ino && other.dev === self.dev;
|
|
1257
1529
|
}
|
|
1258
1530
|
var blocks = (removed, added) => added.startsWith(`${removed}/`) || removed.startsWith(`${added}/`);
|
|
@@ -1289,8 +1561,8 @@ var Engine = class {
|
|
|
1289
1561
|
this.options = options;
|
|
1290
1562
|
this.projectId = projectId;
|
|
1291
1563
|
this.blobs = blobs;
|
|
1292
|
-
this.dir =
|
|
1293
|
-
this.home =
|
|
1564
|
+
this.dir = resolve7(options.projectDir);
|
|
1565
|
+
this.home = join8(options.historyRoot, projectId);
|
|
1294
1566
|
this.foldsCase = ignoresCase(this.dir);
|
|
1295
1567
|
const folder = statSync4(this.dir, { throwIfNoEntry: false });
|
|
1296
1568
|
if (!folder || readId(this.dir) !== projectId || !isRecordedFolder(this.home, folder))
|
|
@@ -1370,7 +1642,7 @@ var Engine = class {
|
|
|
1370
1642
|
persistLog(record) {
|
|
1371
1643
|
if (record) saveRecord(this.logFile, this.log, record);
|
|
1372
1644
|
else writeLog(this.logFile, this.log);
|
|
1373
|
-
if (!existsSync3(
|
|
1645
|
+
if (!existsSync3(join8(this.home, "project.json")))
|
|
1374
1646
|
recordProject(this.home, this.dir, this.folder);
|
|
1375
1647
|
}
|
|
1376
1648
|
replaced() {
|
|
@@ -1409,7 +1681,7 @@ var Engine = class {
|
|
|
1409
1681
|
if (!after || !affectsProjectSignature(realDir, absPath)) return;
|
|
1410
1682
|
const replaced = hashOfVersion(fileContentVersion(bytes));
|
|
1411
1683
|
if (!replaced || replaced === after) return;
|
|
1412
|
-
const path =
|
|
1684
|
+
const path = relative5(realDir, absPath).split(sep5).join("/");
|
|
1413
1685
|
if (!isHistoryPath(path)) return;
|
|
1414
1686
|
this.overwritten.set(
|
|
1415
1687
|
path,
|
|
@@ -1421,7 +1693,7 @@ var Engine = class {
|
|
|
1421
1693
|
});
|
|
1422
1694
|
}
|
|
1423
1695
|
async storeBytes(bytes) {
|
|
1424
|
-
const staged =
|
|
1696
|
+
const staged = join8(this.home, `overwrote-${randomUUID7()}`);
|
|
1425
1697
|
await mkdir2(this.home, { recursive: true });
|
|
1426
1698
|
await writeFile(staged, bytes);
|
|
1427
1699
|
try {
|
|
@@ -1475,9 +1747,9 @@ var Engine = class {
|
|
|
1475
1747
|
this.saveAdopting();
|
|
1476
1748
|
}
|
|
1477
1749
|
saveAdopting() {
|
|
1478
|
-
const file =
|
|
1479
|
-
if (!this.adopting.size) return
|
|
1480
|
-
|
|
1750
|
+
const file = join8(this.home, "adopting.json");
|
|
1751
|
+
if (!this.adopting.size) return rmSync3(file, { force: true });
|
|
1752
|
+
mkdirSync5(this.home, { recursive: true });
|
|
1481
1753
|
replaceFileAtomically5(file, JSON.stringify([...this.adopting.keys()]), 420);
|
|
1482
1754
|
}
|
|
1483
1755
|
async adopt(file, sweptAt) {
|
|
@@ -1511,7 +1783,7 @@ var Engine = class {
|
|
|
1511
1783
|
}
|
|
1512
1784
|
readStatCache() {
|
|
1513
1785
|
try {
|
|
1514
|
-
const files = JSON.parse(
|
|
1786
|
+
const files = JSON.parse(readFileSync10(join8(this.home, "stat.json"), "utf-8"));
|
|
1515
1787
|
return new Map(Object.entries(files));
|
|
1516
1788
|
} catch {
|
|
1517
1789
|
return /* @__PURE__ */ new Map();
|
|
@@ -1520,7 +1792,7 @@ var Engine = class {
|
|
|
1520
1792
|
readAdopting() {
|
|
1521
1793
|
try {
|
|
1522
1794
|
return new Set(
|
|
1523
|
-
JSON.parse(
|
|
1795
|
+
JSON.parse(readFileSync10(join8(this.home, "adopting.json"), "utf-8"))
|
|
1524
1796
|
);
|
|
1525
1797
|
} catch {
|
|
1526
1798
|
return /* @__PURE__ */ new Set();
|
|
@@ -1528,8 +1800,8 @@ var Engine = class {
|
|
|
1528
1800
|
}
|
|
1529
1801
|
saveStatCache() {
|
|
1530
1802
|
const files = JSON.stringify(Object.fromEntries(this.tracked));
|
|
1531
|
-
|
|
1532
|
-
replaceFileAtomically5(
|
|
1803
|
+
mkdirSync5(this.home, { recursive: true });
|
|
1804
|
+
replaceFileAtomically5(join8(this.home, "stat.json"), files, 420);
|
|
1533
1805
|
}
|
|
1534
1806
|
/**
|
|
1535
1807
|
* Takes in every write since the last sweep and files each change to its writer. ponytail: always the whole
|
|
@@ -1571,7 +1843,7 @@ var Engine = class {
|
|
|
1571
1843
|
/** The file's hash once copied in; null when it was removed before the copy (the next sweep records that). */
|
|
1572
1844
|
async storeIfPresent(path) {
|
|
1573
1845
|
try {
|
|
1574
|
-
return await this.blobs.put(
|
|
1846
|
+
return await this.blobs.put(join8(this.dir, path));
|
|
1575
1847
|
} catch (error) {
|
|
1576
1848
|
if (error.code === "ENOENT") return null;
|
|
1577
1849
|
throw error;
|
|
@@ -1645,7 +1917,7 @@ var Engine = class {
|
|
|
1645
1917
|
return group && taken.every((change) => after(change.path, change.before) === change.before) && [...group.changes.values()].every(untouched) ? group : null;
|
|
1646
1918
|
}
|
|
1647
1919
|
logPath(path) {
|
|
1648
|
-
return
|
|
1920
|
+
return relative5(this.dir, resolve7(this.dir, path)).split(sep5).join("/");
|
|
1649
1921
|
}
|
|
1650
1922
|
/** Takes `paths`' uncommitted changes from others, cut at what `who` overwrote. */
|
|
1651
1923
|
async takeClaimed(who, paths, overwrote) {
|
|
@@ -1806,9 +2078,9 @@ var Engine = class {
|
|
|
1806
2078
|
}
|
|
1807
2079
|
/** A watcher saw a write: one sweep per burst takes it in (a deleted folder is reported by its name alone). */
|
|
1808
2080
|
noteChange(path) {
|
|
1809
|
-
const absolute =
|
|
2081
|
+
const absolute = resolve7(this.dir, path);
|
|
1810
2082
|
if (this.notedTimer || !affectsProjectSignature(this.dir, absolute)) return;
|
|
1811
|
-
if (!isHistoryPath(
|
|
2083
|
+
if (!isHistoryPath(relative5(this.dir, absolute).split(sep5).join("/"))) return;
|
|
1812
2084
|
this.notedTimer = setTimeout(() => {
|
|
1813
2085
|
this.notedTimer = null;
|
|
1814
2086
|
this.background(() => this.sweep());
|
|
@@ -1884,7 +2156,7 @@ var Engine = class {
|
|
|
1884
2156
|
const parts = path.split("/");
|
|
1885
2157
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
1886
2158
|
const at = parts.slice(0, depth).join("/");
|
|
1887
|
-
const stat2 =
|
|
2159
|
+
const stat2 = lstatSync4(join8(this.dir, at), { throwIfNoEntry: false });
|
|
1888
2160
|
if (!stat2) return void 0;
|
|
1889
2161
|
if (at === path || !stat2.isDirectory()) return { at, stat: stat2 };
|
|
1890
2162
|
}
|
|
@@ -1896,8 +2168,8 @@ var Engine = class {
|
|
|
1896
2168
|
if (!standing) return void 0;
|
|
1897
2169
|
if (standing.stat.isSymbolicLink()) return standing.at;
|
|
1898
2170
|
if (standing.at === path && !standing.stat.isDirectory()) return void 0;
|
|
1899
|
-
const real = realpathSync.native(
|
|
1900
|
-
const at =
|
|
2171
|
+
const real = realpathSync.native(join8(this.dir, standing.at));
|
|
2172
|
+
const at = relative5(realpathSync.native(this.dir), real).split(sep5).join("/");
|
|
1901
2173
|
if (standing.at !== path) return deleted.has(at) ? void 0 : at;
|
|
1902
2174
|
return fileLeftIn(real, at, deleted);
|
|
1903
2175
|
}
|
|
@@ -1921,7 +2193,7 @@ var Engine = class {
|
|
|
1921
2193
|
/** Writes one project file (null deletes it), first leaving the running operation's receipt for its echo. */
|
|
1922
2194
|
async writeProjectFile(path, hash) {
|
|
1923
2195
|
this.assertWritable();
|
|
1924
|
-
const absPath =
|
|
2196
|
+
const absPath = join8(this.dir, path);
|
|
1925
2197
|
const { writeToken } = this;
|
|
1926
2198
|
if (writeToken) {
|
|
1927
2199
|
const version = hash === null ? DELETED_VERSION : hashVersion(hash);
|
|
@@ -1929,7 +2201,7 @@ var Engine = class {
|
|
|
1929
2201
|
}
|
|
1930
2202
|
if (hash === null) {
|
|
1931
2203
|
this.assertWritable();
|
|
1932
|
-
|
|
2204
|
+
rmSync3(absPath, { force: true });
|
|
1933
2205
|
} else {
|
|
1934
2206
|
await removeEmptyFolders(absPath);
|
|
1935
2207
|
await this.blobs.writeTo(hash, absPath, () => this.assertWritable());
|
|
@@ -2065,17 +2337,17 @@ var Engine = class {
|
|
|
2065
2337
|
checkout: (entryId, side, emptyDir) => this.queue(async () => {
|
|
2066
2338
|
this.entry(entryId);
|
|
2067
2339
|
if (!existsSync3(this.dir)) throw new Error(`The project folder is gone: ${this.dir}`);
|
|
2068
|
-
const dest =
|
|
2340
|
+
const dest = resolve7(emptyDir);
|
|
2069
2341
|
if (isWithin(this.dir, dest))
|
|
2070
2342
|
throw new Error(`Checkout writes outside the project only: ${dest}`);
|
|
2071
2343
|
if ((await readdir3(dest).catch(missingIsEmpty)).length > 0)
|
|
2072
2344
|
throw new Error(`Checkout writes into an empty folder only: ${dest}`);
|
|
2073
2345
|
const files = manifestAround(this.log, entryId, side);
|
|
2074
2346
|
try {
|
|
2075
|
-
for (const [path, hash] of files) await this.blobs.writeTo(hash,
|
|
2347
|
+
for (const [path, hash] of files) await this.blobs.writeTo(hash, join8(dest, path));
|
|
2076
2348
|
} catch (error) {
|
|
2077
2349
|
for (const name of await readdir3(dest).catch(() => []))
|
|
2078
|
-
await rm3(
|
|
2350
|
+
await rm3(join8(dest, name), { recursive: true, force: true });
|
|
2079
2351
|
throw error;
|
|
2080
2352
|
}
|
|
2081
2353
|
}),
|
|
@@ -2113,10 +2385,10 @@ var Engine = class {
|
|
|
2113
2385
|
}
|
|
2114
2386
|
};
|
|
2115
2387
|
function isWithin(dir, path) {
|
|
2116
|
-
let probe =
|
|
2117
|
-
while (!existsSync3(probe) &&
|
|
2118
|
-
const rel =
|
|
2119
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
2388
|
+
let probe = resolve7(path);
|
|
2389
|
+
while (!existsSync3(probe) && dirname5(probe) !== probe) probe = dirname5(probe);
|
|
2390
|
+
const rel = relative5(realpathSync.native(dir), realpathSync.native(probe));
|
|
2391
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute5(rel);
|
|
2120
2392
|
}
|
|
2121
2393
|
function missingIsEmpty(error) {
|
|
2122
2394
|
if (error.code === "ENOENT") return [];
|
|
@@ -2133,11 +2405,11 @@ async function openProjectHistory(options) {
|
|
|
2133
2405
|
throw new HistoryClosedError(`${options.projectDir} is gone or now another project.`);
|
|
2134
2406
|
const projectId = projectHistoryId(options.projectDir, options.historyRoot);
|
|
2135
2407
|
const release = await takeHistoryOwnership(
|
|
2136
|
-
|
|
2408
|
+
join8(options.historyRoot, projectId),
|
|
2137
2409
|
options.ownerWaitMs ?? 5e3
|
|
2138
2410
|
);
|
|
2139
2411
|
try {
|
|
2140
|
-
const blobs = await openBlobStore(
|
|
2412
|
+
const blobs = await openBlobStore(join8(options.historyRoot, projectId, "blobs"));
|
|
2141
2413
|
const closedWindow = options.closedWindowAfterOwnership?.() ?? options.closedWindow;
|
|
2142
2414
|
const engine = new Engine({ ...options, closedWindow }, projectId, blobs);
|
|
2143
2415
|
await engine.queue(async () => {
|
|
@@ -2159,9 +2431,9 @@ async function openProjectHistory(options) {
|
|
|
2159
2431
|
}
|
|
2160
2432
|
}
|
|
2161
2433
|
function readProjectHistory(options) {
|
|
2162
|
-
const dir =
|
|
2434
|
+
const dir = resolve7(options.projectDir);
|
|
2163
2435
|
const id = readId(dir);
|
|
2164
|
-
const home = id && isRecordedFolder(
|
|
2436
|
+
const home = id && isRecordedFolder(join8(options.historyRoot, id), statSync4(dir)) ? join8(options.historyRoot, id) : null;
|
|
2165
2437
|
const read = home && readLog(logFileIn(home), () => {
|
|
2166
2438
|
});
|
|
2167
2439
|
const log = withoutHiddenPaths(read || { baseline: /* @__PURE__ */ new Map(), entries: [], pins: /* @__PURE__ */ new Set() });
|
|
@@ -2170,7 +2442,7 @@ function readProjectHistory(options) {
|
|
|
2170
2442
|
peek: (point) => peekOf(log, point),
|
|
2171
2443
|
readBlob: async (hash) => {
|
|
2172
2444
|
if (!home) throw new Error("That is not a history blob.");
|
|
2173
|
-
return readBlob(
|
|
2445
|
+
return readBlob(join8(home, "blobs"), hash);
|
|
2174
2446
|
}
|
|
2175
2447
|
};
|
|
2176
2448
|
}
|
|
@@ -2218,12 +2490,14 @@ export {
|
|
|
2218
2490
|
HistoryIdError,
|
|
2219
2491
|
listProjectHistories,
|
|
2220
2492
|
pruneGoneProjectHistories,
|
|
2493
|
+
retainSnapshotOutput,
|
|
2221
2494
|
beginArtifactCapture,
|
|
2222
2495
|
recordArtifact,
|
|
2223
2496
|
recordArtifacts,
|
|
2224
2497
|
forgetRecordedArtifacts,
|
|
2225
2498
|
artifactDirectory,
|
|
2226
2499
|
recordRenderSuccess,
|
|
2500
|
+
finishSnapshotRun,
|
|
2227
2501
|
START,
|
|
2228
2502
|
DEFAULT_HISTORY_ROOT,
|
|
2229
2503
|
MAX_WINDOW_IDLE_MS,
|
package/dist/history/index.d.ts
CHANGED
|
@@ -130,7 +130,11 @@ declare function recordArtifacts(projectDir: string, capture: ArtifactCapture, a
|
|
|
130
130
|
declare function forgetRecordedArtifacts(projectDir: string, paths: readonly string[]): Promise<void>;
|
|
131
131
|
declare function artifactDirectory(projectDir: string, kind: "checks" | "snapshots"): string;
|
|
132
132
|
declare function recordRenderSuccess(projectDir: string, output: string, status?: RenderRecord["status"], capture?: ArtifactCapture): Promise<RenderRecord>;
|
|
133
|
+
declare function finishSnapshotRun(projectDir: string, path: string): Promise<void>;
|
|
133
134
|
|
|
134
135
|
declare function registerRenderOutput(projectDir: string, output: string): Promise<void>;
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
/** Explicit outputs remain user evidence even when reusing a managed UUID folder. */
|
|
138
|
+
declare function retainSnapshotOutput(project: string, path: string): void;
|
|
139
|
+
|
|
140
|
+
export { type ArtifactCapture, type ArtifactInput, type ArtifactRecord, HistoryBusyError, HistoryIdError, HistoryTarget, HistoryWho, ProjectHistory, type ProjectHistoryRecord, type PrunedHistory, type RenderRecord, type VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput };
|
package/dist/history/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
UNDO_MODES,
|
|
9
9
|
artifactDirectory,
|
|
10
10
|
beginArtifactCapture,
|
|
11
|
+
finishSnapshotRun,
|
|
11
12
|
forgetRecordedArtifacts,
|
|
12
13
|
historyCache,
|
|
13
14
|
listProjectHistories,
|
|
@@ -17,8 +18,9 @@ import {
|
|
|
17
18
|
recordArtifact,
|
|
18
19
|
recordArtifacts,
|
|
19
20
|
recordRenderSuccess,
|
|
20
|
-
registerRenderOutput
|
|
21
|
-
|
|
21
|
+
registerRenderOutput,
|
|
22
|
+
retainSnapshotOutput
|
|
23
|
+
} from "../chunk-SZF3L5MT.js";
|
|
22
24
|
import "../chunk-K56J3CL7.js";
|
|
23
25
|
import "../chunk-SVQFE44C.js";
|
|
24
26
|
import "../chunk-L3HJVTQE.js";
|
|
@@ -32,6 +34,7 @@ export {
|
|
|
32
34
|
UNDO_MODES,
|
|
33
35
|
artifactDirectory,
|
|
34
36
|
beginArtifactCapture,
|
|
37
|
+
finishSnapshotRun,
|
|
35
38
|
forgetRecordedArtifacts,
|
|
36
39
|
historyCache,
|
|
37
40
|
listProjectHistories,
|
|
@@ -41,5 +44,6 @@ export {
|
|
|
41
44
|
recordArtifact,
|
|
42
45
|
recordArtifacts,
|
|
43
46
|
recordRenderSuccess,
|
|
44
|
-
registerRenderOutput
|
|
47
|
+
registerRenderOutput,
|
|
48
|
+
retainSnapshotOutput
|
|
45
49
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { S as StudioApiAdapter, P as PreviewDocumentStore, M as MediaProcessingJobState } from './mediaProxyPreview-J8vlAvrx.js';
|
|
3
3
|
export { L as LintResult, a as PreviewApiAdapter, R as RenderJobState, b as ResolvedProject, c as StudioSelectionResponse, d as StudioSelectionSnapshot, e as StudioSelectionTextField } from './mediaProxyPreview-J8vlAvrx.js';
|
|
4
4
|
export { C as ClosedWindow, D as DEFAULT_HISTORY_ROOT, S as HISTORY_START, H as HistoryClosedError, a as HistoryEntry, b as HistoryEntrySide, c as HistoryFileChange, d as HistoryListItem, e as HistoryResult, f as HistoryTarget, g as HistoryWho, h as HistoryWindow, M as MAX_WINDOW_IDLE_MS, P as ProjectHistory, i as ProjectHistoryOptions, j as ProjectHistoryView, U as UNDO_MODES, k as UndoMode, o as openProjectHistory, r as readProjectHistory } from './projectHistory-C5dpCjPU.js';
|
|
5
|
-
export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput } from './history/index.js';
|
|
5
|
+
export { ArtifactCapture, ArtifactInput, ArtifactRecord, HistoryBusyError, HistoryIdError, ProjectHistoryRecord, PrunedHistory, RenderRecord, VideoState, artifactDirectory, beginArtifactCapture, finishSnapshotRun, forgetRecordedArtifacts, historyCache, listProjectHistories, pruneGoneProjectHistories, recordArtifact, recordArtifacts, recordRenderSuccess, registerRenderOutput, retainSnapshotOutput } from './history/index.js';
|
|
6
6
|
export { CACHE_DIR_NAME as PROXY_CACHE_DIR_NAME } from './helpers/proxyTranscoder.js';
|
|
7
7
|
export { walkDir } from './helpers/safePath.js';
|
|
8
8
|
export { stampProjectHfIds } from './helpers/hfIdPersist.js';
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
createProjectSignature,
|
|
44
44
|
createWriteToken,
|
|
45
45
|
fileContentVersion,
|
|
46
|
+
finishSnapshotRun,
|
|
46
47
|
forgetRecordedArtifacts,
|
|
47
48
|
historyCache,
|
|
48
49
|
identifyFileWrite,
|
|
@@ -57,8 +58,9 @@ import {
|
|
|
57
58
|
registerRenderOutput,
|
|
58
59
|
resolveProjectAndSignature,
|
|
59
60
|
resolveProjectSignature,
|
|
61
|
+
retainSnapshotOutput,
|
|
60
62
|
settledFileTag
|
|
61
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-SZF3L5MT.js";
|
|
62
64
|
import {
|
|
63
65
|
clearElementScreenshotIsolation,
|
|
64
66
|
getElementScreenshotClip
|
|
@@ -6754,6 +6756,7 @@ export {
|
|
|
6754
6756
|
duplicateElementInHtml,
|
|
6755
6757
|
fileContentVersion,
|
|
6756
6758
|
findTargetElement,
|
|
6759
|
+
finishSnapshotRun,
|
|
6757
6760
|
forgetRecordedArtifacts,
|
|
6758
6761
|
getElementScreenshotClip,
|
|
6759
6762
|
getMimeType,
|
|
@@ -6772,6 +6775,7 @@ export {
|
|
|
6772
6775
|
registerRenderOutput,
|
|
6773
6776
|
removeElementFromHtml,
|
|
6774
6777
|
requestSubPath,
|
|
6778
|
+
retainSnapshotOutput,
|
|
6775
6779
|
settledFileTag,
|
|
6776
6780
|
splitElementInHtml,
|
|
6777
6781
|
stampProjectHfIds,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio-server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.160",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"postcss": "^8.5.8",
|
|
86
86
|
"postcss-selector-parser": "^7.1.2",
|
|
87
87
|
"sharp": "^0.35.0",
|
|
88
|
-
"@hyperframes/core": "0.8.
|
|
89
|
-
"@hyperframes/parsers": "0.8.
|
|
88
|
+
"@hyperframes/core": "0.8.160",
|
|
89
|
+
"@hyperframes/parsers": "0.8.160"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@types/node": "^25.0.10",
|