mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
Publish Ruff crates to crates.io (#26271)
## Summary Publish Ruff's production Rust crates to crates.io as part of the existing release workflow. This follows uv's setup: workspace dependencies carry registry-compatible version pins, internal crates receive generated READMEs and an explicit versioning policy, CI dry-runs the full workspace publish, and release jobs publish missing crate versions idempotently through crates.io trusted publishing. The publishing set contains 32 packages: Ruff's CLI, WASM package, server, linter, formatter, parser, and the internal Ruff component crates required by them. It also includes `ty_module_resolver`, `ty_site_packages`, and `ty_static`, because those three crates are part of Ruff's normal dependency closure. The publishing set deliberately excludes: - Test-only crates: `mdtest`, `ruff_mdtest`, `ruff_python_ast_integration_tests`, and `ruff_python_trivia_integration_tests`. - Benchmark and development crates: `ruff_benchmark` and `ruff_dev`. - All remaining `ty_*` crates. These packages retain `publish = false`, so bootstrap, version bumping, README generation, CI dry-runs, and release publishing skip them through Cargo metadata instead of a separate hard-coded allowlist. The crates.io job is intentionally independent of release announcement, so a crates.io outage cannot block the existing binary, PyPI, npm, or GitHub release outputs. A bootstrap script reserves new crate names, configures the release workflow as each crate's trusted publisher, enables trusted-publishing-only releases, and records configured crates for idempotent reruns. The full workspace publish dry-run packages and verifies all 32 selected crates. Closes https://github.com/astral-sh/ruff/issues/43.
This commit is contained in:
1 parent
a4bfeb12f9
commit
dff016b7a4
81 files changed
+1705
-124
No files matched your search
@@ -301,6 +301,22 @@ jobs:
|
||||
- name: "Clippy (wasm)"
|
||||
run: cargo clippy -p ruff_wasm -p ty_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings
|
||||
|
||||
cargo-publish-dry-run:
|
||||
name: "cargo publish dry-run"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
|
||||
needs: determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: "cargo publish dry-run"
|
||||
run: cargo publish --workspace --dry-run
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Publish a release to crates.io.
|
||||
#
|
||||
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
|
||||
# within `cargo-dist`.
|
||||
name: "Publish to crates.io"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
plan:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
crates-publish-ruff:
|
||||
name: Upload Ruff to crates.io
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
|
||||
id: auth
|
||||
- name: Install nightly toolchain
|
||||
# Nightly is required for the unstable `-Zpublish-timeout` flag, which lets us
|
||||
# raise the per-crate wait above the 60s default. crates.io indexing has been
|
||||
# known to lag long enough to exceed that during workspace publishes.
|
||||
run: rustup toolchain install nightly-2026-06-12 --profile minimal --no-self-update
|
||||
- name: Publish workspace crates
|
||||
# Note `--no-verify` is safe because we do a publish dry-run elsewhere in CI
|
||||
run: python3 scripts/publish-crates.py --cargo cargo +nightly-2026-06-12 --no-verify -- -Zpublish-timeout --config 'publish.timeout=600'
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
|
||||
@@ -273,6 +273,23 @@ jobs:
|
||||
"id-token": "write"
|
||||
"packages": "write"
|
||||
|
||||
custom-publish-crates:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
- release-gate
|
||||
- custom-publish-pypi # DIRTY: see #16989
|
||||
- custom-publish-wasm # DIRTY: see #16989
|
||||
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
|
||||
uses: ./.github/workflows/publish-crates.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
# publish jobs get escalated permissions
|
||||
permissions:
|
||||
"contents": "read"
|
||||
"id-token": "write"
|
||||
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
announce:
|
||||
needs:
|
||||
@@ -284,6 +301,8 @@ jobs:
|
||||
# use "always() && ..." to allow us to wait for all publish jobs while
|
||||
# still allowing individual publish jobs to skip themselves (for prereleases).
|
||||
# "host" however must run to completion, no skipping allowed!
|
||||
# `custom-publish-crates` is intentionally not a dependency because a crates.io outage should
|
||||
# not block the rest of the release.
|
||||
if: ${{ always() && needs.host.result == 'success' && needs.release-gate.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
|
||||
runs-on: "depot-ubuntu-latest-4"
|
||||
permissions:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# GENERATED-BY scripts/setup-crates-io-publish.py
|
||||
@@ -171,6 +171,34 @@ At the time of writing, the repository includes the following crates:
|
||||
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module. Powers the
|
||||
[Ruff Playground](https://play.ruff.rs/).
|
||||
|
||||
#### Adding a new crate
|
||||
|
||||
When adding a workspace crate under `crates/`, first decide whether it should be published to
|
||||
crates.io as part of Ruff's releases:
|
||||
|
||||
- Test, benchmark, development, and other non-release crates must set `publish = false` in their
|
||||
`Cargo.toml`.
|
||||
- Publishable crates should inherit the workspace package metadata and follow Ruff's [crate
|
||||
versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning). If the crate is
|
||||
listed under `[workspace.dependencies]`, specify both its path and matching version.
|
||||
|
||||
For a publishable crate, generate its README and verify that the workspace can still be packaged:
|
||||
|
||||
```shell
|
||||
uv run --script scripts/generate-crate-readmes.py
|
||||
cargo publish --workspace --dry-run
|
||||
```
|
||||
|
||||
Before merging a publishable crate, ask a crates.io owner to bootstrap it by running:
|
||||
|
||||
```shell
|
||||
CARGO_REGISTRY_TOKEN=<token> uv run --no-config --script scripts/setup-crates-io-publish.py
|
||||
```
|
||||
|
||||
The bootstrap script reserves the crate name, configures the release workflow as its trusted
|
||||
publisher, requires trusted publishing for future versions, and adds the crate to `.known-crates`.
|
||||
Commit the generated README and `.known-crates` update with the new crate.
|
||||
|
||||
### Example: Adding a new lint rule
|
||||
|
||||
At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
|
||||
Generated
+28
-28
@@ -3029,7 +3029,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_annotate_snippets"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anstream 1.0.0",
|
||||
"anstyle",
|
||||
@@ -3069,7 +3069,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cache"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"glob",
|
||||
@@ -3082,7 +3082,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_db"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"arc-swap",
|
||||
@@ -3173,7 +3173,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_diagnostics"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"is-macro",
|
||||
@@ -3183,7 +3183,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_formatter"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"drop_bomb",
|
||||
"ruff_cache",
|
||||
@@ -3199,7 +3199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_graph"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -3220,7 +3220,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_index"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"ruff_macros",
|
||||
@@ -3292,7 +3292,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools 0.14.0",
|
||||
@@ -3305,7 +3305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_markdown"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"insta",
|
||||
"regex",
|
||||
@@ -3335,14 +3335,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_memory_usage"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_notebook"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -3360,14 +3360,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_options_metadata"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"arrayvec",
|
||||
@@ -3403,7 +3403,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_codegen"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
@@ -3415,7 +3415,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_formatter"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -3448,7 +3448,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_importer"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"insta",
|
||||
@@ -3463,7 +3463,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
@@ -3474,7 +3474,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"icu_properties",
|
||||
@@ -3484,7 +3484,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.13.0",
|
||||
@@ -3513,7 +3513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"insta",
|
||||
@@ -3534,7 +3534,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
"unicode-ident",
|
||||
@@ -3542,7 +3542,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_python_trivia"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"ruff_source_file",
|
||||
@@ -3562,7 +3562,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_server"
|
||||
version = "0.2.2"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"crossbeam",
|
||||
@@ -3604,7 +3604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"memchr",
|
||||
@@ -3614,7 +3614,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"get-size2",
|
||||
"schemars",
|
||||
@@ -3653,7 +3653,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_workspace"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"colored 3.1.1",
|
||||
@@ -4633,7 +4633,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
@@ -4828,7 +4828,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ty_site_packages"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"colored 3.1.1",
|
||||
|
||||
+31
-31
@@ -13,48 +13,48 @@ authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
ruff = { path = "crates/ruff" }
|
||||
ruff_annotate_snippets = { path = "crates/ruff_annotate_snippets" }
|
||||
ruff_cache = { path = "crates/ruff_cache" }
|
||||
ruff_db = { path = "crates/ruff_db", default-features = false }
|
||||
ruff_diagnostics = { path = "crates/ruff_diagnostics" }
|
||||
ruff_formatter = { path = "crates/ruff_formatter" }
|
||||
ruff_graph = { path = "crates/ruff_graph" }
|
||||
ruff_index = { path = "crates/ruff_index" }
|
||||
ruff_linter = { path = "crates/ruff_linter" }
|
||||
ruff_macros = { path = "crates/ruff_macros" }
|
||||
ruff_markdown = { path = "crates/ruff_markdown" }
|
||||
ruff_memory_usage = { path = "crates/ruff_memory_usage" }
|
||||
ruff_notebook = { path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "crates/ruff_python_codegen" }
|
||||
ruff_python_formatter = { path = "crates/ruff_python_formatter" }
|
||||
ruff_python_importer = { path = "crates/ruff_python_importer" }
|
||||
ruff_python_index = { path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { path = "crates/ruff_python_parser" }
|
||||
ruff_python_semantic = { path = "crates/ruff_python_semantic" }
|
||||
ruff_python_stdlib = { path = "crates/ruff_python_stdlib" }
|
||||
ruff_python_trivia = { path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { path = "crates/ruff_server" }
|
||||
ruff_source_file = { path = "crates/ruff_source_file" }
|
||||
ruff = { version = "0.15.18", path = "crates/ruff" }
|
||||
ruff_annotate_snippets = { version = "0.0.1", path = "crates/ruff_annotate_snippets" }
|
||||
ruff_cache = { version = "0.0.1", path = "crates/ruff_cache" }
|
||||
ruff_db = { version = "0.0.1", path = "crates/ruff_db", default-features = false }
|
||||
ruff_diagnostics = { version = "0.0.1", path = "crates/ruff_diagnostics" }
|
||||
ruff_formatter = { version = "0.0.1", path = "crates/ruff_formatter" }
|
||||
ruff_graph = { version = "0.0.1", path = "crates/ruff_graph" }
|
||||
ruff_index = { version = "0.0.1", path = "crates/ruff_index" }
|
||||
ruff_linter = { version = "0.15.18", path = "crates/ruff_linter" }
|
||||
ruff_macros = { version = "0.0.1", path = "crates/ruff_macros" }
|
||||
ruff_markdown = { version = "0.0.1", path = "crates/ruff_markdown" }
|
||||
ruff_memory_usage = { version = "0.0.1", path = "crates/ruff_memory_usage" }
|
||||
ruff_notebook = { version = "0.0.1", path = "crates/ruff_notebook" }
|
||||
ruff_options_metadata = { version = "0.0.1", path = "crates/ruff_options_metadata" }
|
||||
ruff_python_ast = { version = "0.0.1", path = "crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { version = "0.0.1", path = "crates/ruff_python_codegen" }
|
||||
ruff_python_formatter = { version = "0.0.1", path = "crates/ruff_python_formatter" }
|
||||
ruff_python_importer = { version = "0.0.1", path = "crates/ruff_python_importer" }
|
||||
ruff_python_index = { version = "0.0.1", path = "crates/ruff_python_index" }
|
||||
ruff_python_literal = { version = "0.0.1", path = "crates/ruff_python_literal" }
|
||||
ruff_python_parser = { version = "0.0.1", path = "crates/ruff_python_parser" }
|
||||
ruff_python_semantic = { version = "0.0.1", path = "crates/ruff_python_semantic" }
|
||||
ruff_python_stdlib = { version = "0.0.1", path = "crates/ruff_python_stdlib" }
|
||||
ruff_python_trivia = { version = "0.0.1", path = "crates/ruff_python_trivia" }
|
||||
ruff_server = { version = "0.0.1", path = "crates/ruff_server" }
|
||||
ruff_source_file = { version = "0.0.1", path = "crates/ruff_source_file" }
|
||||
ruff_mdtest = { path = "crates/ruff_mdtest" }
|
||||
ruff_text_size = { path = "crates/ruff_text_size" }
|
||||
ruff_workspace = { path = "crates/ruff_workspace" }
|
||||
ruff_text_size = { version = "0.0.1", path = "crates/ruff_text_size" }
|
||||
ruff_workspace = { version = "0.0.1", path = "crates/ruff_workspace" }
|
||||
|
||||
ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_completion_bench = { path = "crates/ty_completion_bench" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_module_resolver = { path = "crates/ty_module_resolver" }
|
||||
ty_module_resolver = { version = "0.0.1", path = "crates/ty_module_resolver" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_python_core = { path = "crates/ty_python_core" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_site_packages = { path = "crates/ty_site_packages" }
|
||||
ty_static = { path = "crates/ty_static" }
|
||||
ty_site_packages = { version = "0.0.1", path = "crates/ty_site_packages" }
|
||||
ty_static = { version = "0.0.1", path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
ty_vendored = { path = "crates/ty_vendored" }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.15.18"
|
||||
publish = true
|
||||
description = "An extremely fast Python linter and code formatter"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
@@ -9,7 +9,7 @@ homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
readme = "../../README.md"
|
||||
readme = "README.md"
|
||||
default-run = "ruff"
|
||||
|
||||
[package.metadata.cargo-shear]
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# Ruff
|
||||
|
||||
Ruff is an extremely fast Python linter and code formatter.
|
||||
|
||||
See the [documentation](https://docs.astral.sh/ruff/) or
|
||||
[repository](https://github.com/astral-sh/ruff) for more information.
|
||||
|
||||
This crate is the entry point to the Ruff command-line interface. The Rust API exposed here is not
|
||||
considered public interface.
|
||||
|
||||
This is version 0.15.18. The source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff).
|
||||
|
||||
The following Ruff workspace members are also available:
|
||||
|
||||
- [ruff_annotate_snippets](https://crates.io/crates/ruff_annotate_snippets)
|
||||
- [ruff_cache](https://crates.io/crates/ruff_cache)
|
||||
- [ruff_db](https://crates.io/crates/ruff_db)
|
||||
- [ruff_diagnostics](https://crates.io/crates/ruff_diagnostics)
|
||||
- [ruff_formatter](https://crates.io/crates/ruff_formatter)
|
||||
- [ruff_graph](https://crates.io/crates/ruff_graph)
|
||||
- [ruff_index](https://crates.io/crates/ruff_index)
|
||||
- [ruff_linter](https://crates.io/crates/ruff_linter)
|
||||
- [ruff_macros](https://crates.io/crates/ruff_macros)
|
||||
- [ruff_markdown](https://crates.io/crates/ruff_markdown)
|
||||
- [ruff_memory_usage](https://crates.io/crates/ruff_memory_usage)
|
||||
- [ruff_notebook](https://crates.io/crates/ruff_notebook)
|
||||
- [ruff_options_metadata](https://crates.io/crates/ruff_options_metadata)
|
||||
- [ruff_python_ast](https://crates.io/crates/ruff_python_ast)
|
||||
- [ruff_python_codegen](https://crates.io/crates/ruff_python_codegen)
|
||||
- [ruff_python_formatter](https://crates.io/crates/ruff_python_formatter)
|
||||
- [ruff_python_importer](https://crates.io/crates/ruff_python_importer)
|
||||
- [ruff_python_index](https://crates.io/crates/ruff_python_index)
|
||||
- [ruff_python_literal](https://crates.io/crates/ruff_python_literal)
|
||||
- [ruff_python_parser](https://crates.io/crates/ruff_python_parser)
|
||||
- [ruff_python_semantic](https://crates.io/crates/ruff_python_semantic)
|
||||
- [ruff_python_stdlib](https://crates.io/crates/ruff_python_stdlib)
|
||||
- [ruff_python_trivia](https://crates.io/crates/ruff_python_trivia)
|
||||
- [ruff_server](https://crates.io/crates/ruff_server)
|
||||
- [ruff_source_file](https://crates.io/crates/ruff_source_file)
|
||||
- [ruff_text_size](https://crates.io/crates/ruff_text_size)
|
||||
- [ruff_wasm](https://crates.io/crates/ruff_wasm)
|
||||
- [ruff_workspace](https://crates.io/crates/ruff_workspace)
|
||||
- [ty_module_resolver](https://crates.io/crates/ty_module_resolver)
|
||||
- [ty_site_packages](https://crates.io/crates/ty_site_packages)
|
||||
- [ty_static](https://crates.io/crates/ty_static)
|
||||
|
||||
Ruff's workspace members are considered internal and will have frequent breaking changes.
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_annotate_snippets"
|
||||
version = "0.1.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
@@ -16,7 +16,7 @@ memchr = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_annotate_snippets = { workspace = true, features = ["testing-colors"] }
|
||||
ruff_annotate_snippets = { path = ".", features = ["testing-colors"] }
|
||||
|
||||
anstream = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_cache"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_cache
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_cache).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_db"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_db
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_db).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_diagnostics"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_diagnostics
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_diagnostics).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_formatter"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_formatter
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_formatter).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_graph"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_graph
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_graph).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_index"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_index
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_index).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.15.18"
|
||||
publish = false
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_linter
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.15.18) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_linter).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_macros
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_macros).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_markdown"
|
||||
version = "0.0.0"
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_markdown
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_markdown).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_memory_usage"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_memory_usage
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_memory_usage).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_notebook"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_notebook
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_notebook).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_options_metadata"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_options_metadata
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_options_metadata).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_ast
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_ast).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_ast_integration_tests"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_codegen"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_codegen
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_codegen).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_formatter"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_importer"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_importer
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_importer).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_index"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_index
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_index).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,8 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
description = "Common literal handling utilities mostly useful for unparse and repr."
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>", "RustPython Team"]
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_literal
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_literal).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>", "RustPython Team"]
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_semantic
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_semantic).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_stdlib
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_stdlib).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_trivia"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_python_trivia
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_python_trivia).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_python_trivia_integration_tests"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_server"
|
||||
version = "0.2.2"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_source_file
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_source_file).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_text_size"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_text_size
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_text_size).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.15.18"
|
||||
publish = false
|
||||
description = "WebAssembly bindings for Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
@@ -9,7 +9,6 @@ homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
description = "WebAssembly bindings for Ruff"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ruff_workspace"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ruff_workspace
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ruff_workspace).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ty"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
# required for correct pypi metadata
|
||||
homepage = "https://github.com/astral-sh/ty/"
|
||||
documentation = "https://docs.astral.sh/ty/"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ty_combine"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
@@ -31,7 +31,8 @@ tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing", "os"] }
|
||||
ty_vendored = { workspace = true }
|
||||
# Keep this path-only so Cargo strips the unpublished ty dev-dependency when packaging.
|
||||
ty_vendored = { path = "../ty_vendored" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ty_module_resolver
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ty_module_resolver).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ty_project"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ty_site_packages"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ty_site_packages
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ty_site_packages).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "ty_static"
|
||||
version = "0.0.1"
|
||||
description = "This is an internal component crate of Ruff"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- This file is generated. DO NOT EDIT -->
|
||||
|
||||
# ty_static
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version (0.0.1) is a component of [Ruff 0.15.18](https://crates.io/crates/ruff/0.15.18). The
|
||||
source can be found [here](https://github.com/astral-sh/ruff/blob/0.15.18/crates/ty_static).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
+2
-2
@@ -60,11 +60,11 @@ build-local-artifacts = false
|
||||
# Local artifacts jobs to run in CI
|
||||
local-artifacts-jobs = ["./build-binaries", "./build-docker", "./build-wasm"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm", "./publish-crates"]
|
||||
# Post-announce jobs to run in CI
|
||||
post-announce-jobs = ["./notify-dependents", "./publish-docs", "./publish-playground", "./publish-versions", "./publish-mirror"]
|
||||
# Custom permissions for GitHub Jobs
|
||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" }, "publish-mirror" = { contents = "read" } }
|
||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read", id-token = "write", attestations = "write" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" }, "publish-crates" = { contents = "read" }, "publish-mirror" = { contents = "read" } }
|
||||
# Whether to install an updater program
|
||||
install-updater = false
|
||||
# Path that installers should place binaries in
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
Ruff uses a custom versioning scheme that uses the **minor** version number for breaking changes and the **patch** version number for bug fixes. Ruff does not yet have a stable API; once Ruff's API is stable, the **major** version number and semantic versioning will be used.
|
||||
|
||||
## Crate versioning
|
||||
|
||||
Ruff's crates are published to [crates.io](https://crates.io). The following crates follow Ruff's
|
||||
normal versioning policy:
|
||||
|
||||
- `ruff`
|
||||
- `ruff_linter`
|
||||
- `ruff_wasm`
|
||||
|
||||
The Rust interfaces of these crates do not follow semantic versioning.
|
||||
|
||||
The remaining crates published as part of Ruff releases provide **no stability guarantees**. Their
|
||||
Rust interfaces are considered internal and unstable. Consequently, they are versioned as `0.0.x`.
|
||||
The patch version is incremented on every Ruff release, regardless of changes to the crate.
|
||||
|
||||
## Version changes
|
||||
|
||||
**Minor** version increases will occur when:
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Naively increment the patch version of each crate in the workspace.
|
||||
#
|
||||
# This excludes crates which are versioned with Ruff's command-line interface.
|
||||
#
|
||||
# After incrementing the version in each member `Cargo.toml`, it updates the version pins in the
|
||||
# root `Cargo.toml` to match.
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.13"
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tomllib
|
||||
|
||||
NO_BUMP_CRATES = {"ruff", "ruff_linter", "ruff_wasm"}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
# Pre-sync NO_BUMP_CRATES versions before running cargo metadata.
|
||||
#
|
||||
# Rooster updates crate versions but isn't workspace-aware, so it doesn't update
|
||||
# the workspace dependency pins in the root Cargo.toml. This can cause cargo metadata
|
||||
# to fail with version mismatches (e.g., when going from 0.15.x to 0.16.0).
|
||||
# We fix the pins first by reading the crate versions directly.
|
||||
script_dir = pathlib.Path(__file__).parent
|
||||
workspace_manifest = script_dir.parent / "Cargo.toml"
|
||||
workspace_manifest_contents = workspace_manifest.read_text()
|
||||
parsed_workspace_manifest = tomllib.loads(workspace_manifest_contents)
|
||||
|
||||
for crate_name in NO_BUMP_CRATES:
|
||||
crate_manifest = script_dir.parent / "crates" / crate_name / "Cargo.toml"
|
||||
if not crate_manifest.exists():
|
||||
continue
|
||||
crate_version = tomllib.loads(crate_manifest.read_text())["package"]["version"]
|
||||
manifest_dependency = parsed_workspace_manifest["workspace"][
|
||||
"dependencies"
|
||||
].get(crate_name)
|
||||
if manifest_dependency is None:
|
||||
continue
|
||||
manifest_version = manifest_dependency["version"]
|
||||
if manifest_version != crate_version:
|
||||
workspace_manifest_contents = workspace_manifest_contents.replace(
|
||||
f'{crate_name} = {{ version = "{manifest_version}"',
|
||||
f'{crate_name} = {{ version = "{crate_version}"',
|
||||
)
|
||||
|
||||
workspace_manifest.write_text(workspace_manifest_contents)
|
||||
|
||||
# Now cargo metadata will succeed.
|
||||
result = subprocess.run(
|
||||
["cargo", "metadata", "--format-version", "1"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
content = json.loads(result.stdout)
|
||||
packages = {package["id"]: package for package in content["packages"]}
|
||||
|
||||
workspace_manifest_contents = workspace_manifest.read_text()
|
||||
parsed_workspace_manifest = tomllib.loads(workspace_manifest_contents)
|
||||
|
||||
version_changes = {}
|
||||
|
||||
for workspace_member in content["workspace_members"]:
|
||||
manifest = pathlib.Path(packages[workspace_member]["manifest_path"])
|
||||
name = packages[workspace_member]["name"]
|
||||
|
||||
# Only crates released to crates.io participate in the workspace release sequence.
|
||||
if packages[workspace_member].get("publish") == []:
|
||||
continue
|
||||
|
||||
# For the members we're not bumping, we'll still make sure that the version pinned in the
|
||||
# workspace manifest matches the version of the crate. This is done because Rooster isn't
|
||||
# Cargo workspace aware and won't otherwise bump these when updating the member `Cargo.toml`
|
||||
# files.
|
||||
if name in NO_BUMP_CRATES:
|
||||
manifest_dependency = parsed_workspace_manifest["workspace"][
|
||||
"dependencies"
|
||||
].get(name)
|
||||
if manifest_dependency is None:
|
||||
continue
|
||||
manifest_version = manifest_dependency["version"]
|
||||
metadata_version = packages[workspace_member]["version"]
|
||||
if manifest_version != metadata_version:
|
||||
version_changes[name] = (manifest_version, metadata_version)
|
||||
continue
|
||||
|
||||
# For other members, bump the patch version.
|
||||
version = packages[workspace_member]["version"]
|
||||
version_parts = [int(part) for part in version.split(".")]
|
||||
new_version = f"{version_parts[0]}.{version_parts[1]}.{version_parts[2] + 1}"
|
||||
|
||||
contents = manifest.read_text()
|
||||
contents = contents.replace(
|
||||
'version = "' + version + '"',
|
||||
'version = "' + new_version + '"',
|
||||
1,
|
||||
)
|
||||
|
||||
version_changes[name] = (version, new_version)
|
||||
manifest.write_text(contents)
|
||||
|
||||
# Update all the pins in the workspace root.
|
||||
for name, (old_version, new_version) in version_changes.items():
|
||||
workspace_manifest_contents = workspace_manifest_contents.replace(
|
||||
f'{name} = {{ version = "{old_version}"',
|
||||
f'{name} = {{ version = "{new_version}"',
|
||||
)
|
||||
|
||||
workspace_manifest.write_text(workspace_manifest_contents)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,163 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.13"
|
||||
# dependencies = []
|
||||
# ///
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
GENERATED_HEADER = "<!-- This file is generated. DO NOT EDIT -->"
|
||||
|
||||
RUFF_TEMPLATE = """{GENERATED_HEADER}
|
||||
|
||||
# Ruff
|
||||
|
||||
Ruff is an extremely fast Python linter and code formatter.
|
||||
|
||||
See the [documentation](https://docs.astral.sh/ruff/) or
|
||||
[repository](https://github.com/astral-sh/ruff) for more information.
|
||||
|
||||
This crate is the entry point to the Ruff command-line interface. The Rust API exposed here is not
|
||||
considered public interface.
|
||||
|
||||
This is version {ruff_version}. The source can be found [here]({source_url}).
|
||||
|
||||
The following Ruff workspace members are also available:
|
||||
|
||||
{WORKSPACE_MEMBERS}
|
||||
|
||||
Ruff's workspace members are considered internal and will have frequent breaking changes.
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
"""
|
||||
|
||||
|
||||
MEMBER_TEMPLATE = """{GENERATED_HEADER}
|
||||
|
||||
# {name}
|
||||
|
||||
This crate is an internal component of [Ruff](https://crates.io/crates/ruff). The Rust API exposed
|
||||
here is unstable and will have frequent breaking changes.
|
||||
|
||||
This version ({crate_version}) is a component of [Ruff {ruff_version}]({ruff_crates_io_url}). The
|
||||
source can be found [here]({source_url}).
|
||||
|
||||
See Ruff's [crate versioning policy](https://docs.astral.sh/ruff/versioning/#crate-versioning) for
|
||||
details on versioning.
|
||||
"""
|
||||
|
||||
|
||||
REPO_URL = "https://github.com/astral-sh/ruff"
|
||||
PRETTIER_VERSION = "3.8.3"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result = subprocess.run(
|
||||
["cargo", "metadata", "--format-version", "1"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
content = json.loads(result.stdout)
|
||||
packages = {package["id"]: package for package in content["packages"]}
|
||||
|
||||
# Find the Ruff version from the ruff crate.
|
||||
ruff_version = None
|
||||
for package in content["packages"]:
|
||||
if package["name"] == "ruff":
|
||||
ruff_version = package["version"]
|
||||
break
|
||||
if ruff_version is None:
|
||||
raise RuntimeError("Could not find ruff crate")
|
||||
|
||||
workspace_root = pathlib.Path(content["workspace_root"])
|
||||
readme_path = workspace_root / "crates" / "ruff" / "README.md"
|
||||
|
||||
workspace_members = []
|
||||
for workspace_member in content["workspace_members"]:
|
||||
package = packages[workspace_member]
|
||||
name = package["name"]
|
||||
# Skip the main Ruff crate.
|
||||
if name == "ruff":
|
||||
continue
|
||||
# Skip crates with publish = false.
|
||||
if package.get("publish") == []:
|
||||
continue
|
||||
workspace_members.append(name)
|
||||
|
||||
workspace_members.sort()
|
||||
|
||||
members_list = "\n".join(
|
||||
f"- [{name}](https://crates.io/crates/{name})" for name in workspace_members
|
||||
)
|
||||
|
||||
# Generate the README for the main Ruff crate.
|
||||
ruff_source_url = f"{REPO_URL}/blob/{ruff_version}/crates/ruff"
|
||||
readme_content = RUFF_TEMPLATE.format(
|
||||
GENERATED_HEADER=GENERATED_HEADER,
|
||||
WORKSPACE_MEMBERS=members_list,
|
||||
ruff_version=ruff_version,
|
||||
source_url=ruff_source_url,
|
||||
)
|
||||
readme_path.write_text(readme_content)
|
||||
|
||||
# Track all generated README paths for formatting at the end.
|
||||
generated_paths = [readme_path]
|
||||
|
||||
# Generate READMEs for all workspace members.
|
||||
for workspace_member in content["workspace_members"]:
|
||||
package = packages[workspace_member]
|
||||
name = package["name"]
|
||||
|
||||
# Skip the main Ruff crate (already handled above).
|
||||
if name == "ruff":
|
||||
continue
|
||||
# Skip crates that aren't released to crates.io.
|
||||
if package.get("publish") == []:
|
||||
continue
|
||||
|
||||
manifest_path = pathlib.Path(package["manifest_path"])
|
||||
crate_dir = manifest_path.parent
|
||||
member_readme_path = crate_dir / "README.md"
|
||||
|
||||
# Preserve hand-written crate READMEs.
|
||||
if member_readme_path.exists():
|
||||
existing_content = member_readme_path.read_text()
|
||||
if not existing_content.startswith(GENERATED_HEADER):
|
||||
print(f"Skipping {name}: existing README without generated header")
|
||||
continue
|
||||
|
||||
crate_version = package["version"]
|
||||
relative_crate_path = crate_dir.relative_to(workspace_root)
|
||||
source_url = f"{REPO_URL}/blob/{ruff_version}/{relative_crate_path}"
|
||||
|
||||
ruff_crates_io_url = f"https://crates.io/crates/ruff/{ruff_version}"
|
||||
member_readme_content = MEMBER_TEMPLATE.format(
|
||||
GENERATED_HEADER=GENERATED_HEADER,
|
||||
name=name,
|
||||
crate_version=crate_version,
|
||||
ruff_version=ruff_version,
|
||||
ruff_crates_io_url=ruff_crates_io_url,
|
||||
source_url=source_url,
|
||||
)
|
||||
member_readme_path.write_text(member_readme_content)
|
||||
generated_paths.append(member_readme_path)
|
||||
|
||||
print(f"Generated README for {name}")
|
||||
|
||||
# Format all generated READMEs once at the end.
|
||||
subprocess.run(
|
||||
["npx", "--yes", f"prettier@{PRETTIER_VERSION}", "--write"]
|
||||
+ [str(path) for path in generated_paths],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,190 @@
|
||||
# Publish workspace crates to crates.io idempotently.
|
||||
#
|
||||
# `cargo publish --workspace` fails if any selected crate version already exists on crates.io. That
|
||||
# makes release re-runs fail after a previous partial publish. This script queries crates.io first,
|
||||
# excludes package versions that already exist, then delegates to `cargo publish --workspace` for the
|
||||
# remaining packages so Cargo still handles package ordering.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# CARGO_REGISTRY_TOKEN=<tok> python3 scripts/publish-crates.py --no-verify
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
CRATES_IO_API = "https://crates.io/api/v1"
|
||||
USER_AGENT = "ruff-crates-io-publish (github.com/astral-sh/ruff)"
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
|
||||
# Short delay between crates.io API requests to avoid burst rate limits.
|
||||
CRATES_IO_REQUEST_DELAY_SECS = 0.2
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Crate:
|
||||
name: str
|
||||
version: str
|
||||
|
||||
def pretty(self) -> str:
|
||||
return f"{self.name}@{self.version}"
|
||||
|
||||
|
||||
def get_publishable_crates(cargo: list[str]) -> list[Crate]:
|
||||
"""Return publishable workspace crates."""
|
||||
result = subprocess.run(
|
||||
[*cargo, "metadata", "--format-version", "1", "--no-deps"],
|
||||
cwd=REPO_ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
metadata = json.loads(result.stdout)
|
||||
|
||||
workspace_member_ids = set(metadata["workspace_members"])
|
||||
crates = []
|
||||
for package in metadata["packages"]:
|
||||
if package["id"] not in workspace_member_ids:
|
||||
continue
|
||||
# `publish = false` is represented as an empty list in cargo metadata.
|
||||
if package.get("publish") == []:
|
||||
continue
|
||||
crates.append(Crate(name=package["name"], version=package["version"]))
|
||||
|
||||
return crates
|
||||
|
||||
|
||||
def crate_version_url(crate: Crate, api_url: str) -> str:
|
||||
crate_name = urllib.parse.quote(crate.name, safe="")
|
||||
crate_version = urllib.parse.quote(crate.version, safe="")
|
||||
return f"{api_url}/crates/{crate_name}/{crate_version}"
|
||||
|
||||
|
||||
def crate_version_exists(crate: Crate, api_url: str) -> bool:
|
||||
"""Return True if the crate version already exists on crates.io."""
|
||||
request = urllib.request.Request(
|
||||
crate_version_url(crate, api_url),
|
||||
headers={"User-Agent": USER_AGENT},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(request) as response:
|
||||
if response.status == 200:
|
||||
return True
|
||||
raise RuntimeError(
|
||||
f"unexpected status {response.status} for {crate.pretty()}"
|
||||
)
|
||||
except urllib.error.HTTPError as exc:
|
||||
if exc.code == 404:
|
||||
return False
|
||||
raise RuntimeError(
|
||||
f"failed to query {crate.pretty()} from crates.io: HTTP {exc.code}"
|
||||
) from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise RuntimeError(
|
||||
f"failed to query {crate.pretty()} from crates.io: {exc}"
|
||||
) from exc
|
||||
|
||||
|
||||
def existing_crate_versions(crates: list[Crate], api_url: str) -> set[str]:
|
||||
"""Return the names of workspace crates whose current version exists on crates.io."""
|
||||
existing = set()
|
||||
for index, crate in enumerate(crates):
|
||||
if index > 0:
|
||||
time.sleep(CRATES_IO_REQUEST_DELAY_SECS)
|
||||
if crate_version_exists(crate, api_url):
|
||||
print(f"Skipping {crate.pretty()}: already exists on crates.io")
|
||||
existing.add(crate.name)
|
||||
return existing
|
||||
|
||||
|
||||
def build_cargo_publish_command(
|
||||
cargo: list[str], existing: set[str], cargo_publish_args: list[str]
|
||||
) -> list[str]:
|
||||
"""Build the `cargo publish` command for all not-yet-published workspace crates."""
|
||||
command = [*cargo, "publish", "--workspace"]
|
||||
for crate_name in sorted(existing):
|
||||
command.extend(["--exclude", crate_name])
|
||||
command.extend(cargo_publish_args)
|
||||
return command
|
||||
|
||||
|
||||
def publish_workspace(
|
||||
cargo: list[str], crates: list[Crate], api_url: str, cargo_publish_args: list[str]
|
||||
) -> int:
|
||||
existing = existing_crate_versions(crates, api_url)
|
||||
missing = [crate for crate in crates if crate.name not in existing]
|
||||
|
||||
if not missing:
|
||||
print("All publishable workspace crate versions already exist on crates.io")
|
||||
return 0
|
||||
|
||||
print("Publishing workspace crate versions that do not exist on crates.io:")
|
||||
for crate in missing:
|
||||
print(f" {crate.pretty()}")
|
||||
|
||||
command = build_cargo_publish_command(cargo, existing, cargo_publish_args)
|
||||
return subprocess.run(command, cwd=REPO_ROOT).returncode
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Publish workspace crates to crates.io idempotently."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--api-url",
|
||||
default=CRATES_IO_API,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cargo",
|
||||
default=["cargo"],
|
||||
nargs="+",
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Pass --dry-run through to cargo publish.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-verify",
|
||||
action="store_true",
|
||||
help="Pass --no-verify through to cargo publish.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"cargo_publish_args",
|
||||
nargs=argparse.REMAINDER,
|
||||
help=argparse.SUPPRESS,
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
if args.cargo_publish_args[:1] == ["--"]:
|
||||
args.cargo_publish_args = args.cargo_publish_args[1:]
|
||||
return args
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(argv)
|
||||
|
||||
cargo_publish_args = []
|
||||
if args.dry_run:
|
||||
cargo_publish_args.append("--dry-run")
|
||||
if args.no_verify:
|
||||
cargo_publish_args.append("--no-verify")
|
||||
cargo_publish_args.extend(args.cargo_publish_args)
|
||||
|
||||
crates = get_publishable_crates(args.cargo)
|
||||
return publish_workspace(args.cargo, crates, args.api_url, cargo_publish_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "scripts"
|
||||
version = "0.0.1"
|
||||
dependencies = ["stdlibs", "tqdm", "mdformat", "pyyaml", "mypy-primer"]
|
||||
dependencies = ["stdlibs", "tqdm", "mdformat", "pyyaml", "mypy-primer", "httpx"]
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.black]
|
||||
|
||||
@@ -14,5 +14,14 @@ cd "$project_root"
|
||||
uvx --python 3.12 --isolated -- \
|
||||
rooster@0.1.1 release "$@"
|
||||
|
||||
# Bump internal crate versions
|
||||
uv run --script "$project_root/scripts/bump-workspace-crate-versions.py"
|
||||
|
||||
echo "Updating crate READMEs..."
|
||||
uv run --script "$project_root/scripts/generate-crate-readmes.py"
|
||||
|
||||
echo "Updating lockfile..."
|
||||
cargo update -p ruff
|
||||
|
||||
echo "Checking crates.io publish setup..."
|
||||
uv run --no-config --script "$project_root/scripts/setup-crates-io-publish.py" --quiet
|
||||
@@ -0,0 +1,459 @@
|
||||
# Ensure workspace crates are ready for trusted publishing on crates.io.
|
||||
#
|
||||
# This script performs four steps for each candidate crate:
|
||||
#
|
||||
# 1. Publish a placeholder crate if the crate doesn't yet exist on crates.io.
|
||||
# 2. Remove trusted publisher configs that don't match our desired config.
|
||||
# 3. Ensure exactly one desired trusted publishing config exists.
|
||||
# 4. Enable `trustpub_only` ("Require trusted publishing for all new versions").
|
||||
#
|
||||
# It authenticates with `CARGO_REGISTRY_TOKEN`, which must have the `publish-new` and
|
||||
# `trusted-publishing` scopes.
|
||||
#
|
||||
# Crates tracked in `.known-crates` are assumed to be configured and are skipped unless `--force` is
|
||||
# used.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# CARGO_REGISTRY_TOKEN=<tok> uv run --script scripts/setup-crates-io-publish.py [--dry-run] [--force] [--quiet]
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.13"
|
||||
# dependencies = ["httpx"]
|
||||
# ///
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import tomllib
|
||||
|
||||
import httpx
|
||||
|
||||
CRATES_IO_API = "https://crates.io/api/v1"
|
||||
USER_AGENT = "ruff-crates-io-publish-setup (github.com/astral-sh/ruff)"
|
||||
|
||||
REPOSITORY_OWNER = "astral-sh"
|
||||
REPOSITORY_NAME = "ruff"
|
||||
WORKFLOW_FILENAME = "release.yml"
|
||||
ENVIRONMENT = "release"
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
WORKSPACE_MANIFEST_PATH = REPO_ROOT / "Cargo.toml"
|
||||
KNOWN_CRATES_PATH = REPO_ROOT / ".known-crates"
|
||||
KNOWN_CRATES_HEADER = "# GENERATED-BY scripts/setup-crates-io-publish.py\n"
|
||||
|
||||
PLACEHOLDER_VERSION = "0.0.0"
|
||||
|
||||
# Delay between `cargo publish` calls to respect crates.io rate limits.
|
||||
PUBLISH_DELAY_SECS = 15
|
||||
|
||||
|
||||
def get_publishable_crates() -> list[dict[str, str]]:
|
||||
"""Return publishable workspace crates as ``[{"name": …, "version": …}]``."""
|
||||
result = subprocess.run(
|
||||
["cargo", "metadata", "--format-version", "1", "--no-deps"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
metadata = json.loads(result.stdout)
|
||||
|
||||
workspace_member_ids = set(metadata["workspace_members"])
|
||||
crates = []
|
||||
for package in metadata["packages"]:
|
||||
if package["id"] not in workspace_member_ids:
|
||||
continue
|
||||
# ``publish = false`` is represented as an empty list in cargo metadata.
|
||||
if package.get("publish") == []:
|
||||
continue
|
||||
crates.append({"name": package["name"], "version": package["version"]})
|
||||
|
||||
return sorted(crates, key=lambda c: c["name"])
|
||||
|
||||
|
||||
def load_known_crates() -> set[str]:
|
||||
"""Load crate names from ``.known-crates``."""
|
||||
if not KNOWN_CRATES_PATH.exists():
|
||||
return set()
|
||||
|
||||
known = set()
|
||||
for line in KNOWN_CRATES_PATH.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
known.add(line)
|
||||
return known
|
||||
|
||||
|
||||
def save_known_crates(crates: set[str]) -> None:
|
||||
"""Persist the sorted set of fully-configured crate names."""
|
||||
lines = [KNOWN_CRATES_HEADER]
|
||||
for name in sorted(crates):
|
||||
lines.append(f"{name}\n")
|
||||
KNOWN_CRATES_PATH.write_text("".join(lines))
|
||||
|
||||
|
||||
def get_crate_metadata(
|
||||
client: httpx.Client, crate_name: str
|
||||
) -> dict[str, object] | None:
|
||||
"""Get crate metadata from the public crates.io API."""
|
||||
response = client.get(f"{CRATES_IO_API}/crates/{crate_name}")
|
||||
if response.status_code == 404:
|
||||
return None
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def load_workspace_package_metadata() -> dict[str, object]:
|
||||
"""Load shared package metadata from the workspace manifest."""
|
||||
manifest = tomllib.loads(WORKSPACE_MANIFEST_PATH.read_text())
|
||||
workspace_package = manifest.get("workspace", {}).get("package")
|
||||
if not isinstance(workspace_package, dict):
|
||||
raise RuntimeError("workspace.package is missing from Cargo.toml")
|
||||
return workspace_package
|
||||
|
||||
|
||||
def publish_placeholder_crate(
|
||||
crate_name: str,
|
||||
workspace_package: dict[str, object],
|
||||
) -> bool:
|
||||
"""Publish a generated placeholder crate to reserve a new crates.io name."""
|
||||
with tempfile.TemporaryDirectory(prefix=f"{crate_name}-placeholder-") as temp_dir:
|
||||
temp_path = pathlib.Path(temp_dir)
|
||||
src_dir = temp_path / "src"
|
||||
src_dir.mkdir(parents=True)
|
||||
|
||||
authors = workspace_package.get("authors", [])
|
||||
if not isinstance(authors, list):
|
||||
raise RuntimeError("workspace.package.authors must be a list")
|
||||
|
||||
edition = workspace_package.get("edition")
|
||||
rust_version = workspace_package.get("rust-version")
|
||||
homepage = workspace_package.get("homepage")
|
||||
repository = workspace_package.get("repository")
|
||||
license_expression = workspace_package.get("license")
|
||||
|
||||
description = (
|
||||
"This is a placeholder release for an internal component crate of Ruff"
|
||||
)
|
||||
|
||||
manifest = (
|
||||
"[package]\n"
|
||||
f"name = {json.dumps(crate_name)}\n"
|
||||
f"version = {json.dumps(PLACEHOLDER_VERSION)}\n"
|
||||
f"edition = {json.dumps(edition)}\n"
|
||||
f"rust-version = {json.dumps(rust_version)}\n"
|
||||
f"authors = {json.dumps(authors)}\n"
|
||||
f"license = {json.dumps(license_expression)}\n"
|
||||
f"homepage = {json.dumps(homepage)}\n"
|
||||
f"repository = {json.dumps(repository)}\n"
|
||||
f"description = {json.dumps(description)}\n"
|
||||
'readme = "README.md"\n'
|
||||
)
|
||||
(temp_path / "Cargo.toml").write_text(manifest)
|
||||
(temp_path / "README.md").write_text(
|
||||
"<!-- This file is generated. DO NOT EDIT -->\n\n"
|
||||
f"# {crate_name}\n\n"
|
||||
f"This crate is an internal component of [Ruff](https://crates.io/crates/ruff). "
|
||||
f"This placeholder version ({PLACEHOLDER_VERSION}) only exists to reserve the "
|
||||
"crate name and enable trusted publishing for future releases.\n"
|
||||
)
|
||||
(src_dir / "lib.rs").write_text(
|
||||
"//! Placeholder crate published to reserve the name on crates.io.\n\n"
|
||||
"/// Marker type for the placeholder release.\n"
|
||||
"pub struct Placeholder;\n"
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"cargo",
|
||||
"publish",
|
||||
"--manifest-path",
|
||||
str(temp_path / "Cargo.toml"),
|
||||
"--no-verify",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print(result.stderr, file=sys.stderr, end="")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_trusted_publisher(client: httpx.Client, crate_name: str) -> None:
|
||||
"""Create a Trusted Publishing GitHub config for *crate_name*."""
|
||||
response = client.post(
|
||||
f"{CRATES_IO_API}/trusted_publishing/github_configs",
|
||||
json={
|
||||
"github_config": {
|
||||
"crate": crate_name,
|
||||
"repository_owner": REPOSITORY_OWNER,
|
||||
"repository_name": REPOSITORY_NAME,
|
||||
"workflow_filename": WORKFLOW_FILENAME,
|
||||
"environment": ENVIRONMENT,
|
||||
}
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def list_trusted_publishers(
|
||||
client: httpx.Client, crate_name: str
|
||||
) -> list[dict[str, object]]:
|
||||
"""List Trusted Publishing GitHub configs for *crate_name*."""
|
||||
response = client.get(
|
||||
f"{CRATES_IO_API}/trusted_publishing/github_configs",
|
||||
params={"crate": crate_name},
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
return payload.get("github_configs", [])
|
||||
|
||||
|
||||
def delete_trusted_publisher(client: httpx.Client, config_id: int) -> None:
|
||||
"""Delete a Trusted Publishing GitHub config by ID."""
|
||||
response = client.delete(
|
||||
f"{CRATES_IO_API}/trusted_publishing/github_configs/{config_id}"
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def set_trustpub_only(client: httpx.Client, crate_name: str, enabled: bool) -> None:
|
||||
"""Enable or disable `trustpub_only` for a crate."""
|
||||
response = client.patch(
|
||||
f"{CRATES_IO_API}/crates/{crate_name}",
|
||||
json={"crate": {"trustpub_only": enabled}},
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def is_desired_config(config: dict[str, object]) -> bool:
|
||||
"""Return True if *config* matches the workflow this repo uses."""
|
||||
return (
|
||||
config.get("repository_owner") == REPOSITORY_OWNER
|
||||
and config.get("repository_name") == REPOSITORY_NAME
|
||||
and config.get("workflow_filename") == WORKFLOW_FILENAME
|
||||
and config.get("environment") == ENVIRONMENT
|
||||
)
|
||||
|
||||
|
||||
def handle_trusted_publisher_error(exc: httpx.HTTPStatusError) -> None:
|
||||
"""Print a helpful hint for common trusted-publishing API errors, then exit."""
|
||||
print(
|
||||
f"error {exc.response.status_code}: {exc.response.text}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
if exc.response.status_code == 401:
|
||||
if "GitHub session has expired" in exc.response.text:
|
||||
print(
|
||||
"\nhint: your crates.io account's linked GitHub OAuth"
|
||||
" token is stale.\nLog out and back in at"
|
||||
" https://crates.io to refresh it, then re-run.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"\nhint: your CARGO_REGISTRY_TOKEN may be invalid or revoked.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
elif exc.response.status_code == 403:
|
||||
print(
|
||||
"\nhint: your token may lack the trusted-publishing scope,"
|
||||
" or you are not an owner of this crate.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Ensure workspace crates are ready for trusted publishing on crates.io."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Show what would be done without making changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force", action="store_true", help="Re-check crates already in .known-crates"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--quiet", "-q", action="store_true", help="Suppress informational output"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
dry_run = args.dry_run
|
||||
force = args.force
|
||||
quiet = args.quiet
|
||||
|
||||
workspace_package = load_workspace_package_metadata()
|
||||
|
||||
crates = get_publishable_crates()
|
||||
known = set() if force else load_known_crates()
|
||||
candidates = [c for c in crates if c["name"] not in known]
|
||||
|
||||
if not candidates:
|
||||
if not quiet:
|
||||
print(
|
||||
f"All {len(crates)} publishable crates are in .known-crates — nothing to do."
|
||||
)
|
||||
return
|
||||
|
||||
token = os.environ.get("CARGO_REGISTRY_TOKEN", "")
|
||||
if not token:
|
||||
crate_names = ", ".join(crate["name"] for crate in candidates)
|
||||
print(
|
||||
f"Crates requiring crates.io publish setup: {crate_names}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
"error: CARGO_REGISTRY_TOKEN is required (with `publish-new`"
|
||||
" and `trusted-publishing` scopes)",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
"\nPlease ask a crates.io owner to bootstrap releases which add a new crate.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
client = httpx.Client(headers={"User-Agent": USER_AGENT}, timeout=30)
|
||||
auth_client = httpx.Client(
|
||||
headers={"Authorization": token, "User-Agent": USER_AGENT},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
s = "" if len(candidates) == 1 else "s"
|
||||
if not quiet:
|
||||
print(f"Checking {len(candidates)} crate{s}")
|
||||
|
||||
published_any = False
|
||||
for crate in candidates:
|
||||
name = crate["name"]
|
||||
version = crate["version"]
|
||||
metadata = get_crate_metadata(client, name)
|
||||
exists = metadata is not None
|
||||
|
||||
trustpub_only_enabled = False
|
||||
if exists:
|
||||
crate_payload = metadata.get("crate")
|
||||
if isinstance(crate_payload, dict):
|
||||
trustpub_only_enabled = bool(crate_payload.get("trustpub_only", False))
|
||||
|
||||
configs: list[dict[str, object]] = []
|
||||
if exists:
|
||||
try:
|
||||
configs = list_trusted_publishers(auth_client, name)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
print(f"{name}: failed to list trusted publishers", file=sys.stderr)
|
||||
handle_trusted_publisher_error(exc)
|
||||
|
||||
desired_configs = [config for config in configs if is_desired_config(config)]
|
||||
extra_desired_configs = desired_configs[1:]
|
||||
non_matching_configs = [
|
||||
config for config in configs if not is_desired_config(config)
|
||||
]
|
||||
configs_to_delete = non_matching_configs + extra_desired_configs
|
||||
|
||||
needs_initial_publish = not exists
|
||||
needs_add_publisher = not desired_configs
|
||||
needs_trustpub_only = not trustpub_only_enabled
|
||||
|
||||
if dry_run:
|
||||
actions: list[str] = []
|
||||
if needs_initial_publish:
|
||||
actions.append("publish placeholder")
|
||||
if configs_to_delete:
|
||||
count = len(configs_to_delete)
|
||||
noun = "publisher" if count == 1 else "publishers"
|
||||
actions.append(f"remove {count} {noun}")
|
||||
if needs_add_publisher:
|
||||
actions.append("add trusted publisher")
|
||||
if needs_trustpub_only:
|
||||
actions.append("enable trustpub_only")
|
||||
|
||||
if actions:
|
||||
print(f"{name}: would {' and '.join(actions)}")
|
||||
elif not quiet:
|
||||
print(f"{name}: already configured")
|
||||
continue
|
||||
|
||||
if needs_initial_publish:
|
||||
if version == PLACEHOLDER_VERSION:
|
||||
print(
|
||||
f"{name}: workspace version matches placeholder version {PLACEHOLDER_VERSION}; "
|
||||
"bump the real crate version before running this script",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if published_any:
|
||||
print(f"waiting {PUBLISH_DELAY_SECS}s for rate limit")
|
||||
time.sleep(PUBLISH_DELAY_SECS)
|
||||
|
||||
print(f"{name}: publishing placeholder")
|
||||
if not publish_placeholder_crate(name, workspace_package):
|
||||
print(f"{name}: placeholder publish failed", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
published_any = True
|
||||
|
||||
if configs_to_delete:
|
||||
deleted = 0
|
||||
for config in configs_to_delete:
|
||||
config_id = config.get("id")
|
||||
if not isinstance(config_id, int):
|
||||
print(
|
||||
f"{name}: unexpected trusted publisher payload: missing `id`",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
try:
|
||||
delete_trusted_publisher(auth_client, config_id)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
print(
|
||||
f"{name}: failed to delete trusted publisher {config_id}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
handle_trusted_publisher_error(exc)
|
||||
deleted += 1
|
||||
noun = "publisher" if deleted == 1 else "publishers"
|
||||
print(f"{name}: removed {deleted} {noun}")
|
||||
|
||||
if needs_add_publisher:
|
||||
print(f"{name}: registering trusted publisher")
|
||||
try:
|
||||
create_trusted_publisher(auth_client, name)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
handle_trusted_publisher_error(exc)
|
||||
|
||||
if needs_trustpub_only:
|
||||
print(f"{name}: enabling trustpub_only")
|
||||
try:
|
||||
set_trustpub_only(auth_client, name, enabled=True)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
handle_trusted_publisher_error(exc)
|
||||
|
||||
if (
|
||||
not configs_to_delete
|
||||
and not needs_add_publisher
|
||||
and not needs_trustpub_only
|
||||
and not quiet
|
||||
):
|
||||
print(f"{name}: already configured")
|
||||
|
||||
known.add(name)
|
||||
|
||||
if not dry_run:
|
||||
save_known_crates(known)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[manifest]
|
||||
requirements = [{ name = "httpx" }]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.5.20"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
Generated
+79
@@ -2,6 +2,28 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.6.17"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -11,6 +33,52 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "certifi" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "idna" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.2.0"
|
||||
@@ -100,6 +168,7 @@ name = "scripts"
|
||||
version = "0.0.1"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "mdformat" },
|
||||
{ name = "mypy-primer" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -109,6 +178,7 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "httpx" },
|
||||
{ name = "mdformat" },
|
||||
{ name = "mypy-primer", git = "https://github.com/hauntsaninja/mypy_primer" },
|
||||
{ name = "pyyaml" },
|
||||
@@ -136,3 +206,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
Reference in new issue
Block a user