@tangle-network/agent-app 0.60.216 → 0.60.217
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/chat-routes/index.js +1 -1
- package/dist/{chunk-QA2CBATZ.js → chunk-4QUOPWIG.js} +12 -9
- package/dist/chunk-4QUOPWIG.js.map +1 -0
- package/dist/{chunk-J3N6RNMH.js → chunk-WDY47GBR.js} +415 -99
- package/dist/chunk-WDY47GBR.js.map +1 -0
- package/dist/sandbox/index.d.ts +3 -3
- package/dist/sandbox/index.js +1 -1
- package/dist/signoff/cache.d.ts +29 -0
- package/dist/signoff/cli.js +5 -1
- package/dist/signoff/cli.js.map +1 -1
- package/dist/signoff/index.js +1 -1
- package/dist/signoff/store.d.ts +2 -0
- package/dist/signoff/types.d.ts +6 -0
- package/package.json +5 -2
- package/dist/chunk-J3N6RNMH.js.map +0 -1
- package/dist/chunk-QA2CBATZ.js.map +0 -1
|
@@ -561,19 +561,303 @@ async function importConfig(abs) {
|
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
// src/signoff/run.ts
|
|
564
|
-
import { spawnSync as
|
|
565
|
-
import {
|
|
564
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
565
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
566
|
+
import { existsSync as existsSync9 } from "fs";
|
|
566
567
|
import { availableParallelism, homedir } from "os";
|
|
567
|
-
import { basename as
|
|
568
|
+
import { basename as basename4, join as join9, resolve as resolve4 } from "path";
|
|
569
|
+
|
|
570
|
+
// src/signoff/cache.ts
|
|
571
|
+
import { randomUUID } from "crypto";
|
|
572
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
573
|
+
import {
|
|
574
|
+
existsSync as existsSync4,
|
|
575
|
+
lstatSync,
|
|
576
|
+
mkdirSync,
|
|
577
|
+
readFileSync as readFileSync3,
|
|
578
|
+
readdirSync,
|
|
579
|
+
renameSync,
|
|
580
|
+
rmSync as rmSync2,
|
|
581
|
+
readlinkSync,
|
|
582
|
+
realpathSync,
|
|
583
|
+
statSync as statSync2,
|
|
584
|
+
unlinkSync,
|
|
585
|
+
writeFileSync
|
|
586
|
+
} from "fs";
|
|
587
|
+
import { hostname } from "os";
|
|
588
|
+
import { basename as basename2, join as join4, relative, resolve as resolve2, sep } from "path";
|
|
589
|
+
import lockfile from "proper-lockfile";
|
|
590
|
+
var SIGNOFF_CACHE_MAX_AGE_MS = 3 * 24 * 60 * 60 * 1e3;
|
|
591
|
+
var SIGNOFF_CACHE_MAX_BYTES = 4 * 1024 ** 3;
|
|
592
|
+
function processStart(pid) {
|
|
593
|
+
try {
|
|
594
|
+
const stat = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
595
|
+
return stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19] ?? null;
|
|
596
|
+
} catch {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function hasRunProcesses(run) {
|
|
601
|
+
if (process.platform !== "linux") return true;
|
|
602
|
+
try {
|
|
603
|
+
for (const entry of readdirSync("/proc")) {
|
|
604
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
605
|
+
const proc = join4("/proc", entry);
|
|
606
|
+
try {
|
|
607
|
+
const start = processStart(Number(entry));
|
|
608
|
+
if (run.processStart && start && BigInt(start) < BigInt(run.processStart)) continue;
|
|
609
|
+
const cwd = readlinkSync(join4(proc, "cwd"));
|
|
610
|
+
if (cwd === run.tree || cwd.startsWith(run.tree + sep)) return true;
|
|
611
|
+
const env = readFileSync3(join4(proc, "environ"), "utf8").split("\0");
|
|
612
|
+
if (env.includes(`SIGNOFF_TREE=${run.tree}`)) return true;
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (["ENOENT", "ESRCH"].includes(error.code ?? "")) continue;
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return false;
|
|
619
|
+
} catch {
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function alive(run) {
|
|
624
|
+
if (run.host !== hostname()) return true;
|
|
625
|
+
try {
|
|
626
|
+
process.kill(run.pid, 0);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if (error.code === "ESRCH") return hasRunProcesses(run);
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
const currentStart = processStart(run.pid);
|
|
632
|
+
return !run.processStart || !currentStart || currentStart === run.processStart || hasRunProcesses(run);
|
|
633
|
+
}
|
|
634
|
+
function cacheRoot(cacheDir) {
|
|
635
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
636
|
+
return realpathSync(cacheDir);
|
|
637
|
+
}
|
|
638
|
+
async function withCacheLock(cacheDir, action) {
|
|
639
|
+
const release = await lockfile.lock(cacheRoot(cacheDir), {
|
|
640
|
+
realpath: false,
|
|
641
|
+
stale: 3e5,
|
|
642
|
+
update: 3e4,
|
|
643
|
+
retries: { retries: 100, factor: 1, minTimeout: 100, maxTimeout: 100 }
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
return await action();
|
|
647
|
+
} finally {
|
|
648
|
+
await release();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function markerDir(cacheDir) {
|
|
652
|
+
return join4(cacheDir, "active");
|
|
653
|
+
}
|
|
654
|
+
function writeMarker(path, run) {
|
|
655
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
656
|
+
writeFileSync(temp, `${JSON.stringify(run)}
|
|
657
|
+
`, { flag: "wx" });
|
|
658
|
+
renameSync(temp, path);
|
|
659
|
+
}
|
|
660
|
+
function protectedPaths(cacheDir) {
|
|
661
|
+
const paths = /* @__PURE__ */ new Set();
|
|
662
|
+
const root = markerDir(cacheDir);
|
|
663
|
+
if (!existsSync4(root)) return { paths, unknown: false };
|
|
664
|
+
let unknown = false;
|
|
665
|
+
for (const entry of readdirSync(root)) {
|
|
666
|
+
if (!entry.endsWith(".json")) continue;
|
|
667
|
+
const file = join4(root, entry);
|
|
668
|
+
let run;
|
|
669
|
+
try {
|
|
670
|
+
run = JSON.parse(readFileSync3(file, "utf8"));
|
|
671
|
+
if (!Number.isSafeInteger(run.pid) || run.pid <= 0 || typeof run.host !== "string" || typeof run.tree !== "string" || !run.tree.startsWith(resolve2(cacheDir) + sep) || run.processStart !== null && (typeof run.processStart !== "string" || !/^\d+$/.test(run.processStart)) || run.storeKey !== null && (typeof run.storeKey !== "string" || !/^[a-f0-9]{64}$/.test(run.storeKey))) {
|
|
672
|
+
unknown = true;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
} catch {
|
|
676
|
+
unknown = true;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (!alive(run)) {
|
|
680
|
+
unlinkSync(file);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
const tree = resolve2(run.tree);
|
|
684
|
+
if (tree.startsWith(resolve2(cacheDir) + sep)) paths.add(tree);
|
|
685
|
+
if (run.storeKey && /^[a-f0-9]{64}$/.test(run.storeKey)) {
|
|
686
|
+
paths.add(join4(cacheDir, "stores", run.storeKey));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return { paths, unknown };
|
|
690
|
+
}
|
|
691
|
+
function protectLegacyRuns(cacheDir, active) {
|
|
692
|
+
const trees = join4(cacheDir, "trees");
|
|
693
|
+
if (!existsSync4(trees)) return;
|
|
694
|
+
for (const entry of readdirSync(trees, { withFileTypes: true })) {
|
|
695
|
+
if (!entry.isDirectory()) continue;
|
|
696
|
+
const tree = join4(trees, entry.name);
|
|
697
|
+
if (active.paths.has(tree) || !existsSync4(join4(tree, ".git"))) continue;
|
|
698
|
+
const match = /-(\d+)$/.exec(entry.name);
|
|
699
|
+
if (!match) continue;
|
|
700
|
+
const pid = Number(match[1]);
|
|
701
|
+
if (alive({ pid, host: hostname(), processStart: null, tree, storeKey: null })) {
|
|
702
|
+
active.paths.add(tree);
|
|
703
|
+
active.unknown = true;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async function openSignoffCacheSession(cacheDir, tree) {
|
|
708
|
+
const root = cacheRoot(cacheDir);
|
|
709
|
+
const treePath = resolve2(root, relative(resolve2(cacheDir), resolve2(tree)));
|
|
710
|
+
const file = join4(markerDir(root), `${process.pid}-${randomUUID()}.json`);
|
|
711
|
+
let run = {
|
|
712
|
+
pid: process.pid,
|
|
713
|
+
host: hostname(),
|
|
714
|
+
processStart: processStart(process.pid),
|
|
715
|
+
tree: treePath,
|
|
716
|
+
storeKey: null
|
|
717
|
+
};
|
|
718
|
+
await withCacheLock(root, () => {
|
|
719
|
+
mkdirSync(markerDir(root), { recursive: true });
|
|
720
|
+
writeMarker(file, run);
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
cacheDir: root,
|
|
724
|
+
protectStore: async (cacheKey) => {
|
|
725
|
+
if (!/^[a-f0-9]{64}$/.test(cacheKey)) throw new Error("signoff: invalid store cache key");
|
|
726
|
+
await withCacheLock(root, () => {
|
|
727
|
+
run = { ...run, storeKey: cacheKey };
|
|
728
|
+
writeMarker(file, run);
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
close: async () => {
|
|
732
|
+
await withCacheLock(root, () => {
|
|
733
|
+
if (existsSync4(file)) unlinkSync(file);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function activeStoreKeys(cacheDir) {
|
|
739
|
+
cacheDir = cacheRoot(cacheDir);
|
|
740
|
+
const active = protectedPaths(cacheDir);
|
|
741
|
+
protectLegacyRuns(cacheDir, active);
|
|
742
|
+
const { paths, unknown } = active;
|
|
743
|
+
if (unknown && existsSync4(join4(cacheDir, "stores"))) return new Set(readdirSync(join4(cacheDir, "stores"), { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name));
|
|
744
|
+
return new Set([...paths].filter((path) => path.startsWith(join4(cacheDir, "stores") + sep)).map((path) => basename2(path)));
|
|
745
|
+
}
|
|
746
|
+
function allocatedBytes(stat) {
|
|
747
|
+
return process.platform === "win32" ? stat.size : stat.blocks * 512;
|
|
748
|
+
}
|
|
749
|
+
function collectInodes(path, keys, sizes) {
|
|
750
|
+
let stat;
|
|
751
|
+
try {
|
|
752
|
+
stat = lstatSync(path);
|
|
753
|
+
} catch (error) {
|
|
754
|
+
if (error.code === "ENOENT") return;
|
|
755
|
+
throw error;
|
|
756
|
+
}
|
|
757
|
+
const key = `${stat.dev}:${stat.ino}`;
|
|
758
|
+
keys.add(key);
|
|
759
|
+
sizes.set(key, allocatedBytes(stat));
|
|
760
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
|
761
|
+
let children;
|
|
762
|
+
try {
|
|
763
|
+
children = readdirSync(path);
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (error.code === "ENOENT") return;
|
|
766
|
+
throw error;
|
|
767
|
+
}
|
|
768
|
+
for (const child of children) collectInodes(join4(path, child), keys, sizes);
|
|
769
|
+
}
|
|
770
|
+
function entries(cacheDir, protectedSet, unknown) {
|
|
771
|
+
const found = [];
|
|
772
|
+
for (const [kind, child] of [["tree", "trees"], ["store", "stores"]]) {
|
|
773
|
+
const root = join4(cacheDir, child);
|
|
774
|
+
if (!existsSync4(root)) continue;
|
|
775
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
776
|
+
if (!entry.isDirectory()) continue;
|
|
777
|
+
const path = join4(root, entry.name);
|
|
778
|
+
const recognized = kind === "store" ? /^[a-f0-9]{64}$/.test(entry.name) : existsSync4(join4(path, ".git"));
|
|
779
|
+
let mtimeMs;
|
|
780
|
+
try {
|
|
781
|
+
mtimeMs = statSync2(path).mtimeMs;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (error.code === "ENOENT") continue;
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
found.push({
|
|
787
|
+
path,
|
|
788
|
+
kind,
|
|
789
|
+
mtimeMs,
|
|
790
|
+
protected: unknown || protectedSet.has(path) || !recognized,
|
|
791
|
+
inodes: /* @__PURE__ */ new Set()
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return found;
|
|
796
|
+
}
|
|
797
|
+
function removeEntry(entry) {
|
|
798
|
+
if (entry.kind === "store") {
|
|
799
|
+
rmSync2(entry.path, { recursive: true, force: true });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const git2 = spawnSync3("git", ["-C", entry.path, "worktree", "remove", "--force", entry.path], { encoding: "utf8" });
|
|
803
|
+
if (git2.status !== 0) throw new Error(`signoff: cannot retire cached worktree ${entry.path}: ${git2.stderr.trim()}`);
|
|
804
|
+
}
|
|
805
|
+
async function pruneSignoffCache(cacheDir, options = {}) {
|
|
806
|
+
const root = cacheRoot(cacheDir);
|
|
807
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
808
|
+
const maxAgeMs = options.maxAgeMs ?? SIGNOFF_CACHE_MAX_AGE_MS;
|
|
809
|
+
const maxBytes = options.maxBytes ?? SIGNOFF_CACHE_MAX_BYTES;
|
|
810
|
+
if (!Number.isFinite(maxAgeMs) || maxAgeMs < 0 || !Number.isFinite(maxBytes) || maxBytes < 0) {
|
|
811
|
+
throw new Error("signoff: cache limits must be non-negative finite numbers");
|
|
812
|
+
}
|
|
813
|
+
return withCacheLock(root, () => {
|
|
814
|
+
const active = protectedPaths(root);
|
|
815
|
+
protectLegacyRuns(root, active);
|
|
816
|
+
for (const path of options.keepPaths ?? []) active.paths.add(resolve2(path));
|
|
817
|
+
const candidates = entries(root, active.paths, active.unknown);
|
|
818
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
819
|
+
const references = /* @__PURE__ */ new Map();
|
|
820
|
+
for (const entry of candidates) {
|
|
821
|
+
collectInodes(entry.path, entry.inodes, sizes);
|
|
822
|
+
for (const key of entry.inodes) references.set(key, (references.get(key) ?? 0) + 1);
|
|
823
|
+
}
|
|
824
|
+
let bytes = [...sizes.values()].reduce((sum, size) => sum + size, 0);
|
|
825
|
+
const beforeBytes = bytes;
|
|
826
|
+
const selected = [];
|
|
827
|
+
const select = (entry) => {
|
|
828
|
+
selected.push(entry);
|
|
829
|
+
for (const key of entry.inodes) {
|
|
830
|
+
const remaining = (references.get(key) ?? 1) - 1;
|
|
831
|
+
references.set(key, remaining);
|
|
832
|
+
if (remaining === 0) bytes -= sizes.get(key) ?? 0;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
const eligible = candidates.filter((entry) => !entry.protected).sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
836
|
+
for (const entry of eligible) {
|
|
837
|
+
if (nowMs - entry.mtimeMs >= maxAgeMs) select(entry);
|
|
838
|
+
}
|
|
839
|
+
for (const entry of eligible) {
|
|
840
|
+
if (bytes <= maxBytes) break;
|
|
841
|
+
if (!selected.includes(entry)) select(entry);
|
|
842
|
+
}
|
|
843
|
+
for (const entry of selected) removeEntry(entry);
|
|
844
|
+
return {
|
|
845
|
+
beforeBytes,
|
|
846
|
+
afterBytes: bytes,
|
|
847
|
+
removed: selected.map((entry) => entry.path),
|
|
848
|
+
overBudgetBytes: Math.max(0, bytes - maxBytes)
|
|
849
|
+
};
|
|
850
|
+
});
|
|
851
|
+
}
|
|
568
852
|
|
|
569
853
|
// src/signoff/node-version.ts
|
|
570
|
-
import { existsSync as
|
|
571
|
-
import { join as
|
|
854
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
855
|
+
import { join as join6 } from "path";
|
|
572
856
|
|
|
573
857
|
// src/signoff/workflow-pin.ts
|
|
574
|
-
import { existsSync as
|
|
575
|
-
import { join as
|
|
576
|
-
var WORKFLOW_DIR =
|
|
858
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
859
|
+
import { join as join5 } from "path";
|
|
860
|
+
var WORKFLOW_DIR = join5(".github", "workflows");
|
|
577
861
|
function scalarValue(raw) {
|
|
578
862
|
const withoutComment = raw.replace(/\s+#.*$/, "").trim();
|
|
579
863
|
const quoted = /^(['"])(.*)\1$/.exec(withoutComment);
|
|
@@ -623,24 +907,24 @@ function pinsInWorkflow(repoRoot, file, source) {
|
|
|
623
907
|
pins.push({ file, value, via: "node-version" });
|
|
624
908
|
continue;
|
|
625
909
|
}
|
|
626
|
-
const target =
|
|
627
|
-
if (!
|
|
910
|
+
const target = join5(repoRoot, value);
|
|
911
|
+
if (!existsSync5(target)) {
|
|
628
912
|
throw new Error(
|
|
629
913
|
`signoff: ${file} reads its Node pin from "${value}" (node-version-file) and that file does not exist. The workflow this gate replaces cannot itself run, so there is nothing to verify against.`
|
|
630
914
|
);
|
|
631
915
|
}
|
|
632
|
-
const declared =
|
|
916
|
+
const declared = readFileSync4(target, "utf8").split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
|
|
633
917
|
if (declared !== void 0) pins.push({ file, value: declared, via: `node-version-file ${value}` });
|
|
634
918
|
}
|
|
635
919
|
return pins;
|
|
636
920
|
}
|
|
637
921
|
function scanMergeGateNodePins(repoRoot) {
|
|
638
|
-
const dir =
|
|
639
|
-
if (!
|
|
922
|
+
const dir = join5(repoRoot, WORKFLOW_DIR);
|
|
923
|
+
if (!existsSync5(dir)) return [];
|
|
640
924
|
const pins = [];
|
|
641
|
-
const files =
|
|
925
|
+
const files = readdirSync2(dir).filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).sort();
|
|
642
926
|
for (const name of files) {
|
|
643
|
-
const source =
|
|
927
|
+
const source = readFileSync4(join5(dir, name), "utf8");
|
|
644
928
|
if (!triggersOnPullRequest(source)) continue;
|
|
645
929
|
pins.push(...pinsInWorkflow(repoRoot, `${WORKFLOW_DIR}/${name}`, source));
|
|
646
930
|
}
|
|
@@ -690,9 +974,9 @@ function resolveNodeRequirement(repoRoot, configured) {
|
|
|
690
974
|
return { major, declared: configured.trim(), source: "signoff config `nodeVersion`" };
|
|
691
975
|
}
|
|
692
976
|
let fromNvmrc = null;
|
|
693
|
-
const nvmrc =
|
|
694
|
-
if (
|
|
695
|
-
const raw =
|
|
977
|
+
const nvmrc = join6(repoRoot, ".nvmrc");
|
|
978
|
+
if (existsSync6(nvmrc)) {
|
|
979
|
+
const raw = readFileSync5(nvmrc, "utf8").trim();
|
|
696
980
|
const major = majorOf(raw);
|
|
697
981
|
if (major !== null) fromNvmrc = { major, declared: raw, source: ".nvmrc" };
|
|
698
982
|
}
|
|
@@ -716,9 +1000,9 @@ function assertNodeVersion(requirement, running = process.version) {
|
|
|
716
1000
|
}
|
|
717
1001
|
function assertNodeTypesVersion(repoRoot, requirement) {
|
|
718
1002
|
if (!requirement) return;
|
|
719
|
-
const packagePath =
|
|
720
|
-
if (!
|
|
721
|
-
const manifest = JSON.parse(
|
|
1003
|
+
const packagePath = join6(repoRoot, "package.json");
|
|
1004
|
+
if (!existsSync6(packagePath)) return;
|
|
1005
|
+
const manifest = JSON.parse(readFileSync5(packagePath, "utf8"));
|
|
722
1006
|
const declared = manifest.devDependencies?.["@types/node"] ?? manifest.dependencies?.["@types/node"] ?? manifest.optionalDependencies?.["@types/node"];
|
|
723
1007
|
if (!declared) return;
|
|
724
1008
|
const declaredMajor = /^[~^]?v?(\d+)(?:\.|$)/.exec(declared.trim())?.[1];
|
|
@@ -727,11 +1011,11 @@ function assertNodeTypesVersion(repoRoot, requirement) {
|
|
|
727
1011
|
`signoff: this repo pins Node ${requirement.declared} but declares @types/node ${declared}. Use the latest @types/node ${requirement.major}.x release.`
|
|
728
1012
|
);
|
|
729
1013
|
}
|
|
730
|
-
const installedPath =
|
|
731
|
-
if (!
|
|
1014
|
+
const installedPath = join6(repoRoot, "node_modules", "@types", "node", "package.json");
|
|
1015
|
+
if (!existsSync6(installedPath)) {
|
|
732
1016
|
throw new Error(`signoff: @types/node is declared but missing after install in ${repoRoot}.`);
|
|
733
1017
|
}
|
|
734
|
-
const installed = JSON.parse(
|
|
1018
|
+
const installed = JSON.parse(readFileSync5(installedPath, "utf8"));
|
|
735
1019
|
const installedMajor = majorOf(installed.version ?? "");
|
|
736
1020
|
if (installedMajor !== requirement.major) {
|
|
737
1021
|
throw new Error(
|
|
@@ -798,7 +1082,7 @@ function runCommand(options) {
|
|
|
798
1082
|
killGraceMs = DEFAULT_KILL_GRACE_MS,
|
|
799
1083
|
onData
|
|
800
1084
|
} = options;
|
|
801
|
-
return new Promise((
|
|
1085
|
+
return new Promise((resolve8, reject) => {
|
|
802
1086
|
const startedAt = Date.now();
|
|
803
1087
|
const buffer = new BoundedOutput(maxOutputBytes);
|
|
804
1088
|
let timedOut = false;
|
|
@@ -848,7 +1132,7 @@ function runCommand(options) {
|
|
|
848
1132
|
});
|
|
849
1133
|
child.on("close", (code, sig) => {
|
|
850
1134
|
cleanup();
|
|
851
|
-
|
|
1135
|
+
resolve8({
|
|
852
1136
|
command,
|
|
853
1137
|
cwd,
|
|
854
1138
|
// A signalled process reports code `null`; 128+n is the shell's own
|
|
@@ -1015,8 +1299,8 @@ function planAttempts(step, seedBase, overrideRuns) {
|
|
|
1015
1299
|
|
|
1016
1300
|
// src/signoff/store.ts
|
|
1017
1301
|
import { createHash as createHash2 } from "crypto";
|
|
1018
|
-
import { existsSync as
|
|
1019
|
-
import { join as
|
|
1302
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync3, utimesSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1303
|
+
import { basename as basename3, join as join7, relative as relative2, sep as sep2 } from "path";
|
|
1020
1304
|
var MANIFEST_FILES = [
|
|
1021
1305
|
"pnpm-lock.yaml",
|
|
1022
1306
|
"pnpm-workspace.yaml",
|
|
@@ -1029,12 +1313,12 @@ var MANIFEST_FILES = [
|
|
|
1029
1313
|
];
|
|
1030
1314
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".wrangler", ".react-router"]);
|
|
1031
1315
|
function collectManifests(dir, root, out) {
|
|
1032
|
-
for (const entry of
|
|
1316
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1033
1317
|
if (entry.isDirectory()) {
|
|
1034
1318
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1035
|
-
collectManifests(
|
|
1319
|
+
collectManifests(join7(dir, entry.name), root, out);
|
|
1036
1320
|
} else if (MANIFEST_FILES.includes(entry.name)) {
|
|
1037
|
-
out.push(
|
|
1321
|
+
out.push(relative2(root, join7(dir, entry.name)).split(sep2).join("/"));
|
|
1038
1322
|
}
|
|
1039
1323
|
}
|
|
1040
1324
|
}
|
|
@@ -1048,26 +1332,27 @@ function manifestCacheKey(treePath, files) {
|
|
|
1048
1332
|
for (const rel of files) {
|
|
1049
1333
|
hash.update(rel);
|
|
1050
1334
|
hash.update("\0");
|
|
1051
|
-
hash.update(createHash2("sha256").update(
|
|
1335
|
+
hash.update(createHash2("sha256").update(readFileSync6(join7(treePath, rel))).digest("hex"));
|
|
1052
1336
|
hash.update("\n");
|
|
1053
1337
|
}
|
|
1054
1338
|
return hash.digest("hex");
|
|
1055
1339
|
}
|
|
1056
|
-
function pruneStores(storesRoot, keep) {
|
|
1057
|
-
if (!
|
|
1058
|
-
const
|
|
1059
|
-
const full =
|
|
1060
|
-
return { full, mtimeMs:
|
|
1340
|
+
function pruneStores(storesRoot, keep, protectedKeys) {
|
|
1341
|
+
if (!existsSync7(storesRoot)) return [];
|
|
1342
|
+
const entries2 = readdirSync3(storesRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^[a-f0-9]{64}$/.test(entry.name)).map((entry) => {
|
|
1343
|
+
const full = join7(storesRoot, entry.name);
|
|
1344
|
+
return { full, mtimeMs: statSync3(full).mtimeMs };
|
|
1061
1345
|
}).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1062
1346
|
const pruned = [];
|
|
1063
|
-
for (const stale of
|
|
1064
|
-
|
|
1347
|
+
for (const stale of entries2.slice(keep)) {
|
|
1348
|
+
if (protectedKeys.has(basename3(stale.full))) continue;
|
|
1349
|
+
rmSync3(stale.full, { recursive: true, force: true });
|
|
1065
1350
|
pruned.push(stale.full);
|
|
1066
1351
|
}
|
|
1067
1352
|
return pruned;
|
|
1068
1353
|
}
|
|
1069
1354
|
function resolveStore(options) {
|
|
1070
|
-
const { treePath, cacheDir, generations = 4 } = options;
|
|
1355
|
+
const { treePath, cacheDir, generations = 4, protectedStoreKeys = /* @__PURE__ */ new Set() } = options;
|
|
1071
1356
|
const files = manifestFiles(treePath);
|
|
1072
1357
|
if (files.length === 0) {
|
|
1073
1358
|
throw new Error(
|
|
@@ -1075,25 +1360,25 @@ function resolveStore(options) {
|
|
|
1075
1360
|
);
|
|
1076
1361
|
}
|
|
1077
1362
|
const cacheKey = manifestCacheKey(treePath, files);
|
|
1078
|
-
const storesRoot =
|
|
1079
|
-
const storeDir =
|
|
1080
|
-
const marker =
|
|
1081
|
-
const hit =
|
|
1082
|
-
|
|
1083
|
-
|
|
1363
|
+
const storesRoot = join7(cacheDir, "stores");
|
|
1364
|
+
const storeDir = join7(storesRoot, cacheKey);
|
|
1365
|
+
const marker = join7(storeDir, ".signoff-store.json");
|
|
1366
|
+
const hit = existsSync7(storeDir) && readdirSync3(storeDir).some((entry) => entry !== ".signoff-store.json");
|
|
1367
|
+
mkdirSync2(storeDir, { recursive: true });
|
|
1368
|
+
writeFileSync2(marker, `${JSON.stringify({ cacheKey, keyedOn: files, usedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
1084
1369
|
`);
|
|
1085
1370
|
const now = /* @__PURE__ */ new Date();
|
|
1086
1371
|
utimesSync(storeDir, now, now);
|
|
1087
|
-
return { storeDir, cacheKey, hit, keyedOn: files, pruned: pruneStores(storesRoot, generations) };
|
|
1372
|
+
return { storeDir, cacheKey, hit, keyedOn: files, pruned: pruneStores(storesRoot, generations, protectedStoreKeys) };
|
|
1088
1373
|
}
|
|
1089
1374
|
|
|
1090
1375
|
// src/signoff/workspace.ts
|
|
1091
|
-
import { spawnSync as
|
|
1376
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
1092
1377
|
import { createHash as createHash3 } from "crypto";
|
|
1093
|
-
import { copyFileSync, existsSync as
|
|
1094
|
-
import { dirname as dirname2, isAbsolute, join as
|
|
1378
|
+
import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1379
|
+
import { dirname as dirname2, isAbsolute, join as join8, resolve as resolve3 } from "path";
|
|
1095
1380
|
function git(args, cwd) {
|
|
1096
|
-
const result =
|
|
1381
|
+
const result = spawnSync4("git", args, { cwd, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
1097
1382
|
if (result.error) throw new Error(`signoff: git ${args.join(" ")} failed to start: ${result.error.message}`);
|
|
1098
1383
|
if (result.status !== 0) {
|
|
1099
1384
|
throw new Error(`signoff: git ${args.join(" ")} exited ${result.status}
|
|
@@ -1113,8 +1398,8 @@ function materializeCleanTree(options) {
|
|
|
1113
1398
|
const head = git(["rev-parse", "HEAD"], root).trim();
|
|
1114
1399
|
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"], root).trim();
|
|
1115
1400
|
git(["worktree", "prune"], root);
|
|
1116
|
-
|
|
1117
|
-
if (
|
|
1401
|
+
mkdirSync3(dirname2(dest), { recursive: true });
|
|
1402
|
+
if (existsSync8(dest)) rmSync4(dest, { recursive: true, force: true });
|
|
1118
1403
|
git(["worktree", "add", "--detach", "--quiet", dest, head], root);
|
|
1119
1404
|
let diffSha256 = null;
|
|
1120
1405
|
let untrackedFiles = [];
|
|
@@ -1122,29 +1407,29 @@ function materializeCleanTree(options) {
|
|
|
1122
1407
|
const patch = git(["diff", "HEAD", "--binary", "--no-color", "--no-ext-diff"], root);
|
|
1123
1408
|
if (patch.length > 0) {
|
|
1124
1409
|
diffSha256 = createHash3("sha256").update(patch).digest("hex");
|
|
1125
|
-
const patchFile =
|
|
1126
|
-
|
|
1410
|
+
const patchFile = join8(dirname2(dest), `${dest.split("/").pop() ?? "tree"}.patch`);
|
|
1411
|
+
writeFileSync3(patchFile, patch);
|
|
1127
1412
|
git(["apply", "--binary", "--whitespace=nowarn", patchFile], dest);
|
|
1128
|
-
|
|
1413
|
+
rmSync4(patchFile, { force: true });
|
|
1129
1414
|
}
|
|
1130
1415
|
untrackedFiles = zsplit(git(["ls-files", "--others", "--exclude-standard", "-z"], root));
|
|
1131
1416
|
for (const rel of untrackedFiles) {
|
|
1132
|
-
const target =
|
|
1133
|
-
|
|
1134
|
-
copyFileSync(
|
|
1417
|
+
const target = join8(dest, rel);
|
|
1418
|
+
mkdirSync3(dirname2(target), { recursive: true });
|
|
1419
|
+
copyFileSync(join8(root, rel), target);
|
|
1135
1420
|
}
|
|
1136
1421
|
}
|
|
1137
1422
|
const carried = [];
|
|
1138
1423
|
for (const rel of carryFiles) {
|
|
1139
1424
|
if (isAbsolute(rel)) throw new Error(`signoff: carryFiles must be repo-relative; got "${rel}"`);
|
|
1140
|
-
const from =
|
|
1141
|
-
if (!
|
|
1425
|
+
const from = resolve3(root, rel);
|
|
1426
|
+
if (!existsSync8(from)) {
|
|
1142
1427
|
throw new Error(
|
|
1143
1428
|
`signoff: carryFiles names "${rel}", which does not exist at ${from}. Remove it from the config or create the file \u2014 installing without it would resolve against a different registry than the one you think you are verifying.`
|
|
1144
1429
|
);
|
|
1145
1430
|
}
|
|
1146
|
-
const target =
|
|
1147
|
-
|
|
1431
|
+
const target = join8(dest, rel);
|
|
1432
|
+
mkdirSync3(dirname2(target), { recursive: true });
|
|
1148
1433
|
copyFileSync(from, target);
|
|
1149
1434
|
carried.push(rel);
|
|
1150
1435
|
}
|
|
@@ -1165,9 +1450,9 @@ function removeCleanTree(tree) {
|
|
|
1165
1450
|
}
|
|
1166
1451
|
|
|
1167
1452
|
// src/signoff/run.ts
|
|
1168
|
-
var DEFAULT_CACHE_DIR =
|
|
1453
|
+
var DEFAULT_CACHE_DIR = join9(homedir(), ".cache", "agent-app-signoff");
|
|
1169
1454
|
function hostFacts(treePath, requirement) {
|
|
1170
|
-
const pm =
|
|
1455
|
+
const pm = spawnSync5("pnpm", ["--version"], { cwd: treePath, encoding: "utf8" });
|
|
1171
1456
|
return {
|
|
1172
1457
|
node: process.version,
|
|
1173
1458
|
nodePinned: requirement?.declared ?? null,
|
|
@@ -1205,17 +1490,35 @@ function withStoreDir(command, flag, storeDir) {
|
|
|
1205
1490
|
async function runSignoff(options = {}) {
|
|
1206
1491
|
const startedAt = /* @__PURE__ */ new Date();
|
|
1207
1492
|
const wallStart = Date.now();
|
|
1208
|
-
const repoDir =
|
|
1493
|
+
const repoDir = resolve4(options.repoDir ?? process.cwd());
|
|
1209
1494
|
const repoRoot = repoRootOf(repoDir);
|
|
1210
1495
|
const source = options.source ?? "working-tree";
|
|
1211
|
-
let cacheDir =
|
|
1212
|
-
const
|
|
1496
|
+
let cacheDir = resolve4(options.cacheDir ?? DEFAULT_CACHE_DIR);
|
|
1497
|
+
const treeName = `${basename4(repoRoot)}-${process.pid}-${randomUUID2()}`;
|
|
1498
|
+
const treePath = () => join9(cacheDir, "trees", treeName);
|
|
1213
1499
|
let tree = null;
|
|
1500
|
+
const sessions = [];
|
|
1501
|
+
let session = await openSignoffCacheSession(cacheDir, treePath());
|
|
1502
|
+
sessions.push(session);
|
|
1503
|
+
cacheDir = session.cacheDir;
|
|
1504
|
+
const prune = async (root, keepPaths = []) => {
|
|
1505
|
+
const result = await pruneSignoffCache(root, { keepPaths });
|
|
1506
|
+
if (result.removed.length || result.overBudgetBytes) {
|
|
1507
|
+
options.onEvent?.({ kind: "cache-prune", ...result });
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1214
1510
|
try {
|
|
1511
|
+
await prune(cacheDir);
|
|
1215
1512
|
tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath(), source });
|
|
1216
1513
|
let loaded = await loadSignoffConfig({ repoRoot: tree.path, configPath: options.configPath });
|
|
1217
|
-
cacheDir =
|
|
1514
|
+
cacheDir = resolve4(options.cacheDir ?? loaded.config.cacheDir ?? DEFAULT_CACHE_DIR);
|
|
1218
1515
|
if (tree.path !== treePath() || (loaded.config.carryFiles?.length ?? 0) > 0) {
|
|
1516
|
+
if (session.cacheDir !== cacheDir) {
|
|
1517
|
+
session = await openSignoffCacheSession(cacheDir, treePath());
|
|
1518
|
+
sessions.push(session);
|
|
1519
|
+
cacheDir = session.cacheDir;
|
|
1520
|
+
await prune(cacheDir);
|
|
1521
|
+
}
|
|
1219
1522
|
removeCleanTree(tree);
|
|
1220
1523
|
tree = materializeCleanTree({ repoDir: repoRoot, dest: treePath(), source, carryFiles: loaded.config.carryFiles });
|
|
1221
1524
|
loaded = await loadSignoffConfig({ repoRoot: tree.path, configPath: options.configPath });
|
|
@@ -1226,10 +1529,16 @@ async function runSignoff(options = {}) {
|
|
|
1226
1529
|
const nodeRequirement = resolveNodeRequirement(tree.path, config.nodeVersion);
|
|
1227
1530
|
assertNodeVersion(nodeRequirement);
|
|
1228
1531
|
options.onEvent?.({ kind: "tree", path: tree.path, head: tree.head, dirty: tree.dirty });
|
|
1229
|
-
|
|
1532
|
+
await session.protectStore(manifestCacheKey(tree.path, manifestFiles(tree.path)));
|
|
1533
|
+
const store = await withCacheLock(cacheDir, () => resolveStore({
|
|
1534
|
+
treePath: tree.path,
|
|
1535
|
+
cacheDir,
|
|
1536
|
+
generations: config.storeGenerations,
|
|
1537
|
+
protectedStoreKeys: activeStoreKeys(cacheDir)
|
|
1538
|
+
}));
|
|
1230
1539
|
options.onEvent?.({ kind: "store", storeDir: store.storeDir, cacheHit: store.hit, cacheKey: store.cacheKey });
|
|
1231
1540
|
const installSpec = config.install ?? {};
|
|
1232
|
-
const installCwd =
|
|
1541
|
+
const installCwd = join9(tree.path, installSpec.cwd ?? ".");
|
|
1233
1542
|
const installCommand = withStoreDir(
|
|
1234
1543
|
installSpec.run ?? "pnpm install --frozen-lockfile",
|
|
1235
1544
|
installSpec.storeDirFlag,
|
|
@@ -1306,7 +1615,7 @@ async function runSignoff(options = {}) {
|
|
|
1306
1615
|
options.onEvent?.({ kind: "step-start", name: step.name, command: plan.command, seed: plan.seed });
|
|
1307
1616
|
const result = await runCommand({
|
|
1308
1617
|
command: plan.command,
|
|
1309
|
-
cwd:
|
|
1618
|
+
cwd: join9(treeRoot, step.cwd ?? "."),
|
|
1310
1619
|
env: buildEnv(sharedEnv, [step.env]),
|
|
1311
1620
|
timeoutMs: step.timeoutMs,
|
|
1312
1621
|
signal
|
|
@@ -1340,7 +1649,14 @@ async function runSignoff(options = {}) {
|
|
|
1340
1649
|
options
|
|
1341
1650
|
});
|
|
1342
1651
|
} finally {
|
|
1343
|
-
|
|
1652
|
+
try {
|
|
1653
|
+
if (tree && !options.keepWorkspace && existsSync9(tree.path)) removeCleanTree(tree);
|
|
1654
|
+
} finally {
|
|
1655
|
+
for (const active of sessions) {
|
|
1656
|
+
await active.close();
|
|
1657
|
+
await prune(active.cacheDir, options.keepWorkspace && tree ? [tree.path] : []);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1344
1660
|
}
|
|
1345
1661
|
}
|
|
1346
1662
|
function runWorkerDefaults(treeRoot, workerConfigs, options) {
|
|
@@ -1772,8 +2088,8 @@ function round(value) {
|
|
|
1772
2088
|
}
|
|
1773
2089
|
|
|
1774
2090
|
// src/signoff/shell-alignment-cli.ts
|
|
1775
|
-
import { mkdirSync as
|
|
1776
|
-
import { dirname as dirname3, resolve as
|
|
2091
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2092
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
1777
2093
|
function shellAlignmentUsage() {
|
|
1778
2094
|
return [
|
|
1779
2095
|
"Usage: agent-app-signoff shell-alignment --base-url <url> --route <path> [--route <path> ...] [options]",
|
|
@@ -1823,10 +2139,10 @@ function parseShellAlignmentArgs(argv) {
|
|
|
1823
2139
|
});
|
|
1824
2140
|
break;
|
|
1825
2141
|
case "--storage-state":
|
|
1826
|
-
out.storageState =
|
|
2142
|
+
out.storageState = resolve5(take());
|
|
1827
2143
|
break;
|
|
1828
2144
|
case "--theme-script":
|
|
1829
|
-
out.themeScript =
|
|
2145
|
+
out.themeScript = readFileSync7(resolve5(take()), "utf8");
|
|
1830
2146
|
break;
|
|
1831
2147
|
case "--ready":
|
|
1832
2148
|
out.readySelector = take();
|
|
@@ -1838,7 +2154,7 @@ function parseShellAlignmentArgs(argv) {
|
|
|
1838
2154
|
break;
|
|
1839
2155
|
}
|
|
1840
2156
|
case "--screenshots":
|
|
1841
|
-
out.screenshotDir =
|
|
2157
|
+
out.screenshotDir = resolve5(take());
|
|
1842
2158
|
break;
|
|
1843
2159
|
case "--json":
|
|
1844
2160
|
out.jsonPath = take();
|
|
@@ -1868,12 +2184,12 @@ ${shellAlignmentUsage()}
|
|
|
1868
2184
|
`);
|
|
1869
2185
|
return 2;
|
|
1870
2186
|
}
|
|
1871
|
-
if (options.screenshotDir)
|
|
2187
|
+
if (options.screenshotDir) mkdirSync4(options.screenshotDir, { recursive: true });
|
|
1872
2188
|
const report = await checkShellAlignment(options);
|
|
1873
2189
|
if (options.jsonPath) {
|
|
1874
|
-
const abs =
|
|
1875
|
-
|
|
1876
|
-
|
|
2190
|
+
const abs = resolve5(options.jsonPath);
|
|
2191
|
+
mkdirSync4(dirname3(abs), { recursive: true });
|
|
2192
|
+
writeFileSync4(abs, `${JSON.stringify(report, null, 2)}
|
|
1877
2193
|
`);
|
|
1878
2194
|
}
|
|
1879
2195
|
process.stdout.write(`${formatShellAlignmentReport(report)}
|
|
@@ -2165,8 +2481,8 @@ function formatSurfaceLadderReport(report) {
|
|
|
2165
2481
|
}
|
|
2166
2482
|
|
|
2167
2483
|
// src/signoff/surface-ladder-cli.ts
|
|
2168
|
-
import { mkdirSync as
|
|
2169
|
-
import { dirname as dirname4, resolve as
|
|
2484
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
2485
|
+
import { dirname as dirname4, resolve as resolve6 } from "path";
|
|
2170
2486
|
function surfaceLadderUsage() {
|
|
2171
2487
|
return [
|
|
2172
2488
|
"Usage: agent-app-signoff surface-ladder --base-url <url> --route <path> [--route <path> ...] [options]",
|
|
@@ -2224,10 +2540,10 @@ function parseSurfaceLadderArgs(argv) {
|
|
|
2224
2540
|
});
|
|
2225
2541
|
break;
|
|
2226
2542
|
case "--storage-state":
|
|
2227
|
-
out.storageState =
|
|
2543
|
+
out.storageState = resolve6(take());
|
|
2228
2544
|
break;
|
|
2229
2545
|
case "--theme-script":
|
|
2230
|
-
out.themeScript =
|
|
2546
|
+
out.themeScript = readFileSync8(resolve6(take()), "utf8");
|
|
2231
2547
|
break;
|
|
2232
2548
|
case "--ready":
|
|
2233
2549
|
out.readySelector = take();
|
|
@@ -2245,10 +2561,10 @@ function parseSurfaceLadderArgs(argv) {
|
|
|
2245
2561
|
break;
|
|
2246
2562
|
}
|
|
2247
2563
|
case "--inject-css":
|
|
2248
|
-
out.injectCss =
|
|
2564
|
+
out.injectCss = readFileSync8(resolve6(take()), "utf8");
|
|
2249
2565
|
break;
|
|
2250
2566
|
case "--screenshots":
|
|
2251
|
-
out.screenshotDir =
|
|
2567
|
+
out.screenshotDir = resolve6(take());
|
|
2252
2568
|
break;
|
|
2253
2569
|
case "--json":
|
|
2254
2570
|
out.jsonPath = take();
|
|
@@ -2278,12 +2594,12 @@ ${surfaceLadderUsage()}
|
|
|
2278
2594
|
`);
|
|
2279
2595
|
return 2;
|
|
2280
2596
|
}
|
|
2281
|
-
if (options.screenshotDir)
|
|
2597
|
+
if (options.screenshotDir) mkdirSync5(options.screenshotDir, { recursive: true });
|
|
2282
2598
|
const report = await checkSurfaceLadder(options);
|
|
2283
2599
|
if (options.jsonPath) {
|
|
2284
|
-
const abs =
|
|
2285
|
-
|
|
2286
|
-
|
|
2600
|
+
const abs = resolve6(options.jsonPath);
|
|
2601
|
+
mkdirSync5(dirname4(abs), { recursive: true });
|
|
2602
|
+
writeFileSync5(abs, `${JSON.stringify(report, null, 2)}
|
|
2287
2603
|
`);
|
|
2288
2604
|
}
|
|
2289
2605
|
process.stdout.write(`${formatSurfaceLadderReport(report)}
|
|
@@ -2376,8 +2692,8 @@ function formatWorkspaceSurfaceReport(report) {
|
|
|
2376
2692
|
}
|
|
2377
2693
|
|
|
2378
2694
|
// src/signoff/workspace-surface-cli.ts
|
|
2379
|
-
import { mkdirSync as
|
|
2380
|
-
import { dirname as dirname5, resolve as
|
|
2695
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
2696
|
+
import { dirname as dirname5, resolve as resolve7 } from "path";
|
|
2381
2697
|
var USAGE = [
|
|
2382
2698
|
"Usage: agent-app-signoff workspace-surface --base-url <url> --route <path> [--route <path> ...] [options]",
|
|
2383
2699
|
"",
|
|
@@ -2406,12 +2722,12 @@ ${USAGE}
|
|
|
2406
2722
|
`);
|
|
2407
2723
|
return 2;
|
|
2408
2724
|
}
|
|
2409
|
-
if (options.screenshotDir)
|
|
2725
|
+
if (options.screenshotDir) mkdirSync6(options.screenshotDir, { recursive: true });
|
|
2410
2726
|
const report = await checkWorkspaceSurface(options);
|
|
2411
2727
|
if (options.jsonPath) {
|
|
2412
|
-
const abs =
|
|
2413
|
-
|
|
2414
|
-
|
|
2728
|
+
const abs = resolve7(options.jsonPath);
|
|
2729
|
+
mkdirSync6(dirname5(abs), { recursive: true });
|
|
2730
|
+
writeFileSync6(abs, `${JSON.stringify(report, null, 2)}
|
|
2415
2731
|
`);
|
|
2416
2732
|
}
|
|
2417
2733
|
process.stdout.write(`${formatWorkspaceSurfaceReport(report)}
|
|
@@ -2469,4 +2785,4 @@ export {
|
|
|
2469
2785
|
formatWorkspaceSurfaceReport,
|
|
2470
2786
|
runWorkspaceSurfaceCli
|
|
2471
2787
|
};
|
|
2472
|
-
//# sourceMappingURL=chunk-
|
|
2788
|
+
//# sourceMappingURL=chunk-WDY47GBR.js.map
|