@aspruyt/xfg 1.4.0 → 1.5.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.
Files changed (2) hide show
  1. package/README.md +11 -1001
  2. package/package.json +5 -3
package/README.md CHANGED
@@ -5,25 +5,9 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dw/@aspruyt/xfg.svg)](https://www.npmjs.com/package/@aspruyt/xfg)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- A CLI tool that syncs JSON, JSON5, YAML, or text configuration files across multiple GitHub, Azure DevOps, and GitLab repositories by creating pull requests (or merge requests for GitLab). Output format is automatically detected from the target filename extension (`.json` → JSON, `.json5` → JSON5, `.yaml`/`.yml` → YAML, other → text).
8
+ A CLI tool that syncs JSON, JSON5, YAML, or text configuration files across multiple GitHub, Azure DevOps, and GitLab repositories by creating pull requests.
9
9
 
10
- ## Table of Contents
11
-
12
- - [Quick Start](#quick-start)
13
- - [Features](#features)
14
- - [How It Works](#how-it-works)
15
- - [Installation](#installation)
16
- - [Prerequisites](#prerequisites)
17
- - [Usage](#usage)
18
- - [Configuration Format](#configuration-format)
19
- - [Examples](#examples)
20
- - [Supported Git URL Formats](#supported-git-url-formats)
21
- - [CI/CD Integration](#cicd-integration)
22
- - [Output Examples](#output-examples)
23
- - [Troubleshooting](#troubleshooting)
24
- - [IDE Integration](#ide-integration)
25
- - [Development](#development)
26
- - [License](#license)
10
+ **[Full Documentation](https://anthony-spruyt.github.io/xfg/)**
27
11
 
28
12
  ## Quick Start
29
13
 
@@ -45,7 +29,6 @@ files:
45
29
  trailingComma: es5
46
30
 
47
31
  repos:
48
- # Multiple repos can share the same config
49
32
  - git:
50
33
  - git@github.com:your-org/frontend-app.git
51
34
  - git@github.com:your-org/backend-api.git
@@ -61,7 +44,7 @@ xfg --config ./config.yaml
61
44
  ## Features
62
45
 
63
46
  - **Multi-File Sync** - Sync multiple config files in a single run
64
- - **Multi-Format Output** - JSON, YAML, or plain text based on filename extension
47
+ - **Multi-Format Output** - JSON, JSON5, YAML, or plain text based on filename extension
65
48
  - **Subdirectory Support** - Sync files to any path (e.g., `.github/workflows/ci.yaml`)
66
49
  - **Text Files** - Sync `.gitignore`, `.markdownlintignore`, etc. with string or lines array
67
50
  - **File References** - Use `@path/to/file` to load content from external template files
@@ -78,986 +61,13 @@ xfg --config ./config.yaml
78
61
  - **Error Resilience** - Continues processing if individual repos fail
79
62
  - **Automatic Retries** - Retries transient network errors with exponential backoff
80
63
 
81
- ## Installation
82
-
83
- ### From npm
84
-
85
- ```bash
86
- npm install -g @aspruyt/xfg
87
- ```
88
-
89
- ### From Source
90
-
91
- ```bash
92
- git clone https://github.com/anthony-spruyt/xfg.git
93
- cd xfg
94
- npm install
95
- npm run build
96
- ```
97
-
98
- ### Using Dev Container
99
-
100
- Open this repository in VS Code with the Dev Containers extension. The container includes all dependencies pre-installed and the project pre-built.
101
-
102
- ## Prerequisites
103
-
104
- ### GitHub Authentication
105
-
106
- Before using with GitHub repositories, authenticate with the GitHub CLI:
107
-
108
- ```bash
109
- gh auth login
110
- ```
111
-
112
- ### Azure DevOps Authentication
113
-
114
- Before using with Azure DevOps repositories, authenticate with the Azure CLI:
115
-
116
- ```bash
117
- az login
118
- az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG project=YOUR_PROJECT
119
- ```
120
-
121
- ### GitLab Authentication
122
-
123
- Before using with GitLab repositories, authenticate with the GitLab CLI:
124
-
125
- ```bash
126
- glab auth login
127
- ```
128
-
129
- For self-hosted GitLab instances:
130
-
131
- ```bash
132
- glab auth login --hostname gitlab.example.com
133
- ```
134
-
135
- ## Usage
136
-
137
- ```bash
138
- # Basic usage
139
- xfg --config ./config.yaml
140
-
141
- # Dry run (no changes made)
142
- xfg --config ./config.yaml --dry-run
143
-
144
- # Custom work directory
145
- xfg --config ./config.yaml --work-dir ./my-temp
146
-
147
- # Custom branch name
148
- xfg --config ./config.yaml --branch feature/update-eslint
149
- ```
150
-
151
- ### Options
152
-
153
- | Option | Alias | Description | Required |
154
- | ------------------ | ----- | ------------------------------------------------------------------------------ | -------- |
155
- | `--config` | `-c` | Path to YAML config file | Yes |
156
- | `--dry-run` | `-d` | Show what would be done without making changes | No |
157
- | `--work-dir` | `-w` | Temporary directory for cloning (default: `./tmp`) | No |
158
- | `--retries` | `-r` | Number of retries for network operations (default: 3) | No |
159
- | `--branch` | `-b` | Override branch name (default: `chore/sync-{filename}` or `chore/sync-config`) | No |
160
- | `--merge` | `-m` | PR merge mode: `manual`, `auto` (default), `force` (bypass checks) | No |
161
- | `--merge-strategy` | | Merge strategy: `merge`, `squash` (default), `rebase` | No |
162
- | `--delete-branch` | | Delete source branch after merge | No |
163
-
164
- ## Configuration Format
165
-
166
- ### Basic Structure
167
-
168
- ```yaml
169
- files:
170
- my.config.json: # Target file (.json outputs JSON, .yaml/.yml outputs YAML)
171
- mergeStrategy: replace # Array merge strategy for this file (optional)
172
- content: # Base config content
173
- key: value
174
-
175
- repos: # List of repositories
176
- - git: git@github.com:org/repo.git
177
- files: # Per-repo file overrides (optional)
178
- my.config.json:
179
- content:
180
- key: override
181
- ```
182
-
183
- ### Root-Level Fields
184
-
185
- | Field | Description | Required |
186
- | ------------ | ------------------------------------------------------------- | -------- |
187
- | `files` | Map of target filenames to configs | Yes |
188
- | `repos` | Array of repository configurations | Yes |
189
- | `prOptions` | Global PR merge options (can be overridden per-repo) | No |
190
- | `prTemplate` | Custom PR body template (inline or `@path/to/file` reference) | No |
191
-
192
- ### Per-File Fields
193
-
194
- | Field | Description | Required |
195
- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
196
- | `content` | Base config: object for JSON/YAML files, string or string[] for text files, or `@path/to/file` to load from external template (omit for empty file) | No |
197
- | `mergeStrategy` | Merge strategy: `replace`, `append`, `prepend` (for arrays and text lines) | No |
198
- | `createOnly` | If `true`, only create file if it doesn't exist | No |
199
- | `executable` | Mark file as executable. `.sh` files are auto-executable unless set to `false`. Set to `true` for non-.sh files. | No |
200
- | `header` | Comment line(s) at top of YAML files (string or array) | No |
201
- | `schemaUrl` | Adds `# yaml-language-server: $schema=<url>` to YAML files | No |
202
-
203
- ### Per-Repo Fields
204
-
205
- | Field | Description | Required |
206
- | ----------- | -------------------------------------------- | -------- |
207
- | `git` | Git URL (string) or array of URLs | Yes |
208
- | `files` | Per-repo file overrides (optional) | No |
209
- | `prOptions` | Per-repo PR merge options (overrides global) | No |
210
-
211
- ### PR Options Fields
212
-
213
- | Field | Description | Default |
214
- | --------------- | --------------------------------------------------------------------------- | -------- |
215
- | `merge` | Merge mode: `manual` (leave open), `auto` (merge when checks pass), `force` | `auto` |
216
- | `mergeStrategy` | How to merge: `merge`, `squash`, `rebase` | `squash` |
217
- | `deleteBranch` | Delete source branch after merge | `true` |
218
- | `bypassReason` | Reason for bypassing policies (Azure DevOps only, required for `force`) | - |
219
-
220
- ### PR Template Customization
221
-
222
- Customize the PR body with a template. Use the `{{FILE_CHANGES}}` placeholder for the list of changed files:
223
-
224
- ```yaml
225
- # Inline template
226
- prTemplate: |
227
- ## Configuration Update
228
-
229
- This PR synchronizes the following files:
230
-
231
- {{FILE_CHANGES}}
232
-
233
- Please review and merge.
234
-
235
- # Or reference an external file
236
- prTemplate: "@templates/pr-body.md"
237
- ```
238
-
239
- **Available Placeholders:**
240
-
241
- | Placeholder | Description | Example Output |
242
- | ------------------ | ----------------------------------- | -------------------------------------------------------- |
243
- | `{{FILE_CHANGES}}` | Bulleted list of files with actions | `- Created \`config.json\`\n- Updated \`settings.yaml\`` |
244
-
245
- **Default Template:** If not specified, uses the built-in template with Summary, Changes, and Source sections.
246
-
247
- ### Per-Repo File Override Fields
248
-
249
- | Field | Description | Required |
250
- | ------------ | ------------------------------------------------------- | -------- |
251
- | `content` | Content overlay merged onto file's base content | No |
252
- | `override` | If `true`, ignore base content and use only this repo's | No |
253
- | `createOnly` | Override root-level `createOnly` for this repo | No |
254
- | `executable` | Override root-level `executable` for this repo | No |
255
- | `header` | Override root-level `header` for this repo | No |
256
- | `schemaUrl` | Override root-level `schemaUrl` for this repo | No |
257
-
258
- **File Exclusion:** Set a file to `false` to exclude it from a specific repo:
259
-
260
- ```yaml
261
- repos:
262
- - git: git@github.com:org/repo.git
263
- files:
264
- eslint.json: false # This repo won't receive eslint.json
265
- ```
266
-
267
- ### Environment Variables
268
-
269
- Use `${VAR}` syntax in string values:
270
-
271
- ```yaml
272
- files:
273
- app.config.json:
274
- content:
275
- apiUrl: ${API_URL} # Required - errors if not set
276
- environment: ${ENV:-development} # With default value
277
- secretKey: ${SECRET:?Secret required} # Required with custom error message
278
-
279
- repos:
280
- - git: git@github.com:org/backend.git
281
- ```
282
-
283
- #### Escaping Variable Syntax
284
-
285
- If your target file needs literal `${VAR}` syntax (e.g., for devcontainer.json, shell scripts, or other templating systems), use `$$` to escape:
286
-
287
- ```yaml
288
- files:
289
- .devcontainer/devcontainer.json:
290
- content:
291
- name: my-dev-container
292
- remoteEnv:
293
- # Escaped - outputs literal ${localWorkspaceFolder} for VS Code
294
- LOCAL_WORKSPACE_FOLDER: "$${localWorkspaceFolder}"
295
- CONTAINER_WORKSPACE: "$${containerWorkspaceFolder}"
296
- # Interpolated - replaced with actual env value
297
- API_KEY: "${API_KEY}"
298
- ```
299
-
300
- Output:
301
-
302
- ```json
303
- {
304
- "name": "my-dev-container",
305
- "remoteEnv": {
306
- "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}",
307
- "CONTAINER_WORKSPACE": "${containerWorkspaceFolder}",
308
- "API_KEY": "actual-api-key-value"
309
- }
310
- }
311
- ```
312
-
313
- This follows the same escape convention used by Docker Compose.
314
-
315
- ### Merge Directives
316
-
317
- Control array merging with the `$arrayMerge` directive:
318
-
319
- ```yaml
320
- files:
321
- config.json:
322
- content:
323
- features:
324
- - core
325
- - monitoring
326
-
327
- repos:
328
- - git: git@github.com:org/repo.git
329
- files:
330
- config.json:
331
- content:
332
- features:
333
- $arrayMerge: append # append | prepend | replace
334
- values:
335
- - custom-feature # Results in: [core, monitoring, custom-feature]
336
- ```
337
-
338
- ## Examples
339
-
340
- ### Multi-File Sync
341
-
342
- Sync multiple configuration files to all repos:
343
-
344
- ```yaml
345
- files:
346
- .eslintrc.json:
347
- content:
348
- extends: ["@org/eslint-config"]
349
- rules:
350
- no-console: warn
351
-
352
- .prettierrc.yaml:
353
- content:
354
- semi: false
355
- singleQuote: true
356
-
357
- tsconfig.json:
358
- content:
359
- compilerOptions:
360
- strict: true
361
- target: ES2022
362
-
363
- repos:
364
- - git:
365
- - git@github.com:org/frontend.git
366
- - git@github.com:org/backend.git
367
- - git@github.com:org/shared-lib.git
368
- ```
369
-
370
- ### Shared Config Across Teams
371
-
372
- Define common settings once, customize per team:
373
-
374
- ```yaml
375
- files:
376
- service.config.json:
377
- content:
378
- version: "2.0"
379
- logging:
380
- level: info
381
- format: json
382
- features:
383
- - health-check
384
- - metrics
385
-
386
- repos:
387
- # Platform team repos - add extra features
388
- - git:
389
- - git@github.com:org/api-gateway.git
390
- - git@github.com:org/auth-service.git
391
- files:
392
- service.config.json:
393
- content:
394
- team: platform
395
- features:
396
- $arrayMerge: append
397
- values:
398
- - tracing
399
- - rate-limiting
400
-
401
- # Data team repos - different logging
402
- - git:
403
- - git@github.com:org/data-pipeline.git
404
- - git@github.com:org/analytics.git
405
- files:
406
- service.config.json:
407
- content:
408
- team: data
409
- logging:
410
- level: debug
411
-
412
- # Legacy service - completely different config
413
- - git: git@github.com:org/legacy-api.git
414
- files:
415
- service.config.json:
416
- override: true
417
- content:
418
- version: "1.0"
419
- legacy: true
420
- ```
421
-
422
- ### Environment-Specific Values
423
-
424
- Use environment variables for secrets and environment-specific values:
425
-
426
- ```yaml
427
- files:
428
- app.config.json:
429
- content:
430
- database:
431
- host: ${DB_HOST:-localhost}
432
- port: ${DB_PORT:-5432}
433
- password: ${DB_PASSWORD:?Database password required}
434
-
435
- api:
436
- baseUrl: ${API_BASE_URL}
437
- timeout: 30000
438
-
439
- repos:
440
- - git: git@github.com:org/backend.git
441
- ```
442
-
443
- ### Per-File Merge Strategies
444
-
445
- Different files can use different array merge strategies:
446
-
447
- ```yaml
448
- files:
449
- eslint.config.json:
450
- mergeStrategy: append # Extends will append
451
- content:
452
- extends: ["@company/base"]
453
-
454
- tsconfig.json:
455
- mergeStrategy: replace # Lib will replace entirely
456
- content:
457
- compilerOptions:
458
- lib: ["ES2022"]
459
-
460
- repos:
461
- - git: git@github.com:org/frontend.git
462
- files:
463
- eslint.config.json:
464
- content:
465
- extends: ["plugin:react/recommended"]
466
- # Results in extends: ["@company/base", "plugin:react/recommended"]
467
- ```
468
-
469
- ### Create-Only Files (Defaults That Can Be Customized)
470
-
471
- Some files should only be created once as defaults, allowing repos to maintain their own versions:
472
-
473
- ```yaml
474
- files:
475
- .trivyignore.yaml:
476
- createOnly: true # Only create if doesn't exist
477
- content:
478
- vulnerabilities: []
479
-
480
- .prettierignore:
481
- createOnly: true
482
- content:
483
- patterns:
484
- - "dist/"
485
- - "node_modules/"
486
-
487
- eslint.config.json:
488
- content: # Always synced (no createOnly)
489
- extends: ["@company/base"]
490
-
491
- repos:
492
- - git: git@github.com:org/repo.git
493
- # .trivyignore.yaml and .prettierignore only created if missing
494
- # eslint.config.json always updated
495
-
496
- - git: git@github.com:org/special-repo.git
497
- files:
498
- .trivyignore.yaml:
499
- createOnly: false # Override: always sync this file
500
- ```
501
-
502
- ### YAML Comments and Empty Files
503
-
504
- Add schema directives and comments to YAML files, or create empty files:
505
-
506
- ```yaml
507
- files:
508
- # YAML file with schema directive and header comment
509
- trivy.yaml:
510
- schemaUrl: https://trivy.dev/latest/docs/references/configuration/config-file/
511
- header: "Trivy security scanner configuration"
512
- content:
513
- exit-code: 1
514
- scan:
515
- scanners:
516
- - vuln
517
-
518
- # Empty file (content omitted)
519
- .prettierignore:
520
- createOnly: true
521
- # No content = empty file
522
-
523
- # YAML with multi-line header
524
- config.yaml:
525
- header:
526
- - "Auto-generated configuration"
527
- - "Do not edit manually"
528
- content:
529
- version: 1
530
-
531
- repos:
532
- - git: git@github.com:org/repo.git
533
- ```
534
-
535
- **Output for trivy.yaml:**
536
-
537
- ```yaml
538
- # yaml-language-server: $schema=https://trivy.dev/latest/docs/references/configuration/config-file/
539
- # Trivy security scanner configuration
540
- exit-code: 1
541
- scan:
542
- scanners:
543
- - vuln
544
- ```
545
-
546
- **Note:** `header` and `schemaUrl` only apply to YAML output files (`.yaml`, `.yml`). They are ignored for JSON files.
547
-
548
- ### Text Files
549
-
550
- Sync text files like `.gitignore`, `.markdownlintignore`, or `.env.example` using string or lines array content:
551
-
552
- ```yaml
553
- files:
554
- # String content (multiline text)
555
- .markdownlintignore:
556
- createOnly: true
557
- content: |-
558
- # Claude Code generated files
559
- .claude/
560
-
561
- # Lines array with merge strategy
562
- .gitignore:
563
- mergeStrategy: append
564
- content:
565
- - "node_modules/"
566
- - "dist/"
567
-
568
- repos:
569
- - git: git@github.com:org/repo.git
570
- files:
571
- .gitignore:
572
- content:
573
- - "coverage/" # Appended to base lines
574
- ```
575
-
576
- **Content Types:**
577
-
578
- - **String content** (`content: |-`) - Direct text output with environment variable interpolation. Merging always replaces the base.
579
- - **Lines array** (`content: ['line1', 'line2']`) - Each line joined with newlines. Supports merge strategies (`append`, `prepend`, `replace`).
580
-
581
- **Validation:** JSON/JSON5/YAML file extensions (`.json`, `.json5`, `.yaml`, `.yml`) require object content. Other extensions require string or string[] content.
582
-
583
- ### Executable Files
584
-
585
- Shell scripts (`.sh` files) are automatically marked as executable using `git update-index --add --chmod=+x`. You can control this behavior:
586
-
587
- ```yaml
588
- files:
589
- # .sh files are auto-executable (no config needed)
590
- deploy.sh:
591
- content: |-
592
- #!/bin/bash
593
- echo "Deploying..."
594
-
595
- # Disable auto-executable for a specific .sh file
596
- template.sh:
597
- executable: false
598
- content: "# This is just a template"
599
-
600
- # Make a non-.sh file executable
601
- run:
602
- executable: true
603
- content: |-
604
- #!/usr/bin/env python3
605
- print("Hello")
606
-
607
- repos:
608
- - git: git@github.com:org/repo.git
609
- files:
610
- # Override executable per-repo
611
- deploy.sh:
612
- executable: false # Disable for this repo
613
- ```
614
-
615
- **Behavior:**
616
-
617
- - `.sh` files: Automatically executable unless `executable: false`
618
- - Other files: Not executable unless `executable: true`
619
- - Per-repo settings override root-level settings
620
-
621
- ### Subdirectory Paths
622
-
623
- Sync files to any subdirectory path - parent directories are created automatically:
624
-
625
- ```yaml
626
- files:
627
- # GitHub Actions workflow
628
- ".github/workflows/ci.yaml":
629
- content:
630
- name: CI
631
- on: [push, pull_request]
632
- jobs:
633
- build:
634
- runs-on: ubuntu-latest
635
- steps:
636
- - uses: actions/checkout@v4
637
-
638
- # Nested config directory
639
- "config/settings/app.json":
640
- content:
641
- environment: production
642
- debug: false
643
-
644
- repos:
645
- - git:
646
- - git@github.com:org/frontend.git
647
- - git@github.com:org/backend.git
648
- ```
649
-
650
- **Note:** Quote file paths containing `/` in YAML keys. Parent directories are created if they don't exist.
651
-
652
- ### File References
653
-
654
- Instead of inline content, you can reference external template files using the `@path/to/file` syntax:
655
-
656
- ```yaml
657
- files:
658
- .prettierrc.json:
659
- content: "@templates/prettierrc.json"
660
- .eslintrc.yaml:
661
- content: "@templates/eslintrc.yaml"
662
- header: "Auto-generated - do not edit"
663
- schemaUrl: "https://json.schemastore.org/eslintrc"
664
- .gitignore:
665
- content: "@templates/gitignore.txt"
666
-
667
- repos:
668
- - git: git@github.com:org/repo.git
669
- ```
670
-
671
- **How it works:**
672
-
673
- - File references start with `@` followed by a relative path
674
- - Paths are resolved relative to the config file's directory
675
- - JSON/JSON5/YAML files are parsed as objects, other files as strings
676
- - Metadata fields (`header`, `schemaUrl`, `createOnly`, `mergeStrategy`) remain in the config
677
- - Per-repo overlays still work - they merge onto the resolved file content
678
-
679
- **Example directory structure:**
680
-
681
- ```
682
- config/
683
- sync-config.yaml # content: "@templates/prettier.json"
684
- templates/
685
- prettier.json # Actual Prettier config
686
- eslintrc.yaml # Actual ESLint config
687
- gitignore.txt # Template .gitignore content
688
- ```
689
-
690
- **Security:** File references are restricted to the config file's directory tree. Paths like `@../../../etc/passwd` or `@/etc/passwd` are blocked.
691
-
692
- ### Auto-Merge PRs
693
-
694
- By default, xfg enables auto-merge on PRs when checks pass. You can override this behavior per-repo or via CLI flags:
695
-
696
- ```yaml
697
- files:
698
- .prettierrc.json:
699
- content:
700
- semi: false
701
- singleQuote: true
702
-
703
- # Default behavior (auto-merge with squash, delete branch) - no prOptions needed
704
- repos:
705
- # These repos use defaults (auto-merge with squash, delete branch)
706
- - git:
707
- - git@github.com:org/frontend.git
708
- - git@github.com:org/backend.git
709
-
710
- # This repo overrides to manual (leave PR open for review)
711
- - git: git@github.com:org/needs-review.git
712
- prOptions:
713
- merge: manual
714
-
715
- # This repo overrides to force merge (bypass required reviews)
716
- - git: git@github.com:org/internal-tool.git
717
- prOptions:
718
- merge: force
719
- bypassReason: "Automated config sync" # Azure DevOps only
720
- ```
721
-
722
- **Merge Modes:**
723
-
724
- | Mode | GitHub Behavior | Azure DevOps Behavior | GitLab Behavior |
725
- | -------- | ---------------------------------------------------- | -------------------------------------- | ------------------------------------------ |
726
- | `manual` | Leave PR open for review | Leave PR open for review | Leave MR open for review |
727
- | `auto` | Enable auto-merge (requires repo setup, **default**) | Enable auto-complete (**default**) | Merge when pipeline succeeds (**default**) |
728
- | `force` | Merge with `--admin` (bypass checks) | Bypass policies with `--bypass-policy` | Merge immediately |
729
-
730
- **GitHub Auto-Merge Note:** The `auto` mode requires auto-merge to be enabled in the repository settings. If not enabled, the tool will warn and leave the PR open for manual review. Enable it with:
731
-
732
- ```bash
733
- gh repo edit org/repo --enable-auto-merge
734
- ```
735
-
736
- **CLI Override:** You can override config file settings with CLI flags:
737
-
738
- ```bash
739
- # Disable auto-merge, leave PRs open for review
740
- xfg --config ./config.yaml --merge manual
741
-
742
- # Force merge all PRs (useful for urgent updates)
743
- xfg --config ./config.yaml --merge force
744
- ```
745
-
746
- ## Supported Git URL Formats
747
-
748
- ### GitHub
749
-
750
- - SSH: `git@github.com:owner/repo.git`
751
- - HTTPS: `https://github.com/owner/repo.git`
752
-
753
- ### Azure DevOps
754
-
755
- - SSH: `git@ssh.dev.azure.com:v3/organization/project/repo`
756
- - HTTPS: `https://dev.azure.com/organization/project/_git/repo`
757
-
758
- ### GitLab
759
-
760
- - SSH: `git@gitlab.com:owner/repo.git`
761
- - HTTPS: `https://gitlab.com/owner/repo.git`
762
- - Nested groups: `git@gitlab.com:org/group/subgroup/repo.git`
763
- - Self-hosted SSH: `git@gitlab.example.com:owner/repo.git`
764
- - Self-hosted HTTPS: `https://gitlab.example.com/owner/repo.git`
765
-
766
- ## How It Works
767
-
768
- ```mermaid
769
- flowchart TB
770
- subgraph Input
771
- YAML[/"YAML Config File<br/>files{} + repos[]"/]
772
- end
773
-
774
- subgraph Normalization
775
- EXPAND[Expand git arrays] --> MERGE[Merge base + overlay content<br/>for each file]
776
- MERGE --> ENV[Interpolate env vars]
777
- end
778
-
779
- subgraph Processing["For Each Repository"]
780
- CLONE[Clone Repo] --> DETECT_BRANCH[Detect Default Branch]
781
- DETECT_BRANCH --> CLOSE_PR[Close Existing PR<br/>if exists]
782
- CLOSE_PR --> BRANCH[Create Fresh Branch]
783
- BRANCH --> WRITE[Write Config Files]
784
- WRITE --> CHECK{Changes?}
785
- CHECK -->|No| SKIP[Skip - No Changes]
786
- CHECK -->|Yes| COMMIT[Commit & Push]
787
- end
788
-
789
- subgraph Platform["PR Creation"]
790
- COMMIT --> PR_DETECT{Platform?}
791
- PR_DETECT -->|GitHub| GH_PR[Create PR via gh CLI]
792
- PR_DETECT -->|Azure DevOps| AZ_PR[Create PR via az CLI]
793
- PR_DETECT -->|GitLab| GL_PR[Create MR via glab CLI]
794
- GH_PR --> PR_CREATED[PR/MR Created]
795
- AZ_PR --> PR_CREATED
796
- GL_PR --> PR_CREATED
797
- end
798
-
799
- subgraph AutoMerge["Auto-Merge (default)"]
800
- PR_CREATED --> MERGE_MODE{Merge Mode?}
801
- MERGE_MODE -->|manual| OPEN[Leave PR Open]
802
- MERGE_MODE -->|auto| AUTO[Enable Auto-Merge]
803
- MERGE_MODE -->|force| FORCE[Bypass & Merge]
804
- end
805
-
806
- YAML --> EXPAND
807
- ENV --> CLONE
808
- ```
809
-
810
- For each repository in the config, the tool:
811
-
812
- 1. Expands git URL arrays into individual entries
813
- 2. For each file, merges base content with per-repo overlay
814
- 3. Interpolates environment variables
815
- 4. Cleans the temporary workspace
816
- 5. Clones the repository
817
- 6. Detects the default branch (main/master)
818
- 7. Closes any existing PR on the branch and deletes the remote branch (fresh start)
819
- 8. Creates a fresh branch from the default branch
820
- 9. Writes all config files (JSON, JSON5, YAML, or text based on filename extension)
821
- 10. Checks for changes (skips if no changes)
822
- 11. Commits and pushes changes
823
- 12. Creates a pull request
824
- 13. Handles auto-merge based on configuration (auto by default)
64
+ ## Documentation
825
65
 
826
- ## CI/CD Integration
66
+ Visit **[anthony-spruyt.github.io/xfg](https://anthony-spruyt.github.io/xfg/)** for:
827
67
 
828
- ### GitHub Actions
829
-
830
- ```yaml
831
- name: Sync Configs
832
- on:
833
- push:
834
- branches: [main]
835
- paths: ["config.yaml"]
836
-
837
- jobs:
838
- sync:
839
- runs-on: ubuntu-latest
840
- steps:
841
- - uses: actions/checkout@v4
842
- - uses: actions/setup-node@v4
843
- with:
844
- node-version: "20"
845
- - run: npm install -g @aspruyt/xfg
846
- - run: xfg --config ./config.yaml
847
- env:
848
- GH_TOKEN: ${{ secrets.GH_PAT }}
849
- ```
850
-
851
- > **Note:** `GH_PAT` must be a Personal Access Token with `repo` scope to create PRs in target repositories.
852
-
853
- ### Azure Pipelines
854
-
855
- ```yaml
856
- trigger:
857
- branches:
858
- include: [main]
859
- paths:
860
- include: ["config.yaml"]
861
-
862
- pool:
863
- vmImage: "ubuntu-latest"
864
-
865
- steps:
866
- - task: NodeTool@0
867
- inputs:
868
- versionSpec: "20.x"
869
- - script: npm install -g @aspruyt/xfg
870
- displayName: "Install xfg"
871
- - script: xfg --config ./config.yaml
872
- displayName: "Sync configs"
873
- env:
874
- AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
875
- ```
876
-
877
- > **Note:** Ensure the build service account has permission to create PRs in target repositories.
878
-
879
- ## Output Examples
880
-
881
- ### Console Output
882
-
883
- ```
884
- [1/3] Processing example-org/repo1...
885
- ✓ Cloned repository
886
- ✓ Created branch chore/sync-config
887
- ✓ Wrote .eslintrc.json
888
- ✓ Wrote .prettierrc.yaml
889
- ✓ Committed changes
890
- ✓ Pushed to remote
891
- ✓ Created PR: https://github.com/example-org/repo1/pull/42
892
-
893
- [2/3] Processing example-org/repo2...
894
- ✓ Cloned repository
895
- ✓ Checked out existing branch chore/sync-config
896
- ✓ Wrote .eslintrc.json
897
- ✓ Wrote .prettierrc.yaml
898
- ⊘ No changes detected, skipping
899
-
900
- [3/3] Processing example-org/repo3...
901
- ✓ Cloned repository
902
- ✓ Created branch chore/sync-config
903
- ✓ Wrote .eslintrc.json
904
- ✓ Wrote .prettierrc.yaml
905
- ✓ Committed changes
906
- ✓ Pushed to remote
907
- ✓ PR already exists: https://github.com/example-org/repo3/pull/15
908
-
909
- Summary: 2 succeeded, 1 skipped, 0 failed
910
- ```
911
-
912
- ### Created PR
913
-
914
- The tool creates PRs with:
915
-
916
- - **Title:** `chore: sync config files` (or lists files if ≤3)
917
- - **Branch:** `chore/sync-config` (or custom `--branch`)
918
- - **Body:** Describes the sync action and lists changed files
919
-
920
- ## Troubleshooting
921
-
922
- ### Authentication Errors
923
-
924
- **GitHub:**
925
-
926
- ```bash
927
- # Check authentication status
928
- gh auth status
929
-
930
- # Re-authenticate if needed
931
- gh auth login
932
- ```
933
-
934
- **Azure DevOps:**
935
-
936
- ```bash
937
- # Check authentication status
938
- az account show
939
-
940
- # Re-authenticate if needed
941
- az login
942
- az devops configure --defaults organization=https://dev.azure.com/YOUR_ORG
943
- ```
944
-
945
- **GitLab:**
946
-
947
- ```bash
948
- # Check authentication status
949
- glab auth status
950
-
951
- # Re-authenticate if needed
952
- glab auth login
953
-
954
- # For self-hosted instances
955
- glab auth login --hostname gitlab.example.com
956
- ```
957
-
958
- ### Permission Denied
959
-
960
- - Ensure your token has write access to the target repositories
961
- - For GitHub, the token needs `repo` scope
962
- - For Azure DevOps, ensure the user/service account has "Contribute to pull requests" permission
963
- - For GitLab, ensure the user has at least "Developer" role on the project
964
-
965
- ### Branch Already Exists
966
-
967
- The tool uses a fresh-start approach: it closes any existing PR on the branch and deletes the branch before creating a new one. This ensures each sync attempt is isolated and avoids merge conflicts from stale branches.
968
-
969
- If you see issues with stale branches:
970
-
971
- ```bash
972
- # Manually delete the remote branch if needed
973
- git push origin --delete chore/sync-config
974
- ```
975
-
976
- ### Missing Environment Variables
977
-
978
- If you see "Missing required environment variable" errors:
979
-
980
- ```bash
981
- # Set the variable before running
982
- export MY_VAR=value
983
- xfg --config ./config.yaml
984
-
985
- # Or use default values in config
986
- # ${MY_VAR:-default-value}
987
- ```
988
-
989
- ### Network/Proxy Issues
990
-
991
- If cloning fails behind a corporate proxy:
992
-
993
- ```bash
994
- # Configure git proxy
995
- git config --global http.proxy http://proxy.example.com:8080
996
- git config --global https.proxy http://proxy.example.com:8080
997
- ```
998
-
999
- ### Transient Network Errors
1000
-
1001
- The tool automatically retries transient errors (timeouts, connection resets, rate limits) with exponential backoff. By default, it retries 3 times before failing.
1002
-
1003
- ```bash
1004
- # Increase retries for unreliable networks
1005
- xfg --config ./config.yaml --retries 5
1006
-
1007
- # Disable retries
1008
- xfg --config ./config.yaml --retries 0
1009
- ```
1010
-
1011
- Permanent errors (authentication failures, permission denied, repository not found) are not retried.
1012
-
1013
- ## IDE Integration
1014
-
1015
- ### VS Code YAML Schema Support
1016
-
1017
- For autocomplete and validation in VS Code, install the [YAML extension](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) and add a schema reference to your config file:
1018
-
1019
- **Option 1: Inline comment**
1020
-
1021
- ```yaml
1022
- # yaml-language-server: $schema=https://raw.githubusercontent.com/anthony-spruyt/xfg/main/config-schema.json
1023
- files:
1024
- my.config.json:
1025
- content:
1026
- key: value
1027
-
1028
- repos:
1029
- - git: git@github.com:org/repo.git
1030
- ```
1031
-
1032
- **Option 2: VS Code settings** (`.vscode/settings.json`)
1033
-
1034
- ```json
1035
- {
1036
- "yaml.schemas": {
1037
- "https://raw.githubusercontent.com/anthony-spruyt/xfg/main/config-schema.json": [
1038
- "**/sync-config.yaml",
1039
- "**/config-sync.yaml"
1040
- ]
1041
- }
1042
- }
1043
- ```
1044
-
1045
- This enables:
1046
-
1047
- - Autocomplete for `files`, `repos`, `content`, `mergeStrategy`, `git`, `override`
1048
- - Enum suggestions for `mergeStrategy` values (`replace`, `append`, `prepend`)
1049
- - Validation of required fields
1050
- - Hover documentation for each field
1051
-
1052
- ## Development
1053
-
1054
- ```bash
1055
- # Run in development mode
1056
- npm run dev -- --config ./fixtures/test-repos-input.yaml --dry-run
1057
-
1058
- # Run tests
1059
- npm test
1060
-
1061
- # Build
1062
- npm run build
1063
- ```
68
+ - [Getting Started](https://anthony-spruyt.github.io/xfg/getting-started/) - Installation and prerequisites
69
+ - [Configuration](https://anthony-spruyt.github.io/xfg/configuration/) - Full configuration reference
70
+ - [Examples](https://anthony-spruyt.github.io/xfg/examples/) - Real-world usage examples
71
+ - [Platforms](https://anthony-spruyt.github.io/xfg/platforms/) - GitHub, Azure DevOps, GitLab setup
72
+ - [CI/CD Integration](https://anthony-spruyt.github.io/xfg/ci-cd/) - GitHub Actions, Azure Pipelines
73
+ - [Troubleshooting](https://anthony-spruyt.github.io/xfg/troubleshooting/)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aspruyt/xfg",
3
- "version": "1.4.0",
4
- "description": "CLI tool to sync JSON or YAML configuration files across multiple GitHub, Azure DevOps, and GitLab repositories",
3
+ "version": "1.5.0",
4
+ "description": "CLI tool to sync JSON, JSON5, YAML, or text configuration files across multiple Git repositories by creating pull requests",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "type": "git",
16
16
  "url": "git+https://github.com/anthony-spruyt/xfg.git"
17
17
  },
18
- "homepage": "https://github.com/anthony-spruyt/xfg#readme",
18
+ "homepage": "https://anthony-spruyt.github.io/xfg/",
19
19
  "bugs": {
20
20
  "url": "https://github.com/anthony-spruyt/xfg/issues"
21
21
  },
@@ -36,7 +36,9 @@
36
36
  "config",
37
37
  "sync",
38
38
  "json",
39
+ "json5",
39
40
  "yaml",
41
+ "text",
40
42
  "git",
41
43
  "cli",
42
44
  "github",