@plexus-ms/cli 0.4.0 → 0.5.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 +298 -143
  2. package/package.json +3 -3
package/dist/plx.js CHANGED
@@ -7268,7 +7268,7 @@ var require_public_api = __commonJS({
7268
7268
  }
7269
7269
  return doc;
7270
7270
  }
7271
- function parse4(src, reviver, options) {
7271
+ function parse5(src, reviver, options) {
7272
7272
  let _reviver = void 0;
7273
7273
  if (typeof reviver === "function") {
7274
7274
  _reviver = reviver;
@@ -7309,7 +7309,7 @@ var require_public_api = __commonJS({
7309
7309
  return value.toString(options);
7310
7310
  return new Document.Document(value, _replacer, options).toString(options);
7311
7311
  }
7312
- exports.parse = parse4;
7312
+ exports.parse = parse5;
7313
7313
  exports.parseAllDocuments = parseAllDocuments;
7314
7314
  exports.parseDocument = parseDocument;
7315
7315
  exports.stringify = stringify;
@@ -7655,9 +7655,83 @@ async function readCatalog(ctx, compendium) {
7655
7655
  return catalog;
7656
7656
  }
7657
7657
 
7658
+ // ../lib/dist/changedApps.js
7659
+ import { join as join2 } from "node:path";
7660
+ var BOOKKEEPING = /(^|\/)(PLEXUS\.md|\.copier-answers\.yml)$/;
7661
+ var GLOBALS = /^(pnpm-lock\.yaml|pnpm-workspace\.yaml|package\.json|turbo\.json|mise\.toml|\.npmrc|\.node-version)$/;
7662
+ var NO_REF = /^0*$/;
7663
+ async function selectChangedApps(input) {
7664
+ const all = [...input.all].sort();
7665
+ if (input.changed === void 0)
7666
+ return { apps: all, reason: "no base ref: every app counts as changed" };
7667
+ const changed = input.changed.filter((f) => !BOOKKEEPING.test(f));
7668
+ const under = (app) => changed.some((f) => f.startsWith(`apps/${app}/`));
7669
+ if (!input.workspace)
7670
+ return { apps: all.filter(under), reason: "files under the apps changed" };
7671
+ if (changed.length === 0)
7672
+ return { apps: [], reason: "nothing but bookkeeping changed" };
7673
+ if (changed.some((f) => GLOBALS.test(f)))
7674
+ return { apps: all, reason: "a workspace-global input changed: every app counts as changed" };
7675
+ const outside = changed.some((f) => !f.startsWith("apps/"));
7676
+ const listed = new Set(await input.dependents?.() ?? []);
7677
+ return {
7678
+ apps: all.filter((app) => listed.has(app) && (outside || under(app))),
7679
+ reason: "sources or their dependencies changed"
7680
+ };
7681
+ }
7682
+ async function readChangedApps(ctx, dir, from, to = "HEAD") {
7683
+ const git2 = (args) => ctx.read("git", args, { cwd: dir });
7684
+ const workspace = await exists(ctx, join2(dir, "pnpm-workspace.yaml"));
7685
+ const all = await deployableApps(ctx, dir, workspace);
7686
+ let changed;
7687
+ if (from !== "" && !NO_REF.test(from) && await isCommit(git2, from))
7688
+ changed = (await git2(["diff", "--name-only", from, to])).split("\n").filter(Boolean);
7689
+ const dependents = async () => {
7690
+ const out = await ctx.read("pnpm", ["--filter", `...[${from}...${to}]`, "ls", "--depth", "-1", "--parseable"], {
7691
+ cwd: dir
7692
+ });
7693
+ return out.split("\n").map((line) => /(?:^|\/)apps\/([^/]+)$/.exec(line.trim())?.[1]).filter((app) => app !== void 0);
7694
+ };
7695
+ const result = await selectChangedApps({ workspace, all, changed, dependents });
7696
+ ctx.say(`\u2192 ${result.reason}`, 1);
7697
+ return result;
7698
+ }
7699
+ async function isCommit(git2, ref) {
7700
+ try {
7701
+ await git2(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
7702
+ return true;
7703
+ } catch {
7704
+ return false;
7705
+ }
7706
+ }
7707
+ async function deployableApps(ctx, dir, workspace) {
7708
+ const entries = await ctx.readDir(join2(dir, "apps")).catch(() => []);
7709
+ const apps = [];
7710
+ for (const e of entries) {
7711
+ if (!e.directory)
7712
+ continue;
7713
+ const marks = workspace ? ["package.json"] : ["docker/compose.yaml", "compose.yaml"];
7714
+ for (const mark of marks) {
7715
+ if (await exists(ctx, join2(dir, "apps", e.name, mark))) {
7716
+ apps.push(e.name);
7717
+ break;
7718
+ }
7719
+ }
7720
+ }
7721
+ return apps.sort();
7722
+ }
7723
+ async function exists(ctx, path3) {
7724
+ try {
7725
+ await ctx.readFile(path3);
7726
+ return true;
7727
+ } catch {
7728
+ return false;
7729
+ }
7730
+ }
7731
+
7658
7732
  // ../lib/dist/changeset.js
7659
7733
  var import_yaml2 = __toESM(require_dist(), 1);
7660
- import { join as join2 } from "node:path";
7734
+ import { join as join3 } from "node:path";
7661
7735
 
7662
7736
  // ../lib/dist/io.js
7663
7737
  import { spawn } from "node:child_process";
@@ -7680,7 +7754,7 @@ function realIo(prompt) {
7680
7754
  stdout: process.stdout,
7681
7755
  stderr: process.stderr,
7682
7756
  prompt: prompt && isInteractive() ? prompt : void 0,
7683
- exec: (command, args, options = {}) => new Promise((resolve7, reject) => {
7757
+ exec: (command, args, options = {}) => new Promise((resolve9, reject) => {
7684
7758
  const child = spawn(command, args, {
7685
7759
  cwd: options.cwd,
7686
7760
  env: options.env,
@@ -7703,7 +7777,7 @@ function realIo(prompt) {
7703
7777
  child.on("error", reject);
7704
7778
  child.on("close", (code) => {
7705
7779
  if (code === 0)
7706
- resolve7(stdout);
7780
+ resolve9(stdout);
7707
7781
  else
7708
7782
  reject(new ExecError(render(command, args), code, stderr));
7709
7783
  });
@@ -7794,7 +7868,7 @@ ${header.join("\n")}
7794
7868
 
7795
7869
  ${lines.join("\n")}
7796
7870
  `;
7797
- return { path: path3, releases, changes, content, plan: [write(root ? join2(root, path3) : path3, content)] };
7871
+ return { path: path3, releases, changes, content, plan: [write(root ? join3(root, path3) : path3, content)] };
7798
7872
  }
7799
7873
  function describeChange(c) {
7800
7874
  const peer = c.field === "peerDependencies" ? " (peer)" : "";
@@ -7843,7 +7917,7 @@ async function readChangeset(ctx, dir, base, branch) {
7843
7917
  };
7844
7918
  const current = async (path4) => {
7845
7919
  try {
7846
- return await ctx.readFile(join2(dir, path4));
7920
+ return await ctx.readFile(join3(dir, path4));
7847
7921
  } catch {
7848
7922
  return void 0;
7849
7923
  }
@@ -7859,7 +7933,7 @@ async function readChangeset(ctx, dir, base, branch) {
7859
7933
  }
7860
7934
  const config = await current(`${CHANGESET_DIR}/config.json`);
7861
7935
  const ignore = config ? JSON.parse(config).ignore ?? [] : [];
7862
- const entries = await ctx.readDir(join2(dir, CHANGESET_DIR)).catch(() => []);
7936
+ const entries = await ctx.readDir(join3(dir, CHANGESET_DIR)).catch(() => []);
7863
7937
  const path3 = changesetPath(head);
7864
7938
  return planChangeset({
7865
7939
  branch: head,
@@ -7879,7 +7953,7 @@ async function expand(ctx, root, glob) {
7879
7953
  if (star < 0)
7880
7954
  return [glob.replace(/\/$/, "")];
7881
7955
  const parent = glob.slice(0, star).replace(/\/$/, "");
7882
- const entries = await ctx.readDir(join2(root, parent)).catch(() => []);
7956
+ const entries = await ctx.readDir(join3(root, parent)).catch(() => []);
7883
7957
  return entries.filter((e) => e.directory && !e.name.startsWith(".")).map((e) => parent ? `${parent}/${e.name}` : e.name).sort();
7884
7958
  }
7885
7959
 
@@ -7975,6 +8049,8 @@ var Context = class {
7975
8049
  */
7976
8050
  result(data, human = renderHuman) {
7977
8051
  const text = this.json ? JSON.stringify(data, null, 2) : human(data);
8052
+ if (text === "")
8053
+ return;
7978
8054
  this.io.stdout.write(text.endsWith("\n") ? text : `${text}
7979
8055
  `);
7980
8056
  }
@@ -8069,6 +8145,13 @@ function parseAnswers(text) {
8069
8145
  };
8070
8146
  }
8071
8147
 
8148
+ // ../lib/dist/deployTargets.js
8149
+ var import_yaml4 = __toESM(require_dist(), 1);
8150
+ import { join as join5 } from "node:path";
8151
+
8152
+ // ../lib/dist/train.js
8153
+ import { join as join4 } from "node:path";
8154
+
8072
8155
  // ../lib/dist/doctor.js
8073
8156
  import { relative as relative2 } from "node:path";
8074
8157
  function diagnose(root, catalog) {
@@ -8128,6 +8211,130 @@ function compareTrains(a, b) {
8128
8211
  return 0;
8129
8212
  }
8130
8213
 
8214
+ // ../lib/dist/train.js
8215
+ var TRAIN_REPOS = [
8216
+ "platform",
8217
+ "ci-cd",
8218
+ "preset-repo-web",
8219
+ "preset-app-nextjs",
8220
+ "preset-repo-platform",
8221
+ "compendium"
8222
+ ];
8223
+ var UPSTREAM = "plexus-ms";
8224
+ var isTrainVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
8225
+ function nextMinor(version2) {
8226
+ const [major = "0", minor = "0"] = version2.split(".");
8227
+ return `${major}.${Number(minor) + 1}.0`;
8228
+ }
8229
+ function latestTag(tags) {
8230
+ return tags.filter((t) => /^v?\d/.test(t)).sort((a, b) => compareTrains(a.replace(/^v/, ""), b.replace(/^v/, ""))).at(-1);
8231
+ }
8232
+ async function readTrain(ctx, upstream) {
8233
+ const repos = [];
8234
+ const all = /* @__PURE__ */ new Set();
8235
+ for (const name of TRAIN_REPOS) {
8236
+ const path3 = join4(upstream, name);
8237
+ try {
8238
+ await ctx.read("git", ["rev-parse", "--git-dir"], { cwd: path3 });
8239
+ } catch {
8240
+ throw new Error(`${path3} is not a git repo`);
8241
+ }
8242
+ try {
8243
+ await ctx.read("git", ["fetch", "origin", "--tags", "--quiet"], { cwd: path3 });
8244
+ } catch {
8245
+ throw new Error(`${name}: fetch from origin failed`);
8246
+ }
8247
+ const tags = (await ctx.read("git", ["tag", "--list", "v*", "[0-9]*"], { cwd: path3 })).split("\n").filter(Boolean);
8248
+ for (const t of tags)
8249
+ all.add(t.replace(/^v/, ""));
8250
+ const tag = latestTag(tags);
8251
+ const range = tag ? `${tag}..HEAD` : "HEAD";
8252
+ const pending = (await ctx.read("git", ["log", "--oneline", range], { cwd: path3 })).split("\n").filter(Boolean);
8253
+ repos.push({ name, path: path3, tag, latest: tag?.replace(/^v/, ""), pending });
8254
+ }
8255
+ const latest = repos.map((r) => r.latest);
8256
+ return { repos, last: latestTag([...all]), skew: latest.some((l) => l !== latest[0]) };
8257
+ }
8258
+ async function preflightRelease(ctx, train) {
8259
+ for (const { name, path: path3 } of train.repos) {
8260
+ const branch = (await ctx.read("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path3 })).trim();
8261
+ if (branch !== "main")
8262
+ throw new Error(`${name} is on '${branch}', trains cut from main`);
8263
+ if ((await ctx.read("git", ["status", "--porcelain"], { cwd: path3 })).trim() !== "")
8264
+ throw new Error(`${name} has uncommitted changes \u2014 commit first`);
8265
+ const behind = (await ctx.read("git", ["rev-list", "--count", "main..origin/main"], { cwd: path3 })).trim();
8266
+ if (behind !== "0")
8267
+ throw new Error(`${name} is ${behind} commit(s) behind origin/main \u2014 pull first, or the tag lands on a stale commit`);
8268
+ }
8269
+ }
8270
+ function resolveVersion(train, explicit, propagateOnly) {
8271
+ if (explicit !== void 0 && !isTrainVersion(explicit))
8272
+ throw new UsageError(`'${explicit}' is not plain semver (X.Y.Z, no v)`);
8273
+ if (propagateOnly) {
8274
+ const v = explicit ?? train.last;
8275
+ if (!v)
8276
+ throw new Error("no train version found to propagate");
8277
+ return v;
8278
+ }
8279
+ if (explicit)
8280
+ return explicit;
8281
+ if (train.skew) {
8282
+ const report = train.repos.map((r) => ` ${r.name}: ${r.latest ?? "none"}`).join("\n");
8283
+ throw new Error(`the train repos' latest tags disagree:
8284
+ ${report}
8285
+ pass the version explicitly (--train ${train.last}) to finish the half-tagged train, or a higher one to start a new one`);
8286
+ }
8287
+ return train.last ? nextMinor(train.last) : "0.1.0";
8288
+ }
8289
+ var UsageError = class extends Error {
8290
+ };
8291
+
8292
+ // ../lib/dist/deployTargets.js
8293
+ var VARS_DIRS = ["group_vars", "host_vars"];
8294
+ function parseInventoryFile(path3, text) {
8295
+ const data = (0, import_yaml4.parse)(text);
8296
+ const vars = data !== null && typeof data === "object" ? data : {};
8297
+ const target = typeof vars.plexus_deploy_target === "string" ? vars.plexus_deploy_target : void 0;
8298
+ const records = Array.isArray(vars.plexus_apps) ? vars.plexus_apps : [];
8299
+ const apps = records.map((r) => r !== null && typeof r === "object" ? r.name : void 0).filter((n) => typeof n === "string");
8300
+ return { path: path3, target, apps };
8301
+ }
8302
+ function selectDeployTargets(files) {
8303
+ const targets = {};
8304
+ const declaredIn = {};
8305
+ for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
8306
+ if (file.target === void 0)
8307
+ continue;
8308
+ for (const app of file.apps) {
8309
+ if (app in targets)
8310
+ throw new Error(`app "${app}" is declared with a deploy target in both ${declaredIn[app]} and ${file.path}`);
8311
+ targets[app] = file.target;
8312
+ declaredIn[app] = file.path;
8313
+ }
8314
+ }
8315
+ return Object.fromEntries(Object.entries(targets).sort(([a], [b]) => a.localeCompare(b)));
8316
+ }
8317
+ async function readDeployTargets(ctx, inventory) {
8318
+ const files = [];
8319
+ let found = false;
8320
+ for (const sub of VARS_DIRS) {
8321
+ const dir = join5(inventory, sub);
8322
+ const entries = await ctx.readDir(dir).catch(() => void 0);
8323
+ if (entries === void 0)
8324
+ continue;
8325
+ found = true;
8326
+ for (const e of entries) {
8327
+ if (e.directory || !/\.ya?ml$/.test(e.name))
8328
+ continue;
8329
+ const path3 = join5(dir, e.name);
8330
+ files.push(parseInventoryFile(path3, await ctx.readFile(path3)));
8331
+ }
8332
+ }
8333
+ if (!found && await ctx.readDir(inventory).catch(() => void 0) === void 0)
8334
+ throw new UsageError(`no inventory directory at ${inventory}`);
8335
+ return selectDeployTargets(files);
8336
+ }
8337
+
8131
8338
  // ../lib/dist/model.js
8132
8339
  function kindOf(declaration) {
8133
8340
  const has = (code) => declaration.modules.includes(code);
@@ -8149,7 +8356,7 @@ function isNotable(dir) {
8149
8356
  }
8150
8357
 
8151
8358
  // ../lib/dist/overview.js
8152
- import { join as join3 } from "node:path";
8359
+ import { join as join6 } from "node:path";
8153
8360
  var PRUNED = /* @__PURE__ */ new Set([
8154
8361
  "node_modules",
8155
8362
  ".hg",
@@ -8197,7 +8404,7 @@ async function walk(ctx, path3, name, options) {
8197
8404
  }
8198
8405
  const subdirs = entries.filter((e) => e.directory && e.name !== ".git" && !PRUNED.has(e.name)).map((e) => e.name).sort();
8199
8406
  for (const sub of subdirs) {
8200
- const child = await walk(ctx, join3(path3, sub), sub, options);
8407
+ const child = await walk(ctx, join6(path3, sub), sub, options);
8201
8408
  if (isNotable(child) || child.children.length > 0)
8202
8409
  dir.children.push(child);
8203
8410
  }
@@ -8205,7 +8412,7 @@ async function walk(ctx, path3, name, options) {
8205
8412
  }
8206
8413
  async function facet(ctx, dir, file, read) {
8207
8414
  try {
8208
- read(await ctx.readFile(join3(dir.path, file)));
8415
+ read(await ctx.readFile(join6(dir.path, file)));
8209
8416
  } catch (error) {
8210
8417
  dir.problems ??= [];
8211
8418
  dir.problems.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
@@ -8244,93 +8451,12 @@ async function templateFacet(ctx, cwd) {
8244
8451
  }
8245
8452
 
8246
8453
  // ../lib/dist/propagate.js
8247
- import { join as join5, relative as relative3 } from "node:path";
8248
-
8249
- // ../lib/dist/train.js
8250
- import { join as join4 } from "node:path";
8251
- var TRAIN_REPOS = [
8252
- "platform",
8253
- "ci-cd",
8254
- "preset-repo-web",
8255
- "preset-app-nextjs",
8256
- "preset-repo-platform",
8257
- "compendium"
8258
- ];
8259
- var UPSTREAM = "plexus-ms";
8260
- var isTrainVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
8261
- function nextMinor(version2) {
8262
- const [major = "0", minor = "0"] = version2.split(".");
8263
- return `${major}.${Number(minor) + 1}.0`;
8264
- }
8265
- function latestTag(tags) {
8266
- return tags.filter((t) => /^v?\d/.test(t)).sort((a, b) => compareTrains(a.replace(/^v/, ""), b.replace(/^v/, ""))).at(-1);
8267
- }
8268
- async function readTrain(ctx, upstream) {
8269
- const repos = [];
8270
- const all = /* @__PURE__ */ new Set();
8271
- for (const name of TRAIN_REPOS) {
8272
- const path3 = join4(upstream, name);
8273
- try {
8274
- await ctx.read("git", ["rev-parse", "--git-dir"], { cwd: path3 });
8275
- } catch {
8276
- throw new Error(`${path3} is not a git repo`);
8277
- }
8278
- try {
8279
- await ctx.read("git", ["fetch", "origin", "--tags", "--quiet"], { cwd: path3 });
8280
- } catch {
8281
- throw new Error(`${name}: fetch from origin failed`);
8282
- }
8283
- const tags = (await ctx.read("git", ["tag", "--list", "v*", "[0-9]*"], { cwd: path3 })).split("\n").filter(Boolean);
8284
- for (const t of tags)
8285
- all.add(t.replace(/^v/, ""));
8286
- const tag = latestTag(tags);
8287
- const range = tag ? `${tag}..HEAD` : "HEAD";
8288
- const pending = (await ctx.read("git", ["log", "--oneline", range], { cwd: path3 })).split("\n").filter(Boolean);
8289
- repos.push({ name, path: path3, tag, latest: tag?.replace(/^v/, ""), pending });
8290
- }
8291
- const latest = repos.map((r) => r.latest);
8292
- return { repos, last: latestTag([...all]), skew: latest.some((l) => l !== latest[0]) };
8293
- }
8294
- async function preflightRelease(ctx, train) {
8295
- for (const { name, path: path3 } of train.repos) {
8296
- const branch = (await ctx.read("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path3 })).trim();
8297
- if (branch !== "main")
8298
- throw new Error(`${name} is on '${branch}', trains cut from main`);
8299
- if ((await ctx.read("git", ["status", "--porcelain"], { cwd: path3 })).trim() !== "")
8300
- throw new Error(`${name} has uncommitted changes \u2014 commit first`);
8301
- const behind = (await ctx.read("git", ["rev-list", "--count", "main..origin/main"], { cwd: path3 })).trim();
8302
- if (behind !== "0")
8303
- throw new Error(`${name} is ${behind} commit(s) behind origin/main \u2014 pull first, or the tag lands on a stale commit`);
8304
- }
8305
- }
8306
- function resolveVersion(train, explicit, propagateOnly) {
8307
- if (explicit !== void 0 && !isTrainVersion(explicit))
8308
- throw new UsageError(`'${explicit}' is not plain semver (X.Y.Z, no v)`);
8309
- if (propagateOnly) {
8310
- const v = explicit ?? train.last;
8311
- if (!v)
8312
- throw new Error("no train version found to propagate");
8313
- return v;
8314
- }
8315
- if (explicit)
8316
- return explicit;
8317
- if (train.skew) {
8318
- const report = train.repos.map((r) => ` ${r.name}: ${r.latest ?? "none"}`).join("\n");
8319
- throw new Error(`the train repos' latest tags disagree:
8320
- ${report}
8321
- pass the version explicitly (--train ${train.last}) to finish the half-tagged train, or a higher one to start a new one`);
8322
- }
8323
- return train.last ? nextMinor(train.last) : "0.1.0";
8324
- }
8325
- var UsageError = class extends Error {
8326
- };
8327
-
8328
- // ../lib/dist/propagate.js
8454
+ import { join as join7, relative as relative3 } from "node:path";
8329
8455
  var isClean = (s) => s.conflicted.length === 0 && s.failed.length === 0 && s.skipped.length === 0;
8330
8456
  var PREVIEW_BASE = "ship-base";
8331
8457
  async function propagate(ctx, workspace, train) {
8332
8458
  const summary = { train, preview: ctx.dryRun, clean: [], conflicted: [], failed: [], skipped: [] };
8333
- const upstream = join5(workspace, UPSTREAM);
8459
+ const upstream = join7(workspace, UPSTREAM);
8334
8460
  const tree = await overview(ctx, workspace);
8335
8461
  const upstreamDir = tree.children.find((c) => c.path === upstream);
8336
8462
  const presets = TRAIN_REPOS.filter((r) => upstreamDir?.children.some((c) => c.name === r && c.template));
@@ -8347,7 +8473,7 @@ async function propagate(ctx, workspace, train) {
8347
8473
  const cloneOf = async (consumer) => {
8348
8474
  let clone = clones.get(consumer.path);
8349
8475
  if (!clone) {
8350
- clone = join5(scratch ?? "", consumer.rel);
8476
+ clone = join7(scratch ?? "", consumer.rel);
8351
8477
  await ctx.stage(run("git", ["clone", "--quiet", consumer.path, clone]));
8352
8478
  await ctx.stage(run("git", ["tag", PREVIEW_BASE], { cwd: clone }));
8353
8479
  clones.set(consumer.path, clone);
@@ -8374,7 +8500,7 @@ async function propagate(ctx, workspace, train) {
8374
8500
  if (sites.length === 0)
8375
8501
  ctx.say(`\u2022 no consumers of ${presetSource(preset)} found under ${workspace}`);
8376
8502
  for (const [consumer, site] of sites)
8377
- await updateSite(ctx, consumer, site, join5(upstream, preset), summary, cloneOf, previewCommit);
8503
+ await updateSite(ctx, consumer, site, join7(upstream, preset), summary, cloneOf, previewCommit);
8378
8504
  }
8379
8505
  await refreshLockfiles(ctx, active, summary, clones);
8380
8506
  await bumpConsumerRefs(ctx, consumers, train);
@@ -8391,9 +8517,9 @@ async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, pr
8391
8517
  if (ctx.dryRun) {
8392
8518
  ctx.say(`\u2192 ${site.rel}: copier update (preview from ${localPreset}@main)`);
8393
8519
  repo = await cloneOf(consumer);
8394
- target = join5(repo, relative3(consumer.path, site.path));
8395
- const answers = await ctx.readFile(join5(site.path, ANSWERS_FILE));
8396
- await ctx.stage(write(join5(target, ANSWERS_FILE), answers.replace(/^_src_path: .*$/m, `_src_path: ${localPreset}`)));
8520
+ target = join7(repo, relative3(consumer.path, site.path));
8521
+ const answers = await ctx.readFile(join7(site.path, ANSWERS_FILE));
8522
+ await ctx.stage(write(join7(target, ANSWERS_FILE), answers.replace(/^_src_path: .*$/m, `_src_path: ${localPreset}`)));
8397
8523
  await previewCommit(repo, `plx preview: ${site.rel} reads ${localPreset} from the local checkout`);
8398
8524
  await ctx.stage(run("copier", ["update", "--skip-answered", "--trust", "--defaults", "--skip-tasks", "--vcs-ref", "main"], {
8399
8525
  cwd: target
@@ -8412,7 +8538,7 @@ async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, pr
8412
8538
  let conflict = hasUnmergedEntries(status);
8413
8539
  const changed = (await ctx.read("git", ["diff", "--name-only"], { cwd: repo })).split("\n").filter(Boolean);
8414
8540
  for (const f of changed) {
8415
- const text = await ctx.readFile(join5(repo, f)).catch(() => "");
8541
+ const text = await ctx.readFile(join7(repo, f)).catch(() => "");
8416
8542
  if (hasConflictMarkers(text))
8417
8543
  conflict = true;
8418
8544
  }
@@ -8430,17 +8556,17 @@ async function updateSite(ctx, consumer, site, localPreset, summary, cloneOf, pr
8430
8556
  await previewCommit(repo, `plx preview: ${site.rel} updated`);
8431
8557
  }
8432
8558
  async function syncSiteMarker(ctx, site, target) {
8433
- const marker = await ctx.readFile(join5(target, MARKER_FILE)).catch(() => void 0);
8559
+ const marker = await ctx.readFile(join7(target, MARKER_FILE)).catch(() => void 0);
8434
8560
  if (marker === void 0)
8435
8561
  return;
8436
- const { commit } = parseAnswers(await ctx.readFile(join5(target, ANSWERS_FILE)));
8562
+ const { commit } = parseAnswers(await ctx.readFile(join7(target, ANSWERS_FILE)));
8437
8563
  if (!commit)
8438
8564
  return;
8439
8565
  const synced = syncMarker(marker, commit);
8440
8566
  if (synced === marker)
8441
8567
  return;
8442
8568
  ctx.say(` marker: plexus \u2192 ${commit} in ${site.rel}/${MARKER_FILE}`);
8443
- const step = write(join5(target, MARKER_FILE), synced);
8569
+ const step = write(join7(target, MARKER_FILE), synced);
8444
8570
  if (ctx.dryRun)
8445
8571
  await ctx.stage(step);
8446
8572
  else
@@ -8449,7 +8575,7 @@ async function syncSiteMarker(ctx, site, target) {
8449
8575
  async function refreshLockfiles(ctx, consumers, summary, clones) {
8450
8576
  const manifests = ["*package.json", "pnpm-workspace.yaml"];
8451
8577
  for (const c of consumers) {
8452
- if (await ctx.readFile(join5(c.path, "pnpm-workspace.yaml")).catch(() => void 0) === void 0)
8578
+ if (await ctx.readFile(join7(c.path, "pnpm-workspace.yaml")).catch(() => void 0) === void 0)
8453
8579
  continue;
8454
8580
  const repo = ctx.dryRun ? clones.get(c.path) : c.path;
8455
8581
  if (!repo)
@@ -8469,7 +8595,7 @@ async function refreshLockfiles(ctx, consumers, summary, clones) {
8469
8595
  }
8470
8596
  async function bumpConsumerRefs(ctx, consumers, train) {
8471
8597
  for (const c of consumers) {
8472
- const dir = join5(c.path, ".github", "workflows");
8598
+ const dir = join7(c.path, ".github", "workflows");
8473
8599
  const entries = await ctx.readDir(dir).catch(() => []);
8474
8600
  const files = entries.filter((e) => !e.directory && /\.ya?ml$/.test(e.name)).map((e) => e.name).sort();
8475
8601
  if (files.length === 0)
@@ -8479,14 +8605,14 @@ async function bumpConsumerRefs(ctx, consumers, train) {
8479
8605
  continue;
8480
8606
  }
8481
8607
  for (const f of files) {
8482
- const text = await ctx.readFile(join5(dir, f));
8608
+ const text = await ctx.readFile(join7(dir, f));
8483
8609
  if (!text.includes(WORKFLOW_REF))
8484
8610
  continue;
8485
8611
  const bumped = bumpWorkflowRefs(text, train);
8486
8612
  if (bumped === text)
8487
8613
  continue;
8488
8614
  ctx.say(`\u2192 ${c.rel}: ${f} ci-cd refs \u2192 @${train}`);
8489
- await ctx.apply([write(join5(dir, f), bumped)]);
8615
+ await ctx.apply([write(join7(dir, f), bumped)]);
8490
8616
  }
8491
8617
  }
8492
8618
  }
@@ -8497,7 +8623,7 @@ var detail = (error) => {
8497
8623
  };
8498
8624
 
8499
8625
  // ../lib/dist/requirements.js
8500
- import { join as join6 } from "node:path";
8626
+ import { join as join8 } from "node:path";
8501
8627
  var KEYWORD = /MUST|SHOULD|MAY/;
8502
8628
  var hasKeyword = (line) => KEYWORD.test(line.replace(/MUSTs|SHOULDs|MAYs/g, ""));
8503
8629
  var list = (value) => value.replace(/^\s*\[/, "").replace(/\].*$/, "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -8590,7 +8716,7 @@ var MAP_PAGE = "essentials/standard.md";
8590
8716
  async function modulePages(ctx, compendium) {
8591
8717
  const pages = [];
8592
8718
  for (const dir of Object.keys(CATALOG_DIRS)) {
8593
- const entries = await ctx.readDir(join6(compendium, dir));
8719
+ const entries = await ctx.readDir(join8(compendium, dir));
8594
8720
  for (const e of entries.filter((e2) => !e2.directory && e2.name.endsWith(".md") && e2.name !== "index.md").sort())
8595
8721
  pages.push(`${dir}/${e.name}`);
8596
8722
  }
@@ -8598,11 +8724,11 @@ async function modulePages(ctx, compendium) {
8598
8724
  }
8599
8725
  async function checkCompendium(ctx, compendium) {
8600
8726
  const defects = [];
8601
- defects.push(...scanPage(await ctx.readFile(join6(compendium, MAP_PAGE)), MAP_PAGE, "map").defects);
8727
+ defects.push(...scanPage(await ctx.readFile(join8(compendium, MAP_PAGE)), MAP_PAGE, "map").defects);
8602
8728
  const pages = await modulePages(ctx, compendium);
8603
8729
  const scans = /* @__PURE__ */ new Map();
8604
8730
  for (const page of pages) {
8605
- const scan = scanPage(await ctx.readFile(join6(compendium, page)), page, "module");
8731
+ const scan = scanPage(await ctx.readFile(join8(compendium, page)), page, "module");
8606
8732
  defects.push(...scan.defects);
8607
8733
  scans.set(page, scan);
8608
8734
  }
@@ -8649,9 +8775,9 @@ async function checkCompendium(ctx, compendium) {
8649
8775
  }
8650
8776
 
8651
8777
  // ../lib/dist/ship.js
8652
- import { join as join7 } from "node:path";
8778
+ import { join as join9 } from "node:path";
8653
8779
  async function ship(ctx, options) {
8654
- const upstream = join7(options.workspace, UPSTREAM);
8780
+ const upstream = join9(options.workspace, UPSTREAM);
8655
8781
  const propagateOnly = options.propagateOnly ?? false;
8656
8782
  const train = await readTrain(ctx, upstream);
8657
8783
  if (!propagateOnly)
@@ -8702,7 +8828,7 @@ async function planBookkeeping(ctx, train, next, today2) {
8702
8828
  return r;
8703
8829
  };
8704
8830
  const edit = async (r, rel, what, transform) => {
8705
- const path3 = join7(r.path, rel);
8831
+ const path3 = join9(r.path, rel);
8706
8832
  const text = await ctx.readFile(path3).catch(() => void 0);
8707
8833
  if (text === void 0)
8708
8834
  return;
@@ -8723,7 +8849,7 @@ async function planBookkeeping(ctx, train, next, today2) {
8723
8849
  const comp = repo("compendium");
8724
8850
  await edit(comp, MAP_PAGE, `version \u2192 ${next}, timestamp \u2192 ${today2}`, (t) => stampStandard(t, next, today2));
8725
8851
  for (const page of await modulePages(ctx, comp.path)) {
8726
- const text = await ctx.readFile(join7(comp.path, page));
8852
+ const text = await ctx.readFile(join9(comp.path, page));
8727
8853
  if (frontmatterField(text, "maturity") !== "standard")
8728
8854
  continue;
8729
8855
  const previous = comp.tag ? await ctx.read("git", ["show", `${comp.tag}:${page}`], { cwd: comp.path }).catch(() => "") : "";
@@ -9686,13 +9812,13 @@ import { AsyncResource as AsyncResource3 } from "node:async_hooks";
9686
9812
 
9687
9813
  // ../../node_modules/.pnpm/@inquirer+type@4.2.0_@types+node@26.6.5/node_modules/@inquirer/type/dist/utils.js
9688
9814
  function withResolver() {
9689
- let resolve7;
9815
+ let resolve9;
9690
9816
  let reject;
9691
9817
  const promise = new Promise((res, rej) => {
9692
- resolve7 = res;
9818
+ resolve9 = res;
9693
9819
  reject = rej;
9694
9820
  });
9695
- return { promise, resolve: resolve7, reject };
9821
+ return { promise, resolve: resolve9, reject };
9696
9822
  }
9697
9823
 
9698
9824
  // ../../node_modules/.pnpm/@inquirer+core@12.0.5_@types+node@26.6.5/node_modules/@inquirer/core/dist/lib/create-prompt.js
@@ -10071,7 +10197,7 @@ function createPrompt(view) {
10071
10197
  });
10072
10198
  output.mute();
10073
10199
  const screen = new ScreenManager(rl);
10074
- const { promise, resolve: resolve7, reject } = withResolver();
10200
+ const { promise, resolve: resolve9, reject } = withResolver();
10075
10201
  return withHooks(rl, (cycle) => {
10076
10202
  const clearEffects = AsyncResource3.bind(() => effectScheduler.clearAll());
10077
10203
  const settlePrompt = (settle) => {
@@ -10082,7 +10208,7 @@ function createPrompt(view) {
10082
10208
  reject(error);
10083
10209
  }
10084
10210
  };
10085
- const resolvePrompt = (value) => settlePrompt(() => resolve7(value));
10211
+ const resolvePrompt = (value) => settlePrompt(() => resolve9(value));
10086
10212
  const rejectPrompt = (error) => settlePrompt(() => reject(error));
10087
10213
  const promptPromise = Object.assign(promise.finally(() => {
10088
10214
  cleanups.forEach((cleanup) => cleanup());
@@ -13601,12 +13727,22 @@ function useColor() {
13601
13727
  // ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/index.js
13602
13728
  var program = new Command();
13603
13729
 
13604
- // src/commands/changeset.ts
13730
+ // src/commands/changed-apps.ts
13605
13731
  import { resolve } from "node:path";
13606
13732
  function register(program2) {
13733
+ program2.command("changed-apps").description("list the apps whose sources changed between two refs, dependents of a changed package included").argument("<from>", "the base ref; empty, all zeros or unknown lists every app").argument("[to]", "the ref compared against", "HEAD").option("-C, --dir <dir>", "the checkout to read", ".").action(async (from, to, opts, command) => {
13734
+ const ctx = contextOf2(command);
13735
+ const result = await readChangedApps(ctx, resolve(opts.dir), from, to);
13736
+ ctx.result(result.apps);
13737
+ });
13738
+ }
13739
+
13740
+ // src/commands/changeset.ts
13741
+ import { resolve as resolve2 } from "node:path";
13742
+ function register2(program2) {
13607
13743
  program2.command("changeset").description("write the changeset a dependency bump owes: a patch per package whose runtime dependencies moved").argument("[dir]", "a checkout of the package repo, on the bump branch", ".").option("--base <ref>", "the trunk the branch is diffed against, at the merge base", "origin/main").option("--branch <name>", "the branch name that names the file (default: the checked-out branch)").action(async (dir, opts, command) => {
13608
13744
  const ctx = contextOf2(command);
13609
- const changeset = await readChangeset(ctx, resolve(dir), opts.base, opts.branch);
13745
+ const changeset = await readChangeset(ctx, resolve2(dir), opts.base, opts.branch);
13610
13746
  await ctx.apply(changeset.plan);
13611
13747
  ctx.result(changeset, renderChangeset);
13612
13748
  });
@@ -13617,9 +13753,19 @@ function renderChangeset(c) {
13617
13753
  return [`\u2713 ${c.path}`, ...releases.map(([name, bump]) => ` ${name}: ${bump}`)].join("\n");
13618
13754
  }
13619
13755
 
13756
+ // src/commands/deploy-targets.ts
13757
+ import { resolve as resolve3 } from "node:path";
13758
+ function register3(program2) {
13759
+ program2.command("deploy-targets").description("map each app of a platform repo to the deploy target of the inventory file that declares it").argument("[inventory]", "the inventory directory", "inventory").action(async (inventory, _opts, command) => {
13760
+ const ctx = contextOf2(command);
13761
+ const targets = await readDeployTargets(ctx, resolve3(inventory));
13762
+ ctx.result(targets, (t) => JSON.stringify(t));
13763
+ });
13764
+ }
13765
+
13620
13766
  // src/commands/doctor.ts
13621
13767
  import { existsSync } from "node:fs";
13622
- import { join as join8, resolve as resolve2 } from "node:path";
13768
+ import { join as join10, resolve as resolve4 } from "node:path";
13623
13769
 
13624
13770
  // src/style.ts
13625
13771
  function style(enabled) {
@@ -13630,15 +13776,15 @@ var colorByDefault = () => process.stdout.isTTY === true && !process.env.NO_COLO
13630
13776
 
13631
13777
  // src/commands/doctor.ts
13632
13778
  var WORKSPACE_COMPENDIUM = "plexus-ms/compendium";
13633
- function register2(program2) {
13779
+ function register4(program2) {
13634
13780
  program2.command("doctor").description("check every PLEXUS.md under a directory for modules that changed since and dependencies not declared").argument("[dir]", "directory to check", ".").option(
13635
13781
  "--compendium <path>",
13636
13782
  `compendium checkout to read the modules from (default: <dir>/${WORKSPACE_COMPENDIUM})`
13637
13783
  ).option("--color", "colour the report even when piped").option("--no-color", "never colour the report").action(async (dir, opts, command) => {
13638
13784
  const ctx = contextOf2(command);
13639
- const root = resolve2(dir);
13640
- const compendium = resolve2(opts.compendium ?? join8(root, WORKSPACE_COMPENDIUM));
13641
- if (!existsSync(join8(compendium, "contracts")))
13785
+ const root = resolve4(dir);
13786
+ const compendium = resolve4(opts.compendium ?? join10(root, WORKSPACE_COMPENDIUM));
13787
+ if (!existsSync(join10(compendium, "contracts")))
13642
13788
  throw new Error(
13643
13789
  `no compendium at ${compendium}; pass --compendium <path to a checkout of plexus-ms/compendium>`
13644
13790
  );
@@ -13666,11 +13812,11 @@ function renderReport(report, s, showCurrent) {
13666
13812
  }
13667
13813
 
13668
13814
  // src/commands/overview.ts
13669
- import { resolve as resolve3 } from "node:path";
13670
- function register3(program2) {
13815
+ import { resolve as resolve5 } from "node:path";
13816
+ function register5(program2) {
13671
13817
  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) => {
13672
13818
  const ctx = contextOf2(command);
13673
- const tree = await overview(ctx, resolve3(dir));
13819
+ const tree = await overview(ctx, resolve5(dir));
13674
13820
  const s = style(opts.color ?? colorByDefault());
13675
13821
  ctx.result(tree, (t) => renderTree(t, s, ctx.verbose >= 1));
13676
13822
  });
@@ -13713,14 +13859,14 @@ function annotate(dir, s, modules) {
13713
13859
  }
13714
13860
 
13715
13861
  // src/commands/propagate.ts
13716
- import { resolve as resolve5 } from "node:path";
13862
+ import { resolve as resolve7 } from "node:path";
13717
13863
 
13718
13864
  // src/commands/ship.ts
13719
- import { resolve as resolve4 } from "node:path";
13720
- function register4(program2) {
13865
+ import { resolve as resolve6 } from "node:path";
13866
+ function register6(program2) {
13721
13867
  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) => {
13722
13868
  const ctx = contextOf2(command);
13723
- const result = await ship(ctx, { workspace: resolve4(dir), train: opts.train });
13869
+ const result = await ship(ctx, { workspace: resolve6(dir), train: opts.train });
13724
13870
  ctx.result(result, (r) => renderSummary(r, `plx -y ship --train ${r.train} ${dir}`));
13725
13871
  if (!isClean(result)) process.exitCode = 1;
13726
13872
  });
@@ -13743,23 +13889,23 @@ function renderSummary(s, realCommand) {
13743
13889
  }
13744
13890
 
13745
13891
  // src/commands/propagate.ts
13746
- function register5(program2) {
13892
+ function register7(program2) {
13747
13893
  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) => {
13748
13894
  const ctx = contextOf2(command);
13749
- const result = await ship(ctx, { workspace: resolve5(dir), train: opts.train, propagateOnly: true });
13895
+ const result = await ship(ctx, { workspace: resolve7(dir), train: opts.train, propagateOnly: true });
13750
13896
  ctx.result(result, (r) => renderSummary(r, `plx -y propagate --train ${r.train} ${dir}`));
13751
13897
  if (!isClean(result)) process.exitCode = 1;
13752
13898
  });
13753
13899
  }
13754
13900
 
13755
13901
  // src/commands/requirements.ts
13756
- import { join as join9, resolve as resolve6 } from "node:path";
13757
- function register6(program2) {
13902
+ import { join as join11, resolve as resolve8 } from "node:path";
13903
+ function register8(program2) {
13758
13904
  program2.command("requirements").description("lint the standard's map and module pages, or extract one module's requirement lines").argument("[dir]", "a checkout of the compendium", ".").option("--extract <page>", "print a module page's requirement lines instead of linting (path relative to dir)").action(async (dir, opts, command) => {
13759
13905
  const ctx = contextOf2(command);
13760
- const compendium = resolve6(dir);
13906
+ const compendium = resolve8(dir);
13761
13907
  if (opts.extract) {
13762
- ctx.result(extractRequirements(await ctx.readFile(join9(compendium, opts.extract))));
13908
+ ctx.result(extractRequirements(await ctx.readFile(join11(compendium, opts.extract))));
13763
13909
  return;
13764
13910
  }
13765
13911
  const check = await checkCompendium(ctx, compendium);
@@ -13773,7 +13919,16 @@ function renderCheck(c) {
13773
13919
  }
13774
13920
 
13775
13921
  // src/commands/index.ts
13776
- var commands = [register3, register2, register4, register5, register6, register];
13922
+ var commands = [
13923
+ register5,
13924
+ register4,
13925
+ register6,
13926
+ register7,
13927
+ register8,
13928
+ register2,
13929
+ register,
13930
+ register3
13931
+ ];
13777
13932
 
13778
13933
  // src/program.ts
13779
13934
  var { version, description } = createRequire(import.meta.url)("../package.json");
@@ -13782,7 +13937,7 @@ function buildProgram() {
13782
13937
  program2.addOption(new Option("-n, --dry-run", "print every effect this run would have, perform none").env("PLX_DRY_RUN")).addOption(
13783
13938
  new Option("-v, --verbose", "say more: once for reads and inputs, twice for command output").argParser((_value, previous) => previous + 1).default(0)
13784
13939
  ).addOption(new Option("-q, --quiet", "say nothing but results and errors").env("PLX_QUIET")).addOption(new Option("-y, --yes", "answer every confirmation with yes (needed off a terminal)").env("PLX_YES")).addOption(new Option("--json", "print results as JSON").env("PLX_JSON"));
13785
- for (const register7 of commands) register7(program2);
13940
+ for (const register9 of commands) register9(program2);
13786
13941
  return program2;
13787
13942
  }
13788
13943
  function contextOf2(command) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexus-ms/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",
@@ -20,11 +20,11 @@
20
20
  "access": "public"
21
21
  },
22
22
  "engines": {
23
- "node": ">=24"
23
+ "node": ">=24.22.1"
24
24
  },
25
25
  "dependencies": {
26
26
  "@inquirer/prompts": "^8.7.4",
27
- "@plexus-ms/lib": "0.4.0",
27
+ "@plexus-ms/lib": "0.5.0",
28
28
  "commander": "^15.0.0"
29
29
  },
30
30
  "devDependencies": {