@aspruyt/xfg 3.9.12 → 3.9.14
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 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/settings/lifecycle-checks.d.ts +11 -0
- package/dist/cli/settings/lifecycle-checks.js +64 -0
- package/dist/cli/settings/process-labels.d.ts +9 -0
- package/dist/cli/settings/process-labels.js +125 -0
- package/dist/cli/settings/process-repo-settings.d.ts +9 -0
- package/dist/cli/settings/process-repo-settings.js +80 -0
- package/dist/cli/settings/process-rulesets.d.ts +9 -0
- package/dist/cli/settings/process-rulesets.js +118 -0
- package/dist/cli/settings/results-collector.d.ts +11 -0
- package/dist/cli/settings/results-collector.js +28 -0
- package/dist/cli/settings-command.d.ts +3 -3
- package/dist/cli/settings-command.js +28 -268
- package/dist/cli/settings-report-builder.d.ts +6 -0
- package/dist/cli/settings-report-builder.js +23 -0
- package/dist/cli/types.d.ts +12 -2
- package/dist/cli/types.js +5 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/normalizer.d.ts +0 -4
- package/dist/config/normalizer.js +56 -0
- package/dist/config/types.d.ts +17 -0
- package/dist/config/validator.d.ts +2 -3
- package/dist/config/validator.js +62 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/output/github-summary.d.ts +6 -0
- package/dist/output/github-summary.js +39 -0
- package/dist/output/settings-report.d.ts +18 -1
- package/dist/output/settings-report.js +84 -0
- package/dist/output/unified-summary.js +40 -1
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -0
- package/dist/settings/labels/converter.d.ts +15 -0
- package/dist/settings/labels/converter.js +22 -0
- package/dist/settings/labels/diff.d.ts +33 -0
- package/dist/settings/labels/diff.js +156 -0
- package/dist/settings/labels/formatter.d.ts +25 -0
- package/dist/settings/labels/formatter.js +92 -0
- package/dist/settings/labels/github-labels-strategy.d.ts +51 -0
- package/dist/settings/labels/github-labels-strategy.js +102 -0
- package/dist/settings/labels/index.d.ts +6 -0
- package/dist/settings/labels/index.js +10 -0
- package/dist/settings/labels/processor.d.ts +57 -0
- package/dist/settings/labels/processor.js +189 -0
- package/dist/settings/labels/types.d.ts +33 -0
- package/dist/settings/labels/types.js +1 -0
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.js +1 -1
- package/dist/sync/manifest-strategy.d.ts +2 -1
- package/dist/sync/manifest-strategy.js +23 -5
- package/dist/sync/manifest.d.ts +24 -0
- package/dist/sync/manifest.js +98 -6
- package/dist/sync/repository-processor.d.ts +2 -1
- package/dist/sync/repository-processor.js +21 -5
- package/dist/sync/types.d.ts +2 -1
- package/package.json +4 -3
package/dist/config/validator.js
CHANGED
|
@@ -46,9 +46,32 @@ function getGitDisplayName(git) {
|
|
|
46
46
|
return git;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Validates
|
|
49
|
+
* Validates a single label configuration.
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
function validateLabel(label, name, context) {
|
|
52
|
+
if (typeof label !== "object" || label === null || Array.isArray(label)) {
|
|
53
|
+
throw new Error(`${context}: label '${name}' must be an object`);
|
|
54
|
+
}
|
|
55
|
+
const l = label;
|
|
56
|
+
if (typeof l.color !== "string" || !/^#?[0-9a-fA-F]{6}$/.test(l.color)) {
|
|
57
|
+
throw new Error(`${context}: label '${name}' color must be a 6-character hex code (with or without #)`);
|
|
58
|
+
}
|
|
59
|
+
if (l.description !== undefined) {
|
|
60
|
+
if (typeof l.description !== "string") {
|
|
61
|
+
throw new Error(`${context}: label '${name}' description must be a string`);
|
|
62
|
+
}
|
|
63
|
+
if (l.description.length > 100) {
|
|
64
|
+
throw new Error(`${context}: label '${name}' description exceeds 100 characters (GitHub limit)`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (l.new_name !== undefined && typeof l.new_name !== "string") {
|
|
68
|
+
throw new Error(`${context}: label '${name}' new_name must be a string`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validates settings object containing rulesets, labels, and repo settings.
|
|
73
|
+
*/
|
|
74
|
+
export function validateSettings(settings, context, rootRulesetNames, hasRootRepoSettings, rootLabelNames) {
|
|
52
75
|
if (typeof settings !== "object" ||
|
|
53
76
|
settings === null ||
|
|
54
77
|
Array.isArray(settings)) {
|
|
@@ -76,6 +99,26 @@ export function validateSettings(settings, context, rootRulesetNames, hasRootRep
|
|
|
76
99
|
validateRuleset(ruleset, name, context);
|
|
77
100
|
}
|
|
78
101
|
}
|
|
102
|
+
// Validate labels
|
|
103
|
+
if (s.labels !== undefined) {
|
|
104
|
+
if (typeof s.labels !== "object" ||
|
|
105
|
+
s.labels === null ||
|
|
106
|
+
Array.isArray(s.labels)) {
|
|
107
|
+
throw new Error(`${context}: labels must be an object`);
|
|
108
|
+
}
|
|
109
|
+
const labels = s.labels;
|
|
110
|
+
for (const [name, label] of Object.entries(labels)) {
|
|
111
|
+
if (name === "inherit")
|
|
112
|
+
continue;
|
|
113
|
+
if (label === false) {
|
|
114
|
+
if (rootLabelNames && !rootLabelNames.includes(name)) {
|
|
115
|
+
throw new Error(`${context}: Cannot opt out of label '${name}' - not defined in root settings.labels`);
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
validateLabel(label, name, context);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
79
122
|
if (s.deleteOrphaned !== undefined && typeof s.deleteOrphaned !== "boolean") {
|
|
80
123
|
throw new Error(`${context}: settings.deleteOrphaned must be a boolean`);
|
|
81
124
|
}
|
|
@@ -208,6 +251,10 @@ export function validateRawConfig(config) {
|
|
|
208
251
|
if (config.settings.rulesets && "inherit" in config.settings.rulesets) {
|
|
209
252
|
throw new Error("'inherit' is a reserved key and cannot be used as a ruleset name");
|
|
210
253
|
}
|
|
254
|
+
// Check for reserved key 'inherit' at root labels level
|
|
255
|
+
if (config.settings.labels && "inherit" in config.settings.labels) {
|
|
256
|
+
throw new Error("'inherit' is a reserved key and cannot be used as a label name");
|
|
257
|
+
}
|
|
211
258
|
}
|
|
212
259
|
// Validate githubHosts if provided
|
|
213
260
|
if (config.githubHosts !== undefined) {
|
|
@@ -353,7 +400,10 @@ export function validateRawConfig(config) {
|
|
|
353
400
|
? Object.keys(config.settings.rulesets).filter((k) => k !== "inherit")
|
|
354
401
|
: [];
|
|
355
402
|
const hasRootRepoSettings = config.settings?.repo !== undefined && config.settings.repo !== false;
|
|
356
|
-
|
|
403
|
+
const rootLabelNames = config.settings?.labels
|
|
404
|
+
? Object.keys(config.settings.labels).filter((k) => k !== "inherit")
|
|
405
|
+
: [];
|
|
406
|
+
validateSettings(repo.settings, `Repo ${getGitDisplayName(repo.git)}`, rootRulesetNames, hasRootRepoSettings, rootLabelNames);
|
|
357
407
|
}
|
|
358
408
|
}
|
|
359
409
|
}
|
|
@@ -377,19 +427,24 @@ export function validateForSync(config) {
|
|
|
377
427
|
}
|
|
378
428
|
/**
|
|
379
429
|
* Checks if settings contain actionable configuration.
|
|
380
|
-
* Currently only rulesets, but extensible for future settings features.
|
|
381
430
|
*/
|
|
382
431
|
export function hasActionableSettings(settings) {
|
|
383
432
|
if (!settings)
|
|
384
433
|
return false;
|
|
385
|
-
// Check for rulesets
|
|
386
|
-
if (settings.rulesets &&
|
|
434
|
+
// Check for rulesets (filter out inherit key)
|
|
435
|
+
if (settings.rulesets &&
|
|
436
|
+
Object.keys(settings.rulesets).filter((k) => k !== "inherit").length > 0) {
|
|
387
437
|
return true;
|
|
388
438
|
}
|
|
389
439
|
// Check for repo settings
|
|
390
440
|
if (settings.repo && Object.keys(settings.repo).length > 0) {
|
|
391
441
|
return true;
|
|
392
442
|
}
|
|
443
|
+
// Check for labels (filter out inherit key)
|
|
444
|
+
if (settings.labels &&
|
|
445
|
+
Object.keys(settings.labels).filter((k) => k !== "inherit").length > 0) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
393
448
|
return false;
|
|
394
449
|
}
|
|
395
450
|
/**
|
|
@@ -408,7 +463,7 @@ export function validateForSettings(config) {
|
|
|
408
463
|
const rootActionable = hasActionableSettings(config.settings);
|
|
409
464
|
const repoActionable = config.repos.some((repo) => hasActionableSettings(repo.settings));
|
|
410
465
|
if (!rootActionable && !repoActionable) {
|
|
411
|
-
throw new Error("No actionable settings configured. Currently supported: rulesets. " +
|
|
466
|
+
throw new Error("No actionable settings configured. Currently supported: rulesets, repo, labels. " +
|
|
412
467
|
"To sync files instead, use 'xfg sync'. " +
|
|
413
468
|
"See docs: https://anthony-spruyt.github.io/xfg/settings");
|
|
414
469
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { runSync, runSettings } from "./cli/index.js";
|
|
2
2
|
export type { SyncOptions, SettingsOptions, SharedOptions, } from "./cli/index.js";
|
|
3
|
-
export { type IRepositoryProcessor, type ProcessorFactory, defaultProcessorFactory, type IRulesetProcessor, type RulesetProcessorFactory, defaultRulesetProcessorFactory, type RepoSettingsProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./cli/index.js";
|
|
3
|
+
export { type IRepositoryProcessor, type ProcessorFactory, defaultProcessorFactory, type IRulesetProcessor, type RulesetProcessorFactory, defaultRulesetProcessorFactory, type RepoSettingsProcessorFactory, defaultRepoSettingsProcessorFactory, type ILabelsProcessor, type LabelsProcessorFactory, defaultLabelsProcessorFactory, } from "./cli/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Public API for library consumers
|
|
2
2
|
export { runSync, runSettings } from "./cli/index.js";
|
|
3
|
-
export { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, } from "./cli/index.js";
|
|
3
|
+
export { defaultProcessorFactory, defaultRulesetProcessorFactory, defaultRepoSettingsProcessorFactory, defaultLabelsProcessorFactory, } from "./cli/index.js";
|
|
@@ -19,6 +19,11 @@ export interface RepoSettingsPlanDetail {
|
|
|
19
19
|
property: string;
|
|
20
20
|
action: "add" | "change";
|
|
21
21
|
}
|
|
22
|
+
export interface LabelsPlanDetail {
|
|
23
|
+
name: string;
|
|
24
|
+
action: "create" | "update" | "delete" | "unchanged";
|
|
25
|
+
newName?: string;
|
|
26
|
+
}
|
|
22
27
|
export interface RepoResult {
|
|
23
28
|
repoName: string;
|
|
24
29
|
status: "succeeded" | "skipped" | "failed";
|
|
@@ -28,6 +33,7 @@ export interface RepoResult {
|
|
|
28
33
|
fileChanges?: FileChanges;
|
|
29
34
|
rulesetPlanDetails?: RulesetPlanDetail[];
|
|
30
35
|
repoSettingsPlanDetails?: RepoSettingsPlanDetail[];
|
|
36
|
+
labelsPlanDetails?: LabelsPlanDetail[];
|
|
31
37
|
}
|
|
32
38
|
export interface SummaryData {
|
|
33
39
|
title: string;
|
|
@@ -21,6 +21,9 @@ function formatChangesColumn(result) {
|
|
|
21
21
|
result.repoSettingsPlanDetails.length > 0) {
|
|
22
22
|
parts.push(formatSettingsPlanSummary(result.repoSettingsPlanDetails));
|
|
23
23
|
}
|
|
24
|
+
if (result.labelsPlanDetails && result.labelsPlanDetails.length > 0) {
|
|
25
|
+
parts.push(formatLabelsPlanSummary(result.labelsPlanDetails));
|
|
26
|
+
}
|
|
24
27
|
return parts.length > 0 ? parts.join("; ") : "-";
|
|
25
28
|
}
|
|
26
29
|
function formatStatus(result, dryRun) {
|
|
@@ -110,6 +113,19 @@ function formatSettingsPlanSummary(details) {
|
|
|
110
113
|
parts.push(`${changes} to change`);
|
|
111
114
|
return parts.join(", ") || "no changes";
|
|
112
115
|
}
|
|
116
|
+
function formatLabelsPlanSummary(details) {
|
|
117
|
+
const creates = details.filter((d) => d.action === "create").length;
|
|
118
|
+
const updates = details.filter((d) => d.action === "update").length;
|
|
119
|
+
const deletes = details.filter((d) => d.action === "delete").length;
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (creates > 0)
|
|
122
|
+
parts.push(`${creates} to create`);
|
|
123
|
+
if (updates > 0)
|
|
124
|
+
parts.push(`${updates} to update`);
|
|
125
|
+
if (deletes > 0)
|
|
126
|
+
parts.push(`${deletes} to delete`);
|
|
127
|
+
return parts.join(", ") || "no changes";
|
|
128
|
+
}
|
|
113
129
|
export function formatSummary(data) {
|
|
114
130
|
const lines = [];
|
|
115
131
|
// Header
|
|
@@ -176,6 +192,29 @@ export function formatSummary(data) {
|
|
|
176
192
|
lines.push("");
|
|
177
193
|
lines.push("</details>");
|
|
178
194
|
}
|
|
195
|
+
if (result.labelsPlanDetails && result.labelsPlanDetails.length > 0) {
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push("<details>");
|
|
198
|
+
lines.push(`<summary>${result.repoName} — Labels: ${formatLabelsPlanSummary(result.labelsPlanDetails)}</summary>`);
|
|
199
|
+
lines.push("");
|
|
200
|
+
lines.push("| Label | Action |");
|
|
201
|
+
lines.push("|-------|--------|");
|
|
202
|
+
for (const detail of result.labelsPlanDetails) {
|
|
203
|
+
const action = detail.action === "create"
|
|
204
|
+
? "+ Create"
|
|
205
|
+
: detail.action === "update"
|
|
206
|
+
? "~ Update"
|
|
207
|
+
: detail.action === "delete"
|
|
208
|
+
? "- Delete"
|
|
209
|
+
: "No change";
|
|
210
|
+
const name = detail.newName
|
|
211
|
+
? `${detail.name} \u2192 ${detail.newName}`
|
|
212
|
+
: detail.name;
|
|
213
|
+
lines.push(`| ${name} | ${action} |`);
|
|
214
|
+
}
|
|
215
|
+
lines.push("");
|
|
216
|
+
lines.push("</details>");
|
|
217
|
+
}
|
|
179
218
|
}
|
|
180
219
|
lines.push("");
|
|
181
220
|
lines.push("</details>");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PropertyDiff } from "../settings/rulesets/formatter.js";
|
|
2
|
-
import type { Ruleset } from "../config/index.js";
|
|
2
|
+
import type { Ruleset, Label } from "../config/index.js";
|
|
3
3
|
export interface SettingsReport {
|
|
4
4
|
repos: RepoChanges[];
|
|
5
5
|
totals: {
|
|
@@ -12,12 +12,18 @@ export interface SettingsReport {
|
|
|
12
12
|
update: number;
|
|
13
13
|
delete: number;
|
|
14
14
|
};
|
|
15
|
+
labels: {
|
|
16
|
+
create: number;
|
|
17
|
+
update: number;
|
|
18
|
+
delete: number;
|
|
19
|
+
};
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
export interface RepoChanges {
|
|
18
23
|
repoName: string;
|
|
19
24
|
settings: SettingChange[];
|
|
20
25
|
rulesets: RulesetChange[];
|
|
26
|
+
labels: LabelChange[];
|
|
21
27
|
error?: string;
|
|
22
28
|
}
|
|
23
29
|
export interface SettingChange {
|
|
@@ -32,6 +38,17 @@ export interface RulesetChange {
|
|
|
32
38
|
propertyDiffs?: PropertyDiff[];
|
|
33
39
|
config?: Ruleset;
|
|
34
40
|
}
|
|
41
|
+
export interface LabelChange {
|
|
42
|
+
name: string;
|
|
43
|
+
action: "create" | "update" | "delete";
|
|
44
|
+
newName?: string;
|
|
45
|
+
propertyChanges?: {
|
|
46
|
+
property: string;
|
|
47
|
+
oldValue?: string;
|
|
48
|
+
newValue?: string;
|
|
49
|
+
}[];
|
|
50
|
+
config?: Label;
|
|
51
|
+
}
|
|
35
52
|
export declare function formatSettingsReportCLI(report: SettingsReport): string[];
|
|
36
53
|
export declare function formatValuePlain(val: unknown): string;
|
|
37
54
|
export declare function formatRulesetConfigPlain(config: Ruleset): string[];
|
|
@@ -68,6 +68,8 @@ function formatSummary(totals) {
|
|
|
68
68
|
const parts = [];
|
|
69
69
|
const settingsTotal = totals.settings.add + totals.settings.change;
|
|
70
70
|
const rulesetsTotal = totals.rulesets.create + totals.rulesets.update + totals.rulesets.delete;
|
|
71
|
+
const labelTotals = totals.labels;
|
|
72
|
+
const labelsTotal = labelTotals.create + labelTotals.update + labelTotals.delete;
|
|
71
73
|
if (settingsTotal > 0) {
|
|
72
74
|
const settingWord = settingsTotal === 1 ? "setting" : "settings";
|
|
73
75
|
const actions = [];
|
|
@@ -88,6 +90,17 @@ function formatSummary(totals) {
|
|
|
88
90
|
actions.push(`${totals.rulesets.delete} to delete`);
|
|
89
91
|
parts.push(`${rulesetsTotal} ${rulesetWord} (${actions.join(", ")})`);
|
|
90
92
|
}
|
|
93
|
+
if (labelsTotal > 0) {
|
|
94
|
+
const labelWord = labelsTotal === 1 ? "label" : "labels";
|
|
95
|
+
const actions = [];
|
|
96
|
+
if (labelTotals.create > 0)
|
|
97
|
+
actions.push(`${labelTotals.create} to create`);
|
|
98
|
+
if (labelTotals.update > 0)
|
|
99
|
+
actions.push(`${labelTotals.update} to update`);
|
|
100
|
+
if (labelTotals.delete > 0)
|
|
101
|
+
actions.push(`${labelTotals.delete} to delete`);
|
|
102
|
+
parts.push(`${labelsTotal} ${labelWord} (${actions.join(", ")})`);
|
|
103
|
+
}
|
|
91
104
|
if (parts.length === 0) {
|
|
92
105
|
return "No changes";
|
|
93
106
|
}
|
|
@@ -101,6 +114,7 @@ export function formatSettingsReportCLI(report) {
|
|
|
101
114
|
for (const repo of report.repos) {
|
|
102
115
|
if (repo.settings.length === 0 &&
|
|
103
116
|
repo.rulesets.length === 0 &&
|
|
117
|
+
repo.labels.length === 0 &&
|
|
104
118
|
!repo.error) {
|
|
105
119
|
continue;
|
|
106
120
|
}
|
|
@@ -140,6 +154,41 @@ export function formatSettingsReportCLI(report) {
|
|
|
140
154
|
lines.push(chalk.red(` - ruleset "${ruleset.name}"`));
|
|
141
155
|
}
|
|
142
156
|
}
|
|
157
|
+
// Labels
|
|
158
|
+
for (const label of repo.labels) {
|
|
159
|
+
if (label.action === "create") {
|
|
160
|
+
lines.push(chalk.green(` + label "${label.name}"`));
|
|
161
|
+
if (label.config) {
|
|
162
|
+
lines.push(chalk.green(` color: "${label.config.color}"`));
|
|
163
|
+
if (label.config.description !== undefined) {
|
|
164
|
+
lines.push(chalk.green(` description: "${label.config.description}"`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (label.action === "update") {
|
|
169
|
+
if (label.newName) {
|
|
170
|
+
lines.push(chalk.yellow(` ~ label "${label.name}" \u2192 "${label.newName}"`));
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
lines.push(chalk.yellow(` ~ label "${label.name}"`));
|
|
174
|
+
}
|
|
175
|
+
if (label.propertyChanges) {
|
|
176
|
+
for (const prop of label.propertyChanges) {
|
|
177
|
+
if (prop.property === "new_name")
|
|
178
|
+
continue;
|
|
179
|
+
if (prop.oldValue !== undefined) {
|
|
180
|
+
lines.push(chalk.yellow(` ${prop.property}: "${prop.oldValue}" \u2192 "${prop.newValue}"`));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
lines.push(chalk.yellow(` ${prop.property}: "${prop.newValue}"`));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (label.action === "delete") {
|
|
189
|
+
lines.push(chalk.red(` - label "${label.name}"`));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
143
192
|
// Error
|
|
144
193
|
if (repo.error) {
|
|
145
194
|
lines.push(chalk.red(` Error: ${repo.error}`));
|
|
@@ -230,6 +279,7 @@ export function formatSettingsReportMarkdown(report, dryRun) {
|
|
|
230
279
|
for (const repo of report.repos) {
|
|
231
280
|
if (repo.settings.length === 0 &&
|
|
232
281
|
repo.rulesets.length === 0 &&
|
|
282
|
+
repo.labels.length === 0 &&
|
|
233
283
|
!repo.error) {
|
|
234
284
|
continue;
|
|
235
285
|
}
|
|
@@ -274,6 +324,40 @@ export function formatSettingsReportMarkdown(report, dryRun) {
|
|
|
274
324
|
diffLines.push(`- ruleset "${ruleset.name}"`);
|
|
275
325
|
}
|
|
276
326
|
}
|
|
327
|
+
for (const label of repo.labels) {
|
|
328
|
+
if (label.action === "create") {
|
|
329
|
+
diffLines.push(`+ label "${label.name}"`);
|
|
330
|
+
if (label.config) {
|
|
331
|
+
diffLines.push(`+ color: "${label.config.color}"`);
|
|
332
|
+
if (label.config.description !== undefined) {
|
|
333
|
+
diffLines.push(`+ description: "${label.config.description}"`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (label.action === "update") {
|
|
338
|
+
if (label.newName) {
|
|
339
|
+
diffLines.push(`! label "${label.name}" \u2192 "${label.newName}"`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
diffLines.push(`! label "${label.name}"`);
|
|
343
|
+
}
|
|
344
|
+
if (label.propertyChanges) {
|
|
345
|
+
for (const prop of label.propertyChanges) {
|
|
346
|
+
if (prop.property === "new_name")
|
|
347
|
+
continue;
|
|
348
|
+
if (prop.oldValue !== undefined) {
|
|
349
|
+
diffLines.push(`! ${prop.property}: "${prop.oldValue}" \u2192 "${prop.newValue}"`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
diffLines.push(`! ${prop.property}: "${prop.newValue}"`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (label.action === "delete") {
|
|
358
|
+
diffLines.push(`- label "${label.name}"`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
277
361
|
if (repo.error) {
|
|
278
362
|
diffLines.push(`- Error: ${repo.error}`);
|
|
279
363
|
}
|
|
@@ -65,6 +65,19 @@ function formatCombinedSummary(input) {
|
|
|
65
65
|
actions.push(`${t.rulesets.delete} ${dry ? "to delete" : "deleted"}`);
|
|
66
66
|
parts.push(`${rulesetsTotal} ${rulesetWord} (${actions.join(", ")})`);
|
|
67
67
|
}
|
|
68
|
+
const lt = t.labels;
|
|
69
|
+
const labelsTotal = lt.create + lt.update + lt.delete;
|
|
70
|
+
if (labelsTotal > 0) {
|
|
71
|
+
const labelWord = labelsTotal === 1 ? "label" : "labels";
|
|
72
|
+
const actions = [];
|
|
73
|
+
if (lt.create > 0)
|
|
74
|
+
actions.push(`${lt.create} ${dry ? "to create" : "created"}`);
|
|
75
|
+
if (lt.update > 0)
|
|
76
|
+
actions.push(`${lt.update} ${dry ? "to update" : "updated"}`);
|
|
77
|
+
if (lt.delete > 0)
|
|
78
|
+
actions.push(`${lt.delete} ${dry ? "to delete" : "deleted"}`);
|
|
79
|
+
parts.push(`${labelsTotal} ${labelWord} (${actions.join(", ")})`);
|
|
80
|
+
}
|
|
68
81
|
}
|
|
69
82
|
if (parts.length === 0) {
|
|
70
83
|
return "No changes";
|
|
@@ -77,7 +90,10 @@ function hasAnyChanges(input) {
|
|
|
77
90
|
return true;
|
|
78
91
|
if (input.sync?.repos.some((r) => r.files.length > 0 || r.error))
|
|
79
92
|
return true;
|
|
80
|
-
if (input.settings?.repos.some((r) => r.settings.length > 0 ||
|
|
93
|
+
if (input.settings?.repos.some((r) => r.settings.length > 0 ||
|
|
94
|
+
r.rulesets.length > 0 ||
|
|
95
|
+
r.labels.length > 0 ||
|
|
96
|
+
r.error))
|
|
81
97
|
return true;
|
|
82
98
|
return false;
|
|
83
99
|
}
|
|
@@ -163,6 +179,28 @@ function renderSettingsLines(settingsRepo, diffLines) {
|
|
|
163
179
|
diffLines.push(`- ruleset "${ruleset.name}"`);
|
|
164
180
|
}
|
|
165
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
|
+
}
|
|
166
204
|
if (settingsRepo.error) {
|
|
167
205
|
diffLines.push(`- Error: ${settingsRepo.error}`);
|
|
168
206
|
}
|
|
@@ -212,6 +250,7 @@ export function formatUnifiedSummaryMarkdown(input) {
|
|
|
212
250
|
const hasSettingsChanges = settingsRepo &&
|
|
213
251
|
(settingsRepo.settings.length > 0 ||
|
|
214
252
|
settingsRepo.rulesets.length > 0 ||
|
|
253
|
+
settingsRepo.labels.length > 0 ||
|
|
215
254
|
settingsRepo.error);
|
|
216
255
|
if (!hasLcChange && !hasSyncChanges && !hasSettingsChanges)
|
|
217
256
|
continue;
|
package/dist/settings/index.d.ts
CHANGED
package/dist/settings/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Label } from "../../config/types.js";
|
|
2
|
+
export interface GitHubLabelPayload {
|
|
3
|
+
name: string;
|
|
4
|
+
new_name?: string;
|
|
5
|
+
color: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Strips '#' prefix and lowercases hex color.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeColor(color: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Converts a label config entry to a GitHub API payload.
|
|
14
|
+
*/
|
|
15
|
+
export declare function labelConfigToPayload(name: string, label: Label): GitHubLabelPayload;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips '#' prefix and lowercases hex color.
|
|
3
|
+
*/
|
|
4
|
+
export function normalizeColor(color) {
|
|
5
|
+
return color.replace(/^#/, "").toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Converts a label config entry to a GitHub API payload.
|
|
9
|
+
*/
|
|
10
|
+
export function labelConfigToPayload(name, label) {
|
|
11
|
+
const payload = {
|
|
12
|
+
name,
|
|
13
|
+
color: normalizeColor(label.color),
|
|
14
|
+
};
|
|
15
|
+
if (label.new_name !== undefined) {
|
|
16
|
+
payload.new_name = label.new_name;
|
|
17
|
+
}
|
|
18
|
+
if (label.description !== undefined) {
|
|
19
|
+
payload.description = label.description;
|
|
20
|
+
}
|
|
21
|
+
return payload;
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Label } from "../../config/types.js";
|
|
2
|
+
import type { GitHubLabel } from "./types.js";
|
|
3
|
+
export type LabelAction = "create" | "update" | "delete" | "unchanged";
|
|
4
|
+
export interface LabelChange {
|
|
5
|
+
action: LabelAction;
|
|
6
|
+
name: string;
|
|
7
|
+
newName?: string;
|
|
8
|
+
current?: GitHubLabel;
|
|
9
|
+
desired?: Label;
|
|
10
|
+
propertyChanges?: {
|
|
11
|
+
property: string;
|
|
12
|
+
oldValue?: string;
|
|
13
|
+
newValue?: string;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compares current labels (from GitHub) with desired labels (from config).
|
|
18
|
+
*
|
|
19
|
+
* Matching is case-insensitive by name (GitHub label names are case-insensitive).
|
|
20
|
+
* Color comparison is case-insensitive bare hex (strip #, lowercase both sides).
|
|
21
|
+
* Description: undefined in config means "do not compare" (leave current value).
|
|
22
|
+
* An explicit empty string "" means "set to empty."
|
|
23
|
+
* GitHub API returns null for labels without descriptions — treat null and
|
|
24
|
+
* undefined as equivalent when comparing (neither triggers an update).
|
|
25
|
+
*
|
|
26
|
+
* @param current - Current labels from GitHub API
|
|
27
|
+
* @param desired - Desired labels from config (name -> label)
|
|
28
|
+
* @param managedLabels - Names of labels managed by xfg (from manifest)
|
|
29
|
+
* @param noDelete - If true, skip delete operations
|
|
30
|
+
* @returns Array of changes to apply
|
|
31
|
+
* @throws Error if rename collisions are detected
|
|
32
|
+
*/
|
|
33
|
+
export declare function diffLabels(current: GitHubLabel[], desired: Record<string, Label>, managedLabels: string[], noDelete: boolean): LabelChange[];
|