@aspruyt/xfg 3.5.8 → 3.7.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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![codecov](https://codecov.io/gh/anthony-spruyt/xfg/graph/badge.svg)](https://codecov.io/gh/anthony-spruyt/xfg)
5
5
  [![npm version](https://img.shields.io/npm/v/@aspruyt/xfg.svg)](https://www.npmjs.com/package/@aspruyt/xfg)
6
6
  [![npm downloads](https://img.shields.io/npm/dw/@aspruyt/xfg.svg)](https://www.npmjs.com/package/@aspruyt/xfg)
7
- [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-xfg-blue?logo=github)](https://github.com/marketplace/actions/xfg-config-file-sync)
7
+ [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-xfg-blue?logo=github)](https://github.com/marketplace/actions/xfg-repo-as-code)
8
8
  [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://anthony-spruyt.github.io/xfg/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
10
 
@@ -92,10 +92,19 @@ export function mergeSettings(root, perRepo) {
92
92
  result.deleteOrphaned = deleteOrphaned;
93
93
  }
94
94
  // Merge repo settings: per-repo overrides root (shallow merge)
95
- const rootRepo = root?.repo;
96
- const perRepoRepo = perRepo?.repo;
97
- if (rootRepo || perRepoRepo) {
98
- result.repo = { ...rootRepo, ...perRepoRepo };
95
+ // repo: false means opt out of all root repo settings
96
+ if (perRepo?.repo === false) {
97
+ // Opt-out: don't include any repo settings
98
+ }
99
+ else {
100
+ const rootRepo = root?.repo;
101
+ const perRepoRepo = perRepo?.repo;
102
+ if (rootRepo || perRepoRepo) {
103
+ result.repo = {
104
+ ...(rootRepo === false ? {} : rootRepo),
105
+ ...perRepoRepo,
106
+ };
107
+ }
99
108
  }
100
109
  return Object.keys(result).length > 0 ? result : undefined;
101
110
  }
@@ -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, rootRulesetNames?: string[]): void;
10
+ export declare function validateSettings(settings: unknown, context: string, rootRulesetNames?: string[], hasRootRepoSettings?: boolean): 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
@@ -252,7 +252,8 @@ export function validateRawConfig(config) {
252
252
  const rootRulesetNames = config.settings?.rulesets
253
253
  ? Object.keys(config.settings.rulesets).filter((k) => k !== "inherit")
254
254
  : [];
255
- validateSettings(repo.settings, `Repo ${getGitDisplayName(repo.git)}`, rootRulesetNames);
255
+ const hasRootRepoSettings = config.settings?.repo !== undefined && config.settings.repo !== false;
256
+ validateSettings(repo.settings, `Repo ${getGitDisplayName(repo.git)}`, rootRulesetNames, hasRootRepoSettings);
256
257
  }
257
258
  }
258
259
  }
@@ -318,12 +319,17 @@ function validateRepoSettings(repo, context) {
318
319
  "secretScanning",
319
320
  "secretScanningPushProtection",
320
321
  "privateVulnerabilityReporting",
322
+ "webCommitSignoffRequired",
321
323
  ];
322
324
  for (const field of booleanFields) {
323
325
  if (r[field] !== undefined && typeof r[field] !== "boolean") {
324
326
  throw new Error(`${context}: ${field} must be a boolean`);
325
327
  }
326
328
  }
329
+ // Validate string fields
330
+ if (r.defaultBranch !== undefined && typeof r.defaultBranch !== "string") {
331
+ throw new Error(`${context}: defaultBranch must be a string`);
332
+ }
327
333
  // Validate enum fields
328
334
  if (r.visibility !== undefined &&
329
335
  !VALID_VISIBILITY.includes(r.visibility)) {
@@ -552,7 +558,7 @@ function validateRuleset(ruleset, name, context) {
552
558
  /**
553
559
  * Validates settings object containing rulesets.
554
560
  */
555
- export function validateSettings(settings, context, rootRulesetNames) {
561
+ export function validateSettings(settings, context, rootRulesetNames, hasRootRepoSettings) {
556
562
  if (typeof settings !== "object" ||
557
563
  settings === null ||
558
564
  Array.isArray(settings)) {
@@ -585,7 +591,20 @@ export function validateSettings(settings, context, rootRulesetNames) {
585
591
  }
586
592
  // Validate repo settings
587
593
  if (s.repo !== undefined) {
588
- validateRepoSettings(s.repo, context);
594
+ if (s.repo === false) {
595
+ if (!rootRulesetNames) {
596
+ // Root level — repo: false not valid here
597
+ throw new Error(`${context}: repo: false is not valid at root level. Define repo settings or remove the field.`);
598
+ }
599
+ // Per-repo level — check root has repo settings to opt out of
600
+ if (!hasRootRepoSettings) {
601
+ throw new Error(`${context}: Cannot opt out of repo settings — not defined in root settings.repo`);
602
+ }
603
+ // Valid opt-out, skip further repo validation
604
+ }
605
+ else {
606
+ validateRepoSettings(s.repo, context);
607
+ }
589
608
  }
590
609
  }
591
610
  // =============================================================================
package/dist/config.d.ts CHANGED
@@ -239,6 +239,8 @@ export interface GitHubRepoSettings {
239
239
  allowForking?: boolean;
240
240
  visibility?: RepoVisibility;
241
241
  archived?: boolean;
242
+ webCommitSignoffRequired?: boolean;
243
+ defaultBranch?: string;
242
244
  allowSquashMerge?: boolean;
243
245
  allowMergeCommit?: boolean;
244
246
  allowRebaseMerge?: boolean;
@@ -289,7 +291,7 @@ export interface RawRepoSettings {
289
291
  rulesets?: Record<string, Ruleset | false> & {
290
292
  inherit?: boolean;
291
293
  };
292
- repo?: GitHubRepoSettings;
294
+ repo?: GitHubRepoSettings | false;
293
295
  deleteOrphaned?: boolean;
294
296
  }
295
297
  export interface RawRepoConfig {
@@ -20,6 +20,8 @@ const PROPERTY_MAPPING = {
20
20
  squashMergeCommitMessage: "squash_merge_commit_message",
21
21
  mergeCommitTitle: "merge_commit_title",
22
22
  mergeCommitMessage: "merge_commit_message",
23
+ webCommitSignoffRequired: "web_commit_signoff_required",
24
+ defaultBranch: "default_branch",
23
25
  vulnerabilityAlerts: "_vulnerability_alerts",
24
26
  automatedSecurityFixes: "_automated_security_fixes",
25
27
  secretScanning: "_secret_scanning",
@@ -29,6 +29,9 @@ function getWarning(change) {
29
29
  change.newValue === false) {
30
30
  return `disabling ${change.property} may hide existing content`;
31
31
  }
32
+ if (change.property === "defaultBranch") {
33
+ return `changing default branch may affect existing PRs, CI workflows, and branch protections`;
34
+ }
32
35
  return undefined;
33
36
  }
34
37
  /**
@@ -32,6 +32,8 @@ function configToGitHubPayload(settings) {
32
32
  "squashMergeCommitMessage",
33
33
  "mergeCommitTitle",
34
34
  "mergeCommitMessage",
35
+ "webCommitSignoffRequired",
36
+ "defaultBranch",
35
37
  ];
36
38
  for (const key of directMappings) {
37
39
  if (settings[key] !== undefined) {
@@ -26,6 +26,8 @@ export interface CurrentRepoSettings {
26
26
  squash_merge_commit_message?: string;
27
27
  merge_commit_title?: string;
28
28
  merge_commit_message?: string;
29
+ web_commit_signoff_required?: boolean;
30
+ default_branch?: string;
29
31
  security_and_analysis?: {
30
32
  secret_scanning?: {
31
33
  status: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.5.8",
3
+ "version": "3.7.0",
4
4
  "description": "CLI tool for repository-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",