@aspruyt/xfg 4.0.2 → 4.0.4

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 (151) hide show
  1. package/dist/cli/index.d.ts +1 -1
  2. package/dist/cli/index.js +0 -6
  3. package/dist/cli/program.js +3 -2
  4. package/dist/cli/settings-report-builder.js +4 -4
  5. package/dist/cli/sync-command.js +72 -36
  6. package/dist/cli/sync-report-builder.d.ts +2 -6
  7. package/dist/cli/types.d.ts +2 -14
  8. package/dist/cli/types.js +1 -9
  9. package/dist/config/file-reference-resolver.js +13 -23
  10. package/dist/config/formatter.d.ts +0 -6
  11. package/dist/config/formatter.js +0 -9
  12. package/dist/config/index.d.ts +1 -2
  13. package/dist/config/index.js +0 -2
  14. package/dist/config/loader.d.ts +1 -1
  15. package/dist/config/loader.js +3 -3
  16. package/dist/config/normalizer.d.ts +1 -1
  17. package/dist/config/normalizer.js +44 -57
  18. package/dist/config/validator.d.ts +1 -1
  19. package/dist/config/validator.js +120 -121
  20. package/dist/config/validators/file-validator.d.ts +2 -4
  21. package/dist/config/validators/file-validator.js +3 -7
  22. package/dist/config/validators/repo-settings-validator.js +1 -1
  23. package/dist/config/validators/ruleset-validator.js +28 -12
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.js +0 -1
  26. package/dist/lifecycle/ado-migration-source.d.ts +2 -1
  27. package/dist/lifecycle/ado-migration-source.js +7 -5
  28. package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -3
  29. package/dist/lifecycle/github-lifecycle-provider.js +29 -19
  30. package/dist/lifecycle/lifecycle-formatter.js +2 -1
  31. package/dist/lifecycle/lifecycle-helpers.d.ts +5 -1
  32. package/dist/lifecycle/lifecycle-helpers.js +4 -4
  33. package/dist/lifecycle/repo-lifecycle-factory.d.ts +4 -4
  34. package/dist/lifecycle/repo-lifecycle-factory.js +12 -9
  35. package/dist/lifecycle/repo-lifecycle-manager.d.ts +4 -1
  36. package/dist/lifecycle/repo-lifecycle-manager.js +11 -7
  37. package/dist/lifecycle/types.d.ts +0 -15
  38. package/dist/output/github-summary.d.ts +6 -5
  39. package/dist/output/github-summary.js +36 -52
  40. package/dist/output/index.d.ts +2 -2
  41. package/dist/output/index.js +1 -1
  42. package/dist/output/lifecycle-report.d.ts +2 -12
  43. package/dist/output/lifecycle-report.js +18 -35
  44. package/dist/output/settings-report.d.ts +4 -4
  45. package/dist/output/settings-report.js +6 -6
  46. package/dist/output/sync-report.d.ts +4 -6
  47. package/dist/output/sync-report.js +2 -2
  48. package/dist/output/unified-summary.d.ts +1 -0
  49. package/dist/output/unified-summary.js +8 -8
  50. package/dist/settings/base-processor.d.ts +1 -1
  51. package/dist/settings/base-processor.js +1 -1
  52. package/dist/settings/index.d.ts +3 -3
  53. package/dist/settings/index.js +3 -3
  54. package/dist/settings/labels/diff.js +3 -2
  55. package/dist/settings/labels/formatter.js +3 -3
  56. package/dist/settings/labels/github-labels-strategy.d.ts +2 -23
  57. package/dist/settings/labels/github-labels-strategy.js +8 -28
  58. package/dist/settings/labels/index.d.ts +1 -0
  59. package/dist/settings/labels/index.js +2 -0
  60. package/dist/settings/labels/processor.d.ts +2 -2
  61. package/dist/settings/labels/processor.js +3 -4
  62. package/dist/settings/labels/types.d.ts +0 -3
  63. package/dist/settings/repo-settings/diff.d.ts +1 -1
  64. package/dist/settings/repo-settings/diff.js +2 -2
  65. package/dist/settings/repo-settings/formatter.d.ts +1 -1
  66. package/dist/settings/repo-settings/formatter.js +4 -4
  67. package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +2 -7
  68. package/dist/settings/repo-settings/github-repo-settings-strategy.js +9 -17
  69. package/dist/settings/repo-settings/index.d.ts +1 -0
  70. package/dist/settings/repo-settings/index.js +2 -0
  71. package/dist/settings/repo-settings/processor.d.ts +2 -2
  72. package/dist/settings/repo-settings/processor.js +5 -6
  73. package/dist/settings/repo-settings/types.d.ts +9 -13
  74. package/dist/settings/repo-settings/types.js +1 -14
  75. package/dist/settings/rulesets/diff-algorithm.d.ts +0 -1
  76. package/dist/settings/rulesets/diff-algorithm.js +6 -8
  77. package/dist/settings/rulesets/formatter.js +15 -51
  78. package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -20
  79. package/dist/settings/rulesets/github-ruleset-strategy.js +6 -30
  80. package/dist/settings/rulesets/index.d.ts +2 -1
  81. package/dist/settings/rulesets/index.js +3 -1
  82. package/dist/settings/rulesets/processor.d.ts +2 -2
  83. package/dist/settings/rulesets/processor.js +3 -4
  84. package/dist/{vcs → shared}/branch-utils.js +5 -4
  85. package/dist/shared/command-executor.d.ts +2 -1
  86. package/dist/shared/command-executor.js +9 -5
  87. package/dist/shared/env.d.ts +6 -6
  88. package/dist/shared/env.js +10 -17
  89. package/dist/shared/errors.d.ts +26 -0
  90. package/dist/shared/errors.js +34 -0
  91. package/dist/shared/gh-api-utils.d.ts +21 -14
  92. package/dist/shared/gh-api-utils.js +33 -22
  93. package/dist/shared/index.d.ts +9 -2
  94. package/dist/shared/index.js +16 -2
  95. package/dist/shared/logger.d.ts +24 -1
  96. package/dist/shared/logger.js +8 -3
  97. package/dist/shared/repo-detector.js +9 -11
  98. package/dist/shared/retry-utils.d.ts +5 -7
  99. package/dist/shared/retry-utils.js +3 -10
  100. package/dist/shared/shell-utils.d.ts +0 -3
  101. package/dist/shared/shell-utils.js +2 -4
  102. package/dist/shared/type-guards.d.ts +2 -9
  103. package/dist/shared/type-guards.js +0 -6
  104. package/dist/shared/xfg-template.d.ts +2 -2
  105. package/dist/shared/xfg-template.js +2 -1
  106. package/dist/sync/auth-options-builder.d.ts +3 -2
  107. package/dist/sync/auth-options-builder.js +14 -10
  108. package/dist/sync/branch-manager.d.ts +12 -7
  109. package/dist/sync/branch-manager.js +4 -7
  110. package/dist/sync/commit-message.d.ts +1 -1
  111. package/dist/sync/commit-push-manager.d.ts +8 -2
  112. package/dist/sync/commit-push-manager.js +6 -5
  113. package/dist/sync/file-sync-orchestrator.js +17 -21
  114. package/dist/sync/file-writer.js +3 -5
  115. package/dist/sync/index.d.ts +1 -1
  116. package/dist/sync/manifest-manager.d.ts +1 -0
  117. package/dist/sync/manifest.d.ts +4 -7
  118. package/dist/sync/manifest.js +42 -45
  119. package/dist/sync/repository-processor.d.ts +5 -2
  120. package/dist/sync/repository-processor.js +11 -17
  121. package/dist/sync/repository-session.js +2 -1
  122. package/dist/sync/sync-workflow.d.ts +2 -2
  123. package/dist/sync/sync-workflow.js +16 -23
  124. package/dist/sync/types.d.ts +20 -25
  125. package/dist/vcs/authenticated-git-ops.d.ts +3 -4
  126. package/dist/vcs/authenticated-git-ops.js +5 -1
  127. package/dist/vcs/azure-pr-strategy.d.ts +6 -1
  128. package/dist/vcs/azure-pr-strategy.js +38 -31
  129. package/dist/vcs/commit-strategy-selector.d.ts +10 -19
  130. package/dist/vcs/commit-strategy-selector.js +8 -24
  131. package/dist/vcs/git-commit-strategy.d.ts +1 -1
  132. package/dist/vcs/git-commit-strategy.js +1 -3
  133. package/dist/vcs/git-ops.d.ts +4 -8
  134. package/dist/vcs/git-ops.js +18 -22
  135. package/dist/vcs/github-app-token-manager.js +9 -8
  136. package/dist/vcs/github-pr-strategy.js +18 -11
  137. package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
  138. package/dist/vcs/gitlab-pr-strategy.js +14 -7
  139. package/dist/vcs/graphql-commit-strategy.d.ts +1 -7
  140. package/dist/vcs/graphql-commit-strategy.js +24 -32
  141. package/dist/vcs/index.d.ts +2 -1
  142. package/dist/vcs/pr-creator.d.ts +6 -9
  143. package/dist/vcs/pr-strategy-factory.d.ts +1 -1
  144. package/dist/vcs/pr-strategy-factory.js +2 -1
  145. package/dist/vcs/pr-strategy.d.ts +1 -1
  146. package/dist/vcs/pr-strategy.js +2 -3
  147. package/dist/vcs/types.d.ts +6 -10
  148. package/package.json +2 -2
  149. package/dist/config/errors.d.ts +0 -9
  150. package/dist/config/errors.js +0 -11
  151. /package/dist/{vcs → shared}/branch-utils.d.ts +0 -0
