Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 25 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ The ty repository only includes code relevant to distributing the ty project.

Clone the repository:

```bash
```shell
git clone https://github.com/astral-sh/ty.git
```

Then, ensure the submodule is initialized:

```bash
```shell
git submodule update --init --recursive
```

Expand All @@ -44,27 +44,27 @@ pre-commit install

The Python package can be built with any Python build frontend (Maturin is used as a backend), e.g.:

```bash
```shell
uv build
```

## Updating the Ruff commit

To update the Ruff submodule to the latest commit:

```bash
```shell
git -C ruff pull origin main
```

Or, to update the Ruff submodule to a specific commit:

```bash
```shell
git -C ruff checkout <commit>
```

To commit the changes:

```bash
```shell
commit=$(git -C ruff rev-parse --short HEAD)
git switch -c "sync/ruff-${commit}"
git add ruff
Expand All @@ -73,14 +73,14 @@ git commit -m "Update ruff submodule to https://github.com/astral-sh/ruff/commit

To restore the Ruff submodule to a clean-state, reset, then update the submodule:

```bash
```shell
git -C ruff reset --hard
git submodule update
```

To restore the Ruff submodule to the commit from `main`:

```bash
```shell
git -C ruff reset --hard $(git ls-tree main -- ruff | awk '{print $3}')
git add ruff
```
Expand All @@ -89,7 +89,9 @@ git add ruff

Releases can only be performed by Astral team members.

Preparation for the release is automated. First, run:
Preparation for the release is automated.

1. Run `./scripts/release.sh`

```shell
./scripts/release.sh
Expand All @@ -101,17 +103,20 @@ The release script will:
- Generate changelog entries based on pull requests here, and in Ruff
- Bump the versions in the `pyproject.toml` and `dist-workspace.toml`

After running the script, editorialize the `CHANGELOG.md` file to ensure entries are consistently
styled.

Then, open a pull request, e.g., `Bump version to ...`.

Binary builds will automatically be tested for the release.

