@tablation/crew 0.127.0 → 0.127.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +54 -19
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13253,7 +13253,7 @@ async function sandboxBuildTick(repos, deps, force = false) {
13253
13253
  }
13254
13254
 
13255
13255
  // src/doctor-checks.ts
13256
- import { execFileSync as execFileSync9 } from "child_process";
13256
+ import { execFileSync as execFileSync9, spawnSync as spawnSync8 } from "child_process";
13257
13257
  var PROBE_BRANCH = "crew-probe-branch";
13258
13258
  var realGit = (cwd, args, env) => {
13259
13259
  execFileSync9("git", args, { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
@@ -13386,6 +13386,21 @@ function managedRuntimeWarnings(kind, ship) {
13386
13386
  }
13387
13387
  return out;
13388
13388
  }
13389
+ var MANAGED_GIT_EMAIL = "crew@tablation.dev";
13390
+ function readGlobalGitIdentity() {
13391
+ const get2 = (key) => {
13392
+ const res = spawnSync8("git", ["config", "--global", "--get", key], { encoding: "utf8" });
13393
+ const v = (res.stdout ?? "").trim();
13394
+ return res.status === 0 && v ? v : null;
13395
+ };
13396
+ return { name: get2("user.name"), email: get2("user.email") };
13397
+ }
13398
+ function managedGitIdentityWarnings(kind, identity) {
13399
+ if (kind !== "managed_instance" && kind !== "pooled_instance" && kind !== "pooled") return [];
13400
+ const missing = [identity.name ? "" : "user.name", identity.email ? "" : "user.email"].filter(Boolean);
13401
+ if (missing.length === 0) return [];
13402
+ return [`git ${missing.join(" and ")} not set for this user on a ${kind} ship; every commit crew makes here fails with "Committer identity unknown" (re-run \`crew provision\`, or \`git config --global user.name <ship name>\` and \`user.email ${MANAGED_GIT_EMAIL}\`)`];
13403
+ }
13389
13404
 
13390
13405
  // src/selftest.ts
13391
13406
  import { spawn as spawn6 } from "child_process";
@@ -13613,7 +13628,7 @@ function doctorSelftestLines(stored, toolchains) {
13613
13628
  import { resolve as resolve7, dirname as dirname34, join as join56, basename as basename16 } from "path";
13614
13629
  import { fileURLToPath as fileURLToPath2 } from "url";
13615
13630
  import { hostname as hostname5, homedir as homedir11 } from "os";
13616
- import { spawnSync as spawnSync11 } from "child_process";
13631
+ import { spawnSync as spawnSync12 } from "child_process";
13617
13632
  import { parse as parseYaml2 } from "yaml";
13618
13633
 
13619
13634
  // src/argv.ts
@@ -14468,7 +14483,7 @@ function matchShipRows(ships, shipName2, machineId) {
14468
14483
  }
14469
14484
 
14470
14485
  // src/provision.ts
14471
- import { spawnSync as spawnSync8 } from "child_process";
14486
+ import { spawnSync as spawnSync9 } from "child_process";
14472
14487
  import { existsSync as existsSync34, readFileSync as readFileSync33, writeFileSync as writeFileSync26 } from "fs";
14473
14488
  import { hostname as hostname2 } from "os";
14474
14489
  import { parseDocument as parseDocument3, isMap as isMap2, isScalar } from "yaml";
@@ -14916,6 +14931,11 @@ async function runProvision(o, deps, write, human) {
14916
14931
  emit("connect", `set ${key}`, { setting: key });
14917
14932
  }
14918
14933
  }
14934
+ if (isManagedShipKind(o.shipKind)) {
14935
+ summary.gitIdentity = deps.ensureGitIdentity(shipName2, MANAGED_GIT_EMAIL);
14936
+ if (summary.gitIdentity === "set") summary.changed = true;
14937
+ emit("git_identity", `git identity: ${summary.gitIdentity}`, { action: summary.gitIdentity });
14938
+ }
14919
14939
  for (const host of o.forgeHosts) {
14920
14940
  const token = o.tokens[host];
14921
14941
  const stored = deps.storedToken(host);
@@ -15072,7 +15092,7 @@ function realProvisionDeps(crewHome) {
15072
15092
  const env = { ...process.env, ...o.env };
15073
15093
  delete env.CREW_FORGE_TOKEN;
15074
15094
  for (const k of Object.keys(env)) if (k.startsWith("CREW_CONNECT_KEY_")) delete env[k];
15075
- const res = spawnSync8(bin, [...pre, ...args], { cwd: crewHome, encoding: "utf8", env, input: o.input, maxBuffer: 64 * 1024 * 1024 });
15095
+ const res = spawnSync9(bin, [...pre, ...args], { cwd: crewHome, encoding: "utf8", env, input: o.input, maxBuffer: 64 * 1024 * 1024 });
15076
15096
  return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: `${res.stderr ?? ""}${res.error ? String(res.error) : ""}` };
15077
15097
  };
15078
15098
  const configFile = () => {
@@ -15120,15 +15140,15 @@ function realProvisionDeps(crewHome) {
15120
15140
  if (mechanism === "systemd") {
15121
15141
  const system = ctx.root || existsSync34(systemUnitPathFor(label));
15122
15142
  const ctl = system ? ["systemctl"] : ["systemctl", "--user"];
15123
- running = spawnSync8(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
15143
+ running = spawnSync9(ctl[0], [...ctl.slice(1), "is-active", "--quiet", `${label}.service`]).status === 0;
15124
15144
  if (!system) {
15125
- const out = spawnSync8("loginctl", ["show-user", ctx.user, "-p", "Linger"], { encoding: "utf8" });
15145
+ const out = spawnSync9("loginctl", ["show-user", ctx.user, "-p", "Linger"], { encoding: "utf8" });
15126
15146
  lingering = /Linger=yes/.test(out.stdout ?? "");
15127
15147
  }
15128
15148
  } else if (mechanism === "cron") {
15129
15149
  running = true;
15130
15150
  } else if (mechanism === "launchd") {
15131
- running = spawnSync8("launchctl", ["list", label]).status === 0;
15151
+ running = spawnSync9("launchctl", ["list", label]).status === 0;
15132
15152
  }
15133
15153
  return { mechanism, unitsStale, running, lingering };
15134
15154
  },
@@ -15149,6 +15169,17 @@ function realProvisionDeps(crewHome) {
15149
15169
  if (wrote.length) writeFileSync26(file, doc.toString());
15150
15170
  return wrote;
15151
15171
  },
15172
+ ensureGitIdentity(name, email) {
15173
+ const have = readGlobalGitIdentity();
15174
+ let wrote = false;
15175
+ for (const [key, value2] of [["user.name", have.name ? null : name], ["user.email", have.email ? null : email]]) {
15176
+ if (value2 === null) continue;
15177
+ const res = spawnSync9("git", ["config", "--global", key, value2], { encoding: "utf8" });
15178
+ if (res.status !== 0) throw new Error(`git config --global ${key}: ${(res.stderr ?? "").trim() || `exited ${res.status}`}`);
15179
+ wrote = true;
15180
+ }
15181
+ return wrote ? "set" : "kept";
15182
+ },
15152
15183
  storedToken(host) {
15153
15184
  const res = spawnCrew(["credential", "get"], { input: `protocol=https
15154
15185
  host=${host}
@@ -20738,7 +20769,7 @@ async function showLogEntry(route2, entryId, opts = {}) {
20738
20769
  }
20739
20770
 
20740
20771
  // src/repos-check.ts
20741
- import { spawnSync as spawnSync10 } from "child_process";
20772
+ import { spawnSync as spawnSync11 } from "child_process";
20742
20773
  import { readFileSync as readFileSync39 } from "fs";
20743
20774
  import { basename as basename11 } from "path";
20744
20775
  var POSIX_SHELL = /^(?:ba|z|da|k|a|mk)?sh$|^fish$/;
@@ -20747,10 +20778,10 @@ function checkHookSyntax(script, shell) {
20747
20778
  const name = basename11(spec.bin).replace(/\.exe$/i, "").toLowerCase();
20748
20779
  let run3;
20749
20780
  if (POSIX_SHELL.test(name)) {
20750
- run3 = spawnSync10(spec.bin, ["-n"], { input: script, encoding: "utf8" });
20781
+ run3 = spawnSync11(spec.bin, ["-n"], { input: script, encoding: "utf8" });
20751
20782
  } else if (/^(pwsh|powershell)$/.test(name)) {
20752
20783
  const parse3 = "$e=$null;[void][System.Management.Automation.Language.Parser]::ParseInput($env:CREW_CHECK_SCRIPT,[ref]$null,[ref]$e);if($e){$e|ForEach-Object{[Console]::Error.WriteLine($_.Message)};exit 1}";
20753
- run3 = spawnSync10(spec.bin, ["-NoProfile", "-NonInteractive", "-Command", parse3], {
20784
+ run3 = spawnSync11(spec.bin, ["-NoProfile", "-NonInteractive", "-Command", parse3], {
20754
20785
  encoding: "utf8",
20755
20786
  env: { ...process.env, CREW_CHECK_SCRIPT: script }
20756
20787
  });
@@ -25831,7 +25862,7 @@ if (command === "passenger-sync-daemon") {
25831
25862
  `
25832
25863
  );
25833
25864
  if (!dryRun) {
25834
- spawnSync11("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME });
25865
+ spawnSync12("systemctl", ["--user", "disable", "--now", `${label}.timer`], { cwd: CREW_HOME });
25835
25866
  try {
25836
25867
  unlinkSync5(staleTimer);
25837
25868
  } catch {
@@ -25882,7 +25913,7 @@ if (command === "passenger-sync-daemon") {
25882
25913
  async function doctorFix(o) {
25883
25914
  const routes = await Promise.all(routesInScope(cfg.routes, named).map((c) => gatherHealth({ route: c.route, enabled: c.enabled, hasRepos: reposOf(c).length > 0 }, () => routeProblems(c))));
25884
25915
  const unitExists = (p) => existsSync52(p);
25885
- const crontab = o.host === "linux" ? spawnSync11("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25916
+ const crontab = o.host === "linux" ? spawnSync12("crontab", ["-l"], { encoding: "utf8" }).stdout ?? "" : "";
25886
25917
  const schedulerAbsent = o.host !== "windows" ? !schedulerInstalled(planInstall(cfg.ship, CREW_HOME, o.host, detectSystemd(), "run", installContext()), unitExists, crontab) : true;
25887
25918
  const plan = planFix({ routes, schedulerAbsent, host: o.host });
25888
25919
  for (const n of plan.notes) process.stdout.write(`fix: ${n}
@@ -27053,7 +27084,7 @@ ${cu.tail.trimEnd()}
27053
27084
  }
27054
27085
  let failed = false;
27055
27086
  for (const cmd2 of commands) {
27056
- const res2 = spawnSync11(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27087
+ const res2 = spawnSync12(cmd2[0], cmd2.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27057
27088
  if (res2.stdout) process.stdout.write(res2.stdout);
27058
27089
  if (res2.stderr) process.stderr.write(res2.stderr);
27059
27090
  if (cmd2 === control.startCommand && (res2.error || res2.status !== 0)) {
@@ -27071,7 +27102,7 @@ ${cu.tail.trimEnd()}
27071
27102
  `);
27072
27103
  break;
27073
27104
  }
27074
- const res = spawnSync11(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27105
+ const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
27075
27106
  if (res.stdout) process.stdout.write(res.stdout);
27076
27107
  if (res.stderr) process.stderr.write(res.stderr);
27077
27108
  if (daemonSub !== "status" && (res.error || res.status !== 0)) {
@@ -29528,7 +29559,7 @@ ${where.join("\n")}`);
29528
29559
  process.stdout.write(upOutput(res, terminal));
29529
29560
  const opener = openerFor(process.platform);
29530
29561
  if (opener && shouldOpen({ open: flag("open"), noOpen: flag("no-open"), json: jsonP, terminal })) {
29531
- const r = spawnSync11(opener, [res.url], { stdio: "ignore" });
29562
+ const r = spawnSync12(opener, [res.url], { stdio: "ignore" });
29532
29563
  if (r.error || r.status !== 0) process.stderr.write(`crew periscope: could not open ${res.url} with ${opener}
29533
29564
  `);
29534
29565
  }
@@ -29776,7 +29807,7 @@ ${where.join("\n")}`);
29776
29807
  }
29777
29808
  const host = hostPlatform();
29778
29809
  const control = host === "windows" ? null : planDaemonControl(CREW_HOME, host, detectSystemd());
29779
- const unitStatus = control?.statusCommand ? spawnSync11(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
29810
+ const unitStatus = control?.statusCommand ? spawnSync12(control.statusCommand[0], control.statusCommand.slice(1), { cwd: CREW_HOME, encoding: "utf8" }) : null;
29780
29811
  const unitRunning = unitStatus !== null && !unitStatus.error && unitStatus.status === 0;
29781
29812
  if (!unitRunning) {
29782
29813
  process.stdout.write("crew reload: no daemon unit is running \u2014 nothing to reload (the next run reads crew.yaml fresh)\n");
@@ -29790,7 +29821,7 @@ ${where.join("\n")}`);
29790
29821
  );
29791
29822
  if (flag("now") && control?.stopCommand && control.startCommand) {
29792
29823
  for (const cmd of [control.stopCommand, control.startCommand]) {
29793
- const res = spawnSync11(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
29824
+ const res = spawnSync12(cmd[0], cmd.slice(1), { cwd: CREW_HOME, encoding: "utf8" });
29794
29825
  if (cmd === control.startCommand && (res.error || res.status !== 0)) {
29795
29826
  process.stderr.write(`crew reload: ${cmd.join(" ")} exited ${res.status ?? "error"}
29796
29827
  `);
@@ -29932,7 +29963,7 @@ ${where.join("\n")}`);
29932
29963
  writeFileSync38(join56(dir, "README.md"), SAMPLE_README);
29933
29964
  writeFileSync38(join56(dir, ".crew.yaml"), SAMPLE_CREW_YAML);
29934
29965
  const g = (...a) => {
29935
- const r = spawnSync11("git", a, { cwd: dir, encoding: "utf8" });
29966
+ const r = spawnSync12("git", a, { cwd: dir, encoding: "utf8" });
29936
29967
  if (r.status !== 0) fail2(`git ${a[0]} failed in ${dir}: ${(r.stderr || r.stdout).trim()}`);
29937
29968
  };
29938
29969
  g("init", "-b", "main");
@@ -30364,7 +30395,9 @@ watermark: ${state.watermark()}
30364
30395
  }
30365
30396
  for (const message of roleCapWarnings(cfg.ship)) problems.push({ severity: "note", kind: "role_cap_above_ship_cap", message });
30366
30397
  if (!flag("offline")) {
30367
- for (const message of managedRuntimeWarnings(await doctorShipKind(route2, cfg.ship), cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
30398
+ const shipKind = await doctorShipKind(route2, cfg.ship);
30399
+ for (const message of managedRuntimeWarnings(shipKind, cfg.ship)) problems.push({ severity: "attention", kind: "managed_runtime_default", message });
30400
+ for (const message of managedGitIdentityWarnings(shipKind, readGlobalGitIdentity())) problems.push({ severity: "attention", kind: "managed_git_identity", message });
30368
30401
  }
30369
30402
  for (const t of rejectedTunnels(cfg.ship.stateDir)) {
30370
30403
  problems.push({ severity: "attention", kind: TUNNEL_REJECTED_KEY, message: tunnelRejectedMessage(t.workspaceId, t.quickExits), remedy: "rotate the slug with `crew rotate-passenger-url`" });
@@ -30615,6 +30648,8 @@ crew manifest: ${seats2.length ? seats2.map((c) => c.name).join(", ") : "non
30615
30648
  `
30616
30649
  );
30617
30650
  for (const w of managedRuntimeWarnings(row.kind, cfg.ship)) process.stdout.write(`managed runtime: WARN ${w}
30651
+ `);
30652
+ for (const w of managedGitIdentityWarnings(row.kind, readGlobalGitIdentity())) process.stdout.write(`managed git: WARN ${w}
30618
30653
  `);
30619
30654
  }
30620
30655
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablation/crew",
3
- "version": "0.127.0",
3
+ "version": "0.127.1",
4
4
  "description": "A standing team of headless agents that picks work off a Tablation board, does it on a machine you control, and reports back.",
5
5
  "type": "module",
6
6
  "license": "MIT",