@aspruyt/xfg 3.7.5 → 3.7.7

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.
Files changed (156) hide show
  1. package/dist/cli/index.d.ts +6 -0
  2. package/dist/cli/index.js +9 -0
  3. package/dist/cli/program.d.ts +2 -0
  4. package/dist/cli/program.js +70 -0
  5. package/dist/cli/settings-command.d.ts +10 -0
  6. package/dist/cli/settings-command.js +228 -0
  7. package/dist/cli/sync-command.d.ts +25 -0
  8. package/dist/cli/sync-command.js +155 -0
  9. package/dist/cli/types.d.ts +45 -0
  10. package/dist/cli/types.js +15 -0
  11. package/dist/cli.js +2 -19
  12. package/dist/{file-reference-resolver.d.ts → config/file-reference-resolver.d.ts} +1 -1
  13. package/dist/config/index.d.ts +7 -0
  14. package/dist/config/index.js +12 -0
  15. package/dist/config/loader.d.ts +9 -0
  16. package/dist/{config.js → config/loader.js} +3 -24
  17. package/dist/{config-normalizer.d.ts → config/normalizer.d.ts} +1 -1
  18. package/dist/{config-normalizer.js → config/normalizer.js} +1 -1
  19. package/dist/{config.d.ts → config/types.d.ts} +5 -9
  20. package/dist/config/types.js +16 -0
  21. package/dist/{config-validator.d.ts → config/validator.d.ts} +5 -5
  22. package/dist/{config-validator.js → config/validator.js} +60 -372
  23. package/dist/config/validators/file-validator.d.ts +22 -0
  24. package/dist/config/validators/file-validator.js +46 -0
  25. package/dist/config/validators/index.d.ts +3 -0
  26. package/dist/config/validators/index.js +6 -0
  27. package/dist/config/validators/repo-settings-validator.d.ts +10 -0
  28. package/dist/config/validators/repo-settings-validator.js +71 -0
  29. package/dist/config/validators/ruleset-validator.d.ts +18 -0
  30. package/dist/config/validators/ruleset-validator.js +201 -0
  31. package/dist/index.d.ts +3 -66
  32. package/dist/index.js +3 -474
  33. package/dist/output/index.d.ts +4 -0
  34. package/dist/output/index.js +8 -0
  35. package/dist/{summary-utils.d.ts → output/summary-utils.d.ts} +3 -3
  36. package/dist/settings/index.d.ts +3 -0
  37. package/dist/settings/index.js +6 -0
  38. package/dist/{repo-settings-diff.d.ts → settings/repo-settings/diff.d.ts} +2 -2
  39. package/dist/{repo-settings-plan-formatter.d.ts → settings/repo-settings/formatter.d.ts} +1 -1
  40. package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.d.ts +4 -4
  41. package/dist/{strategies → settings/repo-settings}/github-repo-settings-strategy.js +3 -3
  42. package/dist/settings/repo-settings/index.d.ts +5 -0
  43. package/dist/settings/repo-settings/index.js +10 -0
  44. package/dist/{repo-settings-processor.d.ts → settings/repo-settings/processor.d.ts} +4 -4
  45. package/dist/{repo-settings-processor.js → settings/repo-settings/processor.js} +6 -6
  46. package/dist/{strategies/repo-settings-strategy.d.ts → settings/repo-settings/types.d.ts} +2 -2
  47. package/dist/{resource-converters.d.ts → settings/resource-converters.d.ts} +4 -4
  48. package/dist/settings/rulesets/diff-algorithm.d.ts +18 -0
  49. package/dist/settings/rulesets/diff-algorithm.js +166 -0
  50. package/dist/{ruleset-diff.d.ts → settings/rulesets/diff.d.ts} +2 -2
  51. package/dist/{ruleset-diff.js → settings/rulesets/diff.js} +1 -1
  52. package/dist/{ruleset-plan-formatter.d.ts → settings/rulesets/formatter.d.ts} +4 -12
  53. package/dist/{ruleset-plan-formatter.js → settings/rulesets/formatter.js} +5 -166
  54. package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.d.ts +4 -4
  55. package/dist/{strategies → settings/rulesets}/github-ruleset-strategy.js +3 -3
  56. package/dist/settings/rulesets/index.d.ts +6 -0
  57. package/dist/settings/rulesets/index.js +10 -0
  58. package/dist/{ruleset-processor.d.ts → settings/rulesets/processor.d.ts} +4 -4
  59. package/dist/{ruleset-processor.js → settings/rulesets/processor.js} +6 -6
  60. package/dist/{strategies/ruleset-strategy.d.ts → settings/rulesets/types.d.ts} +2 -2
  61. package/dist/{command-executor.d.ts → shared/command-executor.d.ts} +10 -2
  62. package/dist/{command-executor.js → shared/command-executor.js} +2 -1
  63. package/dist/shared/index.d.ts +8 -0
  64. package/dist/shared/index.js +16 -0
  65. package/dist/{logger.d.ts → shared/logger.d.ts} +1 -1
  66. package/dist/{logger.js → shared/logger.js} +1 -1
  67. package/dist/sync/auth-options-builder.d.ts +12 -0
  68. package/dist/sync/auth-options-builder.js +54 -0
  69. package/dist/sync/branch-manager.d.ts +7 -0
  70. package/dist/sync/branch-manager.js +36 -0
  71. package/dist/sync/commit-message.d.ts +11 -0
  72. package/dist/sync/commit-message.js +27 -0
  73. package/dist/sync/commit-push-manager.d.ts +8 -0
  74. package/dist/sync/commit-push-manager.js +71 -0
  75. package/dist/sync/file-sync-orchestrator.d.ts +11 -0
  76. package/dist/sync/file-sync-orchestrator.js +58 -0
  77. package/dist/sync/file-writer.d.ts +18 -0
  78. package/dist/sync/file-writer.js +101 -0
  79. package/dist/sync/index.d.ts +14 -0
  80. package/dist/sync/index.js +17 -0
  81. package/dist/sync/manifest-manager.d.ts +10 -0
  82. package/dist/sync/manifest-manager.js +64 -0
  83. package/dist/sync/pr-merge-handler.d.ts +11 -0
  84. package/dist/sync/pr-merge-handler.js +62 -0
  85. package/dist/sync/repository-processor.d.ts +30 -0
  86. package/dist/sync/repository-processor.js +278 -0
  87. package/dist/sync/repository-session.d.ts +9 -0
  88. package/dist/sync/repository-session.js +35 -0
  89. package/dist/sync/types.d.ts +296 -0
  90. package/dist/{xfg-template.d.ts → sync/xfg-template.d.ts} +2 -2
  91. package/dist/{authenticated-git-ops.js → vcs/authenticated-git-ops.js} +3 -3
  92. package/dist/{strategies → vcs}/azure-pr-strategy.d.ts +2 -2
  93. package/dist/{strategies → vcs}/azure-pr-strategy.js +5 -5
  94. package/dist/{strategies → vcs}/commit-strategy-selector.d.ts +3 -3
  95. package/dist/{strategies → vcs}/commit-strategy-selector.js +1 -1
  96. package/dist/{strategies → vcs}/git-commit-strategy.d.ts +2 -2
  97. package/dist/{strategies → vcs}/git-commit-strategy.js +3 -3
  98. package/dist/{git-ops.d.ts → vcs/git-ops.d.ts} +1 -1
  99. package/dist/{git-ops.js → vcs/git-ops.js} +4 -4
  100. package/dist/{github-app-token-manager.d.ts → vcs/github-app-token-manager.d.ts} +1 -1
  101. package/dist/{github-app-token-manager.js → vcs/github-app-token-manager.js} +1 -1
  102. package/dist/{strategies → vcs}/github-pr-strategy.d.ts +2 -2
  103. package/dist/{strategies → vcs}/github-pr-strategy.js +30 -33
  104. package/dist/{strategies → vcs}/gitlab-pr-strategy.d.ts +2 -2
  105. package/dist/{strategies → vcs}/gitlab-pr-strategy.js +5 -5
  106. package/dist/{strategies → vcs}/graphql-commit-strategy.d.ts +2 -2
  107. package/dist/{strategies → vcs}/graphql-commit-strategy.js +3 -3
  108. package/dist/vcs/index.d.ts +16 -0
  109. package/dist/{strategies → vcs}/index.js +15 -10
  110. package/dist/{pr-creator.d.ts → vcs/pr-creator.d.ts} +4 -4
  111. package/dist/{pr-creator.js → vcs/pr-creator.js} +3 -3
  112. package/dist/vcs/pr-strategy.d.ts +41 -0
  113. package/dist/{strategies → vcs}/pr-strategy.js +1 -1
  114. package/dist/{strategies/pr-strategy.d.ts → vcs/types.d.ts} +32 -35
  115. package/dist/vcs/types.js +1 -0
  116. package/package.json +2 -2
  117. package/dist/repository-processor.d.ts +0 -79
  118. package/dist/repository-processor.js +0 -659
  119. package/dist/strategies/commit-strategy.d.ts +0 -36
  120. package/dist/strategies/index.d.ts +0 -18
  121. /package/dist/{file-reference-resolver.js → config/file-reference-resolver.js} +0 -0
  122. /package/dist/{config-formatter.d.ts → config/formatter.d.ts} +0 -0
  123. /package/dist/{config-formatter.js → config/formatter.js} +0 -0
  124. /package/dist/{merge.d.ts → config/merge.d.ts} +0 -0
  125. /package/dist/{merge.js → config/merge.js} +0 -0
  126. /package/dist/{github-summary.d.ts → output/github-summary.d.ts} +0 -0
  127. /package/dist/{github-summary.js → output/github-summary.js} +0 -0
  128. /package/dist/{plan-formatter.d.ts → output/plan-formatter.d.ts} +0 -0
  129. /package/dist/{plan-formatter.js → output/plan-formatter.js} +0 -0
  130. /package/dist/{plan-summary.d.ts → output/plan-summary.d.ts} +0 -0
  131. /package/dist/{plan-summary.js → output/plan-summary.js} +0 -0
  132. /package/dist/{summary-utils.js → output/summary-utils.js} +0 -0
  133. /package/dist/{repo-settings-diff.js → settings/repo-settings/diff.js} +0 -0
  134. /package/dist/{repo-settings-plan-formatter.js → settings/repo-settings/formatter.js} +0 -0
  135. /package/dist/{strategies/repo-settings-strategy.js → settings/repo-settings/types.js} +0 -0
  136. /package/dist/{resource-converters.js → settings/resource-converters.js} +0 -0
  137. /package/dist/{strategies/commit-strategy.js → settings/rulesets/types.js} +0 -0
  138. /package/dist/{env.d.ts → shared/env.d.ts} +0 -0
  139. /package/dist/{env.js → shared/env.js} +0 -0
  140. /package/dist/{repo-detector.d.ts → shared/repo-detector.d.ts} +0 -0
  141. /package/dist/{repo-detector.js → shared/repo-detector.js} +0 -0
  142. /package/dist/{retry-utils.d.ts → shared/retry-utils.d.ts} +0 -0
  143. /package/dist/{retry-utils.js → shared/retry-utils.js} +0 -0
  144. /package/dist/{sanitize-utils.d.ts → shared/sanitize-utils.d.ts} +0 -0
  145. /package/dist/{sanitize-utils.js → shared/sanitize-utils.js} +0 -0
  146. /package/dist/{shell-utils.d.ts → shared/shell-utils.d.ts} +0 -0
  147. /package/dist/{shell-utils.js → shared/shell-utils.js} +0 -0
  148. /package/dist/{workspace-utils.d.ts → shared/workspace-utils.d.ts} +0 -0
  149. /package/dist/{workspace-utils.js → shared/workspace-utils.js} +0 -0
  150. /package/dist/{diff-utils.d.ts → sync/diff-utils.d.ts} +0 -0
  151. /package/dist/{diff-utils.js → sync/diff-utils.js} +0 -0
  152. /package/dist/{manifest.d.ts → sync/manifest.d.ts} +0 -0
  153. /package/dist/{manifest.js → sync/manifest.js} +0 -0
  154. /package/dist/{strategies/ruleset-strategy.js → sync/types.js} +0 -0
  155. /package/dist/{xfg-template.js → sync/xfg-template.js} +0 -0
  156. /package/dist/{authenticated-git-ops.d.ts → vcs/authenticated-git-ops.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,474 +1,3 @@
1
- #!/usr/bin/env node
2
- import { program, Command } from "commander";
3
- import { resolve, join, dirname } from "node:path";
4
- import { existsSync, readFileSync } from "node:fs";
5
- import { fileURLToPath } from "node:url";
6
- import chalk from "chalk";
7
- import { loadRawConfig, normalizeConfig, } from "./config.js";
8
- import { validateForSync, validateForSettings } from "./config-validator.js";
9
- // Get version from package.json
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = dirname(__filename);
12
- const packageJson = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
13
- import { parseGitUrl, getRepoDisplayName } from "./repo-detector.js";
14
- import { sanitizeBranchName, validateBranchName } from "./git-ops.js";
15
- import { logger } from "./logger.js";
16
- import { generateWorkspaceName } from "./workspace-utils.js";
17
- import { RepositoryProcessor, } from "./repository-processor.js";
18
- import { buildRepoResult, buildErrorResult } from "./summary-utils.js";
19
- import { RulesetProcessor, } from "./ruleset-processor.js";
20
- import { getManagedRulesets } from "./manifest.js";
21
- import { isGitHubRepo } from "./repo-detector.js";
22
- import { RepoSettingsProcessor, } from "./repo-settings-processor.js";
23
- import { printPlan } from "./plan-formatter.js";
24
- import { writePlanSummary } from "./plan-summary.js";
25
- import { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
26
- /**
27
- * Default factory that creates a real RepositoryProcessor.
28
- */
29
- export const defaultProcessorFactory = () => new RepositoryProcessor();
30
- /**
31
- * Default factory that creates a real RulesetProcessor.
32
- */
33
- export const defaultRulesetProcessorFactory = () => new RulesetProcessor();
34
- /**
35
- * Default factory that creates a real RepoSettingsProcessor.
36
- */
37
- export const defaultRepoSettingsProcessorFactory = () => new RepoSettingsProcessor();
38
- /**
39
- * Adds shared options to a command.
40
- */
41
- function addSharedOptions(cmd) {
42
- return cmd
43
- .requiredOption("-c, --config <path>", "Path to YAML config file")
44
- .option("-d, --dry-run", "Show what would be done without making changes")
45
- .option("-w, --work-dir <path>", "Temporary directory for cloning", "./tmp")
46
- .option("-r, --retries <number>", "Number of retries for network operations (0 to disable)", (v) => parseInt(v, 10), 3)
47
- .option("--no-delete", "Skip deletion of orphaned resources even if deleteOrphaned is configured");
48
- }
49
- // =============================================================================
50
- // Sync Command
51
- // =============================================================================
52
- /**
53
- * Get unique file names from all repos in the config
54
- */
55
- function getUniqueFileNames(config) {
56
- const fileNames = new Set();
57
- for (const repo of config.repos) {
58
- for (const file of repo.files) {
59
- fileNames.add(file.fileName);
60
- }
61
- }
62
- return Array.from(fileNames);
63
- }
64
- /**
65
- * Generate default branch name based on files being synced
66
- */
67
- function generateBranchName(fileNames) {
68
- if (fileNames.length === 1) {
69
- return `chore/sync-${sanitizeBranchName(fileNames[0])}`;
70
- }
71
- return "chore/sync-config";
72
- }
73
- /**
74
- * Format file names for display
75
- */
76
- function formatFileNames(fileNames) {
77
- if (fileNames.length === 1) {
78
- return fileNames[0];
79
- }
80
- if (fileNames.length <= 3) {
81
- return fileNames.join(", ");
82
- }
83
- return `${fileNames.length} files`;
84
- }
85
- export async function runSync(options, processorFactory = defaultProcessorFactory) {
86
- const configPath = resolve(options.config);
87
- if (!existsSync(configPath)) {
88
- console.error(`Config file not found: ${configPath}`);
89
- process.exit(1);
90
- }
91
- console.log(`Loading config from: ${configPath}`);
92
- if (options.dryRun) {
93
- console.log("Running in DRY RUN mode - no changes will be made\n");
94
- }
95
- const rawConfig = loadRawConfig(configPath);
96
- // Validate config is suitable for sync command
97
- try {
98
- validateForSync(rawConfig);
99
- }
100
- catch (error) {
101
- console.error(error instanceof Error ? error.message : String(error));
102
- process.exit(1);
103
- }
104
- const config = normalizeConfig(rawConfig);
105
- const fileNames = getUniqueFileNames(config);
106
- let branchName;
107
- if (options.branch) {
108
- validateBranchName(options.branch);
109
- branchName = options.branch;
110
- }
111
- else {
112
- branchName = generateBranchName(fileNames);
113
- }
114
- logger.setTotal(config.repos.length);
115
- console.log(`Found ${config.repos.length} repositories to process`);
116
- console.log(`Target files: ${formatFileNames(fileNames)}`);
117
- console.log(`Branch: ${branchName}\n`);
118
- const processor = processorFactory();
119
- const results = [];
120
- // Build plan for Terraform-style output
121
- const plan = { resources: [], errors: [] };
122
- for (let i = 0; i < config.repos.length; i++) {
123
- const repoConfig = config.repos[i];
124
- // Apply CLI merge overrides to repo config
125
- if (options.merge || options.mergeStrategy || options.deleteBranch) {
126
- repoConfig.prOptions = {
127
- ...repoConfig.prOptions,
128
- merge: options.merge ?? repoConfig.prOptions?.merge,
129
- mergeStrategy: options.mergeStrategy ?? repoConfig.prOptions?.mergeStrategy,
130
- deleteBranch: options.deleteBranch ?? repoConfig.prOptions?.deleteBranch,
131
- };
132
- }
133
- const current = i + 1;
134
- let repoInfo;
135
- try {
136
- repoInfo = parseGitUrl(repoConfig.git, {
137
- githubHosts: config.githubHosts,
138
- });
139
- }
140
- catch (error) {
141
- logger.error(current, repoConfig.git, String(error));
142
- results.push(buildErrorResult(repoConfig.git, error));
143
- plan.errors.push({
144
- repo: repoConfig.git,
145
- message: error instanceof Error ? error.message : String(error),
146
- });
147
- continue;
148
- }
149
- const repoName = getRepoDisplayName(repoInfo);
150
- const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(i)));
151
- try {
152
- logger.progress(current, repoName, "Processing...");
153
- const result = await processor.process(repoConfig, repoInfo, {
154
- branchName,
155
- workDir,
156
- configId: config.id,
157
- dryRun: options.dryRun,
158
- retries: options.retries,
159
- prTemplate: config.prTemplate,
160
- noDelete: options.noDelete,
161
- });
162
- const repoResult = buildRepoResult(repoName, repoConfig, result);
163
- results.push(repoResult);
164
- if (result.skipped) {
165
- logger.skip(current, repoName, result.message);
166
- }
167
- else if (result.success) {
168
- logger.success(current, repoName, repoResult.message);
169
- }
170
- else {
171
- logger.error(current, repoName, result.message);
172
- }
173
- // Collect resources for plan output
174
- plan.resources.push(...syncResultToResources(repoName, repoConfig, result));
175
- }
176
- catch (error) {
177
- logger.error(current, repoName, String(error));
178
- results.push(buildErrorResult(repoName, error));
179
- plan.errors.push({
180
- repo: repoName,
181
- message: error instanceof Error ? error.message : String(error),
182
- });
183
- }
184
- }
185
- // Print Terraform-style plan summary
186
- console.log("");
187
- printPlan(plan);
188
- // Write GitHub Actions job summary
189
- writePlanSummary(plan, {
190
- title: "Config Sync Summary",
191
- dryRun: options.dryRun ?? false,
192
- });
193
- if (plan.errors && plan.errors.length > 0) {
194
- process.exit(1);
195
- }
196
- }
197
- // =============================================================================
198
- // Settings Command
199
- // =============================================================================
200
- export async function runSettings(options, processorFactory = defaultRulesetProcessorFactory, repoProcessorFactory = defaultProcessorFactory, repoSettingsProcessorFactory = defaultRepoSettingsProcessorFactory) {
201
- const configPath = resolve(options.config);
202
- if (!existsSync(configPath)) {
203
- console.error(`Config file not found: ${configPath}`);
204
- process.exit(1);
205
- }
206
- console.log(`Loading config from: ${configPath}`);
207
- if (options.dryRun) {
208
- console.log("Running in DRY RUN mode - no changes will be made\n");
209
- }
210
- const rawConfig = loadRawConfig(configPath);
211
- // Validate config is suitable for settings command
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
- // Check if any repos have rulesets configured or have managed rulesets to clean up
221
- const reposWithRulesets = config.repos.filter((r) => r.settings?.rulesets && Object.keys(r.settings.rulesets).length > 0);
222
- // Check if any repos have repo settings configured
223
- const reposWithRepoSettings = config.repos.filter((r) => r.settings?.repo && Object.keys(r.settings.repo).length > 0);
224
- if (reposWithRulesets.length === 0 && reposWithRepoSettings.length === 0) {
225
- console.log("No settings configured. Add settings.rulesets or settings.repo to your config.");
226
- return;
227
- }
228
- if (reposWithRulesets.length > 0) {
229
- console.log(`Found ${reposWithRulesets.length} repositories with rulesets`);
230
- }
231
- if (reposWithRepoSettings.length > 0) {
232
- console.log(`Found ${reposWithRepoSettings.length} repositories with repo settings`);
233
- }
234
- console.log("");
235
- logger.setTotal(reposWithRulesets.length + reposWithRepoSettings.length);
236
- const processor = processorFactory();
237
- const repoProcessor = repoProcessorFactory();
238
- const results = [];
239
- // Build plan for Terraform-style output
240
- const plan = { resources: [], errors: [] };
241
- for (let i = 0; i < reposWithRulesets.length; i++) {
242
- const repoConfig = reposWithRulesets[i];
243
- let repoInfo;
244
- try {
245
- repoInfo = parseGitUrl(repoConfig.git, {
246
- githubHosts: config.githubHosts,
247
- });
248
- }
249
- catch (error) {
250
- logger.error(i + 1, repoConfig.git, String(error));
251
- results.push(buildErrorResult(repoConfig.git, error));
252
- plan.errors.push({
253
- repo: repoConfig.git,
254
- message: error instanceof Error ? error.message : String(error),
255
- });
256
- continue;
257
- }
258
- const repoName = getRepoDisplayName(repoInfo);
259
- // Skip non-GitHub repos
260
- if (!isGitHubRepo(repoInfo)) {
261
- logger.skip(i + 1, repoName, "GitHub Rulesets only supported for GitHub repos");
262
- // Mark all rulesets from this repo as skipped
263
- if (repoConfig.settings?.rulesets) {
264
- for (const rulesetName of Object.keys(repoConfig.settings.rulesets)) {
265
- plan.resources.push({
266
- type: "ruleset",
267
- repo: repoName,
268
- name: rulesetName,
269
- action: "skipped",
270
- skipReason: "GitHub Rulesets only supported for GitHub repos",
271
- });
272
- }
273
- }
274
- continue;
275
- }
276
- // Note: For settings command, we don't clone repos - we work with the API directly.
277
- // Manifest handling for tracking managed rulesets would require cloning.
278
- // For now, use an empty list - orphan deletion requires the sync command first.
279
- const managedRulesets = getManagedRulesets(null, config.id);
280
- try {
281
- logger.progress(i + 1, repoName, "Processing rulesets...");
282
- const result = await processor.process(repoConfig, repoInfo, {
283
- configId: config.id,
284
- dryRun: options.dryRun,
285
- managedRulesets,
286
- noDelete: options.noDelete,
287
- });
288
- // Print detailed ruleset plan output
289
- if (result.planOutput && result.planOutput.lines.length > 0) {
290
- logger.info("");
291
- logger.info(chalk.bold(`${repoName} - Rulesets:`));
292
- for (const line of result.planOutput.lines) {
293
- logger.info(line);
294
- }
295
- }
296
- if (result.skipped) {
297
- logger.skip(i + 1, repoName, result.message);
298
- }
299
- else if (result.success) {
300
- logger.success(i + 1, repoName, result.message);
301
- // Update manifest with ruleset tracking if there are rulesets to track
302
- if (result.manifestUpdate &&
303
- result.manifestUpdate.rulesets.length > 0) {
304
- const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(i)));
305
- logger.progress(i + 1, repoName, "Updating manifest...");
306
- const manifestResult = await repoProcessor.updateManifestOnly(repoInfo, repoConfig, {
307
- branchName: "chore/sync-rulesets",
308
- workDir,
309
- configId: config.id,
310
- dryRun: options.dryRun,
311
- retries: options.retries,
312
- }, result.manifestUpdate);
313
- if (!manifestResult.success && !manifestResult.skipped) {
314
- logger.info(`Warning: Failed to update manifest for ${repoName}: ${manifestResult.message}`);
315
- }
316
- }
317
- }
318
- else {
319
- logger.error(i + 1, repoName, result.message);
320
- }
321
- results.push({
322
- repoName,
323
- status: result.skipped
324
- ? "skipped"
325
- : result.success
326
- ? "succeeded"
327
- : "failed",
328
- message: result.message,
329
- rulesetPlanDetails: result.planOutput?.entries,
330
- });
331
- // Collect resources for plan output
332
- plan.resources.push(...rulesetResultToResources(repoName, result));
333
- }
334
- catch (error) {
335
- logger.error(i + 1, repoName, String(error));
336
- results.push(buildErrorResult(repoName, error));
337
- plan.errors.push({
338
- repo: repoName,
339
- message: error instanceof Error ? error.message : String(error),
340
- });
341
- }
342
- }
343
- // Process repo settings
344
- if (reposWithRepoSettings.length > 0) {
345
- const repoSettingsProcessor = repoSettingsProcessorFactory();
346
- console.log(`\nProcessing repo settings for ${reposWithRepoSettings.length} repositories\n`);
347
- for (let i = 0; i < reposWithRepoSettings.length; i++) {
348
- const repoConfig = reposWithRepoSettings[i];
349
- let repoInfo;
350
- try {
351
- repoInfo = parseGitUrl(repoConfig.git, {
352
- githubHosts: config.githubHosts,
353
- });
354
- }
355
- catch (error) {
356
- console.error(`Failed to parse ${repoConfig.git}: ${error}`);
357
- plan.errors.push({
358
- repo: repoConfig.git,
359
- message: error instanceof Error ? error.message : String(error),
360
- });
361
- continue;
362
- }
363
- const repoName = getRepoDisplayName(repoInfo);
364
- try {
365
- const result = await repoSettingsProcessor.process(repoConfig, repoInfo, {
366
- dryRun: options.dryRun,
367
- });
368
- if (result.planOutput && result.planOutput.lines.length > 0) {
369
- console.log(`\n ${chalk.bold(repoName)}:`);
370
- console.log(" Repo Settings:");
371
- for (const line of result.planOutput.lines) {
372
- console.log(line);
373
- }
374
- if (result.warnings && result.warnings.length > 0) {
375
- for (const warning of result.warnings) {
376
- console.log(chalk.yellow(` ⚠️ Warning: ${warning}`));
377
- }
378
- }
379
- }
380
- if (result.skipped) {
381
- // Silent skip for repos without repo settings
382
- }
383
- else if (result.success) {
384
- console.log(chalk.green(` ✓ ${repoName}: ${result.message}`));
385
- }
386
- else {
387
- console.log(chalk.red(` ✗ ${repoName}: ${result.message}`));
388
- }
389
- // Merge repo settings plan details into existing result or push new
390
- if (!result.skipped) {
391
- const existing = results.find((r) => r.repoName === repoName);
392
- if (existing) {
393
- existing.repoSettingsPlanDetails = result.planOutput?.entries;
394
- }
395
- else {
396
- results.push({
397
- repoName,
398
- status: result.success ? "succeeded" : "failed",
399
- message: result.message,
400
- repoSettingsPlanDetails: result.planOutput?.entries,
401
- });
402
- }
403
- }
404
- // Collect resources for plan output
405
- plan.resources.push(...repoSettingsResultToResources(repoName, result));
406
- }
407
- catch (error) {
408
- console.error(` ✗ ${repoName}: ${error}`);
409
- plan.errors.push({
410
- repo: repoName,
411
- message: error instanceof Error ? error.message : String(error),
412
- });
413
- }
414
- }
415
- }
416
- // Print Terraform-style plan summary
417
- console.log("");
418
- printPlan(plan);
419
- // Write GitHub Actions job summary
420
- writePlanSummary(plan, {
421
- title: "Repository Settings Summary",
422
- dryRun: options.dryRun ?? false,
423
- });
424
- if (plan.errors && plan.errors.length > 0) {
425
- process.exit(1);
426
- }
427
- }
428
- // =============================================================================
429
- // CLI Program
430
- // =============================================================================
431
- program
432
- .name("xfg")
433
- .description("Sync files and manage settings across repositories")
434
- .version(packageJson.version);
435
- // Sync command (file synchronization)
436
- const syncCommand = new Command("sync")
437
- .description("Sync configuration files across repositories (default command)")
438
- .option("-b, --branch <name>", "Override the branch name (default: chore/sync-{filename} or chore/sync-config)")
439
- .option("-m, --merge <mode>", "PR merge mode: manual, auto (default, merge when checks pass), force (bypass requirements), direct (push to default branch, no PR)", (value) => {
440
- const valid = ["manual", "auto", "force", "direct"];
441
- if (!valid.includes(value)) {
442
- throw new Error(`Invalid merge mode: ${value}. Valid: ${valid.join(", ")}`);
443
- }
444
- return value;
445
- })
446
- .option("--merge-strategy <strategy>", "Merge strategy: merge, squash (default), rebase", (value) => {
447
- const valid = ["merge", "squash", "rebase"];
448
- if (!valid.includes(value)) {
449
- throw new Error(`Invalid merge strategy: ${value}. Valid: ${valid.join(", ")}`);
450
- }
451
- return value;
452
- })
453
- .option("--delete-branch", "Delete source branch after merge")
454
- .action((opts) => {
455
- runSync(opts).catch((error) => {
456
- console.error("Fatal error:", error);
457
- process.exit(1);
458
- });
459
- });
460
- addSharedOptions(syncCommand);
461
- program.addCommand(syncCommand);
462
- // Settings command (ruleset management)
463
- const settingsCommand = new Command("settings")
464
- .description("Manage GitHub Rulesets for repositories")
465
- .action((opts) => {
466
- runSettings(opts).catch((error) => {
467
- console.error("Fatal error:", error);
468
- process.exit(1);
469
- });
470
- });
471
- addSharedOptions(settingsCommand);
472
- program.addCommand(settingsCommand);
473
- // Export program for CLI entry point
474
- export { program };
1
+ // Public API for library consumers
2
+ export { runSync, runSettings } from "./cli/index.js";
3
+ export { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./cli/index.js";
@@ -0,0 +1,4 @@
1
+ export { formatResourceId, formatResourceLine, formatPlanSummary, formatPlan, printPlan, type ResourceType, type ResourceAction, type Resource, type ResourceDetails, type PropertyChange, type PlanCounts, type Plan, type RepoError, } from "./plan-formatter.js";
2
+ export { formatPlanMarkdown, writePlanSummary, type PlanMarkdownOptions, } from "./plan-summary.js";
3
+ export { formatSummary, isGitHubActions, writeSummary, type MergeOutcome, type FileChanges, type RulesetPlanDetail, type RepoSettingsPlanDetail, type RepoResult, type SummaryData, } from "./github-summary.js";
4
+ export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
@@ -0,0 +1,8 @@
1
+ // Plan formatting (console output with chalk)
2
+ export { formatResourceId, formatResourceLine, formatPlanSummary, formatPlan, printPlan, } from "./plan-formatter.js";
3
+ // Plan summary (markdown output for GitHub)
4
+ export { formatPlanMarkdown, writePlanSummary, } from "./plan-summary.js";
5
+ // GitHub Actions summary
6
+ export { formatSummary, isGitHubActions, writeSummary, } from "./github-summary.js";
7
+ // Summary utilities
8
+ export { getMergeOutcome, toFileChanges, buildRepoResult, buildErrorResult, } from "./summary-utils.js";
@@ -1,7 +1,7 @@
1
- import { ProcessorResult } from "./repository-processor.js";
2
- import { RepoConfig } from "./config.js";
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 "./diff-utils.js";
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,3 @@
1
+ export * from "./rulesets/index.js";
2
+ export * from "./repo-settings/index.js";
3
+ export { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
@@ -0,0 +1,6 @@
1
+ // Rulesets
2
+ export * from "./rulesets/index.js";
3
+ // Repo settings
4
+ export * from "./repo-settings/index.js";
5
+ // Resource converters
6
+ export { rulesetResultToResources, syncResultToResources, repoSettingsResultToResources, } from "./resource-converters.js";
@@ -1,5 +1,5 @@
1
- import type { GitHubRepoSettings } from "./config.js";
2
- import type { CurrentRepoSettings } from "./strategies/repo-settings-strategy.js";
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,4 +1,4 @@
1
- import type { RepoSettingsChange } from "./repo-settings-diff.js";
1
+ import type { RepoSettingsChange } from "./diff.js";
2
2
  export interface RepoSettingsPlanEntry {
3
3
  property: string;
4
4
  action: "add" | "change";
@@ -1,7 +1,7 @@
1
- import { ICommandExecutor } from "../command-executor.js";
2
- import { RepoInfo } from "../repo-detector.js";
3
- import type { GitHubRepoSettings } from "../config.js";
4
- import type { IRepoSettingsStrategy, RepoSettingsStrategyOptions, CurrentRepoSettings } from "./repo-settings-strategy.js";
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 "../command-executor.js";
2
- import { isGitHubRepo } from "../repo-detector.js";
3
- import { escapeShellArg } from "../shell-utils.js";
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 "./config.js";
2
- import type { RepoInfo } from "./repo-detector.js";
3
- import type { IRepoSettingsStrategy } from "./strategies/repo-settings-strategy.js";
4
- import { RepoSettingsPlanResult } from "./repo-settings-plan-formatter.js";
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 "./repo-detector.js";
2
- import { GitHubRepoSettingsStrategy } from "./strategies/github-repo-settings-strategy.js";
3
- import { diffRepoSettings, hasChanges } from "./repo-settings-diff.js";
4
- import { formatRepoSettingsPlan, } from "./repo-settings-plan-formatter.js";
5
- import { hasGitHubAppCredentials } from "./strategies/index.js";
6
- import { GitHubAppTokenManager } from "./github-app-token-manager.js";
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;
@@ -1,5 +1,5 @@
1
- import type { RepoInfo } from "../repo-detector.js";
2
- import type { GitHubRepoSettings } from "../config.js";
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;
@@ -1,7 +1,7 @@
1
- import type { Resource } from "./plan-formatter.js";
2
- import type { RulesetProcessorResult } from "./ruleset-processor.js";
3
- import type { ProcessorResult } from "./repository-processor.js";
4
- import type { RepoConfig } from "./config.js";
1
+ import type { Resource } from "../output/plan-formatter.js";
2
+ import type { RulesetProcessorResult } from "./rulesets/processor.js";
3
+ import type { ProcessorResult } from "../sync/index.js";
4
+ import type { RepoConfig } from "../config/index.js";
5
5
  /**
6
6
  * Convert RulesetProcessorResult planOutput entries to Resource objects.
7
7
  * Includes the detailed plan lines in the first resource's details for display.
@@ -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[];