@aspruyt/xfg 3.13.1 → 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.
- package/README.md +1 -4
- package/dist/cli/index.d.ts +1 -4
- package/dist/cli/index.js +0 -2
- package/dist/cli/program.js +7 -14
- package/dist/cli/{settings/results-collector.d.ts → results-collector.d.ts} +1 -1
- package/dist/cli/{settings/results-collector.js → results-collector.js} +2 -1
- package/dist/cli/settings-report-builder.d.ts +1 -3
- package/dist/cli/sync-command.d.ts +3 -26
- package/dist/cli/sync-command.js +312 -179
- package/dist/cli/types.d.ts +68 -41
- package/dist/cli/types.js +1 -12
- package/dist/config/errors.d.ts +9 -0
- package/dist/config/errors.js +11 -0
- package/dist/config/file-reference-resolver.d.ts +2 -1
- package/dist/config/file-reference-resolver.js +10 -8
- package/dist/config/formatter.d.ts +3 -2
- package/dist/config/index.d.ts +4 -6
- package/dist/config/index.js +4 -8
- package/dist/config/loader.js +4 -2
- package/dist/config/merge.d.ts +0 -9
- package/dist/config/merge.js +2 -7
- package/dist/config/normalizer.d.ts +4 -0
- package/dist/config/normalizer.js +61 -110
- package/dist/config/types.d.ts +15 -19
- package/dist/config/types.js +1 -1
- package/dist/config/validator.d.ts +0 -9
- package/dist/config/validator.js +297 -391
- package/dist/config/validators/file-validator.d.ts +2 -8
- package/dist/config/validators/file-validator.js +6 -17
- package/dist/config/validators/index.d.ts +3 -3
- package/dist/config/validators/index.js +3 -3
- package/dist/config/validators/repo-settings-validator.d.ts +0 -6
- package/dist/config/validators/repo-settings-validator.js +9 -9
- package/dist/config/validators/ruleset-validator.d.ts +0 -14
- package/dist/config/validators/ruleset-validator.js +28 -28
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lifecycle/ado-migration-source.js +2 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +15 -5
- package/dist/lifecycle/github-lifecycle-provider.js +101 -81
- package/dist/lifecycle/index.d.ts +2 -6
- package/dist/lifecycle/index.js +0 -4
- package/dist/lifecycle/lifecycle-formatter.d.ts +2 -1
- package/dist/lifecycle/lifecycle-formatter.js +4 -0
- package/dist/lifecycle/lifecycle-helpers.d.ts +3 -2
- package/dist/lifecycle/repo-lifecycle-manager.js +4 -11
- package/dist/lifecycle/types.d.ts +0 -8
- package/dist/output/github-summary.d.ts +5 -0
- package/dist/output/github-summary.js +9 -2
- package/dist/output/index.d.ts +2 -2
- package/dist/output/index.js +1 -1
- package/dist/output/lifecycle-report.js +5 -23
- package/dist/output/settings-report.d.ts +14 -3
- package/dist/output/settings-report.js +137 -197
- package/dist/output/summary-utils.d.ts +1 -1
- package/dist/output/summary-utils.js +2 -1
- package/dist/output/sync-report.js +5 -8
- package/dist/output/unified-summary.d.ts +2 -1
- package/dist/output/unified-summary.js +71 -133
- package/dist/settings/base-processor.d.ts +67 -0
- package/dist/settings/base-processor.js +91 -0
- package/dist/settings/index.d.ts +4 -3
- package/dist/settings/index.js +3 -3
- package/dist/settings/labels/converter.d.ts +2 -1
- package/dist/settings/labels/diff.d.ts +2 -2
- package/dist/settings/labels/diff.js +15 -19
- package/dist/settings/labels/github-labels-strategy.d.ts +9 -18
- package/dist/settings/labels/github-labels-strategy.js +17 -73
- package/dist/settings/labels/index.d.ts +2 -6
- package/dist/settings/labels/index.js +1 -9
- package/dist/settings/labels/processor.d.ts +6 -40
- package/dist/settings/labels/processor.js +62 -165
- package/dist/settings/labels/types.d.ts +5 -8
- package/dist/settings/repo-settings/formatter.d.ts +2 -2
- package/dist/settings/repo-settings/formatter.js +6 -6
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +11 -12
- package/dist/settings/repo-settings/github-repo-settings-strategy.js +32 -79
- package/dist/settings/repo-settings/index.d.ts +2 -5
- package/dist/settings/repo-settings/index.js +1 -9
- package/dist/settings/repo-settings/processor.d.ts +6 -27
- package/dist/settings/repo-settings/processor.js +51 -104
- package/dist/settings/repo-settings/types.d.ts +7 -9
- package/dist/settings/rulesets/diff-algorithm.d.ts +0 -4
- package/dist/settings/rulesets/diff-algorithm.js +1 -10
- package/dist/settings/rulesets/diff.d.ts +3 -3
- package/dist/settings/rulesets/diff.js +8 -29
- package/dist/settings/rulesets/formatter.d.ts +1 -3
- package/dist/settings/rulesets/formatter.js +1 -8
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +11 -51
- package/dist/settings/rulesets/github-ruleset-strategy.js +24 -85
- package/dist/settings/rulesets/index.d.ts +3 -6
- package/dist/settings/rulesets/index.js +5 -9
- package/dist/settings/rulesets/processor.d.ts +8 -43
- package/dist/settings/rulesets/processor.js +58 -166
- package/dist/settings/rulesets/types.d.ts +35 -6
- package/dist/shared/command-executor.d.ts +2 -22
- package/dist/shared/command-executor.js +8 -7
- package/dist/shared/env.d.ts +0 -8
- package/dist/shared/env.js +14 -70
- package/dist/shared/file-status.d.ts +2 -0
- package/dist/shared/file-status.js +13 -0
- package/dist/shared/gh-api-utils.d.ts +46 -0
- package/dist/shared/gh-api-utils.js +107 -0
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +3 -3
- package/dist/shared/interpolation-engine.d.ts +31 -0
- package/dist/shared/interpolation-engine.js +50 -0
- package/dist/shared/logger.d.ts +3 -7
- package/dist/shared/logger.js +4 -1
- package/dist/shared/repo-detector.d.ts +17 -2
- package/dist/shared/repo-detector.js +27 -0
- package/dist/shared/retry-utils.d.ts +9 -17
- package/dist/shared/retry-utils.js +22 -28
- package/dist/shared/sanitize-utils.d.ts +0 -7
- package/dist/shared/sanitize-utils.js +0 -7
- package/dist/shared/shell-utils.d.ts +1 -0
- package/dist/shared/shell-utils.js +3 -0
- package/dist/shared/string-utils.d.ts +4 -0
- package/dist/shared/string-utils.js +6 -0
- package/dist/shared/type-guards.d.ts +17 -0
- package/dist/shared/type-guards.js +26 -0
- package/dist/shared/workspace-utils.d.ts +0 -4
- package/dist/shared/workspace-utils.js +0 -4
- package/dist/{sync → shared}/xfg-template.d.ts +3 -2
- package/dist/{sync → shared}/xfg-template.js +13 -54
- package/dist/sync/auth-options-builder.d.ts +4 -5
- package/dist/sync/auth-options-builder.js +15 -26
- package/dist/sync/branch-manager.d.ts +5 -0
- package/dist/sync/branch-manager.js +12 -10
- package/dist/sync/commit-push-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.js +22 -18
- package/dist/sync/diff-utils.d.ts +4 -9
- package/dist/sync/diff-utils.js +2 -19
- package/dist/sync/file-sync-orchestrator.js +9 -8
- package/dist/sync/file-writer.d.ts +2 -1
- package/dist/sync/file-writer.js +3 -6
- package/dist/sync/index.d.ts +2 -16
- package/dist/sync/index.js +0 -20
- package/dist/sync/manifest-manager.d.ts +4 -0
- package/dist/sync/manifest-manager.js +5 -1
- package/dist/sync/manifest.d.ts +11 -84
- package/dist/sync/manifest.js +50 -215
- package/dist/sync/pr-merge-handler.d.ts +2 -6
- package/dist/sync/pr-merge-handler.js +6 -3
- package/dist/sync/repository-processor.d.ts +2 -8
- package/dist/sync/repository-processor.js +21 -63
- package/dist/sync/repository-session.js +5 -14
- package/dist/sync/sync-workflow.js +31 -38
- package/dist/sync/types.d.ts +43 -182
- package/dist/vcs/authenticated-git-ops.d.ts +27 -70
- package/dist/vcs/authenticated-git-ops.js +70 -96
- package/dist/vcs/azure-pr-strategy.d.ts +6 -4
- package/dist/vcs/azure-pr-strategy.js +34 -82
- package/dist/vcs/branch-utils.d.ts +6 -0
- package/dist/vcs/branch-utils.js +29 -0
- package/dist/vcs/commit-strategy-selector.d.ts +5 -0
- package/dist/vcs/commit-strategy-selector.js +10 -0
- package/dist/vcs/git-commit-strategy.js +1 -2
- package/dist/vcs/git-ops.d.ts +15 -59
- package/dist/vcs/git-ops.js +46 -110
- package/dist/vcs/github-app-token-manager.d.ts +0 -6
- package/dist/vcs/github-app-token-manager.js +5 -12
- package/dist/vcs/github-pr-strategy.d.ts +5 -5
- package/dist/vcs/github-pr-strategy.js +44 -122
- package/dist/vcs/gitlab-pr-strategy.d.ts +6 -4
- package/dist/vcs/gitlab-pr-strategy.js +39 -87
- package/dist/vcs/graphql-commit-strategy.d.ts +3 -4
- package/dist/vcs/graphql-commit-strategy.js +45 -63
- package/dist/vcs/index.d.ts +3 -16
- package/dist/vcs/index.js +2 -33
- package/dist/vcs/pr-creator.d.ts +9 -9
- package/dist/vcs/pr-creator.js +11 -10
- package/dist/vcs/pr-strategy-factory.d.ts +5 -0
- package/dist/vcs/pr-strategy-factory.js +17 -0
- package/dist/vcs/pr-strategy.d.ts +13 -26
- package/dist/vcs/pr-strategy.js +20 -25
- package/dist/vcs/types.d.ts +87 -21
- package/package.json +2 -1
- package/dist/cli/settings/lifecycle-checks.d.ts +0 -11
- package/dist/cli/settings/lifecycle-checks.js +0 -64
- package/dist/cli/settings/process-labels.d.ts +0 -9
- package/dist/cli/settings/process-labels.js +0 -125
- package/dist/cli/settings/process-repo-settings.d.ts +0 -9
- package/dist/cli/settings/process-repo-settings.js +0 -80
- package/dist/cli/settings/process-rulesets.d.ts +0 -9
- package/dist/cli/settings/process-rulesets.js +0 -118
- package/dist/cli/settings-command.d.ts +0 -11
- package/dist/cli/settings-command.js +0 -90
- package/dist/sync/manifest-strategy.d.ts +0 -21
- package/dist/sync/manifest-strategy.js +0 -67
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { projectToDesiredShape, normalizeRuleset, } from "./diff.js";
|
|
3
|
-
import { computePropertyDiffs, isObject, } from "./
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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 {};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { defaultExecutor, } from "../../shared/command-executor.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
// =============================================================================
|
|
6
|
-
// Conversion Functions
|
|
7
|
-
// =============================================================================
|
|
2
|
+
import { assertGitHubRepo } from "../../shared/repo-detector.js";
|
|
3
|
+
import { camelToSnake } from "../../shared/string-utils.js";
|
|
4
|
+
import { GhApiClient, parseApiJson, } from "../../shared/gh-api-utils.js";
|
|
8
5
|
/**
|
|
9
6
|
* Converts camelCase config ruleset to snake_case GitHub API format.
|
|
10
7
|
*/
|
|
@@ -101,117 +98,59 @@ function convertValue(value) {
|
|
|
101
98
|
}
|
|
102
99
|
return value;
|
|
103
100
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Converts camelCase to snake_case.
|
|
106
|
-
*/
|
|
107
|
-
function camelToSnake(str) {
|
|
108
|
-
return str.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
109
|
-
}
|
|
110
101
|
/**
|
|
111
102
|
* GitHub Ruleset Strategy for managing repository rulesets via GitHub REST API.
|
|
112
103
|
* Uses `gh api` CLI for authentication and API calls.
|
|
113
104
|
*/
|
|
114
105
|
export class GitHubRulesetStrategy {
|
|
115
|
-
|
|
116
|
-
retries;
|
|
106
|
+
api;
|
|
117
107
|
constructor(executor, options) {
|
|
118
|
-
this.
|
|
119
|
-
this.retries = options?.retries ?? 3;
|
|
108
|
+
this.api = new GhApiClient(executor ?? defaultExecutor, options?.retries ?? 3);
|
|
120
109
|
}
|
|
121
110
|
/**
|
|
122
111
|
* Lists all rulesets for a repository.
|
|
123
112
|
*/
|
|
124
113
|
async list(repoInfo, options) {
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
return JSON.parse(result);
|
|
114
|
+
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
115
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`;
|
|
116
|
+
const result = await this.api.call("GET", endpoint, undefined, options);
|
|
117
|
+
return parseApiJson(result, "rulesets response");
|
|
130
118
|
}
|
|
131
119
|
/**
|
|
132
120
|
* Gets a single ruleset by ID.
|
|
133
121
|
*/
|
|
134
122
|
async get(repoInfo, rulesetId, options) {
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
return JSON.parse(result);
|
|
123
|
+
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
124
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${rulesetId}`;
|
|
125
|
+
const result = await this.api.call("GET", endpoint, undefined, options);
|
|
126
|
+
return parseApiJson(result, "ruleset response");
|
|
140
127
|
}
|
|
141
128
|
/**
|
|
142
129
|
* Creates a new ruleset.
|
|
143
130
|
*/
|
|
144
131
|
async create(repoInfo, name, ruleset, options) {
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const endpoint = `/repos/${github.owner}/${github.repo}/rulesets`;
|
|
132
|
+
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
133
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets`;
|
|
148
134
|
const payload = configToGitHub(name, ruleset);
|
|
149
|
-
const result = await this.
|
|
150
|
-
return
|
|
135
|
+
const result = await this.api.call("POST", endpoint, payload, options);
|
|
136
|
+
return parseApiJson(result, "ruleset response");
|
|
151
137
|
}
|
|
152
138
|
/**
|
|
153
139
|
* Updates an existing ruleset.
|
|
154
140
|
*/
|
|
155
141
|
async update(repoInfo, rulesetId, name, ruleset, options) {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const endpoint = `/repos/${github.owner}/${github.repo}/rulesets/${rulesetId}`;
|
|
142
|
+
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
143
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${rulesetId}`;
|
|
159
144
|
const payload = configToGitHub(name, ruleset);
|
|
160
|
-
const result = await this.
|
|
161
|
-
return
|
|
145
|
+
const result = await this.api.call("PUT", endpoint, payload, options);
|
|
146
|
+
return parseApiJson(result, "ruleset response");
|
|
162
147
|
}
|
|
163
148
|
/**
|
|
164
149
|
* Deletes a ruleset.
|
|
165
150
|
*/
|
|
166
151
|
async delete(repoInfo, rulesetId, options) {
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
await this.ghApi("DELETE", endpoint, undefined, options);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Validates that the repo is a GitHub repository.
|
|
174
|
-
*/
|
|
175
|
-
validateGitHub(repoInfo) {
|
|
176
|
-
if (!isGitHubRepo(repoInfo)) {
|
|
177
|
-
throw new Error(`GitHub Ruleset strategy requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Executes a GitHub API call using the gh CLI.
|
|
182
|
-
*/
|
|
183
|
-
async ghApi(method, endpoint, payload, options) {
|
|
184
|
-
const args = ["gh", "api"];
|
|
185
|
-
// Add method flag
|
|
186
|
-
if (method !== "GET") {
|
|
187
|
-
args.push("-X", method);
|
|
188
|
-
}
|
|
189
|
-
// Add host flag for GitHub Enterprise
|
|
190
|
-
if (options?.host && options.host !== "github.com") {
|
|
191
|
-
args.push("--hostname", escapeShellArg(options.host));
|
|
192
|
-
}
|
|
193
|
-
// Add endpoint
|
|
194
|
-
args.push(escapeShellArg(endpoint));
|
|
195
|
-
// Build base command
|
|
196
|
-
const baseCommand = args.join(" ");
|
|
197
|
-
// Add GH_TOKEN environment variable prefix if token provided
|
|
198
|
-
// Token is escaped to prevent command injection
|
|
199
|
-
const tokenPrefix = options?.token
|
|
200
|
-
? `GH_TOKEN=${escapeShellArg(options.token)} `
|
|
201
|
-
: "";
|
|
202
|
-
// For POST/PUT with payload, use echo pipe pattern (same as graphql-commit-strategy)
|
|
203
|
-
// This is safer than heredoc as escapeShellArg properly escapes the content
|
|
204
|
-
if (payload && (method === "POST" || method === "PUT")) {
|
|
205
|
-
const payloadJson = JSON.stringify(payload);
|
|
206
|
-
const command = `echo ${escapeShellArg(payloadJson)} | ${tokenPrefix}${baseCommand} --input -`;
|
|
207
|
-
return await withRetry(() => this.executor.exec(command, process.cwd()), {
|
|
208
|
-
retries: this.retries,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
// For GET/DELETE, run command directly
|
|
212
|
-
const command = `${tokenPrefix}${baseCommand}`;
|
|
213
|
-
return await withRetry(() => this.executor.exec(command, process.cwd()), {
|
|
214
|
-
retries: this.retries,
|
|
215
|
-
});
|
|
152
|
+
assertGitHubRepo(repoInfo, "GitHub Ruleset strategy");
|
|
153
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/rulesets/${rulesetId}`;
|
|
154
|
+
await this.api.call("DELETE", endpoint, undefined, options);
|
|
216
155
|
}
|
|
217
156
|
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export {
|
|
3
|
-
export { RulesetProcessor, type IRulesetProcessor
|
|
4
|
-
export { diffRulesets, normalizeRuleset, projectToDesiredShape, type RulesetAction, type RulesetChange, } from "./diff.js";
|
|
5
|
-
export { formatRulesetPlan, type RulesetPlanResult, type RulesetPlanEntry, } from "./formatter.js";
|
|
6
|
-
export { GitHubRulesetStrategy, configToGitHub, type GitHubRuleset, type GitHubBypassActor, type GitHubRulesetConditions, type GitHubRule, type RulesetStrategyOptions, } from "./github-ruleset-strategy.js";
|
|
1
|
+
export { computePropertyDiffs, deepEqual, isObject, isArrayOfObjects, type PropertyDiff, } from "./diff-algorithm.js";
|
|
2
|
+
export { formatPropertyTree, type RulesetPlanEntry } from "./formatter.js";
|
|
3
|
+
export { RulesetProcessor, type IRulesetProcessor } from "./processor.js";
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
// Diff algorithm - property-level diffing for ruleset comparisons
|
|
2
|
-
export { computePropertyDiffs,
|
|
3
|
-
//
|
|
4
|
-
export {
|
|
5
|
-
//
|
|
6
|
-
export {
|
|
7
|
-
// Ruleset formatter
|
|
8
|
-
export { formatRulesetPlan, } from "./formatter.js";
|
|
9
|
-
// Ruleset strategies
|
|
10
|
-
export { GitHubRulesetStrategy, configToGitHub, } from "./github-ruleset-strategy.js";
|
|
2
|
+
export { computePropertyDiffs, deepEqual, isObject, isArrayOfObjects, } from "./diff-algorithm.js";
|
|
3
|
+
// Formatter
|
|
4
|
+
export { formatPropertyTree } from "./formatter.js";
|
|
5
|
+
// Processor
|
|
6
|
+
export { RulesetProcessor } from "./processor.js";
|
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import type { RepoConfig } from "../../config/index.js";
|
|
2
2
|
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
-
import {
|
|
3
|
+
import type { IRulesetStrategy } from "./types.js";
|
|
4
4
|
import { RulesetPlanResult } from "./formatter.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface RulesetProcessorOptions {
|
|
9
|
-
configId: string;
|
|
10
|
-
dryRun?: boolean;
|
|
11
|
-
managedRulesets: string[];
|
|
5
|
+
import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
|
|
6
|
+
export type IRulesetProcessor = ISettingsProcessor<RulesetProcessorOptions, RulesetProcessorResult>;
|
|
7
|
+
export interface RulesetProcessorOptions extends BaseProcessorOptions {
|
|
12
8
|
noDelete?: boolean;
|
|
13
|
-
token?: string;
|
|
14
9
|
}
|
|
15
|
-
export interface RulesetProcessorResult {
|
|
16
|
-
|
|
17
|
-
repoName: string;
|
|
18
|
-
message: string;
|
|
19
|
-
skipped?: boolean;
|
|
20
|
-
dryRun?: boolean;
|
|
21
|
-
changes?: {
|
|
22
|
-
create: number;
|
|
23
|
-
update: number;
|
|
24
|
-
delete: number;
|
|
25
|
-
unchanged: number;
|
|
26
|
-
};
|
|
27
|
-
manifestUpdate?: {
|
|
28
|
-
rulesets: string[];
|
|
29
|
-
};
|
|
10
|
+
export interface RulesetProcessorResult extends BaseProcessorResult {
|
|
11
|
+
changes?: ChangeCounts;
|
|
30
12
|
planOutput?: RulesetPlanResult;
|
|
31
13
|
}
|
|
32
14
|
/**
|
|
@@ -35,24 +17,7 @@ export interface RulesetProcessorResult {
|
|
|
35
17
|
*/
|
|
36
18
|
export declare class RulesetProcessor implements IRulesetProcessor {
|
|
37
19
|
private readonly strategy;
|
|
38
|
-
|
|
39
|
-
constructor(strategy?: GitHubRulesetStrategy);
|
|
40
|
-
/**
|
|
41
|
-
* Process rulesets for a single repository.
|
|
42
|
-
*/
|
|
20
|
+
constructor(strategy?: IRulesetStrategy);
|
|
43
21
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RulesetProcessorOptions): Promise<RulesetProcessorResult>;
|
|
44
|
-
|
|
45
|
-
* Format change counts into a summary string.
|
|
46
|
-
*/
|
|
47
|
-
private formatChangeSummary;
|
|
48
|
-
/**
|
|
49
|
-
* Compute manifest update based on current config.
|
|
50
|
-
* Only rulesets with deleteOrphaned enabled should be tracked.
|
|
51
|
-
*/
|
|
52
|
-
private computeManifestUpdate;
|
|
53
|
-
/**
|
|
54
|
-
* Resolves a GitHub App installation token for the given repo.
|
|
55
|
-
* Returns undefined if no token manager or token resolution fails.
|
|
56
|
-
*/
|
|
57
|
-
private getInstallationToken;
|
|
22
|
+
private processSettings;
|
|
58
23
|
}
|
|
@@ -1,188 +1,80 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GitHubRulesetStrategy, } from "./github-ruleset-strategy.js";
|
|
1
|
+
import { GitHubRulesetStrategy } from "./github-ruleset-strategy.js";
|
|
3
2
|
import { diffRulesets } from "./diff.js";
|
|
4
3
|
import { formatRulesetPlan } from "./formatter.js";
|
|
5
|
-
import {
|
|
6
|
-
import { GitHubAppTokenManager } from "../../vcs/github-app-token-manager.js";
|
|
7
|
-
// =============================================================================
|
|
8
|
-
// Processor Implementation
|
|
9
|
-
// =============================================================================
|
|
4
|
+
import { withGitHubGuards, countActions, buildDryRunResult, buildApplyResult, } from "../base-processor.js";
|
|
10
5
|
/**
|
|
11
6
|
* Processes ruleset configuration for a repository.
|
|
12
7
|
* Handles create/update/delete operations via GitHub Rulesets API.
|
|
13
8
|
*/
|
|
14
9
|
export class RulesetProcessor {
|
|
15
10
|
strategy;
|
|
16
|
-
tokenManager;
|
|
17
11
|
constructor(strategy) {
|
|
18
12
|
this.strategy = strategy ?? new GitHubRulesetStrategy();
|
|
19
|
-
if (hasGitHubAppCredentials()) {
|
|
20
|
-
this.tokenManager = new GitHubAppTokenManager(process.env.XFG_GITHUB_APP_ID, process.env.XFG_GITHUB_APP_PRIVATE_KEY);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
this.tokenManager = null;
|
|
24
|
-
}
|
|
25
13
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Process rulesets for a single repository.
|
|
28
|
-
*/
|
|
29
14
|
async process(repoConfig, repoInfo, options) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
skipped: true,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const githubRepo = repoInfo;
|
|
15
|
+
return withGitHubGuards(repoConfig, repoInfo, options, {
|
|
16
|
+
hasDesiredSettings: (rc) => Object.keys(rc.settings?.rulesets ?? {}).length > 0,
|
|
17
|
+
emptySettingsMessage: "No rulesets configured",
|
|
18
|
+
processSettings: (githubRepo, rc, opts, token, repoName) => this.processSettings(githubRepo, rc, opts, token, repoName),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async processSettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
|
|
22
|
+
const { dryRun, noDelete } = options;
|
|
42
23
|
const settings = repoConfig.settings;
|
|
43
24
|
const desiredRulesets = settings?.rulesets ?? {};
|
|
44
25
|
const deleteOrphaned = settings?.deleteOrphaned ?? false;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Resolve App token if available, fall back to provided token
|
|
57
|
-
const effectiveToken = token ?? (await this.getInstallationToken(githubRepo));
|
|
58
|
-
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
59
|
-
const currentRulesets = await this.strategy.list(githubRepo, strategyOptions);
|
|
60
|
-
// Convert desired rulesets to Map
|
|
61
|
-
const desiredMap = new Map(Object.entries(desiredRulesets));
|
|
62
|
-
// Hydrate rulesets that match desired names with full details from get()
|
|
63
|
-
// The list endpoint only returns summary fields (id, name, target, enforcement)
|
|
64
|
-
// but not rules, conditions, or bypass_actors needed for accurate diffing
|
|
65
|
-
const fullRulesets = [];
|
|
66
|
-
for (const summary of currentRulesets) {
|
|
67
|
-
if (desiredMap.has(summary.name)) {
|
|
68
|
-
const full = await this.strategy.get(githubRepo, summary.id, strategyOptions);
|
|
69
|
-
fullRulesets.push(full);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
fullRulesets.push(summary);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Compute diff
|
|
76
|
-
const changes = diffRulesets(fullRulesets, desiredMap, managedRulesets);
|
|
77
|
-
// Count changes by type
|
|
78
|
-
const changeCounts = {
|
|
79
|
-
create: changes.filter((c) => c.action === "create").length,
|
|
80
|
-
update: changes.filter((c) => c.action === "update").length,
|
|
81
|
-
delete: changes.filter((c) => c.action === "delete").length,
|
|
82
|
-
unchanged: changes.filter((c) => c.action === "unchanged").length,
|
|
83
|
-
};
|
|
84
|
-
const planOutput = formatRulesetPlan(changes);
|
|
85
|
-
// Dry run mode - report planned changes without applying
|
|
86
|
-
if (dryRun) {
|
|
87
|
-
const summary = this.formatChangeSummary(changeCounts);
|
|
88
|
-
return {
|
|
89
|
-
success: true,
|
|
90
|
-
repoName,
|
|
91
|
-
message: `[DRY RUN] ${summary}`,
|
|
92
|
-
dryRun: true,
|
|
93
|
-
changes: changeCounts,
|
|
94
|
-
planOutput,
|
|
95
|
-
manifestUpdate: this.computeManifestUpdate(desiredRulesets, deleteOrphaned),
|
|
96
|
-
};
|
|
26
|
+
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
27
|
+
const currentRulesets = await this.strategy.list(githubRepo, strategyOptions);
|
|
28
|
+
const desiredMap = new Map(Object.entries(desiredRulesets));
|
|
29
|
+
// Hydrate rulesets that match desired names with full details from get()
|
|
30
|
+
// The list endpoint only returns summary fields (id, name, target, enforcement)
|
|
31
|
+
// but not rules, conditions, or bypass_actors needed for accurate diffing
|
|
32
|
+
const fullRulesets = [];
|
|
33
|
+
for (const summary of currentRulesets) {
|
|
34
|
+
if (desiredMap.has(summary.name)) {
|
|
35
|
+
const full = await this.strategy.get(githubRepo, summary.id, strategyOptions);
|
|
36
|
+
fullRulesets.push(full);
|
|
97
37
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const change of changes) {
|
|
101
|
-
switch (change.action) {
|
|
102
|
-
case "create":
|
|
103
|
-
if (change.desired) {
|
|
104
|
-
await this.strategy.create(githubRepo, change.name, change.desired, strategyOptions);
|
|
105
|
-
appliedCount++;
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
case "update":
|
|
109
|
-
if (change.rulesetId !== undefined && change.desired) {
|
|
110
|
-
await this.strategy.update(githubRepo, change.rulesetId, change.name, change.desired, strategyOptions);
|
|
111
|
-
appliedCount++;
|
|
112
|
-
}
|
|
113
|
-
break;
|
|
114
|
-
case "delete":
|
|
115
|
-
// Check if deletion is allowed
|
|
116
|
-
if (!noDelete && deleteOrphaned && change.rulesetId !== undefined) {
|
|
117
|
-
await this.strategy.delete(githubRepo, change.rulesetId, strategyOptions);
|
|
118
|
-
appliedCount++;
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
case "unchanged":
|
|
122
|
-
// No action needed
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
38
|
+
else {
|
|
39
|
+
fullRulesets.push(summary);
|
|
125
40
|
}
|
|
126
|
-
const summary = this.formatChangeSummary(changeCounts);
|
|
127
|
-
return {
|
|
128
|
-
success: true,
|
|
129
|
-
repoName,
|
|
130
|
-
message: appliedCount > 0 ? `Applied: ${summary}` : "No changes needed",
|
|
131
|
-
changes: changeCounts,
|
|
132
|
-
planOutput,
|
|
133
|
-
manifestUpdate: this.computeManifestUpdate(desiredRulesets, deleteOrphaned),
|
|
134
|
-
};
|
|
135
41
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
message: `Failed: ${message}`,
|
|
142
|
-
};
|
|
42
|
+
const changes = diffRulesets(fullRulesets, desiredMap, deleteOrphaned);
|
|
43
|
+
const changeCounts = countActions(changes);
|
|
44
|
+
const planOutput = formatRulesetPlan(changes);
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
return buildDryRunResult(repoName, changeCounts, { planOutput });
|
|
143
47
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Resolves a GitHub App installation token for the given repo.
|
|
174
|
-
* Returns undefined if no token manager or token resolution fails.
|
|
175
|
-
*/
|
|
176
|
-
async getInstallationToken(repoInfo) {
|
|
177
|
-
if (!this.tokenManager) {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
const token = await this.tokenManager.getTokenForRepo(repoInfo);
|
|
182
|
-
return token ?? undefined;
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
return undefined;
|
|
48
|
+
// Apply changes
|
|
49
|
+
let appliedCount = 0;
|
|
50
|
+
for (const change of changes) {
|
|
51
|
+
switch (change.action) {
|
|
52
|
+
case "create":
|
|
53
|
+
if (change.desired) {
|
|
54
|
+
await this.strategy.create(githubRepo, change.name, change.desired, strategyOptions);
|
|
55
|
+
appliedCount++;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case "update":
|
|
59
|
+
if (change.rulesetId !== undefined && change.desired) {
|
|
60
|
+
await this.strategy.update(githubRepo, change.rulesetId, change.name, change.desired, strategyOptions);
|
|
61
|
+
appliedCount++;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
case "delete":
|
|
65
|
+
// Check if deletion is allowed
|
|
66
|
+
if (!noDelete && deleteOrphaned && change.rulesetId !== undefined) {
|
|
67
|
+
await this.strategy.delete(githubRepo, change.rulesetId, strategyOptions);
|
|
68
|
+
appliedCount++;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case "unchanged":
|
|
72
|
+
// No action needed
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
186
75
|
}
|
|
76
|
+
return buildApplyResult(repoName, changeCounts, appliedCount, {
|
|
77
|
+
planOutput,
|
|
78
|
+
});
|
|
187
79
|
}
|
|
188
80
|
}
|