@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.
- package/dist/cli/index.d.ts +1 -2
- package/dist/cli/index.js +0 -1
- package/dist/cli/program.js +7 -2
- 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 +2 -24
- package/dist/cli/sync-command.js +295 -301
- package/dist/cli/types.d.ts +60 -40
- 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 -4
- package/dist/config/validator.js +286 -363
- 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/lifecycle/ado-migration-source.js +2 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +6 -5
- package/dist/lifecycle/github-lifecycle-provider.js +79 -90
- 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/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 -30
- package/dist/settings/labels/processor.js +62 -152
- 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 +1 -1
- package/dist/settings/rulesets/diff.js +2 -21
- 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 -33
- package/dist/settings/rulesets/processor.js +58 -151
- 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 -15
- package/dist/sync/index.js +0 -19
- package/dist/sync/manifest-manager.d.ts +4 -0
- package/dist/sync/manifest-manager.js +5 -1
- package/dist/sync/manifest.d.ts +10 -41
- package/dist/sync/manifest.js +11 -56
- 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 +1 -2
- package/dist/sync/repository-processor.js +20 -12
- package/dist/sync/repository-session.js +5 -14
- package/dist/sync/sync-workflow.js +31 -38
- package/dist/sync/types.d.ts +43 -178
- 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 +31 -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
|
@@ -2,26 +2,13 @@ import type { RepoConfig } from "../../config/index.js";
|
|
|
2
2
|
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
3
|
import { type LabelsPlanResult } from "./formatter.js";
|
|
4
4
|
import type { ILabelsStrategy } from "./types.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface LabelsProcessorOptions {
|
|
9
|
-
dryRun?: boolean;
|
|
5
|
+
import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
|
|
6
|
+
export type ILabelsProcessor = ISettingsProcessor<LabelsProcessorOptions, LabelsProcessorResult>;
|
|
7
|
+
export interface LabelsProcessorOptions extends BaseProcessorOptions {
|
|
10
8
|
noDelete?: boolean;
|
|
11
|
-
token?: string;
|
|
12
9
|
}
|
|
13
|
-
export interface LabelsProcessorResult {
|
|
14
|
-
|
|
15
|
-
repoName: string;
|
|
16
|
-
message: string;
|
|
17
|
-
skipped?: boolean;
|
|
18
|
-
dryRun?: boolean;
|
|
19
|
-
changes?: {
|
|
20
|
-
create: number;
|
|
21
|
-
update: number;
|
|
22
|
-
delete: number;
|
|
23
|
-
unchanged: number;
|
|
24
|
-
};
|
|
10
|
+
export interface LabelsProcessorResult extends BaseProcessorResult {
|
|
11
|
+
changes?: ChangeCounts;
|
|
25
12
|
planOutput?: LabelsPlanResult;
|
|
26
13
|
}
|
|
27
14
|
/**
|
|
@@ -30,18 +17,7 @@ export interface LabelsProcessorResult {
|
|
|
30
17
|
*/
|
|
31
18
|
export declare class LabelsProcessor implements ILabelsProcessor {
|
|
32
19
|
private readonly strategy;
|
|
33
|
-
private readonly tokenManager;
|
|
34
20
|
constructor(strategy?: ILabelsStrategy);
|
|
35
|
-
/**
|
|
36
|
-
* Process labels for a single repository.
|
|
37
|
-
*/
|
|
38
21
|
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: LabelsProcessorOptions): Promise<LabelsProcessorResult>;
|
|
39
|
-
|
|
40
|
-
* Format change counts into a summary string.
|
|
41
|
-
*/
|
|
42
|
-
private formatChangeSummary;
|
|
43
|
-
/**
|
|
44
|
-
* Resolves a GitHub App installation token for the given repo.
|
|
45
|
-
*/
|
|
46
|
-
private getInstallationToken;
|
|
22
|
+
private processSettings;
|
|
47
23
|
}
|
|
@@ -1,176 +1,86 @@
|
|
|
1
|
-
import { isGitHubRepo, getRepoDisplayName, } from "../../shared/repo-detector.js";
|
|
2
1
|
import { GitHubLabelsStrategy } from "./github-labels-strategy.js";
|
|
3
2
|
import { diffLabels } from "./diff.js";
|
|
4
3
|
import { formatLabelsPlan } from "./formatter.js";
|
|
5
4
|
import { labelConfigToPayload } from "./converter.js";
|
|
6
|
-
import {
|
|
7
|
-
import { GitHubAppTokenManager } from "../../vcs/github-app-token-manager.js";
|
|
8
|
-
// =============================================================================
|
|
9
|
-
// Processor Implementation
|
|
10
|
-
// =============================================================================
|
|
5
|
+
import { withGitHubGuards, countActions, buildDryRunResult, buildApplyResult, } from "../base-processor.js";
|
|
11
6
|
/**
|
|
12
7
|
* Processes label configuration for a repository.
|
|
13
8
|
* Handles create/update/delete operations via GitHub Labels API.
|
|
14
9
|
*/
|
|
15
10
|
export class LabelsProcessor {
|
|
16
11
|
strategy;
|
|
17
|
-
tokenManager;
|
|
18
12
|
constructor(strategy) {
|
|
19
13
|
this.strategy = strategy ?? new GitHubLabelsStrategy();
|
|
20
|
-
if (hasGitHubAppCredentials()) {
|
|
21
|
-
this.tokenManager = new GitHubAppTokenManager(process.env.XFG_GITHUB_APP_ID, process.env.XFG_GITHUB_APP_PRIVATE_KEY);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
this.tokenManager = null;
|
|
25
|
-
}
|
|
26
14
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Process labels for a single repository.
|
|
29
|
-
*/
|
|
30
15
|
async process(repoConfig, repoInfo, options) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
skipped: true,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const githubRepo = repoInfo;
|
|
16
|
+
return withGitHubGuards(repoConfig, repoInfo, options, {
|
|
17
|
+
hasDesiredSettings: (rc) => Object.keys(rc.settings?.labels ?? {}).length > 0,
|
|
18
|
+
emptySettingsMessage: "No labels configured",
|
|
19
|
+
processSettings: (githubRepo, rc, opts, token, repoName) => this.processSettings(githubRepo, rc, opts, token, repoName),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async processSettings(githubRepo, repoConfig, options, effectiveToken, repoName) {
|
|
23
|
+
const { dryRun, noDelete } = options;
|
|
43
24
|
const settings = repoConfig.settings;
|
|
44
25
|
const desiredLabels = settings?.labels ?? {};
|
|
45
26
|
const deleteOrphaned = settings?.deleteOrphaned ?? false;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
27
|
+
const strategyOptions = { token: effectiveToken, host: githubRepo.host };
|
|
28
|
+
const currentLabels = await this.strategy.list(githubRepo, strategyOptions);
|
|
29
|
+
// Compute diff
|
|
30
|
+
const changes = diffLabels(currentLabels, desiredLabels, deleteOrphaned, noDelete ?? false);
|
|
31
|
+
const changeCounts = countActions(changes);
|
|
32
|
+
const planOutput = formatLabelsPlan(changes);
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
return buildDryRunResult(repoName, changeCounts, { planOutput });
|
|
54
35
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const change of changes) {
|
|
85
|
-
switch (change.action) {
|
|
86
|
-
case "create":
|
|
87
|
-
if (change.desired) {
|
|
88
|
-
const payload = labelConfigToPayload(change.name, change.desired);
|
|
89
|
-
await this.strategy.create(githubRepo, {
|
|
90
|
-
name: payload.name,
|
|
91
|
-
color: payload.color,
|
|
92
|
-
...(payload.description !== undefined
|
|
93
|
-
? { description: payload.description }
|
|
94
|
-
: {}),
|
|
95
|
-
}, strategyOptions);
|
|
96
|
-
appliedCount++;
|
|
97
|
-
}
|
|
98
|
-
break;
|
|
99
|
-
case "update":
|
|
100
|
-
if (change.desired) {
|
|
101
|
-
const updatePayload = {};
|
|
102
|
-
for (const prop of change.propertyChanges ?? []) {
|
|
103
|
-
if (prop.property === "color") {
|
|
104
|
-
updatePayload.color = prop.newValue;
|
|
105
|
-
}
|
|
106
|
-
else if (prop.property === "description") {
|
|
107
|
-
updatePayload.description = prop.newValue;
|
|
108
|
-
}
|
|
109
|
-
else if (prop.property === "new_name") {
|
|
110
|
-
updatePayload.new_name = prop.newValue;
|
|
111
|
-
}
|
|
36
|
+
// Apply changes (diff is already sorted: delete, update, create, unchanged)
|
|
37
|
+
let appliedCount = 0;
|
|
38
|
+
for (const change of changes) {
|
|
39
|
+
switch (change.action) {
|
|
40
|
+
case "create":
|
|
41
|
+
if (change.desired) {
|
|
42
|
+
const payload = labelConfigToPayload(change.name, change.desired);
|
|
43
|
+
await this.strategy.create(githubRepo, {
|
|
44
|
+
name: payload.name,
|
|
45
|
+
color: payload.color,
|
|
46
|
+
...(payload.description !== undefined
|
|
47
|
+
? { description: payload.description }
|
|
48
|
+
: {}),
|
|
49
|
+
}, strategyOptions);
|
|
50
|
+
appliedCount++;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
case "update":
|
|
54
|
+
if (change.desired) {
|
|
55
|
+
const updatePayload = {};
|
|
56
|
+
for (const prop of change.propertyChanges ?? []) {
|
|
57
|
+
if (prop.property === "color") {
|
|
58
|
+
updatePayload.color = prop.newValue;
|
|
59
|
+
}
|
|
60
|
+
else if (prop.property === "description") {
|
|
61
|
+
updatePayload.description = prop.newValue;
|
|
62
|
+
}
|
|
63
|
+
else if (prop.property === "new_name") {
|
|
64
|
+
updatePayload.new_name = prop.newValue;
|
|
112
65
|
}
|
|
113
|
-
await this.strategy.update(githubRepo, change.name, updatePayload, strategyOptions);
|
|
114
|
-
appliedCount++;
|
|
115
|
-
}
|
|
116
|
-
break;
|
|
117
|
-
case "delete":
|
|
118
|
-
if (!noDelete && deleteOrphaned) {
|
|
119
|
-
await this.strategy.delete(githubRepo, change.name, strategyOptions);
|
|
120
|
-
appliedCount++;
|
|
121
66
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
67
|
+
await this.strategy.update(githubRepo, change.name, updatePayload, strategyOptions);
|
|
68
|
+
appliedCount++;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case "delete":
|
|
72
|
+
if (!noDelete && deleteOrphaned) {
|
|
73
|
+
await this.strategy.delete(githubRepo, change.name, strategyOptions);
|
|
74
|
+
appliedCount++;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case "unchanged":
|
|
78
|
+
// No action needed
|
|
79
|
+
break;
|
|
127
80
|
}
|
|
128
|
-
const summary = this.formatChangeSummary(changeCounts);
|
|
129
|
-
return {
|
|
130
|
-
success: true,
|
|
131
|
-
repoName,
|
|
132
|
-
message: appliedCount > 0 ? `Applied: ${summary}` : "No changes needed",
|
|
133
|
-
changes: changeCounts,
|
|
134
|
-
planOutput,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
139
|
-
return {
|
|
140
|
-
success: false,
|
|
141
|
-
repoName,
|
|
142
|
-
message: `Failed: ${message}`,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Format change counts into a summary string.
|
|
148
|
-
*/
|
|
149
|
-
formatChangeSummary(counts) {
|
|
150
|
-
const parts = [];
|
|
151
|
-
if (counts.create > 0)
|
|
152
|
-
parts.push(`${counts.create} created`);
|
|
153
|
-
if (counts.update > 0)
|
|
154
|
-
parts.push(`${counts.update} updated`);
|
|
155
|
-
if (counts.delete > 0)
|
|
156
|
-
parts.push(`${counts.delete} deleted`);
|
|
157
|
-
if (counts.unchanged > 0)
|
|
158
|
-
parts.push(`${counts.unchanged} unchanged`);
|
|
159
|
-
return parts.length > 0 ? parts.join(", ") : "no changes";
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Resolves a GitHub App installation token for the given repo.
|
|
163
|
-
*/
|
|
164
|
-
async getInstallationToken(repoInfo) {
|
|
165
|
-
if (!this.tokenManager) {
|
|
166
|
-
return undefined;
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
const token = await this.tokenManager.getTokenForRepo(repoInfo);
|
|
170
|
-
return token ?? undefined;
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
return undefined;
|
|
174
81
|
}
|
|
82
|
+
return buildApplyResult(repoName, changeCounts, appliedCount, {
|
|
83
|
+
planOutput,
|
|
84
|
+
});
|
|
175
85
|
}
|
|
176
86
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
2
|
-
|
|
3
|
-
token?: string;
|
|
4
|
-
host?: string;
|
|
5
|
-
}
|
|
2
|
+
import type { GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
6
3
|
/**
|
|
7
4
|
* GitHub label as returned by the API.
|
|
8
5
|
*/
|
|
@@ -18,16 +15,16 @@ export interface GitHubLabel {
|
|
|
18
15
|
* Abstracts platform-specific API calls.
|
|
19
16
|
*/
|
|
20
17
|
export interface ILabelsStrategy {
|
|
21
|
-
list(repoInfo: RepoInfo, options?:
|
|
18
|
+
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubLabel[]>;
|
|
22
19
|
create(repoInfo: RepoInfo, label: {
|
|
23
20
|
name: string;
|
|
24
21
|
color: string;
|
|
25
22
|
description?: string;
|
|
26
|
-
}, options?:
|
|
23
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
27
24
|
update(repoInfo: RepoInfo, currentName: string, label: {
|
|
28
25
|
new_name?: string;
|
|
29
26
|
color?: string;
|
|
30
27
|
description?: string;
|
|
31
|
-
}, options?:
|
|
32
|
-
delete(repoInfo: RepoInfo, name: string, options?:
|
|
28
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
29
|
+
delete(repoInfo: RepoInfo, name: string, options?: GhApiOptions): Promise<void>;
|
|
33
30
|
}
|
|
@@ -40,11 +40,11 @@ function getWarning(change) {
|
|
|
40
40
|
export function formatRepoSettingsPlan(changes) {
|
|
41
41
|
const lines = [];
|
|
42
42
|
const warnings = [];
|
|
43
|
-
let
|
|
44
|
-
let
|
|
43
|
+
let creates = 0;
|
|
44
|
+
let updates = 0;
|
|
45
45
|
const entries = [];
|
|
46
46
|
if (changes.length === 0) {
|
|
47
|
-
return { lines,
|
|
47
|
+
return { lines, creates, updates, warnings, entries };
|
|
48
48
|
}
|
|
49
49
|
for (const change of changes) {
|
|
50
50
|
const warning = getWarning(change);
|
|
@@ -53,7 +53,7 @@ export function formatRepoSettingsPlan(changes) {
|
|
|
53
53
|
}
|
|
54
54
|
if (change.action === "add") {
|
|
55
55
|
lines.push(chalk.green(` + ${change.property}: ${formatValue(change.newValue)}`));
|
|
56
|
-
|
|
56
|
+
creates++;
|
|
57
57
|
entries.push({
|
|
58
58
|
property: change.property,
|
|
59
59
|
action: "add",
|
|
@@ -62,7 +62,7 @@ export function formatRepoSettingsPlan(changes) {
|
|
|
62
62
|
}
|
|
63
63
|
else if (change.action === "change") {
|
|
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
68
|
action: "change",
|
|
@@ -71,7 +71,7 @@ export function formatRepoSettingsPlan(changes) {
|
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
return { lines,
|
|
74
|
+
return { lines, creates, updates, warnings, entries };
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Formats warnings for display.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
-
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
2
|
+
import { type RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
3
4
|
import type { GitHubRepoSettings } from "../../config/index.js";
|
|
4
|
-
import type { IRepoSettingsStrategy,
|
|
5
|
-
|
|
5
|
+
import type { IRepoSettingsStrategy, CurrentRepoSettings } from "./types.js";
|
|
6
|
+
interface GitHubRepoSettingsStrategyOptions {
|
|
6
7
|
retries?: number;
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
@@ -12,17 +13,15 @@ export interface GitHubRepoSettingsStrategyOptions {
|
|
|
12
13
|
* with other strategies in this codebase. Inputs are escaped via escapeShellArg.
|
|
13
14
|
*/
|
|
14
15
|
export declare class GitHubRepoSettingsStrategy implements IRepoSettingsStrategy {
|
|
15
|
-
private
|
|
16
|
-
private retries;
|
|
16
|
+
private api;
|
|
17
17
|
constructor(executor?: ICommandExecutor, options?: GitHubRepoSettingsStrategyOptions);
|
|
18
|
-
getSettings(repoInfo: RepoInfo, options?:
|
|
19
|
-
updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?:
|
|
20
|
-
setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?:
|
|
21
|
-
setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?:
|
|
22
|
-
setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?:
|
|
18
|
+
getSettings(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentRepoSettings>;
|
|
19
|
+
updateSettings(repoInfo: RepoInfo, settings: GitHubRepoSettings, options?: GhApiOptions): Promise<void>;
|
|
20
|
+
setVulnerabilityAlerts(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
21
|
+
setAutomatedSecurityFixes(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
22
|
+
setPrivateVulnerabilityReporting(repoInfo: RepoInfo, enable: boolean, options?: GhApiOptions): Promise<void>;
|
|
23
23
|
private getVulnerabilityAlerts;
|
|
24
24
|
private getAutomatedSecurityFixes;
|
|
25
25
|
private getPrivateVulnerabilityReporting;
|
|
26
|
-
private validateGitHub;
|
|
27
|
-
private ghApi;
|
|
28
26
|
}
|
|
27
|
+
export {};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { defaultExecutor, } from "../../shared/command-executor.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
/**
|
|
6
|
-
* Converts camelCase to snake_case.
|
|
7
|
-
*/
|
|
8
|
-
function camelToSnake(str) {
|
|
9
|
-
return str.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
10
|
-
}
|
|
2
|
+
import { assertGitHubRepo, } from "../../shared/repo-detector.js";
|
|
3
|
+
import { GhApiClient, parseApiJson, isHttp404Error, } from "../../shared/gh-api-utils.js";
|
|
4
|
+
import { camelToSnake } from "../../shared/string-utils.js";
|
|
11
5
|
/**
|
|
12
6
|
* Converts GitHubRepoSettings (camelCase) to GitHub API format (snake_case).
|
|
13
7
|
*/
|
|
@@ -69,70 +63,62 @@ function configToGitHubPayload(settings) {
|
|
|
69
63
|
* with other strategies in this codebase. Inputs are escaped via escapeShellArg.
|
|
70
64
|
*/
|
|
71
65
|
export class GitHubRepoSettingsStrategy {
|
|
72
|
-
|
|
73
|
-
retries;
|
|
66
|
+
api;
|
|
74
67
|
constructor(executor, options) {
|
|
75
|
-
this.
|
|
76
|
-
this.retries = options?.retries ?? 3;
|
|
68
|
+
this.api = new GhApiClient(executor ?? defaultExecutor, options?.retries ?? 3);
|
|
77
69
|
}
|
|
78
70
|
async getSettings(repoInfo, options) {
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const parsed = JSON.parse(result);
|
|
71
|
+
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
72
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}`;
|
|
73
|
+
const result = await this.api.call("GET", endpoint, undefined, options);
|
|
74
|
+
const parsed = parseApiJson(result, "repo settings response");
|
|
84
75
|
const settings = parsed;
|
|
85
76
|
// Extract owner type from nested API response
|
|
86
77
|
settings.owner_type = parsed.owner?.type;
|
|
87
78
|
// Fetch security settings from separate endpoints
|
|
88
|
-
settings.vulnerability_alerts = await this.getVulnerabilityAlerts(
|
|
79
|
+
settings.vulnerability_alerts = await this.getVulnerabilityAlerts(repoInfo, options);
|
|
89
80
|
// Pass vulnerability_alerts state - automated security fixes requires it enabled
|
|
90
|
-
settings.automated_security_fixes = await this.getAutomatedSecurityFixes(
|
|
81
|
+
settings.automated_security_fixes = await this.getAutomatedSecurityFixes(repoInfo, options, settings.vulnerability_alerts);
|
|
91
82
|
settings.private_vulnerability_reporting =
|
|
92
|
-
await this.getPrivateVulnerabilityReporting(
|
|
83
|
+
await this.getPrivateVulnerabilityReporting(repoInfo, options);
|
|
93
84
|
return settings;
|
|
94
85
|
}
|
|
95
86
|
async updateSettings(repoInfo, settings, options) {
|
|
96
|
-
|
|
97
|
-
const github = repoInfo;
|
|
87
|
+
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
98
88
|
const payload = configToGitHubPayload(settings);
|
|
99
89
|
// Skip if no settings to update
|
|
100
90
|
if (Object.keys(payload).length === 0) {
|
|
101
91
|
return;
|
|
102
92
|
}
|
|
103
|
-
const endpoint = `/repos/${
|
|
104
|
-
await this.
|
|
93
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}`;
|
|
94
|
+
await this.api.call("PATCH", endpoint, payload, options);
|
|
105
95
|
}
|
|
106
96
|
async setVulnerabilityAlerts(repoInfo, enable, options) {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const endpoint = `/repos/${github.owner}/${github.repo}/vulnerability-alerts`;
|
|
97
|
+
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
98
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/vulnerability-alerts`;
|
|
110
99
|
const method = enable ? "PUT" : "DELETE";
|
|
111
|
-
await this.
|
|
100
|
+
await this.api.call(method, endpoint, undefined, options);
|
|
112
101
|
}
|
|
113
102
|
async setAutomatedSecurityFixes(repoInfo, enable, options) {
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const endpoint = `/repos/${github.owner}/${github.repo}/automated-security-fixes`;
|
|
103
|
+
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
104
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/automated-security-fixes`;
|
|
117
105
|
const method = enable ? "PUT" : "DELETE";
|
|
118
|
-
await this.
|
|
106
|
+
await this.api.call(method, endpoint, undefined, options);
|
|
119
107
|
}
|
|
120
108
|
async setPrivateVulnerabilityReporting(repoInfo, enable, options) {
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
const endpoint = `/repos/${github.owner}/${github.repo}/private-vulnerability-reporting`;
|
|
109
|
+
assertGitHubRepo(repoInfo, "GitHub Repo Settings strategy");
|
|
110
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/private-vulnerability-reporting`;
|
|
124
111
|
const method = enable ? "PUT" : "DELETE";
|
|
125
|
-
await this.
|
|
112
|
+
await this.api.call(method, endpoint, undefined, options);
|
|
126
113
|
}
|
|
127
114
|
async getVulnerabilityAlerts(github, options) {
|
|
128
115
|
const endpoint = `/repos/${github.owner}/${github.repo}/vulnerability-alerts`;
|
|
129
116
|
try {
|
|
130
|
-
await this.
|
|
117
|
+
await this.api.call("GET", endpoint, undefined, options);
|
|
131
118
|
return true; // 204 = enabled
|
|
132
119
|
}
|
|
133
120
|
catch (error) {
|
|
134
|
-
|
|
135
|
-
if (message.includes("HTTP 404")) {
|
|
121
|
+
if (isHttp404Error(error)) {
|
|
136
122
|
return false; // 404 = disabled
|
|
137
123
|
}
|
|
138
124
|
throw error; // Re-throw other errors
|
|
@@ -142,18 +128,17 @@ export class GitHubRepoSettingsStrategy {
|
|
|
142
128
|
// Note: GitHub returns JSON with {enabled: boolean} for this endpoint
|
|
143
129
|
const endpoint = `/repos/${github.owner}/${github.repo}/automated-security-fixes`;
|
|
144
130
|
try {
|
|
145
|
-
const result = await this.
|
|
131
|
+
const result = await this.api.call("GET", endpoint, undefined, options);
|
|
146
132
|
// Parse JSON response - GitHub returns {"enabled": true/false}
|
|
147
133
|
if (result) {
|
|
148
|
-
const data =
|
|
134
|
+
const data = parseApiJson(result, "automated security fixes response");
|
|
149
135
|
return data.enabled === true;
|
|
150
136
|
}
|
|
151
137
|
// Empty response (204) means enabled
|
|
152
138
|
return true;
|
|
153
139
|
}
|
|
154
140
|
catch (error) {
|
|
155
|
-
|
|
156
|
-
if (message.includes("HTTP 404")) {
|
|
141
|
+
if (isHttp404Error(error)) {
|
|
157
142
|
return false;
|
|
158
143
|
}
|
|
159
144
|
throw error;
|
|
@@ -162,47 +147,15 @@ export class GitHubRepoSettingsStrategy {
|
|
|
162
147
|
async getPrivateVulnerabilityReporting(github, options) {
|
|
163
148
|
const endpoint = `/repos/${github.owner}/${github.repo}/private-vulnerability-reporting`;
|
|
164
149
|
try {
|
|
165
|
-
const result = await this.
|
|
166
|
-
const data =
|
|
150
|
+
const result = await this.api.call("GET", endpoint, undefined, options);
|
|
151
|
+
const data = parseApiJson(result, "private vulnerability reporting response");
|
|
167
152
|
return data.enabled === true;
|
|
168
153
|
}
|
|
169
154
|
catch (error) {
|
|
170
|
-
|
|
171
|
-
if (message.includes("HTTP 404")) {
|
|
155
|
+
if (isHttp404Error(error)) {
|
|
172
156
|
return false; // 404 = not available (e.g. private repos)
|
|
173
157
|
}
|
|
174
158
|
throw error; // Re-throw other errors
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
|
-
validateGitHub(repoInfo) {
|
|
178
|
-
if (!isGitHubRepo(repoInfo)) {
|
|
179
|
-
throw new Error(`GitHub Repo Settings strategy requires GitHub repositories. Got: ${repoInfo.type}`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async ghApi(method, endpoint, payload, options) {
|
|
183
|
-
const args = ["gh", "api"];
|
|
184
|
-
if (method !== "GET") {
|
|
185
|
-
args.push("-X", method);
|
|
186
|
-
}
|
|
187
|
-
if (options?.host && options.host !== "github.com") {
|
|
188
|
-
args.push("--hostname", escapeShellArg(options.host));
|
|
189
|
-
}
|
|
190
|
-
args.push(escapeShellArg(endpoint));
|
|
191
|
-
const baseCommand = args.join(" ");
|
|
192
|
-
const tokenPrefix = options?.token
|
|
193
|
-
? `GH_TOKEN=${escapeShellArg(options.token)} `
|
|
194
|
-
: "";
|
|
195
|
-
if (payload &&
|
|
196
|
-
(method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
197
|
-
const payloadJson = JSON.stringify(payload);
|
|
198
|
-
const command = `echo ${escapeShellArg(payloadJson)} | ${tokenPrefix}${baseCommand} --input -`;
|
|
199
|
-
return await withRetry(() => this.executor.exec(command, process.cwd()), {
|
|
200
|
-
retries: this.retries,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
const command = `${tokenPrefix}${baseCommand}`;
|
|
204
|
-
return await withRetry(() => this.executor.exec(command, process.cwd()), {
|
|
205
|
-
retries: this.retries,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
161
|
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { diffRepoSettings, hasChanges, type RepoSettingsAction, type RepoSettingsChange, } from "./diff.js";
|
|
4
|
-
export { formatRepoSettingsPlan, type RepoSettingsPlanResult, type RepoSettingsPlanEntry, } from "./formatter.js";
|
|
5
|
-
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|
|
1
|
+
export { RepoSettingsProcessor, type IRepoSettingsProcessor, } from "./processor.js";
|
|
2
|
+
export { type RepoSettingsPlanEntry } from "./formatter.js";
|
|
@@ -1,10 +1,2 @@
|
|
|
1
|
-
//
|
|
2
|
-
export { isRepoSettingsStrategy, } from "./types.js";
|
|
3
|
-
// Repo settings processor
|
|
1
|
+
// Processor
|
|
4
2
|
export { RepoSettingsProcessor, } from "./processor.js";
|
|
5
|
-
// Repo settings diff
|
|
6
|
-
export { diffRepoSettings, hasChanges, } from "./diff.js";
|
|
7
|
-
// Repo settings formatter
|
|
8
|
-
export { formatRepoSettingsPlan, } from "./formatter.js";
|
|
9
|
-
// Repo settings strategies
|
|
10
|
-
export { GitHubRepoSettingsStrategy } from "./github-repo-settings-strategy.js";
|