@aspruyt/xfg 3.1.5 → 3.2.0
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/config-normalizer.js +52 -4
- package/dist/config-validator.d.ts +1 -1
- package/dist/config-validator.js +31 -2
- package/dist/config.d.ts +6 -2
- package/package.json +1 -1
|
@@ -60,14 +60,30 @@ export function mergeSettings(root, perRepo) {
|
|
|
60
60
|
// Merge rulesets by name - each ruleset is deep merged
|
|
61
61
|
const rootRulesets = root?.rulesets ?? {};
|
|
62
62
|
const repoRulesets = perRepo?.rulesets ?? {};
|
|
63
|
+
// Check if repo opts out of all inherited rulesets
|
|
64
|
+
const inheritRulesets = repoRulesets?.inherit !== false;
|
|
63
65
|
const allRulesetNames = new Set([
|
|
64
|
-
...Object.keys(rootRulesets),
|
|
65
|
-
...Object.keys(repoRulesets),
|
|
66
|
+
...Object.keys(rootRulesets).filter((name) => name !== "inherit"),
|
|
67
|
+
...Object.keys(repoRulesets).filter((name) => name !== "inherit"),
|
|
66
68
|
]);
|
|
67
69
|
if (allRulesetNames.size > 0) {
|
|
68
70
|
result.rulesets = {};
|
|
69
71
|
for (const name of allRulesetNames) {
|
|
70
|
-
|
|
72
|
+
const rootRuleset = rootRulesets[name];
|
|
73
|
+
const repoRuleset = repoRulesets[name];
|
|
74
|
+
// Skip if repo explicitly opts out of this ruleset
|
|
75
|
+
if (repoRuleset === false) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Skip root rulesets if inherit: false (unless repo has override)
|
|
79
|
+
if (!inheritRulesets && !repoRuleset && rootRuleset) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
result.rulesets[name] = mergeRuleset(rootRuleset, repoRuleset);
|
|
83
|
+
}
|
|
84
|
+
// Clean up empty rulesets object
|
|
85
|
+
if (Object.keys(result.rulesets).length === 0) {
|
|
86
|
+
delete result.rulesets;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
// deleteOrphaned: per-repo overrides root
|
|
@@ -89,13 +105,23 @@ export function normalizeConfig(raw) {
|
|
|
89
105
|
const gitUrls = Array.isArray(rawRepo.git) ? rawRepo.git : [rawRepo.git];
|
|
90
106
|
for (const gitUrl of gitUrls) {
|
|
91
107
|
const files = [];
|
|
108
|
+
// Check if repo opts out of all inherited files
|
|
109
|
+
const inheritFiles = rawRepo.files?.inherit !==
|
|
110
|
+
false;
|
|
92
111
|
// Step 2: Process each file definition
|
|
93
112
|
for (const fileName of fileNames) {
|
|
113
|
+
// Skip reserved key
|
|
114
|
+
if (fileName === "inherit")
|
|
115
|
+
continue;
|
|
94
116
|
const repoOverride = rawRepo.files?.[fileName];
|
|
95
117
|
// Skip excluded files (set to false)
|
|
96
118
|
if (repoOverride === false) {
|
|
97
119
|
continue;
|
|
98
120
|
}
|
|
121
|
+
// Skip if inherit: false and no repo-specific override
|
|
122
|
+
if (!inheritFiles && !repoOverride) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
99
125
|
const fileConfig = raw.files[fileName];
|
|
100
126
|
const fileStrategy = fileConfig.mergeStrategy ?? "replace";
|
|
101
127
|
// Step 3: Compute merged content for this file
|
|
@@ -190,12 +216,34 @@ export function normalizeConfig(raw) {
|
|
|
190
216
|
});
|
|
191
217
|
}
|
|
192
218
|
}
|
|
219
|
+
// Normalize root settings (filter out inherit key if present)
|
|
220
|
+
let normalizedRootSettings;
|
|
221
|
+
if (raw.settings) {
|
|
222
|
+
normalizedRootSettings = {};
|
|
223
|
+
if (raw.settings.rulesets) {
|
|
224
|
+
const filteredRulesets = {};
|
|
225
|
+
for (const [name, ruleset] of Object.entries(raw.settings.rulesets)) {
|
|
226
|
+
if (name === "inherit" || ruleset === false)
|
|
227
|
+
continue;
|
|
228
|
+
filteredRulesets[name] = ruleset;
|
|
229
|
+
}
|
|
230
|
+
if (Object.keys(filteredRulesets).length > 0) {
|
|
231
|
+
normalizedRootSettings.rulesets = filteredRulesets;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (raw.settings.deleteOrphaned !== undefined) {
|
|
235
|
+
normalizedRootSettings.deleteOrphaned = raw.settings.deleteOrphaned;
|
|
236
|
+
}
|
|
237
|
+
if (Object.keys(normalizedRootSettings).length === 0) {
|
|
238
|
+
normalizedRootSettings = undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
193
241
|
return {
|
|
194
242
|
id: raw.id,
|
|
195
243
|
repos: expandedRepos,
|
|
196
244
|
prTemplate: raw.prTemplate,
|
|
197
245
|
githubHosts: raw.githubHosts,
|
|
198
246
|
deleteOrphaned: raw.deleteOrphaned,
|
|
199
|
-
settings:
|
|
247
|
+
settings: normalizedRootSettings,
|
|
200
248
|
};
|
|
201
249
|
}
|
|
@@ -7,7 +7,7 @@ export declare function validateRawConfig(config: RawConfig): void;
|
|
|
7
7
|
/**
|
|
8
8
|
* Validates settings object containing rulesets.
|
|
9
9
|
*/
|
|
10
|
-
export declare function validateSettings(settings: unknown, context: string): void;
|
|
10
|
+
export declare function validateSettings(settings: unknown, context: string, rootRulesetNames?: string[]): void;
|
|
11
11
|
/**
|
|
12
12
|
* Validates that config is suitable for the sync command.
|
|
13
13
|
* @throws Error if files section is missing or empty
|
package/dist/config-validator.js
CHANGED
|
@@ -49,6 +49,10 @@ export function validateRawConfig(config) {
|
|
|
49
49
|
"Use 'files' to sync configuration files, or 'settings' to manage repository settings.");
|
|
50
50
|
}
|
|
51
51
|
const fileNames = hasFiles ? Object.keys(config.files) : [];
|
|
52
|
+
// Check for reserved key 'inherit' at root files level
|
|
53
|
+
if (hasFiles && "inherit" in config.files) {
|
|
54
|
+
throw new Error("'inherit' is a reserved key and cannot be used as a filename");
|
|
55
|
+
}
|
|
52
56
|
// Validate each file definition
|
|
53
57
|
for (const fileName of fileNames) {
|
|
54
58
|
validateFileName(fileName);
|
|
@@ -127,6 +131,10 @@ export function validateRawConfig(config) {
|
|
|
127
131
|
// Validate root settings
|
|
128
132
|
if (config.settings !== undefined) {
|
|
129
133
|
validateSettings(config.settings, "Root");
|
|
134
|
+
// Check for reserved key 'inherit' at root rulesets level
|
|
135
|
+
if (config.settings.rulesets && "inherit" in config.settings.rulesets) {
|
|
136
|
+
throw new Error("'inherit' is a reserved key and cannot be used as a ruleset name");
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
139
|
// Validate githubHosts if provided
|
|
132
140
|
if (config.githubHosts !== undefined) {
|
|
@@ -161,6 +169,14 @@ export function validateRawConfig(config) {
|
|
|
161
169
|
throw new Error(`Repo at index ${i}: files must be an object`);
|
|
162
170
|
}
|
|
163
171
|
for (const fileName of Object.keys(repo.files)) {
|
|
172
|
+
// Skip reserved key 'inherit'
|
|
173
|
+
if (fileName === "inherit") {
|
|
174
|
+
const inheritValue = repo.files.inherit;
|
|
175
|
+
if (typeof inheritValue !== "boolean") {
|
|
176
|
+
throw new Error(`Repo at index ${i}: files.inherit must be a boolean`);
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
164
180
|
// Ensure the file is defined at root level
|
|
165
181
|
if (!config.files || !config.files[fileName]) {
|
|
166
182
|
throw new Error(`Repo at index ${i} references undefined file '${fileName}'. File must be defined in root 'files' object.`);
|
|
@@ -233,7 +249,10 @@ export function validateRawConfig(config) {
|
|
|
233
249
|
}
|
|
234
250
|
// Validate per-repo settings
|
|
235
251
|
if (repo.settings !== undefined) {
|
|
236
|
-
|
|
252
|
+
const rootRulesetNames = config.settings?.rulesets
|
|
253
|
+
? Object.keys(config.settings.rulesets).filter((k) => k !== "inherit")
|
|
254
|
+
: [];
|
|
255
|
+
validateSettings(repo.settings, `Repo ${getGitDisplayName(repo.git)}`, rootRulesetNames);
|
|
237
256
|
}
|
|
238
257
|
}
|
|
239
258
|
}
|
|
@@ -465,7 +484,7 @@ function validateRuleset(ruleset, name, context) {
|
|
|
465
484
|
/**
|
|
466
485
|
* Validates settings object containing rulesets.
|
|
467
486
|
*/
|
|
468
|
-
export function validateSettings(settings, context) {
|
|
487
|
+
export function validateSettings(settings, context, rootRulesetNames) {
|
|
469
488
|
if (typeof settings !== "object" ||
|
|
470
489
|
settings === null ||
|
|
471
490
|
Array.isArray(settings)) {
|
|
@@ -480,6 +499,16 @@ export function validateSettings(settings, context) {
|
|
|
480
499
|
}
|
|
481
500
|
const rulesets = s.rulesets;
|
|
482
501
|
for (const [name, ruleset] of Object.entries(rulesets)) {
|
|
502
|
+
// Skip reserved key
|
|
503
|
+
if (name === "inherit")
|
|
504
|
+
continue;
|
|
505
|
+
// Check for opt-out of non-existent root ruleset
|
|
506
|
+
if (ruleset === false) {
|
|
507
|
+
if (rootRulesetNames && !rootRulesetNames.includes(name)) {
|
|
508
|
+
throw new Error(`${context}: Cannot opt out of '${name}' - not defined in root settings.rulesets`);
|
|
509
|
+
}
|
|
510
|
+
continue; // Skip further validation for false entries
|
|
511
|
+
}
|
|
483
512
|
validateRuleset(ruleset, name, context);
|
|
484
513
|
}
|
|
485
514
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -239,12 +239,16 @@ export interface RawRepoFileOverride {
|
|
|
239
239
|
deleteOrphaned?: boolean;
|
|
240
240
|
}
|
|
241
241
|
export interface RawRepoSettings {
|
|
242
|
-
rulesets?: Record<string, Ruleset
|
|
242
|
+
rulesets?: Record<string, Ruleset | false> & {
|
|
243
|
+
inherit?: boolean;
|
|
244
|
+
};
|
|
243
245
|
deleteOrphaned?: boolean;
|
|
244
246
|
}
|
|
245
247
|
export interface RawRepoConfig {
|
|
246
248
|
git: string | string[];
|
|
247
|
-
files?: Record<string, RawRepoFileOverride | false
|
|
249
|
+
files?: Record<string, RawRepoFileOverride | false> & {
|
|
250
|
+
inherit?: boolean;
|
|
251
|
+
};
|
|
248
252
|
prOptions?: PRMergeOptions;
|
|
249
253
|
settings?: RawRepoSettings;
|
|
250
254
|
}
|