@plexus-ms/cli 0.8.1 → 0.10.0

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/plx.js +370 -160
  2. package/package.json +2 -2
package/dist/plx.js CHANGED
@@ -7567,9 +7567,10 @@ function frontmatterField(text, key) {
7567
7567
 
7568
7568
  // ../lib/dist/bookkeeping.js
7569
7569
  var bumpGalaxyVersion = (text, train) => text.replace(/^(version: )\d+\.\d+\.\d+/m, `$1${train}`);
7570
+ var COLLECTION_SOURCE = "plexus-ms/primitives.git";
7570
7571
  function bumpCollectionPin(text, train) {
7571
7572
  const lines = text.split("\n");
7572
- const start = lines.findIndex((l) => l.includes("plexus-ms/platform.git"));
7573
+ const start = lines.findIndex((l) => l.includes(COLLECTION_SOURCE));
7573
7574
  if (start === -1)
7574
7575
  return text;
7575
7576
  for (let i = start; i < lines.length; i++) {
@@ -7581,8 +7582,13 @@ function bumpCollectionPin(text, train) {
7581
7582
  }
7582
7583
  return lines.join("\n");
7583
7584
  }
7584
- var WORKFLOW_REF = "plexus-ms/ci-cd/.github/workflows/";
7585
- var bumpWorkflowRefs = (text, train) => text.replace(/(plexus-ms\/ci-cd\/\.github\/workflows\/[A-Za-z0-9._-]+)@v?[0-9.]+/g, `$1@${train}`);
7585
+ var WORKFLOW_REF = "plexus-ms/primitives/.github/workflows/";
7586
+ var bumpWorkflowRefs = (text, train) => text.replace(/(plexus-ms\/primitives\/\.github\/workflows\/[A-Za-z0-9._-]+)@v?[0-9.]+/g, `$1@${train}`);
7587
+ var bumpManifestVersion = (text, train) => text.replace(/^(\s*"version":\s*")[^"]*(")/m, `$1${train}$2`);
7588
+ var PLX_PIN = "npm:@plexus-ms/cli";
7589
+ var bumpPlxPin = (text, train) => text.replace(/("npm:@plexus-ms\/cli"\s*=\s*(?:\{\s*version\s*=\s*)?")[^"]*(")/g, `$1${train}$2`);
7590
+ var CONFIG_PIN = "@plexus-ms/config";
7591
+ var bumpConfigPin = (text, train) => text.replace(/("@plexus-ms\/config":\s*")(?!workspace:|catalog:)[^"]*(")/g, `$1${train}$2`);
7586
7592
  var stampStandard = (text, train, today2) => setFrontmatterField(setFrontmatterField(text, "version", train), "timestamp", today2);
7587
7593
  var stampChanged = (text, train) => setFrontmatterField(text, "changed", train, { after: "binds" });
7588
7594
  var hasConflictMarkers = (text) => /^<<<<<<< /m.test(text);
@@ -7968,13 +7974,13 @@ async function expand(ctx, root, glob) {
7968
7974
 
7969
7975
  // ../lib/dist/consumers.js
7970
7976
  import { relative } from "node:path";
7971
- function expectedBranch(declaration) {
7977
+ function expectedBranch(declaration, answers = {}) {
7972
7978
  if (!declaration)
7973
7979
  return void 0;
7974
7980
  if (declaration.modules.includes("PLX-Platform"))
7975
7981
  return "main";
7976
7982
  if (declaration.modules.includes("PLX-Repo"))
7977
- return "develop";
7983
+ return answers.publishes_packages === true ? "main" : "develop";
7978
7984
  return void 0;
7979
7985
  }
7980
7986
  var presetSource = (preset) => `gh:plexus-ms/${preset}`;
@@ -7993,11 +7999,12 @@ async function findConsumers(ctx, root, presets) {
7993
7999
  else {
7994
8000
  let consumer = consumers.get(here.path);
7995
8001
  if (!consumer) {
8002
+ const expected = expectedBranch(here.declaration, here.copier?.answers);
7996
8003
  consumer = {
7997
8004
  path: here.path,
7998
8005
  rel: relative(root.path, here.path) || ".",
7999
- branch: here.git?.branch ?? "?",
8000
- expected: expectedBranch(here.declaration),
8006
+ branch: expected ?? here.git?.branch ?? "main",
8007
+ expected,
8001
8008
  sites: []
8002
8009
  };
8003
8010
  consumers.set(here.path, consumer);
@@ -8012,13 +8019,11 @@ async function findConsumers(ctx, root, presets) {
8012
8019
  for (const site of orphans)
8013
8020
  ctx.say(`\u26A0 ${site.rel}: not inside a git repo \u2014 skipped`);
8014
8021
  for (const c of consumers.values()) {
8015
- if (c.expected && c.branch !== c.expected) {
8016
- c.skipped = `on '${c.branch}', expected '${c.expected}'`;
8017
- continue;
8018
- }
8019
- const tracked = (await ctx.read("git", ["status", "--porcelain", "--untracked-files=no"], { cwd: c.path })).trim();
8020
- if (tracked !== "")
8021
- c.skipped = "has uncommitted changes";
8022
+ const origin = (await ctx.read("git", ["remote", "get-url", "origin"], { cwd: c.path }).catch(() => "")).trim();
8023
+ if (origin === "")
8024
+ c.skipped = "no origin remote";
8025
+ else
8026
+ c.origin = origin;
8022
8027
  }
8023
8028
  return [...consumers.values()];
8024
8029
  }
@@ -8222,14 +8227,20 @@ function compareTrains(a, b) {
8222
8227
  }
8223
8228
 
8224
8229
  // ../lib/dist/train.js
8230
+ var PRIMITIVES = "primitives";
8225
8231
  var TRAIN_REPOS = [
8226
- "platform",
8227
- "ci-cd",
8232
+ PRIMITIVES,
8228
8233
  "preset-repo-web",
8229
8234
  "preset-app-nextjs",
8230
8235
  "preset-repo-platform",
8231
8236
  "compendium"
8232
8237
  ];
8238
+ var PHASES = [[PRIMITIVES], TRAIN_REPOS.filter((r) => r !== PRIMITIVES)];
8239
+ var TRAIN_PACKAGES = [
8240
+ { name: "@plexus-ms/cli", dir: "packages/cli" },
8241
+ { name: "@plexus-ms/lib", dir: "packages/lib" },
8242
+ { name: "@plexus-ms/config", dir: "packages/config" }
8243
+ ];
8233
8244
  var UPSTREAM = "plexus-ms";
8234
8245
  var isTrainVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
8235
8246
  function nextMinor(version2) {
@@ -8240,7 +8251,7 @@ function latestTag(tags) {
8240
8251
  return tags.filter((t) => /^v?\d/.test(t)).sort((a, b) => compareTrains(a.replace(/^v/, ""), b.replace(/^v/, ""))).at(-1);
8241
8252
  }
8242
8253
  async function readTrain(ctx, upstream) {
8243
- const repos = [];
8254
+ const repos2 = [];
8244
8255
  const all = /* @__PURE__ */ new Set();
8245
8256
  for (const name of TRAIN_REPOS) {
8246
8257
  const path3 = join4(upstream, name);
@@ -8260,11 +8271,12 @@ async function readTrain(ctx, upstream) {
8260
8271
  const tag = latestTag(tags);
8261
8272
  const range = tag ? `${tag}..HEAD` : "HEAD";
8262
8273
  const pending = (await ctx.read("git", ["log", "--oneline", range], { cwd: path3 })).split("\n").filter(Boolean);
8263
- repos.push({ name, path: path3, tag, latest: tag?.replace(/^v/, ""), pending });
8274
+ repos2.push({ name, path: path3, tag, latest: tag?.replace(/^v/, ""), pending });
8264
8275
  }
8265
- const latest = repos.map((r) => r.latest);
8266
- return { repos, last: latestTag([...all]), skew: latest.some((l) => l !== latest[0]) };
8276
+ const latest = repos2.map((r) => r.latest);
8277
+ return { repos: repos2, last: latestTag([...all]), skew: latest.some((l) => l !== latest[0]) };
8267
8278
  }
8279
+ var phasesOf = (train) => PHASES.map((names) => train.repos.filter((r) => names.includes(r.name)));
8268
8280
  async function preflightRelease(ctx, train) {
8269
8281
  for (const { name, path: path3 } of train.repos) {
8270
8282
  const branch = (await ctx.read("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path3 })).trim();
@@ -8277,6 +8289,13 @@ async function preflightRelease(ctx, train) {
8277
8289
  throw new Error(`${name} is ${behind} commit(s) behind origin/main \u2014 pull first, or the tag lands on a stale commit`);
8278
8290
  }
8279
8291
  }
8292
+ async function preflightForge(ctx) {
8293
+ try {
8294
+ await ctx.read("gh", ["auth", "status"]);
8295
+ } catch {
8296
+ throw new Error("gh is not logged in (gh auth status failed) \u2014 run gh auth login first");
8297
+ }
8298
+ }
8280
8299
  function resolveVersion(train, explicit, propagateOnly) {
8281
8300
  if (explicit !== void 0 && !isTrainVersion(explicit))
8282
8301
  throw new UsageError(`'${explicit}' is not plain semver (X.Y.Z, no v)`);
@@ -8557,8 +8576,41 @@ var PRUNED = /* @__PURE__ */ new Set([
8557
8576
  ".terraform",
8558
8577
  ".gradle"
8559
8578
  ]);
8560
- function overview(ctx, root, options = {}) {
8561
- return walk(ctx, root, root.replace(/\/+$/, "").split("/").pop() || root, { git: true, ...options });
8579
+ var FETCHES = 8;
8580
+ async function overview(ctx, root, options = {}) {
8581
+ const opts = { git: true, fetch: false, ...options };
8582
+ const tree = await walk(ctx, root, root.replace(/\/+$/, "").split("/").pop() || root, opts);
8583
+ if (opts.git && opts.fetch)
8584
+ await syncAll(ctx, repos(tree));
8585
+ return tree;
8586
+ }
8587
+ function repos(dir) {
8588
+ return [...dir.git ? [dir] : [], ...dir.children.flatMap(repos)];
8589
+ }
8590
+ async function syncAll(ctx, dirs) {
8591
+ const queue = [...dirs];
8592
+ const worker = async () => {
8593
+ for (let dir = queue.shift(); dir; dir = queue.shift())
8594
+ await sync(ctx, dir);
8595
+ };
8596
+ await Promise.all(Array.from({ length: Math.min(FETCHES, queue.length) }, worker));
8597
+ }
8598
+ async function sync(ctx, dir) {
8599
+ const facet2 = dir.git;
8600
+ if (!facet2)
8601
+ return;
8602
+ try {
8603
+ await ctx.read("git", ["fetch", "--all", "--quiet"], { cwd: dir.path });
8604
+ } catch (error) {
8605
+ dir.problems ??= [];
8606
+ dir.problems.push(`git fetch: ${error instanceof Error ? error.message : String(error)}`);
8607
+ }
8608
+ const counts = await git(ctx, dir.path, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
8609
+ const [ahead, behind] = (counts ?? "").split(/\s+/).map(Number);
8610
+ if (ahead === void 0 || behind === void 0 || Number.isNaN(ahead) || Number.isNaN(behind))
8611
+ return;
8612
+ facet2.ahead = ahead;
8613
+ facet2.behind = behind;
8562
8614
  }
8563
8615
  async function walk(ctx, path3, name, options) {
8564
8616
  const entries = await ctx.readDir(path3);
@@ -8630,9 +8682,18 @@ async function templateFacet(ctx, cwd) {
8630
8682
  // ../lib/dist/propagate.js
8631
8683
  import { join as join8, relative as relative3 } from "node:path";
8632
8684
  var isClean = (s) => s.conflicted.length === 0 && s.failed.length === 0 && s.skipped.length === 0;
8633
- var PREVIEW_BASE = "ship-base";
8685
+ var BASE_TAG = "ship-base";
8686
+ var trainBranch = (train) => `train/${train}`;
8634
8687
  async function propagate(ctx, workspace, train) {
8635
- const summary = { train, preview: ctx.dryRun, clean: [], conflicted: [], failed: [], skipped: [] };
8688
+ const summary = {
8689
+ train,
8690
+ preview: ctx.dryRun,
8691
+ clean: [],
8692
+ conflicted: [],
8693
+ failed: [],
8694
+ skipped: [],
8695
+ pulls: []
8696
+ };
8636
8697
  const upstream = join8(workspace, UPSTREAM);
8637
8698
  const tree = await overview(ctx, workspace);
8638
8699
  const upstreamDir = tree.children.find((c) => c.path === upstream);
@@ -8644,64 +8705,63 @@ async function propagate(ctx, workspace, train) {
8644
8705
  ctx.say(`\u26A0 ${c.rel}: ${c.skipped} \u2014 skipped (fix it, then re-run propagate)`);
8645
8706
  summary.skipped.push({ path: c.rel, reason: c.skipped });
8646
8707
  }
8647
- const active = consumers.filter((c) => !c.skipped);
8648
- const scratch = ctx.dryRun ? (await ctx.stage(run("mktemp", ["-d"])))?.trim() : void 0;
8649
- const clones = /* @__PURE__ */ new Map();
8650
- const cloneOf = async (consumer) => {
8651
- let clone = clones.get(consumer.path);
8652
- if (!clone) {
8653
- clone = join8(scratch ?? "", consumer.rel);
8654
- await ctx.stage(run("git", ["clone", "--quiet", consumer.path, clone]));
8655
- await ctx.stage(run("git", ["tag", PREVIEW_BASE], { cwd: clone }));
8656
- clones.set(consumer.path, clone);
8657
- }
8658
- return clone;
8659
- };
8660
- const previewCommit = (clone, message) => ctx.stage(run("git", ["add", "-A"], { cwd: clone })).then(() => ctx.stage(run("git", [
8661
- "-c",
8662
- "commit.gpgsign=false",
8663
- "-c",
8664
- "user.name=plx",
8665
- "-c",
8666
- "user.email=plx@localhost",
8667
- "commit",
8668
- "--quiet",
8669
- "--no-verify",
8670
- "--allow-empty",
8671
- "-m",
8672
- message
8673
- ], { cwd: clone })));
8708
+ for (const preset of presets)
8709
+ if (!consumers.some((c) => c.sites.some((s) => s.preset === preset)))
8710
+ ctx.say(`\u2022 no consumers of ${presetSource(preset)} found under ${workspace}`);
8711
+ const scratch = (await ctx.stage(run("mktemp", ["-d"])))?.trim() ?? "";
8674
8712
  try {
8675
- for (const preset of presets) {
8676
- const sites = active.flatMap((c) => c.sites.filter((s) => s.preset === preset).map((s) => [c, s]));
8677
- if (sites.length === 0)
8678
- ctx.say(`\u2022 no consumers of ${presetSource(preset)} found under ${workspace}`);
8679
- for (const [consumer, site] of sites)
8680
- await updateSite(ctx, consumer, site, join8(upstream, preset), summary, cloneOf, previewCommit);
8681
- }
8682
- await refreshLockfiles(ctx, active, summary, clones);
8683
- await bumpConsumerRefs(ctx, consumers, train);
8713
+ for (const consumer of consumers) {
8714
+ if (consumer.skipped || !consumer.origin)
8715
+ continue;
8716
+ const clone = join8(scratch, consumer.rel);
8717
+ ctx.say(`\u2192 ${consumer.rel}: clone ${consumer.origin} at ${consumer.branch}`);
8718
+ try {
8719
+ await ctx.stage(run("git", ["clone", "--quiet", "--branch", consumer.branch, consumer.origin, clone]));
8720
+ await ctx.stage(run("git", ["tag", BASE_TAG], { cwd: clone }));
8721
+ } catch (error) {
8722
+ ctx.say(` \u2717 FAILED \u2014 clone${detail(error)}`);
8723
+ summary.failed.push(consumer.rel);
8724
+ continue;
8725
+ }
8726
+ for (const site of consumer.sites)
8727
+ await updateSite(ctx, consumer, site, clone, upstream, summary);
8728
+ await refreshLockfile(ctx, consumer, clone, summary);
8729
+ await bumpConsumerRefs(ctx, consumer, clone, train);
8730
+ await openPull(ctx, consumer, clone, train, summary);
8731
+ }
8684
8732
  } finally {
8685
8733
  if (scratch)
8686
8734
  await ctx.stage(run("rm", ["-rf", scratch]));
8687
8735
  }
8688
8736
  return summary;
8689
8737
  }
8690
- async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, previewCommit) {
8691
- let repo = consumer.path;
8692
- let target = site.path;
8738
+ var previewCommit = (ctx, clone, message) => ctx.stage(run("git", ["add", "-A"], { cwd: clone })).then(() => ctx.stage(run("git", [
8739
+ "-c",
8740
+ "commit.gpgsign=false",
8741
+ "-c",
8742
+ "user.name=plx",
8743
+ "-c",
8744
+ "user.email=plx@localhost",
8745
+ "commit",
8746
+ "--quiet",
8747
+ "--no-verify",
8748
+ "--allow-empty",
8749
+ "-m",
8750
+ message
8751
+ ], { cwd: clone })));
8752
+ async function updateSite(ctx, consumer, site, clone, upstream, summary) {
8753
+ const target = join8(clone, relative3(consumer.path, site.path));
8754
+ const localPreset = join8(upstream, site.preset);
8693
8755
  try {
8694
8756
  if (ctx.dryRun) {
8695
8757
  ctx.say(`\u2192 ${site.rel}: copier update (preview from ${localPreset}@main)`);
8696
- repo = await cloneOf(consumer);
8697
- target = join8(repo, relative3(consumer.path, site.path));
8698
- const answers = await ctx.readFile(join8(site.path, ANSWERS_FILE));
8758
+ const answers = await ctx.readFile(join8(target, ANSWERS_FILE));
8699
8759
  await ctx.stage(write(join8(target, ANSWERS_FILE), answers.replace(/^_src_path: .*$/m, `_src_path: ${localPreset}`)));
8700
- await previewCommit(repo, `plx preview: ${site.rel} reads ${localPreset} from the local checkout`);
8760
+ await previewCommit(ctx, clone, `plx preview: ${site.rel} reads ${localPreset} from the local checkout`);
8701
8761
  await ctx.stage(run("mise", copierVia(["update", "--skip-answered", "--trust", "--defaults", "--skip-tasks", "--vcs-ref", "main"]), { cwd: target }));
8702
8762
  } else {
8703
8763
  ctx.say(`\u2192 ${site.rel}: copier update`);
8704
- const args = ["update", "--skip-answered", "--trust", ...ctx.yes ? ["--defaults"] : []];
8764
+ const args = ["update", "--skip-answered", "--trust", "--skip-tasks", ...ctx.yes ? ["--defaults"] : []];
8705
8765
  await ctx.apply([run("mise", copierVia(args), { cwd: target, interactive: true })]);
8706
8766
  }
8707
8767
  } catch (error) {
@@ -8709,17 +8769,17 @@ async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, pr
8709
8769
  summary.failed.push(site.rel);
8710
8770
  return;
8711
8771
  }
8712
- const status = await ctx.read("git", ["status", "--porcelain"], { cwd: repo });
8772
+ const status = await ctx.read("git", ["status", "--porcelain", "--", "."], { cwd: target });
8713
8773
  let conflict = hasUnmergedEntries(status);
8714
- const changed = (await ctx.read("git", ["diff", "--name-only"], { cwd: repo })).split("\n").filter(Boolean);
8774
+ const changed = (await ctx.read("git", ["diff", "--name-only", "--relative", "--", "."], { cwd: target })).split("\n").filter(Boolean);
8715
8775
  for (const f of changed) {
8716
- const text = await ctx.readFile(join8(repo, f)).catch(() => "");
8776
+ const text = await ctx.readFile(join8(target, f)).catch(() => "");
8717
8777
  if (hasConflictMarkers(text))
8718
8778
  conflict = true;
8719
8779
  }
8720
8780
  await syncSiteMarker(ctx, site, target);
8721
8781
  if (conflict) {
8722
- ctx.say(" \u2717 CONFLICTS \u2014 resolve the <<<<<<< markers, then git add:");
8782
+ ctx.say(" \u2717 CONFLICTS \u2014 committed with their <<<<<<< markers, to resolve in the pull request:");
8723
8783
  summary.conflicted.push(site.rel);
8724
8784
  } else {
8725
8785
  ctx.say(" \u2713 merged clean:");
@@ -8728,7 +8788,7 @@ async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, pr
8728
8788
  for (const line of status.split("\n").filter(Boolean))
8729
8789
  ctx.say(` ${line}`);
8730
8790
  if (ctx.dryRun)
8731
- await previewCommit(repo, `plx preview: ${site.rel} updated`);
8791
+ await previewCommit(ctx, clone, `plx preview: ${site.rel} updated`);
8732
8792
  }
8733
8793
  async function syncSiteMarker(ctx, site, target) {
8734
8794
  const marker = await ctx.readFile(join8(target, MARKER_FILE)).catch(() => void 0);
@@ -8741,55 +8801,129 @@ async function syncSiteMarker(ctx, site, target) {
8741
8801
  if (synced === marker)
8742
8802
  return;
8743
8803
  ctx.say(` marker: plexus \u2192 ${commit} in ${site.rel}/${MARKER_FILE}`);
8744
- const step = write(join8(target, MARKER_FILE), synced);
8745
- if (ctx.dryRun)
8746
- await ctx.stage(step);
8747
- else
8748
- await ctx.apply([step]);
8804
+ await ctx.stage(write(join8(target, MARKER_FILE), synced));
8749
8805
  }
8750
- async function refreshLockfiles(ctx, consumers, summary, clones) {
8806
+ async function refreshLockfile(ctx, consumer, clone, summary) {
8807
+ if (await ctx.readFile(join8(clone, "pnpm-workspace.yaml")).catch(() => void 0) === void 0)
8808
+ return;
8751
8809
  const manifests = ["*package.json", "pnpm-workspace.yaml"];
8752
- for (const c of consumers) {
8753
- if (await ctx.readFile(join8(c.path, "pnpm-workspace.yaml")).catch(() => void 0) === void 0)
8754
- continue;
8755
- const repo = ctx.dryRun ? clones.get(c.path) : c.path;
8756
- if (!repo)
8757
- continue;
8758
- const range = ctx.dryRun ? [PREVIEW_BASE] : [];
8759
- const unchanged = await ctx.read("git", ["diff", "--quiet", ...range, "--", ...manifests], { cwd: repo }).then(() => true).catch(() => false);
8760
- if (unchanged)
8761
- continue;
8762
- ctx.say(`\u2192 ${c.rel}: pnpm install (manifests changed)`);
8763
- try {
8764
- await ctx.apply([run("mise", ["exec", "--", "pnpm", "install", "--reporter=silent"], { cwd: c.path })]);
8765
- } catch (error) {
8766
- ctx.say(` \u2717 pnpm install failed${detail(error)}`);
8767
- summary.failed.push(`${c.rel}/pnpm-lock.yaml`);
8768
- }
8810
+ const unchanged = await ctx.read("git", ["diff", "--quiet", BASE_TAG, "--", ...manifests], { cwd: clone }).then(() => true).catch(() => false);
8811
+ if (unchanged)
8812
+ return;
8813
+ ctx.say(`\u2192 ${consumer.rel}: pnpm install (manifests changed)`);
8814
+ try {
8815
+ await ctx.apply([run("mise", ["exec", "--", "pnpm", "install", "--reporter=silent"], { cwd: clone })]);
8816
+ } catch (error) {
8817
+ ctx.say(` \u2717 pnpm install failed${detail(error)}`);
8818
+ summary.failed.push(`${consumer.rel}/pnpm-lock.yaml`);
8769
8819
  }
8770
8820
  }
8771
- async function bumpConsumerRefs(ctx, consumers, train) {
8772
- for (const c of consumers) {
8773
- const dir = join8(c.path, ".github", "workflows");
8774
- const entries = await ctx.readDir(dir).catch(() => []);
8775
- const files = entries.filter((e) => !e.directory && /\.ya?ml$/.test(e.name)).map((e) => e.name).sort();
8776
- if (files.length === 0)
8821
+ async function bumpConsumerRefs(ctx, consumer, clone, train) {
8822
+ const dir = join8(clone, ".github", "workflows");
8823
+ const entries = await ctx.readDir(dir).catch(() => []);
8824
+ const files = entries.filter((e) => !e.directory && /\.ya?ml$/.test(e.name)).map((e) => e.name).sort();
8825
+ for (const f of files) {
8826
+ const text = await ctx.readFile(join8(dir, f));
8827
+ if (!text.includes(WORKFLOW_REF))
8777
8828
  continue;
8778
- if (c.skipped) {
8779
- ctx.say(`\u26A0 ${c.rel}: workflow refs not bumped (skipped above)`);
8829
+ const bumped = bumpWorkflowRefs(text, train);
8830
+ if (bumped === text)
8780
8831
  continue;
8781
- }
8782
- for (const f of files) {
8783
- const text = await ctx.readFile(join8(dir, f));
8784
- if (!text.includes(WORKFLOW_REF))
8785
- continue;
8786
- const bumped = bumpWorkflowRefs(text, train);
8787
- if (bumped === text)
8788
- continue;
8789
- ctx.say(`\u2192 ${c.rel}: ${f} ci-cd refs \u2192 @${train}`);
8790
- await ctx.apply([write(join8(dir, f), bumped)]);
8791
- }
8832
+ ctx.say(`\u2192 ${consumer.rel}: ${f} workflow refs \u2192 @${train}`);
8833
+ await ctx.stage(write(join8(dir, f), bumped));
8834
+ }
8835
+ }
8836
+ async function openPull(ctx, consumer, clone, train, summary) {
8837
+ const branch = trainBranch(train);
8838
+ if (ctx.dryRun) {
8839
+ ctx.say(` (preview) ${consumer.rel}: would commit, push ${branch} and open a pull request against ${consumer.branch}`);
8840
+ return;
8841
+ }
8842
+ if ((await ctx.read("git", ["status", "--porcelain"], { cwd: clone })).trim() === "") {
8843
+ ctx.say(`\u2022 ${consumer.rel}: nothing changed \u2014 no pull request`);
8844
+ return;
8792
8845
  }
8846
+ const head = `${consumer.origin} ${branch}`;
8847
+ try {
8848
+ await ctx.apply([
8849
+ run("git", ["add", "-A"], { cwd: clone }),
8850
+ run("git", ["commit", "--quiet", "--no-verify", "-m", `chore: train ${train}`], { cwd: clone })
8851
+ ]);
8852
+ await ctx.read("git", ["fetch", "--quiet", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`], {
8853
+ cwd: clone
8854
+ }).catch(() => "");
8855
+ await ctx.apply([
8856
+ run("git", ["push", "--quiet", "--force-with-lease", "origin", `HEAD:refs/heads/${branch}`], { cwd: clone })
8857
+ ]);
8858
+ } catch (error) {
8859
+ ctx.say(` \u2717 FAILED \u2014 push to ${head}${detail(error)}`);
8860
+ summary.failed.push(consumer.rel);
8861
+ return;
8862
+ }
8863
+ const existing = (await ctx.read("gh", [
8864
+ "pr",
8865
+ "list",
8866
+ "--head",
8867
+ branch,
8868
+ "--base",
8869
+ consumer.branch,
8870
+ "--state",
8871
+ "open",
8872
+ "--json",
8873
+ "url",
8874
+ "--jq",
8875
+ ".[0].url"
8876
+ ], { cwd: clone }).catch(() => "")).trim();
8877
+ if (existing) {
8878
+ ctx.say(`\u2022 ${consumer.rel}: pull request updated: ${existing}`);
8879
+ summary.pulls.push(existing);
8880
+ return;
8881
+ }
8882
+ const sites = consumer.sites.map((s) => s.rel);
8883
+ const conflicted = sites.filter((s) => summary.conflicted.includes(s));
8884
+ const body2 = [
8885
+ `Train ${train} (\xA7 1.5 PLX): \`copier update\` at ${sites.join(", ")}, the marker, the lockfile where manifests moved, and the workflow refs.`,
8886
+ conflicted.length > 0 ? `Conflicts to resolve, committed with their markers: ${conflicted.join(", ")}.` : "Merged clean at every site.",
8887
+ "Opened by `plx ship`."
8888
+ ].join("\n\n");
8889
+ try {
8890
+ await ctx.apply([
8891
+ run("gh", [
8892
+ "pr",
8893
+ "create",
8894
+ "--base",
8895
+ consumer.branch,
8896
+ "--head",
8897
+ branch,
8898
+ "--title",
8899
+ `chore: train ${train}`,
8900
+ "--body",
8901
+ body2
8902
+ ], { cwd: clone })
8903
+ ]);
8904
+ } catch (error) {
8905
+ ctx.say(` \u2717 FAILED \u2014 pull request${detail(error)}`);
8906
+ summary.failed.push(consumer.rel);
8907
+ return;
8908
+ }
8909
+ const url = (await ctx.read("gh", [
8910
+ "pr",
8911
+ "list",
8912
+ "--head",
8913
+ branch,
8914
+ "--base",
8915
+ consumer.branch,
8916
+ "--state",
8917
+ "open",
8918
+ "--json",
8919
+ "url",
8920
+ "--jq",
8921
+ ".[0].url"
8922
+ ], {
8923
+ cwd: clone
8924
+ }).catch(() => "")).trim();
8925
+ ctx.say(`\u2022 ${consumer.rel}: pull request opened${url ? `: ${url}` : ""}`);
8926
+ summary.pulls.push(url || `${consumer.rel} ${branch}`);
8793
8927
  }
8794
8928
  var detail = (error) => {
8795
8929
  const message = error instanceof Error ? error.message : String(error);
@@ -8963,59 +9097,118 @@ async function preflightSelf(ctx, running) {
8963
9097
 
8964
9098
  // ../lib/dist/ship.js
8965
9099
  import { join as join10 } from "node:path";
9100
+ var DEFAULT_WAIT = { interval: 15e3, timeout: 20 * 6e4 };
8966
9101
  async function ship(ctx, options) {
8967
9102
  const upstream = join10(options.workspace, UPSTREAM);
8968
9103
  const propagateOnly = options.propagateOnly ?? false;
8969
9104
  if (options.plx)
8970
9105
  await preflightSelf(ctx, options.plx);
8971
9106
  const train = await readTrain(ctx, upstream);
8972
- if (!propagateOnly)
9107
+ await preflightForge(ctx);
9108
+ if (!propagateOnly) {
8973
9109
  await preflightRelease(ctx, train);
9110
+ await preflightImmutable(ctx, train);
9111
+ }
8974
9112
  const next = resolveVersion(train, options.train, propagateOnly);
8975
9113
  if (!propagateOnly)
8976
- await release(ctx, train, next, options.today ?? today());
9114
+ await release(ctx, train, next, options.today ?? today(), { ...DEFAULT_WAIT, ...options.wait });
8977
9115
  const summary = await propagate(ctx, options.workspace, next);
8978
9116
  return { ...summary, released: !propagateOnly };
8979
9117
  }
8980
- async function release(ctx, train, next, today2) {
9118
+ async function preflightImmutable(ctx, train) {
9119
+ for (const repo of train.repos) {
9120
+ const newest = (await ctx.read("gh", ["api", "repos/{owner}/{repo}/releases?per_page=1", "--jq", ".[0].immutable"], { cwd: repo.path }).catch(() => "")).trim();
9121
+ if (newest === "false")
9122
+ throw new Error(mutableRelease(`${repo.name}: its newest GitHub release is not immutable`));
9123
+ }
9124
+ }
9125
+ var mutableRelease = (what) => `${what} \u2014 enable immutable releases on the plexus-ms organisation (Settings \u2192 General \u2192 Releases); existing releases stay as they are (never delete one: a deleted immutable release burns its number)`;
9126
+ async function release(ctx, train, next, today2, wait) {
8981
9127
  ctx.say(`\u2192 train ${train.last ?? "none"} \u2192 ${next}`);
8982
9128
  for (const repo of train.repos) {
8983
9129
  if (repo.pending.length === 0)
8984
9130
  ctx.say(` ${repo.name}: no changes since ${repo.latest} \u2014 tag only`);
8985
9131
  else {
8986
9132
  ctx.say(` ${repo.name} (since ${repo.latest ?? "the beginning"}):`);
8987
- for (const line of repo.pending)
9133
+ for (const line of repo.pending.slice(0, PENDING_SHOWN))
8988
9134
  ctx.say(` ${line}`);
9135
+ if (repo.pending.length > PENDING_SHOWN)
9136
+ ctx.say(` \u2026 and ${repo.pending.length - PENDING_SHOWN} more`);
8989
9137
  }
8990
9138
  }
8991
- if (!await ctx.confirm(`tag all ${train.repos.length} repos as ${next} and push?`))
9139
+ if (!await ctx.confirm(`tag all ${train.repos.length} repos as ${next}, release them and push?`))
8992
9140
  throw new Error("aborted");
9141
+ const [one = [], two = []] = phasesOf(train);
8993
9142
  try {
8994
- const plan = await planBookkeeping(ctx, train, next, today2);
8995
- await ctx.apply(plan);
8996
- for (const repo of train.repos) {
8997
- const tagged = await ctx.read("git", ["rev-parse", "-q", "--verify", `refs/tags/${next}`], { cwd: repo.path }).then(() => true).catch(() => false);
8998
- if (tagged)
8999
- ctx.say(`\u2022 ${repo.name}: already tagged ${next}`);
9000
- else
9001
- await ctx.apply([run("git", ["tag", "-a", next, "-m", `train ${next}`], { cwd: repo.path })]);
9002
- await ctx.apply([run("git", ["push", "origin", "main", next], { cwd: repo.path })]);
9003
- }
9143
+ await releasePhase(ctx, train, one, next, today2);
9144
+ await awaitPublished(ctx, next, wait);
9145
+ await releasePhase(ctx, train, two, next, today2);
9004
9146
  } catch (error) {
9005
9147
  const message = error instanceof Error ? error.message : String(error);
9006
9148
  throw new Error(`${message}
9007
- \u2717 ship stopped during the release; re-run with --train ${next} to finish it (tagged repos are skipped)`);
9149
+ \u2717 ship stopped during the release; re-run with --train ${next} to finish it (tagged repos and existing releases are skipped; if the registry already has ${next}, update plx first: it is the same code, built from the commit phase one tagged)`);
9008
9150
  }
9009
9151
  }
9010
- async function planBookkeeping(ctx, train, next, today2) {
9152
+ var PENDING_SHOWN = 20;
9153
+ async function releasePhase(ctx, train, repos2, next, today2) {
9154
+ ctx.say(`\u2192 phase: ${repos2.map((r) => r.name).join(", ")}`);
9155
+ await ctx.apply(await planBookkeeping(ctx, train, repos2, next, today2));
9156
+ for (const repo of repos2)
9157
+ await tagAndRelease(ctx, repo, next);
9158
+ }
9159
+ async function tagAndRelease(ctx, repo, next) {
9160
+ const tagged = await ctx.read("git", ["rev-parse", "-q", "--verify", `refs/tags/${next}`], { cwd: repo.path }).then(() => true).catch(() => false);
9161
+ if (tagged)
9162
+ ctx.say(`\u2022 ${repo.name}: already tagged ${next}`);
9163
+ else
9164
+ await ctx.apply([run("git", ["tag", "-a", next, "-m", `train ${next}`], { cwd: repo.path })]);
9165
+ await ctx.apply([run("git", ["push", "origin", "main", next], { cwd: repo.path })]);
9166
+ const released = await ctx.read("gh", ["release", "view", next], { cwd: repo.path }).then(() => true).catch(() => false);
9167
+ if (released) {
9168
+ ctx.say(`\u2022 ${repo.name}: release ${next} exists`);
9169
+ return;
9170
+ }
9171
+ await ctx.apply([
9172
+ run("gh", ["release", "create", next, "--verify-tag", "--generate-notes", "--title", next], { cwd: repo.path })
9173
+ ]);
9174
+ if (ctx.dryRun)
9175
+ return;
9176
+ const immutable = (await ctx.read("gh", ["api", `repos/{owner}/{repo}/releases/tags/${next}`, "--jq", ".immutable"], {
9177
+ cwd: repo.path
9178
+ })).trim();
9179
+ if (immutable !== "true")
9180
+ throw new Error(mutableRelease(`${repo.name}: release ${next} was created mutable, and its tag can still move`));
9181
+ }
9182
+ async function awaitPublished(ctx, next, wait) {
9183
+ const names = TRAIN_PACKAGES.map((p) => p.name);
9184
+ const list2 = names.map((n) => `${n}@${next}`).join(", ");
9185
+ if (ctx.dryRun) {
9186
+ ctx.say(`\u23F3 [dry-run] would wait for ${list2} on npm (${PRIMITIVES}' release workflow publishes them)`);
9187
+ return;
9188
+ }
9189
+ ctx.say(`\u23F3 waiting for ${list2} on npm (${PRIMITIVES}' release workflow publishes them)`);
9190
+ const started = Date.now();
9191
+ let missing = [...names];
9192
+ for (; ; ) {
9193
+ const still = [];
9194
+ for (const name of missing) {
9195
+ const seen = (await ctx.read("npm", ["view", `${name}@${next}`, "version"]).catch(() => "")).trim();
9196
+ if (seen !== next)
9197
+ still.push(name);
9198
+ }
9199
+ missing = still;
9200
+ if (missing.length === 0)
9201
+ break;
9202
+ if (Date.now() - started >= wait.timeout)
9203
+ throw new Error(`${missing.join(", ")} not on npm at ${next} after ${Math.round(wait.timeout / 6e4)} min \u2014 check the release workflow (gh run list -R plexus-ms/${PRIMITIVES})`);
9204
+ await new Promise((resolve10) => setTimeout(resolve10, wait.interval));
9205
+ }
9206
+ ctx.say(` \u2713 ${list2} on npm`);
9207
+ }
9208
+ async function planBookkeeping(ctx, train, repos2, next, today2) {
9011
9209
  const plan = [];
9012
9210
  const touched = /* @__PURE__ */ new Set();
9013
- const repo = (name) => {
9014
- const r = train.repos.find((r2) => r2.name === name);
9015
- if (!r)
9016
- throw new Error(`${name} is not a train repo`);
9017
- return r;
9018
- };
9211
+ const repo = (name) => repos2.find((r) => r.name === name);
9019
9212
  const edit = async (r, rel, what, transform) => {
9020
9213
  const path3 = join10(r.path, rel);
9021
9214
  const text = await ctx.readFile(path3).catch(() => void 0);
@@ -9028,23 +9221,36 @@ async function planBookkeeping(ctx, train, next, today2) {
9028
9221
  plan.push(write(path3, edited));
9029
9222
  touched.add(r);
9030
9223
  };
9031
- await edit(repo("platform"), "ansible/galaxy.yml", `version \u2192 ${next}`, (t) => bumpGalaxyVersion(t, next));
9032
- await edit(repo("preset-repo-platform"), "template/requirements.yml", `plexus.platform pin \u2192 ${next}`, (t) => bumpCollectionPin(t, next));
9033
- for (const r of train.repos) {
9034
- const files = await ctx.read("git", ["grep", "-l", WORKFLOW_REF, "--", "*.yml", "*.yaml", "*.jinja"], { cwd: r.path }).catch(() => "");
9035
- for (const f of files.split("\n").filter(Boolean))
9036
- await edit(r, f, `ci-cd refs \u2192 @${next}`, (t) => bumpWorkflowRefs(t, next));
9224
+ const grep = async (r, needle, pathspecs) => (await ctx.read("git", ["grep", "-l", needle, "--", ...pathspecs], { cwd: r.path }).catch(() => "")).split("\n").filter(Boolean);
9225
+ const primitives = repo(PRIMITIVES);
9226
+ if (primitives) {
9227
+ await edit(primitives, "ansible/galaxy.yml", `version \u2192 ${next}`, (t) => bumpGalaxyVersion(t, next));
9228
+ for (const p of TRAIN_PACKAGES)
9229
+ await edit(primitives, `${p.dir}/package.json`, `${p.name} \u2192 ${next}`, (t) => bumpManifestVersion(t, next));
9230
+ }
9231
+ const platformPreset = repo("preset-repo-platform");
9232
+ if (platformPreset)
9233
+ await edit(platformPreset, "template/requirements.yml", `plexus.platform pin \u2192 ${next}`, (t) => bumpCollectionPin(t, next));
9234
+ for (const r of repos2) {
9235
+ for (const f of await grep(r, WORKFLOW_REF, ["*.yml", "*.yaml", "*.jinja"]))
9236
+ await edit(r, f, `workflow refs \u2192 @${next}`, (t) => bumpWorkflowRefs(t, next));
9237
+ for (const f of await grep(r, PLX_PIN, ["*.toml", "*.toml.jinja"]))
9238
+ await edit(r, f, `plx pin \u2192 ${next}`, (t) => bumpPlxPin(t, next));
9239
+ for (const f of await grep(r, `"${CONFIG_PIN}"`, ["*package.json", "*package.json.jinja"]))
9240
+ await edit(r, f, `${CONFIG_PIN} pin \u2192 ${next}`, (t) => bumpConfigPin(t, next));
9037
9241
  }
9038
9242
  const comp = repo("compendium");
9039
- await edit(comp, MAP_PAGE, `version \u2192 ${next}, timestamp \u2192 ${today2}`, (t) => stampStandard(t, next, today2));
9040
- for (const page of await modulePages(ctx, comp.path)) {
9041
- const text = await ctx.readFile(join10(comp.path, page));
9042
- if (frontmatterField(text, "maturity") !== "standard")
9043
- continue;
9044
- const previous = comp.tag ? await ctx.read("git", ["show", `${comp.tag}:${page}`], { cwd: comp.path }).catch(() => "") : "";
9045
- if (extractRequirements(text).join("\n") === extractRequirements(previous).join("\n"))
9046
- continue;
9047
- await edit(comp, page, `requirements changed since ${comp.latest ?? "the beginning"} \u2192 changed: ${next}`, (t) => stampChanged(t, next));
9243
+ if (comp) {
9244
+ await edit(comp, MAP_PAGE, `version \u2192 ${next}, timestamp \u2192 ${today2}`, (t) => stampStandard(t, next, today2));
9245
+ for (const page of await modulePages(ctx, comp.path)) {
9246
+ const text = await ctx.readFile(join10(comp.path, page));
9247
+ if (frontmatterField(text, "maturity") !== "standard")
9248
+ continue;
9249
+ const previous = comp.tag ? await ctx.read("git", ["show", `${comp.tag}:${page}`], { cwd: comp.path }).catch(() => "") : "";
9250
+ if (extractRequirements(text).join("\n") === extractRequirements(previous).join("\n"))
9251
+ continue;
9252
+ await edit(comp, page, `requirements changed since ${comp.latest ?? "the beginning"} \u2192 changed: ${next}`, (t) => stampChanged(t, next));
9253
+ }
9048
9254
  }
9049
9255
  for (const r of train.repos) {
9050
9256
  if (!touched.has(r))
@@ -14035,9 +14241,9 @@ function renderReport(report, s, showCurrent) {
14035
14241
  // src/commands/overview.ts
14036
14242
  import { resolve as resolve6 } from "node:path";
14037
14243
  function register6(program2) {
14038
- program2.command("overview").description("map the git repos, copier templates and projects, and Plexus units under a directory").argument("[dir]", "directory to map", ".").option("--color", "colour the tree even when piped").option("--no-color", "never colour the tree").action(async (dir, opts, command) => {
14244
+ program2.command("overview").description("map the git repos, copier templates and projects, and Plexus units under a directory").argument("[dir]", "directory to map", ".").option("-s, --sync", "fetch every repo first, to show branches behind (or ahead of) their upstream").option("--color", "colour the tree even when piped").option("--no-color", "never colour the tree").action(async (dir, opts, command) => {
14039
14245
  const ctx = contextOf2(command);
14040
- const tree = await overview(ctx, resolve6(dir));
14246
+ const tree = await overview(ctx, resolve6(dir), { fetch: opts.sync ?? false });
14041
14247
  const s = style(opts.color ?? colorByDefault());
14042
14248
  ctx.result(tree, (t) => renderTree(t, s, ctx.verbose >= 1));
14043
14249
  });
@@ -14061,9 +14267,10 @@ function renderTree(root, s, modules) {
14061
14267
  function annotate(dir, s, modules) {
14062
14268
  const tags = [];
14063
14269
  if (dir.git) {
14064
- const { branch, dirty, tag, remote } = dir.git;
14270
+ const { branch, dirty, tag, remote, ahead, behind } = dir.git;
14271
+ const drift = `${behind ? ` \u2193${behind}` : ""}${ahead ? ` \u2191${ahead}` : ""}`;
14065
14272
  const rest = `${tag ? ` ${tag}` : ""}${remote ? ` \u2192 ${remote}` : ""}]`;
14066
- tags.push(`${s.ok(`[${branch}`)}${dirty ? s.warn("*") : ""}${s.ok(rest)}`);
14273
+ tags.push(`${s.ok(`[${branch}`)}${dirty ? s.warn("*") : ""}${drift ? s.warn(drift) : ""}${s.ok(rest)}`);
14067
14274
  }
14068
14275
  if (dir.template) tags.push(s.note(`[template ${dir.template.tag ?? "untagged"}]`));
14069
14276
  if (dir.copier) {
@@ -14085,7 +14292,9 @@ import { resolve as resolve8 } from "node:path";
14085
14292
  // src/commands/ship.ts
14086
14293
  import { resolve as resolve7 } from "node:path";
14087
14294
  function register7(program2) {
14088
- program2.command("ship").description("release the train: tag every train repo identically, then copier-update every consumer").argument("[dir]", "the workspace: plexus-ms/ and the consumers sit under it", ".").option("--train <version>", "the train version, plain semver (default: minor+1 over the latest train)").action(async (dir, opts, command) => {
14295
+ program2.command("ship").description(
14296
+ "release the train: tag and release every train repo identically, then open a pull request per consumer"
14297
+ ).argument("[dir]", "the workspace: plexus-ms/ and the consumers sit under it", ".").option("--train <version>", "the train version, plain semver (default: minor+1 over the latest train)").action(async (dir, opts, command) => {
14089
14298
  const ctx = contextOf2(command);
14090
14299
  const result = await ship(ctx, { workspace: resolve7(dir), train: opts.train, plx: version });
14091
14300
  ctx.result(result, (r) => renderSummary(r, `plx -y ship --train ${r.train} ${dir}`));
@@ -14098,12 +14307,13 @@ function renderSummary(s, realCommand) {
14098
14307
  for (const p of s.conflicted) lines.push(` \u2717 conflicts: ${p}`);
14099
14308
  for (const p of s.failed) lines.push(` \u2717 failed: ${p}`);
14100
14309
  for (const { path: path3, reason } of s.skipped) lines.push(` \u26A0 skipped: ${path3} (${reason})`);
14310
+ for (const url of s.pulls) lines.push(` \u2192 pull: ${url}`);
14101
14311
  const clean = isClean(s);
14102
14312
  if (s.preview) {
14103
14313
  lines.push(" (preview only \u2014 nothing was changed)");
14104
14314
  if (clean) lines.push(`\u2713 dry run clean \u2014 run it for real: ${realCommand}`);
14105
14315
  } else if (clean) {
14106
- lines.push("\u2713 done \u2014 review consumer diffs, commit (web repos on develop, platform repos on main), push");
14316
+ lines.push("\u2713 done \u2014 review and merge the pull requests");
14107
14317
  }
14108
14318
  if (!clean) lines.push("\u2717 needs attention \u2014 see above; fix the consumer, then re-run propagate");
14109
14319
  return lines.join("\n");
@@ -14111,7 +14321,7 @@ function renderSummary(s, realCommand) {
14111
14321
 
14112
14322
  // src/commands/propagate.ts
14113
14323
  function register8(program2) {
14114
- program2.command("propagate").description("copier-update every consumer of the presets to the current train, and pin their ci-cd workflow refs").argument("[dir]", "the workspace: plexus-ms/ and the consumers sit under it", ".").option("--train <version>", "the train to propagate (default: the current one)").action(async (dir, opts, command) => {
14324
+ program2.command("propagate").description("copier-update every consumer of the presets to the current train, as one pull request per consumer").argument("[dir]", "the workspace: plexus-ms/ and the consumers sit under it", ".").option("--train <version>", "the train to propagate (default: the current one)").action(async (dir, opts, command) => {
14115
14325
  const ctx = contextOf2(command);
14116
14326
  const result = await ship(ctx, { workspace: resolve8(dir), train: opts.train, propagateOnly: true, plx: version });
14117
14327
  ctx.result(result, (r) => renderSummary(r, `plx -y propagate --train ${r.train} ${dir}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexus-ms/cli",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "plx, the Plexus command-line tool: a single-script adapter over @plexus-ms/lib",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@inquirer/prompts": "^8.7.4",
27
- "@plexus-ms/lib": "0.8.1",
27
+ "@plexus-ms/lib": "0.10.0",
28
28
  "commander": "^15.0.0"
29
29
  },
30
30
  "devDependencies": {