@aspruyt/xfg 3.9.0 → 3.9.2
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/cli/settings-command.js +3 -2
- package/dist/cli/sync-command.js +9 -4
- package/dist/output/index.d.ts +2 -1
- package/dist/output/index.js +3 -1
- package/dist/output/lifecycle-report.js +8 -14
- package/dist/output/settings-report.d.ts +2 -0
- package/dist/output/settings-report.js +17 -25
- package/dist/output/sync-report.js +10 -17
- package/dist/output/unified-summary.d.ts +11 -0
- package/dist/output/unified-summary.js +245 -0
- package/package.json +1 -1
|
@@ -9,7 +9,8 @@ import { logger } from "../shared/logger.js";
|
|
|
9
9
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
10
10
|
import { buildErrorResult } from "../output/summary-utils.js";
|
|
11
11
|
import { getManagedRulesets } from "../sync/manifest.js";
|
|
12
|
-
import { formatSettingsReportCLI
|
|
12
|
+
import { formatSettingsReportCLI } from "../output/settings-report.js";
|
|
13
|
+
import { writeUnifiedSummary } from "../output/unified-summary.js";
|
|
13
14
|
import { buildSettingsReport, } from "./settings-report-builder.js";
|
|
14
15
|
import { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./types.js";
|
|
15
16
|
import { RepoLifecycleManager, runLifecycleCheck, } from "../lifecycle/index.js";
|
|
@@ -318,7 +319,7 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
|
|
|
318
319
|
for (const line of lines) {
|
|
319
320
|
console.log(line);
|
|
320
321
|
}
|
|
321
|
-
|
|
322
|
+
writeUnifiedSummary({ settings: report, dryRun: options.dryRun ?? false });
|
|
322
323
|
const hasErrors = report.repos.some((r) => r.error);
|
|
323
324
|
if (hasErrors) {
|
|
324
325
|
process.exit(1);
|
package/dist/cli/sync-command.js
CHANGED
|
@@ -9,8 +9,9 @@ import { logger } from "../shared/logger.js";
|
|
|
9
9
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
10
10
|
import { defaultProcessorFactory } from "./types.js";
|
|
11
11
|
import { buildSyncReport } from "./sync-report-builder.js";
|
|
12
|
-
import { formatSyncReportCLI
|
|
13
|
-
import { buildLifecycleReport, formatLifecycleReportCLI,
|
|
12
|
+
import { formatSyncReportCLI } from "../output/sync-report.js";
|
|
13
|
+
import { buildLifecycleReport, formatLifecycleReportCLI, hasLifecycleChanges, } from "../output/lifecycle-report.js";
|
|
14
|
+
import { writeUnifiedSummary } from "../output/unified-summary.js";
|
|
14
15
|
import { RepoLifecycleManager, runLifecycleCheck, toCreateRepoSettings, } from "../lifecycle/index.js";
|
|
15
16
|
/**
|
|
16
17
|
* Get unique file names from all repos in the config
|
|
@@ -238,14 +239,18 @@ export async function runSync(options, processorFactory = defaultProcessorFactor
|
|
|
238
239
|
console.log(line);
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
|
-
writeLifecycleReportSummary(lifecycleReport, options.dryRun ?? false);
|
|
242
242
|
// Build and display sync report
|
|
243
243
|
const report = buildSyncReport(reportResults);
|
|
244
244
|
console.log("");
|
|
245
245
|
for (const line of formatSyncReportCLI(report)) {
|
|
246
246
|
console.log(line);
|
|
247
247
|
}
|
|
248
|
-
|
|
248
|
+
// Write unified summary to GITHUB_STEP_SUMMARY
|
|
249
|
+
writeUnifiedSummary({
|
|
250
|
+
lifecycle: lifecycleReport,
|
|
251
|
+
sync: report,
|
|
252
|
+
dryRun: options.dryRun ?? false,
|
|
253
|
+
});
|
|
249
254
|
// Exit with error if any failures
|
|
250
255
|
const hasErrors = reportResults.some((r) => r.error);
|
|
251
256
|
if (hasErrors) {
|
package/dist/output/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { formatSyncReportCLI, formatSyncReportMarkdown, writeSyncReportSummary, type SyncReport, type RepoFileChanges, type FileChange, } from "./sync-report.js";
|
|
2
|
-
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, type SettingsReport, type RepoChanges, type RulesetChange, type SettingChange, } from "./settings-report.js";
|
|
2
|
+
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, formatValuePlain, formatRulesetConfigPlain, type SettingsReport, type RepoChanges, type RulesetChange, type SettingChange, } from "./settings-report.js";
|
|
3
|
+
export { formatUnifiedSummaryMarkdown, writeUnifiedSummary, type UnifiedSummaryInput, } from "./unified-summary.js";
|
|
3
4
|
export { formatSummary, isGitHubActions, writeSummary, type MergeOutcome, type FileChanges, type RulesetPlanDetail, type RepoSettingsPlanDetail, type RepoResult, type SummaryData, } from "./github-summary.js";
|
|
4
5
|
export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
|
package/dist/output/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Sync report (repo-grouped file changes)
|
|
2
2
|
export { formatSyncReportCLI, formatSyncReportMarkdown, writeSyncReportSummary, } from "./sync-report.js";
|
|
3
3
|
// Settings report (repo-grouped settings changes)
|
|
4
|
-
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, } from "./settings-report.js";
|
|
4
|
+
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, formatValuePlain, formatRulesetConfigPlain, } from "./settings-report.js";
|
|
5
|
+
// Unified summary (lifecycle + sync + settings in one diff block)
|
|
6
|
+
export { formatUnifiedSummaryMarkdown, writeUnifiedSummary, } from "./unified-summary.js";
|
|
5
7
|
// GitHub Actions summary
|
|
6
8
|
export { formatSummary, isGitHubActions, writeSummary, } from "./github-summary.js";
|
|
7
9
|
// Summary utilities
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
// src/output/lifecycle-report.ts
|
|
2
2
|
import { appendFileSync } from "node:fs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
function escapeHtml(text) {
|
|
5
|
-
return text
|
|
6
|
-
.replace(/&/g, "&")
|
|
7
|
-
.replace(/</g, "<")
|
|
8
|
-
.replace(/>/g, ">");
|
|
9
|
-
}
|
|
10
4
|
// =============================================================================
|
|
11
5
|
// Builder
|
|
12
6
|
// =============================================================================
|
|
@@ -103,35 +97,35 @@ export function formatLifecycleReportMarkdown(report, dryRun) {
|
|
|
103
97
|
lines.push("> This was a dry run — no changes were applied");
|
|
104
98
|
lines.push("");
|
|
105
99
|
}
|
|
106
|
-
//
|
|
100
|
+
// Diff block
|
|
107
101
|
const diffLines = [];
|
|
108
102
|
for (const action of report.actions) {
|
|
109
103
|
if (action.action === "existed")
|
|
110
104
|
continue;
|
|
111
105
|
switch (action.action) {
|
|
112
106
|
case "created":
|
|
113
|
-
diffLines.push(
|
|
107
|
+
diffLines.push(`+ CREATE ${action.repoName}`);
|
|
114
108
|
break;
|
|
115
109
|
case "forked":
|
|
116
|
-
diffLines.push(
|
|
110
|
+
diffLines.push(`+ FORK ${action.upstream ?? "upstream"} -> ${action.repoName}`);
|
|
117
111
|
break;
|
|
118
112
|
case "migrated":
|
|
119
|
-
diffLines.push(
|
|
113
|
+
diffLines.push(`+ MIGRATE ${action.source ?? "source"} -> ${action.repoName}`);
|
|
120
114
|
break;
|
|
121
115
|
}
|
|
122
116
|
if (action.settings) {
|
|
123
117
|
if (action.settings.visibility) {
|
|
124
|
-
diffLines.push(
|
|
118
|
+
diffLines.push(` visibility: ${action.settings.visibility}`);
|
|
125
119
|
}
|
|
126
120
|
if (action.settings.description) {
|
|
127
|
-
diffLines.push(
|
|
121
|
+
diffLines.push(` description: "${action.settings.description}"`);
|
|
128
122
|
}
|
|
129
123
|
}
|
|
130
124
|
}
|
|
131
125
|
if (diffLines.length > 0) {
|
|
132
|
-
lines.push("
|
|
126
|
+
lines.push("```diff");
|
|
133
127
|
lines.push(...diffLines);
|
|
134
|
-
lines.push("
|
|
128
|
+
lines.push("```");
|
|
135
129
|
lines.push("");
|
|
136
130
|
}
|
|
137
131
|
// Summary
|
|
@@ -33,5 +33,7 @@ export interface RulesetChange {
|
|
|
33
33
|
config?: Ruleset;
|
|
34
34
|
}
|
|
35
35
|
export declare function formatSettingsReportCLI(report: SettingsReport): string[];
|
|
36
|
+
export declare function formatValuePlain(val: unknown): string;
|
|
37
|
+
export declare function formatRulesetConfigPlain(config: Ruleset): string[];
|
|
36
38
|
export declare function formatSettingsReportMarkdown(report: SettingsReport, dryRun: boolean): string;
|
|
37
39
|
export declare function writeSettingsReportSummary(report: SettingsReport, dryRun: boolean): void;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { formatPropertyTree, } from "../settings/rulesets/formatter.js";
|
|
4
|
-
function escapeHtml(text) {
|
|
5
|
-
return text
|
|
6
|
-
.replace(/&/g, "&")
|
|
7
|
-
.replace(/</g, "<")
|
|
8
|
-
.replace(/>/g, ">");
|
|
9
|
-
}
|
|
10
4
|
// =============================================================================
|
|
11
5
|
// Helpers
|
|
12
6
|
// =============================================================================
|
|
@@ -159,7 +153,7 @@ export function formatSettingsReportCLI(report) {
|
|
|
159
153
|
// =============================================================================
|
|
160
154
|
// Markdown Formatter
|
|
161
155
|
// =============================================================================
|
|
162
|
-
function formatValuePlain(val) {
|
|
156
|
+
export function formatValuePlain(val) {
|
|
163
157
|
if (val === null)
|
|
164
158
|
return "null";
|
|
165
159
|
if (val === undefined)
|
|
@@ -170,7 +164,7 @@ function formatValuePlain(val) {
|
|
|
170
164
|
return val ? "true" : "false";
|
|
171
165
|
return String(val);
|
|
172
166
|
}
|
|
173
|
-
function formatRulesetConfigPlain(config) {
|
|
167
|
+
export function formatRulesetConfigPlain(config) {
|
|
174
168
|
const lines = [];
|
|
175
169
|
function renderObject(obj, depth) {
|
|
176
170
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -222,8 +216,8 @@ function formatRulesetConfigPlain(config) {
|
|
|
222
216
|
export function formatSettingsReportMarkdown(report, dryRun) {
|
|
223
217
|
const lines = [];
|
|
224
218
|
// Title
|
|
225
|
-
const
|
|
226
|
-
lines.push(
|
|
219
|
+
const title = dryRun ? "## xfg Plan" : "## xfg Apply";
|
|
220
|
+
lines.push(title);
|
|
227
221
|
lines.push("");
|
|
228
222
|
// Dry-run warning
|
|
229
223
|
if (dryRun) {
|
|
@@ -239,57 +233,55 @@ export function formatSettingsReportMarkdown(report, dryRun) {
|
|
|
239
233
|
!repo.error) {
|
|
240
234
|
continue;
|
|
241
235
|
}
|
|
242
|
-
diffLines.push(
|
|
236
|
+
diffLines.push(`@@ ${repo.repoName} @@`);
|
|
243
237
|
for (const setting of repo.settings) {
|
|
244
238
|
// Skip settings where both values are undefined
|
|
245
239
|
if (setting.oldValue === undefined && setting.newValue === undefined) {
|
|
246
240
|
continue;
|
|
247
241
|
}
|
|
248
242
|
if (setting.action === "add") {
|
|
249
|
-
diffLines.push(
|
|
243
|
+
diffLines.push(`+ ${setting.name}: ${formatValuePlain(setting.newValue)}`);
|
|
250
244
|
}
|
|
251
245
|
else {
|
|
252
|
-
diffLines.push(
|
|
246
|
+
diffLines.push(`! ${setting.name}: ${formatValuePlain(setting.oldValue)} → ${formatValuePlain(setting.newValue)}`);
|
|
253
247
|
}
|
|
254
248
|
}
|
|
255
249
|
for (const ruleset of repo.rulesets) {
|
|
256
250
|
if (ruleset.action === "create") {
|
|
257
|
-
diffLines.push(
|
|
251
|
+
diffLines.push(`+ ruleset "${ruleset.name}"`);
|
|
258
252
|
if (ruleset.config) {
|
|
259
|
-
|
|
260
|
-
diffLines.push(`<span style="color:#3fb950">${escapeHtml(line)}</span>`);
|
|
261
|
-
}
|
|
253
|
+
diffLines.push(...formatRulesetConfigPlain(ruleset.config));
|
|
262
254
|
}
|
|
263
255
|
}
|
|
264
256
|
else if (ruleset.action === "update") {
|
|
265
|
-
diffLines.push(
|
|
257
|
+
diffLines.push(`! ruleset "${ruleset.name}"`);
|
|
266
258
|
if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
|
|
267
259
|
for (const diff of ruleset.propertyDiffs) {
|
|
268
260
|
const path = diff.path.join(".");
|
|
269
261
|
if (diff.action === "add") {
|
|
270
|
-
diffLines.push(
|
|
262
|
+
diffLines.push(`+ ${path}: ${formatValuePlain(diff.newValue)}`);
|
|
271
263
|
}
|
|
272
264
|
else if (diff.action === "change") {
|
|
273
|
-
diffLines.push(
|
|
265
|
+
diffLines.push(`! ${path}: ${formatValuePlain(diff.oldValue)} → ${formatValuePlain(diff.newValue)}`);
|
|
274
266
|
}
|
|
275
267
|
else if (diff.action === "remove") {
|
|
276
|
-
diffLines.push(
|
|
268
|
+
diffLines.push(`- ${path}`);
|
|
277
269
|
}
|
|
278
270
|
}
|
|
279
271
|
}
|
|
280
272
|
}
|
|
281
273
|
else if (ruleset.action === "delete") {
|
|
282
|
-
diffLines.push(
|
|
274
|
+
diffLines.push(`- ruleset "${ruleset.name}"`);
|
|
283
275
|
}
|
|
284
276
|
}
|
|
285
277
|
if (repo.error) {
|
|
286
|
-
diffLines.push(
|
|
278
|
+
diffLines.push(`- Error: ${repo.error}`);
|
|
287
279
|
}
|
|
288
280
|
}
|
|
289
281
|
if (diffLines.length > 0) {
|
|
290
|
-
lines.push("
|
|
282
|
+
lines.push("```diff");
|
|
291
283
|
lines.push(...diffLines);
|
|
292
|
-
lines.push("
|
|
284
|
+
lines.push("```");
|
|
293
285
|
lines.push("");
|
|
294
286
|
}
|
|
295
287
|
// Summary
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
// src/output/sync-report.ts
|
|
2
2
|
import { appendFileSync } from "node:fs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
function escapeHtml(text) {
|
|
5
|
-
return text
|
|
6
|
-
.replace(/&/g, "&")
|
|
7
|
-
.replace(/</g, "<")
|
|
8
|
-
.replace(/>/g, ">");
|
|
9
|
-
}
|
|
10
4
|
function formatSummary(totals) {
|
|
11
5
|
const total = totals.files.create + totals.files.update + totals.files.delete;
|
|
12
6
|
if (total === 0) {
|
|
@@ -55,8 +49,8 @@ export function formatSyncReportCLI(report) {
|
|
|
55
49
|
export function formatSyncReportMarkdown(report, dryRun) {
|
|
56
50
|
const lines = [];
|
|
57
51
|
// Title
|
|
58
|
-
const
|
|
59
|
-
lines.push(
|
|
52
|
+
const title = dryRun ? "## xfg Plan" : "## xfg Apply";
|
|
53
|
+
lines.push(title);
|
|
60
54
|
lines.push("");
|
|
61
55
|
// Dry-run warning
|
|
62
56
|
if (dryRun) {
|
|
@@ -64,33 +58,32 @@ export function formatSyncReportMarkdown(report, dryRun) {
|
|
|
64
58
|
lines.push("> This was a dry run — no changes were applied");
|
|
65
59
|
lines.push("");
|
|
66
60
|
}
|
|
67
|
-
//
|
|
68
|
-
// Colors: green (#3fb950) for creates, yellow (#d29922) for changes, red (#f85149) for deletes
|
|
61
|
+
// Diff block
|
|
69
62
|
const diffLines = [];
|
|
70
63
|
for (const repo of report.repos) {
|
|
71
64
|
if (repo.files.length === 0 && !repo.error) {
|
|
72
65
|
continue;
|
|
73
66
|
}
|
|
74
|
-
diffLines.push(
|
|
67
|
+
diffLines.push(`@@ ${repo.repoName} @@`);
|
|
75
68
|
for (const file of repo.files) {
|
|
76
69
|
if (file.action === "create") {
|
|
77
|
-
diffLines.push(
|
|
70
|
+
diffLines.push(`+ ${file.path}`);
|
|
78
71
|
}
|
|
79
72
|
else if (file.action === "update") {
|
|
80
|
-
diffLines.push(
|
|
73
|
+
diffLines.push(`! ${file.path}`);
|
|
81
74
|
}
|
|
82
75
|
else if (file.action === "delete") {
|
|
83
|
-
diffLines.push(
|
|
76
|
+
diffLines.push(`- ${file.path}`);
|
|
84
77
|
}
|
|
85
78
|
}
|
|
86
79
|
if (repo.error) {
|
|
87
|
-
diffLines.push(
|
|
80
|
+
diffLines.push(`- Error: ${repo.error}`);
|
|
88
81
|
}
|
|
89
82
|
}
|
|
90
83
|
if (diffLines.length > 0) {
|
|
91
|
-
lines.push("
|
|
84
|
+
lines.push("```diff");
|
|
92
85
|
lines.push(...diffLines);
|
|
93
|
-
lines.push("
|
|
86
|
+
lines.push("```");
|
|
94
87
|
lines.push("");
|
|
95
88
|
}
|
|
96
89
|
// Summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LifecycleReport } from "./lifecycle-report.js";
|
|
2
|
+
import type { SyncReport } from "./sync-report.js";
|
|
3
|
+
import type { SettingsReport } from "./settings-report.js";
|
|
4
|
+
export interface UnifiedSummaryInput {
|
|
5
|
+
lifecycle?: LifecycleReport;
|
|
6
|
+
sync?: SyncReport;
|
|
7
|
+
settings?: SettingsReport;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function formatUnifiedSummaryMarkdown(input: UnifiedSummaryInput): string;
|
|
11
|
+
export declare function writeUnifiedSummary(input: UnifiedSummaryInput): void;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// src/output/unified-summary.ts
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
import { hasLifecycleChanges } from "./lifecycle-report.js";
|
|
4
|
+
import { formatValuePlain, formatRulesetConfigPlain, } from "./settings-report.js";
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// Helpers
|
|
7
|
+
// =============================================================================
|
|
8
|
+
function formatCombinedSummary(input) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
// Lifecycle totals
|
|
11
|
+
if (input.lifecycle) {
|
|
12
|
+
const t = input.lifecycle.totals;
|
|
13
|
+
const repoTotal = t.created + t.forked + t.migrated;
|
|
14
|
+
if (repoTotal > 0) {
|
|
15
|
+
const repoParts = [];
|
|
16
|
+
if (t.created > 0)
|
|
17
|
+
repoParts.push(`${t.created} to create`);
|
|
18
|
+
if (t.forked > 0)
|
|
19
|
+
repoParts.push(`${t.forked} to fork`);
|
|
20
|
+
if (t.migrated > 0)
|
|
21
|
+
repoParts.push(`${t.migrated} to migrate`);
|
|
22
|
+
const repoWord = repoTotal === 1 ? "repo" : "repos";
|
|
23
|
+
parts.push(`${repoTotal} ${repoWord} (${repoParts.join(", ")})`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Sync totals
|
|
27
|
+
if (input.sync) {
|
|
28
|
+
const t = input.sync.totals;
|
|
29
|
+
const fileTotal = t.files.create + t.files.update + t.files.delete;
|
|
30
|
+
if (fileTotal > 0) {
|
|
31
|
+
const fileParts = [];
|
|
32
|
+
if (t.files.create > 0)
|
|
33
|
+
fileParts.push(`${t.files.create} to create`);
|
|
34
|
+
if (t.files.update > 0)
|
|
35
|
+
fileParts.push(`${t.files.update} to update`);
|
|
36
|
+
if (t.files.delete > 0)
|
|
37
|
+
fileParts.push(`${t.files.delete} to delete`);
|
|
38
|
+
const fileWord = fileTotal === 1 ? "file" : "files";
|
|
39
|
+
parts.push(`${fileTotal} ${fileWord} (${fileParts.join(", ")})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Settings totals
|
|
43
|
+
if (input.settings) {
|
|
44
|
+
const t = input.settings.totals;
|
|
45
|
+
const settingsTotal = t.settings.add + t.settings.change;
|
|
46
|
+
if (settingsTotal > 0) {
|
|
47
|
+
const settingWord = settingsTotal === 1 ? "setting" : "settings";
|
|
48
|
+
const actions = [];
|
|
49
|
+
if (t.settings.add > 0)
|
|
50
|
+
actions.push(`${t.settings.add} to add`);
|
|
51
|
+
if (t.settings.change > 0)
|
|
52
|
+
actions.push(`${t.settings.change} to change`);
|
|
53
|
+
parts.push(`${settingsTotal} ${settingWord} (${actions.join(", ")})`);
|
|
54
|
+
}
|
|
55
|
+
const rulesetsTotal = t.rulesets.create + t.rulesets.update + t.rulesets.delete;
|
|
56
|
+
if (rulesetsTotal > 0) {
|
|
57
|
+
const rulesetWord = rulesetsTotal === 1 ? "ruleset" : "rulesets";
|
|
58
|
+
const actions = [];
|
|
59
|
+
if (t.rulesets.create > 0)
|
|
60
|
+
actions.push(`${t.rulesets.create} to create`);
|
|
61
|
+
if (t.rulesets.update > 0)
|
|
62
|
+
actions.push(`${t.rulesets.update} to update`);
|
|
63
|
+
if (t.rulesets.delete > 0)
|
|
64
|
+
actions.push(`${t.rulesets.delete} to delete`);
|
|
65
|
+
parts.push(`${rulesetsTotal} ${rulesetWord} (${actions.join(", ")})`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (parts.length === 0) {
|
|
69
|
+
return "No changes";
|
|
70
|
+
}
|
|
71
|
+
return `Plan: ${parts.join(", ")}`;
|
|
72
|
+
}
|
|
73
|
+
function hasAnyChanges(input) {
|
|
74
|
+
if (input.lifecycle && hasLifecycleChanges(input.lifecycle))
|
|
75
|
+
return true;
|
|
76
|
+
if (input.sync?.repos.some((r) => r.files.length > 0 || r.error))
|
|
77
|
+
return true;
|
|
78
|
+
if (input.settings?.repos.some((r) => r.settings.length > 0 || r.rulesets.length > 0 || r.error))
|
|
79
|
+
return true;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// Diff line builders
|
|
84
|
+
// =============================================================================
|
|
85
|
+
function renderLifecycleLines(lcAction, diffLines) {
|
|
86
|
+
if (lcAction.action === "existed")
|
|
87
|
+
return;
|
|
88
|
+
switch (lcAction.action) {
|
|
89
|
+
case "created":
|
|
90
|
+
diffLines.push(`+ CREATE`);
|
|
91
|
+
break;
|
|
92
|
+
case "forked":
|
|
93
|
+
diffLines.push(`+ FORK ${lcAction.upstream ?? "upstream"} -> ${lcAction.repoName}`);
|
|
94
|
+
break;
|
|
95
|
+
case "migrated":
|
|
96
|
+
diffLines.push(`+ MIGRATE ${lcAction.source ?? "source"} -> ${lcAction.repoName}`);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
if (lcAction.settings) {
|
|
100
|
+
if (lcAction.settings.visibility) {
|
|
101
|
+
diffLines.push(`+ visibility: ${lcAction.settings.visibility}`);
|
|
102
|
+
}
|
|
103
|
+
if (lcAction.settings.description) {
|
|
104
|
+
diffLines.push(`+ description: "${lcAction.settings.description}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function renderSyncLines(syncRepo, diffLines) {
|
|
109
|
+
for (const file of syncRepo.files) {
|
|
110
|
+
if (file.action === "create") {
|
|
111
|
+
diffLines.push(`+ ${file.path}`);
|
|
112
|
+
}
|
|
113
|
+
else if (file.action === "update") {
|
|
114
|
+
diffLines.push(`! ${file.path}`);
|
|
115
|
+
}
|
|
116
|
+
else if (file.action === "delete") {
|
|
117
|
+
diffLines.push(`- ${file.path}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (syncRepo.error) {
|
|
121
|
+
diffLines.push(`- Error: ${syncRepo.error}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function renderSettingsLines(settingsRepo, diffLines) {
|
|
125
|
+
for (const setting of settingsRepo.settings) {
|
|
126
|
+
if (setting.oldValue === undefined && setting.newValue === undefined) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (setting.action === "add") {
|
|
130
|
+
diffLines.push(`+ ${setting.name}: ${formatValuePlain(setting.newValue)}`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
diffLines.push(`! ${setting.name}: ${formatValuePlain(setting.oldValue)} → ${formatValuePlain(setting.newValue)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const ruleset of settingsRepo.rulesets) {
|
|
137
|
+
if (ruleset.action === "create") {
|
|
138
|
+
diffLines.push(`+ ruleset "${ruleset.name}"`);
|
|
139
|
+
if (ruleset.config) {
|
|
140
|
+
diffLines.push(...formatRulesetConfigPlain(ruleset.config));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (ruleset.action === "update") {
|
|
144
|
+
diffLines.push(`! ruleset "${ruleset.name}"`);
|
|
145
|
+
if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
|
|
146
|
+
for (const diff of ruleset.propertyDiffs) {
|
|
147
|
+
const path = diff.path.join(".");
|
|
148
|
+
if (diff.action === "add") {
|
|
149
|
+
diffLines.push(`+ ${path}: ${formatValuePlain(diff.newValue)}`);
|
|
150
|
+
}
|
|
151
|
+
else if (diff.action === "change") {
|
|
152
|
+
diffLines.push(`! ${path}: ${formatValuePlain(diff.oldValue)} → ${formatValuePlain(diff.newValue)}`);
|
|
153
|
+
}
|
|
154
|
+
else if (diff.action === "remove") {
|
|
155
|
+
diffLines.push(`- ${path}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (ruleset.action === "delete") {
|
|
161
|
+
diffLines.push(`- ruleset "${ruleset.name}"`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (settingsRepo.error) {
|
|
165
|
+
diffLines.push(`- Error: ${settingsRepo.error}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// Markdown Formatter
|
|
170
|
+
// =============================================================================
|
|
171
|
+
export function formatUnifiedSummaryMarkdown(input) {
|
|
172
|
+
if (!hasAnyChanges(input)) {
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
const lines = [];
|
|
176
|
+
// Title: "xfg Plan" for dry-run, "xfg Apply" otherwise
|
|
177
|
+
const title = input.dryRun ? "## xfg Plan" : "## xfg Apply";
|
|
178
|
+
lines.push(title);
|
|
179
|
+
lines.push("");
|
|
180
|
+
// Dry-run warning
|
|
181
|
+
if (input.dryRun) {
|
|
182
|
+
lines.push("> [!WARNING]");
|
|
183
|
+
lines.push("> This was a dry run — no changes were applied");
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
// Build lookup maps
|
|
187
|
+
const lifecycleByRepo = new Map((input.lifecycle?.actions ?? []).map((a) => [a.repoName, a]));
|
|
188
|
+
const syncByRepo = new Map((input.sync?.repos ?? []).map((r) => [r.repoName, r]));
|
|
189
|
+
const settingsByRepo = new Map((input.settings?.repos ?? []).map((r) => [r.repoName, r]));
|
|
190
|
+
// Collect all repo names in order
|
|
191
|
+
const allRepos = [];
|
|
192
|
+
const addRepo = (name) => {
|
|
193
|
+
if (!allRepos.includes(name))
|
|
194
|
+
allRepos.push(name);
|
|
195
|
+
};
|
|
196
|
+
for (const a of input.lifecycle?.actions ?? [])
|
|
197
|
+
addRepo(a.repoName);
|
|
198
|
+
for (const r of input.sync?.repos ?? [])
|
|
199
|
+
addRepo(r.repoName);
|
|
200
|
+
for (const r of input.settings?.repos ?? [])
|
|
201
|
+
addRepo(r.repoName);
|
|
202
|
+
// Diff block
|
|
203
|
+
const diffLines = [];
|
|
204
|
+
for (const repoName of allRepos) {
|
|
205
|
+
const lcAction = lifecycleByRepo.get(repoName);
|
|
206
|
+
const syncRepo = syncByRepo.get(repoName);
|
|
207
|
+
const settingsRepo = settingsByRepo.get(repoName);
|
|
208
|
+
const hasLcChange = lcAction && lcAction.action !== "existed";
|
|
209
|
+
const hasSyncChanges = syncRepo && (syncRepo.files.length > 0 || syncRepo.error);
|
|
210
|
+
const hasSettingsChanges = settingsRepo &&
|
|
211
|
+
(settingsRepo.settings.length > 0 ||
|
|
212
|
+
settingsRepo.rulesets.length > 0 ||
|
|
213
|
+
settingsRepo.error);
|
|
214
|
+
if (!hasLcChange && !hasSyncChanges && !hasSettingsChanges)
|
|
215
|
+
continue;
|
|
216
|
+
diffLines.push(`@@ ${repoName} @@`);
|
|
217
|
+
if (lcAction)
|
|
218
|
+
renderLifecycleLines(lcAction, diffLines);
|
|
219
|
+
if (syncRepo)
|
|
220
|
+
renderSyncLines(syncRepo, diffLines);
|
|
221
|
+
if (settingsRepo)
|
|
222
|
+
renderSettingsLines(settingsRepo, diffLines);
|
|
223
|
+
}
|
|
224
|
+
if (diffLines.length > 0) {
|
|
225
|
+
lines.push("```diff");
|
|
226
|
+
lines.push(...diffLines);
|
|
227
|
+
lines.push("```");
|
|
228
|
+
lines.push("");
|
|
229
|
+
}
|
|
230
|
+
// Combined summary
|
|
231
|
+
lines.push(`**${formatCombinedSummary(input)}**`);
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
// =============================================================================
|
|
235
|
+
// File Writer
|
|
236
|
+
// =============================================================================
|
|
237
|
+
export function writeUnifiedSummary(input) {
|
|
238
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
239
|
+
if (!summaryPath)
|
|
240
|
+
return;
|
|
241
|
+
const markdown = formatUnifiedSummaryMarkdown(input);
|
|
242
|
+
if (!markdown)
|
|
243
|
+
return;
|
|
244
|
+
appendFileSync(summaryPath, "\n" + markdown + "\n");
|
|
245
|
+
}
|
package/package.json
CHANGED