@4pm/cli 1.19.3 → 1.19.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.
Files changed (2) hide show
  1. package/dist/index.js +75 -5
  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;
@@ -37610,8 +37611,33 @@ async function attachSubmodules(root, submodules, emit) {
37610
37611
  }
37611
37612
  continue;
37612
37613
  }
37614
+ const abs = join6(root, dir2);
37615
+ if (existsSync4(abs)) {
37616
+ emit("submodule", `Removing placeholder ${dir2} before attaching the submodule\u2026`);
37617
+ await rm(abs, { recursive: true, force: true });
37618
+ }
37613
37619
  emit("submodule", `Adding submodule ${sub.url} \u2192 ${dir2}\u2026`);
37614
37620
  const b2 = (sub.branch ?? "").trim();
37621
+ if (b2 && createMissingBranch && !await ensureRemoteBranch(sub.url, b2, emit)) {
37622
+ try {
37623
+ await run2("git", ["submodule", "add", sub.url, dir2], { cwd: root, timeout: 12e4 });
37624
+ await run2("git", ["config", "-f", ".gitmodules", `submodule.${dir2}.branch`, b2], { cwd: root, timeout: 3e4 });
37625
+ try {
37626
+ await run2("git", ["checkout", "-b", b2], { cwd: join6(root, dir2), timeout: 3e4 });
37627
+ await run2("git", ["push", "-u", "origin", b2], { cwd: join6(root, dir2), timeout: 12e4 });
37628
+ } catch (pushErr) {
37629
+ const pmsg = pushErr instanceof Error ? pushErr.message : String(pushErr);
37630
+ emit("git-branch-push-failed", `Submodule ${dir2}: branch "${b2}" created locally but the push failed: ${pmsg}`);
37631
+ }
37632
+ added++;
37633
+ continue;
37634
+ } catch (err) {
37635
+ const msg = err instanceof Error ? err.message : String(err);
37636
+ if (!/already exists|already registered|in the index/i.test(msg)) {
37637
+ throw new Error(`git submodule add ${dir2} failed: ${msg}`);
37638
+ }
37639
+ }
37640
+ }
37615
37641
  try {
37616
37642
  await run2("git", ["submodule", "add", ...b2 ? ["-b", b2] : [], sub.url, dir2], { cwd: root, timeout: 12e4 });
37617
37643
  added++;
@@ -37632,8 +37658,13 @@ async function attachSubmodules(root, submodules, emit) {
37632
37658
  }
37633
37659
  async function commitAndPushSubmodules(root, submodules, emit) {
37634
37660
  const dirs = submodules.map((s3) => (s3.subdir ?? "").trim()).filter(Boolean);
37661
+ const addPaths = [
37662
+ ...existsSync4(join6(root, ".gitmodules")) ? [".gitmodules"] : [],
37663
+ ...dirs.filter((d) => existsSync4(join6(root, d)))
37664
+ ];
37665
+ if (addPaths.length === 0) return;
37635
37666
  emit("submodule", "Committing .gitmodules\u2026");
37636
- await run2("git", ["add", ".gitmodules", ...dirs], { cwd: root, timeout: 6e4 });
37667
+ await run2("git", ["add", ...addPaths], { cwd: root, timeout: 6e4 });
37637
37668
  const staged = (await run2("git", ["diff", "--cached", "--name-only"], { cwd: root, timeout: 3e4 })).stdout.trim();
37638
37669
  if (!staged) return;
37639
37670
  const commitArgs = ["commit", "-m", "chore: add git submodules (4PM)"];
@@ -37654,6 +37685,31 @@ function cloneArgs(url, dest, branch) {
37654
37685
  const b2 = (branch ?? "").trim();
37655
37686
  return b2 ? ["clone", "-b", b2, url, dest] : ["clone", url, dest];
37656
37687
  }
37688
+ async function remoteHasBranch(url, branch) {
37689
+ try {
37690
+ const { stdout } = await run2("git", ["ls-remote", "--heads", url, branch], { timeout: 3e4 });
37691
+ return stdout.trim().length > 0;
37692
+ } catch {
37693
+ return false;
37694
+ }
37695
+ }
37696
+ async function ensureRemoteBranch(url, branch, emit) {
37697
+ if (await remoteHasBranch(url, branch)) return true;
37698
+ const tmp = await mkdtemp(join6(tmpdir2(), "4pm-branch-"));
37699
+ try {
37700
+ emit("git", `Branch "${branch}" not found on ${url} \u2014 creating it from the default branch\u2026`);
37701
+ await run2("git", ["clone", "--depth", "1", url, tmp], { timeout: 12e4 });
37702
+ await run2("git", ["checkout", "-b", branch], { cwd: tmp, timeout: 3e4 });
37703
+ await run2("git", ["push", "-u", "origin", branch], { cwd: tmp, timeout: 12e4 });
37704
+ return true;
37705
+ } catch (err) {
37706
+ const msg = err instanceof Error ? err.message : String(err);
37707
+ emit("git-branch-push-failed", `Could not create branch "${branch}" on ${url}: ${msg}`);
37708
+ return false;
37709
+ } finally {
37710
+ await rm(tmp, { recursive: true, force: true });
37711
+ }
37712
+ }
37657
37713
  async function updateRepo(dir2, branch, emit) {
37658
37714
  const b2 = (branch ?? "").trim();
37659
37715
  emit("git", `Updating repository (fetch + ${b2 ? `checkout ${b2} + ` : ""}fast-forward pull)\u2026`);
@@ -37683,6 +37739,20 @@ async function provisionRepo(target, repo, emit, opts = {}) {
37683
37739
  }
37684
37740
  return;
37685
37741
  }
37742
+ const b2 = (repo.branch ?? "").trim();
37743
+ if (b2 && opts.createMissingBranch && !await remoteHasBranch(repo.url, b2)) {
37744
+ emit("git", `Cloning ${repo.url} (default branch) to create "${b2}"\u2026`);
37745
+ await run2("git", ["clone", repo.url, target], { timeout: 12e4 });
37746
+ await run2("git", ["checkout", "-b", b2], { cwd: target, timeout: 3e4 });
37747
+ try {
37748
+ await run2("git", ["push", "-u", "origin", b2], { cwd: target, timeout: 12e4 });
37749
+ emit("git", `Created and pushed branch "${b2}".`);
37750
+ } catch (err) {
37751
+ const msg = err instanceof Error ? err.message : String(err);
37752
+ emit("git-branch-push-failed", `Branch "${b2}" created locally but the push failed: ${msg} \u2014 push it when credentials are available.`);
37753
+ }
37754
+ return;
37755
+ }
37686
37756
  emit("git", `Cloning ${repo.url}\u2026`);
37687
37757
  await run2("git", cloneArgs(repo.url, target, repo.branch), { timeout: 12e4 });
37688
37758
  }
@@ -37716,9 +37786,9 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
37716
37786
  const repo = singleRepo(reposOf(payload.spec));
37717
37787
  if (!repo) throw new Error("A project must declare one repo (ADR-0314).");
37718
37788
  emit("git", "Cloning repository\u2026");
37719
- await provisionRepo(target, repo, emit);
37789
+ await provisionRepo(target, repo, emit, { createMissingBranch: true });
37720
37790
  await scaffoldRepo(target, payload.projectName, payload.spec, emit);
37721
- await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit);
37791
+ await attachSubmodules(target, submodulesOf(reposOf(payload.spec)), emit, true);
37722
37792
  emit("version", "Writing .4pm/.4pm.json\u2026");
37723
37793
  await writeTemplateMarker(target);
37724
37794
  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.7",
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",