release/1.3.1
py 75 lines 2.41 KB
Raw
1 #!/usr/bin/env python3
2 """Check whether a crate version is visible in the crates.io sparse index.
3
4 Exit 0 when published, 1 when absent, and 2 when the registry could not be
5 queried. Do not use `cargo info` for this: inside a workspace it can resolve
6 the local package and report a version as published when it is not.
7 """
8
9 import argparse
10 import json
11 import sys
12 import time
13 import urllib.error
14 import urllib.request
15
16
17 def index_path(crate: str) -> str:
18 """Return the sparse-index path for a crate name."""
19 name = crate.lower()
20 if len(name) == 1:
21 return f"1/{name}"
22 if len(name) == 2:
23 return f"2/{name}"
24 if len(name) == 3:
25 return f"3/{name[0]}/{name}"
26 return f"{name[:2]}/{name[2:4]}/{name}"
27
28
29 def versions(crate: str) -> list[str]:
30 request = urllib.request.Request(
31 f"https://index.crates.io/{index_path(crate)}",
32 headers={"User-Agent": "ed-release (https://github.com/ondeinference/ed)"},
33 )
34 try:
35 with urllib.request.urlopen(request, timeout=30) as response:
36 body = response.read().decode("utf-8")
37 except urllib.error.HTTPError as error:
38 if error.code == 404:
39 return []
40 raise
41 return [json.loads(line)["vers"] for line in body.splitlines() if line.strip()]
42
43
44 def main() -> int:
45 parser = argparse.ArgumentParser(description=__doc__)
46 parser.add_argument("version")
47 parser.add_argument("--crate", required=True)
48 parser.add_argument("--wait", action="store_true")
49 parser.add_argument("--attempts", type=int, default=10)
50 parser.add_argument("--interval", type=int, default=15)
51 args = parser.parse_args()
52
53 total_attempts = args.attempts if args.wait else 1
54 for attempt in range(1, total_attempts + 1):
55 try:
56 if args.version in versions(args.crate):
57 print(f"{args.crate} {args.version} is on crates.io")
58 return 0
59 except (OSError, ValueError, urllib.error.URLError) as error:
60 print(f"could not reach the crates.io index: {error}", file=sys.stderr)
61 return 2
62
63 if attempt < total_attempts:
64 print(
65 f"attempt {attempt}: {args.crate} {args.version} is not visible; "
66 f"waiting {args.interval}s"
67 )
68 time.sleep(args.interval)
69
70 print(f"{args.crate} {args.version} is not on crates.io")
71 return 1
72
73
74 if __name__ == "__main__":
75 sys.exit(main())