@aspruyt/xfg 4.0.0 → 4.0.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.
Files changed (173) hide show
  1. package/dist/cli/index.d.ts +1 -2
  2. package/dist/cli/index.js +0 -1
  3. package/dist/cli/program.js +7 -2
  4. package/dist/cli/{settings/results-collector.d.ts → results-collector.d.ts} +1 -1
  5. package/dist/cli/{settings/results-collector.js → results-collector.js} +2 -1
  6. package/dist/cli/settings-report-builder.d.ts +1 -3
  7. package/dist/cli/sync-command.d.ts +2 -24
  8. package/dist/cli/sync-command.js +295 -301
  9. package/dist/cli/types.d.ts +60 -40
  10. package/dist/cli/types.js +1 -12
  11. package/dist/config/errors.d.ts +9 -0
  12. package/dist/config/errors.js +11 -0
  13. package/dist/config/file-reference-resolver.d.ts +2 -1
  14. package/dist/config/file-reference-resolver.js +10 -8
  15. package/dist/config/formatter.d.ts +3 -2
  16. package/dist/config/index.d.ts +4 -6
  17. package/dist/config/index.js +4 -8
  18. package/dist/config/loader.js +4 -2
  19. package/dist/config/merge.d.ts +0 -9
  20. package/dist/config/merge.js +2 -7
  21. package/dist/config/normalizer.d.ts +4 -0
  22. package/dist/config/normalizer.js +61 -110
  23. package/dist/config/types.d.ts +15 -19
  24. package/dist/config/types.js +1 -1
  25. package/dist/config/validator.d.ts +0 -4
  26. package/dist/config/validator.js +286 -363
  27. package/dist/config/validators/file-validator.d.ts +2 -8
  28. package/dist/config/validators/file-validator.js +6 -17
  29. package/dist/config/validators/index.d.ts +3 -3
  30. package/dist/config/validators/index.js +3 -3
  31. package/dist/config/validators/repo-settings-validator.d.ts +0 -6
  32. package/dist/config/validators/repo-settings-validator.js +9 -9
  33. package/dist/config/validators/ruleset-validator.d.ts +0 -14
  34. package/dist/config/validators/ruleset-validator.js +28 -28
  35. package/dist/lifecycle/ado-migration-source.js +2 -1
  36. package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -5
  37. package/dist/lifecycle/github-lifecycle-provider.js +79 -90
  38. package/dist/lifecycle/index.d.ts +2 -6
  39. package/dist/lifecycle/index.js +0 -4
  40. package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
  41. package/dist/lifecycle/lifecycle-formatter.js +4 -0
  42. package/dist/lifecycle/lifecycle-helpers.d.ts +3 -2
  43. package/dist/lifecycle/repo-lifecycle-manager.js +4 -11
  44. package/dist/lifecycle/types.d.ts +0 -8
  45. package/dist/output/github-summary.d.ts +5 -0
  46. package/dist/output/github-summary.js +9 -2
  47. package/dist/output/index.d.ts +2 -2
  48. package/dist/output/index.js +1 -1
  49. package/dist/output/lifecycle-report.js +5 -23
  50. package/dist/output/settings-report.d.ts +14 -3
  51. package/dist/output/settings-report.js +137 -197
  52. package/dist/output/summary-utils.d.ts +1 -1
  53. package/dist/output/summary-utils.js +2 -1
  54. package/dist/output/sync-report.js +5 -8
  55. package/dist/output/unified-summary.d.ts +2 -1
  56. package/dist/output/unified-summary.js +71 -133
  57. package/dist/settings/base-processor.d.ts +67 -0
  58. package/dist/settings/base-processor.js +91 -0
  59. package/dist/settings/index.d.ts +4 -3
  60. package/dist/settings/index.js +3 -3
  61. package/dist/settings/labels/converter.d.ts +2 -1
  62. package/dist/settings/labels/github-labels-strategy.d.ts +9 -18
  63. package/dist/settings/labels/github-labels-strategy.js +17 -73
  64. package/dist/settings/labels/index.d.ts +2 -6
  65. package/dist/settings/labels/index.js +1 -9
  66. package/dist/settings/labels/processor.d.ts +6 -30
  67. package/dist/settings/labels/processor.js +62 -152
  68. package/dist/settings/labels/types.d.ts +5 -8
  69. package/dist/settings/repo-settings/formatter.d.ts +2 -2
  70. package/dist/settings/repo-settings/formatter.js +6 -6
  71. package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +11 -12
  72. package/dist/settings/repo-settings/github-repo-settings-strategy.js +32 -79
  73. package/dist/settings/repo-settings/index.d.ts +2 -5
  74. package/dist/settings/repo-settings/index.js +1 -9
  75. package/dist/settings/repo-settings/processor.d.ts +6 -27
  76. package/dist/settings/repo-settings/processor.js +51 -104
  77. package/dist/settings/repo-settings/types.d.ts +7 -9
  78. package/dist/settings/rulesets/diff-algorithm.d.ts +0 -4
  79. package/dist/settings/rulesets/diff-algorithm.js +1 -10
  80. package/dist/settings/rulesets/diff.d.ts +1 -1
  81. package/dist/settings/rulesets/diff.js +2 -21
  82. package/dist/settings/rulesets/formatter.d.ts +1 -3
  83. package/dist/settings/rulesets/formatter.js +1 -8
  84. package/dist/settings/rulesets/github-ruleset-strategy.d.ts +11 -51
  85. package/dist/settings/rulesets/github-ruleset-strategy.js +24 -85
  86. package/dist/settings/rulesets/index.d.ts +3 -6
  87. package/dist/settings/rulesets/index.js +5 -9
  88. package/dist/settings/rulesets/processor.d.ts +8 -33
  89. package/dist/settings/rulesets/processor.js +58 -151
  90. package/dist/settings/rulesets/types.d.ts +35 -6
  91. package/dist/shared/command-executor.d.ts +2 -22
  92. package/dist/shared/command-executor.js +8 -7
  93. package/dist/shared/env.d.ts +0 -8
  94. package/dist/shared/env.js +14 -70
  95. package/dist/shared/file-status.d.ts +2 -0
  96. package/dist/shared/file-status.js +13 -0
  97. package/dist/shared/gh-api-utils.d.ts +46 -0
  98. package/dist/shared/gh-api-utils.js +107 -0
  99. package/dist/shared/index.d.ts +5 -5
  100. package/dist/shared/index.js +3 -3
  101. package/dist/shared/interpolation-engine.d.ts +31 -0
  102. package/dist/shared/interpolation-engine.js +50 -0
  103. package/dist/shared/logger.d.ts +3 -7
  104. package/dist/shared/logger.js +4 -1
  105. package/dist/shared/repo-detector.d.ts +17 -2
  106. package/dist/shared/repo-detector.js +27 -0
  107. package/dist/shared/retry-utils.d.ts +9 -17
  108. package/dist/shared/retry-utils.js +22 -28
  109. package/dist/shared/sanitize-utils.d.ts +0 -7
  110. package/dist/shared/sanitize-utils.js +0 -7
  111. package/dist/shared/shell-utils.d.ts +1 -0
  112. package/dist/shared/shell-utils.js +3 -0
  113. package/dist/shared/string-utils.d.ts +4 -0
  114. package/dist/shared/string-utils.js +6 -0
  115. package/dist/shared/type-guards.d.ts +17 -0
  116. package/dist/shared/type-guards.js +26 -0
  117. package/dist/shared/workspace-utils.d.ts +0 -4
  118. package/dist/shared/workspace-utils.js +0 -4
  119. package/dist/{sync → shared}/xfg-template.d.ts +3 -2
  120. package/dist/{sync → shared}/xfg-template.js +13 -54
  121. package/dist/sync/auth-options-builder.d.ts +4 -5
  122. package/dist/sync/auth-options-builder.js +15 -26
  123. package/dist/sync/branch-manager.d.ts +5 -0
  124. package/dist/sync/branch-manager.js +12 -10
  125. package/dist/sync/commit-push-manager.d.ts +1 -1
  126. package/dist/sync/commit-push-manager.js +22 -18
  127. package/dist/sync/diff-utils.d.ts +4 -9
  128. package/dist/sync/diff-utils.js +2 -19
  129. package/dist/sync/file-sync-orchestrator.js +9 -8
  130. package/dist/sync/file-writer.d.ts +2 -1
  131. package/dist/sync/file-writer.js +3 -6
  132. package/dist/sync/index.d.ts +2 -15
  133. package/dist/sync/index.js +0 -19
  134. package/dist/sync/manifest-manager.d.ts +4 -0
  135. package/dist/sync/manifest-manager.js +5 -1
  136. package/dist/sync/manifest.d.ts +10 -41
  137. package/dist/sync/manifest.js +11 -56
  138. package/dist/sync/pr-merge-handler.d.ts +2 -6
  139. package/dist/sync/pr-merge-handler.js +6 -3
  140. package/dist/sync/repository-processor.d.ts +1 -2
  141. package/dist/sync/repository-processor.js +20 -12
  142. package/dist/sync/repository-session.js +5 -14
  143. package/dist/sync/sync-workflow.js +31 -38
  144. package/dist/sync/types.d.ts +43 -178
  145. package/dist/vcs/authenticated-git-ops.d.ts +27 -70
  146. package/dist/vcs/authenticated-git-ops.js +70 -96
  147. package/dist/vcs/azure-pr-strategy.d.ts +6 -4
  148. package/dist/vcs/azure-pr-strategy.js +34 -82
  149. package/dist/vcs/branch-utils.d.ts +6 -0
  150. package/dist/vcs/branch-utils.js +29 -0
  151. package/dist/vcs/commit-strategy-selector.d.ts +5 -0
  152. package/dist/vcs/commit-strategy-selector.js +10 -0
  153. package/dist/vcs/git-commit-strategy.js +1 -2
  154. package/dist/vcs/git-ops.d.ts +15 -59
  155. package/dist/vcs/git-ops.js +46 -110
  156. package/dist/vcs/github-app-token-manager.d.ts +0 -6
  157. package/dist/vcs/github-app-token-manager.js +5 -12
  158. package/dist/vcs/github-pr-strategy.d.ts +5 -5
  159. package/dist/vcs/github-pr-strategy.js +44 -122
  160. package/dist/vcs/gitlab-pr-strategy.d.ts +6 -4
  161. package/dist/vcs/gitlab-pr-strategy.js +39 -87
  162. package/dist/vcs/graphql-commit-strategy.d.ts +3 -4
  163. package/dist/vcs/graphql-commit-strategy.js +31 -63
  164. package/dist/vcs/index.d.ts +3 -16
  165. package/dist/vcs/index.js +2 -33
  166. package/dist/vcs/pr-creator.d.ts +9 -9
  167. package/dist/vcs/pr-creator.js +11 -10
  168. package/dist/vcs/pr-strategy-factory.d.ts +5 -0
  169. package/dist/vcs/pr-strategy-factory.js +17 -0
  170. package/dist/vcs/pr-strategy.d.ts +13 -26
  171. package/dist/vcs/pr-strategy.js +20 -25
  172. package/dist/vcs/types.d.ts +87 -21
  173. package/package.json +2 -1
