@4pm/cli 1.19.3 → 1.19.6

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/index.js +64 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -37503,9 +37503,10 @@ var activityHeartbeatSchema = external_exports.object({
37503
37503
  });
37504
37504
 
37505
37505
  // src/core/scaffold.ts
37506
- import { cp, mkdir, rm, writeFile } from "fs/promises";
37506
+ import { cp, mkdir, mkdtemp, rm, writeFile } from "fs/promises";
37507
37507
  import { existsSync as existsSync4 } from "fs";
37508
37508
  import { execFile as execFile2 } from "child_process";
37509
+ import { tmpdir as tmpdir2 } from "os";
37509
37510
  import { dirname as dirname3, join as join6, resolve } from "path";
37510
37511
  import { fileURLToPath as fileURLToPath3 } from "url";
37511
37512
  import { promisify as promisify2 } from "util";
@@ -37595,7 +37596,7 @@ function submodulesOf(repos) {
37595
37596
  const primary = singleRepo(repos);
37596
37597
  return repos.filter((r) => r !== primary && !!r.url && !!(r.subdir ?? "").trim());
37597
37598
  }
37598
- async function attachSubmodules(root, submodules, emit) {
37599
+ async function attachSubmodules(root, submodules, emit, createMissingBranch = false) {
37599
37600
  if (submodules.length === 0) return;
37600
37601
  if (!existsSync4(join6(root, ".git"))) return;
37601
37602
  let added = 0;
@@ -37612,6 +37613,26 @@ async function attachSubmodules(root, submodules, emit) {
37612
37613
  }
37613
37614
  emit("submodule", `Adding submodule ${sub.url} \u2192 ${dir2}\u2026`);
37614
37615
  const b2 = (sub.branch ?? "").trim();
37616
+ if (b2 && createMissingBranch && !await ensureRemoteBranch(sub.url, b2, emit)) {
37617
+ try {
37618
+ await run2("git", ["submodule", "add", sub.url, dir2], { cwd: root, timeout: 12e4 });
37619
+ await run2("git", ["config", "-f", ".gitmodules", `submodule.${dir2}.branch`, b2], { cwd: root, timeout: 3e4 });
37620
+ try {
37621
+ await run2("git", ["checkout", "-b", b2], { cwd: join6(root, dir2), timeout: 3e4 });
37622
+ await run2("git", ["push", "-u", "origin", b2], { cwd: join6(root, dir2), timeout: 12e4 });
37623
+ } catch (pushErr) {
37624
+ const pmsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
37625
+ emit("git-branch-push-failed", `Submodule ${dir2}: branch "${b2}" created locally but the push failed: ${pmsg}`);
37626
+ }
37627
+ added++;
37628
+ continue;
37629
+ } catch (err) {
37630
+ const msg = err instanceof Error ? err.message : String(err);
37631
+ if (!/already exists|already registered|in the index/i.test(msg)) {
37632
+ throw new Error(`git submodule add ${dir2} failed: ${msg}`);
37633
+ }
37634
+ }
37635
+ }
37615
37636
  try {
37616
37637
  await run2("git", ["submodule", "add", ...b2 ? ["-b", b2] : [], sub.url, dir2], { cwd: root, timeout: 12e4 });
37617
37638
  added++;
@@ -37654,6 +37675,31 @@ function cloneArgs(url, dest, branch) {
37654
37675
  const b2 = (branch ?? "").trim();
37655
37676
  return b2 ? ["clone", "-b", b2, url, dest] : ["clone", url, dest];
37656
37677
  }
37678
+ async function remoteHasBranch(url, branch) {
37679
+ try {
37680
+ const { stdout } = await run2("git", ["ls-remote", "--heads", url, branch], { timeout: 3e4 });
37681
+ return stdout.trim().length > 0;
37682
+ } catch {
37683
+ return false;
37684
+ }
37685
+ }
37686
+ async function ensureRemoteBranch(url, branch, emit) {
37687
+ if (await remoteHasBranch(url, branch)) return true;
37688
+ const tmp = await mkdtemp(join6(tmpdir2(), "4pm-branch-"));
37689
+ try {
37690
+ emit("git", `Branch "${branch}" not found on ${url} \u2014 creating it from the default branch\u2026`);
37691
+ await run2("git", ["clone", "--depth", "1", url, tmp], { timeout: 12e4 });
37692
+ await run2("git", ["checkout", "-b", branch], { cwd: tmp, timeout: 3e4 });
37693
+ await run2("git", ["push", "-u", "origin", branch], { cwd: tmp, timeout: 12e4 });
37694
+ return true;
37695
+ } catch (err) {
37696
+ const msg = err instanceof Error ? err.message : String(err);
37697
+ emit("git-branch-push-failed", `Could not create branch "${branch}" on ${url}: ${msg}`);
37698
+ return false;
37699
+ } finally {
37700
+ await rm(tmp, { recursive: true, force: true });
37701
+ }
37702
+ }
37657
37703
  async function updateRepo(dir2, branch, emit) {
37658
37704
  const b2 = (branch ?? "").trim();
37659
37705
  emit("git", `Updating repository (fetch + ${b2 ? `checkout ${b2} + ` : ""}fast-forward pull)\u2026`);
@@ -37683,6 +37729,20 @@ async function provisionRepo(target, repo, emit, opts = {}) {
37683
37729
  }
37684
37730
  return;
37685
37731
  }
37732
+ const b2 = (repo.branch ?? "").trim();
37733
+ if (b2 && opts.createMissingBranch && !await remoteHasBranch(repo.url, b2)) {
37734
+ emit("git", `Cloning ${repo.url} (default branch) to create "${b2}"\u2026`);
37735
+ await run2("git", ["clone", repo.url, target], { timeout: 12e4 });
37736
+ await run2("git", ["checkout", "-b", b2], { cwd: target, timeout: 3e4 });
37737
+ try {
37738
+ await run2("git", ["push", "-u", "origin", b2], { cwd: target, timeout: 12e4 });
37739
+ emit("git", `Created and pushed branch "${b2}".`);
37740
+ } catch (err) {
37741
+ const msg = err instanceof Error ? err.message : String(err);
37742
+ emit("git-branch-push-failed", `Branch "${b2}" created locally but the push failed: ${msg} \u2014 push it when credentials are available.`);
37743
+ }
37744
+ return;
37745
+ }
37686
37746
  emit("git", `Cloning ${repo.url}\u2026`);
37687
37747
  await run2("git", cloneArgs(repo.url, target, repo.branch), { timeout: 12e4 });
37688
37748
  }
@@ -37716,9 +37776,9 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
37716
37776
  const repo = singleRepo(reposOf(payload.spec));
37717
37777
  if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
37718
37778
  emit("git", "Cloning repository\u2026");
37719
- await provisionRepo(target, repo, emit);
37779
+ await provisionRepo(target, repo, emit, { createMissingBranch: true });
37720
37780
  await scaffoldRepo(target, payload.projectName, payload.spec, emit);
37721
- await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit);
37781
+ await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
37722
37782
  emit("version", "Writing .4pm/.4pm.json\u2026");
37723
37783
  await writeTemplateMarker(target);
37724
37784
  onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.19.3",
3
+ "version": "1.19.6",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",