#!/usr/bin/env -S uv run --script # # /// script # requires-python = ">=3.12" # dependencies = [ # "pathspec>=1.1.1", # ] # # [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" # /// """Update script lockfiles. Run `uv run scripts/check-scripts.py` to update the lockfiles. Use `--write-ty-args-file ` 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())