@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/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());
|
|
@@ -2290,6 +2383,15 @@ async function startWorker(options) {
|
|
|
2290
2383
|
`);
|
|
2291
2384
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
2292
2385
|
`);
|
|
2386
|
+
const snapshotCleanup = (0, import_project_worktrees.cleanupStaleProjectWorktreeSnapshots)();
|
|
2387
|
+
if (snapshotCleanup.removed.length > 0) {
|
|
2388
|
+
process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
|
|
2389
|
+
`);
|
|
2390
|
+
}
|
|
2391
|
+
for (const failure of snapshotCleanup.failed) {
|
|
2392
|
+
process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
|
|
2393
|
+
`);
|
|
2394
|
+
}
|
|
2293
2395
|
runGit(["--version"]);
|
|
2294
2396
|
await verifyR5dctlAuth(baseUrl, token);
|
|
2295
2397
|
import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
|
|
@@ -2308,6 +2410,8 @@ async function startWorker(options) {
|
|
|
2308
2410
|
const lastObservedProjectHeads = /* @__PURE__ */ new Map();
|
|
2309
2411
|
const pendingMirrorDeletes = /* @__PURE__ */ new Map();
|
|
2310
2412
|
let workspaceRemoteUrl = null;
|
|
2413
|
+
let workspaceCredentialHelper = null;
|
|
2414
|
+
let workspaceCredentialUsername = null;
|
|
2311
2415
|
let workspaceGitIdentity = null;
|
|
2312
2416
|
let workspaceConfigured = false;
|
|
2313
2417
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2322,6 +2426,7 @@ async function startWorker(options) {
|
|
|
2322
2426
|
let reloadAfterClose = false;
|
|
2323
2427
|
let shutdownAfterClose = false;
|
|
2324
2428
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
2429
|
+
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
2325
2430
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2326
2431
|
const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
|
|
2327
2432
|
const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
|
|
@@ -2329,12 +2434,32 @@ async function startWorker(options) {
|
|
|
2329
2434
|
const workspacePlanRelativePath = (projectId, branchName) => import_node_path.default.posix.join("plans", projectId, encodeURIComponent(branchName));
|
|
2330
2435
|
const projectConnection = (project) => {
|
|
2331
2436
|
const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, project.projectId);
|
|
2332
|
-
const mirrorAuth = gitAuthForRemote(mirrorUrl, bearerAuthHeader);
|
|
2333
2437
|
const originUrl = project.repoHttpUrl ?? mirrorUrl;
|
|
2334
2438
|
const originHeader = project.repoHttpUrl ? project.repoAuthHeader : bearerAuthHeader;
|
|
2335
|
-
const
|
|
2336
|
-
|
|
2337
|
-
|
|
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 };
|
|
2338
2463
|
};
|
|
2339
2464
|
const durableDeletionForBranch = (projectId, checkoutPathSegments, branchName) => projectWorkspaceState.tombstones.find(
|
|
2340
2465
|
(tombstone) => tombstone.projectId === projectId && tombstone.checkoutPathSegments[0] === checkoutPathSegments[0] && tombstone.checkoutPathSegments[1] === checkoutPathSegments[1] && (tombstone.kind === "project" || tombstone.branchName === branchName)
|
|
@@ -2358,7 +2483,8 @@ async function startWorker(options) {
|
|
|
2358
2483
|
branchName,
|
|
2359
2484
|
gitDirectory: configuredProjectBranchPath(projectsRoot, project, primaryBranchName),
|
|
2360
2485
|
mirrorUrl: connection.mirrorUrl,
|
|
2361
|
-
|
|
2486
|
+
credentialHelper: connection.credentialHelper,
|
|
2487
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2362
2488
|
sourcePath,
|
|
2363
2489
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branchName),
|
|
2364
2490
|
planSourcePath,
|
|
@@ -2369,7 +2495,7 @@ async function startWorker(options) {
|
|
|
2369
2495
|
};
|
|
2370
2496
|
const stageProjectDeletion = (project) => {
|
|
2371
2497
|
const projectRoot = configuredProjectRoot(projectsRoot, project);
|
|
2372
|
-
const connection =
|
|
2498
|
+
const connection = projectMirrorConnection(project.projectId);
|
|
2373
2499
|
const branches = project.branches.map(({ branchName }) => ({
|
|
2374
2500
|
branchName,
|
|
2375
2501
|
sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
|
|
@@ -2390,7 +2516,8 @@ async function startWorker(options) {
|
|
|
2390
2516
|
// hidden ref only after that publication succeeds.
|
|
2391
2517
|
gitDirectory: workspaceShadowRoot,
|
|
2392
2518
|
mirrorUrl: connection.mirrorUrl,
|
|
2393
|
-
|
|
2519
|
+
credentialHelper: connection.credentialHelper,
|
|
2520
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2394
2521
|
sourcePath: branch.sourcePath,
|
|
2395
2522
|
workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
|
|
2396
2523
|
planSourcePath: branch.planSourcePath,
|
|
@@ -2448,14 +2575,17 @@ async function startWorker(options) {
|
|
|
2448
2575
|
for (const deletion of projectWorkspaceState.pendingMirrorRefDeletions) {
|
|
2449
2576
|
const key = projectBranchKey(deletion.projectId, deletion.branchName);
|
|
2450
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);
|
|
2451
2580
|
import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
|
|
2452
2581
|
pendingMirrorDeletes.set(key, {
|
|
2453
2582
|
id: projectMountId(deletion.projectId, deletion.branchName),
|
|
2454
2583
|
projectId: deletion.projectId,
|
|
2455
2584
|
branchName: deletion.branchName,
|
|
2456
2585
|
gitDirectory: workspaceShadowRoot,
|
|
2457
|
-
mirrorUrl:
|
|
2458
|
-
|
|
2586
|
+
mirrorUrl: connection.mirrorUrl,
|
|
2587
|
+
credentialHelper: connection.credentialHelper,
|
|
2588
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2459
2589
|
sourcePath: deletion.managedPath,
|
|
2460
2590
|
workspaceRelativePath: workspaceProjectRelativePath(deletion.projectId, deletion.branchName),
|
|
2461
2591
|
planSourcePath,
|
|
@@ -2603,9 +2733,9 @@ async function startWorker(options) {
|
|
|
2603
2733
|
primaryBranchName: primaryProjectBranch(project),
|
|
2604
2734
|
branches: project.branches,
|
|
2605
2735
|
originUrl: connection.originUrl,
|
|
2606
|
-
|
|
2736
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2607
2737
|
mirrorUrl: connection.mirrorUrl,
|
|
2608
|
-
|
|
2738
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2609
2739
|
credentialHelper: connection.credentialHelper,
|
|
2610
2740
|
gitIdentity: workspaceGitIdentity
|
|
2611
2741
|
});
|
|
@@ -2644,10 +2774,17 @@ async function startWorker(options) {
|
|
|
2644
2774
|
if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
2645
2775
|
const connection = projectConnection(project);
|
|
2646
2776
|
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
2647
|
-
if (!tryGit(
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
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
|
+
)) {
|
|
2651
2788
|
process.stderr.write(`[r5d-worker] could not refresh origin divergence for ${project.projectPath}
|
|
2652
2789
|
`);
|
|
2653
2790
|
}
|
|
@@ -2697,7 +2834,8 @@ async function startWorker(options) {
|
|
|
2697
2834
|
projectRoot: configuredProjectRoot(projectsRoot, project),
|
|
2698
2835
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2699
2836
|
mirrorUrl: connection.mirrorUrl,
|
|
2700
|
-
|
|
2837
|
+
credentialHelper: connection.credentialHelper,
|
|
2838
|
+
credentialUsername: connection.mirrorCredentialUsername,
|
|
2701
2839
|
onlyBranches: changed
|
|
2702
2840
|
});
|
|
2703
2841
|
for (const result of results) {
|
|
@@ -2724,7 +2862,8 @@ async function startWorker(options) {
|
|
|
2724
2862
|
gitDirectory: deletion.gitDirectory,
|
|
2725
2863
|
branchName: deletion.branchName,
|
|
2726
2864
|
mirrorUrl: deletion.mirrorUrl,
|
|
2727
|
-
|
|
2865
|
+
credentialHelper: deletion.credentialHelper,
|
|
2866
|
+
credentialUsername: deletion.credentialUsername
|
|
2728
2867
|
});
|
|
2729
2868
|
}
|
|
2730
2869
|
if (deletion.tombstoneId && !deletion.projectDeleted) {
|
|
@@ -2734,6 +2873,9 @@ async function startWorker(options) {
|
|
|
2734
2873
|
});
|
|
2735
2874
|
}
|
|
2736
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
|
+
}
|
|
2737
2879
|
lastObservedProjectHeads.delete(key);
|
|
2738
2880
|
}
|
|
2739
2881
|
};
|
|
@@ -2747,7 +2889,8 @@ async function startWorker(options) {
|
|
|
2747
2889
|
primaryBranchName: primaryProjectBranch(project),
|
|
2748
2890
|
branchNames: project.branches.map(({ branchName }) => branchName),
|
|
2749
2891
|
mirrorUrl: connection.mirrorUrl,
|
|
2750
|
-
|
|
2892
|
+
credentialHelper: connection.credentialHelper,
|
|
2893
|
+
credentialUsername: connection.mirrorCredentialUsername
|
|
2751
2894
|
});
|
|
2752
2895
|
for (const state of refreshed) {
|
|
2753
2896
|
lastObservedProjectHeads.set(
|
|
@@ -2772,9 +2915,9 @@ async function startWorker(options) {
|
|
|
2772
2915
|
primaryBranchName: primaryProjectBranch(project),
|
|
2773
2916
|
branches: project.branches,
|
|
2774
2917
|
originUrl: connection.originUrl,
|
|
2775
|
-
|
|
2918
|
+
originCredentialUsername: connection.originCredentialUsername,
|
|
2776
2919
|
mirrorUrl: connection.mirrorUrl,
|
|
2777
|
-
|
|
2920
|
+
mirrorCredentialUsername: connection.mirrorCredentialUsername,
|
|
2778
2921
|
credentialHelper: connection.credentialHelper,
|
|
2779
2922
|
gitIdentity: workspaceGitIdentity
|
|
2780
2923
|
});
|
|
@@ -2821,16 +2964,16 @@ async function startWorker(options) {
|
|
|
2821
2964
|
error: error instanceof Error ? error.message : String(error)
|
|
2822
2965
|
});
|
|
2823
2966
|
const performWorkspaceSync = async (input) => {
|
|
2824
|
-
if (!workspaceRemoteUrl || !
|
|
2967
|
+
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
2968
|
+
throw new Error("Worker workspace configuration has not been received");
|
|
2969
|
+
}
|
|
2825
2970
|
const mounts = buildWorkspaceMounts();
|
|
2826
|
-
const remoteAuth = gitAuthForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2827
|
-
const credentialHelper = credentialHelperForRemote(workspaceRemoteUrl, bearerAuthHeader);
|
|
2828
2971
|
if (input.resetToCanonical) {
|
|
2829
2972
|
const reset = (0, import_workspace_git_sync.resetWorkspaceGit)({
|
|
2830
2973
|
workspacePath: workspaceShadowRoot,
|
|
2831
2974
|
remoteUrl: workspaceRemoteUrl,
|
|
2832
|
-
|
|
2833
|
-
|
|
2975
|
+
credentialHelper: workspaceCredentialHelper,
|
|
2976
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2834
2977
|
gitIdentity: workspaceGitIdentity,
|
|
2835
2978
|
mounts
|
|
2836
2979
|
});
|
|
@@ -2859,8 +3002,8 @@ async function startWorker(options) {
|
|
|
2859
3002
|
workerLabel: label,
|
|
2860
3003
|
workspacePath: workspaceShadowRoot,
|
|
2861
3004
|
remoteUrl: workspaceRemoteUrl,
|
|
2862
|
-
|
|
2863
|
-
|
|
3005
|
+
credentialHelper: workspaceCredentialHelper,
|
|
3006
|
+
credentialUsername: workspaceCredentialUsername,
|
|
2864
3007
|
gitIdentity: workspaceGitIdentity,
|
|
2865
3008
|
mounts,
|
|
2866
3009
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
@@ -2973,13 +3116,47 @@ async function startWorker(options) {
|
|
|
2973
3116
|
try {
|
|
2974
3117
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2975
3118
|
workspaceConfigured = false;
|
|
3119
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
3120
|
+
desiredProjects: message.projects
|
|
3121
|
+
});
|
|
3122
|
+
const requiredCredentialStorePaths = /* @__PURE__ */ new Set();
|
|
3123
|
+
const workspaceStorePath = credentialStorePathForWorkspace(message.workspaceRemoteUrl);
|
|
3124
|
+
requiredCredentialStorePaths.add(workspaceStorePath);
|
|
3125
|
+
const nextWorkspaceCredentialHelper = credentialHelperForRemotes(
|
|
3126
|
+
[{ remoteUrl: message.workspaceRemoteUrl, authHeader: bearerAuthHeader }],
|
|
3127
|
+
workspaceStorePath
|
|
3128
|
+
);
|
|
3129
|
+
if (!nextWorkspaceCredentialHelper) throw new Error("Workspace credentials are unavailable");
|
|
3130
|
+
const desiredProjectConfigById = new Map(message.projects.map((project) => [project.projectId, project]));
|
|
3131
|
+
for (const project of message.projects) {
|
|
3132
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(project.projectId));
|
|
3133
|
+
projectConnection(project);
|
|
3134
|
+
}
|
|
3135
|
+
const pendingCredentialProjectIds = /* @__PURE__ */ new Set([
|
|
3136
|
+
...[...pendingMirrorDeletes.values()].map(({ projectId }) => projectId),
|
|
3137
|
+
...projectWorkspaceState.pendingTreeDeletions.map(({ projectId }) => projectId),
|
|
3138
|
+
...projectWorkspaceState.pendingMirrorRefDeletions.map(({ projectId }) => projectId)
|
|
3139
|
+
]);
|
|
3140
|
+
for (const projectId of pendingCredentialProjectIds) {
|
|
3141
|
+
const desiredProject = desiredProjectConfigById.get(projectId);
|
|
3142
|
+
if (desiredProject) projectConnection(desiredProject);
|
|
3143
|
+
else projectMirrorConnection(projectId);
|
|
3144
|
+
requiredCredentialStorePaths.add(credentialStorePathForProject(projectId));
|
|
3145
|
+
}
|
|
3146
|
+
for (const deletion of pendingMirrorDeletes.values()) {
|
|
3147
|
+
const desiredProject = desiredProjectConfigById.get(deletion.projectId);
|
|
3148
|
+
const connection = desiredProject ? projectConnection(desiredProject) : projectMirrorConnection(deletion.projectId);
|
|
3149
|
+
deletion.mirrorUrl = connection.mirrorUrl;
|
|
3150
|
+
deletion.credentialHelper = connection.credentialHelper;
|
|
3151
|
+
deletion.credentialUsername = connection.mirrorCredentialUsername;
|
|
3152
|
+
}
|
|
3153
|
+
pruneCredentialStoreFiles(requiredCredentialStorePaths);
|
|
2976
3154
|
configureGitHubAuth(message.githubCredential);
|
|
2977
3155
|
visibleGitIdentity = message.gitIdentity;
|
|
2978
3156
|
workspaceGitIdentity = message.gitIdentity;
|
|
2979
3157
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
});
|
|
3158
|
+
workspaceCredentialHelper = nextWorkspaceCredentialHelper;
|
|
3159
|
+
workspaceCredentialUsername = workerCredentialUsername;
|
|
2983
3160
|
(0, import_project_workspace_state.pruneAuthoritativelyDesiredBranchDeletions)(pendingMirrorDeletes, message.projects);
|
|
2984
3161
|
const effectiveProjects = effectiveWorkerProjects(message.projects).map((project) => ({
|
|
2985
3162
|
...project,
|
|
@@ -3010,13 +3187,11 @@ async function startWorker(options) {
|
|
|
3010
3187
|
for (const projectId of [...readyProjectIds]) {
|
|
3011
3188
|
if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
|
|
3012
3189
|
}
|
|
3013
|
-
const remoteAuth = gitAuthForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3014
|
-
const credentialHelper = credentialHelperForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
|
|
3015
3190
|
(0, import_workspace_git_sync.ensureWorkspaceGitClone)({
|
|
3016
3191
|
workspacePath: workspaceShadowRoot,
|
|
3017
3192
|
remoteUrl: message.workspaceRemoteUrl,
|
|
3018
|
-
|
|
3019
|
-
|
|
3193
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
3194
|
+
credentialUsername: workerCredentialUsername,
|
|
3020
3195
|
gitIdentity: message.gitIdentity
|
|
3021
3196
|
});
|
|
3022
3197
|
pruneStaleOuterWorkspaceEntries(projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot));
|
|
@@ -3816,5 +3991,6 @@ if (isCliEntrypoint()) {
|
|
|
3816
3991
|
resolveWorkerFilePath,
|
|
3817
3992
|
resolveWorkerSessionTarget,
|
|
3818
3993
|
syncSessionArtifacts,
|
|
3994
|
+
workerGitSecurityTestHarness,
|
|
3819
3995
|
writeWorkerTextFile
|
|
3820
3996
|
});
|
package/dist/cjs/package.json
CHANGED