@@ -37,23 +37,23 @@ function formatLifecycleSummary(totals) {
37
37
  export function hasLifecycleChanges(report) {
38
38
  return report.actions.some((a) => a.action !== "existed");
39
39
  }
40
- export function formatLifecycleReportCLI(report) {
41
- if (!hasLifecycleChanges(report)) {
42
- return [];
43
- }
40
+ /**
41
+ * Render action diff lines from lifecycle actions (shared between CLI and Markdown).
42
+ */
43
+ function renderActionDiffLines(actions) {
44
44
  const lines = [];
45
- for (const action of report.actions) {
45
+ for (const action of actions) {
46
46
  if (action.action === "existed")
47
47
  continue;
48
48
  switch (action.action) {
49
49
  case "created":
50
- lines.push(chalk.green(`+ CREATE ${action.repoName}`));
50
+ lines.push(`+ CREATE ${action.repoName}`);
51
51
  break;
52
52
  case "forked":
53
- lines.push(chalk.green(`+ FORK ${action.upstream ?? "upstream"} -> ${action.repoName}`));
53
+ lines.push(`+ FORK ${action.upstream ?? "upstream"} -> ${action.repoName}`);
54
54
  break;
55
55
  case "migrated":
56
- lines.push(chalk.green(`+ MIGRATE ${action.source ?? "source"} -> ${action.repoName}`));
56
+ lines.push(`+ MIGRATE ${action.source ?? "source"} -> ${action.repoName}`);
57
57
  break;
58
58
  }
59
59
  if (action.settings) {
@@ -65,8 +65,14 @@ export function formatLifecycleReportCLI(report) {
65
65
  }
66
66
  }
67
67
  }
68
+ return lines;
69
+ }
70
+ export function formatLifecycleReportCLI(report) {
71
+ if (!hasLifecycleChanges(report)) {
72
+ return [];
73
+ }
74
+ const lines = renderActionDiffLines(report.actions).map((line) => line.startsWith("+") ? chalk.green(line) : line);
68
75
  lines.push("");
69
- // Summary
70
76
  lines.push(formatLifecycleSummary(report.totals));
71
77
  return lines;
72
78
  }
@@ -86,30 +92,7 @@ export function formatLifecycleReportMarkdown(report, dryRun) {
86
92
  lines.push("");
87
93
  }
88
94
  // Diff block
89
- const diffLines = [];
90
- for (const action of report.actions) {
91
- if (action.action === "existed")
92
- continue;
93
- switch (action.action) {
94
- case "created":
95
- diffLines.push(`+ CREATE ${action.repoName}`);
96
- break;
97
- case "forked":
98
- diffLines.push(`+ FORK ${action.upstream ?? "upstream"} -> ${action.repoName}`);
99
- break;
100
- case "migrated":
101
- diffLines.push(`+ MIGRATE ${action.source ?? "source"} -> ${action.repoName}`);
102
- break;
103
- }
104
- if (action.settings) {
105
- if (action.settings.visibility) {
106
- diffLines.push(` visibility: ${action.settings.visibility}`);
107
- }
108
- if (action.settings.description) {
109
- diffLines.push(` description: "${action.settings.description}"`);
110
- }
111
- }
112
- }
95
+ const diffLines = renderActionDiffLines(report.actions);
113
96
  if (diffLines.length > 0) {
114
97
  lines.push("```diff");
115
98
  lines.push(...diffLines);
@@ -120,9 +103,9 @@ export function formatLifecycleReportMarkdown(report, dryRun) {
120
103
  lines.push(`**${formatLifecycleSummary(report.totals)}**`);
121
104
  return lines.join("\n");
122
105
  }
123
- export function writeLifecycleReportSummary(report, dryRun) {
106
+ export function writeLifecycleReportSummary(report, dryRun, summaryPath) {
124
107
  const markdown = formatLifecycleReportMarkdown(report, dryRun);
125
108
  if (!markdown)
126
109
  return;
127
- writeGitHubStepSummary(markdown);
110
+ writeGitHubStepSummary(markdown, summaryPath);
128
111
  }
@@ -4,8 +4,8 @@ export interface SettingsReport {
4
4
  repos: RepoChanges[];
5
5
  totals: {
6
6
  settings: {
7
- add: number;
8
- change: number;
7
+ create: number;
8
+ update: number;
9
9
  };
10
10
  rulesets: {
11
11
  create: number;
@@ -28,7 +28,7 @@ export interface RepoChanges {
28
28
  }
29
29
  export interface SettingChange {
30
30
  name: string;
31
- action: "add" | "change";
31
+ action: "create" | "update";
32
32
  oldValue?: unknown;
33
33
  newValue: unknown;
34
34
  }
@@ -64,4 +64,4 @@ export declare function formatSettingsReportCLI(report: SettingsReport): string[
64
64
  */
65
65
  export declare function renderRepoSettingsDiffLines(repo: RepoChanges, diffLines: string[]): void;
66
66
  export declare function formatSettingsReportMarkdown(report: SettingsReport, dryRun: boolean): string;
67
- export declare function writeSettingsReportSummary(report: SettingsReport, dryRun: boolean): void;
67
+ export declare function writeSettingsReportSummary(report: SettingsReport, dryRun: boolean, summaryPath: string | undefined): void;
@@ -74,8 +74,8 @@ export function formatCountEntry(noun, pluralNoun, counts) {
74
74
  function formatSettingsSummary(totals) {
75
75
  const parts = [];
76
76
  const settingsEntry = formatCountEntry("setting", "settings", [
77
- { label: "to add", value: totals.settings.add },
78
- { label: "to change", value: totals.settings.change },
77
+ { label: "to create", value: totals.settings.create },
78
+ { label: "to update", value: totals.settings.update },
79
79
  ]);
80
80
  if (settingsEntry)
81
81
  parts.push(settingsEntry);
@@ -115,7 +115,7 @@ export function formatSettingsReportCLI(report) {
115
115
  if (setting.oldValue === undefined && setting.newValue === undefined) {
116
116
  continue;
117
117
  }
118
- if (setting.action === "add") {
118
+ if (setting.action === "create") {
119
119
  lines.push(chalk.green(` + ${setting.name}: ${formatValuePlain(setting.newValue)}`));
120
120
  }
121
121
  else {
@@ -211,7 +211,7 @@ export function renderRepoSettingsDiffLines(repo, diffLines) {
211
211
  if (setting.oldValue === undefined && setting.newValue === undefined) {
212
212
  continue;
213
213
  }
214
- if (setting.action === "add") {
214
+ if (setting.action === "create") {
215
215
  diffLines.push(`+ ${setting.name}: ${formatValuePlain(setting.newValue)}`);
216
216
  }
217
217
  else {
@@ -318,7 +318,7 @@ export function formatSettingsReportMarkdown(report, dryRun) {
318
318
  lines.push(`**${formatSettingsSummary(report.totals)}**`);
319
319
  return lines.join("\n");
320
320
  }
321
- export function writeSettingsReportSummary(report, dryRun) {
321
+ export function writeSettingsReportSummary(report, dryRun, summaryPath) {
322
322
  const markdown = formatSettingsReportMarkdown(report, dryRun);
323
- writeGitHubStepSummary(markdown);
323
+ writeGitHubStepSummary(markdown, summaryPath);
324
324
  }
@@ -1,3 +1,5 @@
1
+ import type { FileChangeDetail } from "../sync/types.js";
2
+ export type ReportFileChange = FileChangeDetail;
1
3
  export interface SyncReport {
2
4
  repos: RepoFileChanges[];
3
5
  totals: {
@@ -10,15 +12,11 @@ export interface SyncReport {
10
12
  }
11
13
  export interface RepoFileChanges {
12
14
  repoName: string;
13
- files: FileChange[];
15
+ files: ReportFileChange[];
14
16
  prUrl?: string;
15
17
  mergeOutcome?: "manual" | "auto" | "force" | "direct";
16
18
  error?: string;
17
19
  }
18
- export interface FileChange {
19
- path: string;
20
- action: "create" | "update" | "delete";
21
- }
22
20
  export declare function formatSyncReportCLI(report: SyncReport): string[];
23
21
  export declare function formatSyncReportMarkdown(report: SyncReport, dryRun: boolean): string;
24
- export declare function writeSyncReportSummary(report: SyncReport, dryRun: boolean): void;
22
+ export declare function writeSyncReportSummary(report: SyncReport, dryRun: boolean, summaryPath: string | undefined): void;
@@ -90,7 +90,7 @@ export function formatSyncReportMarkdown(report, dryRun) {
90
90
  lines.push(`**${formatSyncSummary(report.totals)}**`);
91
91
  return lines.join("\n");
92
92
  }
93
- export function writeSyncReportSummary(report, dryRun) {
93
+ export function writeSyncReportSummary(report, dryRun, summaryPath) {
94
94
  const markdown = formatSyncReportMarkdown(report, dryRun);
95
- writeGitHubStepSummary(markdown);
95
+ writeGitHubStepSummary(markdown, summaryPath);
96
96
  }
@@ -6,6 +6,7 @@ interface UnifiedSummaryInput {
6
6
  sync?: SyncReport;
7
7
  settings?: SettingsReport;
8
8
  dryRun: boolean;
9
+ summaryPath?: string | undefined;
9
10
  }
10
11
  export declare function formatUnifiedSummaryMarkdown(input: UnifiedSummaryInput): string;
11
12
  export declare function writeUnifiedSummary(input: UnifiedSummaryInput): void;
@@ -42,10 +42,13 @@ function formatCombinedSummary(input) {
42
42
  if (input.settings) {
43
43
  const t = input.settings.totals;
44
44
  const settingsEntry = formatCountEntry("setting", "settings", [
45
- { label: selectLabel(dry, "added", "to add"), value: t.settings.add },
46
45
  {
47
- label: selectLabel(dry, "changed", "to change"),
48
- value: t.settings.change,
46
+ label: selectLabel(dry, "created", "to create"),
47
+ value: t.settings.create,
48
+ },
49
+ {
50
+ label: selectLabel(dry, "updated", "to update"),
51
+ value: t.settings.update,
49
52
  },
50
53
  ]);
51
54
  if (settingsEntry)
@@ -143,9 +146,6 @@ function renderSyncLines(syncRepo, diffLines) {
143
146
  diffLines.push(`- Error: ${syncRepo.error}`);
144
147
  }
145
148
  }
146
- function renderSettingsLines(settingsRepo, diffLines) {
147
- renderRepoSettingsDiffLines(settingsRepo, diffLines);
148
- }
149
149
  // =============================================================================
150
150
  // Markdown Formatter
151
151
  // =============================================================================
@@ -201,7 +201,7 @@ export function formatUnifiedSummaryMarkdown(input) {
201
201
  if (syncRepo)
202
202
  renderSyncLines(syncRepo, diffLines);
203
203
  if (settingsRepo)
204
- renderSettingsLines(settingsRepo, diffLines);
204
+ renderRepoSettingsDiffLines(settingsRepo, diffLines);
205
205
  }
206
206
  if (diffLines.length > 0) {
207
207
  lines.push("```diff");
@@ -220,5 +220,5 @@ export function writeUnifiedSummary(input) {
220
220
  const markdown = formatUnifiedSummaryMarkdown(input);
221
221
  if (!markdown)
222
222
  return;
223
- writeGitHubStepSummary(markdown);
223
+ writeGitHubStepSummary(markdown, input.summaryPath);
224
224
  }
@@ -26,7 +26,7 @@ export interface ISettingsProcessor<TOptions extends BaseProcessorOptions = Base
26
26
  interface SettingsGuards<TOptions extends BaseProcessorOptions, TResult extends BaseProcessorResult> {
27
27
  hasDesiredSettings(repoConfig: RepoConfig): boolean;
28
28
  emptySettingsMessage: string;
29
- processSettings(githubRepo: GitHubRepoInfo, repoConfig: RepoConfig, options: TOptions, effectiveToken: string | undefined, repoName: string): Promise<TResult>;
29
+ applySettings(githubRepo: GitHubRepoInfo, repoConfig: RepoConfig, options: TOptions, effectiveToken: string | undefined, repoName: string): Promise<TResult>;
30
30
  }
31
31
  /**
32
32
  * Common boilerplate for GitHub settings processors: GitHub-only gating,
@@ -23,7 +23,7 @@ export async function withGitHubGuards(repoConfig, repoInfo, options, guards) {
23
23
  };
24
24
  }
25
25
  try {
26
- return await guards.processSettings(repoInfo, repoConfig, options, options.token, repoName);
26
+ return await guards.applySettings(repoInfo, repoConfig, options, options.token, repoName);
27
27
  }
28
28
  catch (error) {
29
29
  const message = toErrorMessage(error);
@@ -1,4 +1,4 @@
1
1
  export { type ISettingsProcessor } from "./base-processor.js";
2
- export { type PropertyDiff, formatPropertyTree, type RulesetPlanEntry, RulesetProcessor, type IRulesetProcessor, } from "./rulesets/index.js";
3
- export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsPlanEntry, } from "./repo-settings/index.js";
4
- export { type LabelsPlanEntry, LabelsProcessor, type ILabelsProcessor, } from "./labels/index.js";
2
+ export { type PropertyDiff, formatPropertyTree, type RulesetPlanEntry, RulesetProcessor, type IRulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
3
+ export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsPlanEntry, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
4
+ export { type LabelsPlanEntry, LabelsProcessor, type ILabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
@@ -1,6 +1,6 @@
1
1
  // Rulesets
2
- export { formatPropertyTree, RulesetProcessor, } from "./rulesets/index.js";
2
+ export { formatPropertyTree, RulesetProcessor, GitHubRulesetStrategy, } from "./rulesets/index.js";
3
3
  // Repo settings
4
- export { RepoSettingsProcessor, } from "./repo-settings/index.js";
4
+ export { RepoSettingsProcessor, GitHubRepoSettingsStrategy, } from "./repo-settings/index.js";
5
5
  // Labels
6
- export { LabelsProcessor, } from "./labels/index.js";
6
+ export { LabelsProcessor, GitHubLabelsStrategy, } from "./labels/index.js";
@@ -1,4 +1,5 @@
1
1
  import { normalizeColor } from "./converter.js";
2
+ import { ValidationError } from "../../shared/errors.js";
2
3
  /**
3
4
  * Compares current labels (from GitHub) with desired labels (from config).
4
5
  *
@@ -29,7 +30,7 @@ export function diffLabels(current, desired, deleteOrphaned, noDelete) {
29
30
  if (label.new_name) {
30
31
  const targetLower = label.new_name.toLowerCase();
31
32
  if (renameTargets.has(targetLower)) {
32
- throw new Error(`Rename collision: both '${renameTargets.get(targetLower)}' and '${name}' rename to '${label.new_name}'`);
33
+ throw new ValidationError(`Rename collision: both '${renameTargets.get(targetLower)}' and '${name}' rename to '${label.new_name}'`);
33
34
  }
34
35
  renameTargets.set(targetLower, name);
35
36
  }
@@ -64,7 +65,7 @@ export function diffLabels(current, desired, deleteOrphaned, noDelete) {
64
65
  !deletedNames.has(targetLower)) {
65
66
  const collidingDesired = Object.entries(desired).find(([n]) => n.toLowerCase() === targetLower);
66
67
  if (!collidingDesired || !collidingDesired[1].new_name) {
67
- throw new Error(`Rename collision: '${name}' would rename to '${label.new_name}', but that label already exists`);
68
+ throw new ValidationError(`Rename collision: '${name}' would rename to '${label.new_name}', but that label already exists`);
68
69
  }
69
70
  }
70
71
  }
@@ -8,11 +8,11 @@ export function formatLabelsPlan(changes) {
8
8
  const createChanges = changes.filter((c) => c.action === "create");
9
9
  const updateChanges = changes.filter((c) => c.action === "update");
10
10
  const deleteChanges = changes.filter((c) => c.action === "delete");
11
- const unchangedChanges = changes.filter((c) => c.action === "unchanged");
11
+ const unchangedItems = changes.filter((c) => c.action === "unchanged");
12
12
  const creates = createChanges.length;
13
13
  const updates = updateChanges.length;
14
14
  const deletes = deleteChanges.length;
15
- const unchanged = unchangedChanges.length;
15
+ const unchanged = unchangedItems.length;
16
16
  // Format creates
17
17
  if (createChanges.length > 0) {
18
18
  lines.push(chalk.bold(" Create:"));
@@ -73,7 +73,7 @@ export function formatLabelsPlan(changes) {
73
73
  lines.push("");
74
74
  }
75
75
  // Unchanged (entries only, no output lines)
76
- for (const change of unchangedChanges) {
76
+ for (const change of unchangedItems) {
77
77
  entries.push({ name: change.name, action: "unchanged" });
78
78
  }
79
79
  // Summary line
@@ -4,43 +4,22 @@ import { type GhApiOptions } from "../../shared/gh-api-utils.js";
4
4
  import type { ILabelsStrategy, GitHubLabel } from "./types.js";
5
5
  interface GitHubLabelsStrategyOptions {
6
6
  retries?: number;
7
+ cwd: string;
7
8
  }
8
- /**
9
- * GitHub Labels Strategy for managing repository labels via GitHub REST API.
10
- * Uses `gh api` CLI for authentication and API calls.
11
- *
12
- * Note: Uses ICommandExecutor (the project's safe executor pattern) with
13
- * escapeShellArg for input sanitization, matching the rulesets strategy pattern.
14
- */
15
9
  export declare class GitHubLabelsStrategy implements ILabelsStrategy {
16
10
  private api;
17
- constructor(executor?: ICommandExecutor, options?: GitHubLabelsStrategyOptions);
18
- /**
19
- * Lists all labels for a repository.
20
- * Uses --paginate to retrieve all labels.
21
- */
11
+ constructor(executor: ICommandExecutor, options: GitHubLabelsStrategyOptions);
22
12
  list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubLabel[]>;
23
- /**
24
- * Creates a new label.
25
- */
26
13
  create(repoInfo: RepoInfo, label: {
27
14
  name: string;
28
15
  color: string;
29
16
  description?: string;
30
17
  }, options?: GhApiOptions): Promise<void>;
31
- /**
32
- * Updates an existing label.
33
- * Uses encodeURIComponent for label name in URL path.
34
- */
35
18
  update(repoInfo: RepoInfo, currentName: string, label: {
36
19
  new_name?: string;
37
20
  color?: string;
38
21
  description?: string;
39
22
  }, options?: GhApiOptions): Promise<void>;
40
- /**
41
- * Deletes a label.
42
- * Uses encodeURIComponent for label name in URL path.
43
- */
44
23
  delete(repoInfo: RepoInfo, name: string, options?: GhApiOptions): Promise<void>;
45
24
  }
46
25
  export {};
@@ -1,52 +1,32 @@
1
- import { defaultExecutor, } from "../../shared/command-executor.js";
2
1
  import { assertGitHubRepo } from "../../shared/repo-detector.js";
3
2
  import { GhApiClient, parseApiJson, } from "../../shared/gh-api-utils.js";
4
- /**
5
- * GitHub Labels Strategy for managing repository labels via GitHub REST API.
6
- * Uses `gh api` CLI for authentication and API calls.
7
- *
8
- * Note: Uses ICommandExecutor (the project's safe executor pattern) with
9
- * escapeShellArg for input sanitization, matching the rulesets strategy pattern.
10
- */
11
3
  export class GitHubLabelsStrategy {
12
4
  api;
13
5
  constructor(executor, options) {
14
- this.api = new GhApiClient(executor ?? defaultExecutor, options?.retries ?? 3);
6
+ this.api = new GhApiClient(executor, options.retries ?? 3, options.cwd);
15
7
  }
16
- /**
17
- * Lists all labels for a repository.
18
- * Uses --paginate to retrieve all labels.
19
- */
20
8
  async list(repoInfo, options) {
21
9
  assertGitHubRepo(repoInfo, "GitHub Labels strategy");
22
10
  const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/labels`;
23
- const result = await this.api.call("GET", endpoint, undefined, options, true);
11
+ const result = await this.api.call("GET", endpoint, {
12
+ options,
13
+ paginate: true,
14
+ });
24
15
  return parseApiJson(result, "labels response");
25
16
  }
26
- /**
27
- * Creates a new label.
28
- */
29
17
  async create(repoInfo, label, options) {
30
18
  assertGitHubRepo(repoInfo, "GitHub Labels strategy");
31
19
  const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/labels`;
32
- await this.api.call("POST", endpoint, label, options);
20
+ await this.api.call("POST", endpoint, { payload: label, options });
33
21
  }
34
- /**
35
- * Updates an existing label.
36
- * Uses encodeURIComponent for label name in URL path.
37
- */
38
22
  async update(repoInfo, currentName, label, options) {
39
23
  assertGitHubRepo(repoInfo, "GitHub Labels strategy");
40
24
  const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/labels/${encodeURIComponent(currentName)}`;
41
- await this.api.call("PATCH", endpoint, label, options);
25
+ await this.api.call("PATCH", endpoint, { payload: label, options });
42
26
  }
43
- /**
44
- * Deletes a label.
45
- * Uses encodeURIComponent for label name in URL path.
46
- */
47
27
  async delete(repoInfo, name, options) {
48
28
  assertGitHubRepo(repoInfo, "GitHub Labels strategy");
49
29
  const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/labels/${encodeURIComponent(name)}`;
50
- await this.api.call("DELETE", endpoint, undefined, options);
30
+ await this.api.call("DELETE", endpoint, { options });
51
31
  }
52
32
  }
@@ -1,2 +1,3 @@
1
1
  export { type LabelsPlanEntry } from "./formatter.js";
2
2
  export { LabelsProcessor, type ILabelsProcessor } from "./processor.js";
3
+ export { GitHubLabelsStrategy } from "./github-labels-strategy.js";
@@ -1,2 +1,4 @@
1
1
  // Processor
2
2
  export { LabelsProcessor } from "./processor.js";
3
+ // Strategy
4
+ export { GitHubLabelsStrategy } from "./github-labels-strategy.js";
@@ -17,7 +17,7 @@ export interface LabelsProcessorResult extends BaseProcessorResult {
17
17
  */
18
18
  export declare class LabelsProcessor implements ILabelsProcessor {
19
19
  private readonly strategy;
20
- constructor(strategy?: ILabelsStrategy);
20
+ constructor(strategy: ILabelsStrategy);
21
21
  process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: LabelsProcessorOptions): Promise<LabelsProcessorResult>;
22
- private processSettings;
22
+ private applySettings;
23
23
  }
@@ -1,4 +1,3 @@
1
- import { GitHubLabelsStrategy } from "./github-labels-strategy.js";
2
1
  import { diffLabels } from "./diff.js";
3
2
  import { formatLabelsPlan } from "./formatter.js";
4
3
  import { labelConfigToPayload } from "./converter.js";
@@ -10,16 +9,16 @@ import { withGitHubGuards, countActions, buildDryRunResult, buildApplyResult, }
10
9
  export class LabelsProcessor {
11
10
  strategy;
12
11
  constructor(strategy) {
13
- this.strategy = strategy ?? new GitHubLabelsStrategy();
12
+ this.strategy = strategy;
14
13
  }
15
14
  async process(repoConfig, repoInfo, options) {
16
15
  return withGitHubGuards(repoConfig, repoInfo, options, {
17
16
  hasDesiredSettings: (rc) => Object.keys(rc.settings?.labels ?? {}).length > 0,
18
17
  emptySettingsMessage: "No labels configured",
19
- processSettings: (githubRepo, rc, opts, token, repoName) => this.processSettings(githubRepo, rc, opts, token, repoName),
18
+ applySettings: (githubRepo, rc, opts, token, repoName) => this.applySettings(githubRepo, rc, opts, token, repoName),
20
19
  });
21
20
  }
22
- async processSettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
21
+ async applySettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
23
22
  const { dryRun, noDelete } = options;
24
23
  const settings = repoConfig.settings;
25
24
  const desiredLabels = settings?.labels ?? {};
@@ -1,8 +1,5 @@
1
1
  import type { RepoInfo } from "../../shared/repo-detector.js";
2
2
  import type { GhApiOptions } from "../../shared/gh-api-utils.js";
3
- /**
4
- * GitHub label as returned by the API.
5
- */
6
3
  export interface GitHubLabel {
7
4
  id: number;
8
5
  name: string;
@@ -1,6 +1,6 @@
1
1
  import type { GitHubRepoSettings } from "../../config/index.js";
2
2
  import type { CurrentRepoSettings } from "./types.js";
3
- export type RepoSettingsAction = "add" | "change" | "unchanged";
3
+ export type RepoSettingsAction = "create" | "update" | "unchanged";
4
4
  export interface RepoSettingsChange {
5
5
  property: keyof GitHubRepoSettings;
6
6
  action: RepoSettingsAction;
@@ -63,14 +63,14 @@ export function diffRepoSettings(current, desired) {
63
63
  // Property not currently set or unknown
64
64
  changes.push({
65
65
  property,
66
- action: "add",
66
+ action: "create",
67
67
  newValue: desiredValue,
68
68
  });
69
69
  }
70
70
  else if (currentValue !== desiredValue) {
71
71
  changes.push({
72
72
  property,
73
- action: "change",
73
+ action: "update",
74
74
  oldValue: currentValue,
75
75
  newValue: desiredValue,
76
76
  });
@@ -1,7 +1,7 @@
1
1
  import type { RepoSettingsChange } from "./diff.js";
2
2
  export interface RepoSettingsPlanEntry {
3
3
  property: string;
4
- action: "add" | "change";
4
+ action: "create" | "update";
5
5
  oldValue?: unknown;
6
6
  newValue?: unknown;
7
7
  }
@@ -51,21 +51,21 @@ export function formatRepoSettingsPlan(changes) {
51
51
  if (warning) {
52
52
  warnings.push(warning);
53
53
  }
54
- if (change.action === "add") {
54
+ if (change.action === "create") {
55
55
  lines.push(chalk.green(` + ${change.property}: ${formatValue(change.newValue)}`));
56
56
  creates++;
57
57
  entries.push({
58
58
  property: change.property,
59
- action: "add",
59
+ action: "create",
60
60
  newValue: change.newValue,
61
61
  });
62
62
  }
63
- else if (change.action === "change") {
63
+ else if (change.action === "update") {
64
64
  lines.push(chalk.yellow(` ~ ${change.property}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`));
65
65
  updates++;
66
66
  entries.push({
67
67
  property: change.property,
68
- action: "change",
68
+ action: "update",
69
69
  oldValue: change.oldValue,
70
70
  newValue: change.newValue,
71
71
  });
@@ -5,16 +5,11 @@ import type { GitHubRepoSettings } from "../../config/index.js";
5
5
  import type { IRepoSettingsStrategy, CurrentRepoSettings } from "./types.js";
6
6
  interface GitHubRepoSettingsStrategyOptions {
7
7
  retries?: number;
8
+ cwd: string;
8
9
  }
9
- /**
10
- * GitHub Repository Settings Strategy.
11
- * Manages repository settings via GitHub REST API using `gh api` CLI.
12
- * Note: Uses exec via ICommandExecutor for gh CLI integration, consistent
13
- * with other strategies in this codebase. Inputs are escaped via escapeShellArg.
14
- */
15
10
  export declare class GitHubRepoSettingsStrategy implements IRepoSettingsStrategy {
16
11
  private api;
17
- constructor(executor?: ICommandExecutor, options?: GitHubRepoSettingsStrategyOptions);
12
+ constructor(executor: ICommandExecutor, options: GitHubRepoSettingsStrategyOptions);
18
13
  getSettings(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentRepoSettings>;
19
14
  updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: GhApiOptions): Promise<void>;
20
15
  setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;