@ricsam/r5d-worker 0.0.137 → 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 -7543
- 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 +3 -1
- package/dist/types/runtime/executor.d.ts +6 -1
- package/dist/types/runtime/protocol.d.ts +176 -4
- package/dist/types/runtime/pty.d.ts +1 -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/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 -7517
- 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 -3595
- package/dist/cjs/workspace-hydration-ledger.cjs +0 -83
- package/dist/cjs/workspace-hydration-merge.cjs +0 -448
- 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 -628
- 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 -3553
- package/dist/mjs/workspace-hydration-ledger.mjs +0 -58
- package/dist/mjs/workspace-hydration-merge.mjs +0 -414
- 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 -594
- 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-stall-fixture.d.ts +0 -1
- 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 -302
- package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
- package/dist/types/workspace-git-sync.d.ts +0 -514
- package/dist/types/workspace-hydration-ledger.d.ts +0 -76
- package/dist/types/workspace-hydration-merge.d.ts +0 -103
- 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 -69
- 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/releases/main.d.ts} +0 -0
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const WORKER_INLINE_OUTPUT_MAX_BYTES = 8 * 1024 * 1024;
|
|
2
|
-
async function collectWorkerInlineOutput(stream, maxBytes = WORKER_INLINE_OUTPUT_MAX_BYTES, signal) {
|
|
3
|
-
if (!stream) return "";
|
|
4
|
-
signal?.throwIfAborted();
|
|
5
|
-
const reader = stream.getReader();
|
|
6
|
-
const abort = () => {
|
|
7
|
-
void reader.cancel().catch(() => {
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
11
|
-
const decoder = new TextDecoder();
|
|
12
|
-
const parts = [];
|
|
13
|
-
let bytes = 0;
|
|
14
|
-
try {
|
|
15
|
-
while (true) {
|
|
16
|
-
const next = await reader.read();
|
|
17
|
-
signal?.throwIfAborted();
|
|
18
|
-
if (next.done) break;
|
|
19
|
-
bytes += next.value.byteLength;
|
|
20
|
-
if (bytes > maxBytes) {
|
|
21
|
-
void reader.cancel().catch(() => {
|
|
22
|
-
});
|
|
23
|
-
throw new Error(`Inline command output exceeded ${maxBytes} bytes. Use streaming execution for large output.`);
|
|
24
|
-
}
|
|
25
|
-
parts.push(decoder.decode(next.value, { stream: true }));
|
|
26
|
-
}
|
|
27
|
-
parts.push(decoder.decode());
|
|
28
|
-
return parts.join("");
|
|
29
|
-
} finally {
|
|
30
|
-
signal?.removeEventListener("abort", abort);
|
|
31
|
-
reader.releaseLock();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export {
|
|
35
|
-
WORKER_INLINE_OUTPUT_MAX_BYTES,
|
|
36
|
-
collectWorkerInlineOutput
|
|
37
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
const BRANCH_NAME_MAX_LENGTH = 45;
|
|
4
|
-
const BRANCH_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/;
|
|
5
|
-
const PROJECT_SEGMENT_PATTERN = /^[a-z0-9._-]+$/;
|
|
6
|
-
const WINDOWS_RESERVED_PATH_SEGMENT = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
7
|
-
const INTERNAL_PROJECT_PATH_SEGMENTS = /* @__PURE__ */ new Set([".r5d-retired-checkouts"]);
|
|
8
|
-
function lstatIfExists(targetPath) {
|
|
9
|
-
try {
|
|
10
|
-
return fs.lstatSync(targetPath);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
if (error.code === "ENOENT") return null;
|
|
13
|
-
throw error;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function fsyncDirectory(directoryPath) {
|
|
17
|
-
const descriptor = fs.openSync(directoryPath, "r");
|
|
18
|
-
try {
|
|
19
|
-
fs.fsyncSync(descriptor);
|
|
20
|
-
} finally {
|
|
21
|
-
fs.closeSync(descriptor);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function fsyncCreatedDirectoryChain(directoryPath, preexistingAncestorPath) {
|
|
25
|
-
let current = path.resolve(directoryPath);
|
|
26
|
-
const ancestor = path.resolve(preexistingAncestorPath);
|
|
27
|
-
const relative = path.relative(ancestor, current);
|
|
28
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
29
|
-
throw new Error(`Managed root escaped its durability ancestor: ${current}`);
|
|
30
|
-
}
|
|
31
|
-
while (true) {
|
|
32
|
-
fsyncDirectory(current);
|
|
33
|
-
if (current === ancestor) return;
|
|
34
|
-
current = path.dirname(current);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function canonicalizeManagedRoot(root) {
|
|
38
|
-
const resolvedRoot = path.resolve(root.rootPath);
|
|
39
|
-
let preexistingAncestor = resolvedRoot;
|
|
40
|
-
while (!lstatIfExists(preexistingAncestor)) {
|
|
41
|
-
const parent = path.dirname(preexistingAncestor);
|
|
42
|
-
if (parent === preexistingAncestor) {
|
|
43
|
-
throw new Error(`Could not find an existing ancestor for ${root.label} (${resolvedRoot})`);
|
|
44
|
-
}
|
|
45
|
-
preexistingAncestor = parent;
|
|
46
|
-
}
|
|
47
|
-
let canonicalPreexistingAncestor;
|
|
48
|
-
try {
|
|
49
|
-
canonicalPreexistingAncestor = fs.realpathSync(preexistingAncestor);
|
|
50
|
-
if (!fs.statSync(canonicalPreexistingAncestor).isDirectory()) {
|
|
51
|
-
throw new Error("the nearest existing ancestor is not a directory");
|
|
52
|
-
}
|
|
53
|
-
} catch (error) {
|
|
54
|
-
throw new Error(`Could not validate the existing ancestor of ${root.label} (${resolvedRoot})`, { cause: error });
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
fs.mkdirSync(resolvedRoot, { recursive: true });
|
|
58
|
-
} catch (error) {
|
|
59
|
-
throw new Error(`Could not prepare ${root.label} (${resolvedRoot}) for overlap validation`, { cause: error });
|
|
60
|
-
}
|
|
61
|
-
const rootStats = fs.statSync(resolvedRoot, { bigint: true });
|
|
62
|
-
if (!rootStats.isDirectory()) {
|
|
63
|
-
throw new Error(`Managed root is not a directory: ${root.label} (${resolvedRoot})`);
|
|
64
|
-
}
|
|
65
|
-
const canonicalRoot = fs.realpathSync(resolvedRoot);
|
|
66
|
-
fsyncCreatedDirectoryChain(canonicalRoot, canonicalPreexistingAncestor);
|
|
67
|
-
return {
|
|
68
|
-
...root,
|
|
69
|
-
rootPath: canonicalRoot,
|
|
70
|
-
identity: { device: rootStats.dev, inode: rootStats.ino }
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function pathContains(root, candidate) {
|
|
74
|
-
const relative = path.relative(root, candidate);
|
|
75
|
-
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
76
|
-
}
|
|
77
|
-
function identitiesMatch(left, right) {
|
|
78
|
-
return left.device === right.device && left.inode === right.inode;
|
|
79
|
-
}
|
|
80
|
-
function pathOrFilesystemContains(root, candidate) {
|
|
81
|
-
if (pathContains(root.rootPath, candidate.rootPath) || identitiesMatch(root.identity, candidate.identity)) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
let ancestor = path.dirname(candidate.rootPath);
|
|
85
|
-
while (true) {
|
|
86
|
-
const stats = fs.statSync(ancestor, { bigint: true });
|
|
87
|
-
if (identitiesMatch(root.identity, { device: stats.dev, inode: stats.ino })) return true;
|
|
88
|
-
const parent = path.dirname(ancestor);
|
|
89
|
-
if (parent === ancestor) return false;
|
|
90
|
-
ancestor = parent;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function assertDisjointManagedRoots(roots) {
|
|
94
|
-
const canonical = roots.map(canonicalizeManagedRoot);
|
|
95
|
-
for (let leftIndex = 0; leftIndex < canonical.length; leftIndex += 1) {
|
|
96
|
-
const left = canonical[leftIndex];
|
|
97
|
-
for (let rightIndex = leftIndex + 1; rightIndex < canonical.length; rightIndex += 1) {
|
|
98
|
-
const right = canonical[rightIndex];
|
|
99
|
-
if (pathOrFilesystemContains(left, right) || pathOrFilesystemContains(right, left)) {
|
|
100
|
-
throw new Error(`Managed roots overlap: ${left.label} (${left.rootPath}) and ${right.label} (${right.rootPath}) must be disjoint`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function validateManagedBranchName(branchName) {
|
|
106
|
-
if (typeof branchName !== "string" || branchName.length === 0) {
|
|
107
|
-
throw new Error("Branch name is required");
|
|
108
|
-
}
|
|
109
|
-
if (branchName.length > BRANCH_NAME_MAX_LENGTH) {
|
|
110
|
-
throw new Error(`Branch name must be ${BRANCH_NAME_MAX_LENGTH} characters or fewer`);
|
|
111
|
-
}
|
|
112
|
-
if (!BRANCH_NAME_PATTERN.test(branchName)) {
|
|
113
|
-
throw new Error(`Invalid branch name from server: ${branchName}`);
|
|
114
|
-
}
|
|
115
|
-
const reserved = branchName.split("/").find((segment) => WINDOWS_RESERVED_PATH_SEGMENT.test(segment));
|
|
116
|
-
if (reserved) {
|
|
117
|
-
throw new Error(`Invalid branch name from server: reserved path segment '${reserved}'`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function validateCheckoutPathSegments(value) {
|
|
121
|
-
if (!Array.isArray(value) || value.length !== 2) {
|
|
122
|
-
throw new Error("Invalid checkout path from server: expected namespace and project segments");
|
|
123
|
-
}
|
|
124
|
-
const segments = value;
|
|
125
|
-
for (const segment of segments) {
|
|
126
|
-
if (typeof segment !== "string" || !PROJECT_SEGMENT_PATTERN.test(segment) || segment !== segment.toLowerCase() || segment === "." || segment === ".." || segment === ".git" || INTERNAL_PROJECT_PATH_SEGMENTS.has(segment) || segment.endsWith(".") || WINDOWS_RESERVED_PATH_SEGMENT.test(segment)) {
|
|
127
|
-
throw new Error(`Invalid checkout path segment from server: ${String(segment)}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return [segments[0], segments[1]];
|
|
131
|
-
}
|
|
132
|
-
function assertPathInside(root, candidate, label) {
|
|
133
|
-
const resolvedRoot = path.resolve(root);
|
|
134
|
-
const resolvedCandidate = path.resolve(candidate);
|
|
135
|
-
const relative = path.relative(resolvedRoot, resolvedCandidate);
|
|
136
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
137
|
-
throw new Error(`${label} escapes its managed root: ${candidate}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function assertNoSymlinkComponents(root, candidate) {
|
|
141
|
-
assertPathInside(root, candidate, "Checkout path");
|
|
142
|
-
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
143
|
-
let current = path.resolve(root);
|
|
144
|
-
for (const component of relative.split(path.sep).filter(Boolean)) {
|
|
145
|
-
current = path.join(current, component);
|
|
146
|
-
if (!fs.existsSync(current)) break;
|
|
147
|
-
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
148
|
-
throw new Error(`Checkout path contains a symbolic link: ${current}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function managedProjectRoot(projectsRoot, checkoutPathSegments) {
|
|
153
|
-
const segments = validateCheckoutPathSegments(checkoutPathSegments);
|
|
154
|
-
const result = path.join(projectsRoot, ...segments);
|
|
155
|
-
assertNoSymlinkComponents(projectsRoot, result);
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
function managedBranchPath(projectsRoot, checkoutPathSegments, branchName) {
|
|
159
|
-
validateManagedBranchName(branchName);
|
|
160
|
-
const projectRoot = managedProjectRoot(projectsRoot, checkoutPathSegments);
|
|
161
|
-
const result = path.join(projectRoot, ...branchName.split("/"));
|
|
162
|
-
assertNoSymlinkComponents(projectRoot, result);
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
export {
|
|
166
|
-
BRANCH_NAME_MAX_LENGTH,
|
|
167
|
-
BRANCH_NAME_PATTERN,
|
|
168
|
-
assertDisjointManagedRoots,
|
|
169
|
-
assertPathInside,
|
|
170
|
-
managedBranchPath,
|
|
171
|
-
managedProjectRoot,
|
|
172
|
-
validateCheckoutPathSegments,
|
|
173
|
-
validateManagedBranchName
|
|
174
|
-
};
|
package/dist/mjs/plan-lint.mjs
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import picomatch from "picomatch";
|
|
4
|
-
import { getTasksSectionRange, PLAN_TASK_LINE_REGEX } from "./plan-parser.mjs";
|
|
5
|
-
import { PLAN_EXTENSION } from "./plan-paths.mjs";
|
|
6
|
-
import { PLAN_TASK_ID_REGEX } from "./plan-parser.mjs";
|
|
7
|
-
const LEGACY_SCHEMA_FIELD_REGEX = /^\s*(type|status|parent|phase|discovered_problem)\s*:/m;
|
|
8
|
-
function lintPlanContent(planId, content) {
|
|
9
|
-
const errors = [];
|
|
10
|
-
const lines = content.split(/\r?\n/);
|
|
11
|
-
if (/^---(?:\r?\n|$)/.test(content)) {
|
|
12
|
-
errors.push({
|
|
13
|
-
planId,
|
|
14
|
-
severity: "error",
|
|
15
|
-
message: "YAML frontmatter is not supported. Use plain Markdown with a ## Tasks checklist.",
|
|
16
|
-
line: 1
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
if (LEGACY_SCHEMA_FIELD_REGEX.test(content)) {
|
|
20
|
-
errors.push({
|
|
21
|
-
planId,
|
|
22
|
-
severity: "error",
|
|
23
|
-
message: "Legacy plan schema fields are not supported. Remove type/status/parent/phase/discovered_problem metadata."
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
const titleLineIndex = lines.findIndex((line) => /^# .+\S\s*$/.test(line));
|
|
27
|
-
if (titleLineIndex === -1) {
|
|
28
|
-
errors.push({
|
|
29
|
-
planId,
|
|
30
|
-
severity: "error",
|
|
31
|
-
message: "Plan must start with or contain an H1 title, for example '# Implementation Plan'."
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
const range = getTasksSectionRange(content);
|
|
35
|
-
if (!range) {
|
|
36
|
-
errors.push({
|
|
37
|
-
planId,
|
|
38
|
-
severity: "error",
|
|
39
|
-
message: "Plan is missing a ## Tasks section."
|
|
40
|
-
});
|
|
41
|
-
return errors;
|
|
42
|
-
}
|
|
43
|
-
const taskIds = /* @__PURE__ */ new Set();
|
|
44
|
-
let taskCount = 0;
|
|
45
|
-
for (let index = range.startIndex + 1; index < range.endIndex; index += 1) {
|
|
46
|
-
const line = lines[index] ?? "";
|
|
47
|
-
if (line.trim() === "") continue;
|
|
48
|
-
const match = line.match(PLAN_TASK_LINE_REGEX);
|
|
49
|
-
if (!match || !match[3].trim()) {
|
|
50
|
-
errors.push({
|
|
51
|
-
planId,
|
|
52
|
-
severity: "error",
|
|
53
|
-
line: index + 1,
|
|
54
|
-
message: 'Malformed task line. Use "- [ ] task-id: Description" or "- [x] task-id: Description".'
|
|
55
|
-
});
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
taskCount += 1;
|
|
59
|
-
const taskId = match[2];
|
|
60
|
-
if (taskIds.has(taskId)) {
|
|
61
|
-
errors.push({
|
|
62
|
-
planId,
|
|
63
|
-
severity: "error",
|
|
64
|
-
line: index + 1,
|
|
65
|
-
message: `Duplicate task id "${taskId}".`
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
taskIds.add(taskId);
|
|
69
|
-
}
|
|
70
|
-
if (taskCount === 0) {
|
|
71
|
-
errors.push({
|
|
72
|
-
planId,
|
|
73
|
-
severity: "error",
|
|
74
|
-
message: "Plan must include at least one task in the ## Tasks section.",
|
|
75
|
-
line: range.startIndex + 1
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
return errors;
|
|
79
|
-
}
|
|
80
|
-
async function lintPlans(plansDir, glob) {
|
|
81
|
-
const errors = [];
|
|
82
|
-
let entries;
|
|
83
|
-
try {
|
|
84
|
-
entries = await fs.readdir(plansDir, { withFileTypes: true });
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (error.code !== "ENOENT") throw error;
|
|
87
|
-
entries = [];
|
|
88
|
-
}
|
|
89
|
-
const matches = glob ? picomatch(glob) : null;
|
|
90
|
-
let lintedPlanCount = 0;
|
|
91
|
-
for (const entry of entries) {
|
|
92
|
-
if (!entry.isFile() || !entry.name.endsWith(PLAN_EXTENSION)) continue;
|
|
93
|
-
const planId = entry.name.slice(0, -PLAN_EXTENSION.length);
|
|
94
|
-
if (!PLAN_TASK_ID_REGEX.test(planId) || matches && !matches(entry.name) && !matches(planId)) continue;
|
|
95
|
-
const content = await fs.readFile(path.join(plansDir, entry.name), "utf8");
|
|
96
|
-
lintedPlanCount += 1;
|
|
97
|
-
errors.push(...lintPlanContent(planId, content));
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
type: "plan_lint",
|
|
101
|
-
errors,
|
|
102
|
-
errorCount: errors.filter((error) => error.severity === "error").length,
|
|
103
|
-
warningCount: errors.filter((error) => error.severity === "warning").length,
|
|
104
|
-
lintedPlanCount
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function formatPlanLintErrors(errors) {
|
|
108
|
-
if (errors.length === 0) return "";
|
|
109
|
-
const lines = ["## Plan Lint Errors", ""];
|
|
110
|
-
const byPlan = /* @__PURE__ */ new Map();
|
|
111
|
-
for (const error of errors) {
|
|
112
|
-
byPlan.set(error.planId, [...byPlan.get(error.planId) ?? [], error]);
|
|
113
|
-
}
|
|
114
|
-
for (const [planId, planErrors] of byPlan) {
|
|
115
|
-
lines.push(`### ${planId}.plan.md`);
|
|
116
|
-
for (const error of planErrors) {
|
|
117
|
-
const location = error.line ? ` line ${error.line}` : "";
|
|
118
|
-
lines.push(`- ${error.severity.toUpperCase()}${location}: ${error.message}`);
|
|
119
|
-
}
|
|
120
|
-
lines.push("");
|
|
121
|
-
}
|
|
122
|
-
lines.push("Fix these issues by editing the Markdown plan file.");
|
|
123
|
-
return lines.join("\n");
|
|
124
|
-
}
|
|
125
|
-
export {
|
|
126
|
-
formatPlanLintErrors,
|
|
127
|
-
lintPlanContent,
|
|
128
|
-
lintPlans
|
|
129
|
-
};
|
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
|
-
};
|