xfg¶
A CLI tool for repository-as-code. Sync files and manage settings across GitHub, Azure DevOps, and GitLab.
Two commands, one config:
xfg sync- Sync JSON, YAML, or text files across repos via PRsxfg settings- Manage GitHub repository settings and rulesets declaratively
Quick Start¶
# Install
npm install -g @aspruyt/xfg
# Authenticate (GitHub)
gh auth login
# Create config.yaml
cat > config.yaml << 'EOF'
files:
.prettierrc.json:
content:
semi: false
singleQuote: true
tabWidth: 2
settings:
repo:
allowSquashMerge: true
deleteBranchOnMerge: true
vulnerabilityAlerts: true
rulesets:
main-protection:
target: branch
enforcement: active
conditions:
refName:
include: [refs/heads/main]
rules:
- type: pull_request
parameters:
requiredApprovingReviewCount: 1
repos:
- git:
- git@github.com:your-org/frontend-app.git
- git@github.com:your-org/backend-api.git
- git@github.com:your-org/shared-lib.git
EOF
# Sync files
xfg sync --config ./config.yaml
# Apply rulesets
xfg settings --config ./config.yaml
Result: PRs are created with .prettierrc.json files, and all repos get standardized merge options, security settings, and branch protection rules.
Features¶
File Sync (xfg sync)¶
- Multi-File Sync - Sync multiple config files in a single run
- Multi-Format Output - JSON, YAML, or plain text based on filename extension
- Subdirectory Support - Sync files to any path (e.g.,
.github/workflows/ci.yaml) - Text Files - Sync
.gitignore,.markdownlintignore, etc. with string or lines array - File References - Use
@path/to/fileto load content from external template files - Content Inheritance - Define base config once, override per-repo as needed
- Multi-Repo Targeting - Apply same config to multiple repos with array syntax
- Environment Variables - Use
${VAR}syntax for dynamic values - Merge Strategies - Control how arrays merge (replace, append, prepend)
- Override Mode - Skip merging entirely for specific repos
- Empty Files - Create files with no content (e.g.,
.prettierignore) - YAML Comments - Add header comments and schema directives to YAML files
GitHub Settings (xfg settings)¶
Repository Settings¶
- Merge Options - Control squash, rebase, and merge commit strategies; auto-delete branches
- Security Settings - Enable Dependabot alerts, automated security fixes, secret scanning
- Feature Toggles - Enable/disable Issues, Wiki, Projects, Discussions
- Visibility Control - Manage public/private/internal visibility
- Terraform-Style Diff - Preview changes with
+/~/-indicators in dry-run mode
GitHub Rulesets¶
- Declarative Rulesets - Define GitHub Rulesets in YAML, apply with a single command
- Full API Coverage - All rule types: pull_request, status_checks, signatures, code_scanning, and more
- Bypass Actors - Configure which teams, users, or apps can bypass rules
- Pattern Conditions - Apply rules to branches/tags matching glob patterns
- Evaluate Mode - Test rules without enforcement
- Orphan Deletion - Automatically remove rulesets not in config
Platform & Operations¶
- Multi-Platform - Works with GitHub, Azure DevOps, and GitLab (including self-hosted)
- Auto-Merge PRs - Automatically merge PRs when checks pass, or force merge with admin privileges
- Direct Push Mode - Push directly to default branch without creating PRs
- Dry-Run Mode - Preview changes without making them
- Error Resilience - Continues processing if individual repos fail
- Automatic Retries - Retries transient network errors with exponential backoff
See Use Cases for real-world scenarios: platform engineering, CI/CD standardization, security governance, and more.
How It Works¶
flowchart TB
subgraph Input
YAML[/"YAML Config File<br/>files{} + repos[]"/]
end
subgraph Normalization
EXPAND[Expand git arrays] --> MERGE[Merge base + overlay content<br/>for each file]
MERGE --> ENV[Interpolate env vars]
end
subgraph Processing["For Each Repository"]
CLONE[Clone Repo] --> DETECT_BRANCH[Detect Default Branch]
DETECT_BRANCH --> MODE_CHECK{Direct Mode?}
MODE_CHECK -->|No| CLOSE_PR[Close Existing PR<br/>if exists]
CLOSE_PR --> BRANCH[Create Fresh Branch]
MODE_CHECK -->|Yes| STAY[Stay on Default Branch]
BRANCH --> WRITE[Write Config Files]
STAY --> WRITE
WRITE --> CHECK{Changes?}
CHECK -->|No| SKIP[Skip - No Changes]
CHECK -->|Yes| COMMIT[Commit & Push]
end
subgraph Platform["PR/Direct Push"]
COMMIT --> DIRECT_CHECK{Direct Mode?}
DIRECT_CHECK -->|Yes| DIRECT_PUSH[Push to Default Branch]
DIRECT_CHECK -->|No| PR_DETECT{Platform?}
PR_DETECT -->|GitHub| GH_PR[Create PR via gh CLI]
PR_DETECT -->|Azure DevOps| AZ_PR[Create PR via az CLI]
PR_DETECT -->|GitLab| GL_PR[Create MR via glab CLI]
GH_PR --> PR_CREATED[PR/MR Created]
AZ_PR --> PR_CREATED
GL_PR --> PR_CREATED
DIRECT_PUSH --> DONE[Done]
end
subgraph AutoMerge["Auto-Merge (default)"]
PR_CREATED --> MERGE_MODE{Merge Mode?}
MERGE_MODE -->|manual| OPEN[Leave PR Open]
MERGE_MODE -->|auto| AUTO[Enable Auto-Merge]
MERGE_MODE -->|force| FORCE[Bypass & Merge]
end
YAML --> EXPAND
ENV --> CLONE
For each repository in the config, the tool:
- Expands git URL arrays into individual entries
- For each file, merges base content with per-repo overlay
- Interpolates environment variables
- Cleans the temporary workspace
- Clones the repository
- Detects the default branch (main/master)
- PR modes: Closes any existing PR on the branch and creates a fresh branch | Direct mode: Stays on default branch
- Writes all config files (JSON, JSON5, YAML, or text based on filename extension)
- Checks for changes (skips if no changes)
- Commits and pushes changes
- PR modes: Creates a pull request and handles auto-merge | Direct mode: Done (changes are on default branch)
Settings Workflow (xfg settings)¶
flowchart TB
subgraph Input
YAML[/"YAML Config File<br/>settings{} + repos[]"/]
end
subgraph Normalization
PARSE[Parse config] --> MERGE[Merge base + per-repo<br/>settings overrides]
end
subgraph Processing["For Each GitHub Repository"]
FETCH[Fetch Current Settings<br/>via GitHub API] --> DIFF{Compare<br/>Current vs Desired}
DIFF -->|No Changes| SKIP[Skip - Already Matches]
DIFF -->|Changes Needed| PLAN[Generate Change Plan]
PLAN --> DRY{Dry Run?}
DRY -->|Yes| SHOW[Show Terraform-Style Diff<br/>+ additions ~ changes - removals]
DRY -->|No| APPLY[Apply via GitHub API]
end
subgraph Settings["What Gets Applied"]
APPLY --> REPO[Repository Settings<br/>merge options, security, features]
APPLY --> RULES[Rulesets<br/>branch/tag protection rules]
end
YAML --> PARSE
MERGE --> FETCH
For each GitHub repository:
- Fetches current repository settings and rulesets via GitHub API
- Merges global settings with per-repo overrides
- Computes diff between current state and desired state
- Dry-run mode: Shows planned changes with
+/~/-indicators - Apply mode: Updates repository settings and rulesets via GitHub API