@ricsam/r5d-worker 0.0.136 → 0.0.138
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/README.md +4 -94
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +46 -7537
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/action-scheduler.mjs +31 -0
- package/dist/mjs/personal/client.mjs +215 -0
- package/dist/mjs/personal/runtime.mjs +328 -0
- package/dist/mjs/personal/tcp.mjs +217 -0
- package/dist/mjs/runtime/adapter.mjs +42 -0
- package/dist/mjs/runtime/client.mjs +95 -0
- package/dist/mjs/runtime/daemon.mjs +131 -0
- package/dist/mjs/runtime/executor.mjs +436 -0
- package/dist/mjs/runtime/index.mjs +15 -0
- package/dist/mjs/runtime/protocol.mjs +139 -0
- package/dist/mjs/runtime/pty-host.mjs +156 -0
- package/dist/mjs/runtime/pty.mjs +127 -0
- package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
- package/dist/mjs/runtime/releases/grants.mjs +93 -0
- package/dist/mjs/runtime/releases/main.mjs +101 -0
- package/dist/mjs/runtime/releases/manager.mjs +541 -0
- package/dist/mjs/runtime/releases/process.mjs +88 -0
- package/dist/mjs/runtime/releases/retirement.mjs +18 -0
- package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
- package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
- package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
- package/dist/mjs/runtime/storage.mjs +156 -0
- package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
- package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
- package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
- package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
- package/dist/mjs/runtime/workspace/files.mjs +308 -0
- package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
- package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
- package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
- package/dist/types/main.d.ts +0 -1167
- package/dist/types/personal/action-scheduler.d.ts +23 -0
- package/dist/types/personal/client.d.ts +14 -0
- package/dist/types/personal/runtime.d.ts +121 -0
- package/dist/types/personal/tcp.d.ts +14 -0
- package/dist/types/runtime/client.d.ts +17 -0
- package/dist/types/runtime/daemon.d.ts +8 -0
- package/dist/types/runtime/executor.d.ts +35 -0
- package/dist/types/runtime/index.d.ts +5 -0
- package/dist/types/runtime/protocol.d.ts +544 -0
- package/dist/types/runtime/pty.d.ts +30 -0
- package/dist/types/runtime/releases/artifacts.d.ts +51 -0
- package/dist/types/runtime/releases/grants.d.ts +16 -0
- package/dist/types/runtime/releases/manager.d.ts +145 -0
- package/dist/types/runtime/releases/process.d.ts +14 -0
- package/dist/types/runtime/releases/retirement.d.ts +15 -0
- package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
- package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
- package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
- package/dist/types/runtime/storage.d.ts +21 -0
- package/dist/types/runtime/test-harness.d.ts +53 -0
- package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
- package/dist/types/runtime/workspace/authority.d.ts +338 -0
- package/dist/types/runtime/workspace/contracts.d.ts +86 -0
- package/dist/types/runtime/workspace/file-write.d.ts +14 -0
- package/dist/types/runtime/workspace/files.d.ts +21 -0
- package/dist/types/runtime/workspace/http-body.d.ts +9 -0
- package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
- package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
- package/package.json +8 -6
- package/dist/cjs/atomic-rename.cjs +0 -303
- package/dist/cjs/browser-upload.cjs +0 -73
- package/dist/cjs/cli-update.cjs +0 -108
- package/dist/cjs/command-launcher.cjs +0 -423
- package/dist/cjs/control-command-policy.cjs +0 -177
- package/dist/cjs/git-blob-hash.cjs +0 -41
- package/dist/cjs/git-identity.cjs +0 -39
- package/dist/cjs/git-process-environment.cjs +0 -133
- package/dist/cjs/inline-output.cjs +0 -62
- package/dist/cjs/main.cjs +0 -7511
- package/dist/cjs/managed-paths.cjs +0 -215
- package/dist/cjs/plan-lint.cjs +0 -165
- package/dist/cjs/plan-parser.cjs +0 -127
- package/dist/cjs/plan-paths.cjs +0 -53
- package/dist/cjs/port-forward-client.cjs +0 -206
- package/dist/cjs/process-tree.cjs +0 -117
- package/dist/cjs/project-checkout-garbage.cjs +0 -219
- package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
- package/dist/cjs/project-mirror-refs-token.cjs +0 -85
- package/dist/cjs/project-workspace-state.cjs +0 -972
- package/dist/cjs/project-worktrees.cjs +0 -1713
- package/dist/cjs/pty-output-coalescer.cjs +0 -87
- package/dist/cjs/recovery-journal-protocol.cjs +0 -56
- package/dist/cjs/recovery-journal-runtime.cjs +0 -133
- package/dist/cjs/recovery-journal-thread.cjs +0 -9
- package/dist/cjs/recovery-journal.cjs +0 -742
- package/dist/cjs/recovery-output-window.cjs +0 -77
- package/dist/cjs/recovery-protocol.cjs +0 -31
- package/dist/cjs/recovery-store.cjs +0 -252
- package/dist/cjs/registry-auth.cjs +0 -310
- package/dist/cjs/repository-transition-policy.cjs +0 -49
- package/dist/cjs/runtime-version.cjs +0 -88
- package/dist/cjs/session-file-mutations.cjs +0 -61
- package/dist/cjs/supervisor.cjs +0 -147
- package/dist/cjs/three-way-merge.cjs +0 -346
- package/dist/cjs/working-tree-mirror.cjs +0 -1872
- package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
- package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
- package/dist/cjs/workspace-command-targets.cjs +0 -63
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
- package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
- package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
- package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
- package/dist/cjs/workspace-git-sync.cjs +0 -3450
- package/dist/cjs/workspace-hydration-ledger.cjs +0 -66
- package/dist/cjs/workspace-hydration-merge.cjs +0 -433
- package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
- package/dist/cjs/workspace-incident-state.cjs +0 -37
- package/dist/cjs/workspace-merge-projection.cjs +0 -467
- package/dist/cjs/workspace-mount-boundary.cjs +0 -71
- package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
- package/dist/cjs/workspace-mutation-gate.cjs +0 -127
- package/dist/cjs/workspace-path-move.cjs +0 -195
- package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
- package/dist/cjs/workspace-project-config-policy.cjs +0 -53
- package/dist/cjs/workspace-projection-ledger.cjs +0 -81
- package/dist/cjs/workspace-publication-evidence.cjs +0 -46
- package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
- package/dist/mjs/atomic-rename.mjs +0 -261
- package/dist/mjs/browser-upload.mjs +0 -42
- package/dist/mjs/command-launcher.mjs +0 -393
- package/dist/mjs/control-command-policy.mjs +0 -146
- package/dist/mjs/git-blob-hash.mjs +0 -16
- package/dist/mjs/git-identity.mjs +0 -15
- package/dist/mjs/git-process-environment.mjs +0 -105
- package/dist/mjs/inline-output.mjs +0 -37
- package/dist/mjs/managed-paths.mjs +0 -174
- package/dist/mjs/plan-lint.mjs +0 -129
- package/dist/mjs/plan-parser.mjs +0 -94
- package/dist/mjs/plan-paths.mjs +0 -25
- package/dist/mjs/port-forward-client.mjs +0 -172
- package/dist/mjs/project-checkout-garbage.mjs +0 -178
- package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
- package/dist/mjs/project-mirror-refs-token.mjs +0 -53
- package/dist/mjs/project-workspace-state.mjs +0 -937
- package/dist/mjs/project-worktrees.mjs +0 -1657
- package/dist/mjs/pty-output-coalescer.mjs +0 -61
- package/dist/mjs/recovery-journal-protocol.mjs +0 -30
- package/dist/mjs/recovery-journal-runtime.mjs +0 -112
- package/dist/mjs/recovery-journal-thread.mjs +0 -8
- package/dist/mjs/recovery-journal.mjs +0 -694
- package/dist/mjs/recovery-output-window.mjs +0 -53
- package/dist/mjs/recovery-protocol.mjs +0 -6
- package/dist/mjs/recovery-store.mjs +0 -217
- package/dist/mjs/registry-auth.mjs +0 -269
- package/dist/mjs/repository-transition-policy.mjs +0 -22
- package/dist/mjs/runtime-version.mjs +0 -54
- package/dist/mjs/session-file-mutations.mjs +0 -37
- package/dist/mjs/supervisor.mjs +0 -114
- package/dist/mjs/three-way-merge.mjs +0 -318
- package/dist/mjs/working-tree-mirror.mjs +0 -1817
- package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
- package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
- package/dist/mjs/workspace-command-targets.mjs +0 -37
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
- package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
- package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
- package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
- package/dist/mjs/workspace-git-sync.mjs +0 -3407
- package/dist/mjs/workspace-hydration-ledger.mjs +0 -42
- package/dist/mjs/workspace-hydration-merge.mjs +0 -399
- package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
- package/dist/mjs/workspace-incident-state.mjs +0 -12
- package/dist/mjs/workspace-merge-projection.mjs +0 -433
- package/dist/mjs/workspace-mount-boundary.mjs +0 -37
- package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
- package/dist/mjs/workspace-mutation-gate.mjs +0 -103
- package/dist/mjs/workspace-path-move.mjs +0 -160
- package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
- package/dist/mjs/workspace-project-config-policy.mjs +0 -27
- package/dist/mjs/workspace-projection-ledger.mjs +0 -57
- package/dist/mjs/workspace-publication-evidence.mjs +0 -21
- package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
- package/dist/types/atomic-rename.d.ts +0 -78
- package/dist/types/browser-upload.d.ts +0 -7
- package/dist/types/command-launcher.d.ts +0 -104
- package/dist/types/control-command-policy.d.ts +0 -53
- package/dist/types/git-blob-hash.d.ts +0 -10
- package/dist/types/git-identity.d.ts +0 -9
- package/dist/types/git-process-environment.d.ts +0 -9
- package/dist/types/inline-output.d.ts +0 -3
- package/dist/types/managed-paths.d.ts +0 -19
- package/dist/types/plan-lint.d.ts +0 -16
- package/dist/types/plan-parser.d.ts +0 -38
- package/dist/types/plan-paths.d.ts +0 -36
- package/dist/types/port-forward-client.d.ts +0 -8
- package/dist/types/project-checkout-garbage.d.ts +0 -86
- package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
- package/dist/types/project-mirror-refs-token.d.ts +0 -53
- package/dist/types/project-workspace-state.d.ts +0 -188
- package/dist/types/project-worktrees.d.ts +0 -397
- package/dist/types/pty-output-coalescer.d.ts +0 -20
- package/dist/types/recovery-journal-protocol.d.ts +0 -35
- package/dist/types/recovery-journal-runtime.d.ts +0 -12
- package/dist/types/recovery-journal-thread.d.ts +0 -1
- package/dist/types/recovery-journal.d.ts +0 -248
- package/dist/types/recovery-output-window.d.ts +0 -19
- package/dist/types/recovery-protocol.d.ts +0 -59
- package/dist/types/recovery-store.d.ts +0 -52
- package/dist/types/registry-auth.d.ts +0 -47
- package/dist/types/repository-transition-policy.d.ts +0 -26
- package/dist/types/runtime-version.d.ts +0 -1
- package/dist/types/session-file-mutations.d.ts +0 -22
- package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
- package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
- package/dist/types/supervisor-signal-fixture.d.ts +0 -1
- package/dist/types/supervisor.d.ts +0 -23
- package/dist/types/three-way-merge.d.ts +0 -77
- package/dist/types/working-tree-mirror.d.ts +0 -424
- package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
- package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
- package/dist/types/workspace-command-sync-policy.d.ts +0 -57
- package/dist/types/workspace-command-targets.d.ts +0 -37
- package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
- package/dist/types/workspace-filesystem-executor.d.ts +0 -123
- package/dist/types/workspace-filesystem-job-types.d.ts +0 -288
- package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
- package/dist/types/workspace-git-sync.d.ts +0 -495
- package/dist/types/workspace-hydration-ledger.d.ts +0 -43
- package/dist/types/workspace-hydration-merge.d.ts +0 -95
- package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
- package/dist/types/workspace-incident-state.d.ts +0 -19
- package/dist/types/workspace-merge-projection.d.ts +0 -60
- package/dist/types/workspace-mount-boundary.d.ts +0 -10
- package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
- package/dist/types/workspace-mutation-gate.d.ts +0 -42
- package/dist/types/workspace-path-move.d.ts +0 -21
- package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
- package/dist/types/workspace-project-config-policy.d.ts +0 -48
- package/dist/types/workspace-projection-ledger.d.ts +0 -59
- package/dist/types/workspace-publication-evidence.d.ts +0 -17
- package/dist/types/workspace-sync-coalescer.d.ts +0 -28
- /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/adapter.d.ts} +0 -0
- /package/dist/types/{recovery-journal-stall-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
package/dist/mjs/plan-parser.mjs
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
const PLAN_TASK_ID_REGEX = /^[a-z0-9][a-z0-9-]*$/;
|
|
2
|
-
const PLAN_TASK_LINE_REGEX = /^- \[( |x)\] ([a-z0-9][a-z0-9-]*): (.+)$/;
|
|
3
|
-
const PLAN_TASKS_HEADING_REGEX = /^## Tasks\s*$/;
|
|
4
|
-
function getPlanMetadata(content) {
|
|
5
|
-
const title = content.split(/\r?\n/).map((line) => line.match(/^# (.+)$/)?.[1]?.trim()).find((value) => Boolean(value));
|
|
6
|
-
return { title: title || "Untitled Plan" };
|
|
7
|
-
}
|
|
8
|
-
function getTasksSectionRange(content) {
|
|
9
|
-
const lines = content.split(/\r?\n/);
|
|
10
|
-
const startIndex = lines.findIndex((line) => PLAN_TASKS_HEADING_REGEX.test(line));
|
|
11
|
-
if (startIndex === -1) return null;
|
|
12
|
-
let endIndex = lines.length;
|
|
13
|
-
for (let index = startIndex + 1; index < lines.length; index += 1) {
|
|
14
|
-
if (/^##\s+/.test(lines[index] ?? "")) {
|
|
15
|
-
endIndex = index;
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return { startIndex, endIndex };
|
|
20
|
-
}
|
|
21
|
-
function parseMarkdownPlan(content, planId) {
|
|
22
|
-
const lines = content.split(/\r?\n/);
|
|
23
|
-
const { title } = getPlanMetadata(content);
|
|
24
|
-
const range = getTasksSectionRange(content);
|
|
25
|
-
const tasks = [];
|
|
26
|
-
if (range) {
|
|
27
|
-
for (let index = range.startIndex + 1; index < range.endIndex; index += 1) {
|
|
28
|
-
const line = lines[index] ?? "";
|
|
29
|
-
const match = line.match(PLAN_TASK_LINE_REGEX);
|
|
30
|
-
if (!match) continue;
|
|
31
|
-
const description = match[3].trim();
|
|
32
|
-
if (!description) continue;
|
|
33
|
-
tasks.push({
|
|
34
|
-
id: match[2],
|
|
35
|
-
description,
|
|
36
|
-
checked: match[1] === "x",
|
|
37
|
-
lineNumber: index + 1
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const currentTask = tasks.find((task) => !task.checked) ?? null;
|
|
42
|
-
return {
|
|
43
|
-
id: planId,
|
|
44
|
-
title,
|
|
45
|
-
content,
|
|
46
|
-
tasks,
|
|
47
|
-
currentTask,
|
|
48
|
-
complete: tasks.length > 0 && tasks.every((task) => task.checked),
|
|
49
|
-
tasksStartLine: range ? range.startIndex + 1 : null,
|
|
50
|
-
tasksEndLine: range ? range.endIndex : null
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const parsePlan = parseMarkdownPlan;
|
|
54
|
-
function createPlanMarkdown(planId, title = formatPlanTitle(planId)) {
|
|
55
|
-
return `# ${title}
|
|
56
|
-
|
|
57
|
-
Brief implementation notes.
|
|
58
|
-
|
|
59
|
-
## Tasks
|
|
60
|
-
`;
|
|
61
|
-
}
|
|
62
|
-
function updateTaskCheckbox(content, taskId, checked) {
|
|
63
|
-
if (!PLAN_TASK_ID_REGEX.test(taskId)) {
|
|
64
|
-
throw new Error(`Invalid task id: ${taskId}`);
|
|
65
|
-
}
|
|
66
|
-
const lines = content.split(/\r?\n/);
|
|
67
|
-
const range = getTasksSectionRange(content);
|
|
68
|
-
if (!range) {
|
|
69
|
-
throw new Error("Plan is missing a ## Tasks section.");
|
|
70
|
-
}
|
|
71
|
-
for (let index = range.startIndex + 1; index < range.endIndex; index += 1) {
|
|
72
|
-
const line = lines[index] ?? "";
|
|
73
|
-
const match = line.match(PLAN_TASK_LINE_REGEX);
|
|
74
|
-
if (!match || match[2] !== taskId) continue;
|
|
75
|
-
lines[index] = `- [${checked ? "x" : " "}] ${match[2]}: ${match[3].trim()}`;
|
|
76
|
-
return lines.join("\n");
|
|
77
|
-
}
|
|
78
|
-
throw new Error(`Task not found in plan: ${taskId}`);
|
|
79
|
-
}
|
|
80
|
-
function formatPlanTitle(planId) {
|
|
81
|
-
return planId.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
82
|
-
}
|
|
83
|
-
export {
|
|
84
|
-
PLAN_TASKS_HEADING_REGEX,
|
|
85
|
-
PLAN_TASK_ID_REGEX,
|
|
86
|
-
PLAN_TASK_LINE_REGEX,
|
|
87
|
-
createPlanMarkdown,
|
|
88
|
-
formatPlanTitle,
|
|
89
|
-
getPlanMetadata,
|
|
90
|
-
getTasksSectionRange,
|
|
91
|
-
parseMarkdownPlan,
|
|
92
|
-
parsePlan,
|
|
93
|
-
updateTaskCheckbox
|
|
94
|
-
};
|
package/dist/mjs/plan-paths.mjs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { PLAN_TASK_ID_REGEX } from "./plan-parser.mjs";
|
|
2
|
-
const PLAN_EXTENSION = ".plan.md";
|
|
3
|
-
function validatePlanId(planId) {
|
|
4
|
-
if (!PLAN_TASK_ID_REGEX.test(planId)) throw new Error(`Invalid plan id: ${planId}`);
|
|
5
|
-
}
|
|
6
|
-
function assertPlanTarget(target) {
|
|
7
|
-
if (target.type === "workspace" && target.rootProfile !== "visible_projects") {
|
|
8
|
-
throw new Error("Ordinary plans are unavailable in canonical synchronization sessions");
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function getWorkerPlansPath(target) {
|
|
12
|
-
assertPlanTarget(target);
|
|
13
|
-
return target.type === "project" ? "$R5D_ROOT/plans/$R5D_PROJECT_ID/$R5D_BRANCH_NAME" : "$R5D_ROOT/plans/workspace";
|
|
14
|
-
}
|
|
15
|
-
function getWorkerPlanPath(target, planId) {
|
|
16
|
-
validatePlanId(planId);
|
|
17
|
-
return `${getWorkerPlansPath(target)}/${planId}${PLAN_EXTENSION}`;
|
|
18
|
-
}
|
|
19
|
-
export {
|
|
20
|
-
PLAN_EXTENSION,
|
|
21
|
-
assertPlanTarget,
|
|
22
|
-
getWorkerPlanPath,
|
|
23
|
-
getWorkerPlansPath,
|
|
24
|
-
validatePlanId
|
|
25
|
-
};
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import net from "node:net";
|
|
2
|
-
import WebSocket, {} from "ws";
|
|
3
|
-
const RELAY_FRAME_BYTES = 64 * 1024;
|
|
4
|
-
const RELAY_PAUSE_BYTES = 1024 * 1024;
|
|
5
|
-
const RELAY_RESUME_BYTES = 256 * 1024;
|
|
6
|
-
const LOOPBACK_HOSTS = ["127.0.0.1", "::1"];
|
|
7
|
-
function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
|
|
8
|
-
const url = new URL("/worker/port-forward/ws", baseUrl);
|
|
9
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
10
|
-
url.searchParams.set("label", label);
|
|
11
|
-
url.searchParams.set("forward", forwardId);
|
|
12
|
-
url.searchParams.set("connection", relayConnectionId);
|
|
13
|
-
return url.toString();
|
|
14
|
-
}
|
|
15
|
-
function loopbackAddress(host, port) {
|
|
16
|
-
return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
|
|
17
|
-
}
|
|
18
|
-
function rawDataBuffer(data) {
|
|
19
|
-
if (Buffer.isBuffer(data)) return data;
|
|
20
|
-
if (Array.isArray(data)) return Buffer.concat(data);
|
|
21
|
-
return Buffer.from(data);
|
|
22
|
-
}
|
|
23
|
-
function openWorkerPortForwardRelay(options) {
|
|
24
|
-
const relay = new WebSocket(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
|
|
25
|
-
headers: { Authorization: `Bearer ${options.token}` },
|
|
26
|
-
perMessageDeflate: false
|
|
27
|
-
});
|
|
28
|
-
let socket;
|
|
29
|
-
let relayReady = false;
|
|
30
|
-
let targetReady = false;
|
|
31
|
-
let targetEndRequested = false;
|
|
32
|
-
let targetFailure;
|
|
33
|
-
const pendingChunks = [];
|
|
34
|
-
let pendingBytes = 0;
|
|
35
|
-
const pendingWrites = [];
|
|
36
|
-
let pendingWriteBytes = 0;
|
|
37
|
-
let closing = false;
|
|
38
|
-
let resumeTimer;
|
|
39
|
-
const maybeResume = () => {
|
|
40
|
-
if (relayReady && targetReady && !resumeTimer) socket?.resume();
|
|
41
|
-
};
|
|
42
|
-
const cleanup = () => {
|
|
43
|
-
if (closing) return;
|
|
44
|
-
closing = true;
|
|
45
|
-
if (resumeTimer) clearInterval(resumeTimer);
|
|
46
|
-
socket?.destroy();
|
|
47
|
-
if (relay.readyState === WebSocket.OPEN || relay.readyState === WebSocket.CONNECTING) relay.close();
|
|
48
|
-
};
|
|
49
|
-
const sendError = (message) => {
|
|
50
|
-
if (closing) return;
|
|
51
|
-
const payload = JSON.stringify({ type: "error", error: message });
|
|
52
|
-
if (relay.readyState === WebSocket.CONNECTING) {
|
|
53
|
-
closing = true;
|
|
54
|
-
if (resumeTimer) clearInterval(resumeTimer);
|
|
55
|
-
socket?.destroy();
|
|
56
|
-
relay.once("open", () => {
|
|
57
|
-
relay.send(payload);
|
|
58
|
-
relay.close();
|
|
59
|
-
});
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (relay.readyState === WebSocket.OPEN) relay.send(payload);
|
|
63
|
-
cleanup();
|
|
64
|
-
};
|
|
65
|
-
const waitForRelayDrain = () => {
|
|
66
|
-
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
67
|
-
socket?.pause();
|
|
68
|
-
resumeTimer = setInterval(() => {
|
|
69
|
-
if (relay.readyState !== WebSocket.OPEN) return cleanup();
|
|
70
|
-
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
71
|
-
clearInterval(resumeTimer);
|
|
72
|
-
resumeTimer = void 0;
|
|
73
|
-
maybeResume();
|
|
74
|
-
}, 10);
|
|
75
|
-
};
|
|
76
|
-
const sendChunk = (chunk) => {
|
|
77
|
-
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
78
|
-
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
79
|
-
}
|
|
80
|
-
waitForRelayDrain();
|
|
81
|
-
};
|
|
82
|
-
const adoptTarget = (target, host) => {
|
|
83
|
-
if (closing) return target.destroy();
|
|
84
|
-
socket = target;
|
|
85
|
-
target.on("data", (chunk) => {
|
|
86
|
-
if (!relayReady || relay.readyState !== WebSocket.OPEN) {
|
|
87
|
-
pendingBytes += chunk.byteLength;
|
|
88
|
-
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
89
|
-
pendingChunks.push(Buffer.from(chunk));
|
|
90
|
-
target.pause();
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
sendChunk(chunk);
|
|
94
|
-
});
|
|
95
|
-
target.on("end", () => {
|
|
96
|
-
if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
97
|
-
});
|
|
98
|
-
target.on("error", (error) => sendError(`Connection to ${loopbackAddress(host, options.workerPort)} failed: ${error.message}`));
|
|
99
|
-
target.on("close", cleanup);
|
|
100
|
-
target.pause();
|
|
101
|
-
targetReady = true;
|
|
102
|
-
for (const chunk of pendingWrites.splice(0)) target.write(chunk);
|
|
103
|
-
pendingWriteBytes = 0;
|
|
104
|
-
if (targetFailure) return void target.destroy(targetFailure);
|
|
105
|
-
if (targetEndRequested) target.end();
|
|
106
|
-
maybeResume();
|
|
107
|
-
};
|
|
108
|
-
const dialFailures = [];
|
|
109
|
-
const dialTarget = (host, fallbackHosts2) => {
|
|
110
|
-
if (closing) return;
|
|
111
|
-
const candidate = net.createConnection({ host, port: options.workerPort, allowHalfOpen: true });
|
|
112
|
-
candidate.pause();
|
|
113
|
-
candidate.setNoDelay(true);
|
|
114
|
-
const onDialError = (error) => {
|
|
115
|
-
candidate.destroy();
|
|
116
|
-
dialFailures.push(`${loopbackAddress(host, options.workerPort)} (${error.code ?? error.message})`);
|
|
117
|
-
const [nextHost, ...rest] = fallbackHosts2;
|
|
118
|
-
if (nextHost) return dialTarget(nextHost, rest);
|
|
119
|
-
sendError(
|
|
120
|
-
`Could not connect to ${dialFailures.join(" or ")} on the worker. Nothing is listening on either loopback address for port ${options.workerPort} \u2014 make sure the server is running and bound to a loopback or wildcard host.`
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
candidate.once("error", onDialError);
|
|
124
|
-
candidate.once("connect", () => {
|
|
125
|
-
candidate.off("error", onDialError);
|
|
126
|
-
adoptTarget(candidate, host);
|
|
127
|
-
});
|
|
128
|
-
};
|
|
129
|
-
relay.on("message", (data, isBinary) => {
|
|
130
|
-
if (!isBinary) {
|
|
131
|
-
let control;
|
|
132
|
-
try {
|
|
133
|
-
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
134
|
-
} catch {
|
|
135
|
-
return cleanup();
|
|
136
|
-
}
|
|
137
|
-
if (control.type === "ready") {
|
|
138
|
-
relayReady = true;
|
|
139
|
-
for (const chunk2 of pendingChunks.splice(0)) sendChunk(chunk2);
|
|
140
|
-
pendingBytes = 0;
|
|
141
|
-
maybeResume();
|
|
142
|
-
} else if (control.type === "end") {
|
|
143
|
-
if (socket) socket.end();
|
|
144
|
-
else targetEndRequested = true;
|
|
145
|
-
} else if (control.type === "error") {
|
|
146
|
-
const failure = new Error(control.error || "Port-forward relay failed.");
|
|
147
|
-
if (socket) socket.destroy(failure);
|
|
148
|
-
else targetFailure = failure;
|
|
149
|
-
}
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
const chunk = rawDataBuffer(data);
|
|
153
|
-
if (!socket) {
|
|
154
|
-
pendingWriteBytes += chunk.byteLength;
|
|
155
|
-
if (pendingWriteBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
156
|
-
pendingWrites.push(Buffer.from(chunk));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const target = socket;
|
|
160
|
-
if (!target.write(chunk)) {
|
|
161
|
-
relay.pause();
|
|
162
|
-
target.once("drain", () => relay.resume());
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
relay.on("error", cleanup);
|
|
166
|
-
relay.once("close", cleanup);
|
|
167
|
-
const [primaryHost, ...fallbackHosts] = LOOPBACK_HOSTS;
|
|
168
|
-
dialTarget(primaryHost, fallbackHosts);
|
|
169
|
-
}
|
|
170
|
-
export {
|
|
171
|
-
openWorkerPortForwardRelay
|
|
172
|
-
};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
const PROJECT_DELETED_CHECKOUTS_DIRECTORY = ".r5d-deleted";
|
|
5
|
-
function isRealDirectory(candidate) {
|
|
6
|
-
try {
|
|
7
|
-
return fs.lstatSync(candidate).isDirectory();
|
|
8
|
-
} catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
function stageProjectCheckoutForDeletion(projectRoot, branchName, checkoutPath) {
|
|
13
|
-
const garbageRoot = path.join(projectRoot, PROJECT_DELETED_CHECKOUTS_DIRECTORY);
|
|
14
|
-
if (!isRealDirectory(checkoutPath)) throw new Error(`Cannot stage ${checkoutPath} for deletion: not a directory`);
|
|
15
|
-
fs.mkdirSync(garbageRoot, { recursive: true });
|
|
16
|
-
if (!isRealDirectory(garbageRoot)) throw new Error(`Cannot stage ${checkoutPath} for deletion: ${garbageRoot} is not a directory`);
|
|
17
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
18
|
-
const target = path.join(garbageRoot, `${branchName.replace(/\//g, "__")}-${timestamp}-${randomUUID().slice(0, 8)}`);
|
|
19
|
-
fs.renameSync(checkoutPath, target);
|
|
20
|
-
return target;
|
|
21
|
-
}
|
|
22
|
-
function stageProjectForDeletion(projectsRoot, projectRoot) {
|
|
23
|
-
const root = path.resolve(projectsRoot);
|
|
24
|
-
const resolved = path.resolve(projectRoot);
|
|
25
|
-
const relative = path.relative(root, resolved);
|
|
26
|
-
const segments = relative.split(path.sep);
|
|
27
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative) || segments.length !== 2 || segments.some((segment) => !segment || segment.startsWith("."))) {
|
|
28
|
-
throw new Error(`Cannot stage ${projectRoot} for deletion: not a project root under ${projectsRoot}`);
|
|
29
|
-
}
|
|
30
|
-
if (!isRealDirectory(resolved)) throw new Error(`Cannot stage ${projectRoot} for deletion: not a directory`);
|
|
31
|
-
const garbageRoot = path.join(root, segments[0], PROJECT_DELETED_CHECKOUTS_DIRECTORY);
|
|
32
|
-
fs.mkdirSync(garbageRoot, { recursive: true });
|
|
33
|
-
if (!isRealDirectory(garbageRoot)) throw new Error(`Cannot stage ${projectRoot} for deletion: ${garbageRoot} is not a directory`);
|
|
34
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
35
|
-
const target = path.join(garbageRoot, `${segments[1]}-${timestamp}-${randomUUID().slice(0, 8)}`);
|
|
36
|
-
fs.renameSync(resolved, target);
|
|
37
|
-
return target;
|
|
38
|
-
}
|
|
39
|
-
function isStagedProjectCheckoutPath(projectsRoot, candidate) {
|
|
40
|
-
const relative = path.relative(path.resolve(projectsRoot), path.resolve(candidate));
|
|
41
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return false;
|
|
42
|
-
const segments = relative.split(path.sep);
|
|
43
|
-
const entry = segments[segments.length - 1];
|
|
44
|
-
if (!entry || entry.startsWith(".")) return false;
|
|
45
|
-
if (segments.length === 4)
|
|
46
|
-
return segments[2] === PROJECT_DELETED_CHECKOUTS_DIRECTORY && !segments[0].startsWith(".") && !segments[1].startsWith(".");
|
|
47
|
-
if (segments.length === 3) return segments[1] === PROJECT_DELETED_CHECKOUTS_DIRECTORY && !segments[0].startsWith(".");
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
function ownedStagedPath(projectsRoot, candidate) {
|
|
51
|
-
const root = path.resolve(projectsRoot);
|
|
52
|
-
const resolved = path.resolve(candidate);
|
|
53
|
-
if (!isStagedProjectCheckoutPath(root, resolved)) return null;
|
|
54
|
-
let current = root;
|
|
55
|
-
for (const segment of path.relative(root, path.dirname(resolved)).split(path.sep)) {
|
|
56
|
-
current = path.join(current, segment);
|
|
57
|
-
if (!isRealDirectory(current)) return null;
|
|
58
|
-
}
|
|
59
|
-
let stat;
|
|
60
|
-
try {
|
|
61
|
-
stat = fs.lstatSync(resolved);
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
if (stat.isSymbolicLink()) return { kind: "link" };
|
|
66
|
-
return stat.isDirectory() ? { kind: "directory" } : null;
|
|
67
|
-
}
|
|
68
|
-
function realDirectoryEntries(directory) {
|
|
69
|
-
if (!isRealDirectory(directory)) return [];
|
|
70
|
-
try {
|
|
71
|
-
return fs.readdirSync(directory, { withFileTypes: true }).map((entry) => entry.name).sort();
|
|
72
|
-
} catch {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function discoverStagedProjectCheckouts(projectsRoot) {
|
|
77
|
-
const root = path.resolve(projectsRoot);
|
|
78
|
-
const staged = [];
|
|
79
|
-
for (const namespace of realDirectoryEntries(root)) {
|
|
80
|
-
if (namespace.startsWith(".")) continue;
|
|
81
|
-
const namespaceGarbageRoot = path.join(root, namespace, PROJECT_DELETED_CHECKOUTS_DIRECTORY);
|
|
82
|
-
for (const entry of realDirectoryEntries(namespaceGarbageRoot)) {
|
|
83
|
-
const candidate = path.join(namespaceGarbageRoot, entry);
|
|
84
|
-
if (ownedStagedPath(root, candidate)) staged.push(candidate);
|
|
85
|
-
}
|
|
86
|
-
for (const project of realDirectoryEntries(path.join(root, namespace))) {
|
|
87
|
-
if (project.startsWith(".")) continue;
|
|
88
|
-
const garbageRoot = path.join(root, namespace, project, PROJECT_DELETED_CHECKOUTS_DIRECTORY);
|
|
89
|
-
for (const entry of realDirectoryEntries(garbageRoot)) {
|
|
90
|
-
const candidate = path.join(garbageRoot, entry);
|
|
91
|
-
if (ownedStagedPath(root, candidate)) staged.push(candidate);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return staged;
|
|
96
|
-
}
|
|
97
|
-
async function removeStagedProjectCheckout(stagedPath) {
|
|
98
|
-
if (fs.lstatSync(stagedPath).isSymbolicLink()) {
|
|
99
|
-
fs.unlinkSync(stagedPath);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (process.platform === "win32") {
|
|
103
|
-
await fs.promises.rm(stagedPath, { recursive: true, force: true });
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const command = ["rm", "-rf", "--", stagedPath];
|
|
107
|
-
if (Bun.which("nice")) command.unshift("nice", "-n", "19");
|
|
108
|
-
if (process.platform === "linux" && Bun.which("ionice")) command.unshift("ionice", "-c", "3");
|
|
109
|
-
const child = Bun.spawn(command, { stdin: "ignore", stdout: "ignore", stderr: "pipe" });
|
|
110
|
-
const [exitCode, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
|
|
111
|
-
if (exitCode !== 0) throw new Error(`rm exited ${exitCode}: ${stderr.trim()}`);
|
|
112
|
-
if (fs.existsSync(stagedPath)) throw new Error("staged checkout still exists after removal");
|
|
113
|
-
}
|
|
114
|
-
class ProjectCheckoutGarbageCollector {
|
|
115
|
-
projectsRoot;
|
|
116
|
-
remove;
|
|
117
|
-
log;
|
|
118
|
-
queued = /* @__PURE__ */ new Set();
|
|
119
|
-
tail = Promise.resolve();
|
|
120
|
-
collectedCount = 0;
|
|
121
|
-
constructor(options) {
|
|
122
|
-
this.projectsRoot = path.resolve(options.projectsRoot);
|
|
123
|
-
this.remove = options.remove ?? removeStagedProjectCheckout;
|
|
124
|
-
this.log = options.log ?? ((message) => process.stderr.write(`${message}
|
|
125
|
-
`));
|
|
126
|
-
}
|
|
127
|
-
/** Number of removals that have completed successfully. */
|
|
128
|
-
get collected() {
|
|
129
|
-
return this.collectedCount;
|
|
130
|
-
}
|
|
131
|
-
/** Resolves once everything queued so far has been attempted. */
|
|
132
|
-
get idle() {
|
|
133
|
-
return this.tail;
|
|
134
|
-
}
|
|
135
|
-
enqueue(stagedPath) {
|
|
136
|
-
const resolved = path.resolve(stagedPath);
|
|
137
|
-
if (!ownedStagedPath(this.projectsRoot, resolved)) {
|
|
138
|
-
throw new Error(`Refusing to collect ${stagedPath}: not a staged checkout under ${this.projectsRoot}`);
|
|
139
|
-
}
|
|
140
|
-
if (this.queued.has(resolved)) return;
|
|
141
|
-
this.queued.add(resolved);
|
|
142
|
-
this.tail = this.tail.then(async () => {
|
|
143
|
-
try {
|
|
144
|
-
const owned = ownedStagedPath(this.projectsRoot, resolved);
|
|
145
|
-
if (owned) {
|
|
146
|
-
const startedAt = performance.now();
|
|
147
|
-
await this.remove(resolved);
|
|
148
|
-
this.collectedCount += 1;
|
|
149
|
-
this.log(`[r5d-worker] collected deleted checkout ${resolved} in ${Math.round(performance.now() - startedAt)}ms`);
|
|
150
|
-
} else if (fs.existsSync(resolved) || isRealDirectory(path.dirname(resolved))) {
|
|
151
|
-
this.log(`[r5d-worker] skipped deleted checkout ${resolved}: no longer an owned staged checkout`);
|
|
152
|
-
}
|
|
153
|
-
} catch (error) {
|
|
154
|
-
this.log(
|
|
155
|
-
`[r5d-worker] deleted checkout ${resolved} could not be collected yet: ${error instanceof Error ? error.message : String(error)}`
|
|
156
|
-
);
|
|
157
|
-
} finally {
|
|
158
|
-
this.queued.delete(resolved);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
/** Enqueue every staged checkout left behind by an earlier process. */
|
|
163
|
-
sweep() {
|
|
164
|
-
const staged = discoverStagedProjectCheckouts(this.projectsRoot);
|
|
165
|
-
for (const stagedPath of staged) this.enqueue(stagedPath);
|
|
166
|
-
return staged.length;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
export {
|
|
170
|
-
PROJECT_DELETED_CHECKOUTS_DIRECTORY,
|
|
171
|
-
ProjectCheckoutGarbageCollector,
|
|
172
|
-
discoverStagedProjectCheckouts,
|
|
173
|
-
isStagedProjectCheckoutPath,
|
|
174
|
-
ownedStagedPath,
|
|
175
|
-
removeStagedProjectCheckout,
|
|
176
|
-
stageProjectCheckoutForDeletion,
|
|
177
|
-
stageProjectForDeletion
|
|
178
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
function projectMirrorFetchDecision(input) {
|
|
2
|
-
if (!input.skippingEnabled) return { fetch: true, reason: "skipping_disabled" };
|
|
3
|
-
if (input.advertisedToken === void 0) return { fetch: true, reason: "no_advertised_token" };
|
|
4
|
-
if (input.fetchedToken === void 0) return { fetch: true, reason: "never_fetched" };
|
|
5
|
-
if (input.advertisedToken !== input.fetchedToken) return { fetch: true, reason: "token_changed" };
|
|
6
|
-
return { fetch: false, reason: "token_match" };
|
|
7
|
-
}
|
|
8
|
-
function planProjectMirrorObservationFetch(input) {
|
|
9
|
-
if (!input.skippingEnabled) return { scope: "project", reason: "skipping_disabled" };
|
|
10
|
-
if (input.advertisedMountTokens === void 0) {
|
|
11
|
-
const decision = projectMirrorFetchDecision({
|
|
12
|
-
advertisedToken: input.advertisedProjectToken,
|
|
13
|
-
fetchedToken: input.fetchedProjectToken,
|
|
14
|
-
skippingEnabled: true
|
|
15
|
-
});
|
|
16
|
-
return decision.fetch ? { scope: "project", reason: decision.reason } : { scope: "none" };
|
|
17
|
-
}
|
|
18
|
-
const mounts = [];
|
|
19
|
-
for (const branchName of [...input.branchNames].sort()) {
|
|
20
|
-
const decision = projectMirrorFetchDecision({
|
|
21
|
-
advertisedToken: input.advertisedMountTokens.get(branchName),
|
|
22
|
-
fetchedToken: input.fetchedMountTokens.get(branchName),
|
|
23
|
-
skippingEnabled: true
|
|
24
|
-
});
|
|
25
|
-
if (decision.fetch) mounts.push({ branchName, reason: decision.reason });
|
|
26
|
-
}
|
|
27
|
-
return mounts.length === 0 ? { scope: "none" } : { scope: "mounts", mounts };
|
|
28
|
-
}
|
|
29
|
-
const TOKEN_RE = /^[0-9a-f]{64}$/;
|
|
30
|
-
const GIT_OBJECT_NAME_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
31
|
-
function parseProjectMirrorRefsTokensMessage(message) {
|
|
32
|
-
const projectTokens = /* @__PURE__ */ new Map();
|
|
33
|
-
if (message.tokens && typeof message.tokens === "object") {
|
|
34
|
-
for (const [projectId, token] of Object.entries(message.tokens)) {
|
|
35
|
-
if (typeof token === "string" && TOKEN_RE.test(token)) projectTokens.set(projectId, token);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const mountTokens = /* @__PURE__ */ new Map();
|
|
39
|
-
if (message.mountTokens && typeof message.mountTokens === "object") {
|
|
40
|
-
for (const [projectId, tokensByBranch] of Object.entries(message.mountTokens)) {
|
|
41
|
-
if (!tokensByBranch || typeof tokensByBranch !== "object") continue;
|
|
42
|
-
const perMount = /* @__PURE__ */ new Map();
|
|
43
|
-
for (const [branchName, token] of Object.entries(tokensByBranch)) {
|
|
44
|
-
if (typeof token === "string" && TOKEN_RE.test(token)) perMount.set(branchName, token);
|
|
45
|
-
}
|
|
46
|
-
mountTokens.set(projectId, perMount);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const workspaceHead = typeof message.workspaceHead === "string" && GIT_OBJECT_NAME_RE.test(message.workspaceHead) ? message.workspaceHead : void 0;
|
|
50
|
-
return { projectTokens, mountTokens, workspaceHead };
|
|
51
|
-
}
|
|
52
|
-
export {
|
|
53
|
-
parseProjectMirrorRefsTokensMessage,
|
|
54
|
-
planProjectMirrorObservationFetch,
|
|
55
|
-
projectMirrorFetchDecision
|
|
56
|
-
};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
const PROJECT_MIRROR_REFS_TOKEN_NAMESPACES = ["refs/heads/", "refs/r5d/server-synced/"];
|
|
3
|
-
const PROJECT_MIRROR_REF_PREFIX = "refs/r5d/mirror/";
|
|
4
|
-
const PROJECT_SERVER_SYNCED_REF_PREFIX = "refs/r5d/server-synced/";
|
|
5
|
-
const PROJECT_MIRROR_REF_LISTING_FORMAT = "--format=%(objectname) %(refname)";
|
|
6
|
-
const GIT_OBJECT_NAME_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
7
|
-
function hashProjectMirrorRefs(refs) {
|
|
8
|
-
const lines = refs.map(({ objectName, refname }) => `${objectName} ${refname}`).sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
9
|
-
return createHash("sha256").update(lines.join("\n")).digest("hex");
|
|
10
|
-
}
|
|
11
|
-
function parseProjectMirrorRefListing(output, options = {}) {
|
|
12
|
-
return output.split(/\r?\n/).filter(Boolean).map((row) => {
|
|
13
|
-
const separator = row.indexOf(" ");
|
|
14
|
-
const objectName = separator < 0 ? "" : row.slice(0, separator);
|
|
15
|
-
const refname = separator < 0 ? "" : row.slice(separator + 1);
|
|
16
|
-
if (!GIT_OBJECT_NAME_RE.test(objectName) || refname.length === 0 || options.namespaces !== void 0 && !options.namespaces.some((prefix) => refname.startsWith(prefix))) {
|
|
17
|
-
throw new Error(`Invalid ref listing row: ${row}`);
|
|
18
|
-
}
|
|
19
|
-
return { refname, objectName };
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function canonicalizeLocalProjectMirrorRefs(refs) {
|
|
23
|
-
const canonical = [];
|
|
24
|
-
for (const { refname, objectName } of refs) {
|
|
25
|
-
if (refname.startsWith(PROJECT_MIRROR_REF_PREFIX)) {
|
|
26
|
-
canonical.push({ refname: `refs/heads/${refname.slice(PROJECT_MIRROR_REF_PREFIX.length)}`, objectName });
|
|
27
|
-
} else if (refname.startsWith(PROJECT_SERVER_SYNCED_REF_PREFIX)) {
|
|
28
|
-
canonical.push({ refname, objectName });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return canonical;
|
|
32
|
-
}
|
|
33
|
-
function projectMirrorMountRefs(refs, mount) {
|
|
34
|
-
const headRef = `refs/heads/${mount.branchName}`;
|
|
35
|
-
const handoffPrefix = `${PROJECT_SERVER_SYNCED_REF_PREFIX}${mount.branchId}/`;
|
|
36
|
-
return refs.filter(({ refname }) => refname === headRef || refname.startsWith(handoffPrefix));
|
|
37
|
-
}
|
|
38
|
-
function projectMirrorRefsTokens(refs, mounts) {
|
|
39
|
-
const mountTokens = /* @__PURE__ */ new Map();
|
|
40
|
-
for (const mount of mounts) mountTokens.set(mount.branchName, hashProjectMirrorRefs(projectMirrorMountRefs(refs, mount)));
|
|
41
|
-
return { project: hashProjectMirrorRefs(refs), mounts: mountTokens };
|
|
42
|
-
}
|
|
43
|
-
export {
|
|
44
|
-
PROJECT_MIRROR_REFS_TOKEN_NAMESPACES,
|
|
45
|
-
PROJECT_MIRROR_REF_LISTING_FORMAT,
|
|
46
|
-
PROJECT_MIRROR_REF_PREFIX,
|
|
47
|
-
PROJECT_SERVER_SYNCED_REF_PREFIX,
|
|
48
|
-
canonicalizeLocalProjectMirrorRefs,
|
|
49
|
-
hashProjectMirrorRefs,
|
|
50
|
-
parseProjectMirrorRefListing,
|
|
51
|
-
projectMirrorMountRefs,
|
|
52
|
-
projectMirrorRefsTokens
|
|
53
|
-
};
|