| #!/usr/bin/env -S uv run --script | |
| # | |
| # /// script | |
| # requires-python = ">=3.12" | |
| # dependencies = [ | |
| # "pathspec>=1.1.1", | |
| # ] | |
| # | |
| # [tool.ty.analysis] | |
| # return-type-inference = "always" | |
| # | |
| # [tool.ty.rules] | |
| # truthiness-test-of-none-union = "warn" | |
| # blanket-ignore-comment = "warn" | |
| # missing-type-argument = "warn" | |
| # possibly-unresolved-reference = "warn" | |
| # unsound-return-statement = "warn" | |
| # unsound-yield = "warn" | |
| # unsupported-dynamic-base = "warn" | |
| # division-by-zero = "warn" | |
| # dynamic-function-decorator-return = "warn" | |
| # unsound-assignment = "warn" | |
| # redundant-condition-strict = "warn" | |
| # disjoint-cast = "warn" | |
| # missing-direct-dependency = "warn" | |
| # | |
| # [tool.uv] | |
| # require-build-hashes = true | |
| # preview-features = ["build-dependency-hashes", "missing-exclude-newer-package-lock"] | |
| # /// | |
| """Update script lockfiles. | |
| Run `uv run scripts/check-scripts.py` to update the lockfiles. | |
| Use `--write-ty-args-file <path>` to write the discovered scripts to an argument file for ty | |
| instead of updating lockfiles. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import subprocess | |
| import sys | |
| from collections.abc import Iterator | |
| from pathlib import Path | |
| from pathspec import GitIgnoreSpec | |
| ROOT = Path(__file__).resolve().parent.parent | |
| # Crate resources and completion fixtures are inputs, not project scripts. | |
| COMPLETION_FIXTURES = Path("crates/ty_completion_eval/truth") | |
| def scripts() -> Iterator[Path]: | |
| """Yield paths to Python scripts containing PEP 723 metadata. | |
| The paths are relative to the repository root. Files ignored by the root | |
| `.gitignore`, symlinks, crate resources, and completion fixtures are | |
| excluded. Scripts with invalid metadata are included so `uv lock` can | |
| report the error. | |
| """ | |
| gitignore = GitIgnoreSpec.from_lines((ROOT / ".gitignore").read_text().splitlines()) | |
| # uv's script listing skips invalid metadata, so find candidates before | |
| # asking uv to validate and lock them. | |
| # | |
| # We use `.walk()` here rather than `.rglob()` because some of our ignored directories are | |
| # huge; `.walk()` allows us to lazily inspect whether they're gitignored before recursively | |
| # descending into them. | |
| for directory, dirnames, filenames in ROOT.walk(): | |
| dirnames[:] = [ | |
| dirname | |
| for dirname in dirnames | |
| if (path := (directory / dirname).relative_to(ROOT)) != Path(".git") | |
| and not gitignore.match_file(f"{path.as_posix()}/") | |
| ] | |
| for filename in filenames: | |
| if not filename.endswith(".py"): | |
| continue | |
| full_path = directory / filename | |
| path = full_path.relative_to(ROOT) | |
| if path.is_relative_to(COMPLETION_FIXTURES): | |
| continue | |
| match full_path.relative_to(ROOT).parts: | |
| case ("crates", _, "resources", *_): | |
| continue | |
| if ( | |
| full_path.is_symlink() | |
| or not full_path.is_file() | |
| or gitignore.match_file(path) | |
| ): | |
| continue | |
| if b"# /// script" in full_path.read_bytes().splitlines(): | |
| yield path | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False) | |
| parser.add_argument( | |
| "--write-ty-args-file", | |
| type=Path, | |
| help="Write script paths to an argument file for ty", | |
| ) | |
| args = parser.parse_args() | |
| if args.write_ty_args_file is not None: | |
| args.write_ty_args_file.write_text( | |
| "".join(f"{script}\n" for script in scripts()), encoding="utf-8" | |
| ) | |
| return 0 | |
| failed = False | |
| for script in scripts(): | |
| print(script, flush=True) | |
| command = ["uv", "lock", "--script", str(script), "--refresh", "--no-locked"] | |
| result = subprocess.run(command, cwd=ROOT, check=False) | |
| failed |= result.returncode != 0 | |
| return int(failed) | |
| if __name__ == "__main__": | |
| sys.exit(main()) |