@aspruyt/xfg 3.8.0 → 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.
@@ -9,58 +9,42 @@ 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
11
  import { formatSettingsReportCLI, writeSettingsReportSummary, } from "../output/settings-report.js";
12
- import { buildSettingsReport } from "./settings-report-builder.js";
12
+ import { buildSettingsReport, } from "./settings-report-builder.js";
13
13
  import { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./types.js";
14
14
  /**
15
- * Run the settings command - manages GitHub Rulesets and repo settings.
15
+ * Collects processing results for the SettingsReport.
16
+ * Provides a centralized way to track results across rulesets and repo settings.
16
17
  */
17
- export async function runSettings(options, processorFactory = defaultRulesetProcessorFactory, repoProcessorFactory = defaultProcessorFactory, repoSettingsProcessorFactory = defaultRepoSettingsProcessorFactory) {
18
- const configPath = resolve(options.config);
19
- if (!existsSync(configPath)) {
20
- console.error(`Config file not found: ${configPath}`);
21
- process.exit(1);
22
- }
23
- console.log(`Loading config from: ${configPath}`);
24
- if (options.dryRun) {
25
- console.log("Running in DRY RUN mode - no changes will be made\n");
26
- }
27
- const rawConfig = loadRawConfig(configPath);
28
- try {
29
- validateForSettings(rawConfig);
30
- }
31
- catch (error) {
32
- console.error(error instanceof Error ? error.message : String(error));
33
- process.exit(1);
34
- }
35
- const config = normalizeConfig(rawConfig);
36
- const reposWithRulesets = config.repos.filter((r) => r.settings?.rulesets && Object.keys(r.settings.rulesets).length > 0);
37
- const reposWithRepoSettings = config.repos.filter((r) => r.settings?.repo && Object.keys(r.settings.repo).length > 0);
38
- if (reposWithRulesets.length === 0 && reposWithRepoSettings.length === 0) {
39
- console.log("No settings configured. Add settings.rulesets or settings.repo to your config.");
40
- return;
41
- }
42
- if (reposWithRulesets.length > 0) {
43
- console.log(`Found ${reposWithRulesets.length} repositories with rulesets`);
44
- }
45
- if (reposWithRepoSettings.length > 0) {
46
- console.log(`Found ${reposWithRepoSettings.length} repositories with repo settings`);
47
- }
48
- console.log("");
49
- logger.setTotal(reposWithRulesets.length + reposWithRepoSettings.length);
50
- const processor = processorFactory();
51
- const repoProcessor = repoProcessorFactory();
52
- const results = [];
53
- const processingResults = [];
54
- function getOrCreateResult(repoName) {
55
- let result = processingResults.find((r) => r.repoName === repoName);
18
+ class ResultsCollector {
19
+ results = [];
20
+ getOrCreate(repoName) {
21
+ let result = this.results.find((r) => r.repoName === repoName);
56
22
  if (!result) {
57
23
  result = { repoName };
58
- processingResults.push(result);
24
+ this.results.push(result);
59
25
  }
60
26
  return result;
61
27
  }
62
- for (let i = 0; i < reposWithRulesets.length; i++) {
63
- const repoConfig = reposWithRulesets[i];
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
+ }
37
+ }
38
+ getAll() {
39
+ return this.results;
40
+ }
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];
64
48
  let repoInfo;
65
49
  try {
66
50
  repoInfo = parseGitUrl(repoConfig.git, {
@@ -70,14 +54,12 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
70
54
  catch (error) {
71
55
  logger.error(i + 1, repoConfig.git, String(error));
72
56
  results.push(buildErrorResult(repoConfig.git, error));
73
- getOrCreateResult(repoConfig.git).error =
74
- error instanceof Error ? error.message : String(error);
57
+ collector.appendError(repoConfig.git, error);
75
58
  continue;
76
59
  }
77
60
  const repoName = getRepoDisplayName(repoInfo);
78
61
  if (!isGitHubRepo(repoInfo)) {
79
62
  logger.skip(i + 1, repoName, "GitHub Rulesets only supported for GitHub repos");
80
- // Skipped repos don't appear in the report
81
63
  continue;
82
64
  }
83
65
  const managedRulesets = getManagedRulesets(null, config.id);
@@ -130,101 +112,133 @@ export async function runSettings(options, processorFactory = defaultRulesetProc
130
112
  message: result.message,
131
113
  rulesetPlanDetails: result.planOutput?.entries,
132
114
  });
133
- // Collect result for SettingsReport
134
115
  if (!result.skipped) {
135
- getOrCreateResult(repoName).rulesetResult = result;
116
+ collector.getOrCreate(repoName).rulesetResult = result;
136
117
  }
137
118
  }
138
119
  catch (error) {
139
120
  logger.error(i + 1, repoName, String(error));
140
121
  results.push(buildErrorResult(repoName, error));
141
- const existingResult = getOrCreateResult(repoName);
142
- const errorMsg = error instanceof Error ? error.message : String(error);
143
- if (existingResult.error) {
144
- existingResult.error += `; ${errorMsg}`;
145
- }
146
- else {
147
- existingResult.error = errorMsg;
148
- }
122
+ collector.appendError(repoName, error);
149
123
  }
150
124
  }
151
- if (reposWithRepoSettings.length > 0) {
152
- const repoSettingsProcessor = repoSettingsProcessorFactory();
153
- console.log(`\nProcessing repo settings for ${reposWithRepoSettings.length} repositories\n`);
154
- for (let i = 0; i < reposWithRepoSettings.length; i++) {
155
- const repoConfig = reposWithRepoSettings[i];
156
- let repoInfo;
157
- try {
158
- repoInfo = parseGitUrl(repoConfig.git, {
159
- githubHosts: config.githubHosts,
160
- });
161
- }
162
- catch (error) {
163
- console.error(`Failed to parse ${repoConfig.git}: ${error}`);
164
- getOrCreateResult(repoConfig.git).error =
165
- error instanceof Error ? error.message : String(error);
166
- continue;
167
- }
168
- const repoName = getRepoDisplayName(repoInfo);
169
- try {
170
- const result = await repoSettingsProcessor.process(repoConfig, repoInfo, {
171
- dryRun: options.dryRun,
172
- });
173
- if (result.planOutput && result.planOutput.lines.length > 0) {
174
- console.log(`\n ${chalk.bold(repoName)}:`);
175
- console.log(" Repo Settings:");
176
- for (const line of result.planOutput.lines) {
177
- console.log(line);
178
- }
179
- if (result.warnings && result.warnings.length > 0) {
180
- for (const warning of result.warnings) {
181
- console.log(chalk.yellow(` ⚠️ Warning: ${warning}`));
182
- }
183
- }
184
- }
185
- if (result.skipped) {
186
- // Silent skip
187
- }
188
- else if (result.success) {
189
- console.log(chalk.green(` ✓ ${repoName}: ${result.message}`));
190
- }
191
- else {
192
- console.log(chalk.red(` ✗ ${repoName}: ${result.message}`));
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);
193
158
  }
194
- if (!result.skipped) {
195
- const existing = results.find((r) => r.repoName === repoName);
196
- if (existing) {
197
- existing.repoSettingsPlanDetails = result.planOutput?.entries;
198
- }
199
- else {
200
- results.push({
201
- repoName,
202
- status: result.success ? "succeeded" : "failed",
203
- message: result.message,
204
- repoSettingsPlanDetails: result.planOutput?.entries,
205
- });
159
+ if (result.warnings && result.warnings.length > 0) {
160
+ for (const warning of result.warnings) {
161
+ console.log(chalk.yellow(` ⚠️ Warning: ${warning}`));
206
162
  }
207
163
  }
208
- // Collect result for SettingsReport
209
- if (!result.skipped) {
210
- getOrCreateResult(repoName).settingsResult = result;
211
- }
212
164
  }
213
- catch (error) {
214
- console.error(` ✗ ${repoName}: ${error}`);
215
- const existingResult = getOrCreateResult(repoName);
216
- const errorMsg = error instanceof Error ? error.message : String(error);
217
- if (existingResult.error) {
218
- existingResult.error += `; ${errorMsg}`;
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;
219
178
  }
220
179
  else {
221
- existingResult.error = errorMsg;
180
+ results.push({
181
+ repoName,
182
+ status: result.success ? "succeeded" : "failed",
183
+ message: result.message,
184
+ repoSettingsPlanDetails: result.planOutput?.entries,
185
+ });
222
186
  }
223
187
  }
188
+ if (!result.skipped) {
189
+ collector.getOrCreate(repoName).settingsResult = result;
190
+ }
191
+ }
192
+ catch (error) {
193
+ console.error(` ✗ ${repoName}: ${error}`);
194
+ collector.appendError(repoName, error);
224
195
  }
225
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`);
231
+ }
232
+ console.log("");
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);
226
240
  console.log("");
227
- const report = buildSettingsReport(processingResults);
241
+ const report = buildSettingsReport(collector.getAll());
228
242
  const lines = formatSettingsReportCLI(report);
229
243
  for (const line of lines) {
230
244
  console.log(line);
@@ -1,7 +1,11 @@
1
1
  import type { SettingsReport } from "../output/settings-report.js";
2
2
  import type { RepoSettingsPlanEntry } from "../settings/repo-settings/formatter.js";
3
3
  import type { RulesetPlanEntry } from "../settings/rulesets/formatter.js";
4
- interface ProcessorResults {
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 {
5
9
  repoName: string;
6
10
  settingsResult?: {
7
11
  planOutput?: {
@@ -16,4 +20,3 @@ interface ProcessorResults {
16
20
  error?: string;
17
21
  }
18
22
  export declare function buildSettingsReport(results: ProcessorResults[]): SettingsReport;
19
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "CLI tool for repository-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",