@aspruyt/xfg 5.5.0 → 5.6.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.
@@ -394,16 +394,18 @@ function mergeGroupSettings(rootSettings, groupNames, groupDefs) {
394
394
  }
395
395
  /**
396
396
  * Evaluates a conditional group's `when` clause against a repo's effective groups.
397
- * Both `allOf` (every listed group present) and `anyOf` (at least one present)
398
- * must be satisfied. Absent conditions are treated as satisfied.
397
+ * All specified operators must be satisfied: `allOf` (every listed group present),
398
+ * `anyOf` (at least one present), and `noneOf` (none of the listed groups present).
399
+ * Absent conditions are treated as satisfied.
399
400
  */
400
401
  function evaluateWhenClause(when, effectiveGroups) {
401
- // Defensive: if neither condition is specified, don't match
402
- if (!when.allOf && !when.anyOf)
402
+ // Defensive: if no condition is specified, don't match
403
+ if (!when.allOf && !when.anyOf && !when.noneOf)
403
404
  return false;
404
405
  const allOfSatisfied = !when.allOf || when.allOf.every((g) => effectiveGroups.has(g));
405
406
  const anyOfSatisfied = !when.anyOf || when.anyOf.some((g) => effectiveGroups.has(g));
406
- return allOfSatisfied && anyOfSatisfied;
407
+ const noneOfSatisfied = !when.noneOf || when.noneOf.every((g) => !effectiveGroups.has(g));
408
+ return allOfSatisfied && anyOfSatisfied && noneOfSatisfied;
407
409
  }
408
410
  /**
409
411
  * Merges matching conditional groups into the accumulated files/prOptions/settings.
@@ -315,6 +315,8 @@ export interface RawConditionalGroupWhen {
315
315
  allOf?: string[];
316
316
  /** At least one listed group must be present */
317
317
  anyOf?: string[];
318
+ /** None of the listed groups may be present */
319
+ noneOf?: string[];
318
320
  }
319
321
  /** Conditional group: activates based on which groups a repo has */
320
322
  export interface RawConditionalGroupConfig {
@@ -449,9 +449,9 @@ function validateConditionalGroups(config) {
449
449
  if (!entry.when || !isPlainObject(entry.when)) {
450
450
  throw new ValidationError(`${ctx}: 'when' is required and must be an object`);
451
451
  }
452
- const { allOf, anyOf } = entry.when;
453
- if (!allOf && !anyOf) {
454
- throw new ValidationError(`${ctx}: 'when' must have at least one of 'allOf' or 'anyOf'`);
452
+ const { allOf, anyOf, noneOf } = entry.when;
453
+ if (!allOf && !anyOf && !noneOf) {
454
+ throw new ValidationError(`${ctx}: 'when' must have at least one of 'allOf', 'anyOf', or 'noneOf'`);
455
455
  }
456
456
  if (allOf !== undefined) {
457
457
  validateGroupRefArray(allOf, "allOf", ctx, groupNames);
@@ -459,6 +459,27 @@ function validateConditionalGroups(config) {
459
459
  if (anyOf !== undefined) {
460
460
  validateGroupRefArray(anyOf, "anyOf", ctx, groupNames);
461
461
  }
462
+ if (noneOf !== undefined) {
463
+ validateGroupRefArray(noneOf, "noneOf", ctx, groupNames);
464
+ }
465
+ // Cross-operator overlap: noneOf must not share groups with allOf or anyOf
466
+ if (noneOf) {
467
+ const noneOfSet = new Set(noneOf);
468
+ if (allOf) {
469
+ for (const g of allOf) {
470
+ if (noneOfSet.has(g)) {
471
+ throw new ValidationError(`${ctx}: noneOf group '${g}' overlaps with allOf (contradictory condition)`);
472
+ }
473
+ }
474
+ }
475
+ if (anyOf) {
476
+ for (const g of anyOf) {
477
+ if (noneOfSet.has(g)) {
478
+ throw new ValidationError(`${ctx}: noneOf group '${g}' overlaps with anyOf (contradictory condition)`);
479
+ }
480
+ }
481
+ }
482
+ }
462
483
  // Validate files
463
484
  if (entry.files) {
464
485
  for (const [fileName, fileConfig] of Object.entries(entry.files)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab — declaratively, from a single YAML config",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",