After merging the pull request, run the
[release workflow](https://github.com/astral-sh/ty/actions/workflows/release.yml) with the version
tag. **Do not include a leading `v`**. The release will automatically be created on GitHub after
everything else publishes.
1. Editorialize the `CHANGELOG.md` file to ensure entries are consistently styled.
1. Create a pull request with the changelog and version changes, e.g., `Bump version to ...`.
Binary builds will automatically be tested for the release.
1. Merge the PR
1. Run the [release workflow](https://github.com/astral-sh/ty/actions/workflows/release.yml) with the version
tag. **Do not include a leading `v`**. The release will automatically be created on GitHub after
everything else publishes.
1. Run `uv run --no-project ./scripts/update_schemastore.py` to prepare a PR to update the `ty.json` schema in the schemastore repository.
Follow the link in the script's output to submit the PR. The script is a no-op if there are no schema changes.
1. If necessary, update and release [`ty-vscode`](https://github.com/astral-sh/ty-vscode).
Follow the instructions in the `ty-vscode` repository. Updating the extension is required when:
- for minor releases to bump the bundled ty version
- for patch releases after fixing an important bug in `ty lsp` to bump the bundled ty version
- when releasing new `ty lsp` features that require changes in `ty-vscode`

When running the release workflow for pre-release versions, use the Cargo version format (not PEP
440), e.g. `0.0.0-alpha.5` (not `0.0.0a5`). For stable releases, these formats are identical.
185 changes: 185 additions & 0 deletions scripts/update_schemastore.py
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly just copy paste from the ruff repository. The only real addition is in main where we check the state of the ruff submodule

Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Update ty.json in schemastore.

This script will clone astral-sh/schemastore, update the schema and push the changes
to a new branch tagged with the ty git hash. You should see a URL to create the PR
to schemastore in the CLI.
"""

from __future__ import annotations

import enum
import json
from pathlib import Path
from subprocess import check_call, check_output
from tempfile import TemporaryDirectory
from typing import NamedTuple, assert_never

TY_REPO = "https://github.com/astral-sh/ty"
TY_JSON = Path("schemas/json/ty.json")


class SchemastoreRepos(NamedTuple):
fork: str
upstream: str


class GitProtocol(enum.Enum):
SSH = "ssh"
HTTPS = "https"

def schemastore_repos(self) -> SchemastoreRepos:
match self:
case GitProtocol.SSH:
return SchemastoreRepos(
fork="git@github.com:astral-sh/schemastore.git",
upstream="git@github.com:SchemaStore/schemastore.git",
)
case GitProtocol.HTTPS:
return SchemastoreRepos(
fork="https://github.com/astral-sh/schemastore.git",
upstream="https://github.com/SchemaStore/schemastore.git",
)
case _:
assert_never(self)


def update_schemastore(
schemastore_path: Path, schemastore_repos: SchemastoreRepos, root: Path
) -> None:
if not schemastore_path.is_dir():
check_call(
["git", "clone", schemastore_repos.fork, schemastore_path, "--depth=1"]
)
check_call(
[
"git",
"remote",
"add",
"upstream",
schemastore_repos.upstream,
],
cwd=schemastore_path,
)
# Create a new branch tagged with the current ty commit up to date with the latest
# upstream schemastore
check_call(["git", "fetch", "upstream"], cwd=schemastore_path)
current_sha = check_output(["git", "rev-parse", "HEAD"], text=True).strip()
branch = f"update-ty-{current_sha}"
check_call(
["git", "switch", "-c", branch],
cwd=schemastore_path,
)
check_call(
["git", "reset", "--hard", "upstream/master"],
cwd=schemastore_path,
)

# Run npm install
src = schemastore_path.joinpath("src")
check_call(["npm", "install"], cwd=schemastore_path)

# Update the schema and format appropriately
schema = json.loads(root.joinpath("ruff/ty.schema.json").read_text())
schema["$id"] = "https://json.schemastore.org/ty.json"
src.joinpath(TY_JSON).write_text(
json.dumps(dict(schema.items()), indent=2, ensure_ascii=False),
)
check_call(
[
"../node_modules/prettier/bin/prettier.cjs",
"--plugin",
"prettier-plugin-sort-json",
"--write",
TY_JSON,
],
cwd=src,
)

# Check if the schema has changed
# https://stackoverflow.com/a/9393642/3549270
if check_output(["git", "status", "-s"], cwd=schemastore_path).strip():
# Schema has changed, commit and push
commit_url = f"{TY_REPO}/commit/{current_sha}"
commit_body = f"This updates ty's JSON schema to [{current_sha}]({commit_url})"
# https://stackoverflow.com/a/22909204/3549270
check_call(
[
"git",
"commit",
"-a",
"-m",
"Update ty's JSON schema",
"-m",
commit_body,
],
cwd=schemastore_path,
)
# This should show the link to create a PR
check_call(
["git", "push", "--set-upstream", "origin", branch, "--force"],
cwd=schemastore_path,
)
else:
print("No changes")


def determine_git_protocol(argv: list[str] | None = None) -> GitProtocol:
import argparse

parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--proto",
choices=[proto.value for proto in GitProtocol],
default="https",
help="Protocol to use for git authentication",
)
args = parser.parse_args(argv)
return GitProtocol(args.proto)


def main() -> None:
root = Path(
check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip(),
)

expected_ruff_revision = check_output(
["git", "ls-tree", "main", "--format", "%(objectname)", "ruff"]
).strip()
actual_ruff_revision = check_output(
["git", "-C", "ruff", "rev-parse", "HEAD"]
).strip()

if expected_ruff_revision != actual_ruff_revision:
print(
f"The ruff submodule is at {expected_ruff_revision} but main expects {actual_ruff_revision}"
)
match input(
"How do you want to proceed (u=reset submodule, n=abort, y=continue)? "
):
case "u":
check_call(
["git", "-C", "ruff", "reset", "--hard", expected_ruff_revision]
)
case "n":
return
case "y":
...
case command:
print(f"Invalid input '{command}', abort")
return

schemastore_repos = determine_git_protocol().schemastore_repos()
schemastore_existing = root.joinpath("schemastore")
if schemastore_existing.is_dir():
update_schemastore(schemastore_existing, schemastore_repos, root)
else:
with TemporaryDirectory() as temp_dir:
update_schemastore(
Path(temp_dir).joinpath("schemastore"), schemastore_repos, root
)


if __name__ == "__main__":
main()