@aspruyt/xfg 3.7.7 → 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.
@@ -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
+ }
@@ -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,3 +1,2 @@
1
1
  export * from "./rulesets/index.js";
2
2
  export * from "./repo-settings/index.js";
3
- export { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
@@ -2,5 +2,3 @@
2
2
  export * from "./rulesets/index.js";
3
3
  // Repo settings
4
4
  export * from "./repo-settings/index.js";
5
- // Resource converters
6
- export { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
@@ -2,6 +2,8 @@ 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({ property: change.property, action: "add" });
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({ property: change.property, action: "change" });
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 };
@@ -69,8 +69,14 @@ export class RepoSettingsProcessor {
69
69
  planOutput,
70
70
  };
71
71
  }
72
- // Apply changes
73
- await this.applyChanges(githubRepo, desiredSettings, strategyOptions);
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,4 +1,5 @@
1
1
  import { type RulesetChange, type RulesetAction } from "./diff.js";
2
+ import type { Ruleset } from "../../config/index.js";
2
3
  import { type PropertyDiff } from "./index.js";
3
4
  export type { DiffAction, PropertyDiff } from "./index.js";
4
5
  export { computePropertyDiffs } from "./index.js";
@@ -11,6 +12,8 @@ export interface RulesetPlanEntry {
11
12
  changed: number;
12
13
  removed: number;
13
14
  };
15
+ propertyDiffs?: PropertyDiff[];
16
+ config?: Ruleset;
14
17
  }
15
18
  export interface RulesetPlanResult {
16
19
  lines: string[];
@@ -264,7 +264,12 @@ export function formatRulesetPlan(changes) {
264
264
  const propertyCount = change.desired
265
265
  ? Object.keys(change.desired).length
266
266
  : 0;
267
- entries.push({ name: change.name, action: "create", propertyCount });
267
+ entries.push({
268
+ name: change.name,
269
+ action: "create",
270
+ propertyCount,
271
+ config: change.desired,
272
+ });
268
273
  lines.push(""); // Blank line between rulesets
269
274
  }
270
275
  }
@@ -289,6 +294,7 @@ export function formatRulesetPlan(changes) {
289
294
  name: change.name,
290
295
  action: "update",
291
296
  propertyChanges: { added, changed, removed },
297
+ propertyDiffs: diffs,
292
298
  });
293
299
  }
