@aspruyt/xfg 5.3.1 → 5.5.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/{shared → cli}/branch-utils.js +1 -1
- package/dist/cli/settings-report-builder.d.ts +6 -1
- package/dist/cli/settings-report-builder.js +19 -0
- package/dist/cli/sync-command.js +38 -23
- package/dist/cli/sync-report-builder.d.ts +2 -1
- package/dist/cli/types.d.ts +5 -2
- package/dist/config/config-merger.d.ts +6 -0
- package/dist/config/config-merger.js +66 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +50 -6
- package/dist/config/merge.d.ts +4 -0
- package/dist/config/merge.js +47 -23
- package/dist/config/normalizer.js +31 -1
- package/dist/config/types.d.ts +12 -0
- package/dist/config/validator.js +104 -46
- package/dist/config/validators/ruleset-validator.js +46 -16
- package/dist/lifecycle/ado-migration-source.d.ts +1 -1
- package/dist/lifecycle/github-lifecycle-provider.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +1 -1
- package/dist/lifecycle/lifecycle-helpers.d.ts +2 -1
- package/dist/lifecycle/lifecycle-helpers.js +1 -1
- package/dist/lifecycle/repo-lifecycle-factory.d.ts +1 -1
- package/dist/lifecycle/repo-lifecycle-manager.js +1 -1
- package/dist/output/types.d.ts +2 -1
- package/dist/settings/code-scanning/diff.d.ts +19 -0
- package/dist/settings/code-scanning/diff.js +75 -0
- package/dist/settings/code-scanning/formatter.d.ts +17 -0
- package/dist/settings/code-scanning/formatter.js +37 -0
- package/dist/settings/code-scanning/github-code-scanning-strategy.d.ts +19 -0
- package/dist/settings/code-scanning/github-code-scanning-strategy.js +20 -0
- package/dist/settings/code-scanning/index.d.ts +3 -0
- package/dist/settings/code-scanning/index.js +4 -0
- package/dist/settings/code-scanning/processor.d.ts +20 -0
- package/dist/settings/code-scanning/processor.js +81 -0
- package/dist/settings/code-scanning/types.d.ts +22 -0
- package/dist/settings/code-scanning/types.js +1 -0
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -0
- package/dist/settings/labels/github-labels-strategy.d.ts +2 -2
- package/dist/settings/repo-settings/diff.d.ts +2 -1
- package/dist/settings/repo-settings/github-repo-settings-strategy.d.ts +1 -1
- package/dist/settings/repo-settings/processor.d.ts +4 -2
- package/dist/settings/repo-settings/processor.js +14 -16
- package/dist/settings/rulesets/github-ruleset-strategy.d.ts +2 -2
- package/dist/settings/rulesets/processor.d.ts +1 -1
- package/dist/shared/cleanup-utils.d.ts +8 -0
- package/dist/shared/cleanup-utils.js +15 -0
- package/dist/shared/gh-api-utils.d.ts +2 -2
- package/dist/shared/gh-api-utils.js +7 -4
- package/dist/shared/repo-detector.d.ts +1 -20
- package/dist/shared/repo-detector.js +3 -45
- package/dist/shared/repo-info-utils.d.ts +20 -0
- package/dist/shared/repo-info-utils.js +46 -0
- package/dist/shared/repo-metadata-provider.d.ts +27 -0
- package/dist/shared/repo-metadata-provider.js +20 -0
- package/dist/shared/retry-utils.d.ts +1 -1
- package/dist/shared/retry-utils.js +1 -1
- package/dist/shared/type-guards.d.ts +0 -8
- package/dist/shared/type-guards.js +0 -14
- package/dist/shared/xfg-template.js +2 -2
- package/dist/sync/auth-options-builder.d.ts +1 -1
- package/dist/sync/branch-manager.d.ts +3 -7
- package/dist/sync/commit-push-manager.d.ts +1 -1
- package/dist/sync/commit-push-manager.js +2 -2
- package/dist/sync/diff-utils.js +0 -7
- package/dist/sync/manifest.js +1 -8
- package/dist/sync/repository-processor.d.ts +1 -1
- package/dist/sync/repository-processor.js +1 -2
- package/dist/sync/repository-session.d.ts +2 -2
- package/dist/sync/repository-session.js +1 -1
- package/dist/sync/sync-workflow.d.ts +1 -1
- package/dist/sync/sync-workflow.js +1 -1
- package/dist/vcs/authenticated-git-ops.js +2 -2
- package/dist/vcs/azure-pr-strategy.d.ts +1 -1
- package/dist/vcs/azure-pr-strategy.js +2 -1
- package/dist/vcs/commit-strategy-selector.d.ts +2 -2
- package/dist/vcs/git-commit-strategy.d.ts +1 -1
- package/dist/vcs/git-ops.d.ts +1 -1
- package/dist/vcs/git-ops.js +1 -1
- package/dist/vcs/github-pr-strategy.js +3 -2
- package/dist/vcs/gitlab-pr-strategy.d.ts +1 -1
- package/dist/vcs/gitlab-pr-strategy.js +4 -3
- package/dist/vcs/graphql-commit-strategy.d.ts +1 -1
- package/dist/vcs/index.d.ts +2 -0
- package/dist/vcs/index.js +3 -0
- package/dist/vcs/pr-creator.d.ts +2 -2
- package/dist/vcs/pr-creator.js +1 -1
- package/dist/vcs/pr-strategy-factory.d.ts +2 -2
- package/dist/vcs/pr-strategy.d.ts +4 -2
- package/dist/vcs/pr-strategy.js +7 -2
- package/package.json +2 -1
- /package/dist/{shared → cli}/branch-utils.d.ts +0 -0
package/dist/config/validator.js
CHANGED
|
@@ -124,6 +124,8 @@ function buildRootSettingsContext(config) {
|
|
|
124
124
|
? Object.keys(config.settings.rulesets).filter((k) => k !== "inherit")
|
|
125
125
|
: [],
|
|
126
126
|
hasRepoSettings: config.settings?.repo !== undefined && config.settings.repo !== false,
|
|
127
|
+
hasCodeScanningSettings: config.settings?.codeScanning !== undefined &&
|
|
128
|
+
config.settings.codeScanning !== false,
|
|
127
129
|
labelNames: config.settings?.labels
|
|
128
130
|
? Object.keys(config.settings.labels).filter((k) => k !== "inherit")
|
|
129
131
|
: [],
|
|
@@ -191,6 +193,58 @@ function validateSettings(settings, context, rootCtx) {
|
|
|
191
193
|
validateRepoSettings(settings.repo, context);
|
|
192
194
|
}
|
|
193
195
|
}
|
|
196
|
+
if (settings.codeScanning !== undefined) {
|
|
197
|
+
if (settings.codeScanning === false) {
|
|
198
|
+
if (!rootCtx) {
|
|
199
|
+
throw new ValidationError(`${context}: codeScanning: false is not valid at root level. Define codeScanning settings or remove the field.`);
|
|
200
|
+
}
|
|
201
|
+
// Per-repo level — check root has codeScanning settings to opt out of
|
|
202
|
+
if (!rootCtx.hasCodeScanningSettings) {
|
|
203
|
+
throw new ValidationError(`${context}: Cannot opt out of code scanning settings — not defined in root settings.codeScanning`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
validateCodeScanningSettings(settings.codeScanning, `${context} codeScanning`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const VALID_CODE_SCANNING_STATES = ["configured", "not-configured"];
|
|
212
|
+
const VALID_CODE_SCANNING_QUERY_SUITES = ["default", "extended"];
|
|
213
|
+
const VALID_CODE_SCANNING_LANGUAGES = [
|
|
214
|
+
"actions",
|
|
215
|
+
"c-cpp",
|
|
216
|
+
"csharp",
|
|
217
|
+
"go",
|
|
218
|
+
"java-kotlin",
|
|
219
|
+
"javascript-typescript",
|
|
220
|
+
"python",
|
|
221
|
+
"ruby",
|
|
222
|
+
"swift",
|
|
223
|
+
];
|
|
224
|
+
function validateCodeScanningSettings(settings, context) {
|
|
225
|
+
if (!isPlainObject(settings)) {
|
|
226
|
+
throw new ValidationError(`${context}: must be an object`);
|
|
227
|
+
}
|
|
228
|
+
if (settings.state === undefined) {
|
|
229
|
+
throw new ValidationError(`${context}: state is required`);
|
|
230
|
+
}
|
|
231
|
+
if (!VALID_CODE_SCANNING_STATES.includes(settings.state)) {
|
|
232
|
+
throw new ValidationError(`${context}: state must be one of: ${VALID_CODE_SCANNING_STATES.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
if (settings.querySuite !== undefined &&
|
|
235
|
+
!VALID_CODE_SCANNING_QUERY_SUITES.includes(settings.querySuite)) {
|
|
236
|
+
throw new ValidationError(`${context}: querySuite must be one of: ${VALID_CODE_SCANNING_QUERY_SUITES.join(", ")}`);
|
|
237
|
+
}
|
|
238
|
+
if (settings.languages !== undefined) {
|
|
239
|
+
if (!Array.isArray(settings.languages)) {
|
|
240
|
+
throw new ValidationError(`${context}: languages must be an array`);
|
|
241
|
+
}
|
|
242
|
+
for (const lang of settings.languages) {
|
|
243
|
+
if (!VALID_CODE_SCANNING_LANGUAGES.includes(lang)) {
|
|
244
|
+
throw new ValidationError(`${context}: invalid language "${lang}". Valid languages: ${VALID_CODE_SCANNING_LANGUAGES.join(", ")}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
194
248
|
}
|
|
195
249
|
function validateConfigId(config) {
|
|
196
250
|
if (!config.id || typeof config.id !== "string") {
|
|
@@ -362,6 +416,24 @@ function validateGroups(config) {
|
|
|
362
416
|
// Validate no circular extends after individual validation
|
|
363
417
|
validateNoCircularExtends(config.groups);
|
|
364
418
|
}
|
|
419
|
+
function validateGroupRefArray(arr, fieldName, ctx, groupNames) {
|
|
420
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
421
|
+
throw new ValidationError(`${ctx}: '${fieldName}' must be a non-empty array of strings`);
|
|
422
|
+
}
|
|
423
|
+
const seen = new Set();
|
|
424
|
+
for (const name of arr) {
|
|
425
|
+
if (typeof name !== "string") {
|
|
426
|
+
throw new ValidationError(`${ctx}: '${fieldName}' entries must be strings`);
|
|
427
|
+
}
|
|
428
|
+
if (!groupNames.includes(name)) {
|
|
429
|
+
throw new ValidationError(`${ctx}: group '${name}' in ${fieldName} is not defined in root 'groups'`);
|
|
430
|
+
}
|
|
431
|
+
if (seen.has(name)) {
|
|
432
|
+
throw new ValidationError(`${ctx}: duplicate group '${name}' in ${fieldName}`);
|
|
433
|
+
}
|
|
434
|
+
seen.add(name);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
365
437
|
function validateConditionalGroups(config) {
|
|
366
438
|
if (config.conditionalGroups === undefined)
|
|
367
439
|
return;
|
|
@@ -382,40 +454,10 @@ function validateConditionalGroups(config) {
|
|
|
382
454
|
throw new ValidationError(`${ctx}: 'when' must have at least one of 'allOf' or 'anyOf'`);
|
|
383
455
|
}
|
|
384
456
|
if (allOf !== undefined) {
|
|
385
|
-
|
|
386
|
-
throw new ValidationError(`${ctx}: 'allOf' must be a non-empty array of strings`);
|
|
387
|
-
}
|
|
388
|
-
const seen = new Set();
|
|
389
|
-
for (const name of allOf) {
|
|
390
|
-
if (typeof name !== "string") {
|
|
391
|
-
throw new ValidationError(`${ctx}: 'allOf' entries must be strings`);
|
|
392
|
-
}
|
|
393
|
-
if (!groupNames.includes(name)) {
|
|
394
|
-
throw new ValidationError(`${ctx}: group '${name}' in allOf is not defined in root 'groups'`);
|
|
395
|
-
}
|
|
396
|
-
if (seen.has(name)) {
|
|
397
|
-
throw new ValidationError(`${ctx}: duplicate group '${name}' in allOf`);
|
|
398
|
-
}
|
|
399
|
-
seen.add(name);
|
|
400
|
-
}
|
|
457
|
+
validateGroupRefArray(allOf, "allOf", ctx, groupNames);
|
|
401
458
|
}
|
|
402
459
|
if (anyOf !== undefined) {
|
|
403
|
-
|
|
404
|
-
throw new ValidationError(`${ctx}: 'anyOf' must be a non-empty array of strings`);
|
|
405
|
-
}
|
|
406
|
-
const seen = new Set();
|
|
407
|
-
for (const name of anyOf) {
|
|
408
|
-
if (typeof name !== "string") {
|
|
409
|
-
throw new ValidationError(`${ctx}: 'anyOf' entries must be strings`);
|
|
410
|
-
}
|
|
411
|
-
if (!groupNames.includes(name)) {
|
|
412
|
-
throw new ValidationError(`${ctx}: group '${name}' in anyOf is not defined in root 'groups'`);
|
|
413
|
-
}
|
|
414
|
-
if (seen.has(name)) {
|
|
415
|
-
throw new ValidationError(`${ctx}: duplicate group '${name}' in anyOf`);
|
|
416
|
-
}
|
|
417
|
-
seen.add(name);
|
|
418
|
-
}
|
|
460
|
+
validateGroupRefArray(anyOf, "anyOf", ctx, groupNames);
|
|
419
461
|
}
|
|
420
462
|
// Validate files
|
|
421
463
|
if (entry.files) {
|
|
@@ -565,6 +607,10 @@ function validateRepoSettingsEntry(config, repo, repoLabel) {
|
|
|
565
607
|
group.settings.repo !== false) {
|
|
566
608
|
rootCtx.hasRepoSettings = true;
|
|
567
609
|
}
|
|
610
|
+
if (group?.settings?.codeScanning !== undefined &&
|
|
611
|
+
group.settings.codeScanning !== false) {
|
|
612
|
+
rootCtx.hasCodeScanningSettings = true;
|
|
613
|
+
}
|
|
568
614
|
}
|
|
569
615
|
}
|
|
570
616
|
if (config.conditionalGroups) {
|
|
@@ -584,6 +630,10 @@ function validateRepoSettingsEntry(config, repo, repoLabel) {
|
|
|
584
630
|
if (cg.settings?.repo !== undefined && cg.settings.repo !== false) {
|
|
585
631
|
rootCtx.hasRepoSettings = true;
|
|
586
632
|
}
|
|
633
|
+
if (cg.settings?.codeScanning !== undefined &&
|
|
634
|
+
cg.settings.codeScanning !== false) {
|
|
635
|
+
rootCtx.hasCodeScanningSettings = true;
|
|
636
|
+
}
|
|
587
637
|
}
|
|
588
638
|
}
|
|
589
639
|
validateSettings(repo.settings, `Repo ${repoLabel}`, rootCtx);
|
|
@@ -600,6 +650,19 @@ function hasGroupFiles(config) {
|
|
|
600
650
|
Object.values(config.groups).some((g) => g.files &&
|
|
601
651
|
Object.keys(g.files).filter((k) => k !== "inherit" && g.files[k] !== false).length > 0));
|
|
602
652
|
}
|
|
653
|
+
function hasConditionalGroupFiles(config) {
|
|
654
|
+
return (Array.isArray(config.conditionalGroups) &&
|
|
655
|
+
config.conditionalGroups.some((cg) => cg.files &&
|
|
656
|
+
Object.keys(cg.files).filter((k) => k !== "inherit" && cg.files[k] !== false).length > 0));
|
|
657
|
+
}
|
|
658
|
+
function hasConditionalGroupSettings(config, predicate) {
|
|
659
|
+
return (Array.isArray(config.conditionalGroups) &&
|
|
660
|
+
config.conditionalGroups.some((cg) => cg.settings && predicate(cg.settings)));
|
|
661
|
+
}
|
|
662
|
+
function hasConditionalGroupPR(config) {
|
|
663
|
+
return (Array.isArray(config.conditionalGroups) &&
|
|
664
|
+
config.conditionalGroups.some((cg) => cg.prOptions && isPlainObject(cg.prOptions)));
|
|
665
|
+
}
|
|
603
666
|
/**
|
|
604
667
|
* Validates raw config structure before normalization.
|
|
605
668
|
* @throws ValidationError if validation fails
|
|
@@ -611,13 +674,9 @@ export function validateRawConfig(config) {
|
|
|
611
674
|
const hasGrpFiles = hasGroupFiles(config);
|
|
612
675
|
const hasGrpSettings = isPlainObject(config.groups) &&
|
|
613
676
|
Object.values(config.groups).some((g) => g.settings && isPlainObject(g.settings));
|
|
614
|
-
const hasCondGrpFiles =
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const hasCondGrpSettings = Array.isArray(config.conditionalGroups) &&
|
|
618
|
-
config.conditionalGroups.some((cg) => cg.settings && isPlainObject(cg.settings));
|
|
619
|
-
const hasCondGrpPR = Array.isArray(config.conditionalGroups) &&
|
|
620
|
-
config.conditionalGroups.some((cg) => cg.prOptions && isPlainObject(cg.prOptions));
|
|
677
|
+
const hasCondGrpFiles = hasConditionalGroupFiles(config);
|
|
678
|
+
const hasCondGrpSettings = hasConditionalGroupSettings(config, isPlainObject);
|
|
679
|
+
const hasCondGrpPR = hasConditionalGroupPR(config);
|
|
621
680
|
if (!hasFiles &&
|
|
622
681
|
!hasSettings &&
|
|
623
682
|
!hasGrpFiles &&
|
|
@@ -659,13 +718,9 @@ export function validateForSync(config) {
|
|
|
659
718
|
const hasRepoSettings = config.repos.some((repo) => hasActionableSettings(repo.settings));
|
|
660
719
|
const hasGroupSettings = isPlainObject(config.groups) &&
|
|
661
720
|
Object.values(config.groups).some((g) => g.settings && hasActionableSettings(g.settings));
|
|
662
|
-
const hasCondGrpFiles =
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const hasCondGrpSettings = Array.isArray(config.conditionalGroups) &&
|
|
666
|
-
config.conditionalGroups.some((cg) => cg.settings && hasActionableSettings(cg.settings));
|
|
667
|
-
const hasCondGrpPR = Array.isArray(config.conditionalGroups) &&
|
|
668
|
-
config.conditionalGroups.some((cg) => cg.prOptions && isPlainObject(cg.prOptions));
|
|
721
|
+
const hasCondGrpFiles = hasConditionalGroupFiles(config);
|
|
722
|
+
const hasCondGrpSettings = hasConditionalGroupSettings(config, hasActionableSettings);
|
|
723
|
+
const hasCondGrpPR = hasConditionalGroupPR(config);
|
|
669
724
|
if (!hasRootFiles &&
|
|
670
725
|
!hasGrpFiles &&
|
|
671
726
|
!hasSettings &&
|
|
@@ -695,5 +750,8 @@ export function hasActionableSettings(settings) {
|
|
|
695
750
|
Object.keys(settings.labels).filter((k) => k !== "inherit").length > 0) {
|
|
696
751
|
return true;
|
|
697
752
|
}
|
|
753
|
+
if (settings.codeScanning && typeof settings.codeScanning === "object") {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
698
756
|
return false;
|
|
699
757
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ValidationError } from "../../shared/errors.js";
|
|
2
|
+
import { isPlainObject } from "../../shared/type-guards.js";
|
|
2
3
|
/** Compile-time validates an array matches a type union, while keeping string[] runtime type for .includes() */
|
|
3
4
|
function validValues(values) {
|
|
4
5
|
return values;
|
|
@@ -62,6 +63,31 @@ const VALID_RULE_TYPES = validValues([
|
|
|
62
63
|
"max_file_path_length",
|
|
63
64
|
"max_file_size",
|
|
64
65
|
]);
|
|
66
|
+
// Intentionally duplicated from merge.ts — validator should not depend on merge internals
|
|
67
|
+
const VALID_MERGE_STRATEGIES = ["replace", "append", "prepend"];
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a value is an $arrayMerge directive: { $arrayMerge: strategy, $values: [...] }
|
|
70
|
+
*/
|
|
71
|
+
function isArrayMergeDirective(value) {
|
|
72
|
+
if (!isPlainObject(value))
|
|
73
|
+
return false;
|
|
74
|
+
const keys = Object.keys(value);
|
|
75
|
+
return (keys.length === 2 &&
|
|
76
|
+
keys.every((k) => k === "$arrayMerge" || k === "$values") &&
|
|
77
|
+
VALID_MERGE_STRATEGIES.includes(value.$arrayMerge) &&
|
|
78
|
+
Array.isArray(value.$values));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Extracts the $values array from a directive, or returns the value as-is if it's already an array.
|
|
82
|
+
* Returns null if value is neither an array nor a valid directive.
|
|
83
|
+
*/
|
|
84
|
+
function extractArrayOrDirectiveValues(value) {
|
|
85
|
+
if (Array.isArray(value))
|
|
86
|
+
return value;
|
|
87
|
+
if (isArrayMergeDirective(value))
|
|
88
|
+
return value.$values;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
65
91
|
/**
|
|
66
92
|
* Validates a single ruleset rule.
|
|
67
93
|
*/
|
|
@@ -158,11 +184,12 @@ export function validateRuleset(ruleset, name, context) {
|
|
|
158
184
|
}
|
|
159
185
|
// Validate bypassActors
|
|
160
186
|
if (rs.bypassActors !== undefined) {
|
|
161
|
-
|
|
162
|
-
|
|
187
|
+
const actors = extractArrayOrDirectiveValues(rs.bypassActors);
|
|
188
|
+
if (actors === null) {
|
|
189
|
+
throw new ValidationError(`${context}: ruleset '${name}' bypassActors must be an array or $arrayMerge directive`);
|
|
163
190
|
}
|
|
164
|
-
for (let i = 0; i <
|
|
165
|
-
const actor =
|
|
191
|
+
for (let i = 0; i < actors.length; i++) {
|
|
192
|
+
const actor = actors[i];
|
|
166
193
|
if (typeof actor !== "object" || actor === null) {
|
|
167
194
|
throw new ValidationError(`${context}: ruleset '${name}' bypassActors[${i}] must be an object`);
|
|
168
195
|
}
|
|
@@ -193,25 +220,28 @@ export function validateRuleset(ruleset, name, context) {
|
|
|
193
220
|
Array.isArray(refName)) {
|
|
194
221
|
throw new ValidationError(`${context}: ruleset '${name}' conditions.refName must be an object`);
|
|
195
222
|
}
|
|
196
|
-
if (refName.include !== undefined
|
|
197
|
-
(
|
|
198
|
-
|
|
199
|
-
|
|
223
|
+
if (refName.include !== undefined) {
|
|
224
|
+
const include = extractArrayOrDirectiveValues(refName.include);
|
|
225
|
+
if (include === null || !include.every((s) => typeof s === "string")) {
|
|
226
|
+
throw new ValidationError(`${context}: ruleset '${name}' conditions.refName.include must be an array of strings or $arrayMerge directive with string $values`);
|
|
227
|
+
}
|
|
200
228
|
}
|
|
201
|
-
if (refName.exclude !== undefined
|
|
202
|
-
(
|
|
203
|
-
|
|
204
|
-
|
|
229
|
+
if (refName.exclude !== undefined) {
|
|
230
|
+
const exclude = extractArrayOrDirectiveValues(refName.exclude);
|
|
231
|
+
if (exclude === null || !exclude.every((s) => typeof s === "string")) {
|
|
232
|
+
throw new ValidationError(`${context}: ruleset '${name}' conditions.refName.exclude must be an array of strings or $arrayMerge directive with string $values`);
|
|
233
|
+
}
|
|
205
234
|
}
|
|
206
235
|
}
|
|
207
236
|
}
|
|
208
237
|
// Validate rules array
|
|
209
238
|
if (rs.rules !== undefined) {
|
|
210
|
-
|
|
211
|
-
|
|
239
|
+
const rules = extractArrayOrDirectiveValues(rs.rules);
|
|
240
|
+
if (rules === null) {
|
|
241
|
+
throw new ValidationError(`${context}: ruleset '${name}' rules must be an array or $arrayMerge directive`);
|
|
212
242
|
}
|
|
213
|
-
for (let i = 0; i <
|
|
214
|
-
validateRule(
|
|
243
|
+
for (let i = 0; i < rules.length; i++) {
|
|
244
|
+
validateRule(rules[i], `${context}: ruleset '${name}' rules[${i}]`);
|
|
215
245
|
}
|
|
216
246
|
}
|
|
217
247
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
1
|
+
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
2
2
|
import { type RepoInfo } from "../shared/repo-detector.js";
|
|
3
3
|
import type { IMigrationSource, LifecyclePlatform } from "./types.js";
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
1
|
+
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
2
2
|
import { type RepoInfo } from "../shared/repo-detector.js";
|
|
3
3
|
import type { DebugWarnLog } from "../shared/logger.js";
|
|
4
4
|
import type { IRepoLifecycleProvider, LifecyclePlatform, CreateRepoSettings } from "./types.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { IRepoLifecycleManager } from "./types.js";
|
|
2
2
|
export { RepoLifecycleManager } from "./repo-lifecycle-manager.js";
|
|
3
|
-
export { runLifecycleCheck, toCreateRepoSettings, } from "./lifecycle-helpers.js";
|
|
3
|
+
export { runLifecycleCheck, toCreateRepoSettings, type LifecycleCheckResult, } from "./lifecycle-helpers.js";
|
|
@@ -20,9 +20,10 @@ interface LifecycleCheckOptions {
|
|
|
20
20
|
* Extracts only the fields relevant for repo creation.
|
|
21
21
|
*/
|
|
22
22
|
export declare function toCreateRepoSettings(repo: GitHubRepoSettings | undefined): CreateRepoSettings | undefined;
|
|
23
|
-
interface LifecycleCheckResult {
|
|
23
|
+
export interface LifecycleCheckResult {
|
|
24
24
|
lifecycleResult: LifecycleResult;
|
|
25
25
|
outputLines: string[];
|
|
26
|
+
createSettings: CreateRepoSettings | undefined;
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
29
|
* Run lifecycle check for a single repo.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICommandExecutor } from "../shared/command-executor.js";
|
|
1
|
+
import type { ICommandExecutor } from "../shared/command-executor.js";
|
|
2
2
|
import type { DebugWarnLog } from "../shared/logger.js";
|
|
3
3
|
import type { IRepoLifecycleFactory, IRepoLifecycleProvider, IMigrationSource, LifecyclePlatform } from "./types.js";
|
|
4
4
|
export declare class RepoLifecycleFactory implements IRepoLifecycleFactory {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { parseGitUrl } from "../shared/repo-detector.js";
|
|
4
|
-
import { safeCleanup } from "../shared/
|
|
4
|
+
import { safeCleanup } from "../shared/cleanup-utils.js";
|
|
5
5
|
import { LifecycleError } from "../shared/errors.js";
|
|
6
6
|
import { NO_OP_DEBUG_LOG } from "../shared/logger.js";
|
|
7
7
|
import { RepoLifecycleFactory } from "./repo-lifecycle-factory.js";
|
package/dist/output/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MergeMode } from "../config/index.js";
|
|
1
2
|
import type { FileChangeDetail } from "../sync/index.js";
|
|
2
3
|
export type ReportFileChange = FileChangeDetail;
|
|
3
4
|
export interface SyncReport {
|
|
@@ -14,6 +15,6 @@ export interface RepoFileChanges {
|
|
|
14
15
|
repoName: string;
|
|
15
16
|
files: ReportFileChange[];
|
|
16
17
|
prUrl?: string;
|
|
17
|
-
mergeOutcome?:
|
|
18
|
+
mergeOutcome?: MergeMode;
|
|
18
19
|
error?: string;
|
|
19
20
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CodeScanningSettings } from "../../config/index.js";
|
|
2
|
+
import type { CurrentCodeScanningSettings } from "./types.js";
|
|
3
|
+
import type { SettingsAction } from "../base-processor.js";
|
|
4
|
+
export interface CodeScanningChange {
|
|
5
|
+
property: "state" | "querySuite" | "languages";
|
|
6
|
+
action: SettingsAction;
|
|
7
|
+
oldValue?: unknown;
|
|
8
|
+
newValue?: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Compares current code scanning default setup with desired settings.
|
|
12
|
+
* Only compares properties that are explicitly set in desired.
|
|
13
|
+
* Languages are compared as sorted arrays (order doesn't matter).
|
|
14
|
+
*/
|
|
15
|
+
export declare function diffCodeScanning(current: CurrentCodeScanningSettings, desired: CodeScanningSettings): CodeScanningChange[];
|
|
16
|
+
/**
|
|
17
|
+
* Checks if there are any actual changes to apply.
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasCodeScanningChanges(changes: CodeScanningChange[]): boolean;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares current code scanning default setup with desired settings.
|
|
3
|
+
* Only compares properties that are explicitly set in desired.
|
|
4
|
+
* Languages are compared as sorted arrays (order doesn't matter).
|
|
5
|
+
*/
|
|
6
|
+
export function diffCodeScanning(current, desired) {
|
|
7
|
+
const changes = [];
|
|
8
|
+
// state is always compared (required field)
|
|
9
|
+
if (current.state !== desired.state) {
|
|
10
|
+
changes.push({
|
|
11
|
+
property: "state",
|
|
12
|
+
action: current.state === undefined ? "create" : "update",
|
|
13
|
+
oldValue: current.state,
|
|
14
|
+
newValue: desired.state,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
changes.push({
|
|
19
|
+
property: "state",
|
|
20
|
+
action: "unchanged",
|
|
21
|
+
oldValue: current.state,
|
|
22
|
+
newValue: desired.state,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// querySuite: only diff if specified in desired
|
|
26
|
+
if (desired.querySuite !== undefined) {
|
|
27
|
+
const currentQS = current.query_suite;
|
|
28
|
+
if (currentQS !== desired.querySuite) {
|
|
29
|
+
changes.push({
|
|
30
|
+
property: "querySuite",
|
|
31
|
+
action: currentQS === undefined ? "create" : "update",
|
|
32
|
+
oldValue: currentQS,
|
|
33
|
+
newValue: desired.querySuite,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
changes.push({
|
|
38
|
+
property: "querySuite",
|
|
39
|
+
action: "unchanged",
|
|
40
|
+
oldValue: currentQS,
|
|
41
|
+
newValue: desired.querySuite,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// languages: only diff if specified in desired (sorted comparison)
|
|
46
|
+
if (desired.languages !== undefined) {
|
|
47
|
+
const currentLangs = [...(current.languages ?? [])].sort();
|
|
48
|
+
const desiredLangs = [...desired.languages].sort();
|
|
49
|
+
const langsMatch = currentLangs.length === desiredLangs.length &&
|
|
50
|
+
currentLangs.every((lang, i) => lang === desiredLangs[i]);
|
|
51
|
+
if (!langsMatch) {
|
|
52
|
+
changes.push({
|
|
53
|
+
property: "languages",
|
|
54
|
+
action: current.languages === undefined ? "create" : "update",
|
|
55
|
+
oldValue: current.languages,
|
|
56
|
+
newValue: desired.languages,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
changes.push({
|
|
61
|
+
property: "languages",
|
|
62
|
+
action: "unchanged",
|
|
63
|
+
oldValue: current.languages,
|
|
64
|
+
newValue: desired.languages,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return changes;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Checks if there are any actual changes to apply.
|
|
72
|
+
*/
|
|
73
|
+
export function hasCodeScanningChanges(changes) {
|
|
74
|
+
return changes.some((c) => c.action !== "unchanged");
|
|
75
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CodeScanningChange } from "./diff.js";
|
|
2
|
+
export interface CodeScanningPlanEntry {
|
|
3
|
+
property: string;
|
|
4
|
+
action: "create" | "update";
|
|
5
|
+
oldValue?: unknown;
|
|
6
|
+
newValue?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface CodeScanningPlanResult {
|
|
9
|
+
lines: string[];
|
|
10
|
+
creates: number;
|
|
11
|
+
updates: number;
|
|
12
|
+
entries: CodeScanningPlanEntry[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Formats code scanning changes as Terraform-style plan output.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatCodeScanningPlan(changes: CodeScanningChange[]): CodeScanningPlanResult;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { formatScalarValue } from "../../shared/string-utils.js";
|
|
3
|
+
import { countActions } from "../base-processor.js";
|
|
4
|
+
function formatValue(val) {
|
|
5
|
+
if (Array.isArray(val)) {
|
|
6
|
+
return `[${val.join(", ")}]`;
|
|
7
|
+
}
|
|
8
|
+
return formatScalarValue(val) ?? String(val);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Formats code scanning changes as Terraform-style plan output.
|
|
12
|
+
*/
|
|
13
|
+
export function formatCodeScanningPlan(changes) {
|
|
14
|
+
const lines = [];
|
|
15
|
+
const entries = [];
|
|
16
|
+
const { create: creates, update: updates } = countActions(changes);
|
|
17
|
+
for (const change of changes) {
|
|
18
|
+
if (change.action === "create") {
|
|
19
|
+
lines.push(chalk.green(` + ${change.property}: ${formatValue(change.newValue)}`));
|
|
20
|
+
entries.push({
|
|
21
|
+
property: change.property,
|
|
22
|
+
action: "create",
|
|
23
|
+
newValue: change.newValue,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else if (change.action === "update") {
|
|
27
|
+
lines.push(chalk.yellow(` ~ ${change.property}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`));
|
|
28
|
+
entries.push({
|
|
29
|
+
property: change.property,
|
|
30
|
+
action: "update",
|
|
31
|
+
oldValue: change.oldValue,
|
|
32
|
+
newValue: change.newValue,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { lines, creates, updates, entries };
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ICommandExecutor } from "../../shared/command-executor.js";
|
|
2
|
+
import { type RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import { type GhApiOptions } from "../../shared/gh-api-utils.js";
|
|
4
|
+
import type { ICodeScanningStrategy, CurrentCodeScanningSettings } from "./types.js";
|
|
5
|
+
interface GitHubCodeScanningStrategyOptions {
|
|
6
|
+
retries?: number;
|
|
7
|
+
cwd: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class GitHubCodeScanningStrategy implements ICodeScanningStrategy {
|
|
10
|
+
private api;
|
|
11
|
+
constructor(executor: ICommandExecutor, options: GitHubCodeScanningStrategyOptions);
|
|
12
|
+
getDefaultSetup(repoInfo: RepoInfo, options?: GhApiOptions): Promise<CurrentCodeScanningSettings>;
|
|
13
|
+
updateDefaultSetup(repoInfo: RepoInfo, settings: {
|
|
14
|
+
state: string;
|
|
15
|
+
query_suite?: string;
|
|
16
|
+
languages?: string[];
|
|
17
|
+
}, options?: GhApiOptions): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { assertGitHubRepo } from "../../shared/repo-detector.js";
|
|
2
|
+
import { GhApiClient } from "../../shared/gh-api-utils.js";
|
|
3
|
+
import { parseApiJson } from "../../shared/json-utils.js";
|
|
4
|
+
export class GitHubCodeScanningStrategy {
|
|
5
|
+
api;
|
|
6
|
+
constructor(executor, options) {
|
|
7
|
+
this.api = new GhApiClient(executor, options.retries ?? 3, options.cwd);
|
|
8
|
+
}
|
|
9
|
+
async getDefaultSetup(repoInfo, options) {
|
|
10
|
+
assertGitHubRepo(repoInfo, "GitHub Code Scanning strategy");
|
|
11
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/code-scanning/default-setup`;
|
|
12
|
+
const result = await this.api.call("GET", endpoint, { options });
|
|
13
|
+
return parseApiJson(result, "code scanning default setup response");
|
|
14
|
+
}
|
|
15
|
+
async updateDefaultSetup(repoInfo, settings, options) {
|
|
16
|
+
assertGitHubRepo(repoInfo, "GitHub Code Scanning strategy");
|
|
17
|
+
const endpoint = `/repos/${repoInfo.owner}/${repoInfo.repo}/code-scanning/default-setup`;
|
|
18
|
+
await this.api.call("PATCH", endpoint, { payload: settings, options });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RepoConfig } from "../../config/index.js";
|
|
2
|
+
import type { RepoInfo } from "../../shared/repo-detector.js";
|
|
3
|
+
import type { ICodeScanningStrategy } from "./types.js";
|
|
4
|
+
import type { IRepoMetadataProvider } from "../../shared/repo-metadata-provider.js";
|
|
5
|
+
import { type CodeScanningPlanResult } from "./formatter.js";
|
|
6
|
+
import { type BaseProcessorOptions, type BaseProcessorResult, type ISettingsProcessor, type ChangeCounts } from "../base-processor.js";
|
|
7
|
+
export type ICodeScanningProcessor = ISettingsProcessor<CodeScanningProcessorOptions, CodeScanningProcessorResult>;
|
|
8
|
+
export type CodeScanningProcessorOptions = BaseProcessorOptions;
|
|
9
|
+
export interface CodeScanningProcessorResult extends BaseProcessorResult {
|
|
10
|
+
changes?: ChangeCounts;
|
|
11
|
+
planOutput?: CodeScanningPlanResult;
|
|
12
|
+
}
|
|
13
|
+
export declare class CodeScanningProcessor implements ICodeScanningProcessor {
|
|
14
|
+
private readonly strategy;
|
|
15
|
+
private readonly metadataProvider;
|
|
16
|
+
constructor(strategy: ICodeScanningStrategy, metadataProvider: IRepoMetadataProvider);
|
|
17
|
+
process(repoConfig: RepoConfig, repoInfo: RepoInfo, options: CodeScanningProcessorOptions): Promise<CodeScanningProcessorResult>;
|
|
18
|
+
private applySettings;
|
|
19
|
+
private validateGHAS;
|
|
20
|
+
}
|