@ricsam/r5d-worker 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ r5d-worker --version
10
10
  The worker uses the existing `r5dctl` config file and accepts `R5D_WORKER_TOKEN` or `R5D_API_KEY`. Project checkouts are managed under:
11
11
 
12
12
  ```text
13
- ~/.r5d/projects/<repo-slug>/<branch-name>
13
+ ~/.r5d/projects/<namespace-repo>/<branch-name>
14
14
  ```
15
15
 
16
16
  Visible branch checkouts use GitHub `origin` remotes. r5d internal sync git metadata lives separately under `~/.r5d/sync`.
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var git_identity_exports = {};
20
+ __export(git_identity_exports, {
21
+ configureVisibleGitIdentity: () => configureVisibleGitIdentity
22
+ });
23
+ module.exports = __toCommonJS(git_identity_exports);
24
+ function configureVisibleGitIdentity(branchPath, identity, runGit) {
25
+ if (!identity) {
26
+ return;
27
+ }
28
+ const name = identity.name.trim();
29
+ const email = identity.email.trim();
30
+ if (!name || !email) {
31
+ throw new Error("Git identity must include name and email");
32
+ }
33
+ runGit(["config", "user.name", name], { cwd: branchPath });
34
+ runGit(["config", "user.email", email], { cwd: branchPath });
35
+ }
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ configureVisibleGitIdentity
39
+ });
package/dist/cjs/main.cjs CHANGED
@@ -28,6 +28,7 @@ var import_node_path = __toESM(require("node:path"), 1);
28
28
  var import_node_crypto = require("node:crypto");
29
29
  var import_node_os2 = require("node:os");
30
30
  var import_node_child_process = require("node:child_process");
31
+ var import_git_identity = require("./git-identity.cjs");
31
32
  const DEFAULT_BASE_URL = "https://r5d.dev";
32
33
  const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
33
34
  const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
@@ -35,10 +36,13 @@ const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
35
36
  const DEFAULT_READ_LIMIT = 2e3;
36
37
  const MAX_LINE_LENGTH = 2e3;
37
38
  const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
39
+ const R5D_REMOTE_NAME = "r5d";
40
+ const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
38
41
  const activeProcesses = /* @__PURE__ */ new Map();
39
42
  const activePtys = /* @__PURE__ */ new Map();
40
43
  const branchLocks = /* @__PURE__ */ new Map();
41
44
  let containerRegistryCredential = null;
45
+ let visibleGitIdentity = null;
42
46
  function defaultConfigPath() {
43
47
  return import_node_path.default.join(import_node_os.default.homedir(), ".config", "r5d", "r5dctl", "config.json");
44
48
  }
@@ -93,7 +97,7 @@ function getWorkerVersion() {
93
97
  return packageVersion;
94
98
  }
95
99
  const sourceVersion = readVersionFile(import_node_path.default.join(current, "VERSION.txt"));
96
- if (sourceVersion && import_node_path.default.basename(current) === "binctl-packages") {
100
+ if (sourceVersion && import_node_path.default.basename(current) === "packages") {
97
101
  return sourceVersion;
98
102
  }
99
103
  const parent = import_node_path.default.dirname(current);
@@ -219,8 +223,8 @@ async function readResponseText(response) {
219
223
  return "";
220
224
  }
221
225
  }
