@aspruyt/xfg 6.0.3 → 6.2.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.
Files changed (143) hide show
  1. package/dist/cli/lifecycle-report-builder.d.ts +2 -2
  2. package/dist/cli/lifecycle-report-builder.js +3 -11
  3. package/dist/cli/program.d.ts +2 -1
  4. package/dist/cli/program.js +2 -3
  5. package/dist/cli/repo-sync-runner.d.ts +24 -0
  6. package/dist/cli/repo-sync-runner.js +156 -0
  7. package/dist/cli/results-collector.d.ts +1 -1
  8. package/dist/cli/results-collector.js +2 -2
  9. package/dist/cli/settings-factories.d.ts +7 -0
  10. package/dist/cli/settings-factories.js +27 -0
  11. package/dist/cli/settings-report-builder.d.ts +1 -1
  12. package/dist/cli/settings-report-builder.js +12 -23
  13. package/dist/cli/settings-runner.d.ts +2 -0
  14. package/dist/cli/settings-runner.js +87 -0
  15. package/dist/cli/sync-command.d.ts +1 -1
  16. package/dist/cli/sync-command.js +31 -372
  17. package/dist/cli/sync-report-builder.d.ts +1 -1
  18. package/dist/cli/sync-utils.d.ts +8 -0
  19. package/dist/cli/sync-utils.js +36 -0
  20. package/dist/cli/types.d.ts +5 -7
  21. package/dist/cli/unified-summary.d.ts +1 -3
  22. package/dist/cli/unified-summary.js +7 -5
  23. package/dist/cli.js +2 -1
  24. package/dist/{shared → config}/env.js +2 -2
  25. package/dist/config/extends-resolver.js +4 -3
  26. package/dist/config/file-reference-resolver.js +4 -2
  27. package/dist/config/formatter.js +18 -1
  28. package/dist/config/index.d.ts +1 -1
  29. package/dist/config/loader.js +30 -6
  30. package/dist/config/merge.d.ts +11 -1
  31. package/dist/config/merge.js +78 -6
  32. package/dist/config/normalizer.js +53 -38
  33. package/dist/config/validator.d.ts +1 -4
  34. package/dist/config/validator.js +13 -599
  35. package/dist/config/validators/file-validator.d.ts +2 -1
  36. package/dist/config/validators/file-validator.js +9 -1
  37. package/dist/config/validators/group-validator.d.ts +3 -0
  38. package/dist/config/validators/group-validator.js +167 -0
  39. package/dist/config/validators/repo-entry-validator.d.ts +2 -0
  40. package/dist/config/validators/repo-entry-validator.js +165 -0
  41. package/dist/config/validators/repo-settings-validator.js +18 -7
  42. package/dist/config/validators/ruleset-validator.js +2 -5
  43. package/dist/config/validators/shared.d.ts +11 -0
  44. package/dist/config/validators/shared.js +242 -0
  45. package/dist/lifecycle/ado-migration-source.js +2 -4
  46. package/dist/lifecycle/github-lifecycle-provider.d.ts +7 -11
  47. package/dist/lifecycle/github-lifecycle-provider.js +125 -136
  48. package/dist/lifecycle/{lifecycle-helpers.d.ts → helpers.d.ts} +5 -1
  49. package/dist/lifecycle/{lifecycle-helpers.js → helpers.js} +9 -8
  50. package/dist/lifecycle/index.d.ts +2 -2
  51. package/dist/lifecycle/index.js +1 -1
  52. package/dist/lifecycle/repo-lifecycle-factory.d.ts +2 -2
  53. package/dist/output/github-summary.js +2 -3
  54. package/dist/output/index.d.ts +4 -0
  55. package/dist/output/index.js +4 -0
  56. package/dist/output/lifecycle-report.d.ts +1 -1
  57. package/dist/output/lifecycle-report.js +5 -0
  58. package/dist/output/sync-report.d.ts +25 -3
  59. package/dist/output/sync-report.js +11 -11
  60. package/dist/settings/base-processor.d.ts +18 -7
  61. package/dist/settings/base-processor.js +26 -5
  62. package/dist/settings/code-scanning/diff.js +2 -2
  63. package/dist/settings/code-scanning/formatter.d.ts +2 -6
  64. package/dist/settings/code-scanning/formatter.js +2 -25
  65. package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +3 -7
  66. package/dist/settings/code-scanning/github-code-scanning-strategy.js +2 -2
  67. package/dist/settings/code-scanning/processor.js +6 -4
  68. package/dist/settings/code-scanning/types.d.ts +10 -8
  69. package/dist/settings/labels/github-labels-strategy.d.ts +3 -11
  70. package/dist/settings/labels/types.d.ts +12 -10
  71. package/dist/settings/repo-settings/diff.d.ts +1 -1
  72. package/dist/settings/repo-settings/diff.js +1 -1
  73. package/dist/settings/repo-settings/formatter.d.ts +2 -6
  74. package/dist/settings/repo-settings/formatter.js +4 -23
  75. package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -2
  76. package/dist/settings/repo-settings/github-repo-settings-strategy.js +8 -7
  77. package/dist/settings/repo-settings/processor.js +11 -11
  78. package/dist/settings/repo-settings/types.d.ts +2 -2
  79. package/dist/settings/rulesets/diff-algorithm.js +4 -2
  80. package/dist/settings/rulesets/diff.js +2 -51
  81. package/dist/settings/rulesets/formatter.js +4 -0
  82. package/dist/settings/rulesets/github-ruleset-strategy.d.ts +3 -3
  83. package/dist/settings/rulesets/github-ruleset-strategy.js +4 -6
  84. package/dist/settings/rulesets/index.d.ts +1 -1
  85. package/dist/settings/rulesets/index.js +0 -2
  86. package/dist/settings/rulesets/processor.js +1 -1
  87. package/dist/settings/rulesets/types.d.ts +6 -2
  88. package/dist/shared/command-executor.d.ts +4 -4
  89. package/dist/shared/command-executor.js +9 -7
  90. package/dist/shared/diff-format.d.ts +1 -0
  91. package/dist/shared/diff-format.js +10 -0
  92. package/dist/shared/errors.d.ts +7 -4
  93. package/dist/shared/errors.js +8 -8
  94. package/dist/shared/gh-api-utils.d.ts +3 -34
  95. package/dist/shared/gh-api-utils.js +23 -53
  96. package/dist/shared/gh-token-utils.d.ts +26 -0
  97. package/dist/shared/gh-token-utils.js +32 -0
  98. package/dist/shared/json-utils.js +1 -1
  99. package/dist/shared/regex-utils.d.ts +1 -0
  100. package/dist/shared/regex-utils.js +3 -0
  101. package/dist/shared/retry-utils.d.ts +1 -0
  102. package/dist/shared/retry-utils.js +13 -7
  103. package/dist/sync/auth-options-builder.js +1 -1
  104. package/dist/sync/branch-manager.js +5 -3
  105. package/dist/sync/commit-push-manager.js +2 -3
  106. package/dist/sync/diff-utils.d.ts +0 -1
  107. package/dist/sync/diff-utils.js +5 -10
  108. package/dist/sync/file-sync-orchestrator.js +0 -2
  109. package/dist/sync/file-writer.d.ts +3 -0
  110. package/dist/sync/file-writer.js +84 -81
  111. package/dist/sync/index.d.ts +0 -1
  112. package/dist/sync/index.js +0 -1
  113. package/dist/sync/manifest.js +1 -1
  114. package/dist/sync/pr-merge-handler.js +6 -6
  115. package/dist/sync/sync-workflow.js +1 -1
  116. package/dist/sync/types.d.ts +2 -2
  117. package/dist/vcs/ado-pr-strategy.d.ts +3 -5
  118. package/dist/vcs/ado-pr-strategy.js +131 -33
  119. package/dist/vcs/authenticated-git-ops.js +45 -23
  120. package/dist/vcs/git-commit-strategy.js +10 -6
  121. package/dist/vcs/git-ops.js +30 -24
  122. package/dist/vcs/github-pr-strategy.d.ts +3 -2
  123. package/dist/vcs/github-pr-strategy.js +80 -30
  124. package/dist/vcs/gitlab-pr-strategy.d.ts +2 -5
  125. package/dist/vcs/gitlab-pr-strategy.js +88 -87
  126. package/dist/vcs/graphql-commit-strategy.d.ts +1 -5
  127. package/dist/vcs/graphql-commit-strategy.js +21 -37
  128. package/dist/vcs/pr-creator.js +9 -2
  129. package/dist/vcs/pr-strategy.d.ts +2 -3
  130. package/dist/vcs/pr-strategy.js +0 -1
  131. package/dist/vcs/types.d.ts +9 -5
  132. package/package.json +5 -5
  133. package/dist/config/validators/index.d.ts +0 -3
  134. package/dist/config/validators/index.js +0 -6
  135. package/dist/output/types.d.ts +0 -20
  136. package/dist/output/types.js +0 -1
  137. package/dist/shared/shell-utils.d.ts +0 -6
  138. package/dist/shared/shell-utils.js +0 -17
  139. /package/dist/{shared → config}/env.d.ts +0 -0
  140. /package/dist/lifecycle/{lifecycle-formatter.d.ts → formatter.d.ts} +0 -0
  141. /package/dist/lifecycle/{lifecycle-formatter.js → formatter.js} +0 -0
  142. /package/dist/{vcs → shared}/sanitize-utils.d.ts +0 -0
  143. /package/dist/{vcs → shared}/sanitize-utils.js +0 -0