@@ -2,40 +2,19 @@ import type { RepoConfig } from "../../config/index.js";
2
2
  import type { RepoInfo } from "../../shared/repo-detector.js";
3
3
  import type { IRepoSettingsStrategy } from "./types.js";
4
4
  import { RepoSettingsPlanResult } from "./formatter.js";
5
- export interface IRepoSettingsProcessor {
6
- process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
7
- }
8
- export interface RepoSettingsProcessorOptions {
9
- dryRun?: boolean;
10
- token?: string;
11
- }
12
- export interface RepoSettingsProcessorResult {
13
- success: boolean;
14
- repoName: string;
15
- message: string;
16
- skipped?: boolean;
17
- dryRun?: boolean;
18
- changes?: {
19
- adds: number;
20
- changes: number;
21
- };
5
+ import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
6
+ export type IRepoSettingsProcessor = ISettingsProcessor<RepoSettingsProcessorOptions, RepoSettingsProcessorResult>;
7
+ export type RepoSettingsProcessorOptions = BaseProcessorOptions;
8
+ export interface RepoSettingsProcessorResult extends BaseProcessorResult {
9
+ changes?: ChangeCounts;
22
10
  warnings?: string[];
23
11
  planOutput?: RepoSettingsPlanResult;
24
12
  }
25
13
  export declare class RepoSettingsProcessor implements IRepoSettingsProcessor {
26
14
  private readonly strategy;
27
- private readonly tokenManager;
28
15
  constructor(strategy?: IRepoSettingsStrategy);
29
16
  process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
17
+ private processSettings;
30
18
  private applyChanges;
31
- /**
32
- * Validates that desired security settings are compatible with the repo's
33
- * visibility and owner type. Returns error messages for incompatible settings.
34
- */
35
19
  private validateSecuritySettings;
36
- /**
37
- * Resolves a GitHub App installation token for the given repo.
38
- * Returns undefined if no token manager or token resolution fails.
39
- */
40
- private getInstallationToken;
41
20
  }
@@ -1,108 +1,75 @@
1
- import { isGitHubRepo, getRepoDisplayName, } from "../../shared/repo-detector.js";
2
1
  import { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
3
2
  import { diffRepoSettings, hasChanges } from "./diff.js";
4
3
  import { formatRepoSettingsPlan } from "./formatter.js";
5
- import { hasGitHubAppCredentials } from "../../vcs/index.js";
6
- import { GitHubAppTokenManager } from "../../vcs/github-app-token-manager.js";
4
+ import { withGitHubGuards, buildDryRunResult, buildApplyResult, } from "../base-processor.js";
7
5
  export class RepoSettingsProcessor {
8
6
  strategy;
9
- tokenManager;
10
7
  constructor(strategy) {
11
8
  this.strategy = strategy ?? new GitHubRepoSettingsStrategy();
12
- if (hasGitHubAppCredentials()) {
13
- this.tokenManager = new GitHubAppTokenManager(process.env.XFG_GITHUB_APP_ID, process.env.XFG_GITHUB_APP_PRIVATE_KEY);
14
- }
15
- else {
16
- this.tokenManager = null;
17
- }
18
9
  }
19
10
  async process(repoConfig, repoInfo, options) {
20
- const repoName = getRepoDisplayName(repoInfo);
21
- const { dryRun, token } = options;
22
- // Check if this is a GitHub repo
23
- if (!isGitHubRepo(repoInfo)) {
11
+ return withGitHubGuards(repoConfig, repoInfo, options, {
12
+ hasDesiredSettings: (rc) => {
13
+ const s = rc.settings?.repo;
14
+ return !!s && Object.keys(s).length > 0;
15
+ },
16
+ emptySettingsMessage: "No repo settings configured",
17
+ processSettings: (githubRepo, rc, opts, token, repoName) => this.processSettings(githubRepo, rc, opts, token, repoName),
18
+ });
19
+ }
20
+ async processSettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
21
+ const { dryRun } = options;
22
+ const desiredSettings = repoConfig.settings.repo;
23
+ const strategyOptions = { token: effectiveToken, host: githubRepo.host };
24
+ // Fetch current settings
25
+ const currentSettings = await this.strategy.getSettings(githubRepo, strategyOptions);
26
+ // Validate security settings compatibility
27
+ const securityErrors = this.validateSecuritySettings(desiredSettings, currentSettings);
28
+ if (securityErrors.length > 0) {
24
29
  return {
25
- success: true,
30
+ success: false,
26
31
  repoName,
27
- message: `Skipped: ${repoName} is not a GitHub repository`,
28
- skipped: true,
32
+ message: `Failed: ${securityErrors.join("; ")}`,
29
33
  };
30
34
  }
31
- const githubRepo = repoInfo;
32
- const desiredSettings = repoConfig.settings?.repo;
33
- // If no repo settings configured, skip
34
- if (!desiredSettings || Object.keys(desiredSettings).length === 0) {
35
+ // Compute diff
36
+ const changes = diffRepoSettings(currentSettings, desiredSettings);
37
+ if (!hasChanges(changes)) {
38
+ const unchangedCount = changes.filter((c) => c.action === "unchanged").length;
35
39
  return {
36
40
  success: true,
37
41
  repoName,
38
- message: "No repo settings configured",
39
- skipped: true,
42
+ message: "No changes needed",
43
+ changes: { create: 0, update: 0, delete: 0, unchanged: unchangedCount },
40
44
  };
41
45
  }
42
- try {
43
- // Resolve App token if available, fall back to provided token
44
- const effectiveToken = token ?? (await this.getInstallationToken(githubRepo));
45
- const strategyOptions = { token: effectiveToken, host: githubRepo.host };
46
- // Fetch current settings
47
- const currentSettings = await this.strategy.getSettings(githubRepo, strategyOptions);
48
- // Validate security settings compatibility
49
- const securityErrors = this.validateSecuritySettings(desiredSettings, currentSettings);
50
- if (securityErrors.length > 0) {
51
- return {
52
- success: false,
53
- repoName,
54
- message: `Failed: ${securityErrors.join("; ")}`,
55
- };
56
- }
57
- // Compute diff
58
- const changes = diffRepoSettings(currentSettings, desiredSettings);
59
- if (!hasChanges(changes)) {
60
- return {
61
- success: true,
62
- repoName,
63
- message: "No changes needed",
64
- changes: { adds: 0, changes: 0 },
65
- };
66
- }
67
- // Format plan output
68
- const planOutput = formatRepoSettingsPlan(changes);
69
- // Dry run mode - report planned changes without applying
70
- if (dryRun) {
71
- return {
72
- success: true,
73
- repoName,
74
- message: `[DRY RUN] ${planOutput.adds} to add, ${planOutput.changes} to change`,
75
- dryRun: true,
76
- changes: { adds: planOutput.adds, changes: planOutput.changes },
77
- warnings: planOutput.warnings,
78
- planOutput,
79
- };
80
- }
81
- // Apply changes - only send settings that actually changed
82
- const changedSettings = changes.reduce((acc, change) => {
83
- if (change.action !== "unchanged") {
84
- acc[change.property] = change.newValue;
85
- }
86
- return acc;
87
- }, {});
88
- await this.applyChanges(githubRepo, changedSettings, strategyOptions);
89
- return {
90
- success: true,
91
- repoName,
92
- message: `Applied: ${planOutput.adds} added, ${planOutput.changes} changed`,
93
- changes: { adds: planOutput.adds, changes: planOutput.changes },
46
+ // Format plan output
47
+ const planOutput = formatRepoSettingsPlan(changes);
48
+ const changeCounts = {
49
+ create: planOutput.creates,
50
+ update: planOutput.updates,
51
+ delete: 0,
52
+ unchanged: changes.filter((c) => c.action === "unchanged").length,
53
+ };
54
+ if (dryRun) {
55
+ return buildDryRunResult(repoName, changeCounts, {
94
56
  warnings: planOutput.warnings,
95
57
  planOutput,
96
- };
97
- }
98
- catch (error) {
99
- const message = error instanceof Error ? error.message : String(error);
100
- return {
101
- success: false,
102
- repoName,
103
- message: `Failed: ${message}`,
104
- };
58
+ });
105
59
  }
60
+ // Apply changes - only send settings that actually changed
61
+ const changedSettings = changes.reduce((acc, change) => {
62
+ if (change.action !== "unchanged") {
63
+ acc[change.property] = change.newValue;
64
+ }
65
+ return acc;
66
+ }, {});
67
+ await this.applyChanges(githubRepo, changedSettings, strategyOptions);
68
+ const appliedCount = Object.keys(changedSettings).length;
69
+ return buildApplyResult(repoName, changeCounts, appliedCount, {
70
+ warnings: planOutput.warnings,
71
+ planOutput,
72
+ });
106
73
  }
107
74
  async applyChanges(repoInfo, settings, options) {
108
75
  // Extract settings that need separate API calls
@@ -126,10 +93,6 @@ export class RepoSettingsProcessor {
126
93
  await this.strategy.setAutomatedSecurityFixes(repoInfo, automatedSecurityFixes, options);
127
94
  }
128
95
  }
129
- /**
130
- * Validates that desired security settings are compatible with the repo's
131
- * visibility and owner type. Returns error messages for incompatible settings.
132
- */
133
96
  validateSecuritySettings(desiredSettings, currentSettings) {
134
97
  const errors = [];
135
98
  const isPublic = currentSettings.visibility === "public";
@@ -155,20 +118,4 @@ export class RepoSettingsProcessor {
155
118
  }
156
119
  return errors;
157
120
  }
158
- /**
159
- * Resolves a GitHub App installation token for the given repo.
160
- * Returns undefined if no token manager or token resolution fails.
161
- */
162
- async getInstallationToken(repoInfo) {
163
- if (!this.tokenManager) {
164
- return undefined;
165
- }
166
- try {
167
- const token = await this.tokenManager.getTokenForRepo(repoInfo);
168
- return token ?? undefined;
169
- }
170
- catch {
171
- return undefined;
172
- }
173
- }
174
121
  }
@@ -1,13 +1,11 @@
1
1
  import type { RepoInfo } from "../../shared/repo-detector.js";
2
2
  import type { GitHubRepoSettings } from "../../config/index.js";
3
- export interface RepoSettingsStrategyOptions {
4
- token?: string;
5
- host?: string;
6
- }
3
+ import type { GhApiOptions } from "../../shared/gh-api-utils.js";
7
4
  /**
8
5
  * Current repository settings from GitHub API (snake_case).
9
6
  */
10
7
  export interface CurrentRepoSettings {
8
+ description?: string;
11
9
  has_issues?: boolean;
12
10
  has_projects?: boolean;
13
11
  has_wiki?: boolean;
@@ -48,23 +46,23 @@ export interface IRepoSettingsStrategy {
48
46
  /**
49
47
  * Gets current repository settings.
50
48
  */
51
- getSettings(repoInfo: RepoInfo, options?: RepoSettingsStrategyOptions): Promise<CurrentRepoSettings>;
49
+ getSettings(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentRepoSettings>;
52
50
  /**
53
51
  * Updates repository settings.
54
52
  */
55
- updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: RepoSettingsStrategyOptions): Promise<void>;
53
+ updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: GhApiOptions): Promise<void>;
56
54
  /**
57
55
  * Enables or disables vulnerability alerts.
58
56
  */
59
- setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
57
+ setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
60
58
  /**
61
59
  * Enables or disables automated security fixes.
62
60
  */
63
- setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
61
+ setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
64
62
  /**
65
63
  * Enables or disables private vulnerability reporting.
66
64
  */
67
- setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
65
+ setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
68
66
  }
69
67
  /**
70
68
  * Type guard to check if an object implements IRepoSettingsStrategy.
@@ -12,7 +12,3 @@ export declare function isArrayOfObjects(arr: unknown[]): boolean;
12
12
  * Recursively compute property-level diffs between two objects.
13
13
  */
14
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[];
@@ -1,7 +1,4 @@
1
1
  // src/settings/rulesets/diff-algorithm.ts
2
- // =============================================================================
3
- // Helpers
4
- // =============================================================================
5
2
  export function isObject(val) {
6
3
  return val !== null && typeof val === "object" && !Array.isArray(val);
7
4
  }
@@ -29,9 +26,6 @@ export function deepEqual(a, b) {
29
26
  export function isArrayOfObjects(arr) {
30
27
  return arr.length > 0 && arr.every((item) => isObject(item));
31
28
  }
32
- // =============================================================================
33
- // Property Diff Algorithm
34
- // =============================================================================
35
29
  /**
36
30
  * Recursively compute property-level diffs between two objects.
37
31
  */
@@ -76,13 +70,10 @@ export function computePropertyDiffs(current, desired, parentPath = []) {
76
70
  }
77
71
  return diffs;
78
72
  }
79
- // =============================================================================
80
- // Array Diffing
81
- // =============================================================================
82
73
  /**
83
74
  * Diff two arrays of objects by matching items on `type` field (or by index).
84
75
  */
85
- export function diffObjectArrays(currentArr, desiredArr, parentPath) {
76
+ function diffObjectArrays(currentArr, desiredArr, parentPath) {
86
77
  const diffs = [];
87
78
  const hasType = desiredArr.every((item) => isObject(item) && "type" in item);
88
79
  if (hasType) {
@@ -1,5 +1,5 @@
1
1
  import { type Ruleset } from "../../config/index.js";
2
- import type { GitHubRuleset } from "./github-ruleset-strategy.js";
2
+ import type { GitHubRuleset } from "./types.js";
3
3
  export type RulesetAction = "create" | "update" | "delete" | "unchanged";
4
4
  export interface RulesetChange {
5
5
  action: RulesetAction;
@@ -1,13 +1,6 @@
1
1
  import { RULESET_COMPARABLE_FIELDS } from "../../config/index.js";
2
- // =============================================================================
3
- // Normalization (for comparison)
4
- // =============================================================================
5
- /**
6
- * Converts camelCase to snake_case for comparison.
7
- */
8
- function camelToSnake(str) {
9
- return str.replace(/([A-Z])/g, "_$1").toLowerCase();
10
- }
2
+ import { isPlainObject } from "../../shared/type-guards.js";
3
+ import { camelToSnake } from "../../shared/string-utils.js";
11
4
  /**
12
5
  * Normalizes a value recursively, converting keys to a consistent format (snake_case).
13
6
  * This allows comparing GitHub API responses (snake_case) with config (camelCase).
@@ -91,9 +84,6 @@ function deepEqual(a, b) {
91
84
  }
92
85
  return false;
93
86
  }
94
- // =============================================================================
95
- // Desired-Side Projection
96
- // =============================================================================
97
87
  /**
98
88
  * Projects `current` onto the shape of `desired`.
99
89
  * Only keeps keys/structure present in `desired`, filtering out API noise.
@@ -116,9 +106,6 @@ export function projectToDesiredShape(current, desired) {
116
106
  // Scalars — return current as-is
117
107
  return current;
118
108
  }
119
- function isPlainObject(val) {
120
- return val !== null && typeof val === "object" && !Array.isArray(val);
121
- }
122
109
  function projectObjects(current, desired) {
123
110
  const result = {};
124
111
  for (const key of Object.keys(desired)) {
@@ -193,9 +180,6 @@ function matchByIndex(current, desired) {
193
180
  }
194
181
  return result;
195
182
  }
196
- // =============================================================================
197
- // Diff Algorithm
198
- // =============================================================================
199
183
  /**
200
184
  * Compares current rulesets (from GitHub) with desired rulesets (from config).
201
185
  *
@@ -265,9 +249,6 @@ export function diffRulesets(current, desired, deleteOrphaned) {
265
249
  };
266
250
  return changes.sort((a, b) => actionOrder[a.action] - actionOrder[b.action]);
267
251
  }
268
- // =============================================================================
269
- // Formatting
270
- // =============================================================================
271
252
  /**
272
253
  * Formats a ruleset change for display.
273
254
  */
@@ -1,8 +1,6 @@
1
1
  import { type RulesetChange, type RulesetAction } from "./diff.js";
2
2
  import type { Ruleset } from "../../config/index.js";
3
- import { type PropertyDiff } from "./index.js";
4
- export type { DiffAction, PropertyDiff } from "./index.js";
5
- export { computePropertyDiffs } from "./index.js";
3
+ import { type PropertyDiff } from "./diff-algorithm.js";
6
4
  export interface RulesetPlanEntry {
7
5
  name: string;
8
6
  action: RulesetAction;
@@ -1,7 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { projectToDesiredShape, normalizeRuleset, } from "./diff.js";
3
- import { computePropertyDiffs, isObject, } from "./index.js";
4
- export { computePropertyDiffs } from "./index.js";
3
+ import { computePropertyDiffs, isObject, } from "./diff-algorithm.js";
5
4
  /**
6
5
  * Build a tree structure from flat property diffs.
7
6
  */
@@ -187,9 +186,6 @@ export function formatPropertyTree(diffs) {
187
186
  const tree = buildTree(diffs);
188
187
  return renderTree(tree);
189
188
  }
190
- // =============================================================================
191
- // Ruleset Plan Formatter
192
- // =============================================================================
193
189
  /**
194
190
  * Format a full ruleset config as tree lines (for create action).
195
191
  */
@@ -253,7 +249,6 @@ export function formatRulesetPlan(changes) {
253
249
  updates = updateChanges.length;
254
250
  deletes = deleteChanges.length;
255
251
  unchanged = unchangedChanges.length;
256
- // Format creates
257
252
  if (createChanges.length > 0) {
258
253
  lines.push(chalk.bold(" Create:"));
259
254
  for (const change of createChanges) {
@@ -273,7 +268,6 @@ export function formatRulesetPlan(changes) {
273
268
  lines.push(""); // Blank line between rulesets
274
269
  }
275
270
  }
276
- // Format updates
277
271
  if (updateChanges.length > 0) {
278
272
  lines.push(chalk.bold(" Update:"));
279
273
  for (const change of updateChanges) {
@@ -303,7 +297,6 @@ export function formatRulesetPlan(changes) {
303
297
  lines.push(""); // Blank line between rulesets
304
298
  }
305
299
  }
306
- // Format deletes
307
300
  if (deleteChanges.length > 0) {
308
301
  lines.push(chalk.bold(" Delete:"));
309
302
  for (const change of deleteChanges) {
@@ -1,41 +1,13 @@
1
1
  import { ICommandExecutor } from "../../shared/command-executor.js";
2
2
  import { RepoInfo } from "../../shared/repo-detector.js";
3
+ import { type GhApiOptions } from "../../shared/gh-api-utils.js";
3
4
  import type { Ruleset } from "../../config/index.js";
4
- import type { IRulesetStrategy } from "./types.js";
5
- /**
6
- * GitHub Ruleset response from API (snake_case).
7
- */
8
- export interface GitHubRuleset {
9
- id: number;
10
- name: string;
11
- target: "branch" | "tag";
12
- enforcement: "active" | "disabled" | "evaluate";
13
- bypass_actors?: GitHubBypassActor[];
14
- conditions?: GitHubRulesetConditions;
15
- rules?: GitHubRule[];
16
- source_type?: string;
17
- source?: string;
18
- }
19
- export interface GitHubBypassActor {
20
- actor_id: number;
21
- actor_type: "Team" | "User" | "Integration";
22
- bypass_mode?: "always" | "pull_request";
23
- }
24
- export interface GitHubRulesetConditions {
25
- ref_name?: {
26
- include?: string[];
27
- exclude?: string[];
28
- };
29
- }
30
- export interface GitHubRule {
31
- type: string;
32
- parameters?: Record<string, unknown>;
33
- }
5
+ import type { IRulesetStrategy, GitHubRuleset, GitHubBypassActor, GitHubRulesetConditions, GitHubRule } from "./types.js";
34
6
  /**
35
7
  * Converts camelCase config ruleset to snake_case GitHub API format.
36
8
  */
37
9
  export declare function configToGitHub(name: string, ruleset: Ruleset): GitHubRulesetPayload;
38
- export interface GitHubRulesetPayload {
10
+ interface GitHubRulesetPayload {
39
11
  name: string;
40
12
  target: "branch" | "tag";
41
13
  enforcement: "active" | "disabled" | "evaluate";
@@ -43,11 +15,7 @@ export interface GitHubRulesetPayload {
43
15
  conditions?: GitHubRulesetConditions;
44
16
  rules?: GitHubRule[];
45
17
  }
46
- export interface RulesetStrategyOptions {
47
- token?: string;
48
- host?: string;
49
- }
50
- export interface GitHubRulesetStrategyOptions {
18
+ interface GitHubRulesetStrategyOptions {
51
19
  retries?: number;
52
20
  }
53
21
  /**
@@ -55,35 +23,27 @@ export interface GitHubRulesetStrategyOptions {
55
23
  * Uses `gh api` CLI for authentication and API calls.
56
24
  */
57
25
  export declare class GitHubRulesetStrategy implements IRulesetStrategy {
58
- private executor;
59
- private retries;
26
+ private api;
60
27
  constructor(executor?: ICommandExecutor, options?: GitHubRulesetStrategyOptions);
61
28
  /**
62
29
  * Lists all rulesets for a repository.
63
30
  */
64
- list(repoInfo: RepoInfo, options?: RulesetStrategyOptions): Promise<GitHubRuleset[]>;
31
+ list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubRuleset[]>;
65
32
  /**
66
33
  * Gets a single ruleset by ID.
67
34
  */
68
- get(repoInfo: RepoInfo, rulesetId: number, options?: RulesetStrategyOptions): Promise<GitHubRuleset>;
35
+ get(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<GitHubRuleset>;
69
36
  /**
70
37
  * Creates a new ruleset.
71
38
  */
72
- create(repoInfo: RepoInfo, name: string, ruleset: Ruleset, options?: RulesetStrategyOptions): Promise<GitHubRuleset>;
39
+ create(repoInfo: RepoInfo, name: string, ruleset: Ruleset, options?: GhApiOptions): Promise<GitHubRuleset>;
73
40
  /**
74
41
  * Updates an existing ruleset.
75
42
  */
76
- update(repoInfo: RepoInfo, rulesetId: number, name: string, ruleset: Ruleset, options?: RulesetStrategyOptions): Promise<GitHubRuleset>;
43
+ update(repoInfo: RepoInfo, rulesetId: number, name: string, ruleset: Ruleset, options?: GhApiOptions): Promise<GitHubRuleset>;
77
44
  /**
78
45
  * Deletes a ruleset.
79
46
  */
80
- delete(repoInfo: RepoInfo, rulesetId: number, options?: RulesetStrategyOptions): Promise<void>;
81
- /**
82
- * Validates that the repo is a GitHub repository.
83
- */
84
- private validateGitHub;
85
- /**
86
- * Executes a GitHub API call using the gh CLI.
87
- */
88
- private ghApi;
47
+ delete(repoInfo: RepoInfo, rulesetId: number, options?: GhApiOptions): Promise<void>;
89
48
  }
49
+ export {};