ruff-sync 0.0.5.dev2__tar.gz → 0.1.0.dev0__tar.gz

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 (67) hide show
  1. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.github/workflows/ci.yaml +21 -4
  2. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.github/workflows/complexity.yaml +4 -5
  3. ruff_sync-0.1.0.dev0/.github/workflows/docs.yaml +32 -0
  4. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/AGENTS.md +15 -12
  5. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/PKG-INFO +40 -10
  6. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/README.md +39 -9
  7. ruff_sync-0.1.0.dev0/docs/ci-integration.md +59 -0
  8. ruff_sync-0.1.0.dev0/docs/configuration.md +64 -0
  9. ruff_sync-0.1.0.dev0/docs/gen_ref_pages.py +34 -0
  10. ruff_sync-0.1.0.dev0/docs/index.md +40 -0
  11. ruff_sync-0.1.0.dev0/docs/installation.md +47 -0
  12. ruff_sync-0.1.0.dev0/docs/usage.md +75 -0
  13. ruff_sync-0.1.0.dev0/mkdocs.yml +100 -0
  14. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/pyproject.toml +54 -6
  15. ruff_sync-0.1.0.dev0/src/ruff_sync/__init__.py +44 -0
  16. ruff_sync-0.1.0.dev0/src/ruff_sync/__main__.py +10 -0
  17. ruff_sync-0.0.5.dev2/ruff_sync.py → ruff_sync-0.1.0.dev0/src/ruff_sync/cli.py +192 -93
  18. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tasks.py +16 -6
  19. ruff_sync-0.1.0.dev0/tests/__init__.py +1 -0
  20. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_basic.py +34 -33
  21. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_config_validation.py +2 -1
  22. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_deprecation.py +4 -2
  23. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_e2e.py +3 -2
  24. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_git_fetch.py +7 -4
  25. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_project.py +2 -1
  26. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_url_handling.py +38 -3
  27. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/uv.lock +508 -1
  28. ruff_sync-0.0.5.dev2/tests/__init__.py +0 -0
  29. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.agents/TESTING.md +0 -0
  30. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.agents/workflows/add-test-case.md +0 -0
  31. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.git-blame-ignore-revs +0 -0
  32. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.github/dependabot.yml +0 -0
  33. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.gitignore +0 -0
  34. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/.pre-commit-config.yaml +0 -0
  35. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/LICENSE.md +0 -0
  36. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/codecov.yml +0 -0
  37. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/configs/fastapi/ruff.toml +0 -0
  38. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/configs/kitchen-sink/ruff.toml +0 -0
  39. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/ruff_sync_banner.png +0 -0
  40. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/scripts/check_dogfood.sh +0 -0
  41. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/scripts/gitclone_dogfood.sh +0 -0
  42. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/scripts/pull_dogfood.sh +0 -0
  43. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/conftest.py +0 -0
  44. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  45. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  46. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  47. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  48. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  49. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  50. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  51. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  52. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  53. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  54. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  55. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  56. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_final.toml +0 -0
  57. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  58. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  59. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/ruff.toml +0 -0
  60. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_check.py +0 -0
  61. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_corner_cases.py +0 -0
  62. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_scaffold.py +0 -0
  63. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_toml_operations.py +0 -0
  64. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/test_whitespace.py +0 -0
  65. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  66. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  67. {ruff_sync-0.0.5.dev2 → ruff_sync-0.1.0.dev0}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -66,8 +66,10 @@ jobs:
66
66
 
67
67
  pre-publish:
68
68
  name: Test package installation
69
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
70
69
  needs: [static-analysis, tests]
