@aspruyt/xfg 3.7.1 → 3.7.3
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/repo-settings-diff.js +3 -3
- package/dist/repo-settings-processor.d.ts +6 -0
- package/dist/repo-settings-processor.js +35 -2
- package/dist/strategies/github-repo-settings-strategy.d.ts +4 -0
- package/dist/strategies/github-repo-settings-strategy.js +56 -1
- package/dist/strategies/repo-settings-strategy.d.ts +7 -0
- package/dist/strategies/repo-settings-strategy.js +2 -1
- package/package.json +1 -1
|
@@ -22,11 +22,11 @@ const PROPERTY_MAPPING = {
|
|
|
22
22
|
mergeCommitMessage: "merge_commit_message",
|
|
23
23
|
webCommitSignoffRequired: "web_commit_signoff_required",
|
|
24
24
|
defaultBranch: "default_branch",
|
|
25
|
-
vulnerabilityAlerts: "
|
|
26
|
-
automatedSecurityFixes: "
|
|
25
|
+
vulnerabilityAlerts: "vulnerability_alerts",
|
|
26
|
+
automatedSecurityFixes: "automated_security_fixes",
|
|
27
27
|
secretScanning: "_secret_scanning",
|
|
28
28
|
secretScanningPushProtection: "_secret_scanning_push_protection",
|
|
29
|
-
privateVulnerabilityReporting: "
|
|
29
|
+
privateVulnerabilityReporting: "private_vulnerability_reporting",
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
32
|
* Gets the current value for a property from GitHub API response.
|
|
@@ -24,7 +24,13 @@ export interface RepoSettingsProcessorResult {
|
|
|
24
24
|
}
|
|
25
25
|
export declare class RepoSettingsProcessor implements IRepoSettingsProcessor {
|
|
26
26
|
private readonly strategy;
|
|
27
|
+
private readonly tokenManager;
|
|
27
28
|
constructor(strategy?: IRepoSettingsStrategy);
|
|
28
29
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: RepoSettingsProcessorOptions): Promise<RepoSettingsProcessorResult>;
|
|
29
30
|
private applyChanges;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves a GitHub App installation token for the given repo.
|
|
33
|
+
* Returns undefined if no token manager or token resolution fails.
|
|
34
|
+
*/
|
|
35
|
+
private getInstallationToken;
|
|
30
36
|
}
|
|
@@ -2,10 +2,19 @@ import { isGitHubRepo, getRepoDisplayName } from "./repo-detector.js";
|
|
|
2
2
|
import { GitHubRepoSettingsStrategy } from "./strategies/github-repo-settings-strategy.js";
|
|
3
3
|
import { diffRepoSettings, hasChanges } from "./repo-settings-diff.js";
|
|
4
4
|
import { formatRepoSettingsPlan, } from "./repo-settings-plan-formatter.js";
|
|
5
|
+
import { hasGitHubAppCredentials } from "./strategies/index.js";
|
|
6
|
+
import { GitHubAppTokenManager } from "./github-app-token-manager.js";
|
|
5
7
|
export class RepoSettingsProcessor {
|
|
6
8
|
strategy;
|
|
9
|
+
tokenManager;
|
|
7
10
|
constructor(strategy) {
|
|
8
11
|
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
|
+
}
|
|
9
18
|
}
|
|
10
19
|
async process(repoConfig, repoInfo, options) {
|
|
11
20
|
const repoName = getRepoDisplayName(repoInfo);
|
|
@@ -31,7 +40,9 @@ export class RepoSettingsProcessor {
|
|
|
31
40
|
};
|
|
32
41
|
}
|
|
33
42
|
try {
|
|
34
|
-
|
|
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 };
|
|
35
46
|
// Fetch current settings
|
|
36
47
|
const currentSettings = await this.strategy.getSettings(githubRepo, strategyOptions);
|
|
37
48
|
// Compute diff
|
|
@@ -80,18 +91,40 @@ export class RepoSettingsProcessor {
|
|
|
80
91
|
}
|
|
81
92
|
async applyChanges(repoInfo, settings, options) {
|
|
82
93
|
// Extract settings that need separate API calls
|
|
83
|
-
const { vulnerabilityAlerts, automatedSecurityFixes, ...mainSettings } = settings;
|
|
94
|
+
const { vulnerabilityAlerts, automatedSecurityFixes, privateVulnerabilityReporting, ...mainSettings } = settings;
|
|
84
95
|
// Update main settings via PATCH /repos
|
|
85
96
|
if (Object.keys(mainSettings).length > 0) {
|
|
86
97
|
await this.strategy.updateSettings(repoInfo, mainSettings, options);
|
|
87
98
|
}
|
|
88
99
|
// Handle vulnerability alerts (separate endpoint)
|
|
100
|
+
// Must be done before automated security fixes
|
|
89
101
|
if (vulnerabilityAlerts !== undefined) {
|
|
90
102
|
await this.strategy.setVulnerabilityAlerts(repoInfo, vulnerabilityAlerts, options);
|
|
91
103
|
}
|
|
104
|
+
// Handle private vulnerability reporting (separate endpoint)
|
|
105
|
+
if (privateVulnerabilityReporting !== undefined) {
|
|
106
|
+
await this.strategy.setPrivateVulnerabilityReporting(repoInfo, privateVulnerabilityReporting, options);
|
|
107
|
+
}
|
|
92
108
|
// Handle automated security fixes (separate endpoint)
|
|
109
|
+
// Done last to ensure vulnerability alerts have been fully processed
|
|
93
110
|
if (automatedSecurityFixes !== undefined) {
|
|
94
111
|
await this.strategy.setAutomatedSecurityFixes(repoInfo, automatedSecurityFixes, options);
|
|
95
112
|
}
|
|
96
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolves a GitHub App installation token for the given repo.
|
|
116
|
+
* Returns undefined if no token manager or token resolution fails.
|
|
117
|
+
*/
|
|
118
|
+
async getInstallationToken(repoInfo) {
|
|
119
|
+
if (!this.tokenManager) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const token = await this.tokenManager.getTokenForRepo(repoInfo);
|
|
124
|
+
return token ?? undefined;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
97
130
|
}
|
|
@@ -15,6 +15,10 @@ export declare class GitHubRepoSettingsStrategy implements IRepoSettingsStrategy
|
|
|
15
15
|
updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
16
16
|
setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
17
17
|
setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
18
|
+
setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
19
|
+
private getVulnerabilityAlerts;
|
|
20
|
+
private getAutomatedSecurityFixes;
|
|
21
|
+
private getPrivateVulnerabilityReporting;
|
|
18
22
|
private validateGitHub;
|
|
19
23
|
private ghApi;
|
|
20
24
|
}
|
|
@@ -76,7 +76,14 @@ export class GitHubRepoSettingsStrategy {
|
|
|
76
76
|
const github = repoInfo;
|
|
77
77
|
const endpoint = `/repos/${github.owner}/${github.repo}`;
|
|
78
78
|
const result = await this.ghApi("GET", endpoint, undefined, options);
|
|
79
|
-
|
|
79
|
+
const settings = JSON.parse(result);
|
|
80
|
+
// Fetch security settings from separate endpoints
|
|
81
|
+
settings.vulnerability_alerts = await this.getVulnerabilityAlerts(github, options);
|
|
82
|
+
// Pass vulnerability_alerts state - automated security fixes requires it enabled
|
|
83
|
+
settings.automated_security_fixes = await this.getAutomatedSecurityFixes(github, options, settings.vulnerability_alerts);
|
|
84
|
+
settings.private_vulnerability_reporting =
|
|
85
|
+
await this.getPrivateVulnerabilityReporting(github, options);
|
|
86
|
+
return settings;
|
|
80
87
|
}
|
|
81
88
|
async updateSettings(repoInfo, settings, options) {
|
|
82
89
|
this.validateGitHub(repoInfo);
|
|
@@ -103,6 +110,54 @@ export class GitHubRepoSettingsStrategy {
|
|
|
103
110
|
const method = enable ? "PUT" : "DELETE";
|
|
104
111
|
await this.ghApi(method, endpoint, undefined, options);
|
|
105
112
|
}
|
|
113
|
+
async setPrivateVulnerabilityReporting(repoInfo, enable, options) {
|
|
114
|
+
this.validateGitHub(repoInfo);
|
|
115
|
+
const github = repoInfo;
|
|
116
|
+
const endpoint = `/repos/${github.owner}/${github.repo}/private-vulnerability-reporting`;
|
|
117
|
+
const method = enable ? "PUT" : "DELETE";
|
|
118
|
+
await this.ghApi(method, endpoint, undefined, options);
|
|
119
|
+
}
|
|
120
|
+
async getVulnerabilityAlerts(github, options) {
|
|
121
|
+
const endpoint = `/repos/${github.owner}/${github.repo}/vulnerability-alerts`;
|
|
122
|
+
try {
|
|
123
|
+
await this.ghApi("GET", endpoint, undefined, options);
|
|
124
|
+
return true; // 204 = enabled
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
128
|
+
if (message.includes("HTTP 404")) {
|
|
129
|
+
return false; // 404 = disabled
|
|
130
|
+
}
|
|
131
|
+
throw error; // Re-throw other errors
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async getAutomatedSecurityFixes(github, options, _vulnerabilityAlertsEnabled) {
|
|
135
|
+
// Note: GitHub returns JSON with {enabled: boolean} for this endpoint
|
|
136
|
+
const endpoint = `/repos/${github.owner}/${github.repo}/automated-security-fixes`;
|
|
137
|
+
try {
|
|
138
|
+
const result = await this.ghApi("GET", endpoint, undefined, options);
|
|
139
|
+
// Parse JSON response - GitHub returns {"enabled": true/false}
|
|
140
|
+
if (result) {
|
|
141
|
+
const data = JSON.parse(result);
|
|
142
|
+
return data.enabled === true;
|
|
143
|
+
}
|
|
144
|
+
// Empty response (204) means enabled
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
if (message.includes("HTTP 404")) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async getPrivateVulnerabilityReporting(github, options) {
|
|
156
|
+
const endpoint = `/repos/${github.owner}/${github.repo}/private-vulnerability-reporting`;
|
|
157
|
+
const result = await this.ghApi("GET", endpoint, undefined, options);
|
|
158
|
+
const data = JSON.parse(result);
|
|
159
|
+
return data.enabled === true;
|
|
160
|
+
}
|
|
106
161
|
validateGitHub(repoInfo) {
|
|
107
162
|
if (!isGitHubRepo(repoInfo)) {
|
|
108
163
|
throw new Error(`GitHub Repo Settings strategy requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
@@ -39,6 +39,9 @@ export interface CurrentRepoSettings {
|
|
|
39
39
|
status: string;
|
|
40
40
|
};
|
|
41
41
|
};
|
|
42
|
+
vulnerability_alerts?: boolean;
|
|
43
|
+
automated_security_fixes?: boolean;
|
|
44
|
+
private_vulnerability_reporting?: boolean;
|
|
42
45
|
}
|
|
43
46
|
export interface IRepoSettingsStrategy {
|
|
44
47
|
/**
|
|
@@ -57,6 +60,10 @@ export interface IRepoSettingsStrategy {
|
|
|
57
60
|
* Enables or disables automated security fixes.
|
|
58
61
|
*/
|
|
59
62
|
setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Enables or disables private vulnerability reporting.
|
|
65
|
+
*/
|
|
66
|
+
setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: RepoSettingsStrategyOptions): Promise<void>;
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
62
69
|
* Type guard to check if an object implements IRepoSettingsStrategy.
|
|
@@ -9,5 +9,6 @@ export function isRepoSettingsStrategy(obj) {
|
|
|
9
9
|
return (typeof strategy.getSettings === "function" &&
|
|
10
10
|
typeof strategy.updateSettings === "function" &&
|
|
11
11
|
typeof strategy.setVulnerabilityAlerts === "function" &&
|
|
12
|
-
typeof strategy.setAutomatedSecurityFixes === "function"
|
|
12
|
+
typeof strategy.setAutomatedSecurityFixes === "function" &&
|
|
13
|
+
typeof strategy.setPrivateVulnerabilityReporting === "function");
|
|
13
14
|
}
|