@@ -1,2 +1,2 @@
1
- import type { LifecycleReport, LifecycleAction } from "../output/lifecycle-report.js";
2
- export declare function buildLifecycleReport(results: LifecycleAction[]): LifecycleReport;
1
+ import type { LifecycleReport, LifecycleAction } from "../output/index.js";
2
+ export declare function buildLifecycleReport(actions: LifecycleAction[]): LifecycleReport;
@@ -1,15 +1,7 @@
1
- export function buildLifecycleReport(results) {
2
- const actions = [];
1
+ export function buildLifecycleReport(actions) {
3
2
  const totals = { created: 0, forked: 0, migrated: 0, existed: 0 };
4
- for (const result of results) {
5
- actions.push({
6
- repoName: result.repoName,
7
- action: result.action,
8
- upstream: result.upstream,
9
- source: result.source,
10
- settings: result.settings,
11
- });
12
- totals[result.action]++;
3
+ for (const action of actions) {
4
+ totals[action.action]++;
13
5
  }
14
6
  return { actions, totals };
15
7
  }
@@ -1,5 +1,6 @@
1
1
  import { program } from "commander";
2
2
  import { MergeMode, MergeStrategy } from "../config/index.js";
3
+ declare function getVersion(): string;
3
4
  export declare function parseMergeMode(value: string): MergeMode;
4
5
  export declare function parseMergeStrategy(value: string): MergeStrategy;
5
- export { program };
6
+ export { program, getVersion };
@@ -51,8 +51,7 @@ export function parseMergeStrategy(value) {
51
51
  // =============================================================================
52
52
  program
53
53
  .name("xfg")
54
- .description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab")
55
- .version(getVersion());
54
+ .description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab");
56
55
  // Sync command (file synchronization)
57
56
  const syncCommand = new Command("sync")
58
57
  .description("Sync configuration files across repositories")
@@ -71,4 +70,4 @@ const syncCommand = new Command("sync")
71
70
  });
72
71
  addSharedOptions(syncCommand);
73
72
  program.addCommand(syncCommand);
74
- export { program };
73
+ export { program, getVersion };
@@ -0,0 +1,24 @@
1
+ import type { Config, RepoConfig } from "../config/index.js";
2
+ import type { createTokenManager } from "../vcs/index.js";
3
+ import type { IRepositoryProcessor } from "../sync/index.js";
4
+ import type { ProcessExecutor } from "../shared/command-executor.js";
5
+ import type { Logger } from "../shared/logger.js";
6
+ import { type IRepoLifecycleManager } from "../lifecycle/index.js";
7
+ import type { SyncOptions, SyncResultEntry, SettingsProcessorFactories } from "./types.js";
8
+ import type { ResultsCollector } from "./results-collector.js";
9
+ import type { LifecycleAction } from "../output/index.js";
10
+ export interface RepoIterationContext {
11
+ config: Config;
12
+ options: SyncOptions;
13
+ branchName: string;
14
+ processor: IRepositoryProcessor;
15
+ lifecycleManager: IRepoLifecycleManager;
16
+ tokenManager: ReturnType<typeof createTokenManager>;
17
+ reportResults: SyncResultEntry[];
18
+ lifecycleReportInputs: LifecycleAction[];
19
+ settingsCollector: ResultsCollector;
20
+ factories: SettingsProcessorFactories;
21
+ logger: Logger;
22
+ executor: ProcessExecutor;
23
+ }
24
+ export declare function runSingleRepo(repoConfig: RepoConfig, index: number, ctx: RepoIterationContext): Promise<void>;
@@ -0,0 +1,156 @@
1
+ import { resolve, join } from "node:path";
2
+ import { parseGitUrl, getRepoDisplayName, isGitHubRepo, } from "../repo/index.js";
3
+ import { generateWorkspaceName } from "../shared/workspace-utils.js";
4
+ import { toErrorMessage } from "../shared/type-guards.js";
5
+ import { resolveGitHubToken } from "../shared/gh-token-utils.js";
6
+ import { runLifecycleCheck, } from "../lifecycle/index.js";
7
+ import { applyRepoSettings } from "./settings-runner.js";
8
+ import { determineMergeOutcome } from "./sync-utils.js";
9
+ function pushFailure(results, repoName, error) {
10
+ results.push({
11
+ repoName,
12
+ success: false,
13
+ fileChanges: [],
14
+ error: toErrorMessage(error),
15
+ });
16
+ }
17
+ async function runLifecyclePhase(repo, ctx) {
18
+ const repoNumber = repo.index + 1;
19
+ try {
20
+ const { outputLines, lifecycleResult, reportSettings } = await runLifecycleCheck(repo.repoConfig, repo.repoInfo, {
21
+ dryRun: ctx.options.dryRun ?? false,
22
+ resolvedWorkDir: repo.workDir,
23
+ githubHosts: ctx.config.githubHosts,
24
+ token: repo.token,
25
+ repoIndex: repo.index,
26
+ lifecycleManager: ctx.lifecycleManager,
27
+ repoSettings: ctx.config.settings?.repo,
28
+ });
29
+ for (const line of outputLines) {
30
+ ctx.logger.info(line);
31
+ }
32
+ ctx.lifecycleReportInputs.push({
33
+ repoName: repo.repoName,
34
+ action: lifecycleResult.action,
35
+ upstream: repo.repoConfig.upstream,
36
+ source: repo.repoConfig.source,
37
+ settings: reportSettings,
38
+ });
39
+ if (ctx.options.dryRun && lifecycleResult.action !== "existed") {
40
+ ctx.reportResults.push({
41
+ repoName: repo.repoName,
42
+ success: true,
43
+ fileChanges: [],
44
+ });
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ catch (error) {
50
+ ctx.logger.error(repoNumber, repo.repoName, `Lifecycle error: ${toErrorMessage(error)}`);
51
+ pushFailure(ctx.reportResults, repo.repoName, error);
52
+ return true;
53
+ }
54
+ }
55
+ async function runFileSyncPhase(repo, ctx) {
56
+ const repoNumber = repo.index + 1;
57
+ try {
58
+ ctx.logger.progress(repoNumber, repo.repoName, "Processing...");
59
+ const result = await ctx.processor.process(repo.repoConfig, repo.repoInfo, {
60
+ branchName: ctx.branchName,
61
+ workDir: repo.workDir,
62
+ configId: ctx.config.id,
63
+ dryRun: ctx.options.dryRun,
64
+ retries: ctx.options.retries,
65
+ executor: ctx.executor,
66
+ prTemplate: ctx.config.prTemplate,
67
+ noDelete: ctx.options.noDelete,
68
+ token: repo.token,
69
+ hasAppCredentials: isGitHubRepo(repo.repoInfo) && ctx.tokenManager !== null,
70
+ });
71
+ const mergeOutcome = determineMergeOutcome(result);
72
+ ctx.reportResults.push({
73
+ repoName: repo.repoName,
74
+ success: result.success,
75
+ fileChanges: result.fileChanges ?? [],
76
+ prUrl: result.prUrl,
77
+ mergeOutcome,
78
+ error: result.success ? undefined : result.message,
79
+ });
80
+ if (result.skipped) {
81
+ ctx.logger.skip(repoNumber, repo.repoName, result.message);
82
+ }
83
+ else if (result.success) {
84
+ ctx.logger.success(repoNumber, repo.repoName, result.message);
85
+ }
86
+ else {
87
+ ctx.logger.error(repoNumber, repo.repoName, result.message);
88
+ }
89
+ }
90
+ catch (error) {
91
+ ctx.logger.error(repoNumber, repo.repoName, toErrorMessage(error));
92
+ pushFailure(ctx.reportResults, repo.repoName, error);
93
+ }
94
+ }
95
+ export async function runSingleRepo(repoConfig, index, ctx) {
96
+ const { config, options, logger } = ctx;
97
+ const repoNumber = index + 1;
98
+ const effectivePrOptions = options.merge || options.mergeStrategy || options.deleteBranch
99
+ ? {
100
+ ...repoConfig.prOptions,
101
+ merge: options.merge ?? repoConfig.prOptions?.merge,
102
+ mergeStrategy: options.mergeStrategy ?? repoConfig.prOptions?.mergeStrategy,
103
+ deleteBranch: options.deleteBranch ?? repoConfig.prOptions?.deleteBranch,
104
+ }
105
+ : repoConfig.prOptions;
106
+ const effectiveRepoConfig = { ...repoConfig, prOptions: effectivePrOptions };
107
+ const mergeMode = effectivePrOptions?.merge ?? "auto";
108
+ if (mergeMode === "direct" && effectivePrOptions?.mergeStrategy) {
109
+ logger.warn(`mergeStrategy '${effectivePrOptions.mergeStrategy}' is ignored in direct mode for ${repoConfig.git}`);
110
+ }
111
+ let repoInfo;
112
+ try {
113
+ repoInfo = parseGitUrl(repoConfig.git, {
114
+ githubHosts: config.githubHosts,
115
+ });
116
+ }
117
+ catch (error) {
118
+ logger.error(repoNumber, repoConfig.git, toErrorMessage(error));
119
+ pushFailure(ctx.reportResults, repoConfig.git, error);
120
+ return;
121
+ }
122
+ const repoName = getRepoDisplayName(repoInfo);
123
+ const workDir = resolve(join(options.workDir ?? "./tmp", generateWorkspaceName(index)));
124
+ const repoToken = isGitHubRepo(repoInfo)
125
+ ? (await resolveGitHubToken({
126
+ repoInfo: repoInfo,
127
+ tokenManager: ctx.tokenManager,
128
+ context: repoName,
129
+ log: logger,
130
+ envToken: process.env.GH_TOKEN,
131
+ })).token
132
+ : undefined;
133
+ const repo = {
134
+ repoConfig: effectiveRepoConfig,
135
+ repoInfo,
136
+ repoName,
137
+ index,
138
+ workDir,
139
+ token: repoToken,
140
+ };
141
+ const skipFileSync = await runLifecyclePhase(repo, ctx);
142
+ if (skipFileSync)
143
+ return;
144
+ await runFileSyncPhase(repo, ctx);
145
+ await applyRepoSettings({
146
+ repoConfig: effectiveRepoConfig,
147
+ repoInfo,
148
+ repoName,
149
+ repoNumber,
150
+ options,
151
+ token: repoToken,
152
+ settingsCollector: ctx.settingsCollector,
153
+ factories: ctx.factories,
154
+ logger,
155
+ });
156
+ }
@@ -5,7 +5,7 @@ import type { ProcessorResults } from "./settings-report-builder.js";
5
5
  */
6
6
  export declare class ResultsCollector {
7
7
  private readonly results;
8
- getOrCreate(repoName: string): ProcessorResults;
8
+ findOrCreate(repoName: string): ProcessorResults;
9
9
  appendError(repoName: string, error: unknown): void;
10
10
  getAll(): ProcessorResults[];
11
11
  }
@@ -5,7 +5,7 @@ import { toErrorMessage } from "../shared/type-guards.js";
5
5
  */
6
6
  export class ResultsCollector {
7
7
  results = [];
8
- getOrCreate(repoName) {
8
+ findOrCreate(repoName) {
9
9
  let result = this.results.find((r) => r.repoName === repoName);
10
10
  if (!result) {
11
11
  result = { repoName };
@@ -14,7 +14,7 @@ export class ResultsCollector {
14
14
  return result;
15
15
  }
16
16
  appendError(repoName, error) {
17
- const existing = this.getOrCreate(repoName);
17
+ const existing = this.findOrCreate(repoName);
18
18
  const errorMsg = toErrorMessage(error);
19
19
  if (existing.error) {
20
20
  existing.error += `; ${errorMsg}`;
@@ -0,0 +1,7 @@
1
+ import type { ProcessExecutor } from "../shared/command-executor.js";
2
+ import type { RulesetProcessorFactory, RepoSettingsProcessorFactory, LabelsProcessorFactory, CodeScanningProcessorFactory, SettingsProcessorFactories } from "./types.js";
3
+ export declare function createDefaultRulesetProcessorFactory(executor: ProcessExecutor): RulesetProcessorFactory;
4
+ export declare function createDefaultRepoSettingsProcessorFactory(executor: ProcessExecutor): RepoSettingsProcessorFactory;
5
+ export declare function createDefaultLabelsProcessorFactory(executor: ProcessExecutor): LabelsProcessorFactory;
6
+ export declare function createDefaultCodeScanningProcessorFactory(executor: ProcessExecutor): CodeScanningProcessorFactory;
7
+ export declare function createDefaultFactories(executor: ProcessExecutor, overrides?: Partial<SettingsProcessorFactories>): SettingsProcessorFactories;
@@ -0,0 +1,27 @@
1
+ import { RulesetProcessor, RepoSettingsProcessor, LabelsProcessor, CodeScanningProcessor, GitHubRulesetStrategy, GitHubRepoSettingsStrategy, GitHubLabelsStrategy, GitHubCodeScanningStrategy, } from "../settings/index.js";
2
+ import { GitHubRepoMetadataProvider } from "../repo/index.js";
3
+ export function createDefaultRulesetProcessorFactory(executor) {
4
+ const cwd = process.cwd();
5
+ return () => new RulesetProcessor(new GitHubRulesetStrategy(executor, { cwd }));
6
+ }
7
+ export function createDefaultRepoSettingsProcessorFactory(executor) {
8
+ const cwd = process.cwd();
9
+ return () => new RepoSettingsProcessor(new GitHubRepoSettingsStrategy(executor, { cwd }), new GitHubRepoMetadataProvider(executor, { cwd }));
10
+ }
11
+ export function createDefaultLabelsProcessorFactory(executor) {
12
+ const cwd = process.cwd();
13
+ return () => new LabelsProcessor(new GitHubLabelsStrategy(executor, { cwd }));
14
+ }
15
+ export function createDefaultCodeScanningProcessorFactory(executor) {
16
+ const cwd = process.cwd();
17
+ return () => new CodeScanningProcessor(new GitHubCodeScanningStrategy(executor, { cwd }), new GitHubRepoMetadataProvider(executor, { cwd }));
18
+ }
19
+ export function createDefaultFactories(executor, overrides) {
20
+ return {
21
+ rulesets: overrides?.rulesets ?? createDefaultRulesetProcessorFactory(executor),
22
+ labels: overrides?.labels ?? createDefaultLabelsProcessorFactory(executor),
23
+ repo: overrides?.repo ?? createDefaultRepoSettingsProcessorFactory(executor),
24
+ codeScanning: overrides?.codeScanning ??
25
+ createDefaultCodeScanningProcessorFactory(executor),
26
+ };
27
+ }
@@ -1,4 +1,4 @@
1
- import type { SettingsReport } from "../output/settings-report.js";
1
+ import type { SettingsReport } from "../output/index.js";
2
2
  import { type RepoSettingsPlanEntry, type RulesetPlanEntry, type LabelsPlanEntry, type CodeScanningPlanEntry } from "../settings/index.js";
3
3
  /**
4
4
  * Result from processing a repository's settings and rulesets.
@@ -13,10 +13,8 @@ export function buildSettingsReport(results) {
13
13
  rulesets: [],
14
14
  labels: [],
15
15
  };
16
- // Convert settings processor output
17
16
  if (result.settingsResult?.planOutput?.entries) {
18
17
  for (const entry of result.settingsResult.planOutput.entries) {
19
- // Skip settings where both values are undefined (no actual change)
20
18
  if (entry.oldValue === undefined && entry.newValue === undefined) {
21
19
  continue;
22
20
  }
@@ -27,11 +25,22 @@ export function buildSettingsReport(results) {
27
25
  newValue: entry.newValue,
28
26
  });
29
27
  }
28
+ }
29
+ if (result.codeScanningResult?.planOutput?.entries) {
30
+ for (const entry of result.codeScanningResult.planOutput.entries) {
31
+ repoChanges.settings.push({
32
+ name: `codeScanning.${entry.property}`,
33
+ action: entry.action,
34
+ oldValue: entry.oldValue,
35
+ newValue: entry.newValue ?? null,
36
+ });
37
+ }
38
+ }
39
+ if (repoChanges.settings.length > 0) {
30
40
  const counts = countActions(repoChanges.settings);
31
41
  totals.settings.create += counts.create;
32
42
  totals.settings.update += counts.update;
33
43
  }
34
- // Convert ruleset processor output
35
44
  if (result.rulesetResult?.planOutput?.entries) {
36
45
  for (const entry of result.rulesetResult.planOutput.entries) {
37
46
  if (!isActiveAction(entry))
@@ -48,7 +57,6 @@ export function buildSettingsReport(results) {
48
57
  totals.rulesets.update += counts.update;
49
58
  totals.rulesets.delete += counts.delete;
50
59
  }
51
- // Convert labels processor output
52
60
  if (result.labelsResult?.planOutput?.entries) {
53
61
  for (const entry of result.labelsResult.planOutput.entries) {
54
62
  if (!isActiveAction(entry))
@@ -66,25 +74,6 @@ export function buildSettingsReport(results) {
66
74
  totals.labels.update += counts.update;
67
75
  totals.labels.delete += counts.delete;
68
76
  }
69
- // Convert code scanning processor output
70
- if (result.codeScanningResult?.planOutput?.entries) {
71
- let csCreates = 0;
72
- let csUpdates = 0;
73
- for (const entry of result.codeScanningResult.planOutput.entries) {
74
- repoChanges.settings.push({
75
- name: `codeScanning.${entry.property}`,
76
- action: entry.action,
77
- oldValue: entry.oldValue,
78
- newValue: entry.newValue ?? null,
79
- });
80
- if (entry.action === "create")
81
- csCreates++;
82
- if (entry.action === "update")
83
- csUpdates++;
84
- }
85
- totals.settings.create += csCreates;
86
- totals.settings.update += csUpdates;
87
- }
88
77
  if (result.error) {
89
78
  repoChanges.error = result.error;
90
79
  }
@@ -0,0 +1,2 @@
1
+ import type { ApplyRepoSettingsContext } from "./types.js";
2
+ export declare function applyRepoSettings(ctx: ApplyRepoSettingsContext): Promise<void>;
@@ -0,0 +1,87 @@
1
+ import { isGitHubRepo } from "../repo/index.js";
2
+ import { toErrorMessage } from "../shared/type-guards.js";
3
+ function logSettingsResult(logger, result, label, repoNumber, repoName, settingsCollector) {
4
+ if (result.planOutput?.lines?.length) {
5
+ logger.info("");
6
+ logger.info(`${repoName} - ${label}:`);
7
+ for (const line of result.planOutput.lines) {
8
+ logger.info(line);
9
+ }
10
+ if (result.warnings?.length) {
11
+ for (const warning of result.warnings) {
12
+ logger.warn(warning);
13
+ }
14
+ }
15
+ }
16
+ else if (!result.skipped && result.success) {
17
+ logger.success(repoNumber, repoName, `${label}: ${result.message}`);
18
+ }
19
+ if (!result.success && !result.skipped) {
20
+ logger.error(repoNumber, repoName, `${label}: ${result.message}`);
21
+ settingsCollector.appendError(repoName, result.message);
22
+ }
23
+ }
24
+ async function runAndStoreResult(factory, repoConfig, repoInfo, opts, repoName, settingsCollector, assign) {
25
+ const result = await factory().process(repoConfig, repoInfo, opts);
26
+ if (!result.skipped) {
27
+ assign(settingsCollector.findOrCreate(repoName), result);
28
+ }
29
+ return result;
30
+ }
31
+ function buildSettingsDescriptors(ctx) {
32
+ const { repoConfig, repoInfo, options, token, repoName, settingsCollector } = ctx;
33
+ const { factories } = ctx;
34
+ const sharedOpts = {
35
+ dryRun: options.dryRun,
36
+ noDelete: options.noDelete,
37
+ token,
38
+ };
39
+ return [
40
+ {
41
+ key: "rulesets",
42
+ label: "Rulesets",
43
+ run: () => runAndStoreResult(factories.rulesets, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
44
+ e.rulesetResult = r;
45
+ }),
46
+ },
47
+ {
48
+ key: "labels",
49
+ label: "Labels",
50
+ run: () => runAndStoreResult(factories.labels, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
51
+ e.labelsResult = r;
52
+ }),
53
+ },
54
+ {
55
+ key: "repo",
56
+ label: "Repo Settings",
57
+ run: () => runAndStoreResult(factories.repo, repoConfig, repoInfo, { dryRun: options.dryRun, token }, repoName, settingsCollector, (e, r) => {
58
+ e.settingsResult = r;
59
+ }),
60
+ },
61
+ {
62
+ key: "codeScanning",
63
+ label: "Code Scanning",
64
+ run: () => runAndStoreResult(factories.codeScanning, repoConfig, repoInfo, sharedOpts, repoName, settingsCollector, (e, r) => {
65
+ e.codeScanningResult = r;
66
+ }),
67
+ },
68
+ ];
69
+ }
70
+ export async function applyRepoSettings(ctx) {
71
+ const { repoConfig, repoInfo, repoName, repoNumber, settingsCollector, logger, } = ctx;
72
+ if (!repoConfig.settings || !isGitHubRepo(repoInfo))
73
+ return;
74
+ for (const desc of buildSettingsDescriptors(ctx)) {
75
+ const settingsValue = repoConfig.settings[desc.key];
76
+ if (!settingsValue || Object.keys(settingsValue).length === 0)
77
+ continue;
78
+ try {
79
+ const result = await desc.run();
80
+ logSettingsResult(logger, result, desc.label, repoNumber, repoName, settingsCollector);
81
+ }
82
+ catch (error) {
83
+ logger.error(repoNumber, repoName, `${desc.label}: ${toErrorMessage(error)}`);
84
+ settingsCollector.appendError(repoName, error);
85
+ }
86
+ }
87
+ }
@@ -1,3 +1,3 @@
1
- import { type SyncDependencies, type SyncOptions } from "./types.js";
1
+ import type { SyncDependencies, SyncOptions } from "./types.js";
2
2
  export type { SharedOptions, SyncOptions } from "./types.js";
3
3
  export declare function runSync(options: SyncOptions, deps?: SyncDependencies): Promise<void>;