@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.
- package/dist/plx.js +370 -160
- 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(
|
|
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/
|
|
7585
|
-
var bumpWorkflowRefs = (text, train) => text.replace(/(plexus-ms\/
|
|
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
|
|
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
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8274
|
+
repos2.push({ name, path: path3, tag, latest: tag?.replace(/^v/, ""), pending });
|
|
8264
8275
|
}
|
|
8265
|
-
const 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
|
-
|
|
8561
|
-
|
|
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
|
|
8685
|
+
var BASE_TAG = "ship-base";
|
|
8686
|
+
var trainBranch = (train) => `train/${train}`;
|
|
8634
8687
|
async function propagate(ctx, workspace, train) {
|
|
8635
|
-
const summary = {
|
|
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
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
const
|
|
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
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
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
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
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,
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
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
|
-
|
|
8779
|
-
|
|
8829
|
+
const bumped = bumpWorkflowRefs(text, train);
|
|
8830
|
+
if (bumped === text)
|
|
8780
8831
|
continue;
|
|
8781
|
-
}
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8995
|
-
await ctx
|
|
8996
|
-
|
|
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
|
-
|
|
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
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
for (const
|
|
9036
|
-
await edit(
|
|
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
|
-
|
|
9040
|
-
|
|
9041
|
-
const
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
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(
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
27
|
+
"@plexus-ms/lib": "0.10.0",
|
|
28
28
|
"commander": "^15.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|