@aspruyt/xfg 3.7.6 → 3.8.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/cli/index.d.ts +6 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +70 -0
- package/dist/cli/settings-command.d.ts +10 -0
- package/dist/cli/settings-command.js +237 -0
- package/dist/cli/settings-report-builder.d.ts +19 -0
- package/dist/cli/settings-report-builder.js +64 -0
- package/dist/cli/sync-command.d.ts +25 -0
- package/dist/cli/sync-command.js +180 -0
- package/dist/cli/sync-report-builder.d.ts +15 -0
- package/dist/cli/sync-report-builder.js +29 -0
- package/dist/cli/types.d.ts +45 -0
- package/dist/cli/types.js +15 -0
- package/dist/cli.js +2 -19
- package/dist/{file-reference-resolver.d.ts → config/file-reference-resolver.d.ts} +1 -1
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +12 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/{config.js → config/loader.js} +3 -24
- package/dist/{config-normalizer.d.ts → config/normalizer.d.ts} +1 -1
- package/dist/{config-normalizer.js → config/normalizer.js} +1 -1
- package/dist/{config.d.ts → config/types.d.ts} +5 -9
- package/dist/config/types.js +16 -0
- package/dist/{config-validator.d.ts → config/validator.d.ts} +5 -5
- package/dist/{config-validator.js → config/validator.js} +60 -372
- package/dist/config/validators/file-validator.d.ts +22 -0
- package/dist/config/validators/file-validator.js +46 -0
- package/dist/config/validators/index.d.ts +3 -0
- package/dist/config/validators/index.js +6 -0
- package/dist/config/validators/repo-settings-validator.d.ts +10 -0
- package/dist/config/validators/repo-settings-validator.js +71 -0
- package/dist/config/validators/ruleset-validator.d.ts +18 -0
- package/dist/config/validators/ruleset-validator.js +201 -0
- package/dist/index.d.ts +3 -66
- package/dist/index.js +3 -474
- package/dist/output/index.d.ts +4 -0
- package/dist/output/index.js +8 -0
- package/dist/output/settings-report.d.ts +37 -0
- package/dist/output/settings-report.js +300 -0
- package/dist/{summary-utils.d.ts → output/summary-utils.d.ts} +3 -3
- package/dist/output/sync-report.d.ts +24 -0
- package/dist/output/sync-report.js +99 -0
- package/dist/settings/index.d.ts +2 -0
- package/dist/settings/index.js +4 -0
- package/dist/{repo-settings-diff.d.ts → settings/repo-settings/diff.d.ts} +2 -2
- package/dist/{repo-settings-plan-formatter.d.ts → settings/repo-settings/formatter.d.ts} +3 -1
- package/dist/{repo-settings-plan-formatter.js → settings/repo-settings/formatter.js} +11 -2
- package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.d.ts +4 -4
- package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.js +3 -3
- package/dist/settings/repo-settings/index.d.ts +5 -0
- package/dist/settings/repo-settings/index.js +10 -0
- package/dist/{repo-settings-processor.d.ts → settings/repo-settings/processor.d.ts} +4 -4
- package/dist/{repo-settings-processor.js → settings/repo-settings/processor.js} +14 -8
- package/dist/{strategies/repo-settings-strategy.d.ts → settings/repo-settings/types.d.ts} +2 -2
- package/dist/settings/rulesets/diff-algorithm.d.ts +18 -0
- package/dist/settings/rulesets/diff-algorithm.js +166 -0
- package/dist/{ruleset-diff.d.ts → settings/rulesets/diff.d.ts} +2 -2
- package/dist/{ruleset-diff.js → settings/rulesets/diff.js} +1 -1
- package/dist/{ruleset-plan-formatter.d.ts → settings/rulesets/formatter.d.ts} +7 -12
- package/dist/{ruleset-plan-formatter.js → settings/rulesets/formatter.js} +10 -165
- package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.d.ts +4 -4
- package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.js +3 -3
- package/dist/settings/rulesets/index.d.ts +6 -0
- package/dist/settings/rulesets/index.js +10 -0
- package/dist/{ruleset-processor.d.ts → settings/rulesets/processor.d.ts} +4 -4
- package/dist/{ruleset-processor.js → settings/rulesets/processor.js} +6 -6
- package/dist/{strategies/ruleset-strategy.d.ts → settings/rulesets/types.d.ts} +2 -2
- package/dist/{command-executor.d.ts → shared/command-executor.d.ts} +10 -2
- package/dist/{command-executor.js → shared/command-executor.js} +2 -1
- package/dist/shared/index.d.ts +8 -0
- package/dist/shared/index.js +16 -0
- package/dist/{logger.d.ts → shared/logger.d.ts} +1 -1
- package/dist/{logger.js → shared/logger.js} +1 -1
- package/dist/sync/auth-options-builder.d.ts +12 -0
- package/dist/sync/auth-options-builder.js +54 -0
- package/dist/sync/branch-manager.d.ts +7 -0
- package/dist/sync/branch-manager.js +36 -0
- package/dist/sync/commit-message.d.ts +11 -0
- package/dist/sync/commit-message.js +27 -0
- package/dist/sync/commit-push-manager.d.ts +8 -0
- package/dist/sync/commit-push-manager.js +71 -0
- package/dist/sync/file-sync-orchestrator.d.ts +11 -0
- package/dist/sync/file-sync-orchestrator.js +58 -0
- package/dist/sync/file-writer.d.ts +18 -0
- package/dist/sync/file-writer.js +101 -0
- package/dist/sync/index.d.ts +14 -0
- package/dist/sync/index.js +17 -0
- package/dist/sync/manifest-manager.d.ts +10 -0
- package/dist/sync/manifest-manager.js +64 -0
- package/dist/sync/pr-merge-handler.d.ts +11 -0
- package/dist/sync/pr-merge-handler.js +63 -0
- package/dist/sync/repository-processor.d.ts +30 -0
- package/dist/sync/repository-processor.js +298 -0
- package/dist/sync/repository-session.d.ts +9 -0
- package/dist/sync/repository-session.js +35 -0
- package/dist/sync/types.d.ts +304 -0
- package/dist/{xfg-template.d.ts → sync/xfg-template.d.ts} +2 -2
- package/dist/{authenticated-git-ops.js → vcs/authenticated-git-ops.js} +3 -3
- package/dist/{strategies → vcs}/azure-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/azure-pr-strategy.js +5 -5
- package/dist/{strategies → vcs}/commit-strategy-selector.d.ts +3 -3
- package/dist/{strategies → vcs}/commit-strategy-selector.js +1 -1
- package/dist/{strategies → vcs}/git-commit-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/git-commit-strategy.js +3 -3
- package/dist/{git-ops.d.ts → vcs/git-ops.d.ts} +1 -1
- package/dist/{git-ops.js → vcs/git-ops.js} +4 -4
- package/dist/{github-app-token-manager.d.ts → vcs/github-app-token-manager.d.ts} +1 -1
- package/dist/{github-app-token-manager.js → vcs/github-app-token-manager.js} +1 -1
- package/dist/{strategies → vcs}/github-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/github-pr-strategy.js +30 -33
- package/dist/{strategies → vcs}/gitlab-pr-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/gitlab-pr-strategy.js +5 -5
- package/dist/{strategies → vcs}/graphql-commit-strategy.d.ts +2 -2
- package/dist/{strategies → vcs}/graphql-commit-strategy.js +3 -3
- package/dist/vcs/index.d.ts +16 -0
- package/dist/{strategies → vcs}/index.js +15 -10
- package/dist/{pr-creator.d.ts → vcs/pr-creator.d.ts} +4 -4
- package/dist/{pr-creator.js → vcs/pr-creator.js} +3 -3
- package/dist/vcs/pr-strategy.d.ts +41 -0
- package/dist/{strategies → vcs}/pr-strategy.js +1 -1
- package/dist/{strategies/pr-strategy.d.ts → vcs/types.d.ts} +32 -35
- package/dist/vcs/types.js +1 -0
- package/package.json +2 -2
- package/dist/plan-formatter.d.ts +0 -39
- package/dist/plan-formatter.js +0 -84
- package/dist/plan-summary.d.ts +0 -8
- package/dist/plan-summary.js +0 -110
- package/dist/repository-processor.d.ts +0 -79
- package/dist/repository-processor.js +0 -659
- package/dist/resource-converters.d.ts +0 -28
- package/dist/resource-converters.js +0 -107
- package/dist/strategies/commit-strategy.d.ts +0 -36
- package/dist/strategies/index.d.ts +0 -18
- /package/dist/{file-reference-resolver.js → config/file-reference-resolver.js} +0 -0
- /package/dist/{config-formatter.d.ts → config/formatter.d.ts} +0 -0
- /package/dist/{config-formatter.js → config/formatter.js} +0 -0
- /package/dist/{merge.d.ts → config/merge.d.ts} +0 -0
- /package/dist/{merge.js → config/merge.js} +0 -0
- /package/dist/{github-summary.d.ts → output/github-summary.d.ts} +0 -0
- /package/dist/{github-summary.js → output/github-summary.js} +0 -0
- /package/dist/{summary-utils.js → output/summary-utils.js} +0 -0
- /package/dist/{repo-settings-diff.js → settings/repo-settings/diff.js} +0 -0
- /package/dist/{strategies/repo-settings-strategy.js → settings/repo-settings/types.js} +0 -0
- /package/dist/{strategies/commit-strategy.js → settings/rulesets/types.js} +0 -0
- /package/dist/{env.d.ts → shared/env.d.ts} +0 -0
- /package/dist/{env.js → shared/env.js} +0 -0
- /package/dist/{repo-detector.d.ts → shared/repo-detector.d.ts} +0 -0
- /package/dist/{repo-detector.js → shared/repo-detector.js} +0 -0
- /package/dist/{retry-utils.d.ts → shared/retry-utils.d.ts} +0 -0
- /package/dist/{retry-utils.js → shared/retry-utils.js} +0 -0
- /package/dist/{sanitize-utils.d.ts → shared/sanitize-utils.d.ts} +0 -0
- /package/dist/{sanitize-utils.js → shared/sanitize-utils.js} +0 -0
- /package/dist/{shell-utils.d.ts → shared/shell-utils.d.ts} +0 -0
- /package/dist/{shell-utils.js → shared/shell-utils.js} +0 -0
- /package/dist/{workspace-utils.d.ts → shared/workspace-utils.d.ts} +0 -0
- /package/dist/{workspace-utils.js → shared/workspace-utils.js} +0 -0
- /package/dist/{diff-utils.d.ts → sync/diff-utils.d.ts} +0 -0
- /package/dist/{diff-utils.js → sync/diff-utils.js} +0 -0
- /package/dist/{manifest.d.ts → sync/manifest.d.ts} +0 -0
- /package/dist/{manifest.js → sync/manifest.js} +0 -0
- /package/dist/{strategies/ruleset-strategy.js → sync/types.js} +0 -0
- /package/dist/{xfg-template.js → sync/xfg-template.js} +0 -0
- /package/dist/{authenticated-git-ops.d.ts → vcs/authenticated-git-ops.d.ts} +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { formatPropertyTree, } from "../settings/rulesets/formatter.js";
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Helpers
|
|
6
|
+
// =============================================================================
|
|
7
|
+
function formatValue(val) {
|
|
8
|
+
if (val === null)
|
|
9
|
+
return "null";
|
|
10
|
+
if (val === undefined)
|
|
11
|
+
return "undefined";
|
|
12
|
+
if (typeof val === "string")
|
|
13
|
+
return `"${val}"`;
|
|
14
|
+
if (typeof val === "boolean")
|
|
15
|
+
return val ? "true" : "false";
|
|
16
|
+
return String(val);
|
|
17
|
+
}
|
|
18
|
+
function formatRulesetConfig(config, indent) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
function renderObject(obj, currentIndent) {
|
|
21
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
22
|
+
renderValue(k, v, currentIndent);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function renderValue(key, value, currentIndent) {
|
|
26
|
+
const pad = " ".repeat(currentIndent);
|
|
27
|
+
if (value === null || value === undefined)
|
|
28
|
+
return;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
if (value.length === 0) {
|
|
31
|
+
lines.push(chalk.green(`${pad}+ ${key}: []`));
|
|
32
|
+
}
|
|
33
|
+
else if (value.every((v) => typeof v !== "object")) {
|
|
34
|
+
lines.push(chalk.green(`${pad}+ ${key}: [${value.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(", ")}]`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push(chalk.green(`${pad}+ ${key}:`));
|
|
38
|
+
for (let i = 0; i < value.length; i++) {
|
|
39
|
+
const item = value[i];
|
|
40
|
+
if (typeof item === "object" && item !== null) {
|
|
41
|
+
const obj = item;
|
|
42
|
+
const typeLabel = "type" in obj ? ` (${obj.type})` : "";
|
|
43
|
+
lines.push(chalk.green(`${pad} + [${i}]${typeLabel}:`));
|
|
44
|
+
renderObject(obj, currentIndent + 2);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
lines.push(chalk.green(`${pad} + ${formatValue(item)}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (typeof value === "object") {
|
|
53
|
+
lines.push(chalk.green(`${pad}+ ${key}:`));
|
|
54
|
+
renderObject(value, currentIndent + 1);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lines.push(chalk.green(`${pad}+ ${key}: ${formatValue(value)}`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const [key, value] of Object.entries(config)) {
|
|
61
|
+
if (key === "name")
|
|
62
|
+
continue; // Name is in the header
|
|
63
|
+
renderValue(key, value, indent);
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
function formatSummary(totals) {
|
|
68
|
+
const parts = [];
|
|
69
|
+
const settingsTotal = totals.settings.add + totals.settings.change;
|
|
70
|
+
const rulesetsTotal = totals.rulesets.create + totals.rulesets.update + totals.rulesets.delete;
|
|
71
|
+
if (settingsTotal > 0) {
|
|
72
|
+
const settingWord = settingsTotal === 1 ? "setting" : "settings";
|
|
73
|
+
const actions = [];
|
|
74
|
+
if (totals.settings.add > 0)
|
|
75
|
+
actions.push(`${totals.settings.add} to add`);
|
|
76
|
+
if (totals.settings.change > 0)
|
|
77
|
+
actions.push(`${totals.settings.change} to change`);
|
|
78
|
+
parts.push(`${settingsTotal} ${settingWord} (${actions.join(", ")})`);
|
|
79
|
+
}
|
|
80
|
+
if (rulesetsTotal > 0) {
|
|
81
|
+
const rulesetWord = rulesetsTotal === 1 ? "ruleset" : "rulesets";
|
|
82
|
+
const actions = [];
|
|
83
|
+
if (totals.rulesets.create > 0)
|
|
84
|
+
actions.push(`${totals.rulesets.create} to create`);
|
|
85
|
+
if (totals.rulesets.update > 0)
|
|
86
|
+
actions.push(`${totals.rulesets.update} to update`);
|
|
87
|
+
if (totals.rulesets.delete > 0)
|
|
88
|
+
actions.push(`${totals.rulesets.delete} to delete`);
|
|
89
|
+
parts.push(`${rulesetsTotal} ${rulesetWord} (${actions.join(", ")})`);
|
|
90
|
+
}
|
|
91
|
+
if (parts.length === 0) {
|
|
92
|
+
return "No changes";
|
|
93
|
+
}
|
|
94
|
+
return `Plan: ${parts.join(", ")}`;
|
|
95
|
+
}
|
|
96
|
+
// =============================================================================
|
|
97
|
+
// CLI Formatter
|
|
98
|
+
// =============================================================================
|
|
99
|
+
export function formatSettingsReportCLI(report) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const repo of report.repos) {
|
|
102
|
+
if (repo.settings.length === 0 &&
|
|
103
|
+
repo.rulesets.length === 0 &&
|
|
104
|
+
!repo.error) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Repo header
|
|
108
|
+
lines.push(chalk.yellow(`~ ${repo.repoName}`));
|
|
109
|
+
// Settings
|
|
110
|
+
for (const setting of repo.settings) {
|
|
111
|
+
// Skip settings where both values are undefined
|
|
112
|
+
if (setting.oldValue === undefined && setting.newValue === undefined) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (setting.action === "add") {
|
|
116
|
+
lines.push(chalk.green(` + ${setting.name}: ${formatValue(setting.newValue)}`));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
lines.push(chalk.yellow(` ~ ${setting.name}: ${formatValue(setting.oldValue)} → ${formatValue(setting.newValue)}`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Rulesets
|
|
123
|
+
for (const ruleset of repo.rulesets) {
|
|
124
|
+
if (ruleset.action === "create") {
|
|
125
|
+
lines.push(chalk.green(` + ruleset "${ruleset.name}"`));
|
|
126
|
+
if (ruleset.config) {
|
|
127
|
+
lines.push(...formatRulesetConfig(ruleset.config, 2));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (ruleset.action === "update") {
|
|
131
|
+
lines.push(chalk.yellow(` ~ ruleset "${ruleset.name}"`));
|
|
132
|
+
if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
|
|
133
|
+
const treeLines = formatPropertyTree(ruleset.propertyDiffs);
|
|
134
|
+
for (const line of treeLines) {
|
|
135
|
+
lines.push(` ${line}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else if (ruleset.action === "delete") {
|
|
140
|
+
lines.push(chalk.red(` - ruleset "${ruleset.name}"`));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Error
|
|
144
|
+
if (repo.error) {
|
|
145
|
+
lines.push(chalk.red(` Error: ${repo.error}`));
|
|
146
|
+
}
|
|
147
|
+
lines.push(""); // Blank line between repos
|
|
148
|
+
}
|
|
149
|
+
// Summary
|
|
150
|
+
lines.push(formatSummary(report.totals));
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// Markdown Formatter
|
|
155
|
+
// =============================================================================
|
|
156
|
+
function formatValuePlain(val) {
|
|
157
|
+
if (val === null)
|
|
158
|
+
return "null";
|
|
159
|
+
if (val === undefined)
|
|
160
|
+
return "undefined";
|
|
161
|
+
if (typeof val === "string")
|
|
162
|
+
return `"${val}"`;
|
|
163
|
+
if (typeof val === "boolean")
|
|
164
|
+
return val ? "true" : "false";
|
|
165
|
+
return String(val);
|
|
166
|
+
}
|
|
167
|
+
function formatRulesetConfigPlain(config, indent) {
|
|
168
|
+
const lines = [];
|
|
169
|
+
function renderObject(obj, currentIndent) {
|
|
170
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
171
|
+
renderValue(k, v, currentIndent);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function renderValue(key, value, currentIndent) {
|
|
175
|
+
const pad = " ".repeat(currentIndent);
|
|
176
|
+
if (value === null || value === undefined)
|
|
177
|
+
return;
|
|
178
|
+
if (Array.isArray(value)) {
|
|
179
|
+
if (value.length === 0) {
|
|
180
|
+
lines.push(`${pad}+ ${key}: []`);
|
|
181
|
+
}
|
|
182
|
+
else if (value.every((v) => typeof v !== "object")) {
|
|
183
|
+
lines.push(`${pad}+ ${key}: [${value.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(", ")}]`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
lines.push(`${pad}+ ${key}:`);
|
|
187
|
+
for (let i = 0; i < value.length; i++) {
|
|
188
|
+
const item = value[i];
|
|
189
|
+
if (typeof item === "object" && item !== null) {
|
|
190
|
+
const obj = item;
|
|
191
|
+
const typeLabel = "type" in obj ? ` (${obj.type})` : "";
|
|
192
|
+
lines.push(`${pad} + [${i}]${typeLabel}:`);
|
|
193
|
+
renderObject(obj, currentIndent + 2);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
lines.push(`${pad} + ${formatValuePlain(item)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (typeof value === "object") {
|
|
202
|
+
lines.push(`${pad}+ ${key}:`);
|
|
203
|
+
renderObject(value, currentIndent + 1);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
lines.push(`${pad}+ ${key}: ${formatValuePlain(value)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (const [key, value] of Object.entries(config)) {
|
|
210
|
+
if (key === "name")
|
|
211
|
+
continue;
|
|
212
|
+
renderValue(key, value, indent);
|
|
213
|
+
}
|
|
214
|
+
return lines;
|
|
215
|
+
}
|
|
216
|
+
export function formatSettingsReportMarkdown(report, dryRun) {
|
|
217
|
+
const lines = [];
|
|
218
|
+
// Title
|
|
219
|
+
const titleSuffix = dryRun ? " (Dry Run)" : "";
|
|
220
|
+
lines.push(`## Repository Settings Summary${titleSuffix}`);
|
|
221
|
+
lines.push("");
|
|
222
|
+
// Dry-run warning
|
|
223
|
+
if (dryRun) {
|
|
224
|
+
lines.push("> [!WARNING]");
|
|
225
|
+
lines.push("> This was a dry run — no changes were applied");
|
|
226
|
+
lines.push("");
|
|
227
|
+
}
|
|
228
|
+
// Diff block
|
|
229
|
+
const diffLines = [];
|
|
230
|
+
for (const repo of report.repos) {
|
|
231
|
+
if (repo.settings.length === 0 &&
|
|
232
|
+
repo.rulesets.length === 0 &&
|
|
233
|
+
!repo.error) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
diffLines.push(`~ ${repo.repoName}`);
|
|
237
|
+
for (const setting of repo.settings) {
|
|
238
|
+
// Skip settings where both values are undefined
|
|
239
|
+
if (setting.oldValue === undefined && setting.newValue === undefined) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (setting.action === "add") {
|
|
243
|
+
diffLines.push(` + ${setting.name}: ${formatValuePlain(setting.newValue)}`);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
diffLines.push(` ~ ${setting.name}: ${formatValuePlain(setting.oldValue)} → ${formatValuePlain(setting.newValue)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const ruleset of repo.rulesets) {
|
|
250
|
+
if (ruleset.action === "create") {
|
|
251
|
+
diffLines.push(` + ruleset "${ruleset.name}"`);
|
|
252
|
+
if (ruleset.config) {
|
|
253
|
+
diffLines.push(...formatRulesetConfigPlain(ruleset.config, 2));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else if (ruleset.action === "update") {
|
|
257
|
+
diffLines.push(` ~ ruleset "${ruleset.name}"`);
|
|
258
|
+
if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
|
|
259
|
+
for (const diff of ruleset.propertyDiffs) {
|
|
260
|
+
const path = diff.path.join(".");
|
|
261
|
+
if (diff.action === "add") {
|
|
262
|
+
diffLines.push(` + ${path}: ${formatValuePlain(diff.newValue)}`);
|
|
263
|
+
}
|
|
264
|
+
else if (diff.action === "change") {
|
|
265
|
+
diffLines.push(` ~ ${path}: ${formatValuePlain(diff.oldValue)} → ${formatValuePlain(diff.newValue)}`);
|
|
266
|
+
}
|
|
267
|
+
else if (diff.action === "remove") {
|
|
268
|
+
diffLines.push(` - ${path}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (ruleset.action === "delete") {
|
|
274
|
+
diffLines.push(` - ruleset "${ruleset.name}"`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (repo.error) {
|
|
278
|
+
diffLines.push(` ! Error: ${repo.error}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (diffLines.length > 0) {
|
|
282
|
+
lines.push("```diff");
|
|
283
|
+
lines.push(...diffLines);
|
|
284
|
+
lines.push("```");
|
|
285
|
+
lines.push("");
|
|
286
|
+
}
|
|
287
|
+
// Summary
|
|
288
|
+
lines.push(`**${formatSummary(report.totals)}**`);
|
|
289
|
+
return lines.join("\n");
|
|
290
|
+
}
|
|
291
|
+
// =============================================================================
|
|
292
|
+
// File Writer
|
|
293
|
+
// =============================================================================
|
|
294
|
+
export function writeSettingsReportSummary(report, dryRun) {
|
|
295
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
296
|
+
if (!summaryPath)
|
|
297
|
+
return;
|
|
298
|
+
const markdown = formatSettingsReportMarkdown(report, dryRun);
|
|
299
|
+
appendFileSync(summaryPath, "\n" + markdown + "\n");
|
|
300
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ProcessorResult } from "
|
|
2
|
-
import { RepoConfig } from "
|
|
1
|
+
import type { ProcessorResult } from "../sync/index.js";
|
|
2
|
+
import { RepoConfig } from "../config/index.js";
|
|
3
3
|
import { MergeOutcome, FileChanges, RepoResult } from "./github-summary.js";
|
|
4
|
-
import { DiffStats } from "
|
|
4
|
+
import { DiffStats } from "../sync/diff-utils.js";
|
|
5
5
|
/**
|
|
6
6
|
* Determine merge outcome from repo config and processor result
|
|
7
7
|
*/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface SyncReport {
|
|
2
|
+
repos: RepoFileChanges[];
|
|
3
|
+
totals: {
|
|
4
|
+
files: {
|
|
5
|
+
create: number;
|
|
6
|
+
update: number;
|
|
7
|
+
delete: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface RepoFileChanges {
|
|
12
|
+
repoName: string;
|
|
13
|
+
files: FileChange[];
|
|
14
|
+
prUrl?: string;
|
|
15
|
+
mergeOutcome?: "manual" | "auto" | "force" | "direct";
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FileChange {
|
|
19
|
+
path: string;
|
|
20
|
+
action: "create" | "update" | "delete";
|
|
21
|
+
}
|
|
22
|
+
export declare function formatSyncReportCLI(report: SyncReport): string[];
|
|
23
|
+
export declare function formatSyncReportMarkdown(report: SyncReport, dryRun: boolean): string;
|
|
24
|
+
export declare function writeSyncReportSummary(report: SyncReport, dryRun: boolean): void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/output/sync-report.ts
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
function formatSummary(totals) {
|
|
5
|
+
const total = totals.files.create + totals.files.update + totals.files.delete;
|
|
6
|
+
if (total === 0) {
|
|
7
|
+
return "No changes";
|
|
8
|
+
}
|
|
9
|
+
const parts = [];
|
|
10
|
+
if (totals.files.create > 0)
|
|
11
|
+
parts.push(`${totals.files.create} to create`);
|
|
12
|
+
if (totals.files.update > 0)
|
|
13
|
+
parts.push(`${totals.files.update} to update`);
|
|
14
|
+
if (totals.files.delete > 0)
|
|
15
|
+
parts.push(`${totals.files.delete} to delete`);
|
|
16
|
+
const fileWord = total === 1 ? "file" : "files";
|
|
17
|
+
return `Plan: ${total} ${fileWord} (${parts.join(", ")})`;
|
|
18
|
+
}
|
|
19
|
+
export function formatSyncReportCLI(report) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
for (const repo of report.repos) {
|
|
22
|
+
if (repo.files.length === 0 && !repo.error) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
// Repo header
|
|
26
|
+
lines.push(chalk.yellow(`~ ${repo.repoName}`));
|
|
27
|
+
// Files
|
|
28
|
+
for (const file of repo.files) {
|
|
29
|
+
if (file.action === "create") {
|
|
30
|
+
lines.push(chalk.green(` + ${file.path}`));
|
|
31
|
+
}
|
|
32
|
+
else if (file.action === "update") {
|
|
33
|
+
lines.push(chalk.yellow(` ~ ${file.path}`));
|
|
34
|
+
}
|
|
35
|
+
else if (file.action === "delete") {
|
|
36
|
+
lines.push(chalk.red(` - ${file.path}`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Error
|
|
40
|
+
if (repo.error) {
|
|
41
|
+
lines.push(chalk.red(` Error: ${repo.error}`));
|
|
42
|
+
}
|
|
43
|
+
lines.push(""); // Blank line between repos
|
|
44
|
+
}
|
|
45
|
+
// Summary
|
|
46
|
+
lines.push(formatSummary(report.totals));
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
export function formatSyncReportMarkdown(report, dryRun) {
|
|
50
|
+
const lines = [];
|
|
51
|
+
// Title
|
|
52
|
+
const titleSuffix = dryRun ? " (Dry Run)" : "";
|
|
53
|
+
lines.push(`## Config Sync Summary${titleSuffix}`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
// Dry-run warning
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
lines.push("> [!WARNING]");
|
|
58
|
+
lines.push("> This was a dry run — no changes were applied");
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
// Diff block
|
|
62
|
+
const diffLines = [];
|
|
63
|
+
for (const repo of report.repos) {
|
|
64
|
+
if (repo.files.length === 0 && !repo.error) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
diffLines.push(`~ ${repo.repoName}`);
|
|
68
|
+
for (const file of repo.files) {
|
|
69
|
+
if (file.action === "create") {
|
|
70
|
+
diffLines.push(` + ${file.path}`);
|
|
71
|
+
}
|
|
72
|
+
else if (file.action === "update") {
|
|
73
|
+
diffLines.push(` ~ ${file.path}`);
|
|
74
|
+
}
|
|
75
|
+
else if (file.action === "delete") {
|
|
76
|
+
diffLines.push(` - ${file.path}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (repo.error) {
|
|
80
|
+
diffLines.push(` ! Error: ${repo.error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (diffLines.length > 0) {
|
|
84
|
+
lines.push("```diff");
|
|
85
|
+
lines.push(...diffLines);
|
|
86
|
+
lines.push("```");
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
// Summary
|
|
90
|
+
lines.push(`**${formatSummary(report.totals)}**`);
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
export function writeSyncReportSummary(report, dryRun) {
|
|
94
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
95
|
+
if (!summaryPath)
|
|
96
|
+
return;
|
|
97
|
+
const markdown = formatSyncReportMarkdown(report, dryRun);
|
|
98
|
+
appendFileSync(summaryPath, "\n" + markdown + "\n");
|
|
99
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { GitHubRepoSettings } from "
|
|
2
|
-
import type { CurrentRepoSettings } from "./
|
|
1
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
2
|
+
import type { CurrentRepoSettings } from "./types.js";
|
|
3
3
|
export type RepoSettingsAction = "add" | "change" | "unchanged";
|
|
4
4
|
export interface RepoSettingsChange {
|
|
5
5
|
property: keyof GitHubRepoSettings;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { RepoSettingsChange } from "./
|
|
1
|
+
import type { RepoSettingsChange } from "./diff.js";
|
|
2
2
|
export interface RepoSettingsPlanEntry {
|
|
3
3
|
property: string;
|
|
4
4
|
action: "add" | "change";
|
|
5
|
+
oldValue?: unknown;
|
|
6
|
+
newValue?: unknown;
|
|
5
7
|
}
|
|
6
8
|
export interface RepoSettingsPlanResult {
|
|
7
9
|
lines: string[];
|
|
@@ -54,12 +54,21 @@ export function formatRepoSettingsPlan(changes) {
|
|
|
54
54
|
if (change.action === "add") {
|
|
55
55
|
lines.push(chalk.green(` + ${change.property}: ${formatValue(change.newValue)}`));
|
|
56
56
|
adds++;
|
|
57
|
-
entries.push({
|
|
57
|
+
entries.push({
|
|
58
|
+
property: change.property,
|
|
59
|
+
action: "add",
|
|
60
|
+
newValue: change.newValue,
|
|
61
|
+
});
|
|
58
62
|
}
|
|
59
63
|
else if (change.action === "change") {
|
|
60
64
|
lines.push(chalk.yellow(` ~ ${change.property}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`));
|
|
61
65
|
changesCount++;
|
|
62
|
-
entries.push({
|
|
66
|
+
entries.push({
|
|
67
|
+
property: change.property,
|
|
68
|
+
action: "change",
|
|
69
|
+
oldValue: change.oldValue,
|
|
70
|
+
newValue: change.newValue,
|
|
71
|
+
});
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
return { lines, adds, changes: changesCount, warnings, entries };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ICommandExecutor } from "
|
|
2
|
-
import { RepoInfo } from "
|
|
3
|
-
import type { GitHubRepoSettings } from "
|
|
4
|
-
import type { IRepoSettingsStrategy, RepoSettingsStrategyOptions, CurrentRepoSettings } from "./
|
|
1
|
+
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
+
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
4
|
+
import type { IRepoSettingsStrategy, RepoSettingsStrategyOptions, CurrentRepoSettings } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* GitHub Repository Settings Strategy.
|
|
7
7
|
* Manages repository settings via GitHub REST API using `gh api` CLI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { defaultExecutor } from "
|
|
2
|
-
import { isGitHubRepo } from "
|
|
3
|
-
import { escapeShellArg } from "
|
|
1
|
+
import { defaultExecutor, } from "../../shared/command-executor.js";
|
|
2
|
+
import { isGitHubRepo, } from "../../shared/repo-detector.js";
|
|
3
|
+
import { escapeShellArg } from "../../shared/shell-utils.js";
|
|
4
4
|
/**
|
|
5
5
|
* Converts camelCase to snake_case.
|
|
6
6
|
*/
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { isRepoSettingsStrategy, type IRepoSettingsStrategy, type RepoSettingsStrategyOptions, type CurrentRepoSettings, } from "./types.js";
|
|
2
|
+
export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsProcessorOptions, type RepoSettingsProcessorResult, } from "./processor.js";
|
|
3
|
+
export { diffRepoSettings, hasChanges, type RepoSettingsAction, type RepoSettingsChange, } from "./diff.js";
|
|
4
|
+
export { formatRepoSettingsPlan, type RepoSettingsPlanResult, type RepoSettingsPlanEntry, } from "./formatter.js";
|
|
5
|
+
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export { isRepoSettingsStrategy, } from "./types.js";
|
|
3
|
+
// Repo settings processor
|
|
4
|
+
export { RepoSettingsProcessor, } from "./processor.js";
|
|
5
|
+
// Repo settings diff
|
|
6
|
+
export { diffRepoSettings, hasChanges, } from "./diff.js";
|
|
7
|
+
// Repo settings formatter
|
|
8
|
+
export { formatRepoSettingsPlan, } from "./formatter.js";
|
|
9
|
+
// Repo settings strategies
|
|
10
|
+
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { RepoConfig } from "
|
|
2
|
-
import type { RepoInfo } from "
|
|
3
|
-
import type { IRepoSettingsStrategy } from "./
|
|
4
|
-
import { RepoSettingsPlanResult } from "./
|
|
1
|
+
import type { RepoConfig } from "../../config/index.js";
|
|
2
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import type { IRepoSettingsStrategy } from "./types.js";
|
|
4
|
+
import { RepoSettingsPlanResult } from "./formatter.js";
|
|
5
5
|
export interface IRepoSettingsProcessor {
|
|
6
6
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
|
|
7
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { isGitHubRepo, getRepoDisplayName } from "
|
|
2
|
-
import { GitHubRepoSettingsStrategy } from "./
|
|
3
|
-
import { diffRepoSettings, hasChanges } from "./
|
|
4
|
-
import { formatRepoSettingsPlan
|
|
5
|
-
import { hasGitHubAppCredentials } from "
|
|
6
|
-
import { GitHubAppTokenManager } from "
|
|
1
|
+
import { isGitHubRepo, getRepoDisplayName, } from "../../shared/repo-detector.js";
|
|
2
|
+
import { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
3
|
+
import { diffRepoSettings, hasChanges } from "./diff.js";
|
|
4
|
+
import { formatRepoSettingsPlan } from "./formatter.js";
|
|
5
|
+
import { hasGitHubAppCredentials } from "../../vcs/index.js";
|
|
6
|
+
import { GitHubAppTokenManager } from "../../vcs/github-app-token-manager.js";
|
|
7
7
|
export class RepoSettingsProcessor {
|
|
8
8
|
strategy;
|
|
9
9
|
tokenManager;
|
|
@@ -69,8 +69,14 @@ export class RepoSettingsProcessor {
|
|
|
69
69
|
planOutput,
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
// Apply changes
|
|
73
|
-
|
|
72
|
+
// Apply changes - only send settings that actually changed
|
|
73
|
+
const changedSettings = changes.reduce((acc, change) => {
|
|
74
|
+
if (change.action !== "unchanged") {
|
|
75
|
+
acc[change.property] = change.newValue;
|
|
76
|
+
}
|
|
77
|
+
return acc;
|
|
78
|
+
}, {});
|
|
79
|
+
await this.applyChanges(githubRepo, changedSettings, strategyOptions);
|
|
74
80
|
return {
|
|
75
81
|
success: true,
|
|
76
82
|
repoName,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RepoInfo } from "
|
|
2
|
-
import type { GitHubRepoSettings } from "
|
|
1
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
2
|
+
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
3
3
|
export interface RepoSettingsStrategyOptions {
|
|
4
4
|
token?: string;
|
|
5
5
|
host?: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type DiffAction = "add" | "change" | "remove";
|
|
2
|
+
export interface PropertyDiff {
|
|
3
|
+
path: string[];
|
|
4
|
+
action: DiffAction;
|
|
5
|
+
oldValue?: unknown;
|
|
6
|
+
newValue?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export declare function isObject(val: unknown): val is Record<string, unknown>;
|
|
9
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
10
|
+
export declare function isArrayOfObjects(arr: unknown[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Recursively compute property-level diffs between two objects.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computePropertyDiffs(current: Record<string, unknown>, desired: Record<string, unknown>, parentPath?: string[]): PropertyDiff[];
|
|
15
|
+
/**
|
|
16
|
+
* Diff two arrays of objects by matching items on `type` field (or by index).
|
|
17
|
+
*/
|
|
18
|
+
export declare function diffObjectArrays(currentArr: unknown[], desiredArr: unknown[], parentPath: string[]): PropertyDiff[];
|