@aspruyt/xfg 3.7.7 → 3.8.1
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 +150 -127
- package/dist/cli/settings-report-builder.d.ts +22 -0
- package/dist/cli/settings-report-builder.js +64 -0
- package/dist/cli/sync-command.js +49 -24
- package/dist/cli/sync-report-builder.d.ts +15 -0
- package/dist/cli/sync-report-builder.js +29 -0
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +4 -4
- package/dist/output/settings-report.d.ts +37 -0
- package/dist/output/settings-report.js +300 -0
- package/dist/output/sync-report.d.ts +24 -0
- package/dist/output/sync-report.js +99 -0
- package/dist/settings/index.d.ts +0 -1
- package/dist/settings/index.js +0 -2
- package/dist/settings/repo-settings/formatter.d.ts +2 -0
- package/dist/settings/repo-settings/formatter.js +11 -2
- package/dist/settings/repo-settings/processor.js +8 -2
- package/dist/settings/rulesets/formatter.d.ts +3 -0
- package/dist/settings/rulesets/formatter.js +7 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/pr-merge-handler.d.ts +2 -2
- package/dist/sync/pr-merge-handler.js +2 -1
- package/dist/sync/repository-processor.js +22 -2
- package/dist/sync/types.d.ts +9 -1
- package/package.json +1 -1
- package/dist/output/plan-formatter.d.ts +0 -39
- package/dist/output/plan-formatter.js +0 -84
- package/dist/output/plan-summary.d.ts +0 -8
- package/dist/output/plan-summary.js +0 -110
- package/dist/settings/resource-converters.d.ts +0 -28
- package/dist/settings/resource-converters.js +0 -107
|
@@ -8,52 +8,43 @@ import { logger } from "../shared/logger.js";
|
|
|
8
8
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
9
9
|
import { buildErrorResult } from "../output/summary-utils.js";
|
|
10
10
|
import { getManagedRulesets } from "../sync/manifest.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { rulesetResultToResources, repoSettingsResultToResources, } from "../settings/resource-converters.js";
|
|
11
|
+
import { formatSettingsReportCLI, writeSettingsReportSummary, } from "../output/settings-report.js";
|
|
12
|
+
import { buildSettingsReport, } from "./settings-report-builder.js";
|
|
14
13
|
import { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./types.js";
|
|
15
14
|
/**
|
|
16
|
-
*
|
|
15
|
+
* Collects processing results for the SettingsReport.
|
|
16
|
+
* Provides a centralized way to track results across rulesets and repo settings.
|
|
17
17
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
const rawConfig = loadRawConfig(configPath);
|
|
29
|
-
try {
|
|
30
|
-
validateForSettings(rawConfig);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
const config = normalizeConfig(rawConfig);
|
|
37
|
-
const reposWithRulesets = config.repos.filter((r) => r.settings?.rulesets && Object.keys(r.settings.rulesets).length > 0);
|
|
38
|
-
const reposWithRepoSettings = config.repos.filter((r) => r.settings?.repo && Object.keys(r.settings.repo).length > 0);
|
|
39
|
-
if (reposWithRulesets.length === 0 && reposWithRepoSettings.length === 0) {
|
|
40
|
-
console.log("No settings configured. Add settings.rulesets or settings.repo to your config.");
|
|
41
|
-
return;
|
|
18
|
+
class ResultsCollector {
|
|
19
|
+
results = [];
|
|
20
|
+
getOrCreate(repoName) {
|
|
21
|
+
let result = this.results.find((r) => r.repoName === repoName);
|
|
22
|
+
if (!result) {
|
|
23
|
+
result = { repoName };
|
|
24
|
+
this.results.push(result);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
42
27
|
}
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
appendError(repoName, error) {
|
|
29
|
+
const existing = this.getOrCreate(repoName);
|
|
30
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
31
|
+
if (existing.error) {
|
|
32
|
+
existing.error += `; ${errorMsg}`;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
existing.error = errorMsg;
|
|
36
|
+
}
|
|
45
37
|
}
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
getAll() {
|
|
39
|
+
return this.results;
|
|
48
40
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const repoConfig = reposWithRulesets[i];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Process rulesets for all configured repositories.
|
|
44
|
+
*/
|
|
45
|
+
async function processRulesets(repos, config, options, processor, repoProcessor, results, collector) {
|
|
46
|
+
for (let i = 0; i < repos.length; i++) {
|
|
47
|
+
const repoConfig = repos[i];
|
|
57
48
|
let repoInfo;
|
|
58
49
|
try {
|
|
59
50
|
repoInfo = parseGitUrl(repoConfig.git, {
|
|
@@ -63,26 +54,12 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
|
|
|
63
54
|
catch (error) {
|
|
64
55
|
logger.error(i + 1, repoConfig.git, String(error));
|
|
65
56
|
results.push(buildErrorResult(repoConfig.git, error));
|
|
66
|
-
|
|
67
|
-
repo: repoConfig.git,
|
|
68
|
-
message: error instanceof Error ? error.message : String(error),
|
|
69
|
-
});
|
|
57
|
+
collector.appendError(repoConfig.git, error);
|
|
70
58
|
continue;
|
|
71
59
|
}
|
|
72
60
|
const repoName = getRepoDisplayName(repoInfo);
|
|
73
61
|
if (!isGitHubRepo(repoInfo)) {
|
|
74
62
|
logger.skip(i + 1, repoName, "GitHub Rulesets only supported for GitHub repos");
|
|
75
|
-
if (repoConfig.settings?.rulesets) {
|
|
76
|
-
for (const rulesetName of Object.keys(repoConfig.settings.rulesets)) {
|
|
77
|
-
plan.resources.push({
|
|
78
|
-
type: "ruleset",
|
|
79
|
-
repo: repoName,
|
|
80
|
-
name: rulesetName,
|
|
81
|
-
action: "skipped",
|
|
82
|
-
skipReason: "GitHub Rulesets only supported for GitHub repos",
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
63
|
continue;
|
|
87
64
|
}
|
|
88
65
|
const managedRulesets = getManagedRulesets(null, config.id);
|
|
@@ -135,94 +112,140 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
|
|
|
135
112
|
message: result.message,
|
|
136
113
|
rulesetPlanDetails: result.planOutput?.entries,
|
|
137
114
|
});
|
|
138
|
-
|
|
115
|
+
if (!result.skipped) {
|
|
116
|
+
collector.getOrCreate(repoName).rulesetResult = result;
|
|
117
|
+
}
|
|
139
118
|
}
|
|
140
119
|
catch (error) {
|
|
141
120
|
logger.error(i + 1, repoName, String(error));
|
|
142
121
|
results.push(buildErrorResult(repoName, error));
|
|
143
|
-
|
|
144
|
-
repo: repoName,
|
|
145
|
-
message: error instanceof Error ? error.message : String(error),
|
|
146
|
-
});
|
|
122
|
+
collector.appendError(repoName, error);
|
|
147
123
|
}
|
|
148
124
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Process repo settings for all configured repositories.
|
|
128
|
+
*/
|
|
129
|
+
async function processRepoSettings(repos, config, options, processorFactory, results, collector) {
|
|
130
|
+
if (repos.length === 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const processor = processorFactory();
|
|
134
|
+
console.log(`\nProcessing repo settings for ${repos.length} repositories\n`);
|
|
135
|
+
for (let i = 0; i < repos.length; i++) {
|
|
136
|
+
const repoConfig = repos[i];
|
|
137
|
+
let repoInfo;
|
|
138
|
+
try {
|
|
139
|
+
repoInfo = parseGitUrl(repoConfig.git, {
|
|
140
|
+
githubHosts: config.githubHosts,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error(`Failed to parse ${repoConfig.git}: ${error}`);
|
|
145
|
+
collector.appendError(repoConfig.git, error);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const repoName = getRepoDisplayName(repoInfo);
|
|
149
|
+
try {
|
|
150
|
+
const result = await processor.process(repoConfig, repoInfo, {
|
|
151
|
+
dryRun: options.dryRun,
|
|
152
|
+
});
|
|
153
|
+
if (result.planOutput && result.planOutput.lines.length > 0) {
|
|
154
|
+
console.log(`\n ${chalk.bold(repoName)}:`);
|
|
155
|
+
console.log(" Repo Settings:");
|
|
156
|
+
for (const line of result.planOutput.lines) {
|
|
157
|
+
console.log(line);
|
|
184
158
|
}
|
|
185
|
-
if (result.
|
|
186
|
-
|
|
159
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
160
|
+
for (const warning of result.warnings) {
|
|
161
|
+
console.log(chalk.yellow(` ⚠️ Warning: ${warning}`));
|
|
162
|
+
}
|
|
187
163
|
}
|
|
188
|
-
|
|
189
|
-
|
|
164
|
+
}
|
|
165
|
+
if (result.skipped) {
|
|
166
|
+
// Silent skip
|
|
167
|
+
}
|
|
168
|
+
else if (result.success) {
|
|
169
|
+
console.log(chalk.green(` ✓ ${repoName}: ${result.message}`));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(chalk.red(` ✗ ${repoName}: ${result.message}`));
|
|
173
|
+
}
|
|
174
|
+
if (!result.skipped) {
|
|
175
|
+
const existing = results.find((r) => r.repoName === repoName);
|
|
176
|
+
if (existing) {
|
|
177
|
+
existing.repoSettingsPlanDetails = result.planOutput?.entries;
|
|
190
178
|
}
|
|
191
179
|
else {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
results.push({
|
|
201
|
-
repoName,
|
|
202
|
-
status: result.success ? "succeeded" : "failed",
|
|
203
|
-
message: result.message,
|
|
204
|
-
repoSettingsPlanDetails: result.planOutput?.entries,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
180
|
+
results.push({
|
|
181
|
+
repoName,
|
|
182
|
+
status: result.success ? "succeeded" : "failed",
|
|
183
|
+
message: result.message,
|
|
184
|
+
repoSettingsPlanDetails: result.planOutput?.entries,
|
|
185
|
+
});
|
|
207
186
|
}
|
|
208
|
-
plan.resources.push(...repoSettingsResultToResources(repoName, result));
|
|
209
187
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
plan.errors.push({
|
|
213
|
-
repo: repoName,
|
|
214
|
-
message: error instanceof Error ? error.message : String(error),
|
|
215
|
-
});
|
|
188
|
+
if (!result.skipped) {
|
|
189
|
+
collector.getOrCreate(repoName).settingsResult = result;
|
|
216
190
|
}
|
|
217
191
|
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.error(` ✗ ${repoName}: ${error}`);
|
|
194
|
+
collector.appendError(repoName, error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Run the settings command - manages GitHub Rulesets and repo settings.
|
|
200
|
+
*/
|
|
201
|
+
export async function runSettings(options, processorFactory = defaultRulesetProcessorFactory, repoProcessorFactory = defaultProcessorFactory, repoSettingsProcessorFactory = defaultRepoSettingsProcessorFactory) {
|
|
202
|
+
const configPath = resolve(options.config);
|
|
203
|
+
if (!existsSync(configPath)) {
|
|
204
|
+
console.error(`Config file not found: ${configPath}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
console.log(`Loading config from: ${configPath}`);
|
|
208
|
+
if (options.dryRun) {
|
|
209
|
+
console.log("Running in DRY RUN mode - no changes will be made\n");
|
|
210
|
+
}
|
|
211
|
+
const rawConfig = loadRawConfig(configPath);
|
|
212
|
+
try {
|
|
213
|
+
validateForSettings(rawConfig);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
const config = normalizeConfig(rawConfig);
|
|
220
|
+
const reposWithRulesets = config.repos.filter((r) => r.settings?.rulesets && Object.keys(r.settings.rulesets).length > 0);
|
|
221
|
+
const reposWithRepoSettings = config.repos.filter((r) => r.settings?.repo && Object.keys(r.settings.repo).length > 0);
|
|
222
|
+
if (reposWithRulesets.length === 0 && reposWithRepoSettings.length === 0) {
|
|
223
|
+
console.log("No settings configured. Add settings.rulesets or settings.repo to your config.");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (reposWithRulesets.length > 0) {
|
|
227
|
+
console.log(`Found ${reposWithRulesets.length} repositories with rulesets`);
|
|
228
|
+
}
|
|
229
|
+
if (reposWithRepoSettings.length > 0) {
|
|
230
|
+
console.log(`Found ${reposWithRepoSettings.length} repositories with repo settings`);
|
|
218
231
|
}
|
|
219
232
|
console.log("");
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
logger.setTotal(reposWithRulesets.length + reposWithRepoSettings.length);
|
|
234
|
+
const processor = processorFactory();
|
|
235
|
+
const repoProcessor = repoProcessorFactory();
|
|
236
|
+
const results = [];
|
|
237
|
+
const collector = new ResultsCollector();
|
|
238
|
+
await processRulesets(reposWithRulesets, config, options, processor, repoProcessor, results, collector);
|
|
239
|
+
await processRepoSettings(reposWithRepoSettings, config, options, repoSettingsProcessorFactory, results, collector);
|
|
240
|
+
console.log("");
|
|
241
|
+
const report = buildSettingsReport(collector.getAll());
|
|
242
|
+
const lines = formatSettingsReportCLI(report);
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
console.log(line);
|
|
245
|
+
}
|
|
246
|
+
writeSettingsReportSummary(report, options.dryRun ?? false);
|
|
247
|
+
const hasErrors = report.repos.some((r) => r.error);
|
|
248
|
+
if (hasErrors) {
|
|
226
249
|
process.exit(1);
|
|
227
250
|
}
|
|
228
251
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SettingsReport } from "../output/settings-report.js";
|
|
2
|
+
import type { RepoSettingsPlanEntry } from "../settings/repo-settings/formatter.js";
|
|
3
|
+
import type { RulesetPlanEntry } from "../settings/rulesets/formatter.js";
|
|
4
|
+
/**
|
|
5
|
+
* Result from processing a repository's settings and rulesets.
|
|
6
|
+
* Used to collect results during settings command execution.
|
|
7
|
+
*/
|
|
8
|
+
export interface ProcessorResults {
|
|
9
|
+
repoName: string;
|
|
10
|
+
settingsResult?: {
|
|
11
|
+
planOutput?: {
|
|
12
|
+
entries?: RepoSettingsPlanEntry[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
rulesetResult?: {
|
|
16
|
+
planOutput?: {
|
|
17
|
+
entries?: RulesetPlanEntry[];
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildSettingsReport(results: ProcessorResults[]): SettingsReport;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function buildSettingsReport(results) {
|
|
2
|
+
const repos = [];
|
|
3
|
+
const totals = {
|
|
4
|
+
settings: { add: 0, change: 0 },
|
|
5
|
+
rulesets: { create: 0, update: 0, delete: 0 },
|
|
6
|
+
};
|
|
7
|
+
for (const result of results) {
|
|
8
|
+
const repoChanges = {
|
|
9
|
+
repoName: result.repoName,
|
|
10
|
+
settings: [],
|
|
11
|
+
rulesets: [],
|
|
12
|
+
};
|
|
13
|
+
// Convert settings processor output
|
|
14
|
+
if (result.settingsResult?.planOutput?.entries) {
|
|
15
|
+
for (const entry of result.settingsResult.planOutput.entries) {
|
|
16
|
+
// Skip settings where both values are undefined (no actual change)
|
|
17
|
+
if (entry.oldValue === undefined && entry.newValue === undefined) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const settingChange = {
|
|
21
|
+
name: entry.property,
|
|
22
|
+
action: entry.action,
|
|
23
|
+
oldValue: entry.oldValue,
|
|
24
|
+
newValue: entry.newValue,
|
|
25
|
+
};
|
|
26
|
+
repoChanges.settings.push(settingChange);
|
|
27
|
+
if (entry.action === "add") {
|
|
28
|
+
totals.settings.add++;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
totals.settings.change++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Convert ruleset processor output
|
|
36
|
+
if (result.rulesetResult?.planOutput?.entries) {
|
|
37
|
+
for (const entry of result.rulesetResult.planOutput.entries) {
|
|
38
|
+
if (entry.action === "unchanged")
|
|
39
|
+
continue;
|
|
40
|
+
const rulesetChange = {
|
|
41
|
+
name: entry.name,
|
|
42
|
+
action: entry.action,
|
|
43
|
+
propertyDiffs: entry.propertyDiffs,
|
|
44
|
+
config: entry.config,
|
|
45
|
+
};
|
|
46
|
+
repoChanges.rulesets.push(rulesetChange);
|
|
47
|
+
if (entry.action === "create") {
|
|
48
|
+
totals.rulesets.create++;
|
|
49
|
+
}
|
|
50
|
+
else if (entry.action === "update") {
|
|
51
|
+
totals.rulesets.update++;
|
|
52
|
+
}
|
|
53
|
+
else if (entry.action === "delete") {
|
|
54
|
+
totals.rulesets.delete++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (result.error) {
|
|
59
|
+
repoChanges.error = result.error;
|
|
60
|
+
}
|
|
61
|
+
repos.push(repoChanges);
|
|
62
|
+
}
|
|
63
|
+
return { repos, totals };
|
|
64
|
+
}
|
package/dist/cli/sync-command.js
CHANGED
|
@@ -6,11 +6,9 @@ import { parseGitUrl, getRepoDisplayName } from "../shared/repo-detector.js";
|
|
|
6
6
|
import { sanitizeBranchName, validateBranchName } from "../vcs/git-ops.js";
|
|
7
7
|
import { logger } from "../shared/logger.js";
|
|
8
8
|
import { generateWorkspaceName } from "../shared/workspace-utils.js";
|
|
9
|
-
import { buildRepoResult, buildErrorResult } from "../output/summary-utils.js";
|
|
10
|
-
import { printPlan } from "../output/plan-formatter.js";
|
|
11
|
-
import { writePlanSummary } from "../output/plan-summary.js";
|
|
12
|
-
import { syncResultToResources } from "../settings/resource-converters.js";
|
|
13
9
|
import { defaultProcessorFactory } from "./types.js";
|
|
10
|
+
import { buildSyncReport } from "./sync-report-builder.js";
|
|
11
|
+
import { formatSyncReportCLI, writeSyncReportSummary, } from "../output/sync-report.js";
|
|
14
12
|
/**
|
|
15
13
|
* Get unique file names from all repos in the config
|
|
16
14
|
*/
|
|
@@ -44,6 +42,20 @@ function formatFileNames(fileNames) {
|
|
|
44
42
|
}
|
|
45
43
|
return `${fileNames.length} files`;
|
|
46
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Determine merge outcome from processor result
|
|
47
|
+
*/
|
|
48
|
+
function determineMergeOutcome(result) {
|
|
49
|
+
if (!result.success)
|
|
50
|
+
return undefined;
|
|
51
|
+
if (!result.prUrl)
|
|
52
|
+
return "direct";
|
|
53
|
+
if (result.mergeResult?.merged)
|
|
54
|
+
return "force";
|
|
55
|
+
if (result.mergeResult?.autoMergeEnabled)
|
|
56
|
+
return "auto";
|
|
57
|
+
return "manual";
|
|
58
|
+
}
|
|
47
59
|
/**
|
|
48
60
|
* Run the sync command - synchronizes files across repositories.
|
|
49
61
|
*/
|
|
@@ -80,8 +92,7 @@ export async function runSync(options, processorFactory = defaultProcessorFactor
|
|
|
80
92
|
console.log(`Target files: ${formatFileNames(fileNames)}`);
|
|
81
93
|
console.log(`Branch: ${branchName}\n`);
|
|
82
94
|
const processor = processorFactory();
|
|
83
|
-
const
|
|
84
|
-
const plan = { resources: [], errors: [] };
|
|
95
|
+
const reportResults = [];
|
|
85
96
|
for (let i = 0; i < config.repos.length; i++) {
|
|
86
97
|
const repoConfig = config.repos[i];
|
|
87
98
|
if (options.merge || options.mergeStrategy || options.deleteBranch) {
|
|
@@ -101,10 +112,11 @@ export async function runSync(options, processorFactory = defaultProcessorFactor
|
|
|
101
112
|
}
|
|
102
113
|
catch (error) {
|
|
103
114
|
logger.error(current, repoConfig.git, String(error));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
reportResults.push({
|
|
116
|
+
repoName: repoConfig.git,
|
|
117
|
+
success: false,
|
|
118
|
+
fileChanges: [],
|
|
119
|
+
error: error instanceof Error ? error.message : String(error),
|
|
108
120
|
});
|
|
109
121
|
continue;
|
|
110
122
|
}
|
|
@@ -121,35 +133,48 @@ export async function runSync(options, processorFactory = defaultProcessorFactor
|
|
|
121
133
|
prTemplate: config.prTemplate,
|
|
122
134
|
noDelete: options.noDelete,
|
|
123
135
|
});
|
|
124
|
-
const
|
|
125
|
-
|
|
136
|
+
const mergeOutcome = determineMergeOutcome(result);
|
|
137
|
+
reportResults.push({
|
|
138
|
+
repoName,
|
|
139
|
+
success: result.success,
|
|
140
|
+
fileChanges: (result.fileChanges ?? []).map((f) => ({
|
|
141
|
+
path: f.path,
|
|
142
|
+
action: f.action,
|
|
143
|
+
})),
|
|
144
|
+
prUrl: result.prUrl,
|
|
145
|
+
mergeOutcome,
|
|
146
|
+
error: result.success ? undefined : result.message,
|
|
147
|
+
});
|
|
126
148
|
if (result.skipped) {
|
|
127
149
|
logger.skip(current, repoName, result.message);
|
|
128
150
|
}
|
|
129
151
|
else if (result.success) {
|
|
130
|
-
logger.success(current, repoName,
|
|
152
|
+
logger.success(current, repoName, result.message);
|
|
131
153
|
}
|
|
132
154
|
else {
|
|
133
155
|
logger.error(current, repoName, result.message);
|
|
134
156
|
}
|
|
135
|
-
plan.resources.push(...syncResultToResources(repoName, repoConfig, result));
|
|
136
157
|
}
|
|
137
158
|
catch (error) {
|
|
138
159
|
logger.error(current, repoName, String(error));
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
reportResults.push({
|
|
161
|
+
repoName,
|
|
162
|
+
success: false,
|
|
163
|
+
fileChanges: [],
|
|
164
|
+
error: error instanceof Error ? error.message : String(error),
|
|
143
165
|
});
|
|
144
166
|
}
|
|
145
167
|
}
|
|
168
|
+
// Build and display report
|
|
169
|
+
const report = buildSyncReport(reportResults);
|
|
146
170
|
console.log("");
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
for (const line of formatSyncReportCLI(report)) {
|
|
172
|
+
console.log(line);
|
|
173
|
+
}
|
|
174
|
+
writeSyncReportSummary(report, options.dryRun ?? false);
|
|
175
|
+
// Exit with error if any failures
|
|
176
|
+
const hasErrors = reportResults.some((r) => r.error);
|
|
177
|
+
if (hasErrors) {
|
|
153
178
|
process.exit(1);
|
|
154
179
|
}
|
|
155
180
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SyncReport } from "../output/sync-report.js";
|
|
2
|
+
interface FileChangeInput {
|
|
3
|
+
path: string;
|
|
4
|
+
action: "create" | "update" | "delete";
|
|
5
|
+
}
|
|
6
|
+
interface SyncResultInput {
|
|
7
|
+
repoName: string;
|
|
8
|
+
success: boolean;
|
|
9
|
+
fileChanges: FileChangeInput[];
|
|
10
|
+
prUrl?: string;
|
|
11
|
+
mergeOutcome?: "manual" | "auto" | "force" | "direct";
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildSyncReport(results: SyncResultInput[]): SyncReport;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function buildSyncReport(results) {
|
|
2
|
+
const repos = [];
|
|
3
|
+
const totals = {
|
|
4
|
+
files: { create: 0, update: 0, delete: 0 },
|
|
5
|
+
};
|
|
6
|
+
for (const result of results) {
|
|
7
|
+
const files = result.fileChanges.map((f) => ({
|
|
8
|
+
path: f.path,
|
|
9
|
+
action: f.action,
|
|
10
|
+
}));
|
|
11
|
+
// Count totals
|
|
12
|
+
for (const file of files) {
|
|
13
|
+
if (file.action === "create")
|
|
14
|
+
totals.files.create++;
|
|
15
|
+
else if (file.action === "update")
|
|
16
|
+
totals.files.update++;
|
|
17
|
+
else if (file.action === "delete")
|
|
18
|
+
totals.files.delete++;
|
|
19
|
+
}
|
|
20
|
+
repos.push({
|
|
21
|
+
repoName: result.repoName,
|
|
22
|
+
files,
|
|
23
|
+
prUrl: result.prUrl,
|
|
24
|
+
mergeOutcome: result.mergeOutcome,
|
|
25
|
+
error: result.error,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return { repos, totals };
|
|
29
|
+
}
|
package/dist/output/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
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";
|
|
3
3
|
export { formatSummary, isGitHubActions, writeSummary, type MergeOutcome, type FileChanges, type RulesetPlanDetail, type RepoSettingsPlanDetail, type RepoResult, type SummaryData, } from "./github-summary.js";
|
|
4
4
|
export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
|
package/dist/output/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
export {
|
|
3
|
-
//
|
|
4
|
-
export {
|
|
1
|
+
// Sync report (repo-grouped file changes)
|
|
2
|
+
export { formatSyncReportCLI, formatSyncReportMarkdown, writeSyncReportSummary, } from "./sync-report.js";
|
|
3
|
+
// Settings report (repo-grouped settings changes)
|
|
4
|
+
export { formatSettingsReportCLI, formatSettingsReportMarkdown, writeSettingsReportSummary, } from "./settings-report.js";
|
|
5
5
|
// GitHub Actions summary
|
|
6
6
|
export { formatSummary, isGitHubActions, writeSummary, } from "./github-summary.js";
|
|
7
7
|
// Summary utilities
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PropertyDiff } from "../settings/rulesets/formatter.js";
|
|
2
|
+
import type { Ruleset } from "../config/index.js";
|
|
3
|
+
export interface SettingsReport {
|
|
4
|
+
repos: RepoChanges[];
|
|
5
|
+
totals: {
|
|
6
|
+
settings: {
|
|
7
|
+
add: number;
|
|
8
|
+
change: number;
|
|
9
|
+
};
|
|
10
|
+
rulesets: {
|
|
11
|
+
create: number;
|
|
12
|
+
update: number;
|
|
13
|
+
delete: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface RepoChanges {
|
|
18
|
+
repoName: string;
|
|
19
|
+
settings: SettingChange[];
|
|
20
|
+
rulesets: RulesetChange[];
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SettingChange {
|
|
24
|
+
name: string;
|
|
25
|
+
action: "add" | "change";
|
|
26
|
+
oldValue?: unknown;
|
|
27
|
+
newValue: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface RulesetChange {
|
|
30
|
+
name: string;
|
|
31
|
+
action: "create" | "update" | "delete";
|
|
32
|
+
propertyDiffs?: PropertyDiff[];
|
|
33
|
+
config?: Ruleset;
|
|
34
|
+
}
|
|
35
|
+
export declare function formatSettingsReportCLI(report: SettingsReport): string[];
|
|
36
|
+
export declare function formatSettingsReportMarkdown(report: SettingsReport, dryRun: boolean): string;
|
|
37
|
+
export declare function writeSettingsReportSummary(report: SettingsReport, dryRun: boolean): void;
|