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