@aspruyt/xfg 3.5.2 → 3.5.4
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/ruleset-diff.d.ts +6 -0
- package/dist/ruleset-diff.js +13 -15
- package/dist/ruleset-plan-formatter.js +3 -35
- package/dist/ruleset-processor.js +15 -2
- package/package.json +1 -1
package/dist/ruleset-diff.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface RulesetChange {
|
|
|
8
8
|
current?: GitHubRuleset;
|
|
9
9
|
desired?: Ruleset;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalizes any ruleset object (GitHub API or config) for comparison.
|
|
13
|
+
* Converts all keys to snake_case, filters to comparable fields only,
|
|
14
|
+
* and recursively normalizes values.
|
|
15
|
+
*/
|
|
16
|
+
export declare function normalizeRuleset(obj: GitHubRuleset | Ruleset): Record<string, unknown>;
|
|
11
17
|
/**
|
|
12
18
|
* Projects `current` onto the shape of `desired`.
|
|
13
19
|
* Only keeps keys/structure present in `desired`, filtering out API noise.
|
package/dist/ruleset-diff.js
CHANGED
|
@@ -30,15 +30,21 @@ function normalizeValue(value) {
|
|
|
30
30
|
return value;
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* Normalizes
|
|
33
|
+
* Normalizes any ruleset object (GitHub API or config) for comparison.
|
|
34
|
+
* Converts all keys to snake_case, filters to comparable fields only,
|
|
35
|
+
* and recursively normalizes values.
|
|
34
36
|
*/
|
|
35
|
-
function
|
|
37
|
+
export function normalizeRuleset(obj) {
|
|
36
38
|
const normalized = {};
|
|
37
|
-
for (const [key, value] of Object.entries(
|
|
38
|
-
if (
|
|
39
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const snakeKey = camelToSnake(key);
|
|
44
|
+
if (!RULESET_COMPARABLE_FIELDS.has(snakeKey)) {
|
|
39
45
|
continue;
|
|
40
46
|
}
|
|
41
|
-
normalized[
|
|
47
|
+
normalized[snakeKey] = normalizeValue(value);
|
|
42
48
|
}
|
|
43
49
|
return normalized;
|
|
44
50
|
}
|
|
@@ -51,15 +57,7 @@ function normalizeConfigRuleset(ruleset) {
|
|
|
51
57
|
enforcement: ruleset.enforcement ?? "active",
|
|
52
58
|
...ruleset,
|
|
53
59
|
};
|
|
54
|
-
|
|
55
|
-
for (const [key, value] of Object.entries(withDefaults)) {
|
|
56
|
-
if (value === undefined) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const snakeKey = camelToSnake(key);
|
|
60
|
-
normalized[snakeKey] = normalizeValue(value);
|
|
61
|
-
}
|
|
62
|
-
return normalized;
|
|
60
|
+
return normalizeRuleset(withDefaults);
|
|
63
61
|
}
|
|
64
62
|
/**
|
|
65
63
|
* Performs deep equality comparison of two normalized values.
|
|
@@ -198,7 +196,7 @@ export function diffRulesets(current, desired, managedNames) {
|
|
|
198
196
|
}
|
|
199
197
|
else {
|
|
200
198
|
// Existing ruleset - check if changed
|
|
201
|
-
const normalizedCurrent =
|
|
199
|
+
const normalizedCurrent = normalizeRuleset(currentRuleset);
|
|
202
200
|
const normalizedDesired = normalizeConfigRuleset(desiredRuleset);
|
|
203
201
|
const projectedCurrent = projectToDesiredShape(normalizedCurrent, normalizedDesired);
|
|
204
202
|
if (deepEqual(projectedCurrent, normalizedDesired)) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/ruleset-plan-formatter.ts
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { projectToDesiredShape, } from "./ruleset-diff.js";
|
|
4
|
-
import { RULESET_COMPARABLE_FIELDS } from "./config.js";
|
|
3
|
+
import { projectToDesiredShape, normalizeRuleset, } from "./ruleset-diff.js";
|
|
5
4
|
// =============================================================================
|
|
6
5
|
// Property Diff Algorithm
|
|
7
6
|
// =============================================================================
|
|
@@ -352,37 +351,6 @@ export function formatPropertyTree(diffs) {
|
|
|
352
351
|
// =============================================================================
|
|
353
352
|
// Ruleset Plan Formatter
|
|
354
353
|
// =============================================================================
|
|
355
|
-
/**
|
|
356
|
-
* Normalize a GitHubRuleset or Ruleset for comparison.
|
|
357
|
-
* Converts to snake_case and removes metadata fields.
|
|
358
|
-
*/
|
|
359
|
-
function normalizeForDiff(obj) {
|
|
360
|
-
const result = {};
|
|
361
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
362
|
-
// Convert camelCase to snake_case for consistency
|
|
363
|
-
const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
364
|
-
if (!RULESET_COMPARABLE_FIELDS.has(snakeKey) || value === undefined)
|
|
365
|
-
continue;
|
|
366
|
-
result[snakeKey] = normalizeNestedValue(value);
|
|
367
|
-
}
|
|
368
|
-
return result;
|
|
369
|
-
}
|
|
370
|
-
function normalizeNestedValue(value) {
|
|
371
|
-
if (value === null || value === undefined)
|
|
372
|
-
return value;
|
|
373
|
-
if (Array.isArray(value))
|
|
374
|
-
return value.map(normalizeNestedValue);
|
|
375
|
-
if (typeof value === "object") {
|
|
376
|
-
const obj = value;
|
|
377
|
-
const result = {};
|
|
378
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
379
|
-
const snakeKey = key.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
380
|
-
result[snakeKey] = normalizeNestedValue(val);
|
|
381
|
-
}
|
|
382
|
-
return result;
|
|
383
|
-
}
|
|
384
|
-
return value;
|
|
385
|
-
}
|
|
386
354
|
/**
|
|
387
355
|
* Format a full ruleset config as tree lines (for create action).
|
|
388
356
|
*/
|
|
@@ -467,8 +435,8 @@ export function formatRulesetPlan(changes) {
|
|
|
467
435
|
for (const change of updateChanges) {
|
|
468
436
|
lines.push(chalk.yellow(` ~ ruleset "${change.name}"`));
|
|
469
437
|
if (change.current && change.desired) {
|
|
470
|
-
const currentNorm =
|
|
471
|
-
const desiredNorm =
|
|
438
|
+
const currentNorm = normalizeRuleset(change.current);
|
|
439
|
+
const desiredNorm = normalizeRuleset(change.desired);
|
|
472
440
|
const projectedCurrent = projectToDesiredShape(currentNorm, desiredNorm);
|
|
473
441
|
const diffs = computePropertyDiffs(projectedCurrent, desiredNorm);
|
|
474
442
|
const treeLines = formatPropertyTree(diffs);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isGitHubRepo, getRepoDisplayName } from "./repo-detector.js";
|
|
2
|
-
import { GitHubRulesetStrategy } from "./strategies/github-ruleset-strategy.js";
|
|
2
|
+
import { GitHubRulesetStrategy, } from "./strategies/github-ruleset-strategy.js";
|
|
3
3
|
import { diffRulesets } from "./ruleset-diff.js";
|
|
4
4
|
import { formatRulesetPlan, } from "./ruleset-plan-formatter.js";
|
|
5
5
|
// =============================================================================
|
|
@@ -49,8 +49,21 @@ export class RulesetProcessor {
|
|
|
49
49
|
const currentRulesets = await this.strategy.list(githubRepo, strategyOptions);
|
|
50
50
|
// Convert desired rulesets to Map
|
|
51
51
|
const desiredMap = new Map(Object.entries(desiredRulesets));
|
|
52
|
+
// Hydrate rulesets that match desired names with full details from get()
|
|
53
|
+
// The list endpoint only returns summary fields (id, name, target, enforcement)
|
|
54
|
+
// but not rules, conditions, or bypass_actors needed for accurate diffing
|
|
55
|
+
const fullRulesets = [];
|
|
56
|
+
for (const summary of currentRulesets) {
|
|
57
|
+
if (desiredMap.has(summary.name)) {
|
|
58
|
+
const full = await this.strategy.get(githubRepo, summary.id, strategyOptions);
|
|
59
|
+
fullRulesets.push(full);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
fullRulesets.push(summary);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
52
65
|
// Compute diff
|
|
53
|
-
const changes = diffRulesets(
|
|
66
|
+
const changes = diffRulesets(fullRulesets, desiredMap, managedRulesets);
|
|
54
67
|
// Count changes by type
|
|
55
68
|
const changeCounts = {
|
|
56
69
|
create: changes.filter((c) => c.action === "create").length,
|