70
+ if: >-
71
+ (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
72
+ (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
71
73
  runs-on: ubuntu-latest
72
74
  steps:
73
75
  - name: Checkout
@@ -80,14 +82,29 @@ jobs:
80
82
  run: uv python install 3.10
81
83
 
82
84
  - name: Build package
83
- run: uv build
85
+ run: |
86
+ echo "Building ruff-sync package..."
87
+ uv build
84
88
 
85
- - name: Install and test packaged program
89
+ - name: Install packaged program
86
90
  run: |
91
+ echo "Installing built wheel..."
87
92
  uv tool install $(ls dist/*.whl)
88
- ruff-sync --version
93
+ echo "Version: $(ruff-sync --version)"
94
+
95
+ - name: Test package installation (Pull)
96
+ run: |
97
+ echo "Testing ruff-sync pull against Kilo59/ruff-sync..."
89
98
  ruff-sync https://github.com/Kilo59/ruff-sync
99
+
100
+ - name: Test package installation (Check)
101
+ run: |
102
+ echo "Testing ruff-sync check against Kilo59/ruff-sync..."
90
103
  ruff-sync check https://github.com/Kilo59/ruff-sync
104
+
105
+ - name: Verify semantic check failure
106
+ run: |
107
+ echo "Verifying that --semantic check fails for kitchen-sink config (expected failure)..."
91
108
  ! ruff-sync check --semantic https://github.com/Kilo59/ruff-sync --path configs/kitchen-sink
92
109
 
93
110
  publish:
@@ -28,11 +28,10 @@ jobs:
28
28
  DIFF=$(wily diff ruff_sync.py tasks.py tests/ --no-detail -r origin/${{ github.event.pull_request.base.ref }})
29
29
  echo "$DIFF"
30
30
 
31
- # Build multine output
32
- DIFF="${DIFF//'%'/'%25'}"
33
- DIFF="${DIFF//$'\n'/'%0A'}"
34
- DIFF="${DIFF//$'\r'/'%0D'}"
35
- echo "diff=$DIFF" >> "$GITHUB_OUTPUT"
31
+ # Build multiline output
32
+ echo "diff<<DIFF_EOF" >> "$GITHUB_OUTPUT"
33
+ echo "$DIFF" >> "$GITHUB_OUTPUT"
34
+ echo "DIFF_EOF" >> "$GITHUB_OUTPUT"
36
35
  - name: Add Wily PR Comment
37
36
  uses: marocchino/sticky-pull-request-comment@v2
38
37
  if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.number && steps.wily.outputs.diff != ''
@@ -0,0 +1,32 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ deploy:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+
25
+ - name: Set up Python
26
+ run: uv python install
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --all-extras --dev --group docs
30
+
31
+ - name: Build and deploy documentation
32
+ run: uv run mkdocs gh-deploy --force
@@ -5,7 +5,7 @@
5
5
  **ruff-sync** is a CLI tool that synchronizes [Ruff](https://docs.astral.sh/ruff/) linter configuration across multiple Python projects. It downloads an upstream `pyproject.toml`, extracts the `[tool.ruff]` section, and merges it into a local project's `pyproject.toml` while preserving formatting, comments, and whitespace.
6
6
 
7
7
  - **GitHub Repository**: [`Kilo59/ruff-sync`](https://github.com/Kilo59/ruff-sync)
8
- - The entire application lives in a single module: `ruff_sync.py`.
8
+ - The application uses a `src` layout in `src/ruff_sync/`.
9
9
  - Dev tasks are defined in `tasks.py` using [Invoke](https://www.pyinvoke.org/).
10
10
 
11
11
  ## GitHub Context
@@ -54,11 +54,14 @@ gh label list # See available labels
54
54
 
55
55
  ## Project Structure
56
56
 
57
- ```
57
+ ```text
58
58
  .agents/ # Agent-specific instructions (Deep Standards)
59
59
  TESTING.md # Mandatory testing patterns and rules
60
60
  workflows/ # Step-by-step guides for common tasks
61
- ruff_sync.py # The entire application — CLI, merging logic, HTTP
61
+ src/ruff_sync/ # The application source
62
+ __init__.py # Public API
63
+ cli.py # CLI, merging logic, HTTP
64
+ __main__.py # -m support
62
65
  tasks.py # Invoke tasks: lint, fmt, type-check, deps, new-case
63
66
  pyproject.toml # Project config, dependencies, ruff/mypy settings
64
67
  tests/
@@ -114,7 +117,7 @@ uv run mypy .
114
117
  ```
115
118
 
116
119
  - mypy is configured in strict mode with `python_version = "3.10"`.
117
- - It checks `ruff_sync.py`, `tests/`, and `tasks.py`.
120
+ - It checks `src/`, `tests/`, and `tasks.py`.
118
121
  - Tests have relaxed rules: `type-arg` and `no-untyped-def` are disabled for `tests.*`.
119
122
  - `tomlkit` returns complex union types — use `cast(Any, ...)` in tests when indexing parsed TOML documents to satisfy mypy without verbose type narrowing.
120
123
 
@@ -130,7 +133,7 @@ Or with coverage:
130
133
  uv run coverage run -m pytest -vv
131
134
  ```
132
135
 
133
- - Coverage is tracked for `ruff_sync.py` only.
136
+ - Coverage is tracked for `src/ruff_sync/` only.
134
137
  - Tests use `respx` to mock HTTP calls and `pyfakefs` for filesystem mocking.
135
138
  - `pytest-asyncio` is in **strict** mode — async tests need the `@pytest.mark.asyncio` decorator.
136
139
 
@@ -165,13 +168,13 @@ uv run coverage run -m pytest -vv
165
168
 
166
169
  Defined in `tasks.py`. **ALWAYS** run these through uv: `uv run invoke <task>`
167
170
 
168
- | Task | Alias | Description |
169
- | ------------ | ------------ | ----------------------------------- |
170
- | `lint` | | Lint with ruff (auto-fixes by default) |
171
- | `fmt` | | Format with ruff format |
172
- | `type-check` | `types` | Type-check with mypy |
173
- | `deps` | `sync` | Sync dependencies with uv |
174
- | `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures (See [Workflow](.agents/workflows/add-test-case.md)) |
171
+ | Task | Alias | Description |
172
+ | ------------ | --------------------- | -------------------------------------- |
173
+ | `lint` | | Lint with ruff (auto-fixes by default) |
174
+ | `fmt` | | Format with ruff format |
175
+ | `type-check` | `types` | Type-check with mypy |
176
+ | `deps` | `sync` | Sync dependencies with uv |
177
+ | `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures |
175
178
 
176
179
  ## CI
177
180
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruff-sync
3
- Version: 0.0.5.dev2
3
+ Version: 0.1.0.dev0
4
4
  Summary: Synchronize Ruff linter configuration across projects
5
5
  Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
6
6
  Project-URL: Documentation, https://github.com/Kilo59/ruff-sync#readme
@@ -47,7 +47,7 @@ Description-Content-Type: text/markdown
47
47
 
48
48
  ---
49
49
 
50
- ### Table of Contents
50
+ ## Table of Contents
51
51
 
52
52
  - [The Problem](#the-problem)
53
53
  - [How It Works](#how-it-works)
@@ -102,9 +102,9 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
102
102
  └─────────────────────────────┘
103
103
  ```
104
104
 
105
- 1. You point `ruff-sync` at the URL of your canonical `pyproject.toml`.
106
- 2. It downloads the file, extracts the `[tool.ruff]` section.
107
- 3. It **merges** the upstream config into your local `pyproject.toml` — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
105
+ 1. You point `ruff-sync` at the URL of your canonical configuration (repository, directory, or direct file).
106
+ 2. It downloads the file and extracts the configuration (from `[tool.ruff]` in `pyproject.toml` or the top-level in `ruff.toml`).
107
+ 3. It **merges** the upstream config into your local project — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
108
108
 
109
109
  No package registry. No publishing step. Just a URL.
110
110
 
@@ -133,8 +133,9 @@ pip install ruff-sync
133
133
  ### Usage
134
134
 
135
135
  ```console
136
- # Sync from a GitHub/GitLab repository (defaults to main/pyproject.toml)
136
+ # Sync from a GitHub/GitLab repository (root or specific directory)
137
137
  ruff-sync https://github.com/my-org/standards
138
+ ruff-sync https://github.com/my-org/standards/tree/main/configs/shared
138
139
 
139
140
  # Or a direct blob/file URL (auto-converts to raw)
140
141
  ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
@@ -162,7 +163,8 @@ Run `ruff-sync --help` for full details on all available options.
162
163
  ## Key Features
163
164
 
164
165
  - 🏗️ **Format-preserving merges** — Uses [tomlkit](https://github.com/sdispater/tomlkit) under the hood, so your comments, whitespace, and TOML structure are preserved. No reformatting surprises.
165
- - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
166
+ - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs, tree (directory) URLs, or blob (file) URLs to raw content URLs.
167
+ - 🔍 **Smart configuration discovery** — Point at a directory and `ruff-sync` will automatically find your config. It checks `pyproject.toml`, `ruff.toml`, and `.ruff.toml` (in that order).
166
168
  - 📥 **Git clone support** — If the URL starts with `git@` or uses the `ssh://`, `git://`, or `git+ssh://` schemes, `ruff-sync` will perform an efficient shallow clone (using `--filter=blob:none` and `--no-checkout`) to safely extract the configuration with minimal network traffic.
167
169
  - 🛡️ **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
168
170
  - 🌍 **Works with any host** — GitHub, GitLab, Bitbucket, private SSH servers, or any raw URL that serves a `pyproject.toml` or `ruff.toml`.
@@ -267,18 +269,46 @@ git commit -am "sync ruff config from upstream"
267
269
 
268
270
  While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
269
271
 
270
- For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
272
+ `ruff-sync` is flexible with URLs. You can point it at a repository root, a specific directory (tree), a direct file (blob), or even a raw content URL.
273
+
274
+ #### Kitchen Sink
275
+
276
+ An exhaustive configuration that explicitly enables and documents almost all available Ruff rules. Great for establishing a strict baseline.
271
277
 
272
278
  ```console
279
+ # Directory URL (recommended)
280
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/kitchen-sink
281
+
282
+ # Direct file URL (blob)
273
283
  ruff-sync https://github.com/Kilo59/ruff-sync/blob/main/configs/kitchen-sink/ruff.toml
284
+
285
+ # Raw content URL
286
+ ruff-sync https://raw.githubusercontent.com/Kilo59/ruff-sync/main/configs/kitchen-sink/ruff.toml
287
+
288
+ # Git SSH URL (clones the repo)
289
+ ruff-sync git@github.com:Kilo59/ruff-sync.git --path configs/kitchen-sink
290
+ ```
291
+
292
+ #### FastAPI & Async
293
+
294
+ Tailored for modern web applications. Includes rules for `asyncio`, security (`flake8-bandit`), and Pydantic-friendly naming conventions.
295
+
296
+ ```console
297
+ # Repository Root (if the config is at the root)
298
+ ruff-sync https://github.com/my-org/fastapi-standards
299
+
300
+ # Directory URL
301
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/fastapi
274
302
  ```
275
303
 
276
- Or configure it using `pyproject.toml` so it's always the default for your local project:
304
+ #### Default Syncing
305
+
306
+ Set your preferred standard as the default in your `pyproject.toml`:
277
307
 
278
308
  ```toml
279
309
  [tool.ruff-sync]
280
310
  upstream = "https://github.com/Kilo59/ruff-sync"
281
- path = "configs/kitchen-sink"
311
+ path = "configs/fastapi"
282
312
  ```
283
313
 
284
314
  ## Bootstrapping a New Project
@@ -16,7 +16,7 @@
16
16
 
17
17
  ---
18
18
 
19
- ### Table of Contents
19
+ ## Table of Contents
20
20
 
21
21
  - [The Problem](#the-problem)
22
22
  - [How It Works](#how-it-works)
@@ -71,9 +71,9 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
71
71
  └─────────────────────────────┘
72
72
  ```
73
73
 
74
- 1. You point `ruff-sync` at the URL of your canonical `pyproject.toml`.
75
- 2. It downloads the file, extracts the `[tool.ruff]` section.
76
- 3. It **merges** the upstream config into your local `pyproject.toml` — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
74
+ 1. You point `ruff-sync` at the URL of your canonical configuration (repository, directory, or direct file).
75
+ 2. It downloads the file and extracts the configuration (from `[tool.ruff]` in `pyproject.toml` or the top-level in `ruff.toml`).
76
+ 3. It **merges** the upstream config into your local project — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
77
77
 
78
78
  No package registry. No publishing step. Just a URL.
79
79
 
@@ -102,8 +102,9 @@ pip install ruff-sync
102
102
  ### Usage
103
103
 
104
104
  ```console
105
- # Sync from a GitHub/GitLab repository (defaults to main/pyproject.toml)
105
+ # Sync from a GitHub/GitLab repository (root or specific directory)
106
106
  ruff-sync https://github.com/my-org/standards
107
+ ruff-sync https://github.com/my-org/standards/tree/main/configs/shared
107
108
 
108
109
  # Or a direct blob/file URL (auto-converts to raw)
109
110
  ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
@@ -131,7 +132,8 @@ Run `ruff-sync --help` for full details on all available options.
131
132
  ## Key Features
132
133
 
133
134
  - 🏗️ **Format-preserving merges** — Uses [tomlkit](https://github.com/sdispater/tomlkit) under the hood, so your comments, whitespace, and TOML structure are preserved. No reformatting surprises.
134
- - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
135
+ - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs, tree (directory) URLs, or blob (file) URLs to raw content URLs.
136
+ - 🔍 **Smart configuration discovery** — Point at a directory and `ruff-sync` will automatically find your config. It checks `pyproject.toml`, `ruff.toml`, and `.ruff.toml` (in that order).
135
137
  - 📥 **Git clone support** — If the URL starts with `git@` or uses the `ssh://`, `git://`, or `git+ssh://` schemes, `ruff-sync` will perform an efficient shallow clone (using `--filter=blob:none` and `--no-checkout`) to safely extract the configuration with minimal network traffic.
136
138
  - 🛡️ **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
137
139
  - 🌍 **Works with any host** — GitHub, GitLab, Bitbucket, private SSH servers, or any raw URL that serves a `pyproject.toml` or `ruff.toml`.
@@ -236,18 +238,46 @@ git commit -am "sync ruff config from upstream"
236
238
 
237
239
  While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
238
240
 
239
- For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
241
+ `ruff-sync` is flexible with URLs. You can point it at a repository root, a specific directory (tree), a direct file (blob), or even a raw content URL.
242
+
243
+ #### Kitchen Sink
244
+
245
+ An exhaustive configuration that explicitly enables and documents almost all available Ruff rules. Great for establishing a strict baseline.
240
246
 
241
247
  ```console
248
+ # Directory URL (recommended)
249
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/kitchen-sink
250
+
251
+ # Direct file URL (blob)
242
252
  ruff-sync https://github.com/Kilo59/ruff-sync/blob/main/configs/kitchen-sink/ruff.toml
253
+
254
+ # Raw content URL
255
+ ruff-sync https://raw.githubusercontent.com/Kilo59/ruff-sync/main/configs/kitchen-sink/ruff.toml
256
+
257
+ # Git SSH URL (clones the repo)
258
+ ruff-sync git@github.com:Kilo59/ruff-sync.git --path configs/kitchen-sink
259
+ ```
260
+
261
+ #### FastAPI & Async
262
+
263
+ Tailored for modern web applications. Includes rules for `asyncio`, security (`flake8-bandit`), and Pydantic-friendly naming conventions.
264
+
265
+ ```console
266
+ # Repository Root (if the config is at the root)
267
+ ruff-sync https://github.com/my-org/fastapi-standards
268
+
269
+ # Directory URL
270
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/fastapi
243
271
  ```
244
272
 
245
- Or configure it using `pyproject.toml` so it's always the default for your local project:
273
+ #### Default Syncing
274
+
275
+ Set your preferred standard as the default in your `pyproject.toml`:
246
276
 
247
277
  ```toml
248
278
  [tool.ruff-sync]
249
279
  upstream = "https://github.com/Kilo59/ruff-sync"
250
- path = "configs/kitchen-sink"
280
+ path = "configs/fastapi"
251
281
  ```
252
282
 
253
283
  ## Bootstrapping a New Project
@@ -0,0 +1,59 @@
1
+ # CI Integration
2
+
3
+ `ruff-sync` is designed to be run in CI pipelines to ensure that all repositories in an organization stay in sync with the central standards.
4
+
5
+ ## Usage in CI
6
+
7
+ The best way to use `ruff-sync` in CI is with the `check` command. If the configuration has drifted, `ruff-sync check` will exit with a non-zero code, failing the build.
8
+
9
+ ### GitHub Actions
10
+
11
+ We recommend using `uv` to run `ruff-sync` in GitHub Actions.
12
+
13
+ ```yaml
14
+ name: "Standards Check"
15
+
16
+ on:
17
+ push:
18
+ branches: [main]
19
+ pull_request:
20
+ branches: [main]
21
+
22
+ jobs:
23
+ ruff-sync:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v5
30
+
31
+ - name: Check Ruff Sync
32
+ run: uvx ruff-sync check --semantic
33
+ ```
34
+
35
+ ### GitLab CI
36
+
37
+ ```yaml
38
+ ruff-sync-check:
39
+ image: python:3.12
40
+ script:
41
+ - pip install ruff-sync
42
+ - ruff-sync check --semantic
43
+ ```
44
+
45
+ ## Best Practices
46
+
47
+ ### Use `--semantic`
48
+
49
+ By default, `ruff-sync check` does a strict comparison of the TOML files. This means that if you manually reformat `pyproject.toml` or add a comment, the check will fail even if the actual Ruff rules are the same.
50
+
51
+ In CI, you usually only care about the functional configuration. Using `--semantic` ensures that minor formatting changes don't break your builds.
52
+
53
+ ### Use a Dedicated Workflow
54
+
55
+ Running `ruff-sync` as a separate job in your linting workflow makes it easy to identify when a failure is due to configuration drift rather than a code quality issue.
56
+
57
+ ### Automated PRs
58
+
59
+ Instead of just checking, some organizations prefer to have a bot automatically open a PR when the upstream configuration changes. You can achieve this by running `ruff-sync pull` and using an action like `create-pull-request`.
@@ -0,0 +1,64 @@
1
+ # Configuration
2
+
3
+ `ruff-sync` can be configured in your `pyproject.toml` file under the `[tool.ruff-sync]` section.
4
+
5
+ ## Reference
6
+
7
+ | Key | Type | Default | Description |
8
+ | :--- | :--- | :--- | :--- |
9
+ | `upstream` | `str` | *Required* | The URL of the upstream `pyproject.toml` or `ruff.toml`. |
10
+ | `to` | `str` | `"."` | The local directory or file where configuration should be merged. |
11
+ | `exclude` | `list[str]` | `["lint.per-file-ignores"]` | A list of configuration keys to preserve locally. |
12
+ | `branch` | `str` | `"main"` | The default branch to use when resolving repository URLs. |
13
+ | `path` | `str` | `""` | The directory path within the repository where the config is located. |
14
+ | `semantic` | `bool` | `false` | Whether `check` should default to semantic matching. |
15
+ | `diff` | `bool` | `true` | Whether `check` should show a diff by default. |
16
+
17
+ ## Exclude Patterns
18
+
19
+ The `exclude` setting is powerful. It allows you to adopt most of an upstream configuration while keeping some parts specific to your repository.
20
+
21
+ Exclusions use dotted paths to target specific sections or keys.
22
+
23
+ ### Examples
24
+
25
+ #### Preserve per-file ignores
26
+
27
+ This is the default. It ensures that any custom ignores you've set for specific files in your repo aren't overwritten by the upstream.
28
+
29
+ ```toml
30
+ [tool.ruff-sync]
31
+ exclude = ["lint.per-file-ignores"]
32
+ ```
33
+
34
+ #### Manage specific plugins locally
35
+
36
+ If you want to use the upstream rules but manage `isort` settings yourself:
37
+
38
+ ```toml
39
+ [tool.ruff-sync]
40
+ exclude = ["lint.isort"]
41
+ ```
42
+
43
+ #### Keep a specific rule toggle
44
+
45
+ If you want to manage whether a specific rule is ignored or selected:
46
+
47
+ ```toml
48
+ [tool.ruff-sync]
49
+ exclude = ["lint.ignore", "lint.select"]
50
+ ```
51
+
52
+ #### Preserve target version
53
+
54
+ If your projects are on different Python versions but share linting rules:
55
+
56
+ ```toml
57
+ [tool.ruff-sync]
58
+ exclude = ["target-version"]
59
+ ```
60
+
61
+ ## Deprecation Notes
62
+
63
+ - The key `source` in `[tool.ruff-sync]` is deprecated and will be removed in a future version. Use `to` instead.
64
+ - The `--source` CLI flag is also deprecated in favor of `--to`.
@@ -0,0 +1,34 @@
1
+ """Generate the code reference pages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pathlib
6
+
7
+ import mkdocs_gen_files
8
+
9
+ nav = mkdocs_gen_files.Nav() # type: ignore[attr-defined,no-untyped-call]
10
+
11
+ for path in sorted(pathlib.Path("src").rglob("*.py")):
12
+ module_path = path.relative_to("src").with_suffix("")
13
+ doc_path = path.relative_to("src").with_suffix(".md")
14
+ full_doc_path = pathlib.Path("reference", doc_path)
15
+
16
+ parts = list(module_path.parts)
17
+
18
+ if parts[-1] == "__init__":
19
+ parts.pop()
20
+ doc_path = doc_path.with_name("index.md")
21
+ full_doc_path = full_doc_path.with_name("index.md")
22
+ elif parts[-1] == "__main__":
23
+ continue
24
+
25
+ nav[tuple(parts)] = doc_path.as_posix()
26
+
27
+ with mkdocs_gen_files.open(full_doc_path, "w") as fd:
28
+ identifier = ".".join(parts)
29
+ print(f"::: {identifier}", file=fd)
30
+
31
+ mkdocs_gen_files.set_edit_path(full_doc_path, path)
32
+
33
+ with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
34
+ nav_file.writelines(nav.build_literate_nav())
@@ -0,0 +1,40 @@
1
+ # ruff-sync
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/ruff-sync.svg)](https://pypi.org/project/ruff-sync/)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/ruff-sync.svg)](https://pypi.org/project/ruff-sync/)
5
+ [![License](https://img.shields.io/github/license/Kilo59/ruff-sync.svg)](https://github.com/Kilo59/ruff-sync/blob/main/LICENSE)
6
+
7
+ **ruff-sync** is a lightweight CLI tool to synchronize [Ruff](https://docs.astral.sh/ruff/) linter configuration across multiple Python projects.
8
+
9
+ ## The Problem
10
+
11
+ Maintaining a consistent Ruff configuration across 10, 50, or 100 repositories is painful. When you decide to adopt a new rule or change a setting, you have to manually update every single `pyproject.toml`.
12
+
13
+ Internal "base" configurations or shared presets often fall out of sync, or require complex inheritance setups that are hard to debug.
14
+
15
+ ## The Solution
16
+
17
+ `ruff-sync` lets you define a "source of truth" (a URL to a `pyproject.toml` or `ruff.toml`) and pull the `[tool.ruff]` section into your local projects with a single command.
18
+
19
+ - **Formatting Preserved**: Unlike standard TOML parsers, `ruff-sync` uses `tomlkit` to preserve your comments, indentation, and whitespace.
20
+ - **Smart Merging**: Intelligently merges nested tables (like `lint.per-file-ignores`) while allowing you to exclude specific keys you want to manage locally.
21
+ - **CI Friendly**: Use `ruff-sync check` in your CI pipeline to ensure projects haven't drifted from the upstream standard.
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Configure your project
26
+
27
+ Add the upstream URL to your `pyproject.toml`:
28
+
29
+ ```toml
30
+ [tool.ruff-sync]
31
+ upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
32
+ ```
33
+
34
+ ### 2. Pull the configuration
35
+
36
+ ```bash
37
+ uv run ruff-sync pull
38
+ ```
39
+
40
+ This will download the upstream file, extract the `[tool.ruff]` section, and merge it into your local file.
@@ -0,0 +1,47 @@
1
+ # Installation
2
+
3
+ `ruff-sync` is a Python CLI tool. We recommend using `uv` for the best experience, but it works with standard Python package managers as well.
4
+
5
+ ## Recommended: Using `uv`
6
+
7
+ If you are using [uv](https://docs.astral.sh/uv/), you can run `ruff-sync` without installing it globally:
8
+
9
+ ```bash
10
+ uvx ruff-sync pull
11
+ ```
12
+
13
+ Or add it to your project's development dependencies:
14
+
15
+ ```bash
16
+ uv add --dev ruff-sync
17
+ uv run ruff-sync pull
18
+ ```
19
+
20
+ ## Other Methods
21
+
22
+ === "pipx"
23
+
24
+ [pipx](https://github.com/pypa/pipx) is the recommended way to install Python CLIs globally in isolated environments.
25
+
26
+ ```bash
27
+ pipx install ruff-sync
28
+ ```
29
+
30
+ === "pip"
31
+
32
+ You can install `ruff-sync` from PyPI using `pip`:
33
+
34
+ ```bash
35
+ pip install ruff-sync
36
+ ```
37
+
38
+ > [!WARNING]
39
+ > We recommend using a virtual environment or `pipx` to avoid dependency conflicts with other global packages.
40
+
41
+ ## Verifying Installation
42
+
43
+ Check that `ruff-sync` is installed correctly by running:
44
+
45
+ ```bash
46
+ ruff-sync --version
47
+ ```