@aspruyt/xfg 3.5.5 → 3.5.6

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.
@@ -37,14 +37,15 @@ function normalizeValue(value) {
37
37
  export function normalizeRuleset(obj) {
38
38
  const normalized = {};
39
39
  for (const [key, value] of Object.entries(obj)) {
40
- if (value == null) {
40
+ if (value === undefined) {
41
41
  continue;
42
42
  }
43
43
  const snakeKey = camelToSnake(key);
44
44
  if (!RULESET_COMPARABLE_FIELDS.has(snakeKey)) {
45
45
  continue;
46
46
  }
47
- normalized[snakeKey] = normalizeValue(value);
47
+ // Preserve null explicitly — it means "API couldn't read this field"
48
+ normalized[snakeKey] = value === null ? null : normalizeValue(value);
48
49
  }
49
50
  return normalized;
50
51
  }
@@ -122,7 +123,13 @@ function projectObjects(current, desired) {
122
123
  const result = {};
123
124
  for (const key of Object.keys(desired)) {
124
125
  if (key in current) {
125
- result[key] = projectToDesiredShape(current[key], desired[key]);
126
+ if (current[key] === null) {
127
+ // null means "API token can't read this field" — assume it matches desired
128
+ result[key] = desired[key];
129
+ }
130
+ else {
131
+ result[key] = projectToDesiredShape(current[key], desired[key]);
132
+ }
126
133
  }
127
134
  else if (Array.isArray(desired[key]) &&
128
135
  desired[key].length === 0) {
@@ -35,6 +35,7 @@ export interface RulesetProcessorResult {
35
35
  */
36
36
  export declare class RulesetProcessor implements IRulesetProcessor {
37
37
  private readonly strategy;
38
+ private readonly tokenManager;
38
39
  constructor(strategy?: GitHubRulesetStrategy);
39
40
  /**
40
41
  * Process rulesets for a single repository.
@@ -49,4 +50,9 @@ export declare class RulesetProcessor implements IRulesetProcessor {
49
50
  * Only rulesets with deleteOrphaned enabled should be tracked.
50
51
  */
51
52
  private computeManifestUpdate;
53
+ /**
54
+ * Resolves a GitHub App installation token for the given repo.
55
+ * Returns undefined if no token manager or token resolution fails.
56
+ */
57
+ private getInstallationToken;
52
58
  }
@@ -2,6 +2,8 @@ import { isGitHubRepo, getRepoDisplayName } from "./repo-detector.js";
2
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
+ import { hasGitHubAppCredentials } from "./strategies/index.js";
6
+ import { GitHubAppTokenManager } from "./github-app-token-manager.js";
5
7
  // =============================================================================
6
8
  // Processor Implementation
7
9
  // =============================================================================
@@ -11,8 +13,15 @@ import { formatRulesetPlan, } from "./ruleset-plan-formatter.js";
11
13
  */
12
14
  export class RulesetProcessor {
13
15
  strategy;
16
+ tokenManager;
14
17
  constructor(strategy) {
15
18
  this.strategy = strategy ?? new GitHubRulesetStrategy();
19
+ if (hasGitHubAppCredentials()) {
20
+ this.tokenManager = new GitHubAppTokenManager(process.env.XFG_GITHUB_APP_ID, process.env.XFG_GITHUB_APP_PRIVATE_KEY);
21
+ }
22
+ else {
23
+ this.tokenManager = null;
24
+ }
16
25
  }
17
26
  /**
18
27
  * Process rulesets for a single repository.
@@ -44,8 +53,9 @@ export class RulesetProcessor {
44
53
  };
45
54
  }
46
55
  try {
47
- // Fetch current rulesets
48
- const strategyOptions = { token, host: githubRepo.host };
56
+ // Resolve App token if available, fall back to provided token
57
+ const effectiveToken = token ?? (await this.getInstallationToken(githubRepo));
58
+ const strategyOptions = { token: effectiveToken, host: githubRepo.host };
49
59
  const currentRulesets = await this.strategy.list(githubRepo, strategyOptions);
50
60
  // Convert desired rulesets to Map
51
61
  const desiredMap = new Map(Object.entries(desiredRulesets));
@@ -159,4 +169,20 @@ export class RulesetProcessor {
159
169
  const rulesetNames = Object.keys(rulesets).sort();
160
170
  return { rulesets: rulesetNames };
161
171
  }
172
+ /**
173
+ * Resolves a GitHub App installation token for the given repo.
174
+ * Returns undefined if no token manager or token resolution fails.
175
+ */
176
+ async getInstallationToken(repoInfo) {
177
+ if (!this.tokenManager) {
178
+ return undefined;
179
+ }
180
+ try {
181
+ const token = await this.tokenManager.getTokenForRepo(repoInfo);
182
+ return token ?? undefined;
183
+ }
184
+ catch {
185
+ return undefined;
186
+ }
187
+ }
162
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.5.5",
3
+ "version": "3.5.6",
4
4
  "description": "CLI tool for repository-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",