294
300
  else {
@@ -7,7 +7,7 @@ export { CommitPushManager } from "./commit-push-manager.js";
7
7
  export { formatCommitMessage } from "./commit-message.js";
8
8
  export { FileSyncOrchestrator } from "./file-sync-orchestrator.js";
9
9
  export { PRMergeHandler } from "./pr-merge-handler.js";
10
- export type { IFileWriter, FileWriteContext, FileWriterDeps, FileWriteAllResult, FileWriteResult, IManifestManager, OrphanProcessResult, OrphanDeleteOptions, OrphanDeleteDeps, IBranchManager, BranchSetupOptions, IAuthOptionsBuilder, AuthResult, IRepositorySession, SessionOptions, SessionContext, ICommitPushManager, CommitPushOptions, CommitPushResult, GitOpsFactory, IRepositoryProcessor, ProcessorOptions, ProcessorResult, IFileSyncOrchestrator, FileSyncResult, IPRMergeHandler, PRHandlerOptions, } from "./types.js";
10
+ export type { IFileWriter, FileWriteContext, FileWriterDeps, FileWriteAllResult, FileWriteResult, IManifestManager, OrphanProcessResult, OrphanDeleteOptions, OrphanDeleteDeps, IBranchManager, BranchSetupOptions, IAuthOptionsBuilder, AuthResult, IRepositorySession, SessionOptions, SessionContext, ICommitPushManager, CommitPushOptions, CommitPushResult, GitOpsFactory, IRepositoryProcessor, ProcessorOptions, ProcessorResult, FileChangeDetail, IFileSyncOrchestrator, FileSyncResult, IPRMergeHandler, PRHandlerOptions, } from "./types.js";
11
11
  export { RepositoryProcessor } from "./repository-processor.js";
12
12
  export { createEmptyManifest, loadManifest, saveManifest, getManagedFiles, getManagedRulesets, updateManifest, updateManifestRulesets, MANIFEST_FILENAME, type XfgManifest, type XfgManifestConfigEntry, } from "./manifest.js";
13
13
  export { getFileStatus, formatStatusBadge, formatDiffLine, generateDiff, createDiffStats, incrementDiffStats, type FileStatus, type DiffStats, } from "./diff-utils.js";
@@ -3,9 +3,9 @@ import type { RepoInfo } from "../shared/repo-detector.js";
3
3
  import type { ILogger } from "../shared/logger.js";
4
4
  import { type FileAction } from "../vcs/pr-creator.js";
5
5
  import type { DiffStats } from "./diff-utils.js";
6
- import type { ProcessorResult, PRHandlerOptions, IPRMergeHandler } from "./types.js";
6
+ import type { ProcessorResult, PRHandlerOptions, IPRMergeHandler, FileChangeDetail } from "./types.js";
7
7
  export declare class PRMergeHandler implements IPRMergeHandler {
8
8
  private readonly log;
9
9
  constructor(log: ILogger);
10
- createAndMerge(repoInfo: RepoInfo, repoConfig: RepoConfig, options: PRHandlerOptions, changedFiles: FileAction[], repoName: string, diffStats?: DiffStats): Promise<ProcessorResult>;
10
+ createAndMerge(repoInfo: RepoInfo, repoConfig: RepoConfig, options: PRHandlerOptions, changedFiles: FileAction[], repoName: string, diffStats?: DiffStats, fileChanges?: FileChangeDetail[]): Promise<ProcessorResult>;
11
11
  }
@@ -4,7 +4,7 @@ export class PRMergeHandler {
4
4
  constructor(log) {
5
5
  this.log = log;
6
6
  }
7
- async createAndMerge(repoInfo, repoConfig, options, changedFiles, repoName, diffStats) {
7
+ async createAndMerge(repoInfo, repoConfig, options, changedFiles, repoName, diffStats, fileChanges) {
8
8
  this.log.info("Creating pull request...");
9
9
  const prResult = await createPR({
10
10
  repoInfo,
@@ -57,6 +57,7 @@ export class PRMergeHandler {
57
57
  prUrl: prResult.url,
58
58
  mergeResult,
59
59
  diffStats,
60
+ fileChanges,
60
61
  };
61
62
  }
62
63
  }
@@ -8,6 +8,14 @@ import { loadManifest, saveManifest, updateManifestRulesets, MANIFEST_FILENAME,
8
8
  import { GitHubAppTokenManager } from "../vcs/github-app-token-manager.js";
9
9
  import { formatCommitMessage } from "./commit-message.js";
10
10
  import { FileWriter, ManifestManager, BranchManager, AuthOptionsBuilder, RepositorySession, CommitPushManager, FileSyncOrchestrator, PRMergeHandler, } from "./index.js";
11
+ function mapToFileChangeDetails(changedFiles) {
12
+ return changedFiles
13
+ .filter((f) => f.action !== "skip")
14
+ .map((f) => ({
15
+ path: f.fileName,
16
+ action: f.action,
17
+ }));
18
+ }
11
19
  export class RepositoryProcessor {
12
20
  gitOpsFactory;
13
21
  log;
@@ -87,6 +95,8 @@ export class RepositoryProcessor {
87
95
  });
88
96
  // Process files and manifest
89
97
  const { fileChanges, diffStats, changedFiles, hasChanges } = await this.fileSyncOrchestrator.sync(repoConfig, repoInfo, session, options);
98
+ // Map file changes for reporting
99
+ const fileChangeDetails = mapToFileChangeDetails(changedFiles);
90
100
  if (!hasChanges) {
91
101
  return {
92
102
  success: true,
@@ -94,6 +104,7 @@ export class RepositoryProcessor {
94
104
  message: "No changes detected",
95
105
  skipped: true,
96
106
  diffStats,
107
+ fileChanges: fileChangeDetails,
97
108
  };
98
109
  }
99
110
  // Commit and push
@@ -122,6 +133,7 @@ export class RepositoryProcessor {
122
133
  message: "No changes detected after staging",
123
134
  skipped: true,
124
135
  diffStats,
136
+ fileChanges: fileChangeDetails,
125
137
  };
126
138
  }
127
139
  // Direct mode: no PR
@@ -132,6 +144,7 @@ export class RepositoryProcessor {
132
144
  repoName,
133
145
  message: `Pushed directly to ${session.baseBranch}`,
134
146
  diffStats,
147
+ fileChanges: fileChangeDetails,
135
148
  };
136
149
  }
137
150
  // Create and merge PR
@@ -144,7 +157,7 @@ export class RepositoryProcessor {
144
157
  prTemplate,
145
158
  token: authResult.token,
146
159
  executor,
147
- }, changedFiles, repoName, diffStats);
160
+ }, changedFiles, repoName, diffStats, fileChangeDetails);
148
161
  }
149
162
  finally {
150
163
  try {
@@ -190,12 +203,17 @@ export class RepositoryProcessor {
190
203
  skipped: true,
191
204
  };
192
205
  }
206
+ // Prepare manifest file change details for reporting
207
+ const manifestFileChange = [
208
+ { path: MANIFEST_FILENAME, action: "update" },
209
+ ];
193
210
  if (dryRun) {
194
211
  this.log.info(`Would update ${MANIFEST_FILENAME} with rulesets`);
195
212
  return {
196
213
  success: true,
197
214
  repoName,
198
215
  message: "Would update manifest (dry-run)",
216
+ fileChanges: manifestFileChange,
199
217
  };
200
218
  }
201
219
  // Setup branch and commit
@@ -246,6 +264,7 @@ export class RepositoryProcessor {
246
264
  repoName,
247
265
  message: "No changes detected after staging",
248
266
  skipped: true,
267
+ fileChanges: manifestFileChange,
249
268
  };
250
269
  }
251
270
  if (isDirectMode) {
@@ -253,6 +272,7 @@ export class RepositoryProcessor {
253
272
  success: true,
254
273
  repoName,
255
274
  message: `Manifest updated directly on ${session.baseBranch}`,
275
+ fileChanges: manifestFileChange,
256
276
  };
257
277
  }
258
278
  // Create and merge PR
@@ -264,7 +284,7 @@ export class RepositoryProcessor {
264
284
  retries,
265
285
  token: authResult.token,
266
286
  executor,
267
- }, [{ fileName: MANIFEST_FILENAME, action: "update" }], repoName);
287
+ }, [{ fileName: MANIFEST_FILENAME, action: "update" }], repoName, undefined, manifestFileChange);
268
288
  }
269
289
  finally {
270
290
  try {