@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,83 +1,87 @@
|
|
|
1
|
-
// src/output/unified-summary.ts
|
|
2
|
-
import { appendFileSync } from "node:fs";
|
|
3
1
|
import { hasLifecycleChanges } from "./lifecycle-report.js";
|
|
4
|
-
import {
|
|
2
|
+
import { writeGitHubStepSummary } from "./github-summary.js";
|
|
3
|
+
import { renderRepoSettingsDiffLines, formatCountEntry, } from "./settings-report.js";
|
|
5
4
|
// =============================================================================
|
|
6
5
|
// Helpers
|
|
7
6
|
// =============================================================================
|
|
7
|
+
function selectLabel(dry, pastLabel, futureLabel) {
|
|
8
|
+
return dry ? futureLabel : pastLabel;
|
|
9
|
+
}
|
|
8
10
|
function formatCombinedSummary(input) {
|
|
9
11
|
const parts = [];
|
|
10
12
|
const dry = input.dryRun;
|
|
11
|
-
// Lifecycle totals
|
|
12
13
|
if (input.lifecycle) {
|
|
13
14
|
const t = input.lifecycle.totals;
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (t.migrated > 0)
|
|
22
|
-
repoParts.push(`${t.migrated} ${dry ? "to migrate" : "migrated"}`);
|
|
23
|
-
const repoWord = repoTotal === 1 ? "repo" : "repos";
|
|
24
|
-
parts.push(`${repoTotal} ${repoWord} (${repoParts.join(", ")})`);
|
|
25
|
-
}
|
|
15
|
+
const entry = formatCountEntry("repo", "repos", [
|
|
16
|
+
{ label: selectLabel(dry, "created", "to create"), value: t.created },
|
|
17
|
+
{ label: selectLabel(dry, "forked", "to fork"), value: t.forked },
|
|
18
|
+
{ label: selectLabel(dry, "migrated", "to migrate"), value: t.migrated },
|
|
19
|
+
]);
|
|
20
|
+
if (entry)
|
|
21
|
+
parts.push(entry);
|
|
26
22
|
}
|
|
27
|
-
// Sync totals
|
|
28
23
|
if (input.sync) {
|
|
29
24
|
const t = input.sync.totals;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
const entry = formatCountEntry("file", "files", [
|
|
26
|
+
{
|
|
27
|
+
label: selectLabel(dry, "created", "to create"),
|
|
28
|
+
value: t.files.create,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: selectLabel(dry, "updated", "to update"),
|
|
32
|
+
value: t.files.update,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: selectLabel(dry, "deleted", "to delete"),
|
|
36
|
+
value: t.files.delete,
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
if (entry)
|
|
40
|
+
parts.push(entry);
|
|
42
41
|
}
|
|
43
|
-
// Settings totals
|
|
44
42
|
if (input.settings) {
|
|
45
43
|
const t = input.settings.totals;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
parts.push(
|
|
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
|
-
|
|
44
|
+
const settingsEntry = formatCountEntry("setting", "settings", [
|
|
45
|
+
{ label: selectLabel(dry, "added", "to add"), value: t.settings.add },
|
|
46
|
+
{
|
|
47
|
+
label: selectLabel(dry, "changed", "to change"),
|
|
48
|
+
value: t.settings.change,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
if (settingsEntry)
|
|
52
|
+
parts.push(settingsEntry);
|
|
53
|
+
const rulesetsEntry = formatCountEntry("ruleset", "rulesets", [
|
|
54
|
+
{
|
|
55
|
+
label: selectLabel(dry, "created", "to create"),
|
|
56
|
+
value: t.rulesets.create,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: selectLabel(dry, "updated", "to update"),
|
|
60
|
+
value: t.rulesets.update,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: selectLabel(dry, "deleted", "to delete"),
|
|
64
|
+
value: t.rulesets.delete,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
if (rulesetsEntry)
|
|
68
|
+
parts.push(rulesetsEntry);
|
|
69
|
+
const labelsEntry = formatCountEntry("label", "labels", [
|
|
70
|
+
{
|
|
71
|
+
label: selectLabel(dry, "created", "to create"),
|
|
72
|
+
value: t.labels.create,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
label: selectLabel(dry, "updated", "to update"),
|
|
76
|
+
value: t.labels.update,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: selectLabel(dry, "deleted", "to delete"),
|
|
80
|
+
value: t.labels.delete,
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
if (labelsEntry)
|
|
84
|
+
parts.push(labelsEntry);
|
|
81
85
|
}
|
|
82
86
|
if (parts.length === 0) {
|
|
83
87
|
return "No changes";
|
|
@@ -140,70 +144,7 @@ function renderSyncLines(syncRepo, diffLines) {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
function renderSettingsLines(settingsRepo, diffLines) {
|
|
143
|
-
|
|
144
|
-
if (setting.oldValue === undefined && setting.newValue === undefined) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (setting.action === "add") {
|
|
148
|
-
diffLines.push(`+ ${setting.name}: ${formatValuePlain(setting.newValue)}`);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
diffLines.push(`! ${setting.name}: ${formatValuePlain(setting.oldValue)} → ${formatValuePlain(setting.newValue)}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
for (const ruleset of settingsRepo.rulesets) {
|
|
155
|
-
if (ruleset.action === "create") {
|
|
156
|
-
diffLines.push(`+ ruleset "${ruleset.name}"`);
|
|
157
|
-
if (ruleset.config) {
|
|
158
|
-
diffLines.push(...formatRulesetConfigPlain(ruleset.config));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else if (ruleset.action === "update") {
|
|
162
|
-
diffLines.push(`! ruleset "${ruleset.name}"`);
|
|
163
|
-
if (ruleset.propertyDiffs && ruleset.propertyDiffs.length > 0) {
|
|
164
|
-
for (const diff of ruleset.propertyDiffs) {
|
|
165
|
-
const path = diff.path.join(".");
|
|
166
|
-
if (diff.action === "add") {
|
|
167
|
-
diffLines.push(`+ ${path}: ${formatValuePlain(diff.newValue)}`);
|
|
168
|
-
}
|
|
169
|
-
else if (diff.action === "change") {
|
|
170
|
-
diffLines.push(`! ${path}: ${formatValuePlain(diff.oldValue)} → ${formatValuePlain(diff.newValue)}`);
|
|
171
|
-
}
|
|
172
|
-
else if (diff.action === "remove") {
|
|
173
|
-
diffLines.push(`- ${path}`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else if (ruleset.action === "delete") {
|
|
179
|
-
diffLines.push(`- ruleset "${ruleset.name}"`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
for (const label of settingsRepo.labels) {
|
|
183
|
-
if (label.action === "create") {
|
|
184
|
-
diffLines.push(`+ label "${label.name}"`);
|
|
185
|
-
if (label.config) {
|
|
186
|
-
diffLines.push(`+ color: "${label.config.color}"`);
|
|
187
|
-
if (label.config.description !== undefined) {
|
|
188
|
-
diffLines.push(`+ description: "${label.config.description}"`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else if (label.action === "update") {
|
|
193
|
-
if (label.newName) {
|
|
194
|
-
diffLines.push(`! label "${label.name}" \u2192 "${label.newName}"`);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
diffLines.push(`! label "${label.name}"`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else if (label.action === "delete") {
|
|
201
|
-
diffLines.push(`- label "${label.name}"`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (settingsRepo.error) {
|
|
205
|
-
diffLines.push(`- Error: ${settingsRepo.error}`);
|
|
206
|
-
}
|
|
147
|
+
renderRepoSettingsDiffLines(settingsRepo, diffLines);
|
|
207
148
|
}
|
|
208
149
|
// =============================================================================
|
|
209
150
|
// Markdown Formatter
|
|
@@ -276,11 +217,8 @@ export function formatUnifiedSummaryMarkdown(input) {
|
|
|
276
217
|
// File Writer
|
|
277
218
|
// =============================================================================
|
|
278
219
|
export function writeUnifiedSummary(input) {
|
|
279
|
-
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
280
|
-
if (!summaryPath)
|
|
281
|
-
return;
|
|
282
220
|
const markdown = formatUnifiedSummaryMarkdown(input);
|
|
283
221
|
if (!markdown)
|
|
284
222
|
return;
|
|
285
|
-
|
|
223
|
+
writeGitHubStepSummary(markdown);
|
|
286
224
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RepoConfig } from "../config/index.js";
|
|
2
|
+
import type { RepoInfo, GitHubRepoInfo } from "../shared/repo-detector.js";
|
|
3
|
+
export interface BaseProcessorOptions {
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
/** Pre-resolved auth token. Callers (e.g. sync-command) must resolve via resolveGitHubToken before passing. */
|
|
6
|
+
token?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface BaseProcessorResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
repoName: string;
|
|
11
|
+
message: string;
|
|
12
|
+
skipped?: boolean;
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generic settings processor interface for dependency injection.
|
|
17
|
+
* All three settings processors (rulesets, labels, repo-settings)
|
|
18
|
+
* share this contract — specific interfaces extend it for type safety.
|
|
19
|
+
*/
|
|
20
|
+
export interface ISettingsProcessor<TOptions extends BaseProcessorOptions = BaseProcessorOptions, TResult extends BaseProcessorResult = BaseProcessorResult> {
|
|
21
|
+
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: TOptions): Promise<TResult>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Guards for GitHub settings processing — passed to withGitHubGuards.
|
|
25
|
+
*/
|
|
26
|
+
interface SettingsGuards<TOptions extends BaseProcessorOptions, TResult extends BaseProcessorResult> {
|
|
27
|
+
hasDesiredSettings(repoConfig: RepoConfig): boolean;
|
|
28
|
+
emptySettingsMessage: string;
|
|
29
|
+
processSettings(githubRepo: GitHubRepoInfo, repoConfig: RepoConfig, options: TOptions, effectiveToken: string | undefined, repoName: string): Promise<TResult>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Common boilerplate for GitHub settings processors: GitHub-only gating,
|
|
33
|
+
* empty settings check, token resolution, and error wrapping.
|
|
34
|
+
*/
|
|
35
|
+
export declare function withGitHubGuards<TOptions extends BaseProcessorOptions, TResult extends BaseProcessorResult>(repoConfig: RepoConfig, repoInfo: RepoInfo, options: TOptions, guards: SettingsGuards<TOptions, TResult>): Promise<TResult>;
|
|
36
|
+
export interface ChangeCounts {
|
|
37
|
+
create: number;
|
|
38
|
+
update: number;
|
|
39
|
+
delete: number;
|
|
40
|
+
unchanged: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Count actions from a diff result array.
|
|
44
|
+
* Works with any change type that has an `action` field.
|
|
45
|
+
*/
|
|
46
|
+
export declare function countActions(changes: ReadonlyArray<{
|
|
47
|
+
action: string;
|
|
48
|
+
}>): ChangeCounts;
|
|
49
|
+
export declare function formatChangeSummary(counts: ChangeCounts): string;
|
|
50
|
+
/**
|
|
51
|
+
* Build a standardized dry-run result for settings processors.
|
|
52
|
+
* Returns an intersection of BaseProcessorResult with the extra fields,
|
|
53
|
+
* which is assignable to any result subtype whose extra fields are provided.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildDryRunResult<E extends Record<string, unknown> = Record<string, never>>(repoName: string, changeCounts: ChangeCounts, extra?: E): BaseProcessorResult & {
|
|
56
|
+
changes: ChangeCounts;
|
|
57
|
+
dryRun: true;
|
|
58
|
+
} & E;
|
|
59
|
+
/**
|
|
60
|
+
* Build a standardized apply result for settings processors.
|
|
61
|
+
* Returns an intersection of BaseProcessorResult with the extra fields,
|
|
62
|
+
* which is assignable to any result subtype whose extra fields are provided.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildApplyResult<E extends Record<string, unknown> = Record<string, never>>(repoName: string, changeCounts: ChangeCounts, appliedCount: number, extra?: E): BaseProcessorResult & {
|
|
65
|
+
changes: ChangeCounts;
|
|
66
|
+
} & E;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isGitHubRepo, getRepoDisplayName } from "../shared/repo-detector.js";
|
|
2
|
+
import { toErrorMessage } from "../shared/type-guards.js";
|
|
3
|
+
/**
|
|
4
|
+
* Common boilerplate for GitHub settings processors: GitHub-only gating,
|
|
5
|
+
* empty settings check, token resolution, and error wrapping.
|
|
6
|
+
*/
|
|
7
|
+
export async function withGitHubGuards(repoConfig, repoInfo, options, guards) {
|
|
8
|
+
const repoName = getRepoDisplayName(repoInfo);
|
|
9
|
+
if (!isGitHubRepo(repoInfo)) {
|
|
10
|
+
return {
|
|
11
|
+
success: true,
|
|
12
|
+
repoName,
|
|
13
|
+
message: `Skipped: ${repoName} is not a GitHub repository`,
|
|
14
|
+
skipped: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (!guards.hasDesiredSettings(repoConfig)) {
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
repoName,
|
|
21
|
+
message: guards.emptySettingsMessage,
|
|
22
|
+
skipped: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return await guards.processSettings(repoInfo, repoConfig, options, options.token, repoName);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const message = toErrorMessage(error);
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
repoName,
|
|
33
|
+
message: `Failed: ${message}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Count actions from a diff result array.
|
|
39
|
+
* Works with any change type that has an `action` field.
|
|
40
|
+
*/
|
|
41
|
+
export function countActions(changes) {
|
|
42
|
+
return {
|
|
43
|
+
create: changes.filter((c) => c.action === "create").length,
|
|
44
|
+
update: changes.filter((c) => c.action === "update").length,
|
|
45
|
+
delete: changes.filter((c) => c.action === "delete").length,
|
|
46
|
+
unchanged: changes.filter((c) => c.action === "unchanged").length,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function formatChangeSummary(counts) {
|
|
50
|
+
const parts = [];
|
|
51
|
+
if (counts.create > 0)
|
|
52
|
+
parts.push(`${counts.create} created`);
|
|
53
|
+
if (counts.update > 0)
|
|
54
|
+
parts.push(`${counts.update} updated`);
|
|
55
|
+
if (counts.delete > 0)
|
|
56
|
+
parts.push(`${counts.delete} deleted`);
|
|
57
|
+
if (counts.unchanged > 0)
|
|
58
|
+
parts.push(`${counts.unchanged} unchanged`);
|
|
59
|
+
return parts.length > 0 ? parts.join(", ") : "no changes";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build a standardized dry-run result for settings processors.
|
|
63
|
+
* Returns an intersection of BaseProcessorResult with the extra fields,
|
|
64
|
+
* which is assignable to any result subtype whose extra fields are provided.
|
|
65
|
+
*/
|
|
66
|
+
export function buildDryRunResult(repoName, changeCounts, extra) {
|
|
67
|
+
const summary = formatChangeSummary(changeCounts);
|
|
68
|
+
const base = {
|
|
69
|
+
success: true,
|
|
70
|
+
repoName,
|
|
71
|
+
message: `[DRY RUN] ${summary}`,
|
|
72
|
+
dryRun: true,
|
|
73
|
+
changes: changeCounts,
|
|
74
|
+
};
|
|
75
|
+
return Object.assign(base, extra);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build a standardized apply result for settings processors.
|
|
79
|
+
* Returns an intersection of BaseProcessorResult with the extra fields,
|
|
80
|
+
* which is assignable to any result subtype whose extra fields are provided.
|
|
81
|
+
*/
|
|
82
|
+
export function buildApplyResult(repoName, changeCounts, appliedCount, extra) {
|
|
83
|
+
const summary = formatChangeSummary(changeCounts);
|
|
84
|
+
const base = {
|
|
85
|
+
success: true,
|
|
86
|
+
repoName,
|
|
87
|
+
message: appliedCount > 0 ? `Applied: ${summary}` : "No changes needed",
|
|
88
|
+
changes: changeCounts,
|
|
89
|
+
};
|
|
90
|
+
return Object.assign(base, extra);
|
|
91
|
+
}
|
package/dist/settings/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export { type ISettingsProcessor } from "./base-processor.js";
|
|
2
|
+
export { type PropertyDiff, formatPropertyTree, type RulesetPlanEntry, RulesetProcessor, type IRulesetProcessor, } from "./rulesets/index.js";
|
|
3
|
+
export { RepoSettingsProcessor, type IRepoSettingsProcessor, type RepoSettingsPlanEntry, } from "./repo-settings/index.js";
|
|
4
|
+
export { type LabelsPlanEntry, LabelsProcessor, type ILabelsProcessor, } from "./labels/index.js";
|
package/dist/settings/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Rulesets
|
|
2
|
-
export
|
|
2
|
+
export { formatPropertyTree, RulesetProcessor, } from "./rulesets/index.js";
|
|
3
3
|
// Repo settings
|
|
4
|
-
export
|
|
4
|
+
export { RepoSettingsProcessor, } from "./repo-settings/index.js";
|
|
5
5
|
// Labels
|
|
6
|
-
export
|
|
6
|
+
export { LabelsProcessor, } from "./labels/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Label } from "../../config/types.js";
|
|
2
|
-
|
|
2
|
+
interface GitHubLabelPayload {
|
|
3
3
|
name: string;
|
|
4
4
|
new_name?: string;
|
|
5
5
|
color: string;
|
|
@@ -13,3 +13,4 @@ export declare function normalizeColor(color: string): string;
|
|
|
13
13
|
* Converts a label config entry to a GitHub API payload.
|
|
14
14
|
*/
|
|
15
15
|
export declare function labelConfigToPayload(name: string, label: Label): GitHubLabelPayload;
|
|
16
|
+
export {};
|
|
@@ -25,9 +25,9 @@ export interface LabelChange {
|
|
|
25
25
|
*
|
|
26
26
|
* @param current - Current labels from GitHub API
|
|
27
27
|
* @param desired - Desired labels from config (name -> label)
|
|
28
|
-
* @param
|
|
28
|
+
* @param deleteOrphaned - If true, delete current labels not in desired config
|
|
29
29
|
* @param noDelete - If true, skip delete operations
|
|
30
30
|
* @returns Array of changes to apply
|
|
31
31
|
* @throws Error if rename collisions are detected
|
|
32
32
|
*/
|
|
33
|
-
export declare function diffLabels(current: GitHubLabel[], desired: Record<string, Label>,
|
|
33
|
+
export declare function diffLabels(current: GitHubLabel[], desired: Record<string, Label>, deleteOrphaned: boolean, noDelete: boolean): LabelChange[];
|
|
@@ -11,19 +11,18 @@ import { normalizeColor } from "./converter.js";
|
|
|
11
11
|
*
|
|
12
12
|
* @param current - Current labels from GitHub API
|
|
13
13
|
* @param desired - Desired labels from config (name -> label)
|
|
14
|
-
* @param
|
|
14
|
+
* @param deleteOrphaned - If true, delete current labels not in desired config
|
|
15
15
|
* @param noDelete - If true, skip delete operations
|
|
16
16
|
* @returns Array of changes to apply
|
|
17
17
|
* @throws Error if rename collisions are detected
|
|
18
18
|
*/
|
|
19
|
-
export function diffLabels(current, desired,
|
|
19
|
+
export function diffLabels(current, desired, deleteOrphaned, noDelete) {
|
|
20
20
|
const changes = [];
|
|
21
21
|
// Build case-insensitive lookup of current labels
|
|
22
22
|
const currentByName = new Map();
|
|
23
23
|
for (const label of current) {
|
|
24
24
|
currentByName.set(label.name.toLowerCase(), label);
|
|
25
25
|
}
|
|
26
|
-
const managedSet = new Set(managedLabels.map((n) => n.toLowerCase()));
|
|
27
26
|
// Collect rename targets for collision detection
|
|
28
27
|
const renameTargets = new Map(); // lowercase target -> source name
|
|
29
28
|
for (const [name, label] of Object.entries(desired)) {
|
|
@@ -38,10 +37,10 @@ export function diffLabels(current, desired, managedLabels, noDelete) {
|
|
|
38
37
|
// Determine which labels will be deleted (for collision checking)
|
|
39
38
|
const desiredLower = new Set(Object.keys(desired).map((n) => n.toLowerCase()));
|
|
40
39
|
const deletedNames = new Set();
|
|
41
|
-
if (!noDelete) {
|
|
42
|
-
for (const
|
|
43
|
-
if (!desiredLower.has(
|
|
44
|
-
deletedNames.add(
|
|
40
|
+
if (deleteOrphaned && !noDelete) {
|
|
41
|
+
for (const nameLower of currentByName.keys()) {
|
|
42
|
+
if (!desiredLower.has(nameLower)) {
|
|
43
|
+
deletedNames.add(nameLower);
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
}
|
|
@@ -130,18 +129,15 @@ export function diffLabels(current, desired, managedLabels, noDelete) {
|
|
|
130
129
|
}
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
|
-
//
|
|
134
|
-
if (!noDelete) {
|
|
135
|
-
for (const
|
|
136
|
-
if (!desiredLower.has(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
current: currentLabel,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
132
|
+
// Desired-state orphan detection: delete ALL current not in desired
|
|
133
|
+
if (deleteOrphaned && !noDelete) {
|
|
134
|
+
for (const [nameLower, currentLabel] of currentByName) {
|
|
135
|
+
if (!desiredLower.has(nameLower)) {
|
|
136
|
+
changes.push({
|
|
137
|
+
action: "delete",
|
|
138
|
+
name: currentLabel.name,
|
|
139
|
+
current: currentLabel,
|
|
140
|
+
});
|
|
145
141
|
}
|
|
146
142
|
}
|
|
147
143
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
2
|
import { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
|
+
import type { ILabelsStrategy, GitHubLabel } from "./types.js";
|
|
5
|
+
interface GitHubLabelsStrategyOptions {
|
|
5
6
|
retries?: number;
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
@@ -12,14 +13,13 @@ export interface GitHubLabelsStrategyOptions {
|
|
|
12
13
|
* escapeShellArg for input sanitization, matching the rulesets strategy pattern.
|
|
13
14
|
*/
|
|
14
15
|
export declare class GitHubLabelsStrategy implements ILabelsStrategy {
|
|
15
|
-
private
|
|
16
|
-
private retries;
|
|
16
|
+
private api;
|
|
17
17
|
constructor(executor?: ICommandExecutor, options?: GitHubLabelsStrategyOptions);
|
|
18
18
|
/**
|
|
19
19
|
* Lists all labels for a repository.
|
|
20
20
|
* Uses --paginate to retrieve all labels.
|
|
21
21
|
*/
|
|
22
|
-
list(repoInfo: RepoInfo, options?:
|
|
22
|
+
list(repoInfo: RepoInfo, options?: GhApiOptions): Promise<GitHubLabel[]>;
|
|
23
23
|
/**
|
|
24
24
|
* Creates a new label.
|
|
25
25
|
*/
|
|
@@ -27,7 +27,7 @@ export declare class GitHubLabelsStrategy implements ILabelsStrategy {
|
|
|
27
27
|
name: string;
|
|
28
28
|
color: string;
|
|
29
29
|
description?: string;
|
|
30
|
-
}, options?:
|
|
30
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
31
31
|
/**
|
|
32
32
|
* Updates an existing label.
|
|
33
33
|
* Uses encodeURIComponent for label name in URL path.
|
|
@@ -36,20 +36,11 @@ export declare class GitHubLabelsStrategy implements ILabelsStrategy {
|
|
|
36
36
|
new_name?: string;
|
|
37
37
|
color?: string;
|
|
38
38
|
description?: string;
|
|
39
|
-
}, options?:
|
|
39
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
40
40
|
/**
|
|
41
41
|
* Deletes a label.
|
|
42
42
|
* Uses encodeURIComponent for label name in URL path.
|
|
43
43
|
*/
|
|
44
|
-
delete(repoInfo: RepoInfo, name: string, options?:
|
|
45
|
-
/**
|
|
46
|
-
* Validates that the repo is a GitHub repository.
|
|
47
|
-
*/
|
|
48
|
-
private validateGitHub;
|
|
49
|
-
/**
|
|
50
|
-
* Executes a GitHub API call using the gh CLI.
|
|
51
|
-
* Uses the project's ICommandExecutor + escapeShellArg pattern
|
|
52
|
-
* (matching github-ruleset-strategy.ts).
|
|
53
|
-
*/
|
|
54
|
-
private ghApi;
|
|
44
|
+
delete(repoInfo: RepoInfo, name: string, options?: GhApiOptions): Promise<void>;
|
|
55
45
|
}
|
|
46
|
+
export {};
|