@topogram/cli 0.3.45 → 0.3.47
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/package.json +1 -1
- package/src/adoption/review-groups.js +1 -0
- package/src/cli.js +595 -8
- package/src/workflows.js +10 -2
package/package.json
CHANGED
|
@@ -172,6 +172,7 @@ export function buildBundleAdoptionPriorities(report, confidenceRank) {
|
|
|
172
172
|
enums: bundle.enums.length,
|
|
173
173
|
capabilities: bundle.capabilities.length,
|
|
174
174
|
shapes: bundle.shapes.length,
|
|
175
|
+
components: bundle.components?.length || 0,
|
|
175
176
|
screens: bundle.screens.length,
|
|
176
177
|
workflows: bundle.workflows.length,
|
|
177
178
|
docs: bundle.docs.length
|
package/src/cli.js
CHANGED
|
@@ -137,6 +137,20 @@ const KNOWN_CLI_CONSUMER_REPOS = [
|
|
|
137
137
|
"topogram-demo-todo",
|
|
138
138
|
"topogram-hello"
|
|
139
139
|
];
|
|
140
|
+
const KNOWN_CLI_CONSUMER_WORKFLOWS = {
|
|
141
|
+
"topogram-generator-express-api": "Generator Verification",
|
|
142
|
+
"topogram-generator-hono-api": "Generator Verification",
|
|
143
|
+
"topogram-generator-postgres-db": "Generator Verification",
|
|
144
|
+
"topogram-generator-react-web": "Generator Verification",
|
|
145
|
+
"topogram-generator-sqlite-db": "Generator Verification",
|
|
146
|
+
"topogram-generator-sveltekit-web": "Generator Verification",
|
|
147
|
+
"topogram-generator-swiftui-native": "Generator Verification",
|
|
148
|
+
"topogram-generator-vanilla-web": "Generator Verification",
|
|
149
|
+
"topogram-starters": "Starter Verification",
|
|
150
|
+
"topogram-template-todo": "Template Verification",
|
|
151
|
+
"topogram-demo-todo": "Demo Verification",
|
|
152
|
+
"topogram-hello": "Topogram Package Verification"
|
|
153
|
+
};
|
|
140
154
|
const PACKAGE_UPDATE_CLI_CHECK_SCRIPTS = [
|
|
141
155
|
"cli:surface",
|
|
142
156
|
"doctor",
|
|
@@ -178,6 +192,7 @@ function printUsage(options = {}) {
|
|
|
178
192
|
console.log("Usage: topogram doctor [--json] [--catalog <path-or-source>]");
|
|
179
193
|
console.log("Usage: topogram setup package-auth|catalog-auth");
|
|
180
194
|
console.log("Usage: topogram release status [--json] [--strict]");
|
|
195
|
+
console.log(" or: topogram release roll-consumers <version|--latest> [--json] [--no-push]");
|
|
181
196
|
console.log("Usage: topogram check [path] [--json]");
|
|
182
197
|
console.log(" or: topogram component check [path] [--projection <id>] [--component <id>] [--json]");
|
|
183
198
|
console.log(" or: topogram component behavior [path] [--projection <id>] [--component <id>] [--json]");
|
|
@@ -231,6 +246,7 @@ function printUsage(options = {}) {
|
|
|
231
246
|
console.log(" topogram doctor");
|
|
232
247
|
console.log(" topogram setup package-auth");
|
|
233
248
|
console.log(" topogram release status");
|
|
249
|
+
console.log(" topogram release roll-consumers --latest");
|
|
234
250
|
console.log(" topogram new ./my-app");
|
|
235
251
|
console.log(" topogram new --list-templates");
|
|
236
252
|
console.log(" topogram new ./my-app --template todo");
|
|
@@ -744,13 +760,17 @@ function printPackageHelp() {
|
|
|
744
760
|
|
|
745
761
|
function printReleaseHelp() {
|
|
746
762
|
console.log("Usage: topogram release status [--json] [--strict]");
|
|
763
|
+
console.log(" or: topogram release roll-consumers <version|--latest> [--json] [--no-push]");
|
|
747
764
|
console.log("");
|
|
748
|
-
console.log("Checks the local CLI version, latest published package version, release tag,
|
|
765
|
+
console.log("Checks the local CLI version, latest published package version, release tag, first-party consumer pins, and strict consumer CI state.");
|
|
766
|
+
console.log("Rolls first-party consumers to a published CLI version, runs their checks, commits, pushes, and prints latest workflow URLs.");
|
|
749
767
|
console.log("");
|
|
750
768
|
console.log("Examples:");
|
|
751
769
|
console.log(" topogram release status");
|
|
752
770
|
console.log(" topogram release status --json");
|
|
753
771
|
console.log(" topogram release status --strict");
|
|
772
|
+
console.log(" topogram release roll-consumers 0.3.46");
|
|
773
|
+
console.log(" topogram release roll-consumers --latest");
|
|
754
774
|
console.log("");
|
|
755
775
|
console.log("Release preparation and publishing are repo-level tasks in the Topogram source checkout:");
|
|
756
776
|
console.log(" npm run release:prepare -- <version>");
|
|
@@ -2493,8 +2513,208 @@ function printPackageUpdateCli(payload) {
|
|
|
2493
2513
|
}
|
|
2494
2514
|
|
|
2495
2515
|
/**
|
|
2496
|
-
* @param {
|
|
2497
|
-
* @
|
|
2516
|
+
* @param {string} requested
|
|
2517
|
+
* @param {{ cwd?: string, push?: boolean }} [options]
|
|
2518
|
+
* @returns {{ ok: boolean, packageName: string, requestedVersion: string, requestedLatest: boolean, pushed: boolean, consumers: Array<Record<string, any>>, diagnostics: Array<Record<string, any>>, errors: string[] }}
|
|
2519
|
+
*/
|
|
2520
|
+
function buildReleaseRollConsumersPayload(requested, options = {}) {
|
|
2521
|
+
const cwd = options.cwd || process.cwd();
|
|
2522
|
+
const push = options.push !== false;
|
|
2523
|
+
const requestedLatest = requested === "latest" || requested === "--latest";
|
|
2524
|
+
const diagnostics = [];
|
|
2525
|
+
const version = requestedLatest
|
|
2526
|
+
? resolveLatestTopogramCliVersionForPackageUpdate(cwd, diagnostics)
|
|
2527
|
+
: requested;
|
|
2528
|
+
if (!isPackageVersion(version)) {
|
|
2529
|
+
throw new Error("topogram release roll-consumers requires <version> or --latest.");
|
|
2530
|
+
}
|
|
2531
|
+
const consumers = [];
|
|
2532
|
+
for (const consumer of discoverTopogramCliVersionConsumers(cwd)) {
|
|
2533
|
+
const workflow = expectedConsumerWorkflowName(consumer.name);
|
|
2534
|
+
const item = {
|
|
2535
|
+
name: consumer.name,
|
|
2536
|
+
root: consumer.root,
|
|
2537
|
+
workflow,
|
|
2538
|
+
updated: false,
|
|
2539
|
+
committed: false,
|
|
2540
|
+
pushed: false,
|
|
2541
|
+
commit: null,
|
|
2542
|
+
update: null,
|
|
2543
|
+
ci: null,
|
|
2544
|
+
diagnostics: []
|
|
2545
|
+
};
|
|
2546
|
+
consumers.push(item);
|
|
2547
|
+
if (!consumer.root || !fs.existsSync(consumer.root)) {
|
|
2548
|
+
item.diagnostics.push({
|
|
2549
|
+
code: "release_consumer_repo_missing",
|
|
2550
|
+
severity: "error",
|
|
2551
|
+
message: `First-party consumer repo ${consumer.name} was not found.`,
|
|
2552
|
+
path: consumer.path,
|
|
2553
|
+
suggestedFix: `Clone ${consumer.name} beside the topogram repo, then rerun roll-consumers.`
|
|
2554
|
+
});
|
|
2555
|
+
diagnostics.push(...item.diagnostics);
|
|
2556
|
+
continue;
|
|
2557
|
+
}
|
|
2558
|
+
const packagePath = path.join(consumer.root, "package.json");
|
|
2559
|
+
if (!fs.existsSync(packagePath)) {
|
|
2560
|
+
item.diagnostics.push({
|
|
2561
|
+
code: "release_consumer_package_missing",
|
|
2562
|
+
severity: "error",
|
|
2563
|
+
message: `First-party consumer repo ${consumer.name} does not contain package.json.`,
|
|
2564
|
+
path: packagePath,
|
|
2565
|
+
suggestedFix: "Only package-backed first-party consumers can be rolled by this command."
|
|
2566
|
+
});
|
|
2567
|
+
diagnostics.push(...item.diagnostics);
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
const clean = inspectGitWorktreeClean(consumer.root);
|
|
2571
|
+
if (clean.ok !== true) {
|
|
2572
|
+
item.diagnostics.push({
|
|
2573
|
+
code: "release_consumer_worktree_dirty",
|
|
2574
|
+
severity: "error",
|
|
2575
|
+
message: clean.error || `First-party consumer repo ${consumer.name} has uncommitted changes.`,
|
|
2576
|
+
path: consumer.root,
|
|
2577
|
+
suggestedFix: "Commit, stash, or discard unrelated consumer changes before rolling the CLI version."
|
|
2578
|
+
});
|
|
2579
|
+
diagnostics.push(...item.diagnostics);
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
try {
|
|
2583
|
+
item.update = buildPackageUpdateCliPayload(version, { cwd: consumer.root });
|
|
2584
|
+
item.updated = true;
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
item.diagnostics.push({
|
|
2587
|
+
code: "release_consumer_update_failed",
|
|
2588
|
+
severity: "error",
|
|
2589
|
+
message: `Failed to update ${consumer.name}: ${messageFromError(error)}`,
|
|
2590
|
+
path: consumer.root,
|
|
2591
|
+
suggestedFix: "Fix the consumer update/check failure, then rerun roll-consumers."
|
|
2592
|
+
});
|
|
2593
|
+
diagnostics.push(...item.diagnostics);
|
|
2594
|
+
continue;
|
|
2595
|
+
}
|
|
2596
|
+
const filesToStage = ["package.json", "package-lock.json", "topogram-cli.version"]
|
|
2597
|
+
.filter((file) => fs.existsSync(path.join(consumer.root, file)));
|
|
2598
|
+
const addResult = runGit(["add", ...filesToStage], consumer.root);
|
|
2599
|
+
if (addResult.status !== 0) {
|
|
2600
|
+
item.diagnostics.push(commandDiagnostic({
|
|
2601
|
+
code: "release_consumer_git_add_failed",
|
|
2602
|
+
severity: "error",
|
|
2603
|
+
message: `Failed to stage ${consumer.name} CLI update.`,
|
|
2604
|
+
path: consumer.root,
|
|
2605
|
+
suggestedFix: "Inspect git output, stage the changed files manually, then commit and push.",
|
|
2606
|
+
result: addResult
|
|
2607
|
+
}));
|
|
2608
|
+
diagnostics.push(...item.diagnostics);
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
const staged = hasStagedGitChanges(consumer.root);
|
|
2612
|
+
if (!staged.ok) {
|
|
2613
|
+
item.diagnostics.push(commandDiagnostic({
|
|
2614
|
+
code: "release_consumer_git_diff_failed",
|
|
2615
|
+
severity: "error",
|
|
2616
|
+
message: `Could not inspect staged changes for ${consumer.name}.`,
|
|
2617
|
+
path: consumer.root,
|
|
2618
|
+
suggestedFix: "Inspect git status manually before committing.",
|
|
2619
|
+
result: staged.result
|
|
2620
|
+
}));
|
|
2621
|
+
diagnostics.push(...item.diagnostics);
|
|
2622
|
+
continue;
|
|
2623
|
+
}
|
|
2624
|
+
if (!staged.changed) {
|
|
2625
|
+
item.ci = inspectConsumerCi(consumer, { strict: false });
|
|
2626
|
+
item.diagnostics.push(...item.ci.diagnostics);
|
|
2627
|
+
diagnostics.push(...item.ci.diagnostics);
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
const commitResult = runGit(["commit", "-m", `Update Topogram CLI to ${version}`], consumer.root);
|
|
2631
|
+
if (commitResult.status !== 0) {
|
|
2632
|
+
item.diagnostics.push(commandDiagnostic({
|
|
2633
|
+
code: "release_consumer_git_commit_failed",
|
|
2634
|
+
severity: "error",
|
|
2635
|
+
message: `Failed to commit ${consumer.name} CLI update.`,
|
|
2636
|
+
path: consumer.root,
|
|
2637
|
+
suggestedFix: "Inspect git output, commit the consumer update manually, then push.",
|
|
2638
|
+
result: commitResult
|
|
2639
|
+
}));
|
|
2640
|
+
diagnostics.push(...item.diagnostics);
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
item.committed = true;
|
|
2644
|
+
item.commit = currentGitHead(consumer.root);
|
|
2645
|
+
if (push) {
|
|
2646
|
+
const pushResult = runGit(["push", "origin", "main"], consumer.root);
|
|
2647
|
+
if (pushResult.status !== 0) {
|
|
2648
|
+
item.diagnostics.push(commandDiagnostic({
|
|
2649
|
+
code: "release_consumer_git_push_failed",
|
|
2650
|
+
severity: "error",
|
|
2651
|
+
message: `Failed to push ${consumer.name} CLI update.`,
|
|
2652
|
+
path: consumer.root,
|
|
2653
|
+
suggestedFix: "Push the consumer update manually, then confirm its verification workflow passes.",
|
|
2654
|
+
result: pushResult
|
|
2655
|
+
}));
|
|
2656
|
+
diagnostics.push(...item.diagnostics);
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
item.pushed = true;
|
|
2660
|
+
}
|
|
2661
|
+
item.ci = inspectConsumerCi(consumer, { strict: false });
|
|
2662
|
+
item.diagnostics.push(...item.ci.diagnostics);
|
|
2663
|
+
diagnostics.push(...item.ci.diagnostics);
|
|
2664
|
+
}
|
|
2665
|
+
const errors = diagnostics
|
|
2666
|
+
.filter((diagnostic) => diagnostic.severity === "error")
|
|
2667
|
+
.map((diagnostic) => diagnostic.message);
|
|
2668
|
+
return {
|
|
2669
|
+
ok: errors.length === 0,
|
|
2670
|
+
packageName: CLI_PACKAGE_NAME,
|
|
2671
|
+
requestedVersion: version,
|
|
2672
|
+
requestedLatest,
|
|
2673
|
+
pushed: push,
|
|
2674
|
+
consumers,
|
|
2675
|
+
diagnostics,
|
|
2676
|
+
errors
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/**
|
|
2681
|
+
* @param {ReturnType<typeof buildReleaseRollConsumersPayload>} payload
|
|
2682
|
+
* @returns {void}
|
|
2683
|
+
*/
|
|
2684
|
+
function printReleaseRollConsumers(payload) {
|
|
2685
|
+
console.log(payload.ok ? "Topogram consumer rollout completed." : "Topogram consumer rollout found issues.");
|
|
2686
|
+
if (payload.requestedLatest) {
|
|
2687
|
+
console.log(`Resolved latest version: ${payload.requestedVersion}`);
|
|
2688
|
+
}
|
|
2689
|
+
console.log(`Package: ${payload.packageName}@${payload.requestedVersion}`);
|
|
2690
|
+
console.log(`Push: ${payload.pushed ? "enabled" : "disabled"}`);
|
|
2691
|
+
for (const consumer of payload.consumers) {
|
|
2692
|
+
const state = consumer.committed
|
|
2693
|
+
? consumer.pushed ? "pushed" : "committed"
|
|
2694
|
+
: consumer.updated ? "updated" : "skipped";
|
|
2695
|
+
console.log(`- ${consumer.name}: ${state}`);
|
|
2696
|
+
if (consumer.update) {
|
|
2697
|
+
console.log(` Checks run: ${consumer.update.scriptsRun.join(", ") || "none"}`);
|
|
2698
|
+
}
|
|
2699
|
+
if (consumer.commit) {
|
|
2700
|
+
console.log(` Commit: ${consumer.commit}`);
|
|
2701
|
+
}
|
|
2702
|
+
if (consumer.ci?.run?.url) {
|
|
2703
|
+
const run = consumer.ci.run;
|
|
2704
|
+
console.log(` CI: ${run.workflowName || consumer.workflow} ${run.status || "unknown"}/${run.conclusion || "unknown"} ${run.url}`);
|
|
2705
|
+
} else if (consumer.workflow) {
|
|
2706
|
+
console.log(` CI: ${consumer.workflow} not found`);
|
|
2707
|
+
}
|
|
2708
|
+
for (const diagnostic of consumer.diagnostics || []) {
|
|
2709
|
+
const label = diagnostic.severity === "error" ? "Error" : diagnostic.severity === "warning" ? "Warning" : "Note";
|
|
2710
|
+
console.log(` ${label}: ${diagnostic.message}`);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* @param {{ cwd?: string, strict?: boolean }} [options]
|
|
2717
|
+
* @returns {{ ok: boolean, packageName: string, localVersion: string, latestVersion: string|null, currentPublished: boolean|null, git: { tag: string, local: boolean|null, remote: boolean|null, diagnostics: Array<Record<string, any>> }, consumerPins: ReturnType<typeof summarizeConsumerPins>, consumerCi: ReturnType<typeof summarizeConsumerCi>, consumers: Array<{ name: string, root: string|null, path: string, version: string|null, found: boolean, matchesLocal: boolean|null, workflow: string|null, ci: ReturnType<typeof inspectConsumerCi>|null }>, diagnostics: Array<Record<string, any>>, errors: string[] }}
|
|
2498
2718
|
*/
|
|
2499
2719
|
function buildReleaseStatusPayload(options = {}) {
|
|
2500
2720
|
const cwd = options.cwd || process.cwd();
|
|
@@ -2517,9 +2737,20 @@ function buildReleaseStatusPayload(options = {}) {
|
|
|
2517
2737
|
diagnostics.push(...git.diagnostics);
|
|
2518
2738
|
const consumers = discoverTopogramCliVersionConsumers(cwd).map((consumer) => ({
|
|
2519
2739
|
...consumer,
|
|
2520
|
-
matchesLocal: consumer.version ? consumer.version === localVersion : null
|
|
2740
|
+
matchesLocal: consumer.version ? consumer.version === localVersion : null,
|
|
2741
|
+
workflow: expectedConsumerWorkflowName(consumer.name),
|
|
2742
|
+
ci: null
|
|
2521
2743
|
}));
|
|
2744
|
+
if (strict) {
|
|
2745
|
+
for (const consumer of consumers) {
|
|
2746
|
+
if (consumer.matchesLocal === true) {
|
|
2747
|
+
consumer.ci = inspectConsumerCi(consumer, { strict: true });
|
|
2748
|
+
diagnostics.push(...consumer.ci.diagnostics);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2522
2752
|
const consumerPins = summarizeConsumerPins(consumers);
|
|
2753
|
+
const consumerCi = summarizeConsumerCi(consumers);
|
|
2523
2754
|
const currentPublished = latestVersion ? latestVersion === localVersion : null;
|
|
2524
2755
|
if (strict) {
|
|
2525
2756
|
diagnostics.push(...releaseStatusStrictDiagnostics({
|
|
@@ -2527,7 +2758,8 @@ function buildReleaseStatusPayload(options = {}) {
|
|
|
2527
2758
|
latestVersion,
|
|
2528
2759
|
currentPublished,
|
|
2529
2760
|
git,
|
|
2530
|
-
consumerPins
|
|
2761
|
+
consumerPins,
|
|
2762
|
+
consumerCi
|
|
2531
2763
|
}));
|
|
2532
2764
|
}
|
|
2533
2765
|
const errors = diagnostics
|
|
@@ -2542,6 +2774,7 @@ function buildReleaseStatusPayload(options = {}) {
|
|
|
2542
2774
|
currentPublished,
|
|
2543
2775
|
git,
|
|
2544
2776
|
consumerPins,
|
|
2777
|
+
consumerCi,
|
|
2545
2778
|
consumers,
|
|
2546
2779
|
diagnostics,
|
|
2547
2780
|
errors
|
|
@@ -2554,7 +2787,8 @@ function buildReleaseStatusPayload(options = {}) {
|
|
|
2554
2787
|
* latestVersion: string|null,
|
|
2555
2788
|
* currentPublished: boolean|null,
|
|
2556
2789
|
* git: ReturnType<typeof inspectReleaseGitTag>,
|
|
2557
|
-
* consumerPins: ReturnType<typeof summarizeConsumerPins
|
|
2790
|
+
* consumerPins: ReturnType<typeof summarizeConsumerPins>,
|
|
2791
|
+
* consumerCi: ReturnType<typeof summarizeConsumerCi>
|
|
2558
2792
|
* }} release
|
|
2559
2793
|
* @returns {Array<{ code: string, severity: "error", message: string, path: string, suggestedFix: string }>}
|
|
2560
2794
|
*/
|
|
@@ -2598,6 +2832,15 @@ function releaseStatusStrictDiagnostics(release) {
|
|
|
2598
2832
|
suggestedFix: "Roll first-party consumer repositories to the current CLI version before treating this release as complete."
|
|
2599
2833
|
});
|
|
2600
2834
|
}
|
|
2835
|
+
if (release.consumerCi.allCheckedAndPassing !== true) {
|
|
2836
|
+
diagnostics.push({
|
|
2837
|
+
code: "release_consumer_ci_not_current",
|
|
2838
|
+
severity: "error",
|
|
2839
|
+
message: "First-party consumer verification workflows are not all passing on the checked-out consumer commits.",
|
|
2840
|
+
path: "GitHub Actions",
|
|
2841
|
+
suggestedFix: "Wait for or fix the consumer verification workflows, then rerun `topogram release status --strict`."
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2601
2844
|
return diagnostics;
|
|
2602
2845
|
}
|
|
2603
2846
|
|
|
@@ -2618,13 +2861,27 @@ function printReleaseStatus(payload) {
|
|
|
2618
2861
|
`Consumer pins: ${payload.consumerPins.pinned}/${payload.consumerPins.known} pinned, ` +
|
|
2619
2862
|
`${payload.consumerPins.matching} matching, ${payload.consumerPins.differing} differing, ${payload.consumerPins.missing} missing`
|
|
2620
2863
|
);
|
|
2864
|
+
if (payload.strict) {
|
|
2865
|
+
console.log(
|
|
2866
|
+
`Consumer CI: ${payload.consumerCi.passing}/${payload.consumerCi.checked} passing, ` +
|
|
2867
|
+
`${payload.consumerCi.failing} failing, ${payload.consumerCi.unavailable} unavailable, ${payload.consumerCi.skipped} skipped`
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2621
2870
|
for (const consumer of payload.consumers) {
|
|
2622
2871
|
const status = consumer.matchesLocal === true
|
|
2623
2872
|
? "matches"
|
|
2624
2873
|
: consumer.matchesLocal === false
|
|
2625
2874
|
? "differs"
|
|
2626
2875
|
: "missing";
|
|
2627
|
-
|
|
2876
|
+
const ciStatus = consumer.ci?.run
|
|
2877
|
+
? `; ${consumer.ci.run.workflowName || consumer.workflow}: ${consumer.ci.run.status || "unknown"}/${consumer.ci.run.conclusion || "unknown"}`
|
|
2878
|
+
: consumer.ci?.checked
|
|
2879
|
+
? `; ${consumer.workflow || "workflow"} unavailable`
|
|
2880
|
+
: "";
|
|
2881
|
+
console.log(`- ${consumer.name}: ${consumer.version || "missing"} (${status})${ciStatus}`);
|
|
2882
|
+
if (consumer.ci?.run?.url) {
|
|
2883
|
+
console.log(` CI: ${consumer.ci.run.url}`);
|
|
2884
|
+
}
|
|
2628
2885
|
}
|
|
2629
2886
|
if (payload.diagnostics.length > 0) {
|
|
2630
2887
|
console.log("Diagnostics:");
|
|
@@ -2697,9 +2954,236 @@ function inspectReleaseGitTag(version, cwd) {
|
|
|
2697
2954
|
return { tag, local, remote, diagnostics };
|
|
2698
2955
|
}
|
|
2699
2956
|
|
|
2957
|
+
/**
|
|
2958
|
+
* @param {string} name
|
|
2959
|
+
* @returns {string|null}
|
|
2960
|
+
*/
|
|
2961
|
+
function expectedConsumerWorkflowName(name) {
|
|
2962
|
+
return KNOWN_CLI_CONSUMER_WORKFLOWS[name] || null;
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
/**
|
|
2966
|
+
* @param {string[]} args
|
|
2967
|
+
* @param {string} cwd
|
|
2968
|
+
* @returns {ReturnType<typeof childProcess.spawnSync>}
|
|
2969
|
+
*/
|
|
2970
|
+
function runGit(args, cwd) {
|
|
2971
|
+
return childProcess.spawnSync("git", args, {
|
|
2972
|
+
cwd,
|
|
2973
|
+
encoding: "utf8",
|
|
2974
|
+
env: { ...process.env, PATH: process.env.PATH || "" }
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2700
2978
|
/**
|
|
2701
2979
|
* @param {string} cwd
|
|
2702
|
-
* @returns {
|
|
2980
|
+
* @returns {{ ok: boolean, dirty: boolean|null, error: string|null }}
|
|
2981
|
+
*/
|
|
2982
|
+
function inspectGitWorktreeClean(cwd) {
|
|
2983
|
+
const result = runGit(["status", "--porcelain"], cwd);
|
|
2984
|
+
if (result.status !== 0) {
|
|
2985
|
+
return {
|
|
2986
|
+
ok: false,
|
|
2987
|
+
dirty: null,
|
|
2988
|
+
error: `Could not inspect git status: ${commandOutput(result) || "unknown error"}`
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
const dirty = String(result.stdout || "").trim().length > 0;
|
|
2992
|
+
return {
|
|
2993
|
+
ok: !dirty,
|
|
2994
|
+
dirty,
|
|
2995
|
+
error: dirty ? "Consumer repo has uncommitted changes." : null
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
/**
|
|
3000
|
+
* @param {string} cwd
|
|
3001
|
+
* @returns {{ ok: boolean, changed: boolean, result: ReturnType<typeof childProcess.spawnSync> }}
|
|
3002
|
+
*/
|
|
3003
|
+
function hasStagedGitChanges(cwd) {
|
|
3004
|
+
const result = runGit(["diff", "--cached", "--quiet"], cwd);
|
|
3005
|
+
return {
|
|
3006
|
+
ok: result.status === 0 || result.status === 1,
|
|
3007
|
+
changed: result.status === 1,
|
|
3008
|
+
result
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
/**
|
|
3013
|
+
* @param {string} cwd
|
|
3014
|
+
* @returns {string|null}
|
|
3015
|
+
*/
|
|
3016
|
+
function currentGitHead(cwd) {
|
|
3017
|
+
const result = runGit(["rev-parse", "HEAD"], cwd);
|
|
3018
|
+
return result.status === 0 ? String(result.stdout || "").trim() || null : null;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
/**
|
|
3022
|
+
* @param {{ code: string, severity: "error"|"warning", message: string, path: string|null, suggestedFix: string, result: ReturnType<typeof childProcess.spawnSync> }} input
|
|
3023
|
+
* @returns {{ code: string, severity: "error"|"warning", message: string, path: string|null, suggestedFix: string }}
|
|
3024
|
+
*/
|
|
3025
|
+
function commandDiagnostic(input) {
|
|
3026
|
+
const output = commandOutput(input.result);
|
|
3027
|
+
return {
|
|
3028
|
+
code: input.code,
|
|
3029
|
+
severity: input.severity,
|
|
3030
|
+
message: output ? `${input.message}\n${output}` : input.message,
|
|
3031
|
+
path: input.path,
|
|
3032
|
+
suggestedFix: input.suggestedFix
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
/**
|
|
3037
|
+
* @param {ReturnType<typeof childProcess.spawnSync>} result
|
|
3038
|
+
* @returns {string}
|
|
3039
|
+
*/
|
|
3040
|
+
function commandOutput(result) {
|
|
3041
|
+
return [result.error?.message, result.stderr, result.stdout].filter(Boolean).join("\n").trim();
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
/**
|
|
3045
|
+
* @param {{ name: string, root?: string|null, workflow?: string|null }} consumer
|
|
3046
|
+
* @param {{ strict?: boolean }} [options]
|
|
3047
|
+
* @returns {{ checked: boolean, ok: boolean|null, expectedWorkflow: string|null, headSha: string|null, run: { databaseId?: number, workflowName?: string, status?: string, conclusion?: string, headSha?: string, url?: string }|null, diagnostics: Array<Record<string, any>> }}
|
|
3048
|
+
*/
|
|
3049
|
+
function inspectConsumerCi(consumer, options = {}) {
|
|
3050
|
+
const diagnostics = [];
|
|
3051
|
+
const expectedWorkflow = consumer.workflow || expectedConsumerWorkflowName(consumer.name);
|
|
3052
|
+
if (!consumer.root || !fs.existsSync(consumer.root)) {
|
|
3053
|
+
return {
|
|
3054
|
+
checked: false,
|
|
3055
|
+
ok: null,
|
|
3056
|
+
expectedWorkflow,
|
|
3057
|
+
headSha: null,
|
|
3058
|
+
run: null,
|
|
3059
|
+
diagnostics: []
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
const headSha = currentGitHead(consumer.root);
|
|
3063
|
+
if (!headSha) {
|
|
3064
|
+
diagnostics.push({
|
|
3065
|
+
code: "release_consumer_head_unavailable",
|
|
3066
|
+
severity: options.strict ? "error" : "warning",
|
|
3067
|
+
message: `Could not inspect local HEAD for ${consumer.name}.`,
|
|
3068
|
+
path: consumer.root,
|
|
3069
|
+
suggestedFix: "Run from a checked-out consumer git repository."
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
if (!expectedWorkflow) {
|
|
3073
|
+
diagnostics.push({
|
|
3074
|
+
code: "release_consumer_workflow_unknown",
|
|
3075
|
+
severity: options.strict ? "error" : "warning",
|
|
3076
|
+
message: `No expected verification workflow is configured for ${consumer.name}.`,
|
|
3077
|
+
path: consumer.name,
|
|
3078
|
+
suggestedFix: "Add the consumer repo to KNOWN_CLI_CONSUMER_WORKFLOWS."
|
|
3079
|
+
});
|
|
3080
|
+
return {
|
|
3081
|
+
checked: true,
|
|
3082
|
+
ok: false,
|
|
3083
|
+
expectedWorkflow,
|
|
3084
|
+
headSha,
|
|
3085
|
+
run: null,
|
|
3086
|
+
diagnostics
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
const result = childProcess.spawnSync("gh", [
|
|
3090
|
+
"run",
|
|
3091
|
+
"list",
|
|
3092
|
+
"--repo",
|
|
3093
|
+
`attebury/${consumer.name}`,
|
|
3094
|
+
"--branch",
|
|
3095
|
+
"main",
|
|
3096
|
+
"--workflow",
|
|
3097
|
+
expectedWorkflow,
|
|
3098
|
+
"--limit",
|
|
3099
|
+
"1",
|
|
3100
|
+
"--json",
|
|
3101
|
+
"databaseId,workflowName,status,conclusion,headSha,url"
|
|
3102
|
+
], {
|
|
3103
|
+
cwd: consumer.root,
|
|
3104
|
+
encoding: "utf8",
|
|
3105
|
+
env: { ...process.env, PATH: process.env.PATH || "" }
|
|
3106
|
+
});
|
|
3107
|
+
if (result.status !== 0) {
|
|
3108
|
+
diagnostics.push(commandDiagnostic({
|
|
3109
|
+
code: "release_consumer_ci_unavailable",
|
|
3110
|
+
severity: options.strict ? "error" : "warning",
|
|
3111
|
+
message: `Could not inspect ${expectedWorkflow} for ${consumer.name}.`,
|
|
3112
|
+
path: `attebury/${consumer.name}`,
|
|
3113
|
+
suggestedFix: "Check GitHub CLI auth/network access, then rerun release status.",
|
|
3114
|
+
result
|
|
3115
|
+
}));
|
|
3116
|
+
return {
|
|
3117
|
+
checked: true,
|
|
3118
|
+
ok: false,
|
|
3119
|
+
expectedWorkflow,
|
|
3120
|
+
headSha,
|
|
3121
|
+
run: null,
|
|
3122
|
+
diagnostics
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
let runs = [];
|
|
3126
|
+
try {
|
|
3127
|
+
runs = JSON.parse(String(result.stdout || "[]"));
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
diagnostics.push({
|
|
3130
|
+
code: "release_consumer_ci_unreadable",
|
|
3131
|
+
severity: options.strict ? "error" : "warning",
|
|
3132
|
+
message: `Could not parse ${consumer.name} workflow status: ${messageFromError(error)}`,
|
|
3133
|
+
path: `attebury/${consumer.name}`,
|
|
3134
|
+
suggestedFix: "Rerun release status after GitHub CLI output is valid JSON."
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
const run = Array.isArray(runs) && runs.length > 0 ? runs[0] : null;
|
|
3138
|
+
if (!run) {
|
|
3139
|
+
diagnostics.push({
|
|
3140
|
+
code: "release_consumer_ci_missing",
|
|
3141
|
+
severity: options.strict ? "error" : "warning",
|
|
3142
|
+
message: `${consumer.name} has no ${expectedWorkflow} run on main.`,
|
|
3143
|
+
path: `attebury/${consumer.name}`,
|
|
3144
|
+
suggestedFix: "Push the consumer repo and wait for its verification workflow."
|
|
3145
|
+
});
|
|
3146
|
+
return {
|
|
3147
|
+
checked: true,
|
|
3148
|
+
ok: false,
|
|
3149
|
+
expectedWorkflow,
|
|
3150
|
+
headSha,
|
|
3151
|
+
run: null,
|
|
3152
|
+
diagnostics
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
if (headSha && run.headSha && run.headSha !== headSha) {
|
|
3156
|
+
diagnostics.push({
|
|
3157
|
+
code: "release_consumer_ci_head_mismatch",
|
|
3158
|
+
severity: options.strict ? "error" : "warning",
|
|
3159
|
+
message: `${consumer.name} latest ${expectedWorkflow} run is for ${run.headSha}, not checked-out HEAD ${headSha}.`,
|
|
3160
|
+
path: run.url || `attebury/${consumer.name}`,
|
|
3161
|
+
suggestedFix: "Wait for the verification workflow on the current consumer commit, then rerun release status."
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
if (run.status !== "completed" || run.conclusion !== "success") {
|
|
3165
|
+
diagnostics.push({
|
|
3166
|
+
code: "release_consumer_ci_not_successful",
|
|
3167
|
+
severity: options.strict ? "error" : "warning",
|
|
3168
|
+
message: `${consumer.name} ${expectedWorkflow} is ${run.status || "unknown"}/${run.conclusion || "unknown"}.`,
|
|
3169
|
+
path: run.url || `attebury/${consumer.name}`,
|
|
3170
|
+
suggestedFix: "Wait for or fix the consumer verification workflow, then rerun release status."
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
return {
|
|
3174
|
+
checked: true,
|
|
3175
|
+
ok: diagnostics.filter((diagnostic) => diagnostic.severity === "error").length === 0 &&
|
|
3176
|
+
(!options.strict || (run.status === "completed" && run.conclusion === "success" && (!headSha || !run.headSha || run.headSha === headSha))),
|
|
3177
|
+
expectedWorkflow,
|
|
3178
|
+
headSha,
|
|
3179
|
+
run,
|
|
3180
|
+
diagnostics
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
/**
|
|
3185
|
+
* @param {string} cwd
|
|
3186
|
+
* @returns {Array<{ name: string, root: string|null, path: string, version: string|null, found: boolean }>}
|
|
2703
3187
|
*/
|
|
2704
3188
|
function discoverTopogramCliVersionConsumers(cwd) {
|
|
2705
3189
|
const roots = [];
|
|
@@ -2718,6 +3202,7 @@ function discoverTopogramCliVersionConsumers(cwd) {
|
|
|
2718
3202
|
if (fs.existsSync(consumerRoot) && !fs.existsSync(versionPath)) {
|
|
2719
3203
|
found = {
|
|
2720
3204
|
name,
|
|
3205
|
+
root: consumerRoot,
|
|
2721
3206
|
path: versionPath,
|
|
2722
3207
|
version: null,
|
|
2723
3208
|
found: false
|
|
@@ -2729,6 +3214,7 @@ function discoverTopogramCliVersionConsumers(cwd) {
|
|
|
2729
3214
|
}
|
|
2730
3215
|
found = {
|
|
2731
3216
|
name,
|
|
3217
|
+
root: consumerRoot,
|
|
2732
3218
|
path: versionPath,
|
|
2733
3219
|
version: fs.readFileSync(versionPath, "utf8").trim() || null,
|
|
2734
3220
|
found: true
|
|
@@ -2737,6 +3223,7 @@ function discoverTopogramCliVersionConsumers(cwd) {
|
|
|
2737
3223
|
}
|
|
2738
3224
|
consumers.push(found || {
|
|
2739
3225
|
name,
|
|
3226
|
+
root: null,
|
|
2740
3227
|
path: path.join(roots[0], name, "topogram-cli.version"),
|
|
2741
3228
|
version: null,
|
|
2742
3229
|
found: false
|
|
@@ -2766,6 +3253,30 @@ function summarizeConsumerPins(consumers) {
|
|
|
2766
3253
|
};
|
|
2767
3254
|
}
|
|
2768
3255
|
|
|
3256
|
+
/**
|
|
3257
|
+
* @param {Array<{ name: string, matchesLocal?: boolean|null, ci?: ReturnType<typeof inspectConsumerCi>|null }>} consumers
|
|
3258
|
+
* @returns {{ checked: number, passing: number, failing: number, unavailable: number, skipped: number, allCheckedAndPassing: boolean, passingNames: string[], failingNames: string[], unavailableNames: string[], skippedNames: string[] }}
|
|
3259
|
+
*/
|
|
3260
|
+
function summarizeConsumerCi(consumers) {
|
|
3261
|
+
const checked = consumers.filter((consumer) => consumer.ci?.checked);
|
|
3262
|
+
const passingNames = checked.filter((consumer) => consumer.ci?.ok === true).map((consumer) => consumer.name);
|
|
3263
|
+
const failingNames = checked.filter((consumer) => consumer.ci?.ok === false && consumer.ci?.run).map((consumer) => consumer.name);
|
|
3264
|
+
const unavailableNames = checked.filter((consumer) => consumer.ci?.ok === false && !consumer.ci?.run).map((consumer) => consumer.name);
|
|
3265
|
+
const skippedNames = consumers.filter((consumer) => !consumer.ci?.checked).map((consumer) => consumer.name);
|
|
3266
|
+
return {
|
|
3267
|
+
checked: checked.length,
|
|
3268
|
+
passing: passingNames.length,
|
|
3269
|
+
failing: failingNames.length,
|
|
3270
|
+
unavailable: unavailableNames.length,
|
|
3271
|
+
skipped: skippedNames.length,
|
|
3272
|
+
allCheckedAndPassing: consumers.length > 0 && checked.length === consumers.length && failingNames.length === 0 && unavailableNames.length === 0,
|
|
3273
|
+
passingNames,
|
|
3274
|
+
failingNames,
|
|
3275
|
+
unavailableNames,
|
|
3276
|
+
skippedNames
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
|
|
2769
3280
|
/**
|
|
2770
3281
|
* @param {string|null} source
|
|
2771
3282
|
* @returns {{ ok: boolean, node: { version: string, minimum: string, ok: boolean, diagnostics: any[] }, npm: { available: boolean, version: string|null, diagnostics: any[] }, packageRegistry: { required: boolean, reason: string|null, registry: string, configuredRegistry: string|null, registryConfigured: boolean, nodeAuthTokenEnv: boolean, packageName: string, packageSpec: string|null, packageAccess: { ok: boolean, checkedVersion: string|null, diagnostics: any[] } }, lockfile: ReturnType<typeof inspectTopogramCliLockfile>, catalog: ReturnType<typeof buildCatalogDoctorPayload>, diagnostics: any[], errors: string[] }}
|
|
@@ -5565,6 +6076,32 @@ function importAdoptCommand(projectRoot, selector, write = false) {
|
|
|
5565
6076
|
return `topogram import adopt ${selector} ${importProjectCommandPath(projectRoot)} ${write ? "--write" : "--dry-run"}`;
|
|
5566
6077
|
}
|
|
5567
6078
|
|
|
6079
|
+
const BROWNFIELD_BROAD_ADOPT_SELECTORS = [
|
|
6080
|
+
{
|
|
6081
|
+
selector: "from-plan",
|
|
6082
|
+
kind: "plan",
|
|
6083
|
+
label: "approved or pending plan items",
|
|
6084
|
+
matches: (item) => item.current_state === "stage" || item.current_state === "accept"
|
|
6085
|
+
},
|
|
6086
|
+
{ selector: "actors", kind: "kind", label: "actors", matches: (item) => item.kind === "actor" },
|
|
6087
|
+
{ selector: "roles", kind: "kind", label: "roles", matches: (item) => item.kind === "role" },
|
|
6088
|
+
{ selector: "enums", kind: "kind", label: "enums", matches: (item) => item.kind === "enum" },
|
|
6089
|
+
{ selector: "shapes", kind: "kind", label: "shapes", matches: (item) => item.kind === "shape" },
|
|
6090
|
+
{ selector: "entities", kind: "kind", label: "entities", matches: (item) => item.kind === "entity" },
|
|
6091
|
+
{ selector: "capabilities", kind: "kind", label: "capabilities", matches: (item) => item.kind === "capability" },
|
|
6092
|
+
{ selector: "components", kind: "kind", label: "components", matches: (item) => item.kind === "component" },
|
|
6093
|
+
{ selector: "docs", kind: "track", label: "docs", matches: (item) => item.track === "docs" },
|
|
6094
|
+
{
|
|
6095
|
+
selector: "journeys",
|
|
6096
|
+
kind: "track",
|
|
6097
|
+
label: "journey docs",
|
|
6098
|
+
matches: (item) => item.track === "docs" && String(item.canonical_rel_path || "").startsWith("docs/journeys/")
|
|
6099
|
+
},
|
|
6100
|
+
{ selector: "workflows", kind: "track", label: "workflows", matches: (item) => item.track === "workflows" || item.kind === "decision" },
|
|
6101
|
+
{ selector: "verification", kind: "kind", label: "verification", matches: (item) => item.kind === "verification" },
|
|
6102
|
+
{ selector: "ui", kind: "track", label: "UI reports and components", matches: (item) => item.track === "ui" }
|
|
6103
|
+
];
|
|
6104
|
+
|
|
5568
6105
|
function readImportAdoptionArtifacts(inputPath) {
|
|
5569
6106
|
const projectRoot = normalizeProjectRoot(inputPath);
|
|
5570
6107
|
const topogramRoot = normalizeTopogramPath(inputPath);
|
|
@@ -5589,6 +6126,27 @@ function readImportAdoptionArtifacts(inputPath) {
|
|
|
5589
6126
|
};
|
|
5590
6127
|
}
|
|
5591
6128
|
|
|
6129
|
+
function buildBrownfieldBroadAdoptSelectors(projectRoot, adoptionPlan) {
|
|
6130
|
+
const surfaces = adoptionPlan.imported_proposal_surfaces || [];
|
|
6131
|
+
return BROWNFIELD_BROAD_ADOPT_SELECTORS.map((definition) => {
|
|
6132
|
+
const items = surfaces.filter(definition.matches);
|
|
6133
|
+
const pendingItems = items.filter((item) => !["accept", "accepted", "applied"].includes(item.current_state));
|
|
6134
|
+
const appliedItems = items.filter((item) => ["accept", "accepted", "applied"].includes(item.current_state));
|
|
6135
|
+
const blockedItems = items.filter((item) => item.human_review_required);
|
|
6136
|
+
return {
|
|
6137
|
+
selector: definition.selector,
|
|
6138
|
+
kind: definition.kind,
|
|
6139
|
+
label: definition.label,
|
|
6140
|
+
itemCount: items.length,
|
|
6141
|
+
pendingItemCount: pendingItems.length,
|
|
6142
|
+
appliedItemCount: appliedItems.length,
|
|
6143
|
+
blockedItemCount: blockedItems.length,
|
|
6144
|
+
previewCommand: importAdoptCommand(projectRoot, definition.selector, false),
|
|
6145
|
+
writeCommand: importAdoptCommand(projectRoot, definition.selector, true)
|
|
6146
|
+
};
|
|
6147
|
+
}).filter((selector) => selector.itemCount > 0);
|
|
6148
|
+
}
|
|
6149
|
+
|
|
5592
6150
|
function summarizeImportAdoption(adoptionPlan, adoptionStatus, projectRoot) {
|
|
5593
6151
|
const surfaces = adoptionPlan.imported_proposal_surfaces || [];
|
|
5594
6152
|
const slugs = [];
|
|
@@ -5700,6 +6258,7 @@ function printBrownfieldImportPlan(payload) {
|
|
|
5700
6258
|
}
|
|
5701
6259
|
|
|
5702
6260
|
function buildBrownfieldImportAdoptListPayload(inputPath) {
|
|
6261
|
+
const artifacts = readImportAdoptionArtifacts(inputPath);
|
|
5703
6262
|
const plan = buildBrownfieldImportPlanPayload(inputPath);
|
|
5704
6263
|
const selectors = plan.bundles.map((bundle) => ({
|
|
5705
6264
|
selector: `bundle:${bundle.bundle}`,
|
|
@@ -5713,12 +6272,15 @@ function buildBrownfieldImportAdoptListPayload(inputPath) {
|
|
|
5713
6272
|
previewCommand: importAdoptCommand(plan.projectRoot, `bundle:${bundle.bundle}`, false),
|
|
5714
6273
|
writeCommand: importAdoptCommand(plan.projectRoot, `bundle:${bundle.bundle}`, true)
|
|
5715
6274
|
}));
|
|
6275
|
+
const broadSelectors = buildBrownfieldBroadAdoptSelectors(plan.projectRoot, artifacts.adoptionPlan);
|
|
5716
6276
|
return {
|
|
5717
6277
|
ok: true,
|
|
5718
6278
|
projectRoot: plan.projectRoot,
|
|
5719
6279
|
topogramRoot: plan.topogramRoot,
|
|
5720
6280
|
selectorCount: selectors.length,
|
|
5721
6281
|
selectors,
|
|
6282
|
+
broadSelectorCount: broadSelectors.length,
|
|
6283
|
+
broadSelectors,
|
|
5722
6284
|
nextCommand: selectors.find((selector) => !selector.complete)?.previewCommand || plan.commands.status
|
|
5723
6285
|
};
|
|
5724
6286
|
}
|
|
@@ -5734,6 +6296,15 @@ function printBrownfieldImportAdoptList(payload) {
|
|
|
5734
6296
|
console.log(` Preview: ${selector.previewCommand}`);
|
|
5735
6297
|
console.log(` Write: ${selector.writeCommand}`);
|
|
5736
6298
|
}
|
|
6299
|
+
if (payload.broadSelectors.length > 0) {
|
|
6300
|
+
console.log("");
|
|
6301
|
+
console.log("Broad selectors:");
|
|
6302
|
+
for (const selector of payload.broadSelectors) {
|
|
6303
|
+
console.log(`- ${selector.selector}: ${selector.itemCount} ${selector.label}`);
|
|
6304
|
+
console.log(` Preview: ${selector.previewCommand}`);
|
|
6305
|
+
console.log(` Write: ${selector.writeCommand}`);
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
5737
6308
|
console.log("");
|
|
5738
6309
|
console.log(`Next: ${payload.nextCommand}`);
|
|
5739
6310
|
}
|
|
@@ -7547,6 +8118,8 @@ if (args[0] === "version" || args[0] === "--version") {
|
|
|
7547
8118
|
commandArgs = { doctor: true, inputPath: args[1] && !args[1].startsWith("-") ? args[1] : null };
|
|
7548
8119
|
} else if (args[0] === "release" && args[1] === "status") {
|
|
7549
8120
|
commandArgs = { releaseStatus: true, inputPath: null };
|
|
8121
|
+
} else if (args[0] === "release" && args[1] === "roll-consumers") {
|
|
8122
|
+
commandArgs = { releaseRollConsumers: true, releaseRollVersion: args[2], inputPath: null };
|
|
7550
8123
|
} else if (args[0] === "new" || args[0] === "create") {
|
|
7551
8124
|
commandArgs = args.includes("--list-templates")
|
|
7552
8125
|
? { templateList: true, inputPath: null }
|
|
@@ -7763,9 +8336,11 @@ if (commandArgs && Object.prototype.hasOwnProperty.call(commandArgs, "inputPath"
|
|
|
7763
8336
|
}
|
|
7764
8337
|
const emitJson = args.includes("--json");
|
|
7765
8338
|
const strictReleaseStatus = args.includes("--strict");
|
|
8339
|
+
const shouldPushReleaseConsumers = !args.includes("--no-push");
|
|
7766
8340
|
const shouldVersion = Boolean(commandArgs?.version);
|
|
7767
8341
|
const shouldDoctor = Boolean(commandArgs?.doctor);
|
|
7768
8342
|
const shouldReleaseStatus = Boolean(commandArgs?.releaseStatus);
|
|
8343
|
+
const shouldReleaseRollConsumers = Boolean(commandArgs?.releaseRollConsumers);
|
|
7769
8344
|
const shouldCheck = Boolean(commandArgs?.check);
|
|
7770
8345
|
const shouldComponentCheck = Boolean(commandArgs?.componentCheck);
|
|
7771
8346
|
const shouldComponentBehavior = Boolean(commandArgs?.componentBehavior);
|
|
@@ -7999,6 +8574,18 @@ try {
|
|
|
7999
8574
|
process.exit(payload.ok ? 0 : 1);
|
|
8000
8575
|
}
|
|
8001
8576
|
|
|
8577
|
+
if (shouldReleaseRollConsumers) {
|
|
8578
|
+
const payload = buildReleaseRollConsumersPayload(commandArgs.releaseRollVersion, {
|
|
8579
|
+
push: shouldPushReleaseConsumers
|
|
8580
|
+
});
|
|
8581
|
+
if (emitJson) {
|
|
8582
|
+
console.log(stableStringify(payload));
|
|
8583
|
+
} else {
|
|
8584
|
+
printReleaseRollConsumers(payload);
|
|
8585
|
+
}
|
|
8586
|
+
process.exit(payload.ok ? 0 : 1);
|
|
8587
|
+
}
|
|
8588
|
+
|
|
8002
8589
|
if (shouldQueryList) {
|
|
8003
8590
|
const payload = buildQueryListPayload();
|
|
8004
8591
|
if (emitJson) {
|
package/src/workflows.js
CHANGED
|
@@ -1679,7 +1679,8 @@ function summarizeBundleParticipants(bundle) {
|
|
|
1679
1679
|
}
|
|
1680
1680
|
|
|
1681
1681
|
function summarizeBundleSurface(bundle, values, empty = "_none_") {
|
|
1682
|
-
|
|
1682
|
+
const list = Array.isArray(values) ? values : [];
|
|
1683
|
+
return list.length ? list.map((item) => `\`${item}\``).join(", ") : empty;
|
|
1683
1684
|
}
|
|
1684
1685
|
|
|
1685
1686
|
function buildBundleOperatorSummary(bundle) {
|
|
@@ -1693,6 +1694,7 @@ function buildBundleOperatorSummary(bundle) {
|
|
|
1693
1694
|
bundle.id;
|
|
1694
1695
|
const participants = summarizeBundleParticipants(bundle);
|
|
1695
1696
|
const capabilityIds = [...new Set((bundle.capabilities || []).map((entry) => entry.id_hint))].slice(0, 4);
|
|
1697
|
+
const componentIds = [...new Set((bundle.components || []).map((entry) => entry.id_hint))].slice(0, 4);
|
|
1696
1698
|
const screenIds = [...new Set((bundle.screens || []).map((entry) => entry.id_hint))].slice(0, 4);
|
|
1697
1699
|
const routePaths = [...new Set((bundle.uiRoutes || []).map((entry) => entry.path).filter(Boolean))].slice(0, 4);
|
|
1698
1700
|
const workflowIds = [...new Set((bundle.workflows || []).map((entry) => entry.id_hint))].slice(0, 4);
|
|
@@ -1708,6 +1710,7 @@ function buildBundleOperatorSummary(bundle) {
|
|
|
1708
1710
|
const evidenceKinds = [
|
|
1709
1711
|
(bundle.entities || []).length > 0 ? "entity evidence" : null,
|
|
1710
1712
|
(bundle.capabilities || []).length > 0 ? "API capability evidence" : null,
|
|
1713
|
+
(bundle.components || []).length > 0 ? "UI component evidence" : null,
|
|
1711
1714
|
(bundle.screens || []).length > 0 || (bundle.uiRoutes || []).length > 0 ? "UI screen/route evidence" : null,
|
|
1712
1715
|
(bundle.workflows || []).length > 0 ? "workflow evidence" : null,
|
|
1713
1716
|
(bundle.docs || []).length > 0 ? "doc evidence" : null,
|
|
@@ -1723,6 +1726,7 @@ function buildBundleOperatorSummary(bundle) {
|
|
|
1723
1726
|
primaryEntityId,
|
|
1724
1727
|
participants,
|
|
1725
1728
|
capabilityIds,
|
|
1729
|
+
componentIds,
|
|
1726
1730
|
screenIds,
|
|
1727
1731
|
routePaths,
|
|
1728
1732
|
workflowIds,
|
|
@@ -2711,6 +2715,7 @@ function renderCandidateBundleReadme(bundle, proposalSurfaces = []) {
|
|
|
2711
2715
|
`Enums: ${bundle.enums.length}`,
|
|
2712
2716
|
`Capabilities: ${bundle.capabilities.length}`,
|
|
2713
2717
|
`Shapes: ${bundle.shapes.length}`,
|
|
2718
|
+
`Components: ${bundle.components.length}`,
|
|
2714
2719
|
`Screens: ${bundle.screens.length}`,
|
|
2715
2720
|
`UI routes: ${bundle.uiRoutes.length}`,
|
|
2716
2721
|
`UI actions: ${bundle.uiActions.length}`,
|
|
@@ -2728,6 +2733,7 @@ function renderCandidateBundleReadme(bundle, proposalSurfaces = []) {
|
|
|
2728
2733
|
`- Primary entity: ${summary.primaryEntityId ? `\`${summary.primaryEntityId}\`` : "_none_"}`,
|
|
2729
2734
|
`- Participants: ${summary.participants.label}`,
|
|
2730
2735
|
`- Main capabilities: ${summarizeBundleSurface(bundle, summary.capabilityIds)}`,
|
|
2736
|
+
`- Main components: ${summarizeBundleSurface(bundle, summary.componentIds)}`,
|
|
2731
2737
|
`- Main screens: ${summarizeBundleSurface(bundle, summary.screenIds)}`,
|
|
2732
2738
|
`- Main routes: ${summarizeBundleSurface(bundle, summary.routePaths)}`,
|
|
2733
2739
|
`- Main workflows: ${summarizeBundleSurface(bundle, summary.workflowIds)}`,
|
|
@@ -7581,6 +7587,7 @@ function reconcileWorkflow(inputPath, options = {}) {
|
|
|
7581
7587
|
enums: bundle.enums.map((entry) => entry.id_hint),
|
|
7582
7588
|
capabilities: bundle.capabilities.map((entry) => entry.id_hint),
|
|
7583
7589
|
shapes: bundle.shapes.map((entry) => entry.id),
|
|
7590
|
+
components: bundle.components.map((entry) => entry.id_hint),
|
|
7584
7591
|
screens: bundle.screens.map((entry) => entry.id_hint),
|
|
7585
7592
|
workflows: bundle.workflows.map((entry) => entry.id_hint),
|
|
7586
7593
|
docs: bundle.docs.map((entry) => entry.id),
|
|
@@ -7604,10 +7611,11 @@ function reconcileWorkflow(inputPath, options = {}) {
|
|
|
7604
7611
|
: "## Promoted Canonical Items";
|
|
7605
7612
|
files["candidates/reconcile/report.json"] = `${stableStringify(report)}\n`;
|
|
7606
7613
|
const candidateModelBundlesMarkdown = report.candidate_model_bundles.length
|
|
7607
|
-
? report.candidate_model_bundles.map((bundle) => `- \`${bundle.slug}\` (${bundle.actors.length} actors, ${bundle.roles.length} roles, ${bundle.entities.length} entities, ${bundle.enums.length} enums, ${bundle.capabilities.length} capabilities, ${bundle.shapes.length} shapes, ${bundle.screens.length} screens, ${bundle.workflows.length} workflows, ${bundle.docs.length} docs)
|
|
7614
|
+
? report.candidate_model_bundles.map((bundle) => `- \`${bundle.slug}\` (${bundle.actors.length} actors, ${bundle.roles.length} roles, ${bundle.entities.length} entities, ${bundle.enums.length} enums, ${bundle.capabilities.length} capabilities, ${bundle.shapes.length} shapes, ${bundle.components.length} components, ${bundle.screens.length} screens, ${bundle.workflows.length} workflows, ${bundle.docs.length} docs)
|
|
7608
7615
|
- primary concept \`${bundle.operator_summary.primaryConcept}\`${bundle.operator_summary.primaryEntityId ? `, primary entity \`${bundle.operator_summary.primaryEntityId}\`` : ""}
|
|
7609
7616
|
- participants ${bundle.operator_summary.participants.label}
|
|
7610
7617
|
- main capabilities ${summarizeBundleSurface(bundle, bundle.operator_summary.capabilityIds)}
|
|
7618
|
+
- main components ${summarizeBundleSurface(bundle, bundle.operator_summary.componentIds)}
|
|
7611
7619
|
- main routes ${summarizeBundleSurface(bundle, bundle.operator_summary.routePaths)}
|
|
7612
7620
|
- candidate maintained seam mappings ${renderMaintainedSeamCandidatesInline(bundle)}
|
|
7613
7621
|
- permission hints ${bundle.auth_permission_hints?.length ? bundle.auth_permission_hints.map((entry) => formatAuthPermissionHintInline(entry)).join(", ") : "_none_"}
|