@aspruyt/xfg 3.5.5 → 3.5.6
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/dist/ruleset-diff.js +10 -3
- package/dist/ruleset-processor.d.ts +6 -0
- package/dist/ruleset-processor.js +28 -2
- package/package.json +1 -1
package/dist/ruleset-diff.js
CHANGED
|
@@ -37,14 +37,15 @@ function normalizeValue(value) {
|
|
|
37
37
|
export function normalizeRuleset(obj) {
|
|
38
38
|
const normalized = {};
|
|
39
39
|
for (const [key, value] of Object.entries(obj)) {
|
|
40
|
-
if (value
|
|
40
|
+
if (value === undefined) {
|
|
41
41
|
continue;
|
|
42
42
|
}
|
|
43
43
|
const snakeKey = camelToSnake(key);
|
|
44
44
|
if (!RULESET_COMPARABLE_FIELDS.has(snakeKey)) {
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
// Preserve null explicitly — it means "API couldn't read this field"
|
|
48
|
+
normalized[snakeKey] = value === null ? null : normalizeValue(value);
|
|
48
49
|
}
|
|
49
50
|
return normalized;
|
|
50
51
|
}
|
|
@@ -122,7 +123,13 @@ function projectObjects(current, desired) {
|
|
|
122
123
|
const result = {};
|
|
123
124
|
for (const key of Object.keys(desired)) {
|
|
124
125
|
if (key in current) {
|
|
125
|
-
|
|
126
|
+
if (current[key] === null) {
|
|
127
|
+
// null means "API token can't read this field" — assume it matches desired
|
|
128
|
+
result[key] = desired[key];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
result[key] = projectToDesiredShape(current[key], desired[key]);
|
|
132
|
+
}
|
|
126
133
|
}
|
|
127
134
|
else if (Array.isArray(desired[key]) &&
|
|
128
135
|
desired[key].length === 0) {
|
|
@@ -35,6 +35,7 @@ export interface RulesetProcessorResult {
|
|
|
35
35
|
*/
|
|
36
36
|
export declare class RulesetProcessor implements IRulesetProcessor {
|
|
37
37
|
private readonly strategy;
|
|
38
|
+
private readonly tokenManager;
|
|
38
39
|
constructor(strategy?: GitHubRulesetStrategy);
|
|
39
40
|
/**
|
|
40
41
|
* Process rulesets for a single repository.
|
|
@@ -49,4 +50,9 @@ export declare class RulesetProcessor implements IRulesetProcessor {
|
|
|
49
50
|
* Only rulesets with deleteOrphaned enabled should be tracked.
|
|
50
51
|
*/
|
|
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;
|
|
52
58
|
}
|
|
@@ -2,6 +2,8 @@ import { isGitHubRepo, getRepoDisplayName } from "./repo-detector.js";
|
|
|
2
2
|
import { GitHubRulesetStrategy, } from "./strategies/github-ruleset-strategy.js";
|
|
3
3
|
import { diffRulesets } from "./ruleset-diff.js";
|
|
4
4
|
import { formatRulesetPlan, } from "./ruleset-plan-formatter.js";
|
|
5
|
+
import { hasGitHubAppCredentials } from "./strategies/index.js";
|
|
6
|
+
import { GitHubAppTokenManager } from "./github-app-token-manager.js";
|
|
5
7
|
// =============================================================================
|
|
6
8
|
// Processor Implementation
|
|
7
9
|
// =============================================================================
|
|
@@ -11,8 +13,15 @@ import { formatRulesetPlan, } from "./ruleset-plan-formatter.js";
|
|
|
11
13
|
*/
|
|
12
14
|
export class RulesetProcessor {
|
|
13
15
|
strategy;
|
|
16
|
+
tokenManager;
|
|
14
17
|
constructor(strategy) {
|
|
15
18
|
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
|
+
}
|
|
16
25
|
}
|
|
17
26
|
/**
|
|
18
27
|
* Process rulesets for a single repository.
|
|
@@ -44,8 +53,9 @@ export class RulesetProcessor {
|
|
|
44
53
|
};
|
|
45
54
|
}
|
|
46
55
|
try {
|
|
47
|
-
//
|
|
48
|
-
const
|
|
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 };
|
|
49
59
|
const currentRulesets = await this.strategy.list(githubRepo, strategyOptions);
|
|
50
60
|
// Convert desired rulesets to Map
|
|
51
61
|
const desiredMap = new Map(Object.entries(desiredRulesets));
|
|
@@ -159,4 +169,20 @@ export class RulesetProcessor {
|
|
|
159
169
|
const rulesetNames = Object.keys(rulesets).sort();
|
|
160
170
|
return { rulesets: rulesetNames };
|
|
161
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;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
162
188
|
}
|