@ricsam/r5d-worker 0.0.75 → 0.0.77
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 +276 -100
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +135 -56
- 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 +276 -100
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +133 -56
- 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 +33 -10
- package/dist/types/workspace-git-sync.d.ts +18 -5
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { createHash } from "node:crypto";
|
|
|
5
5
|
import os, { hostname } from "node:os";
|
|
6
6
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
7
7
|
import { installCliUpdate, readInstalledCliVersion } from "./cli-update.mjs";
|
|
8
|
+
import { gitTransportSecurityArgs, workerGitProcessEnvironment } from "./git-process-environment.mjs";
|
|
8
9
|
import {
|
|
9
10
|
grantWorkerHeartbeatBusyGrace,
|
|
10
11
|
hasWorkerHeartbeatTimedOutWithBusyGrace,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
} from "./supervisor.mjs";
|
|
27
28
|
import { managedProjectRoot, validateManagedBranchName } from "./managed-paths.mjs";
|
|
28
29
|
import {
|
|
30
|
+
cleanupStaleProjectWorktreeSnapshots,
|
|
29
31
|
createLinkedProjectBranch,
|
|
30
32
|
deleteLinkedProjectBranch,
|
|
31
33
|
deleteProjectMirrorBranch,
|
|
@@ -635,29 +637,16 @@ const NON_RECURSIVE_GIT_CONFIG_ARGS = [
|
|
|
635
637
|
"-c",
|
|
636
638
|
"push.recurseSubmodules=false"
|
|
637
639
|
];
|
|
638
|
-
function gitExtraHeaderConfigKey(extraHeaderUrl) {
|
|
639
|
-
return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
|
|
640
|
-
}
|
|
641
|
-
function gitAuthArgs(auth) {
|
|
642
|
-
if (!auth) {
|
|
643
|
-
return [];
|
|
644
|
-
}
|
|
645
|
-
const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
|
|
646
|
-
return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
|
|
647
|
-
}
|
|
648
640
|
const workerGitCommand = {
|
|
649
|
-
commandArgs: (args
|
|
641
|
+
commandArgs: (args) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...args]
|
|
650
642
|
};
|
|
651
643
|
function runGit(args, options = {}) {
|
|
652
|
-
const command = workerGitCommand.commandArgs(args
|
|
644
|
+
const command = workerGitCommand.commandArgs(args);
|
|
653
645
|
const result = Bun.spawnSync(command, {
|
|
654
646
|
cwd: options.cwd,
|
|
655
647
|
stdout: "pipe",
|
|
656
648
|
stderr: "pipe",
|
|
657
|
-
env:
|
|
658
|
-
...process.env,
|
|
659
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
660
|
-
}
|
|
649
|
+
env: workerGitProcessEnvironment()
|
|
661
650
|
});
|
|
662
651
|
if (result.exitCode !== 0) {
|
|
663
652
|
throw new Error(
|
|
@@ -790,28 +779,92 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
|
790
779
|
function canonicalProjectRemoteUrl(baseUrl, projectId) {
|
|
791
780
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
792
781
|
}
|
|
793
|
-
function gitExtraHeaderUrlForRemote(remoteUrl) {
|
|
794
|
-
try {
|
|
795
|
-
return new URL(remoteUrl).origin;
|
|
796
|
-
} catch {
|
|
797
|
-
return remoteUrl;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
function gitAuthForRemote(remoteUrl, authHeader) {
|
|
801
|
-
if (!authHeader) {
|
|
802
|
-
return void 0;
|
|
803
|
-
}
|
|
804
|
-
return {
|
|
805
|
-
extraHeaderUrl: gitExtraHeaderUrlForRemote(remoteUrl),
|
|
806
|
-
header: authHeader
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
782
|
function shellQuote(value) {
|
|
810
783
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
811
784
|
}
|
|
812
|
-
function
|
|
785
|
+
function readOnlyCredentialStoreHelper(storePath) {
|
|
786
|
+
return `!f() { if [ "$1" = get ]; then git credential-store --file=${shellQuote(storePath)} get; else return 0; fi; }; f`;
|
|
787
|
+
}
|
|
788
|
+
function credentialStoreDirectory() {
|
|
789
|
+
return path.join(os.homedir(), ".r5d", "git-credentials");
|
|
790
|
+
}
|
|
791
|
+
function legacySharedCredentialStorePath() {
|
|
813
792
|
return path.join(os.homedir(), ".r5d", "github-credentials");
|
|
814
793
|
}
|
|
794
|
+
function credentialStorePathForProject(projectId) {
|
|
795
|
+
validateProjectId(projectId);
|
|
796
|
+
const digest = createHash("sha256").update(`project\0${projectId}`).digest("hex");
|
|
797
|
+
return path.join(credentialStoreDirectory(), `project-${digest}.store`);
|
|
798
|
+
}
|
|
799
|
+
function credentialStorePathForWorkspace(remoteUrl) {
|
|
800
|
+
const digest = createHash("sha256").update(`workspace\0${remoteUrl}`).digest("hex");
|
|
801
|
+
return path.join(credentialStoreDirectory(), `workspace-${digest}.store`);
|
|
802
|
+
}
|
|
803
|
+
function lstatIfExists(filePath) {
|
|
804
|
+
try {
|
|
805
|
+
return fs.lstatSync(filePath);
|
|
806
|
+
} catch (error) {
|
|
807
|
+
if (error.code === "ENOENT") return null;
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function ensurePrivateCredentialStoreDirectory(directoryPath) {
|
|
812
|
+
const stat = lstatIfExists(directoryPath);
|
|
813
|
+
if (stat) {
|
|
814
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Git credential-store path must be a private directory");
|
|
815
|
+
} else {
|
|
816
|
+
fs.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
817
|
+
}
|
|
818
|
+
fs.chmodSync(directoryPath, 448);
|
|
819
|
+
}
|
|
820
|
+
function removeLegacySharedCredentialStore(storePath = legacySharedCredentialStorePath()) {
|
|
821
|
+
fs.rmSync(storePath, { force: true });
|
|
822
|
+
}
|
|
823
|
+
let credentialStoreDirectoryInitialized = false;
|
|
824
|
+
function resetCredentialStoreDirectory(directoryPath = credentialStoreDirectory()) {
|
|
825
|
+
const stat = lstatIfExists(directoryPath);
|
|
826
|
+
if (stat) {
|
|
827
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) fs.unlinkSync(directoryPath);
|
|
828
|
+
else fs.rmSync(directoryPath, { recursive: true, force: true });
|
|
829
|
+
}
|
|
830
|
+
ensurePrivateCredentialStoreDirectory(directoryPath);
|
|
831
|
+
}
|
|
832
|
+
function initializeCredentialStoreDirectoryOnce() {
|
|
833
|
+
if (credentialStoreDirectoryInitialized) return;
|
|
834
|
+
removeLegacySharedCredentialStore();
|
|
835
|
+
resetCredentialStoreDirectory();
|
|
836
|
+
credentialStoreDirectoryInitialized = true;
|
|
837
|
+
}
|
|
838
|
+
const APP_CREDENTIAL_STORE_FILE = /^(?:project|workspace)-[0-9a-f]{64}\.store$/;
|
|
839
|
+
function pruneCredentialStoreFiles(requiredStorePaths, directoryPath = credentialStoreDirectory()) {
|
|
840
|
+
ensurePrivateCredentialStoreDirectory(directoryPath);
|
|
841
|
+
const resolvedDirectory = path.resolve(directoryPath);
|
|
842
|
+
const requiredNames = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const storePath of requiredStorePaths) {
|
|
844
|
+
const resolvedStorePath = path.resolve(storePath);
|
|
845
|
+
if (path.dirname(resolvedStorePath) !== resolvedDirectory || !APP_CREDENTIAL_STORE_FILE.test(path.basename(resolvedStorePath))) {
|
|
846
|
+
throw new Error("Invalid app-owned Git credential-store path");
|
|
847
|
+
}
|
|
848
|
+
requiredNames.add(path.basename(resolvedStorePath));
|
|
849
|
+
}
|
|
850
|
+
const removed = [];
|
|
851
|
+
for (const name of fs.readdirSync(directoryPath)) {
|
|
852
|
+
if (!APP_CREDENTIAL_STORE_FILE.test(name) || requiredNames.has(name)) continue;
|
|
853
|
+
const storePath = path.join(directoryPath, name);
|
|
854
|
+
const stat = fs.lstatSync(storePath);
|
|
855
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) continue;
|
|
856
|
+
fs.unlinkSync(storePath);
|
|
857
|
+
removed.push(storePath);
|
|
858
|
+
}
|
|
859
|
+
return removed;
|
|
860
|
+
}
|
|
861
|
+
function removeProjectCredentialStore(projectId) {
|
|
862
|
+
const storePath = credentialStorePathForProject(projectId);
|
|
863
|
+
const stat = lstatIfExists(storePath);
|
|
864
|
+
if (!stat) return;
|
|
865
|
+
if (!stat.isFile() && !stat.isSymbolicLink()) throw new Error("Git credential store must be a regular file");
|
|
866
|
+
fs.unlinkSync(storePath);
|
|
867
|
+
}
|
|
815
868
|
function decodeGitAuthHeader(authHeader) {
|
|
816
869
|
const match = /^Authorization:\s*Basic\s+(.+)$/i.exec(authHeader.trim());
|
|
817
870
|
if (match) {
|
|
@@ -823,45 +876,84 @@ function decodeGitAuthHeader(authHeader) {
|
|
|
823
876
|
const bearer = /^Authorization:\s*Bearer\s+(.+)$/i.exec(authHeader.trim());
|
|
824
877
|
return bearer?.[1] ? { username: "r5d-worker", password: bearer[1] } : null;
|
|
825
878
|
}
|
|
826
|
-
function
|
|
879
|
+
function credentialUsernameForAuthHeader(authHeader) {
|
|
827
880
|
const credentials = decodeGitAuthHeader(authHeader);
|
|
828
|
-
if (!credentials)
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
881
|
+
if (!credentials) throw new Error("Unsupported Git authorization header");
|
|
882
|
+
if (/[\0\r\n]/.test(credentials.username)) throw new Error("Invalid Git credential username");
|
|
883
|
+
return credentials.username;
|
|
884
|
+
}
|
|
885
|
+
function replaceCredentialStoreFile(storePath, content) {
|
|
886
|
+
const temporaryPath = path.join(path.dirname(storePath), `.${path.basename(storePath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
832
887
|
try {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
return null;
|
|
888
|
+
fs.writeFileSync(temporaryPath, content, { flag: "wx", mode: 384 });
|
|
889
|
+
fs.chmodSync(temporaryPath, 384);
|
|
890
|
+
fs.renameSync(temporaryPath, storePath);
|
|
891
|
+
} finally {
|
|
892
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
839
893
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
894
|
+
}
|
|
895
|
+
function writeCredentialStoreEntries(remotes, storePath) {
|
|
896
|
+
const authoritativeEntries = /* @__PURE__ */ new Map();
|
|
897
|
+
for (const input of remotes) {
|
|
898
|
+
let remote;
|
|
845
899
|
try {
|
|
846
|
-
|
|
847
|
-
return `${parsed.protocol}//${parsed.host}` !== hostKey;
|
|
900
|
+
remote = new URL(input.remoteUrl);
|
|
848
901
|
} catch {
|
|
849
|
-
|
|
902
|
+
if (input.authHeader != null) throw new Error("Invalid authenticated Git remote URL");
|
|
903
|
+
continue;
|
|
850
904
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
905
|
+
if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
|
|
906
|
+
throw new Error("Git credentials must not be embedded in a remote URL");
|
|
907
|
+
}
|
|
908
|
+
if (remote.protocol !== "https:" && remote.protocol !== "http:") {
|
|
909
|
+
if (input.authHeader != null) throw new Error("Unsupported authenticated Git remote protocol");
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
const hostKey = `${remote.protocol}//${remote.host}`;
|
|
913
|
+
let replacement = null;
|
|
914
|
+
if (input.authHeader != null) {
|
|
915
|
+
const credentials = decodeGitAuthHeader(input.authHeader);
|
|
916
|
+
if (!credentials) throw new Error("Unsupported Git authorization header");
|
|
917
|
+
const credentialUrl = new URL(hostKey);
|
|
918
|
+
credentialUrl.username = credentials.username;
|
|
919
|
+
credentialUrl.password = credentials.password;
|
|
920
|
+
replacement = credentialUrl.toString();
|
|
921
|
+
}
|
|
922
|
+
if (authoritativeEntries.has(hostKey) && authoritativeEntries.get(hostKey) !== replacement) {
|
|
923
|
+
throw new Error("Conflicting Git credentials for one transport host");
|
|
924
|
+
}
|
|
925
|
+
authoritativeEntries.set(hostKey, replacement);
|
|
926
|
+
}
|
|
927
|
+
const storeStat = lstatIfExists(storePath);
|
|
928
|
+
if (storeStat) {
|
|
929
|
+
if (storeStat.isSymbolicLink() || !storeStat.isFile()) throw new Error("Git credential store must be a regular file");
|
|
930
|
+
}
|
|
931
|
+
const replacements = [...authoritativeEntries.values()].filter((entry) => entry !== null);
|
|
932
|
+
if (replacements.length === 0) {
|
|
933
|
+
if (storeStat) fs.unlinkSync(storePath);
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
ensurePrivateCredentialStoreDirectory(path.dirname(storePath));
|
|
937
|
+
replaceCredentialStoreFile(storePath, `${replacements.join("\n")}
|
|
938
|
+
`);
|
|
939
|
+
return { hasCredentials: true, storePath };
|
|
940
|
+
}
|
|
941
|
+
function credentialHelperForRemotes(remotes, storePath) {
|
|
942
|
+
const credentialStore = writeCredentialStoreEntries(remotes, storePath);
|
|
943
|
+
return credentialStore?.hasCredentials ? readOnlyCredentialStoreHelper(credentialStore.storePath) : null;
|
|
944
|
+
}
|
|
945
|
+
const workerGitSecurityTestHarness = {
|
|
946
|
+
commandArgs: workerGitCommand.commandArgs,
|
|
947
|
+
credentialHelperForRemotes,
|
|
948
|
+
credentialStorePathForProject,
|
|
949
|
+
credentialStorePathForWorkspace,
|
|
950
|
+
legacySharedCredentialStorePath,
|
|
951
|
+
removeLegacySharedCredentialStore,
|
|
952
|
+
pruneCredentialStoreFiles,
|
|
953
|
+
readOnlyCredentialStoreHelper,
|
|
954
|
+
resetCredentialStoreDirectory,
|
|
955
|
+
replaceCredentialStoreFile
|
|
956
|
+
};
|
|
865
957
|
function dockerConfigPath() {
|
|
866
958
|
return path.join(os.homedir(), ".docker", "config.json");
|
|
867
959
|
}
|
|
@@ -2247,6 +2339,7 @@ async function startWorker(options) {
|
|
|
2247
2339
|
});
|
|
2248
2340
|
const config = readConfig(options.configPath);
|
|
2249
2341
|
const { baseUrl, token } = resolveCredentials(options, config);
|
|
2342
|
+
initializeCredentialStoreDirectoryOnce();
|
|
2250
2343
|
const label = requireValue(options.label, "Missing required --label <label>");
|
|
2251
2344
|
validateLabel(label);
|
|
2252
2345
|
const projectsRoot = path.resolve(options.projectRoot ?? defaultProjectsRoot());
|
|
@@ -2266,6 +2359,15 @@ async function startWorker(options) {
|
|
|
2266
2359
|
`);
|
|
2267
2360
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
2268
2361
|
`);
|
|
2362
|
+
const snapshotCleanup = cleanupStaleProjectWorktreeSnapshots();
|
|
2363
|
+
if (snapshotCleanup.removed.length > 0) {
|
|
2364
|
+
process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
|
|
2365
|
+
`);
|
|
2366
|
+
}
|
|
2367
|
+
for (const failure of snapshotCleanup.failed) {
|
|
2368
|
+
process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
|
|
2369
|
+
`);
|
|
2370
|
+
}
|
|
2269
2371
|
runGit(["--version"]);
|
|
2270
2372
|
await verifyR5dctlAuth(baseUrl, token);
|
|
2271
2373
|
fs.mkdirSync(projectsRoot, { recursive: true });
|
|
@@ -2284,6 +2386,8 @@ async function startWorker(options) {
|
|
|
2284
2386
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
2285
2387
|
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
2286
2388
|
let workspaceRemoteUrl = null;
|
|
2389
|
+
let workspaceCredentialHelper = null;
|
|
2390
|
+
let workspaceCredentialUsername = null;
|
|
2287
2391
|
let workspaceGitIdentity = null;
|
|
2288
2392
|
let workspaceConfigured = false;
|
|
2289
2393
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2298,6 +2402,7 @@ async function startWorker(options) {
|
|
|
2298
2402
|
let reloadAfterClose = false;
|
|
2299
2403
|
let shutdownAfterClose = false;
|
|
2300
2404
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
2405
|
+
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
2301
2406
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2302
2407
|
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
2303
2408
|
const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
|
|
@@ -2305,12 +2410,32 @@ async function startWorker(options) {
|
|
|
2305
2410
|
const workspacePlanRelativePath = (projectId, branchName) => path.posix.join("plans", projectId, encodeURIComponent(branchName));
|
|
2306
2411
|
const projectConnection = (project) => {
|
|
2307
2412
|
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, project.projectId);
|
|
2308
|
-
const mirrorAuth = gitAuthForRemote(mirrorUrl, bearerAuthHeader);
|
|
2309
2413
|
const originUrl = project.repoHttpUrl ?? mirrorUrl;
|
|
2310
2414
|
const originHeader = project.repoHttpUrl ? project.repoAuthHeader : bearerAuthHeader;
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2415
|
+
const credentialHelper = credentialHelperForRemotes(
|
|
2416
|
+
[
|
|
2417
|
+
{ remoteUrl: mirrorUrl, authHeader: bearerAuthHeader },
|
|
2418
|
+
{ remoteUrl: originUrl, authHeader: originHeader }
|
|
2419
|
+
],
|
|
2420
|
+
credentialStorePathForProject(project.projectId)
|
|
2421
|
+
);
|
|
2422
|
+
if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
|
|
2423
|
+
return {
|
|
2424
|
+
mirrorUrl,
|
|
2425
|
+
mirrorCredentialUsername: workerCredentialUsername,
|
|
2426
|
+
originUrl,
|
|
2427
|
+
originCredentialUsername: originHeader ? credentialUsernameForAuthHeader(originHeader) : null,
|
|
2428
|
+
credentialHelper
|
|
2429
|
+
};
|
|
2430
|
+
};
|
|
2431
|
+
const projectMirrorConnection = (projectId) => {
|
|
2432
|
+
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, projectId);
|
|
2433
|
+
const credentialHelper = credentialHelperForRemotes(
|
|
2434
|
+
[{ remoteUrl: mirrorUrl, authHeader: bearerAuthHeader }],
|
|
2435
|
+
credentialStorePathForProject(projectId)
|
|
2436
|
+
);
|
|
2437
|
+
if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
|
|
2438
|
+
return { mirrorUrl, credentialHelper, mirrorCredentialUsername: workerCredentialUsername };
|
|
2314
2439
|
};
|
|
2315
2440
|
const durableDeletionForBranch = (projectId, checkoutPathSegments, branchName) => projectWorkspaceState.tombstones.find(
|
|
2316
2441
|
(tombstone) => tombstone.projectId === projectId && tombstone.checkoutPathSegments[0] === checkoutPathSegments[0] && tombstone.checkoutPathSegments[1] === checkoutPathSegments[1] && (tombstone.kind === "project" || tombstone.branchName === branchName)
|
|
@@ -2334,7 +2459,8 @@ async function startWorker(options) {
|
|
|
2334
2459
|
branchName,
|
|
2335
2460
|
gitDirectory: configuredProjectBranchPath(projectsRoot, project, primaryBranchName),
|
|
2336
2461
|
mirrorUrl: connection.mirrorUrl,
|
|
2337
|
-
|
|
2462
|
+
credentialHelper: connection.credentialHelper,
|
|
2463
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2338
2464
|
sourcePath,
|
|
2339
2465
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branchName),
|
|
2340
2466
|
planSourcePath,
|
|
@@ -2345,7 +2471,7 @@ async function startWorker(options) {
|
|
|
2345
2471
|
};
|
|
2346
2472
|
const stageProjectDeletion = (project) => {
|
|
2347
2473
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2348
|
-
const connection =
|
|
2474
|
+
const connection = projectMirrorConnection(project.projectId);
|
|
2349
2475
|
const branches = project.branches.map(({ branchName }) => ({
|
|
2350
2476
|
branchName,
|
|
2351
2477
|
sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
|
|
@@ -2366,7 +2492,8 @@ async function startWorker(options) {
|
|
|
2366
2492
|
// hidden ref only after that publication succeeds.
|
|
2367
2493
|
gitDirectory: workspaceShadowRoot,
|
|
2368
2494
|
mirrorUrl: connection.mirrorUrl,
|
|
2369
|
-
|
|
2495
|
+
credentialHelper: connection.credentialHelper,
|
|
2496
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2370
2497
|
sourcePath: branch.sourcePath,
|
|
2371
2498
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
|
|
2372
2499
|
planSourcePath: branch.planSourcePath,
|
|
@@ -2424,14 +2551,17 @@ async function startWorker(options) {
|
|
|
2424
2551
|
for (const deletion of projectWorkspaceState.pendingMirrorRefDeletions) {
|
|
2425
2552
|
const key = projectBranchKey(deletion.projectId, deletion.branchName);
|
|
2426
2553
|
const planSourcePath = path.join(planRoot, deletion.projectId, ...deletion.branchName.split("/"));
|
|
2554
|
+
const liveProject = projectConfigById.get(deletion.projectId);
|
|
2555
|
+
const connection = liveProject ? projectConnection(liveProject) : projectMirrorConnection(deletion.projectId);
|
|
2427
2556
|
fs.rmSync(planSourcePath, { recursive: true, force: true });
|
|
2428
2557
|
pendingMirrorDeletes.set(key, {
|
|
2429
2558
|
id: projectMountId(deletion.projectId, deletion.branchName),
|
|
2430
2559
|
projectId: deletion.projectId,
|
|
2431
2560
|
branchName: deletion.branchName,
|
|
2432
2561
|
gitDirectory: workspaceShadowRoot,
|
|
2433
|
-
mirrorUrl:
|
|
2434
|
-
|
|
2562
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2563
|
+
credentialHelper: connection.credentialHelper,
|
|
2564
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2435
2565
|
sourcePath: deletion.managedPath,
|
|
2436
2566
|
workspaceRelativePath: workspaceProjectRelativePath(deletion.projectId, deletion.branchName),
|
|
2437
2567
|
planSourcePath,
|
|
@@ -2579,9 +2709,9 @@ async function startWorker(options) {
|
|
|
2579
2709
|
primaryBranchName: primaryProjectBranch(project),
|
|
2580
2710
|
branches: project.branches,
|
|
2581
2711
|
originUrl: connection.originUrl,
|
|
2582
|
-
|
|
2712
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2583
2713
|
mirrorUrl: connection.mirrorUrl,
|
|
2584
|
-
|
|
2714
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2585
2715
|
credentialHelper: connection.credentialHelper,
|
|
2586
2716
|
gitIdentity: workspaceGitIdentity
|
|
2587
2717
|
});
|
|
@@ -2620,10 +2750,17 @@ async function startWorker(options) {
|
|
|
2620
2750
|
if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
2621
2751
|
const connection = projectConnection(project);
|
|
2622
2752
|
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
2623
|
-
if (!tryGit(
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2753
|
+
if (!tryGit(
|
|
2754
|
+
[
|
|
2755
|
+
...gitTransportSecurityArgs(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
|
|
2756
|
+
"fetch",
|
|
2757
|
+
"--no-recurse-submodules",
|
|
2758
|
+
"--prune",
|
|
2759
|
+
"origin",
|
|
2760
|
+
"+refs/heads/*:refs/remotes/origin/*"
|
|
2761
|
+
],
|
|
2762
|
+
{ cwd: primaryPath }
|
|
2763
|
+
)) {
|
|
2627
2764
|
process.stderr.write(`[r5d-worker] could not refresh origin divergence for ${project.projectPath}
|
|
2628
2765
|
`);
|
|
2629
2766
|
}
|
|
@@ -2673,7 +2810,8 @@ async function startWorker(options) {
|
|
|
2673
2810
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2674
2811
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2675
2812
|
mirrorUrl: connection.mirrorUrl,
|
|
2676
|
-
|
|
2813
|
+
credentialHelper: connection.credentialHelper,
|
|
2814
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2677
2815
|
onlyBranches: changed
|
|
2678
2816
|
});
|
|
2679
2817
|
for (const result of results) {
|
|
@@ -2700,7 +2838,8 @@ async function startWorker(options) {
|
|
|
2700
2838
|
gitDirectory: deletion.gitDirectory,
|
|
2701
2839
|
branchName: deletion.branchName,
|
|
2702
2840
|
mirrorUrl: deletion.mirrorUrl,
|
|
2703
|
-
|
|
2841
|
+
credentialHelper: deletion.credentialHelper,
|
|
2842
|
+
credentialUsername: deletion.credentialUsername
|
|
2704
2843
|
});
|
|
2705
2844
|
}
|
|
2706
2845
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
@@ -2710,6 +2849,9 @@ async function startWorker(options) {
|
|
|
2710
2849
|
});
|
|
2711
2850
|
}
|
|
2712
2851
|
pendingMirrorDeletes.delete(key);
|
|
2852
|
+
if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
|
|
2853
|
+
removeProjectCredentialStore(deletion.projectId);
|
|
2854
|
+
}
|
|
2713
2855
|
lastObservedProjectHeads.delete(key);
|
|
2714
2856
|
}
|
|
2715
2857
|
};
|
|
@@ -2723,7 +2865,8 @@ async function startWorker(options) {
|
|
|
2723
2865
|
primaryBranchName: primaryProjectBranch(project),
|
|
2724
2866
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2725
2867
|
mirrorUrl: connection.mirrorUrl,
|
|
2726
|
-
|
|
2868
|
+
credentialHelper: connection.credentialHelper,
|
|
2869
|
+
credentialUsername: connection.mirrorCredentialUsername
|
|
2727
2870
|
});
|
|
2728
2871
|
for (const state of refreshed) {
|
|
2729
2872
|
lastObservedProjectHeads.set(
|
|
@@ -2748,9 +2891,9 @@ async function startWorker(options) {
|
|
|
2748
2891
|
primaryBranchName: primaryProjectBranch(project),
|
|
2749
2892
|
branches: project.branches,
|
|
2750
2893
|
originUrl: connection.originUrl,
|
|
2751
|
-
|
|
2894
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2752
2895
|
mirrorUrl: connection.mirrorUrl,
|
|
2753
|
-
|
|
2896
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2754
2897
|
credentialHelper: connection.credentialHelper,
|
|
2755
2898
|
gitIdentity: workspaceGitIdentity
|
|
2756
2899
|
});
|
|
@@ -2797,16 +2940,16 @@ async function startWorker(options) {
|
|
|
2797
2940
|
error: error instanceof Error ? error.message : String(error)
|
|
2798
2941
|
});
|
|
2799
2942
|
const performWorkspaceSync = async (input) => {
|
|
2800
|
-
if (!workspaceRemoteUrl || !
|
|
2943
|
+
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
2944
|
+
throw new Error("Worker workspace configuration has not been received");
|
|
2945
|
+
}
|
|
2801
2946
|
const mounts = buildWorkspaceMounts();
|
|
2802
|
-
const remoteAuth = gitAuthForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2803
|
-
const credentialHelper = credentialHelperForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2804
2947
|
if (input.resetToCanonical) {
|
|
2805
2948
|
const reset = resetWorkspaceGit({
|
|
2806
2949
|
workspacePath: workspaceShadowRoot,
|
|
2807
2950
|
remoteUrl: workspaceRemoteUrl,
|
|
2808
|
-
|
|
2809
|
-
|
|
2951
|
+
credentialHelper: workspaceCredentialHelper,
|
|
2952
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2810
2953
|
gitIdentity: workspaceGitIdentity,
|
|
2811
2954
|
mounts
|
|
2812
2955
|
});
|
|
@@ -2835,8 +2978,8 @@ async function startWorker(options) {
|
|
|
2835
2978
|
workerLabel: label,
|
|
2836
2979
|
workspacePath: workspaceShadowRoot,
|
|
2837
2980
|
remoteUrl: workspaceRemoteUrl,
|
|
2838
|
-
|
|
2839
|
-
|
|
2981
|
+
credentialHelper: workspaceCredentialHelper,
|
|
2982
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2840
2983
|
gitIdentity: workspaceGitIdentity,
|
|
2841
2984
|
mounts,
|
|
2842
2985
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
@@ -2949,13 +3092,47 @@ async function startWorker(options) {
|
|
|
2949
3092
|
try {
|
|
2950
3093
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2951
3094
|
workspaceConfigured = false;
|
|
3095
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
3096
|
+
desiredProjects: message.projects
|
|
3097
|
+
});
|
|
3098
|
+
const requiredCredentialStorePaths = /* @__PURE__ */ new Set();
|
|
3099
|
+
const workspaceStorePath = credentialStorePathForWorkspace(message.workspaceRemoteUrl);
|
|
3100
|
+
requiredCredentialStorePaths.add(workspaceStorePath);
|
|
3101
|
+
const nextWorkspaceCredentialHelper = credentialHelperForRemotes(
|
|
3102
|
+
[{ remoteUrl: message.workspaceRemoteUrl, authHeader: bearerAuthHeader }],
|
|
3103
|
+
workspaceStorePath
|
|
3104
|
+
);
|
|
3105
|
+
if (!nextWorkspaceCredentialHelper) throw new Error("Workspace credentials are unavailable");
|
|
3106
|
+
const desiredProjectConfigById = new Map(message.projects.map((project) => [project.projectId, project]));
|
|
3107
|
+
for (const project of message.projects) {
|
|
3108
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(project.projectId));
|
|
3109
|
+
projectConnection(project);
|
|
3110
|
+
}
|
|
3111
|
+
const pendingCredentialProjectIds = /* @__PURE__ */ new Set([
|
|
3112
|
+
...[...pendingMirrorDeletes.values()].map(({ projectId }) => projectId),
|
|
3113
|
+
...projectWorkspaceState.pendingTreeDeletions.map(({ projectId }) => projectId),
|
|
3114
|
+
...projectWorkspaceState.pendingMirrorRefDeletions.map(({ projectId }) => projectId)
|
|
3115
|
+
]);
|
|
3116
|
+
for (const projectId of pendingCredentialProjectIds) {
|
|
3117
|
+
const desiredProject = desiredProjectConfigById.get(projectId);
|
|
3118
|
+
if (desiredProject) projectConnection(desiredProject);
|
|
3119
|
+
else projectMirrorConnection(projectId);
|
|
3120
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(projectId));
|
|
3121
|
+
}
|
|
3122
|
+
for (const deletion of pendingMirrorDeletes.values()) {
|
|
3123
|
+
const desiredProject = desiredProjectConfigById.get(deletion.projectId);
|
|
3124
|
+
const connection = desiredProject ? projectConnection(desiredProject) : projectMirrorConnection(deletion.projectId);
|
|
3125
|
+
deletion.mirrorUrl = connection.mirrorUrl;
|
|
3126
|
+
deletion.credentialHelper = connection.credentialHelper;
|
|
3127
|
+
deletion.credentialUsername = connection.mirrorCredentialUsername;
|
|
3128
|
+
}
|
|
3129
|
+
pruneCredentialStoreFiles(requiredCredentialStorePaths);
|
|
2952
3130
|
configureGitHubAuth(message.githubCredential);
|
|
2953
3131
|
visibleGitIdentity = message.gitIdentity;
|
|
2954
3132
|
workspaceGitIdentity = message.gitIdentity;
|
|
2955
3133
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
});
|
|
3134
|
+
workspaceCredentialHelper = nextWorkspaceCredentialHelper;
|
|
3135
|
+
workspaceCredentialUsername = workerCredentialUsername;
|
|
2959
3136
|
pruneAuthoritativelyDesiredBranchDeletions(pendingMirrorDeletes, message.projects);
|
|
2960
3137
|
const effectiveProjects = effectiveWorkerProjects(message.projects).map((project) => ({
|
|
2961
3138
|
...project,
|
|
@@ -2986,13 +3163,11 @@ async function startWorker(options) {
|
|
|
2986
3163
|
for (const projectId of [...readyProjectIds]) {
|
|
2987
3164
|
if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
|
|
2988
3165
|
}
|
|
2989
|
-
const remoteAuth = gitAuthForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
2990
|
-
const credentialHelper = credentialHelperForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
2991
3166
|
ensureWorkspaceGitClone({
|
|
2992
3167
|
workspacePath: workspaceShadowRoot,
|
|
2993
3168
|
remoteUrl: message.workspaceRemoteUrl,
|
|
2994
|
-
|
|
2995
|
-
|
|
3169
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
3170
|
+
credentialUsername: workerCredentialUsername,
|
|
2996
3171
|
gitIdentity: message.gitIdentity
|
|
2997
3172
|
});
|
|
2998
3173
|
pruneStaleOuterWorkspaceEntries(projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot));
|
|
@@ -3791,5 +3966,6 @@ export {
|
|
|
3791
3966
|
resolveWorkerFilePath,
|
|
3792
3967
|
resolveWorkerSessionTarget,
|
|
3793
3968
|
syncSessionArtifacts,
|
|
3969
|
+
workerGitSecurityTestHarness,
|
|
3794
3970
|
writeWorkerTextFile
|
|
3795
3971
|
};
|
package/dist/mjs/package.json
CHANGED