@ricsam/r5d-worker 0.0.76 → 0.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/git-process-environment.cjs +133 -0
- package/dist/cjs/main.cjs +279 -100
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +72 -47
- package/dist/cjs/working-tree-mirror.cjs +2 -1
- package/dist/cjs/workspace-git-sync.cjs +67 -53
- package/dist/mjs/git-process-environment.mjs +105 -0
- package/dist/mjs/main.mjs +278 -100
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +72 -47
- package/dist/mjs/working-tree-mirror.mjs +2 -1
- package/dist/mjs/workspace-git-sync.mjs +67 -53
- package/dist/types/git-process-environment.d.ts +9 -0
- package/dist/types/main.d.ts +24 -0
- package/dist/types/project-worktrees.d.ts +22 -10
- package/dist/types/workspace-git-sync.d.ts +18 -5
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -48,6 +48,7 @@ __export(main_exports, {
|
|
|
48
48
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
49
49
|
resolveWorkerSessionTarget: () => resolveWorkerSessionTarget,
|
|
50
50
|
syncSessionArtifacts: () => syncSessionArtifacts,
|
|
51
|
+
workerGitSecurityTestHarness: () => workerGitSecurityTestHarness,
|
|
51
52
|
writeWorkerTextFile: () => writeWorkerTextFile
|
|
52
53
|
});
|
|
53
54
|
module.exports = __toCommonJS(main_exports);
|
|
@@ -57,6 +58,7 @@ var import_node_crypto = require("node:crypto");
|
|
|
57
58
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
58
59
|
var import_node_child_process = require("node:child_process");
|
|
59
60
|
var import_cli_update = require("./cli-update.cjs");
|
|
61
|
+
var import_git_process_environment = require("./git-process-environment.cjs");
|
|
60
62
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
61
63
|
var import_process_tree = require("./process-tree.cjs");
|
|
62
64
|
var import_pty_output_coalescer = require("./pty-output-coalescer.cjs");
|
|
@@ -659,29 +661,16 @@ const NON_RECURSIVE_GIT_CONFIG_ARGS = [
|
|
|
659
661
|
"-c",
|
|
660
662
|
"push.recurseSubmodules=false"
|
|
661
663
|
];
|
|
662
|
-
function gitExtraHeaderConfigKey(extraHeaderUrl) {
|
|
663
|
-
return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
|
|
664
|
-
}
|
|
665
|
-
function gitAuthArgs(auth) {
|
|
666
|
-
if (!auth) {
|
|
667
|
-
return [];
|
|
668
|
-
}
|
|
669
|
-
const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
|
|
670
|
-
return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
|
|
671
|
-
}
|
|
672
664
|
const workerGitCommand = {
|
|
673
|
-
commandArgs: (args
|
|
665
|
+
commandArgs: (args) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...args]
|
|
674
666
|
};
|
|
675
667
|
function runGit(args, options = {}) {
|
|
676
|
-
const command = workerGitCommand.commandArgs(args
|
|
668
|
+
const command = workerGitCommand.commandArgs(args);
|
|
677
669
|
const result = Bun.spawnSync(command, {
|
|
678
670
|
cwd: options.cwd,
|
|
679
671
|
stdout: "pipe",
|
|
680
672
|
stderr: "pipe",
|
|
681
|
-
env:
|
|
682
|
-
...process.env,
|
|
683
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
684
|
-
}
|
|
673
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
685
674
|
});
|
|
686
675
|
if (result.exitCode !== 0) {
|
|
687
676
|
throw new Error(
|
|
@@ -814,28 +803,92 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
|
814
803
|
function canonicalProjectRemoteUrl(baseUrl, projectId) {
|
|
815
804
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
816
805
|
}
|
|
817
|
-
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
818
|
-
try {
|
|
819
|
-
return new URL(remoteUrl).origin;
|
|
820
|
-
} catch {
|
|
821
|
-
return remoteUrl;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
function gitAuthForRemote(remoteUrl, authHeader) {
|
|
825
|
-
if (!authHeader) {
|
|
826
|
-
return void 0;
|
|
827
|
-
}
|
|
828
|
-
return {
|
|
829
|
-
extraHeaderUrl: gitExtraHeaderUrlForRemote(remoteUrl),
|
|
830
|
-
header: authHeader
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
806
|
function shellQuote(value) {
|
|
834
807
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
835
808
|
}
|
|
836
|
-
function
|
|
809
|
+
function readOnlyCredentialStoreHelper(storePath) {
|
|
810
|
+
return `!f() { if [ "$1" = get ]; then git credential-store --file=${shellQuote(storePath)} get; else return 0; fi; }; f`;
|
|
811
|
+
}
|
|
812
|
+
function credentialStoreDirectory() {
|
|
813
|
+
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "git-credentials");
|
|
814
|
+
}
|
|
815
|
+
function legacySharedCredentialStorePath() {
|
|
837
816
|
return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "github-credentials");
|
|
838
817
|
}
|
|
818
|
+
function credentialStorePathForProject(projectId) {
|
|
819
|
+
validateProjectId(projectId);
|
|
820
|
+
const digest = (0, import_node_crypto.createHash)("sha256").update(`project\0${projectId}`).digest("hex");
|
|
821
|
+
return import_node_path.default.join(credentialStoreDirectory(), `project-${digest}.store`);
|
|
822
|
+
}
|
|
823
|
+
function credentialStorePathForWorkspace(remoteUrl) {
|
|
824
|
+
const digest = (0, import_node_crypto.createHash)("sha256").update(`workspace\0${remoteUrl}`).digest("hex");
|
|
825
|
+
return import_node_path.default.join(credentialStoreDirectory(), `workspace-${digest}.store`);
|
|
826
|
+
}
|
|
827
|
+
function lstatIfExists(filePath) {
|
|
828
|
+
try {
|
|
829
|
+
return import_node_fs.default.lstatSync(filePath);
|
|
830
|
+
} catch (error) {
|
|
831
|
+
if (error.code === "ENOENT") return null;
|
|
832
|
+
throw error;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function ensurePrivateCredentialStoreDirectory(directoryPath) {
|
|
836
|
+
const stat = lstatIfExists(directoryPath);
|
|
837
|
+
if (stat) {
|
|
838
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Git credential-store path must be a private directory");
|
|
839
|
+
} else {
|
|
840
|
+
import_node_fs.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
841
|
+
}
|
|
842
|
+
import_node_fs.default.chmodSync(directoryPath, 448);
|
|
843
|
+
}
|
|
844
|
+
function removeLegacySharedCredentialStore(storePath = legacySharedCredentialStorePath()) {
|
|
845
|
+
import_node_fs.default.rmSync(storePath, { force: true });
|
|
846
|
+
}
|
|
847
|
+
let credentialStoreDirectoryInitialized = false;
|
|
848
|
+
function resetCredentialStoreDirectory(directoryPath = credentialStoreDirectory()) {
|
|
849
|
+
const stat = lstatIfExists(directoryPath);
|
|
850
|
+
if (stat) {
|
|
851
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) import_node_fs.default.unlinkSync(directoryPath);
|
|
852
|
+
else import_node_fs.default.rmSync(directoryPath, { recursive: true, force: true });
|
|
853
|
+
}
|
|
854
|
+
ensurePrivateCredentialStoreDirectory(directoryPath);
|
|
855
|
+
}
|
|
856
|
+
function initializeCredentialStoreDirectoryOnce() {
|
|
857
|
+
if (credentialStoreDirectoryInitialized) return;
|
|
858
|
+
removeLegacySharedCredentialStore();
|
|
859
|
+
resetCredentialStoreDirectory();
|
|
860
|
+
credentialStoreDirectoryInitialized = true;
|
|
861
|
+
}
|
|
862
|
+
const APP_CREDENTIAL_STORE_FILE = /^(?:project|workspace)-[0-9a-f]{64}\.store$/;
|
|
863
|
+
function pruneCredentialStoreFiles(requiredStorePaths, directoryPath = credentialStoreDirectory()) {
|
|
864
|
+
ensurePrivateCredentialStoreDirectory(directoryPath);
|
|
865
|
+
const resolvedDirectory = import_node_path.default.resolve(directoryPath);
|
|
866
|
+
const requiredNames = /* @__PURE__ */ new Set();
|
|
867
|
+
for (const storePath of requiredStorePaths) {
|
|
868
|
+
const resolvedStorePath = import_node_path.default.resolve(storePath);
|
|
869
|
+
if (import_node_path.default.dirname(resolvedStorePath) !== resolvedDirectory || !APP_CREDENTIAL_STORE_FILE.test(import_node_path.default.basename(resolvedStorePath))) {
|
|
870
|
+
throw new Error("Invalid app-owned Git credential-store path");
|
|
871
|
+
}
|
|
872
|
+
requiredNames.add(import_node_path.default.basename(resolvedStorePath));
|
|
873
|
+
}
|
|
874
|
+
const removed = [];
|
|
875
|
+
for (const name of import_node_fs.default.readdirSync(directoryPath)) {
|
|
876
|
+
if (!APP_CREDENTIAL_STORE_FILE.test(name) || requiredNames.has(name)) continue;
|
|
877
|
+
const storePath = import_node_path.default.join(directoryPath, name);
|
|
878
|
+
const stat = import_node_fs.default.lstatSync(storePath);
|
|
879
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) continue;
|
|
880
|
+
import_node_fs.default.unlinkSync(storePath);
|
|
881
|
+
removed.push(storePath);
|
|
882
|
+
}
|
|
883
|
+
return removed;
|
|
884
|
+
}
|
|
885
|
+
function removeProjectCredentialStore(projectId) {
|
|
886
|
+
const storePath = credentialStorePathForProject(projectId);
|
|
887
|
+
const stat = lstatIfExists(storePath);
|
|
888
|
+
if (!stat) return;
|
|
889
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) throw new Error("Git credential store must be a regular file");
|
|
890
|
+
import_node_fs.default.unlinkSync(storePath);
|
|
891
|
+
}
|
|
839
892
|
function decodeGitAuthHeader(authHeader) {
|
|
840
893
|
const match = /^Authorization:\s*Basic\s+(.+)$/i.exec(authHeader.trim());
|
|
841
894
|
if (match) {
|
|
@@ -847,45 +900,84 @@ function decodeGitAuthHeader(authHeader) {
|
|
|
847
900
|
const bearer = /^Authorization:\s*Bearer\s+(.+)$/i.exec(authHeader.trim());
|
|
848
901
|
return bearer?.[1] ? { username: "r5d-worker", password: bearer[1] } : null;
|
|
849
902
|
}
|
|
850
|
-
function
|
|
903
|
+
function credentialUsernameForAuthHeader(authHeader) {
|
|
851
904
|
const credentials = decodeGitAuthHeader(authHeader);
|
|
852
|
-
if (!credentials)
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
905
|
+
if (!credentials) throw new Error("Unsupported Git authorization header");
|
|
906
|
+
if (/[\0\r\n]/.test(credentials.username)) throw new Error("Invalid Git credential username");
|
|
907
|
+
return credentials.username;
|
|
908
|
+
}
|
|
909
|
+
function replaceCredentialStoreFile(storePath, content) {
|
|
910
|
+
const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(storePath), `.${import_node_path.default.basename(storePath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
856
911
|
try {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
return null;
|
|
912
|
+
import_node_fs.default.writeFileSync(temporaryPath, content, { flag: "wx", mode: 384 });
|
|
913
|
+
import_node_fs.default.chmodSync(temporaryPath, 384);
|
|
914
|
+
import_node_fs.default.renameSync(temporaryPath, storePath);
|
|
915
|
+
} finally {
|
|
916
|
+
import_node_fs.default.rmSync(temporaryPath, { force: true });
|
|
863
917
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
918
|
+
}
|
|
919
|
+
function writeCredentialStoreEntries(remotes, storePath) {
|
|
920
|
+
const authoritativeEntries = /* @__PURE__ */ new Map();
|
|
921
|
+
for (const input of remotes) {
|
|
922
|
+
let remote;
|
|
869
923
|
try {
|
|
870
|
-
|
|
871
|
-
return `${parsed.protocol}//${parsed.host}` !== hostKey;
|
|
924
|
+
remote = new URL(input.remoteUrl);
|
|
872
925
|
} catch {
|
|
873
|
-
|
|
926
|
+
if (input.authHeader != null) throw new Error("Invalid authenticated Git remote URL");
|
|
927
|
+
continue;
|
|
874
928
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
929
|
+
if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
|
|
930
|
+
throw new Error("Git credentials must not be embedded in a remote URL");
|
|
931
|
+
}
|
|
932
|
+
if (remote.protocol !== "https:" && remote.protocol !== "http:") {
|
|
933
|
+
if (input.authHeader != null) throw new Error("Unsupported authenticated Git remote protocol");
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const hostKey = `${remote.protocol}//${remote.host}`;
|
|
937
|
+
let replacement = null;
|
|
938
|
+
if (input.authHeader != null) {
|
|
939
|
+
const credentials = decodeGitAuthHeader(input.authHeader);
|
|
940
|
+
if (!credentials) throw new Error("Unsupported Git authorization header");
|
|
941
|
+
const credentialUrl = new URL(hostKey);
|
|
942
|
+
credentialUrl.username = credentials.username;
|
|
943
|
+
credentialUrl.password = credentials.password;
|
|
944
|
+
replacement = credentialUrl.toString();
|
|
945
|
+
}
|
|
946
|
+
if (authoritativeEntries.has(hostKey) && authoritativeEntries.get(hostKey) !== replacement) {
|
|
947
|
+
throw new Error("Conflicting Git credentials for one transport host");
|
|
948
|
+
}
|
|
949
|
+
authoritativeEntries.set(hostKey, replacement);
|
|
950
|
+
}
|
|
951
|
+
const storeStat = lstatIfExists(storePath);
|
|
952
|
+
if (storeStat) {
|
|
953
|
+
if (storeStat.isSymbolicLink() || !storeStat.isFile()) throw new Error("Git credential store must be a regular file");
|
|
954
|
+
}
|
|
955
|
+
const replacements = [...authoritativeEntries.values()].filter((entry) => entry !== null);
|
|
956
|
+
if (replacements.length === 0) {
|
|
957
|
+
if (storeStat) import_node_fs.default.unlinkSync(storePath);
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
ensurePrivateCredentialStoreDirectory(import_node_path.default.dirname(storePath));
|
|
961
|
+
replaceCredentialStoreFile(storePath, `${replacements.join("\n")}
|
|
962
|
+
`);
|
|
963
|
+
return { hasCredentials: true, storePath };
|
|
964
|
+
}
|
|
965
|
+
function credentialHelperForRemotes(remotes, storePath) {
|
|
966
|
+
const credentialStore = writeCredentialStoreEntries(remotes, storePath);
|
|
967
|
+
return credentialStore?.hasCredentials ? readOnlyCredentialStoreHelper(credentialStore.storePath) : null;
|
|
968
|
+
}
|
|
969
|
+
const workerGitSecurityTestHarness = {
|
|
970
|
+
commandArgs: workerGitCommand.commandArgs,
|
|
971
|
+
credentialHelperForRemotes,
|
|
972
|
+
credentialStorePathForProject,
|
|
973
|
+
credentialStorePathForWorkspace,
|
|
974
|
+
legacySharedCredentialStorePath,
|
|
975
|
+
removeLegacySharedCredentialStore,
|
|
976
|
+
pruneCredentialStoreFiles,
|
|
977
|
+
readOnlyCredentialStoreHelper,
|
|
978
|
+
resetCredentialStoreDirectory,
|
|
979
|
+
replaceCredentialStoreFile
|
|
980
|
+
};
|
|
889
981
|
function dockerConfigPath() {
|
|
890
982
|
return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
|
|
891
983
|
}
|
|
@@ -2271,6 +2363,7 @@ async function startWorker(options) {
|
|
|
2271
2363
|
});
|
|
2272
2364
|
const config = readConfig(options.configPath);
|
|
2273
2365
|
const { baseUrl, token } = resolveCredentials(options, config);
|
|
2366
|
+
initializeCredentialStoreDirectoryOnce();
|
|
2274
2367
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
2275
2368
|
validateLabel(label);
|
|
2276
2369
|
const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
@@ -2317,6 +2410,8 @@ async function startWorker(options) {
|
|
|
2317
2410
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
2318
2411
|
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
2319
2412
|
let workspaceRemoteUrl = null;
|
|
2413
|
+
let workspaceCredentialHelper = null;
|
|
2414
|
+
let workspaceCredentialUsername = null;
|
|
2320
2415
|
let workspaceGitIdentity = null;
|
|
2321
2416
|
let workspaceConfigured = false;
|
|
2322
2417
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2331,6 +2426,7 @@ async function startWorker(options) {
|
|
|
2331
2426
|
let reloadAfterClose = false;
|
|
2332
2427
|
let shutdownAfterClose = false;
|
|
2333
2428
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
2429
|
+
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
2334
2430
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2335
2431
|
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
2336
2432
|
const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
|
|
@@ -2338,12 +2434,32 @@ async function startWorker(options) {
|
|
|
2338
2434
|
const workspacePlanRelativePath = (projectId, branchName) => import_node_path.default.posix.join("plans", projectId, encodeURIComponent(branchName));
|
|
2339
2435
|
const projectConnection = (project) => {
|
|
2340
2436
|
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, project.projectId);
|
|
2341
|
-
const mirrorAuth = gitAuthForRemote(mirrorUrl, bearerAuthHeader);
|
|
2342
2437
|
const originUrl = project.repoHttpUrl ?? mirrorUrl;
|
|
2343
2438
|
const originHeader = project.repoHttpUrl ? project.repoAuthHeader : bearerAuthHeader;
|
|
2344
|
-
const
|
|
2345
|
-
|
|
2346
|
-
|
|
2439
|
+
const credentialHelper = credentialHelperForRemotes(
|
|
2440
|
+
[
|
|
2441
|
+
{ remoteUrl: mirrorUrl, authHeader: bearerAuthHeader },
|
|
2442
|
+
{ remoteUrl: originUrl, authHeader: originHeader }
|
|
2443
|
+
],
|
|
2444
|
+
credentialStorePathForProject(project.projectId)
|
|
2445
|
+
);
|
|
2446
|
+
if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
|
|
2447
|
+
return {
|
|
2448
|
+
mirrorUrl,
|
|
2449
|
+
mirrorCredentialUsername: workerCredentialUsername,
|
|
2450
|
+
originUrl,
|
|
2451
|
+
originCredentialUsername: originHeader ? credentialUsernameForAuthHeader(originHeader) : null,
|
|
2452
|
+
credentialHelper
|
|
2453
|
+
};
|
|
2454
|
+
};
|
|
2455
|
+
const projectMirrorConnection = (projectId) => {
|
|
2456
|
+
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, projectId);
|
|
2457
|
+
const credentialHelper = credentialHelperForRemotes(
|
|
2458
|
+
[{ remoteUrl: mirrorUrl, authHeader: bearerAuthHeader }],
|
|
2459
|
+
credentialStorePathForProject(projectId)
|
|
2460
|
+
);
|
|
2461
|
+
if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
|
|
2462
|
+
return { mirrorUrl, credentialHelper, mirrorCredentialUsername: workerCredentialUsername };
|
|
2347
2463
|
};
|
|
2348
2464
|
const durableDeletionForBranch = (projectId, checkoutPathSegments, branchName) => projectWorkspaceState.tombstones.find(
|
|
2349
2465
|
(tombstone) => tombstone.projectId === projectId && tombstone.checkoutPathSegments[0] === checkoutPathSegments[0] && tombstone.checkoutPathSegments[1] === checkoutPathSegments[1] && (tombstone.kind === "project" || tombstone.branchName === branchName)
|
|
@@ -2367,7 +2483,8 @@ async function startWorker(options) {
|
|
|
2367
2483
|
branchName,
|
|
2368
2484
|
gitDirectory: configuredProjectBranchPath(projectsRoot, project, primaryBranchName),
|
|
2369
2485
|
mirrorUrl: connection.mirrorUrl,
|
|
2370
|
-
|
|
2486
|
+
credentialHelper: connection.credentialHelper,
|
|
2487
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2371
2488
|
sourcePath,
|
|
2372
2489
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branchName),
|
|
2373
2490
|
planSourcePath,
|
|
@@ -2378,7 +2495,7 @@ async function startWorker(options) {
|
|
|
2378
2495
|
};
|
|
2379
2496
|
const stageProjectDeletion = (project) => {
|
|
2380
2497
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2381
|
-
const connection =
|
|
2498
|
+
const connection = projectMirrorConnection(project.projectId);
|
|
2382
2499
|
const branches = project.branches.map(({ branchName }) => ({
|
|
2383
2500
|
branchName,
|
|
2384
2501
|
sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
|
|
@@ -2399,7 +2516,8 @@ async function startWorker(options) {
|
|
|
2399
2516
|
// hidden ref only after that publication succeeds.
|
|
2400
2517
|
gitDirectory: workspaceShadowRoot,
|
|
2401
2518
|
mirrorUrl: connection.mirrorUrl,
|
|
2402
|
-
|
|
2519
|
+
credentialHelper: connection.credentialHelper,
|
|
2520
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2403
2521
|
sourcePath: branch.sourcePath,
|
|
2404
2522
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
|
|
2405
2523
|
planSourcePath: branch.planSourcePath,
|
|
@@ -2457,14 +2575,17 @@ async function startWorker(options) {
|
|
|
2457
2575
|
for (const deletion of projectWorkspaceState.pendingMirrorRefDeletions) {
|
|
2458
2576
|
const key = projectBranchKey(deletion.projectId, deletion.branchName);
|
|
2459
2577
|
const planSourcePath = import_node_path.default.join(planRoot, deletion.projectId, ...deletion.branchName.split("/"));
|
|
2578
|
+
const liveProject = projectConfigById.get(deletion.projectId);
|
|
2579
|
+
const connection = liveProject ? projectConnection(liveProject) : projectMirrorConnection(deletion.projectId);
|
|
2460
2580
|
import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
|
|
2461
2581
|
pendingMirrorDeletes.set(key, {
|
|
2462
2582
|
id: projectMountId(deletion.projectId, deletion.branchName),
|
|
2463
2583
|
projectId: deletion.projectId,
|
|
2464
2584
|
branchName: deletion.branchName,
|
|
2465
2585
|
gitDirectory: workspaceShadowRoot,
|
|
2466
|
-
mirrorUrl:
|
|
2467
|
-
|
|
2586
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2587
|
+
credentialHelper: connection.credentialHelper,
|
|
2588
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2468
2589
|
sourcePath: deletion.managedPath,
|
|
2469
2590
|
workspaceRelativePath: workspaceProjectRelativePath(deletion.projectId, deletion.branchName),
|
|
2470
2591
|
planSourcePath,
|
|
@@ -2612,9 +2733,9 @@ async function startWorker(options) {
|
|
|
2612
2733
|
primaryBranchName: primaryProjectBranch(project),
|
|
2613
2734
|
branches: project.branches,
|
|
2614
2735
|
originUrl: connection.originUrl,
|
|
2615
|
-
|
|
2736
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2616
2737
|
mirrorUrl: connection.mirrorUrl,
|
|
2617
|
-
|
|
2738
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2618
2739
|
credentialHelper: connection.credentialHelper,
|
|
2619
2740
|
gitIdentity: workspaceGitIdentity
|
|
2620
2741
|
});
|
|
@@ -2653,10 +2774,17 @@ async function startWorker(options) {
|
|
|
2653
2774
|
if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
2654
2775
|
const connection = projectConnection(project);
|
|
2655
2776
|
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
2656
|
-
if (!tryGit(
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2777
|
+
if (!tryGit(
|
|
2778
|
+
[
|
|
2779
|
+
...(0, import_git_process_environment.gitTransportSecurityArgs)(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
|
|
2780
|
+
"fetch",
|
|
2781
|
+
"--no-recurse-submodules",
|
|
2782
|
+
"--prune",
|
|
2783
|
+
"origin",
|
|
2784
|
+
"+refs/heads/*:refs/remotes/origin/*"
|
|
2785
|
+
],
|
|
2786
|
+
{ cwd: primaryPath }
|
|
2787
|
+
)) {
|
|
2660
2788
|
process.stderr.write(`[r5d-worker] could not refresh origin divergence for ${project.projectPath}
|
|
2661
2789
|
`);
|
|
2662
2790
|
}
|
|
@@ -2706,7 +2834,8 @@ async function startWorker(options) {
|
|
|
2706
2834
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2707
2835
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2708
2836
|
mirrorUrl: connection.mirrorUrl,
|
|
2709
|
-
|
|
2837
|
+
credentialHelper: connection.credentialHelper,
|
|
2838
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2710
2839
|
onlyBranches: changed
|
|
2711
2840
|
});
|
|
2712
2841
|
for (const result of results) {
|
|
@@ -2733,7 +2862,8 @@ async function startWorker(options) {
|
|
|
2733
2862
|
gitDirectory: deletion.gitDirectory,
|
|
2734
2863
|
branchName: deletion.branchName,
|
|
2735
2864
|
mirrorUrl: deletion.mirrorUrl,
|
|
2736
|
-
|
|
2865
|
+
credentialHelper: deletion.credentialHelper,
|
|
2866
|
+
credentialUsername: deletion.credentialUsername
|
|
2737
2867
|
});
|
|
2738
2868
|
}
|
|
2739
2869
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
@@ -2743,6 +2873,9 @@ async function startWorker(options) {
|
|
|
2743
2873
|
});
|
|
2744
2874
|
}
|
|
2745
2875
|
pendingMirrorDeletes.delete(key);
|
|
2876
|
+
if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
|
|
2877
|
+
removeProjectCredentialStore(deletion.projectId);
|
|
2878
|
+
}
|
|
2746
2879
|
lastObservedProjectHeads.delete(key);
|
|
2747
2880
|
}
|
|
2748
2881
|
};
|
|
@@ -2756,7 +2889,8 @@ async function startWorker(options) {
|
|
|
2756
2889
|
primaryBranchName: primaryProjectBranch(project),
|
|
2757
2890
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2758
2891
|
mirrorUrl: connection.mirrorUrl,
|
|
2759
|
-
|
|
2892
|
+
credentialHelper: connection.credentialHelper,
|
|
2893
|
+
credentialUsername: connection.mirrorCredentialUsername
|
|
2760
2894
|
});
|
|
2761
2895
|
for (const state of refreshed) {
|
|
2762
2896
|
lastObservedProjectHeads.set(
|
|
@@ -2781,9 +2915,9 @@ async function startWorker(options) {
|
|
|
2781
2915
|
primaryBranchName: primaryProjectBranch(project),
|
|
2782
2916
|
branches: project.branches,
|
|
2783
2917
|
originUrl: connection.originUrl,
|
|
2784
|
-
|
|
2918
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2785
2919
|
mirrorUrl: connection.mirrorUrl,
|
|
2786
|
-
|
|
2920
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2787
2921
|
credentialHelper: connection.credentialHelper,
|
|
2788
2922
|
gitIdentity: workspaceGitIdentity
|
|
2789
2923
|
});
|
|
@@ -2830,16 +2964,16 @@ async function startWorker(options) {
|
|
|
2830
2964
|
error: error instanceof Error ? error.message : String(error)
|
|
2831
2965
|
});
|
|
2832
2966
|
const performWorkspaceSync = async (input) => {
|
|
2833
|
-
if (!workspaceRemoteUrl || !
|
|
2967
|
+
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
2968
|
+
throw new Error("Worker workspace configuration has not been received");
|
|
2969
|
+
}
|
|
2834
2970
|
const mounts = buildWorkspaceMounts();
|
|
2835
|
-
const remoteAuth = gitAuthForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2836
|
-
const credentialHelper = credentialHelperForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2837
2971
|
if (input.resetToCanonical) {
|
|
2838
2972
|
const reset = (0, import_workspace_git_sync.resetWorkspaceGit)({
|
|
2839
2973
|
workspacePath: workspaceShadowRoot,
|
|
2840
2974
|
remoteUrl: workspaceRemoteUrl,
|
|
2841
|
-
|
|
2842
|
-
|
|
2975
|
+
credentialHelper: workspaceCredentialHelper,
|
|
2976
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2843
2977
|
gitIdentity: workspaceGitIdentity,
|
|
2844
2978
|
mounts
|
|
2845
2979
|
});
|
|
@@ -2868,8 +3002,8 @@ async function startWorker(options) {
|
|
|
2868
3002
|
workerLabel: label,
|
|
2869
3003
|
workspacePath: workspaceShadowRoot,
|
|
2870
3004
|
remoteUrl: workspaceRemoteUrl,
|
|
2871
|
-
|
|
2872
|
-
|
|
3005
|
+
credentialHelper: workspaceCredentialHelper,
|
|
3006
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2873
3007
|
gitIdentity: workspaceGitIdentity,
|
|
2874
3008
|
mounts,
|
|
2875
3009
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
@@ -2902,6 +3036,12 @@ async function startWorker(options) {
|
|
|
2902
3036
|
const runWorkspaceSync = async (input) => {
|
|
2903
3037
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
2904
3038
|
const requestedAt = Date.now();
|
|
3039
|
+
if (!input.requestId && input.sendResult !== false) {
|
|
3040
|
+
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
3041
|
+
throw new Error("Cannot start autonomous workspace synchronization without an open worker control socket");
|
|
3042
|
+
}
|
|
3043
|
+
ws.send(JSON.stringify({ type: "workspace_sync_started", attemptId }));
|
|
3044
|
+
}
|
|
2905
3045
|
let queueEnteredAt = requestedAt;
|
|
2906
3046
|
let syncStartedAt = requestedAt;
|
|
2907
3047
|
workspaceSyncRequestsInFlight += 1;
|
|
@@ -2955,6 +3095,12 @@ async function startWorker(options) {
|
|
|
2955
3095
|
if (result.outcome === "failed") {
|
|
2956
3096
|
pendingAutomaticTrigger = scheduledTrigger;
|
|
2957
3097
|
}
|
|
3098
|
+
}).catch((error) => {
|
|
3099
|
+
pendingAutomaticTrigger = scheduledTrigger;
|
|
3100
|
+
process.stderr.write(
|
|
3101
|
+
`[r5d-worker] failed to start automatic workspace sync: ${error instanceof Error ? error.message : String(error)}
|
|
3102
|
+
`
|
|
3103
|
+
);
|
|
2958
3104
|
}).finally(() => {
|
|
2959
3105
|
automaticSyncInFlight = false;
|
|
2960
3106
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
@@ -2982,13 +3128,47 @@ async function startWorker(options) {
|
|
|
2982
3128
|
try {
|
|
2983
3129
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2984
3130
|
workspaceConfigured = false;
|
|
3131
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
3132
|
+
desiredProjects: message.projects
|
|
3133
|
+
});
|
|
3134
|
+
const requiredCredentialStorePaths = /* @__PURE__ */ new Set();
|
|
3135
|
+
const workspaceStorePath = credentialStorePathForWorkspace(message.workspaceRemoteUrl);
|
|
3136
|
+
requiredCredentialStorePaths.add(workspaceStorePath);
|
|
3137
|
+
const nextWorkspaceCredentialHelper = credentialHelperForRemotes(
|
|
3138
|
+
[{ remoteUrl: message.workspaceRemoteUrl, authHeader: bearerAuthHeader }],
|
|
3139
|
+
workspaceStorePath
|
|
3140
|
+
);
|
|
3141
|
+
if (!nextWorkspaceCredentialHelper) throw new Error("Workspace credentials are unavailable");
|
|
3142
|
+
const desiredProjectConfigById = new Map(message.projects.map((project) => [project.projectId, project]));
|
|
3143
|
+
for (const project of message.projects) {
|
|
3144
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(project.projectId));
|
|
3145
|
+
projectConnection(project);
|
|
3146
|
+
}
|
|
3147
|
+
const pendingCredentialProjectIds = /* @__PURE__ */ new Set([
|
|
3148
|
+
...[...pendingMirrorDeletes.values()].map(({ projectId }) => projectId),
|
|
3149
|
+
...projectWorkspaceState.pendingTreeDeletions.map(({ projectId }) => projectId),
|
|
3150
|
+
...projectWorkspaceState.pendingMirrorRefDeletions.map(({ projectId }) => projectId)
|
|
3151
|
+
]);
|
|
3152
|
+
for (const projectId of pendingCredentialProjectIds) {
|
|
3153
|
+
const desiredProject = desiredProjectConfigById.get(projectId);
|
|
3154
|
+
if (desiredProject) projectConnection(desiredProject);
|
|
3155
|
+
else projectMirrorConnection(projectId);
|
|
3156
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(projectId));
|
|
3157
|
+
}
|
|
3158
|
+
for (const deletion of pendingMirrorDeletes.values()) {
|
|
3159
|
+
const desiredProject = desiredProjectConfigById.get(deletion.projectId);
|
|
3160
|
+
const connection = desiredProject ? projectConnection(desiredProject) : projectMirrorConnection(deletion.projectId);
|
|
3161
|
+
deletion.mirrorUrl = connection.mirrorUrl;
|
|
3162
|
+
deletion.credentialHelper = connection.credentialHelper;
|
|
3163
|
+
deletion.credentialUsername = connection.mirrorCredentialUsername;
|
|
3164
|
+
}
|
|
3165
|
+
pruneCredentialStoreFiles(requiredCredentialStorePaths);
|
|
2985
3166
|
configureGitHubAuth(message.githubCredential);
|
|
2986
3167
|
visibleGitIdentity = message.gitIdentity;
|
|
2987
3168
|
workspaceGitIdentity = message.gitIdentity;
|
|
2988
3169
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
});
|
|
3170
|
+
workspaceCredentialHelper = nextWorkspaceCredentialHelper;
|
|
3171
|
+
workspaceCredentialUsername = workerCredentialUsername;
|
|
2992
3172
|
(0, import_project_workspace_state.pruneAuthoritativelyDesiredBranchDeletions)(pendingMirrorDeletes, message.projects);
|
|
2993
3173
|
const effectiveProjects = effectiveWorkerProjects(message.projects).map((project) => ({
|
|
2994
3174
|
...project,
|
|
@@ -3019,13 +3199,11 @@ async function startWorker(options) {
|
|
|
3019
3199
|
for (const projectId of [...readyProjectIds]) {
|
|
3020
3200
|
if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
|
|
3021
3201
|
}
|
|
3022
|
-
const remoteAuth = gitAuthForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3023
|
-
const credentialHelper = credentialHelperForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3024
3202
|
(0, import_workspace_git_sync.ensureWorkspaceGitClone)({
|
|
3025
3203
|
workspacePath: workspaceShadowRoot,
|
|
3026
3204
|
remoteUrl: message.workspaceRemoteUrl,
|
|
3027
|
-
|
|
3028
|
-
|
|
3205
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
3206
|
+
credentialUsername: workerCredentialUsername,
|
|
3029
3207
|
gitIdentity: message.gitIdentity
|
|
3030
3208
|
});
|
|
3031
3209
|
pruneStaleOuterWorkspaceEntries(projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot));
|
|
@@ -3825,5 +4003,6 @@ if (isCliEntrypoint()) {
|
|
|
3825
4003
|
resolveWorkerFilePath,
|
|
3826
4004
|
resolveWorkerSessionTarget,
|
|
3827
4005
|
syncSessionArtifacts,
|
|
4006
|
+
workerGitSecurityTestHarness,
|
|
3828
4007
|
writeWorkerTextFile
|
|
3829
4008
|
});
|
package/dist/cjs/package.json
CHANGED