@ricsam/r5d-worker 0.0.46 → 0.0.48
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 +2 -0
- package/dist/cjs/main.cjs +258 -33
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/port-forward-client.cjs +148 -0
- package/dist/cjs/workspace-sync.cjs +619 -57
- package/dist/mjs/main.mjs +257 -34
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/port-forward-client.mjs +114 -0
- package/dist/mjs/workspace-sync.mjs +616 -56
- package/dist/types/main.d.ts +40 -3
- package/dist/types/port-forward-client.d.ts +8 -0
- package/dist/types/workspace-sync.d.ts +30 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,3 +37,5 @@ The agent runs commands through the virtual shell command:
|
|
|
37
37
|
worker exec macos docker compose up -d
|
|
38
38
|
worker exec macos bun test
|
|
39
39
|
```
|
|
40
|
+
|
|
41
|
+
When the connected `r5d-browser` requests a port forward, the worker opens each relayed connection only to `127.0.0.1` on the requested worker port. Browser-side and worker-side ports may differ. The worker never opens a public listener, and a disconnected worker leaves the browser's long-lived mapping unavailable until the same worker label reconnects.
|
package/dist/cjs/main.cjs
CHANGED
|
@@ -43,6 +43,9 @@ __export(main_exports, {
|
|
|
43
43
|
resolveHostShell: () => resolveHostShell,
|
|
44
44
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
45
45
|
syncSessionArtifacts: () => syncSessionArtifacts,
|
|
46
|
+
visibleCheckoutGitTestHarness: () => visibleCheckoutGitTestHarness,
|
|
47
|
+
visibleCheckoutRemoteFor: () => visibleCheckoutRemoteFor,
|
|
48
|
+
workspaceSyncCheckoutTargets: () => workspaceSyncCheckoutTargets,
|
|
46
49
|
writeWorkerTextFile: () => writeWorkerTextFile
|
|
47
50
|
});
|
|
48
51
|
module.exports = __toCommonJS(main_exports);
|
|
@@ -55,6 +58,7 @@ var import_cli_update = require("./cli-update.cjs");
|
|
|
55
58
|
var import_git_identity = require("./git-identity.cjs");
|
|
56
59
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
57
60
|
var import_process_tree = require("./process-tree.cjs");
|
|
61
|
+
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
58
62
|
var import_supervisor = require("./supervisor.cjs");
|
|
59
63
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
60
64
|
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
@@ -614,6 +618,14 @@ function validatePlanId(planId) {
|
|
|
614
618
|
throw new Error(`Invalid plan id from server: ${planId}`);
|
|
615
619
|
}
|
|
616
620
|
}
|
|
621
|
+
const NON_RECURSIVE_GIT_CONFIG_ARGS = [
|
|
622
|
+
"-c",
|
|
623
|
+
"submodule.recurse=false",
|
|
624
|
+
"-c",
|
|
625
|
+
"fetch.recurseSubmodules=false",
|
|
626
|
+
"-c",
|
|
627
|
+
"push.recurseSubmodules=false"
|
|
628
|
+
];
|
|
617
629
|
function gitExtraHeaderConfigKey(extraHeaderUrl) {
|
|
618
630
|
return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
|
|
619
631
|
}
|
|
@@ -621,10 +633,23 @@ function gitAuthArgs(auth) {
|
|
|
621
633
|
if (!auth) {
|
|
622
634
|
return [];
|
|
623
635
|
}
|
|
624
|
-
|
|
636
|
+
const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
|
|
637
|
+
return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
|
|
638
|
+
}
|
|
639
|
+
const visibleCheckoutGitTestHarness = {
|
|
640
|
+
gitAuthArgs,
|
|
641
|
+
commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args],
|
|
642
|
+
cloneArgs: (remoteUrl, branchPath) => ["clone", "--no-recurse-submodules", "--origin", "origin", remoteUrl, branchPath],
|
|
643
|
+
fetchArgs: (...args) => ["fetch", "--no-recurse-submodules", ...args]
|
|
644
|
+
};
|
|
645
|
+
function visibleCheckoutCloneArgs(remoteUrl, branchPath) {
|
|
646
|
+
return visibleCheckoutGitTestHarness.cloneArgs(remoteUrl, branchPath);
|
|
647
|
+
}
|
|
648
|
+
function visibleCheckoutFetchArgs(...args) {
|
|
649
|
+
return visibleCheckoutGitTestHarness.fetchArgs(...args);
|
|
625
650
|
}
|
|
626
651
|
function runGit(args, options = {}) {
|
|
627
|
-
const command =
|
|
652
|
+
const command = visibleCheckoutGitTestHarness.commandArgs(args, options.auth);
|
|
628
653
|
const result = Bun.spawnSync(command, {
|
|
629
654
|
cwd: options.cwd,
|
|
630
655
|
stdout: "pipe",
|
|
@@ -768,8 +793,28 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
|
768
793
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
769
794
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
770
795
|
}
|
|
771
|
-
function
|
|
772
|
-
|
|
796
|
+
function visibleCheckoutRemoteFor(input) {
|
|
797
|
+
const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
|
|
798
|
+
const isCanonicalManifestBranch = Boolean(canonicalCheckout);
|
|
799
|
+
const usesCanonicalRemote = isCanonicalManifestBranch || !input.manifest?.repoHttpUrl;
|
|
800
|
+
if (usesCanonicalRemote) {
|
|
801
|
+
return {
|
|
802
|
+
remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
803
|
+
authHeader: `Authorization: Bearer ${input.token}`,
|
|
804
|
+
persistAuth: false,
|
|
805
|
+
reconcileExistingOrigin: isCanonicalManifestBranch,
|
|
806
|
+
requireRemoteBranch: isCanonicalManifestBranch,
|
|
807
|
+
requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
remoteUrl: input.manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
|
|
812
|
+
authHeader: input.manifest?.repoAuthHeader ?? null,
|
|
813
|
+
persistAuth: true,
|
|
814
|
+
reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl),
|
|
815
|
+
requireRemoteBranch: false,
|
|
816
|
+
requiredBaseCommit: null
|
|
817
|
+
};
|
|
773
818
|
}
|
|
774
819
|
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
775
820
|
try {
|
|
@@ -798,6 +843,10 @@ function ensureOriginRemote(branchPath, remoteUrl) {
|
|
|
798
843
|
runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
|
|
799
844
|
}
|
|
800
845
|
}
|
|
846
|
+
function originRemoteUrl(branchPath) {
|
|
847
|
+
if (!tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) return null;
|
|
848
|
+
return runGit(["remote", "get-url", "origin"], { cwd: branchPath });
|
|
849
|
+
}
|
|
801
850
|
function shellQuote(value) {
|
|
802
851
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
803
852
|
}
|
|
@@ -866,6 +915,18 @@ function configureVisibleGitHubAuth(branchPath, remoteUrl, authHeader) {
|
|
|
866
915
|
}
|
|
867
916
|
runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
|
|
868
917
|
}
|
|
918
|
+
function clearVisibleGitAuthentication(branchPath) {
|
|
919
|
+
let extraHeaderKeys = [];
|
|
920
|
+
try {
|
|
921
|
+
extraHeaderKeys = runGit(["config", "--local", "--name-only", "--get-regexp", "^http\\..*extraheader$"], { cwd: branchPath }).split(/\r?\n/).filter(Boolean);
|
|
922
|
+
} catch {
|
|
923
|
+
}
|
|
924
|
+
for (const key of extraHeaderKeys) {
|
|
925
|
+
tryGit(["config", "--local", "--unset-all", key], { cwd: branchPath });
|
|
926
|
+
}
|
|
927
|
+
runGit(["config", "--local", "--replace-all", "credential.helper", ""], { cwd: branchPath });
|
|
928
|
+
tryGit(["config", "--local", "--unset-all", "credential.useHttpPath"], { cwd: branchPath });
|
|
929
|
+
}
|
|
869
930
|
function dockerConfigPath() {
|
|
870
931
|
return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
|
|
871
932
|
}
|
|
@@ -954,18 +1015,58 @@ function checkoutVisibleBranch(input) {
|
|
|
954
1015
|
});
|
|
955
1016
|
const clean = !hasWorktreeChanges(input.branchPath);
|
|
956
1017
|
if (remoteBranchExists && clean) {
|
|
957
|
-
runGit(["checkout", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1018
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1019
|
+
cwd: input.branchPath
|
|
1020
|
+
});
|
|
958
1021
|
return;
|
|
959
1022
|
}
|
|
960
1023
|
if (branchExists) {
|
|
961
|
-
runGit(["checkout", input.branchName], { cwd: input.branchPath });
|
|
1024
|
+
runGit(["checkout", "--no-recurse-submodules", input.branchName], { cwd: input.branchPath });
|
|
962
1025
|
return;
|
|
963
1026
|
}
|
|
964
1027
|
if (defaultRemoteExists) {
|
|
965
|
-
runGit(["checkout", "-B", input.branchName, `origin/${input.defaultBranch}`], {
|
|
1028
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.defaultBranch}`], {
|
|
1029
|
+
cwd: input.branchPath
|
|
1030
|
+
});
|
|
966
1031
|
return;
|
|
967
1032
|
}
|
|
968
|
-
runGit(["checkout", "-B", input.branchName], { cwd: input.branchPath });
|
|
1033
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName], { cwd: input.branchPath });
|
|
1034
|
+
}
|
|
1035
|
+
function migrateExistingCheckoutToRequiredRemote(input) {
|
|
1036
|
+
if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
|
|
1037
|
+
if (hasWorktreeChanges(input.branchPath)) {
|
|
1038
|
+
throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
|
|
1039
|
+
}
|
|
1040
|
+
runGit(
|
|
1041
|
+
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1042
|
+
{
|
|
1043
|
+
cwd: input.branchPath,
|
|
1044
|
+
auth: input.remoteAuth
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
const canonicalRemoteRef = `refs/remotes/origin/${input.branchName}`;
|
|
1048
|
+
if (!tryGit(["show-ref", "--verify", "--quiet", canonicalRemoteRef], { cwd: input.branchPath })) {
|
|
1049
|
+
throw new Error(`Canonical branch ${input.branchName} is unavailable during checkout migration`);
|
|
1050
|
+
}
|
|
1051
|
+
if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
|
|
1052
|
+
throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
|
|
1055
|
+
const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
|
|
1056
|
+
runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
|
|
1057
|
+
}
|
|
1058
|
+
ensureOriginRemote(input.branchPath, input.remoteUrl);
|
|
1059
|
+
runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
|
|
1060
|
+
cwd: input.branchPath
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
function assertCheckoutDerivedFromRequiredBase(input) {
|
|
1064
|
+
const remoteRevision = `refs/remotes/origin/${input.branchName}`;
|
|
1065
|
+
for (const revision of [remoteRevision, "HEAD"]) {
|
|
1066
|
+
if (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, revision], { cwd: input.branchPath })) {
|
|
1067
|
+
throw new Error(`Canonical checkout ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
969
1070
|
}
|
|
970
1071
|
function ensureVisibleGitCheckout(input) {
|
|
971
1072
|
validateBranchName(input.branchName);
|
|
@@ -975,40 +1076,114 @@ function ensureVisibleGitCheckout(input) {
|
|
|
975
1076
|
if (createdCheckout) {
|
|
976
1077
|
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
977
1078
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
|
|
978
|
-
|
|
1079
|
+
const cloned = tryGit(visibleCheckoutCloneArgs(input.remoteUrl, branchPath), {
|
|
1080
|
+
auth: input.remoteAuth
|
|
1081
|
+
});
|
|
1082
|
+
if (!cloned && input.requireRemoteBranch) {
|
|
1083
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1084
|
+
throw new Error(`Failed to clone canonical checkout for branch ${input.branchName}`);
|
|
1085
|
+
}
|
|
1086
|
+
if (!cloned) {
|
|
979
1087
|
import_node_fs.default.mkdirSync(branchPath, { recursive: true });
|
|
980
1088
|
runGit(["init"], { cwd: branchPath });
|
|
981
1089
|
}
|
|
982
|
-
ensureOriginRemote(branchPath, input.
|
|
983
|
-
|
|
1090
|
+
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
1091
|
+
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1092
|
+
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
984
1093
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
985
|
-
tryGit(
|
|
1094
|
+
tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
|
|
1095
|
+
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1096
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1097
|
+
throw new Error(`Canonical branch ${input.branchName} is unavailable after clone`);
|
|
1098
|
+
}
|
|
986
1099
|
checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
|
|
1100
|
+
if (input.requiredBaseCommit) {
|
|
1101
|
+
try {
|
|
1102
|
+
assertCheckoutDerivedFromRequiredBase({
|
|
1103
|
+
branchPath,
|
|
1104
|
+
branchName: input.branchName,
|
|
1105
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1106
|
+
});
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
|
|
1109
|
+
throw error;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
987
1112
|
return branchPath;
|
|
988
1113
|
}
|
|
1114
|
+
if (input.requireRemoteBranch) {
|
|
1115
|
+
if (input.allowRequiredRemoteMigration === false && originRemoteUrl(branchPath) !== input.remoteUrl) {
|
|
1116
|
+
throw new Error(`Canonical checkout ${input.branchName} no longer uses its required remote`);
|
|
1117
|
+
}
|
|
1118
|
+
migrateExistingCheckoutToRequiredRemote({
|
|
1119
|
+
branchPath,
|
|
1120
|
+
branchName: input.branchName,
|
|
1121
|
+
remoteUrl: input.remoteUrl,
|
|
1122
|
+
remoteAuth: input.remoteAuth,
|
|
1123
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1124
|
+
});
|
|
1125
|
+
runGit(
|
|
1126
|
+
visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
|
|
1127
|
+
{
|
|
1128
|
+
cwd: branchPath,
|
|
1129
|
+
auth: input.remoteAuth
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
989
1133
|
if (input.reconcileExistingOrigin) {
|
|
990
|
-
ensureOriginRemote(branchPath, input.
|
|
1134
|
+
ensureOriginRemote(branchPath, input.remoteUrl);
|
|
991
1135
|
}
|
|
992
|
-
|
|
1136
|
+
if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
|
|
1137
|
+
throw new Error(`Existing checkout for ${input.branchName} does not contain its canonical remote branch`);
|
|
1138
|
+
}
|
|
1139
|
+
if (input.requiredBaseCommit) {
|
|
1140
|
+
assertCheckoutDerivedFromRequiredBase({
|
|
1141
|
+
branchPath,
|
|
1142
|
+
branchName: input.branchName,
|
|
1143
|
+
requiredBaseCommit: input.requiredBaseCommit
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
|
|
1147
|
+
else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
|
|
993
1148
|
(0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
|
|
994
1149
|
return branchPath;
|
|
995
1150
|
}
|
|
996
1151
|
function ensureBranchWorkspace(input) {
|
|
997
1152
|
const defaultBranch = input.manifest?.defaultBranch || "main";
|
|
998
|
-
const
|
|
999
|
-
const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
|
|
1153
|
+
const remote = visibleCheckoutRemoteFor(input);
|
|
1000
1154
|
return {
|
|
1001
1155
|
branchPath: ensureVisibleGitCheckout({
|
|
1002
1156
|
projectRoot: input.projectRoot,
|
|
1003
1157
|
branchName: input.branchName,
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1158
|
+
remoteUrl: remote.remoteUrl,
|
|
1159
|
+
remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
|
|
1160
|
+
persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
|
|
1007
1161
|
defaultBranch,
|
|
1008
|
-
reconcileExistingOrigin:
|
|
1162
|
+
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
1163
|
+
requireRemoteBranch: remote.requireRemoteBranch,
|
|
1164
|
+
requiredBaseCommit: remote.requiredBaseCommit,
|
|
1165
|
+
clearPersistentAuth: !remote.persistAuth
|
|
1009
1166
|
})
|
|
1010
1167
|
};
|
|
1011
1168
|
}
|
|
1169
|
+
function workspaceSyncCheckoutTargets(input) {
|
|
1170
|
+
const selected = /* @__PURE__ */ new Map();
|
|
1171
|
+
const add = (target) => {
|
|
1172
|
+
selected.set(`${target.projectId}\0${target.branchName}`, target);
|
|
1173
|
+
};
|
|
1174
|
+
if (input.canonicalCheckoutOnly) {
|
|
1175
|
+
add(input.canonicalCheckoutOnly);
|
|
1176
|
+
} else if (input.includeAllManifestCheckouts) {
|
|
1177
|
+
for (const project of input.projects) {
|
|
1178
|
+
for (const branchName of project.branches) add({ projectId: project.projectId, branchName });
|
|
1179
|
+
}
|
|
1180
|
+
} else {
|
|
1181
|
+
for (const target of input.pendingTargets) add(target);
|
|
1182
|
+
}
|
|
1183
|
+
return [...selected.values()].sort(
|
|
1184
|
+
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1012
1187
|
function resolveCommandCwd(branchPath, cwd) {
|
|
1013
1188
|
if (!cwd) {
|
|
1014
1189
|
return branchPath;
|
|
@@ -2232,6 +2407,7 @@ async function startWorker(options) {
|
|
|
2232
2407
|
let reloadAfterClose = false;
|
|
2233
2408
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2234
2409
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2410
|
+
const projects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2235
2411
|
return {
|
|
2236
2412
|
workerLabel: label,
|
|
2237
2413
|
remoteUrl: workspaceRemoteUrl,
|
|
@@ -2239,7 +2415,7 @@ async function startWorker(options) {
|
|
|
2239
2415
|
projectsRoot,
|
|
2240
2416
|
plansRoot: planRoot,
|
|
2241
2417
|
shadowRoot: workspaceShadowRoot,
|
|
2242
|
-
projects
|
|
2418
|
+
projects,
|
|
2243
2419
|
trigger,
|
|
2244
2420
|
...overrides
|
|
2245
2421
|
};
|
|
@@ -2251,7 +2427,7 @@ async function startWorker(options) {
|
|
|
2251
2427
|
const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
|
|
2252
2428
|
(manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
|
|
2253
2429
|
);
|
|
2254
|
-
const ensureVisibleWorkspaceCheckouts = (targets) => {
|
|
2430
|
+
const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
|
|
2255
2431
|
const created = [];
|
|
2256
2432
|
for (const target of targets) {
|
|
2257
2433
|
const manifest = manifestByProjectId.get(target.projectId);
|
|
@@ -2263,15 +2439,25 @@ async function startWorker(options) {
|
|
|
2263
2439
|
const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
|
|
2264
2440
|
const branchPath = import_node_path.default.join(projectRoot, target.branchName);
|
|
2265
2441
|
const existed = hasNormalVisibleGitDir(branchPath);
|
|
2266
|
-
const
|
|
2442
|
+
const remote = visibleCheckoutRemoteFor({
|
|
2443
|
+
baseUrl,
|
|
2444
|
+
token,
|
|
2445
|
+
projectId: manifest.projectId,
|
|
2446
|
+
branchName: target.branchName,
|
|
2447
|
+
manifest
|
|
2448
|
+
});
|
|
2267
2449
|
ensureVisibleGitCheckout({
|
|
2268
2450
|
projectRoot,
|
|
2269
2451
|
branchName: target.branchName,
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2452
|
+
remoteUrl: remote.remoteUrl,
|
|
2453
|
+
remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
|
|
2454
|
+
persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
|
|
2273
2455
|
defaultBranch: manifest.defaultBranch || "main",
|
|
2274
|
-
reconcileExistingOrigin:
|
|
2456
|
+
reconcileExistingOrigin: remote.reconcileExistingOrigin,
|
|
2457
|
+
requireRemoteBranch: remote.requireRemoteBranch,
|
|
2458
|
+
requiredBaseCommit: remote.requiredBaseCommit,
|
|
2459
|
+
allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.requireRemoteBranch),
|
|
2460
|
+
clearPersistentAuth: !remote.persistAuth
|
|
2275
2461
|
});
|
|
2276
2462
|
if (!existed) created.push(target);
|
|
2277
2463
|
}
|
|
@@ -2283,15 +2469,30 @@ async function startWorker(options) {
|
|
|
2283
2469
|
let pendingTargets = [];
|
|
2284
2470
|
const result = await workspaceSyncSingleFlight.runPrepared(() => {
|
|
2285
2471
|
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2472
|
+
const syncProjects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2473
|
+
const allowedCheckoutKeys = new Set(
|
|
2474
|
+
syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
|
|
2475
|
+
);
|
|
2476
|
+
const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
|
|
2477
|
+
(target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2478
|
+
);
|
|
2286
2479
|
let checkoutTargets = [];
|
|
2287
2480
|
if (shouldEnsureCheckouts) {
|
|
2288
|
-
checkoutTargets =
|
|
2481
|
+
checkoutTargets = workspaceSyncCheckoutTargets({
|
|
2482
|
+
projects: syncProjects,
|
|
2483
|
+
pendingTargets: scopedPendingTargets,
|
|
2484
|
+
includeAllManifestCheckouts: trigger.type === "connect",
|
|
2485
|
+
...trigger.canonicalCheckoutOnly && trigger.projectId && trigger.branchName ? { canonicalCheckoutOnly: { projectId: trigger.projectId, branchName: trigger.branchName } } : {}
|
|
2486
|
+
});
|
|
2289
2487
|
}
|
|
2290
|
-
pendingTargets = shouldEnsureCheckouts ?
|
|
2291
|
-
const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets
|
|
2488
|
+
pendingTargets = shouldEnsureCheckouts ? scopedPendingTargets : [];
|
|
2489
|
+
const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets, {
|
|
2490
|
+
strictCanonicalRevalidation: true
|
|
2491
|
+
});
|
|
2292
2492
|
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
2293
2493
|
for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
|
|
2294
|
-
|
|
2494
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2495
|
+
if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
|
|
2295
2496
|
}
|
|
2296
2497
|
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2297
2498
|
return workspaceSyncInput(trigger, {
|
|
@@ -2367,7 +2568,9 @@ async function startWorker(options) {
|
|
|
2367
2568
|
version: getWorkerVersion(),
|
|
2368
2569
|
r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
|
|
2369
2570
|
capabilities: {
|
|
2370
|
-
updateClis: true
|
|
2571
|
+
updateClis: true,
|
|
2572
|
+
canonicalResolverCheckout: true,
|
|
2573
|
+
browserPortForwarding: true
|
|
2371
2574
|
},
|
|
2372
2575
|
projectRoot: projectsRoot,
|
|
2373
2576
|
artifactRoot,
|
|
@@ -2427,7 +2630,15 @@ async function startWorker(options) {
|
|
|
2427
2630
|
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2428
2631
|
periodicWorkspaceScanInFlight = true;
|
|
2429
2632
|
void (async () => {
|
|
2430
|
-
|
|
2633
|
+
const genericCheckoutKeys = new Set(
|
|
2634
|
+
(0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], { type: "periodic" }).flatMap(
|
|
2635
|
+
(project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName))
|
|
2636
|
+
)
|
|
2637
|
+
);
|
|
2638
|
+
const hasPendingGenericCheckout = [...pendingManifestCheckouts.values()].some(
|
|
2639
|
+
(target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2640
|
+
);
|
|
2641
|
+
if (hasPendingGenericCheckout) {
|
|
2431
2642
|
previousPeriodicFingerprint = null;
|
|
2432
2643
|
await runRequestedWorkspaceSync(crypto.randomUUID(), {
|
|
2433
2644
|
type: "periodic",
|
|
@@ -2512,6 +2723,17 @@ async function startWorker(options) {
|
|
|
2512
2723
|
pendingProcessTerminals.delete(message.runId);
|
|
2513
2724
|
return;
|
|
2514
2725
|
}
|
|
2726
|
+
if (message.type === "port_forward_connect") {
|
|
2727
|
+
(0, import_port_forward_client.openWorkerPortForwardRelay)({
|
|
2728
|
+
baseUrl,
|
|
2729
|
+
token,
|
|
2730
|
+
label,
|
|
2731
|
+
forwardId: message.forwardId,
|
|
2732
|
+
relayConnectionId: message.relayConnectionId,
|
|
2733
|
+
workerPort: message.workerPort
|
|
2734
|
+
});
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2515
2737
|
if (message.type === "update_clis") {
|
|
2516
2738
|
if (cliUpdateInProgress) {
|
|
2517
2739
|
sendWorkerMessage(ws, {
|
|
@@ -2845,5 +3067,8 @@ if (isCliEntrypoint()) {
|
|
|
2845
3067
|
resolveHostShell,
|
|
2846
3068
|
resolveWorkerFilePath,
|
|
2847
3069
|
syncSessionArtifacts,
|
|
3070
|
+
visibleCheckoutGitTestHarness,
|
|
3071
|
+
visibleCheckoutRemoteFor,
|
|
3072
|
+
workspaceSyncCheckoutTargets,
|
|
2848
3073
|
writeWorkerTextFile
|
|
2849
3074
|
});
|
package/dist/cjs/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var port_forward_client_exports = {};
|
|
30
|
+
__export(port_forward_client_exports, {
|
|
31
|
+
openWorkerPortForwardRelay: () => openWorkerPortForwardRelay
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(port_forward_client_exports);
|
|
34
|
+
var import_node_net = __toESM(require("node:net"), 1);
|
|
35
|
+
var import_ws = __toESM(require("ws"), 1);
|
|
36
|
+
const RELAY_FRAME_BYTES = 64 * 1024;
|
|
37
|
+
const RELAY_PAUSE_BYTES = 1024 * 1024;
|
|
38
|
+
const RELAY_RESUME_BYTES = 256 * 1024;
|
|
39
|
+
function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
|
|
40
|
+
const url = new URL("/worker/port-forward/ws", baseUrl);
|
|
41
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
42
|
+
url.searchParams.set("label", label);
|
|
43
|
+
url.searchParams.set("forward", forwardId);
|
|
44
|
+
url.searchParams.set("connection", relayConnectionId);
|
|
45
|
+
return url.toString();
|
|
46
|
+
}
|
|
47
|
+
function rawDataBuffer(data) {
|
|
48
|
+
if (Buffer.isBuffer(data)) return data;
|
|
49
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
50
|
+
return Buffer.from(data);
|
|
51
|
+
}
|
|
52
|
+
function openWorkerPortForwardRelay(options) {
|
|
53
|
+
const socket = import_node_net.default.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
|
|
54
|
+
socket.pause();
|
|
55
|
+
socket.setNoDelay(true);
|
|
56
|
+
const relay = new import_ws.default(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
|
|
57
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
58
|
+
perMessageDeflate: false
|
|
59
|
+
});
|
|
60
|
+
let relayReady = false;
|
|
61
|
+
let targetReady = false;
|
|
62
|
+
const pendingChunks = [];
|
|
63
|
+
let pendingBytes = 0;
|
|
64
|
+
let closing = false;
|
|
65
|
+
let resumeTimer;
|
|
66
|
+
const maybeResume = () => {
|
|
67
|
+
if (relayReady && targetReady && !resumeTimer) socket.resume();
|
|
68
|
+
};
|
|
69
|
+
const cleanup = () => {
|
|
70
|
+
if (closing) return;
|
|
71
|
+
closing = true;
|
|
72
|
+
if (resumeTimer) clearInterval(resumeTimer);
|
|
73
|
+
socket.destroy();
|
|
74
|
+
if (relay.readyState === import_ws.default.OPEN || relay.readyState === import_ws.default.CONNECTING) relay.close();
|
|
75
|
+
};
|
|
76
|
+
const sendError = (message) => {
|
|
77
|
+
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
|
|
78
|
+
cleanup();
|
|
79
|
+
};
|
|
80
|
+
const waitForRelayDrain = () => {
|
|
81
|
+
if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
|
|
82
|
+
socket.pause();
|
|
83
|
+
resumeTimer = setInterval(() => {
|
|
84
|
+
if (relay.readyState !== import_ws.default.OPEN) return cleanup();
|
|
85
|
+
if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
|
|
86
|
+
clearInterval(resumeTimer);
|
|
87
|
+
resumeTimer = void 0;
|
|
88
|
+
maybeResume();
|
|
89
|
+
}, 10);
|
|
90
|
+
};
|
|
91
|
+
const sendChunk = (chunk) => {
|
|
92
|
+
for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
|
|
93
|
+
relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
|
|
94
|
+
}
|
|
95
|
+
waitForRelayDrain();
|
|
96
|
+
};
|
|
97
|
+
socket.once("connect", () => {
|
|
98
|
+
targetReady = true;
|
|
99
|
+
maybeResume();
|
|
100
|
+
});
|
|
101
|
+
socket.on("data", (chunk) => {
|
|
102
|
+
if (!relayReady || relay.readyState !== import_ws.default.OPEN) {
|
|
103
|
+
pendingBytes += chunk.byteLength;
|
|
104
|
+
if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
|
|
105
|
+
pendingChunks.push(Buffer.from(chunk));
|
|
106
|
+
socket.pause();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
sendChunk(chunk);
|
|
110
|
+
});
|
|
111
|
+
socket.on("end", () => {
|
|
112
|
+
if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "end" }));
|
|
113
|
+
});
|
|
114
|
+
socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
|
|
115
|
+
socket.on("close", cleanup);
|
|
116
|
+
relay.on("message", (data, isBinary) => {
|
|
117
|
+
if (!isBinary) {
|
|
118
|
+
let control;
|
|
119
|
+
try {
|
|
120
|
+
control = JSON.parse(rawDataBuffer(data).toString("utf8"));
|
|
121
|
+
} catch {
|
|
122
|
+
return cleanup();
|
|
123
|
+
}
|
|
124
|
+
if (control.type === "ready") {
|
|
125
|
+
relayReady = true;
|
|
126
|
+
for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
|
|
127
|
+
pendingBytes = 0;
|
|
128
|
+
maybeResume();
|
|
129
|
+
} else if (control.type === "end") {
|
|
130
|
+
socket.end();
|
|
131
|
+
} else if (control.type === "error") {
|
|
132
|
+
socket.destroy(new Error(control.error || "Port-forward relay failed."));
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!socket.write(rawDataBuffer(data))) {
|
|
137
|
+
relay.pause();
|
|
138
|
+
socket.once("drain", () => relay.resume());
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
relay.once("error", cleanup);
|
|
142
|
+
relay.once("close", cleanup);
|
|
143
|
+
socket.pause();
|
|
144
|
+
}
|
|
145
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
146
|
+
0 && (module.exports = {
|
|
147
|
+
openWorkerPortForwardRelay
|
|
148
|
+
});
|