222
- async function verifyBinctlAuth(baseUrl, token) {
223
- const statusUrl = new URL("/api/binctl/auth/status", baseUrl);
226
+ async function verifyR5dctlAuth(baseUrl, token) {
227
+ const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
224
228
  let response;
225
229
  try {
226
230
  response = await fetch(statusUrl, {
@@ -249,12 +253,12 @@ async function verifyBinctlAuth(baseUrl, token) {
249
253
  );
250
254
  }
251
255
  function resolveCredentials(options, config) {
252
- const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.BINCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.BINCTL_API_KEY ?? config.apiKey;
256
+ const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
253
257
  if (!token) {
254
258
  throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
255
259
  }
256
260
  return {
257
- baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.BINCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
261
+ baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
258
262
  token
259
263
  };
260
264
  }
@@ -601,15 +605,19 @@ function ensureVisibleGitCheckout(input) {
601
605
  }
602
606
  ensureOriginRemote(branchPath, input.githubRemoteUrl);
603
607
  configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
608
+ (0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
604
609
  tryGit(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
605
610
  checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
606
611
  return branchPath;
607
612
  }
608
613
  function configureInternalRepo(context, remoteUrl) {
609
- if (tryInternalGit(context, ["remote", "get-url", "build-it-now"])) {
610
- runInternalGit(context, ["remote", "set-url", "build-it-now", remoteUrl]);
614
+ if (tryInternalGit(context, ["remote", "get-url", R5D_REMOTE_NAME])) {
615
+ runInternalGit(context, ["remote", "set-url", R5D_REMOTE_NAME, remoteUrl]);
611
616
  } else {
612
- runInternalGit(context, ["remote", "add", "build-it-now", remoteUrl]);
617
+ runInternalGit(context, ["remote", "add", R5D_REMOTE_NAME, remoteUrl]);
618
+ }
619
+ if (tryInternalGit(context, ["remote", "get-url", LEGACY_BUILD_IT_NOW_REMOTE_NAME])) {
620
+ runInternalGit(context, ["remote", "remove", LEGACY_BUILD_IT_NOW_REMOTE_NAME]);
613
621
  }
614
622
  runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
615
623
  runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
@@ -625,9 +633,9 @@ function ensureInternalSyncGit(input) {
625
633
  const context = { gitDir, workTree: input.branchPath, auth };
626
634
  const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
627
635
  configureInternalRepo(context, remoteUrl);
628
- runInternalGit(context, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
629
- const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/build-it-now/${input.branchName}`]);
630
- const target = hasRemoteBranch ? `refs/remotes/build-it-now/${input.branchName}` : "refs/remotes/build-it-now/main";
636
+ runInternalGit(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
637
+ const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`]);
638
+ const target = hasRemoteBranch ? `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}` : `refs/remotes/${R5D_REMOTE_NAME}/main`;
631
639
  const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
632
640
  if (!hasHead || !hasInternalWorktreeChanges(context)) {
633
641
  runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
@@ -677,6 +685,31 @@ async function collectStream(stream) {
677
685
  }
678
686
  return await new Response(stream).text();
679
687
  }
688
+ async function streamCommandOutput(stream, onData) {
689
+ if (!stream) {
690
+ return;
691
+ }
692
+ const reader = stream.getReader();
693
+ const decoder = new TextDecoder();
694
+ try {
695
+ while (true) {
696
+ const chunk = await reader.read();
697
+ if (chunk.done) {
698
+ break;
699
+ }
700
+ const text = decoder.decode(chunk.value, { stream: true });
701
+ if (text.length > 0) {
702
+ onData(text);
703
+ }
704
+ }
705
+ const trailing = decoder.decode();
706
+ if (trailing.length > 0) {
707
+ onData(trailing);
708
+ }
709
+ } finally {
710
+ reader.releaseLock();
711
+ }
712
+ }
680
713
  function ensureOperationBranch(input) {
681
714
  return ensureBranchWorkspace(input);
682
715
  }
@@ -896,7 +929,7 @@ function syncBranch(input) {
896
929
  });
897
930
  runInternalGit(workspace.internalGit, ["commit", "-m", message]);
898
931
  const commitHash = getInternalCommitHash(workspace.internalGit);
899
- runInternalGit(workspace.internalGit, ["push", "build-it-now", `HEAD:refs/heads/${input.branchName}`]);
932
+ runInternalGit(workspace.internalGit, ["push", R5D_REMOTE_NAME, `HEAD:refs/heads/${input.branchName}`]);
900
933
  return {
901
934
  type: "sync",
902
935
  changed: true,
@@ -927,8 +960,8 @@ function confirmLargeDiff(input) {
927
960
  }
928
961
  function pullBranch(input) {
929
962
  const workspace = ensureOperationBranch(input);
930
- runInternalGit(workspace.internalGit, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
931
- const target = input.commitHash ?? `build-it-now/${input.branchName}`;
963
+ runInternalGit(workspace.internalGit, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
964
+ const target = input.commitHash ?? `${R5D_REMOTE_NAME}/${input.branchName}`;
932
965
  runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
933
966
  runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
934
967
  return {
@@ -1045,6 +1078,95 @@ async function executeCommand(input) {
1045
1078
  activeProcesses.delete(input.message.runId);
1046
1079
  }
1047
1080
  }
1081
+ async function executeStreamingCommand(input) {
1082
+ const startedAt = Date.now();
1083
+ let timeout;
1084
+ let started = false;
1085
+ let timedOut = false;
1086
+ try {
1087
+ const branchPath = ensureBranchWorkspace({
1088
+ projectId: input.projectId,
1089
+ baseUrl: input.baseUrl,
1090
+ token: input.token,
1091
+ projectRoot: input.projectRoot,
1092
+ syncRoot: input.syncRoot,
1093
+ branchName: input.message.branchName,
1094
+ manifest: input.manifest
1095
+ });
1096
+ const cwd = resolveCommandCwd(branchPath.branchPath, input.message.cwd);
1097
+ const subprocess = Bun.spawn(input.message.argv, {
1098
+ cwd,
1099
+ stdout: "pipe",
1100
+ stderr: "pipe",
1101
+ env: {
1102
+ ...process.env,
1103
+ ...containerRegistryEnv(),
1104
+ ...input.message.env ?? {}
1105
+ }
1106
+ });
1107
+ activeProcesses.set(input.message.runId, { process: subprocess, command: input.message.argv });
1108
+ started = true;
1109
+ sendWorkerMessage(input.ws, {
1110
+ type: "exec_started",
1111
+ requestId: input.message.requestId,
1112
+ runId: input.message.runId
1113
+ });
1114
+ if (input.message.timeoutMs) {
1115
+ timeout = setTimeout(() => {
1116
+ timedOut = true;
1117
+ subprocess.kill("SIGTERM");
1118
+ }, input.message.timeoutMs);
1119
+ }
1120
+ const [exitCode] = await Promise.all([
1121
+ subprocess.exited,
1122
+ streamCommandOutput(subprocess.stdout, (data) => {
1123
+ sendWorkerMessage(input.ws, {
1124
+ type: "exec_output",
1125
+ runId: input.message.runId,
1126
+ stream: "stdout",
1127
+ data
1128
+ });
1129
+ }),
1130
+ streamCommandOutput(subprocess.stderr, (data) => {
1131
+ sendWorkerMessage(input.ws, {
1132
+ type: "exec_output",
1133
+ runId: input.message.runId,
1134
+ stream: "stderr",
1135
+ data
1136
+ });
1137
+ })
1138
+ ]);
1139
+ sendWorkerMessage(input.ws, {
1140
+ type: "exec_exit",
1141
+ runId: input.message.runId,
1142
+ exitCode,
1143
+ durationMs: Date.now() - startedAt,
1144
+ ...timedOut ? { timedOut: true } : {}
1145
+ });
1146
+ } catch (error) {
1147
+ const message = error instanceof Error ? error.message : String(error);
1148
+ if (started) {
1149
+ sendWorkerMessage(input.ws, {
1150
+ type: "exec_error",
1151
+ runId: input.message.runId,
1152
+ error: message,
1153
+ durationMs: Date.now() - startedAt
1154
+ });
1155
+ } else {
1156
+ sendWorkerMessage(input.ws, {
1157
+ type: "exec_start_error",
1158
+ requestId: input.message.requestId,
1159
+ runId: input.message.runId,
1160
+ error: message
1161
+ });
1162
+ }
1163
+ } finally {
1164
+ if (timeout) {
1165
+ clearTimeout(timeout);
1166
+ }
1167
+ activeProcesses.delete(input.message.runId);
1168
+ }
1169
+ }
1048
1170
  function sendWorkerMessage(ws, message) {
1049
1171
  ws.send(JSON.stringify(message));
1050
1172
  }
@@ -1254,8 +1376,8 @@ function openPty(input) {
1254
1376
  ...process.env,
1255
1377
  ...containerRegistryEnv(),
1256
1378
  ...input.message.env ?? {},
1257
- BUILD_IT_NOW_PROJECT_ID: input.projectId,
1258
- BUILD_IT_NOW_BRANCH_NAME: input.message.branchName
1379
+ R5D_PROJECT_ID: input.projectId,
1380
+ R5D_BRANCH_NAME: input.message.branchName
1259
1381
  }
1260
1382
  },
1261
1383
  onOpened: () => {
@@ -1362,7 +1484,7 @@ async function startWorker(options) {
1362
1484
  process.stdout.write(`[r5d-worker] server: ${baseUrl}
1363
1485
  `);
1364
1486
  runGit(["--version"]);
1365
- await verifyBinctlAuth(baseUrl, token);
1487
+ await verifyR5dctlAuth(baseUrl, token);
1366
1488
  import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
1367
1489
  import_node_fs.default.mkdirSync(syncRoot, { recursive: true });
1368
1490
  const manifestByProjectId = /* @__PURE__ */ new Map();
@@ -1394,6 +1516,7 @@ async function startWorker(options) {
1394
1516
  }
1395
1517
  if (message.type === "project_manifest") {
1396
1518
  configureContainerRegistryAuth(message.githubContainerRegistry);
1519
+ visibleGitIdentity = message.gitIdentity;
1397
1520
  manifestByProjectId.clear();
1398
1521
  for (const project of message.projects) {
1399
1522
  manifestByProjectId.set(project.projectId, project);
@@ -1479,6 +1602,34 @@ async function startWorker(options) {
1479
1602
  ws.send(JSON.stringify(result));
1480
1603
  return;
1481
1604
  }
1605
+ if (message.type === "exec_start") {
1606
+ try {
1607
+ const manifest = manifestByProjectId.get(message.projectId);
1608
+ const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
1609
+ process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
1610
+ `);
1611
+ void withBranchLock(
1612
+ message.projectId,
1613
+ message.branchName,
1614
+ () => executeStreamingCommand({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
1615
+ ).catch((error) => {
1616
+ sendWorkerMessage(ws, {
1617
+ type: "exec_start_error",
1618
+ requestId: message.requestId,
1619
+ runId: message.runId,
1620
+ error: error instanceof Error ? error.message : String(error)
1621
+ });
1622
+ });
1623
+ } catch (error) {
1624
+ sendWorkerMessage(ws, {
1625
+ type: "exec_start_error",
1626
+ requestId: message.requestId,
1627
+ runId: message.runId,
1628
+ error: error instanceof Error ? error.message : String(error)
1629
+ });
1630
+ }
1631
+ return;
1632
+ }
1482
1633
  if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
1483
1634
  try {
1484
1635
  const manifest = manifestByProjectId.get(message.projectId);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,15 @@
1
+ function configureVisibleGitIdentity(branchPath, identity, runGit) {
2
+ if (!identity) {
3
+ return;
4
+ }
5
+ const name = identity.name.trim();
6
+ const email = identity.email.trim();
7
+ if (!name || !email) {
8
+ throw new Error("Git identity must include name and email");
9
+ }
10
+ runGit(["config", "user.name", name], { cwd: branchPath });
11
+ runGit(["config", "user.email", email], { cwd: branchPath });
12
+ }
13
+ export {
14
+ configureVisibleGitIdentity
15
+ };
package/dist/mjs/main.mjs CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { createHash } from "node:crypto";
6
6
  import { hostname } from "node:os";
7
7
  import { spawn as spawnChildProcess } from "node:child_process";
8
+ import { configureVisibleGitIdentity } from "./git-identity.mjs";
8
9
  const DEFAULT_BASE_URL = "https://r5d.dev";
9
10
  const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
10
11
  const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
@@ -12,10 +13,13 @@ const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
12
13
  const DEFAULT_READ_LIMIT = 2e3;
13
14
  const MAX_LINE_LENGTH = 2e3;
14
15
  const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
16
+ const R5D_REMOTE_NAME = "r5d";
17
+ const LEGACY_BUILD_IT_NOW_REMOTE_NAME = "build-it-now";
15
18
  const activeProcesses = /* @__PURE__ */ new Map();
16
19
  const activePtys = /* @__PURE__ */ new Map();
17
20
  const branchLocks = /* @__PURE__ */ new Map();
18
21
  let containerRegistryCredential = null;
22
+ let visibleGitIdentity = null;
19
23
  function defaultConfigPath() {
20
24
  return path.join(os.homedir(), ".config", "r5d", "r5dctl", "config.json");
21
25
  }
@@ -70,7 +74,7 @@ function getWorkerVersion() {
70
74
  return packageVersion;
71
75
  }
72
76
  const sourceVersion = readVersionFile(path.join(current, "VERSION.txt"));
73
- if (sourceVersion && path.basename(current) === "binctl-packages") {
77
+ if (sourceVersion && path.basename(current) === "packages") {
74
78
  return sourceVersion;
75
79
  }
76
80
  const parent = path.dirname(current);
@@ -196,8 +200,8 @@ async function readResponseText(response) {
196
200
  return "";
197
201
  }
198
202
  }
199
- async function verifyBinctlAuth(baseUrl, token) {
200
- const statusUrl = new URL("/api/binctl/auth/status", baseUrl);
203
+ async function verifyR5dctlAuth(baseUrl, token) {
204
+ const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
201
205
  let response;
202
206
  try {
203
207
  response = await fetch(statusUrl, {
@@ -226,12 +230,12 @@ async function verifyBinctlAuth(baseUrl, token) {
226
230
  );
227
231
  }
228
232
  function resolveCredentials(options, config) {
229
- const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.BINCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.BINCTL_API_KEY ?? config.apiKey;
233
+ const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
230
234
  if (!token) {
231
235
  throw new Error("Authentication required. Run `r5dctl auth login` or set R5D_WORKER_TOKEN/R5D_API_KEY.");
232
236
  }
233
237
  return {
234
- baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.BINCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
238
+ baseUrl: normalizeBaseUrl(options.baseUrl ?? process.env.R5D_BASE_URL ?? process.env.R5DCTL_BASE_URL ?? config.baseUrl ?? DEFAULT_BASE_URL),
235
239
  token
236
240
  };
237
241
  }
@@ -578,15 +582,19 @@ function ensureVisibleGitCheckout(input) {
578
582
  }
579
583
  ensureOriginRemote(branchPath, input.githubRemoteUrl);
580
584
  configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
585
+ configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
581
586
  tryGit(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
582
587
  checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
583
588
  return branchPath;
584
589
  }
585
590
  function configureInternalRepo(context, remoteUrl) {
586
- if (tryInternalGit(context, ["remote", "get-url", "build-it-now"])) {
587
- runInternalGit(context, ["remote", "set-url", "build-it-now", remoteUrl]);
591
+ if (tryInternalGit(context, ["remote", "get-url", R5D_REMOTE_NAME])) {
592
+ runInternalGit(context, ["remote", "set-url", R5D_REMOTE_NAME, remoteUrl]);
588
593
  } else {
589
- runInternalGit(context, ["remote", "add", "build-it-now", remoteUrl]);
594
+ runInternalGit(context, ["remote", "add", R5D_REMOTE_NAME, remoteUrl]);
595
+ }
596
+ if (tryInternalGit(context, ["remote", "get-url", LEGACY_BUILD_IT_NOW_REMOTE_NAME])) {
597
+ runInternalGit(context, ["remote", "remove", LEGACY_BUILD_IT_NOW_REMOTE_NAME]);
590
598
  }
591
599
  runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
592
600
  runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
@@ -602,9 +610,9 @@ function ensureInternalSyncGit(input) {
602
610
  const context = { gitDir, workTree: input.branchPath, auth };
603
611
  const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
604
612
  configureInternalRepo(context, remoteUrl);
605
- runInternalGit(context, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
606
- const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/build-it-now/${input.branchName}`]);
607
- const target = hasRemoteBranch ? `refs/remotes/build-it-now/${input.branchName}` : "refs/remotes/build-it-now/main";
613
+ runInternalGit(context, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
614
+ const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}`]);
615
+ const target = hasRemoteBranch ? `refs/remotes/${R5D_REMOTE_NAME}/${input.branchName}` : `refs/remotes/${R5D_REMOTE_NAME}/main`;
608
616
  const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
609
617
  if (!hasHead || !hasInternalWorktreeChanges(context)) {
610
618
  runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
@@ -654,6 +662,31 @@ async function collectStream(stream) {
654
662
  }
655
663
  return await new Response(stream).text();
656
664
  }
665
+ async function streamCommandOutput(stream, onData) {
666
+ if (!stream) {
667
+ return;
668
+ }
669
+ const reader = stream.getReader();
670
+ const decoder = new TextDecoder();
671
+ try {
672
+ while (true) {
673
+ const chunk = await reader.read();
674
+ if (chunk.done) {
675
+ break;
676
+ }
677
+ const text = decoder.decode(chunk.value, { stream: true });
678
+ if (text.length > 0) {
679
+ onData(text);
680
+ }
681
+ }
682
+ const trailing = decoder.decode();
683
+ if (trailing.length > 0) {
684
+ onData(trailing);
685
+ }
686
+ } finally {
687
+ reader.releaseLock();
688
+ }
689
+ }
657
690
  function ensureOperationBranch(input) {
658
691
  return ensureBranchWorkspace(input);
659
692
  }
@@ -873,7 +906,7 @@ function syncBranch(input) {
873
906
  });
874
907
  runInternalGit(workspace.internalGit, ["commit", "-m", message]);
875
908
  const commitHash = getInternalCommitHash(workspace.internalGit);
876
- runInternalGit(workspace.internalGit, ["push", "build-it-now", `HEAD:refs/heads/${input.branchName}`]);
909
+ runInternalGit(workspace.internalGit, ["push", R5D_REMOTE_NAME, `HEAD:refs/heads/${input.branchName}`]);
877
910
  return {
878
911
  type: "sync",
879
912
  changed: true,
@@ -904,8 +937,8 @@ function confirmLargeDiff(input) {
904
937
  }
905
938
  function pullBranch(input) {
906
939
  const workspace = ensureOperationBranch(input);
907
- runInternalGit(workspace.internalGit, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
908
- const target = input.commitHash ?? `build-it-now/${input.branchName}`;
940
+ runInternalGit(workspace.internalGit, ["fetch", R5D_REMOTE_NAME, "--prune", `+refs/heads/*:refs/remotes/${R5D_REMOTE_NAME}/*`]);
941
+ const target = input.commitHash ?? `${R5D_REMOTE_NAME}/${input.branchName}`;
909
942
  runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
910
943
  runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
911
944
  return {
@@ -1022,6 +1055,95 @@ async function executeCommand(input) {
1022
1055
  activeProcesses.delete(input.message.runId);
1023
1056
  }
1024
1057
  }
1058
+ async function executeStreamingCommand(input) {
1059
+ const startedAt = Date.now();
1060
+ let timeout;
1061
+ let started = false;
1062
+ let timedOut = false;
1063
+ try {
1064
+ const branchPath = ensureBranchWorkspace({
1065
+ projectId: input.projectId,
1066
+ baseUrl: input.baseUrl,
1067
+ token: input.token,
1068
+ projectRoot: input.projectRoot,
1069
+ syncRoot: input.syncRoot,
1070
+ branchName: input.message.branchName,
1071
+ manifest: input.manifest
1072
+ });
1073
+ const cwd = resolveCommandCwd(branchPath.branchPath, input.message.cwd);
1074
+ const subprocess = Bun.spawn(input.message.argv, {
1075
+ cwd,
1076
+ stdout: "pipe",
1077
+ stderr: "pipe",
1078
+ env: {
1079
+ ...process.env,
1080
+ ...containerRegistryEnv(),
1081
+ ...input.message.env ?? {}
1082
+ }
1083
+ });
1084
+ activeProcesses.set(input.message.runId, { process: subprocess, command: input.message.argv });
1085
+ started = true;
1086
+ sendWorkerMessage(input.ws, {
1087
+ type: "exec_started",
1088
+ requestId: input.message.requestId,
1089
+ runId: input.message.runId
1090
+ });
1091
+ if (input.message.timeoutMs) {
1092
+ timeout = setTimeout(() => {
1093
+ timedOut = true;
1094
+ subprocess.kill("SIGTERM");
1095
+ }, input.message.timeoutMs);
1096
+ }
1097
+ const [exitCode] = await Promise.all([
1098
+ subprocess.exited,
1099
+ streamCommandOutput(subprocess.stdout, (data) => {
1100
+ sendWorkerMessage(input.ws, {
1101
+ type: "exec_output",
1102
+ runId: input.message.runId,
1103
+ stream: "stdout",
1104
+ data
1105
+ });
1106
+ }),
1107
+ streamCommandOutput(subprocess.stderr, (data) => {
1108
+ sendWorkerMessage(input.ws, {
1109
+ type: "exec_output",
1110
+ runId: input.message.runId,
1111
+ stream: "stderr",
1112
+ data
1113
+ });
1114
+ })
1115
+ ]);
1116
+ sendWorkerMessage(input.ws, {
1117
+ type: "exec_exit",
1118
+ runId: input.message.runId,
1119
+ exitCode,
1120
+ durationMs: Date.now() - startedAt,
1121
+ ...timedOut ? { timedOut: true } : {}
1122
+ });
1123
+ } catch (error) {
1124
+ const message = error instanceof Error ? error.message : String(error);
1125
+ if (started) {
1126
+ sendWorkerMessage(input.ws, {
1127
+ type: "exec_error",
1128
+ runId: input.message.runId,
1129
+ error: message,
1130
+ durationMs: Date.now() - startedAt
1131
+ });
1132
+ } else {
1133
+ sendWorkerMessage(input.ws, {
1134
+ type: "exec_start_error",
1135
+ requestId: input.message.requestId,
1136
+ runId: input.message.runId,
1137
+ error: message
1138
+ });
1139
+ }
1140
+ } finally {
1141
+ if (timeout) {
1142
+ clearTimeout(timeout);
1143
+ }
1144
+ activeProcesses.delete(input.message.runId);
1145
+ }
1146
+ }
1025
1147
  function sendWorkerMessage(ws, message) {
1026
1148
  ws.send(JSON.stringify(message));
1027
1149
  }
@@ -1231,8 +1353,8 @@ function openPty(input) {
1231
1353
  ...process.env,
1232
1354
  ...containerRegistryEnv(),
1233
1355
  ...input.message.env ?? {},
1234
- BUILD_IT_NOW_PROJECT_ID: input.projectId,
1235
- BUILD_IT_NOW_BRANCH_NAME: input.message.branchName
1356
+ R5D_PROJECT_ID: input.projectId,
1357
+ R5D_BRANCH_NAME: input.message.branchName
1236
1358
  }
1237
1359
  },
1238
1360
  onOpened: () => {
@@ -1339,7 +1461,7 @@ async function startWorker(options) {
1339
1461
  process.stdout.write(`[r5d-worker] server: ${baseUrl}
1340
1462
  `);
1341
1463
  runGit(["--version"]);
1342
- await verifyBinctlAuth(baseUrl, token);
1464
+ await verifyR5dctlAuth(baseUrl, token);
1343
1465
  fs.mkdirSync(projectsRoot, { recursive: true });
1344
1466
  fs.mkdirSync(syncRoot, { recursive: true });
1345
1467
  const manifestByProjectId = /* @__PURE__ */ new Map();
@@ -1371,6 +1493,7 @@ async function startWorker(options) {
1371
1493
  }
1372
1494
  if (message.type === "project_manifest") {
1373
1495
  configureContainerRegistryAuth(message.githubContainerRegistry);
1496
+ visibleGitIdentity = message.gitIdentity;
1374
1497
  manifestByProjectId.clear();
1375
1498
  for (const project of message.projects) {
1376
1499
  manifestByProjectId.set(project.projectId, project);
@@ -1456,6 +1579,34 @@ async function startWorker(options) {
1456
1579
  ws.send(JSON.stringify(result));
1457
1580
  return;
1458
1581
  }
1582
+ if (message.type === "exec_start") {
1583
+ try {
1584
+ const manifest = manifestByProjectId.get(message.projectId);
1585
+ const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
1586
+ process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
1587
+ `);
1588
+ void withBranchLock(
1589
+ message.projectId,
1590
+ message.branchName,
1591
+ () => executeStreamingCommand({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
1592
+ ).catch((error) => {
1593
+ sendWorkerMessage(ws, {
1594
+ type: "exec_start_error",
1595
+ requestId: message.requestId,
1596
+ runId: message.runId,
1597
+ error: error instanceof Error ? error.message : String(error)
1598
+ });
1599
+ });
1600
+ } catch (error) {
1601
+ sendWorkerMessage(ws, {
1602
+ type: "exec_start_error",
1603
+ requestId: message.requestId,
1604
+ runId: message.runId,
1605
+ error: error instanceof Error ? error.message : String(error)
1606
+ });
1607
+ }
1608
+ return;
1609
+ }
1459
1610
  if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
1460
1611
  try {
1461
1612
  const manifest = manifestByProjectId.get(message.projectId);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module"
5
5
  }
@@ -0,0 +1,9 @@
1
+ export type WorkerGitIdentity = {
2
+ name: string;
3
+ email: string;
4
+ };
5
+ type RunGit = (args: string[], options?: {
6
+ cwd?: string;
7
+ }) => string | void;
8
+ export declare function configureVisibleGitIdentity(branchPath: string, identity: WorkerGitIdentity | null | undefined, runGit: RunGit): void;
9
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/main.cjs",
6
6
  "module": "./dist/mjs/main.mjs",
@@ -15,7 +15,7 @@
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/ricsam/r5d-dev.git",
18
- "directory": "binctl-packages/r5d-worker"
18
+ "directory": "packages/r5d-worker"
19
19
  },
20
20
  "bin": {
21
21
  "r5d-worker": "dist/cjs/main.cjs"