@aspruyt/xfg 3.9.4 → 3.9.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.
package/README.md CHANGED
@@ -8,7 +8,9 @@
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
 
11
- A CLI tool for repository-as-code. Sync files and manage settings across GitHub, Azure DevOps, and GitLab.
11
+ Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab — declaratively, from a single YAML config.
12
+
13
+ Define your organization's standards once. xfg creates PRs to sync config files, applies repository settings and rulesets via API, and can even create, fork, or migrate repositories — all from one config file.
12
14
 
13
15
  **[Full Documentation](https://anthony-spruyt.github.io/xfg/)**
14
16
 
@@ -48,7 +50,7 @@ gh auth login
48
50
  # Sync files across repos
49
51
  xfg sync --config ./config.yaml
50
52
 
51
- # Apply repository settings
53
+ # Apply repository settings and rulesets
52
54
  xfg settings --config ./config.yaml
53
55
  ```
54
56
 
@@ -56,7 +58,8 @@ xfg settings --config ./config.yaml
56
58
 
57
59
  ```yaml
58
60
  # sync-config.yaml
59
- id: my-org-config
61
+ id: my-org-standards
62
+
60
63
  files:
61
64
  .prettierrc.json:
62
65
  content:
@@ -69,6 +72,7 @@ settings:
69
72
  allowSquashMerge: true
70
73
  deleteBranchOnMerge: true
71
74
  vulnerabilityAlerts: true
75
+ secretScanning: true
72
76
 
73
77
  rulesets:
74
78
  main-protection:
@@ -82,6 +86,10 @@ settings:
82
86
  - type: pull_request
83
87
  parameters:
84
88
  requiredApprovingReviewCount: 1
89
+ - type: required_status_checks
90
+ parameters:
91
+ requiredStatusChecks:
92
+ - context: "ci/build"
85
93
 
86
94
  repos:
87
95
  - git:
@@ -89,8 +97,8 @@ repos:
89
97
  - git@github.com:your-org/backend-api.git
90
98
  ```
91
99
 
92
- **Result:** PRs are created with `.prettierrc.json` files, and repos get standardized merge options, security settings, and branch protection rules.
100
+ **Result:** PRs are created with `.prettierrc.json` files, and repos get standardized merge options, security settings, and branch protection rulesets.
93
101
 
94
102
  ## Documentation
95
103
 
96
- See **[anthony-spruyt.github.io/xfg](https://anthony-spruyt.github.io/xfg/)** for configuration reference, examples, platform setup, and troubleshooting.
104
+ See **[anthony-spruyt.github.io/xfg](https://anthony-spruyt.github.io/xfg/)** for the full feature list, configuration reference, examples, platform setup, and troubleshooting.
@@ -27,7 +27,7 @@ function addSharedOptions(cmd) {
27
27
  // =============================================================================
28
28
  program
29
29
  .name("xfg")
30
- .description("Sync files and manage settings across repositories")
30
+ .description("Manage files, settings, and repositories across GitHub, Azure DevOps, and GitLab")
31
31
  .version(packageJson.version);
32
32
  // Sync command (file synchronization)
33
33
  const syncCommand = new Command("sync")
@@ -1,6 +1,7 @@
1
1
  import { defaultExecutor, } from "../../shared/command-executor.js";
2
2
  import { isGitHubRepo, } from "../../shared/repo-detector.js";
3
3
  import { escapeShellArg } from "../../shared/shell-utils.js";
4
+ import { withRetry } from "../../shared/retry-utils.js";
4
5
  /**
5
6
  * Converts camelCase to snake_case.
6
7
  */
@@ -181,9 +182,9 @@ export class GitHubRepoSettingsStrategy {
181
182
  (method === "POST" || method === "PUT" || method === "PATCH")) {
182
183
  const payloadJson = JSON.stringify(payload);
183
184
  const command = `echo ${escapeShellArg(payloadJson)} | ${tokenPrefix}${baseCommand} --input -`;
184
- return await this.executor.exec(command, process.cwd());
185
+ return await withRetry(() => this.executor.exec(command, process.cwd()));
185
186
  }
186
187
  const command = `${tokenPrefix}${baseCommand}`;
187
- return await this.executor.exec(command, process.cwd());
188
+ return await withRetry(() => this.executor.exec(command, process.cwd()));
188
189
  }
189
190
  }
@@ -1,6 +1,7 @@
1
1
  import { defaultExecutor, } from "../../shared/command-executor.js";
2
2
  import { isGitHubRepo, } from "../../shared/repo-detector.js";
3
3
  import { escapeShellArg } from "../../shared/shell-utils.js";
4
+ import { withRetry } from "../../shared/retry-utils.js";
4
5
  // =============================================================================
5
6
  // Conversion Functions
6
7
  // =============================================================================
@@ -201,10 +202,10 @@ export class GitHubRulesetStrategy {
201
202
  if (payload && (method === "POST" || method === "PUT")) {
202
203
  const payloadJson = JSON.stringify(payload);
203
204
  const command = `echo ${escapeShellArg(payloadJson)} | ${tokenPrefix}${baseCommand} --input -`;
204
- return await this.executor.exec(command, process.cwd());
205
+ return await withRetry(() => this.executor.exec(command, process.cwd()));
205
206
  }
206
207
  // For GET/DELETE, run command directly
207
208
  const command = `${tokenPrefix}${baseCommand}`;
208
- return await this.executor.exec(command, process.cwd());
209
+ return await withRetry(() => this.executor.exec(command, process.cwd()));
209
210
  }
210
211
  }
@@ -1,6 +1,7 @@
1
1
  import { defaultExecutor, } from "../shared/command-executor.js";
2
2
  import { isGitHubRepo } from "../shared/repo-detector.js";
3
3
  import { escapeShellArg } from "../shared/shell-utils.js";
4
+ import { withRetry, DEFAULT_PERMANENT_ERROR_PATTERNS, } from "../shared/retry-utils.js";
4
5
  /**
5
6
  * Maximum payload size for GitHub GraphQL API (50MB).
6
7
  * Base64 encoding adds ~33% overhead, so raw content should be checked.
@@ -29,6 +30,16 @@ export function validateBranchName(branchName) {
29
30
  `alphanumeric characters, hyphens, underscores, dots, and forward slashes.`);
30
31
  }
31
32
  }
33
+ /**
34
+ * OID mismatch error patterns that should NOT be retried by the inner withRetry.
35
+ * The outer retry loop in commit() handles these by fetching a fresh HEAD OID.
36
+ */
37
+ const OID_MISMATCH_PATTERNS = [
38
+ /expected branch to point to/i,
39
+ /expectedheadoid/i,
40
+ /head oid/i,
41
+ /was provided invalid value/i,
42
+ ];
32
43
  /**
33
44
  * GraphQL-based commit strategy using GitHub's createCommitOnBranch mutation.
34
45
  * Used with GitHub App authentication. Commits via this strategy ARE verified
@@ -162,7 +173,12 @@ export class GraphQLCommitStrategy {
162
173
  // GH_TOKEN env var must be set for the gh command (after the pipe), not echo
163
174
  const tokenPrefix = token ? `GH_TOKEN=${token} ` : "";
164
175
  const command = `echo ${escapeShellArg(requestBody)} | ${tokenPrefix}gh api graphql ${hostnameArg} --input -`;
165
- const response = await this.executor.exec(command, workDir);
176
+ const response = await withRetry(() => this.executor.exec(command, workDir), {
177
+ permanentErrorPatterns: [
178
+ ...DEFAULT_PERMANENT_ERROR_PATTERNS,
179
+ ...OID_MISMATCH_PATTERNS,
180
+ ],
181
+ });
166
182
  // Parse the response
167
183
  const parsed = JSON.parse(response);
168
184
  if (parsed.errors) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "3.9.4",
4
- "description": "CLI tool for repository-as-code: sync files and manage settings across GitHub, Azure DevOps, and GitLab",
3
+ "version": "3.9.6",
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",
7
7
  "bin": {