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: 0 additions & 45 deletions .github/scripts/_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import json
import re
import subprocess
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from typing import Literal
Expand All @@ -13,11 +12,6 @@
BumpKind = Literal["patch", "minor", "major"]
ManifestKind = Literal["cargo", "node", "python"]

# Pre-release suffixes offered by the Create Tag workflow. `stable` is not a
# suffix but the promotion path (drop the pre-release, keep the base version),
# so it lives in the same input.
PRERELEASE_SUFFIXES = ("alpha", "beta", "rc")


def parse_semver(v: str) -> SemverKey:
"""Returns a tuple suitable for lexicographic compare.
Expand Down Expand Up @@ -58,45 +52,6 @@ def bump(current: str, kind: BumpKind) -> str:
return f"{major}.{minor}.{patch}"


def core_version(v: str) -> str:
"""Returns `v` without its pre-release / build suffix (1.2.3-rc.1 -> 1.2.3)."""
return v.partition("-")[0].partition("+")[0]


def next_prerelease(base: str, suffix: str, existing: Iterable[str]) -> str:
"""Returns `base-suffix.N`, one past the highest N already released.

`existing` is every version already tagged for the worker; only entries
matching this exact base and suffix count, so alpha and beta lines at the
same base advance independently.
"""
pattern = re.compile(rf"^{re.escape(base)}-{re.escape(suffix)}\.(\d+)$")
highest = 0
for version in existing:
m = pattern.match(version.strip())
if m:
highest = max(highest, int(m.group(1)))
return f"{base}-{suffix}.{highest + 1}"


def list_tagged_versions(worker: str) -> list[str]:
"""Versions already tagged for `worker`, from git tags `<worker>/v<version>`.

Returns [] when git is unavailable or the worker has no tags yet, so a
first pre-release starts its counter at 1.
"""
prefix = f"{worker}/v"
try:
out = subprocess.check_output(
["git", "tag", "--list", f"{prefix}*"],
text=True,
stderr=subprocess.DEVNULL,
)
except (subprocess.CalledProcessError, FileNotFoundError):
return []
return [line[len(prefix):] for line in out.splitlines() if line.startswith(prefix)]


def detect_kind(manifest_path: Path) -> ManifestKind:
"""Identifies a manifest file by its filename."""
name = manifest_path.name
Expand Down
30 changes: 1 addition & 29 deletions .github/scripts/manifest_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
Subcommands:
read <path> print the manifest's version to stdout
bump <path> --kind ... bump the version in-place
[--suffix alpha|beta|rc|stable|none --worker NAME]
verify <path> --expected V
assert the file's version equals V
deploy-mode <worker> print the interface-collection mode
Expand Down Expand Up @@ -32,28 +31,11 @@ def cmd_read(args: argparse.Namespace) -> int:
return 0


def _resolve_version(current: str, kind: str, suffix: str, worker: str | None) -> str:
"""Applies `kind` (version bump) and `suffix` (pre-release line) to `current`.

The two are independent: `kind` picks the base version, `suffix` decides
whether that base ships as a pre-release. `none` leaves the manifest value
alone (a merged PR may have set it); `stable` promotes a pre-release to its
base without bumping (1.2.3-rc.2 -> 1.2.3).
"""
if suffix in _lib.PRERELEASE_SUFFIXES:
base = _lib.core_version(current) if kind == "none" else _lib.bump(current, kind)
existing = _lib.list_tagged_versions(worker) if worker else []
return _lib.next_prerelease(base, suffix, existing)
if suffix == "stable":
return _lib.core_version(current) if kind == "none" else _lib.bump(current, kind)
return current if kind == "none" else _lib.bump(current, kind)


def cmd_bump(args: argparse.Namespace) -> int:
path = Path(args.manifest)
try:
current = _lib.read_version(path)
new = _resolve_version(current, args.kind, args.suffix, args.worker)
new = current if args.kind == "none" else _lib.bump(current, args.kind)
_lib.write_version(path, new)
except (FileNotFoundError, ValueError) as e:
print(f"error: {e}", file=sys.stderr)
Expand Down Expand Up @@ -149,16 +131,6 @@ def main(argv: list[str] | None = None) -> int:
p_bump = sub.add_parser("bump", help="bump the manifest version in place")
p_bump.add_argument("manifest")
p_bump.add_argument("--kind", choices=["patch", "minor", "major", "none"], required=True)
p_bump.add_argument(
"--suffix",
choices=["none", "stable", *_lib.PRERELEASE_SUFFIXES],
default="none",
help="pre-release line for the bumped version (none = leave as-is)",
)
p_bump.add_argument(
"--worker",
help="worker name; used to read existing git tags when numbering a pre-release",
)
p_bump.set_defaults(func=cmd_bump)

p_verify = sub.add_parser("verify", help="assert the manifest version equals --expected")
Expand Down
14 changes: 0 additions & 14 deletions .github/scripts/parse_release_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
DRY_RUN_RE = re.compile(r"-dry-run\.\d+$")
PRERELEASE_RE = re.compile(r"-[a-z]+\.\d+$")

# Distribution channels, orthogonal to the version's pre-release suffix: a
# release is `<version>@<channel>`, e.g. 1.2.3-rc.1@next. The registry stores
# `registry-tag` verbatim (a free-form string column), so a typo in the
# annotated tag message would silently create a dead channel that nothing
# resolves. Keep the accepted set closed here, matching the Create Tag options.
RELEASE_CHANNELS = ("latest", "next", "experimental")


def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser()
Expand Down Expand Up @@ -63,13 +56,6 @@ def main(argv: list[str] | None = None) -> int:
return 1

registry_tag = _lib.read_tag_annotation(raw).get("registry-tag", "latest") or "latest"
if registry_tag not in RELEASE_CHANNELS:
print(
f"::error::Unknown registry-tag {registry_tag!r} in the annotated tag "
f"message; expected one of {', '.join(RELEASE_CHANNELS)}",
file=sys.stderr,
)
return 1

# `targets` is an optional iii.worker.yaml field that can be either a
# list (`- aarch64-apple-darwin\n- x86_64-unknown-linux-gnu`) or a comma
Expand Down
20 changes: 0 additions & 20 deletions .github/scripts/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,6 @@ def pyproject_manifest(tmp_path: Path) -> Path:
return p


@pytest.fixture
def git_repo_manifest(tmp_path: Path) -> tuple[Path, Path]:
"""A git repo with a committed Cargo.toml at 0.1.0, for pre-release numbering.

Returns (repo_dir, manifest_path). Tests add `<worker>/v<version>` tags to
the repo to exercise the counter `manifest_version.py bump --worker` reads.
"""
def git(*args: str) -> None:
subprocess.run(args, cwd=tmp_path, check=True, env=GIT_HERMETIC_ENV)

git("git", "init", "-q", "-b", "main")
git("git", "config", "user.email", "test@example.com")
git("git", "config", "user.name", "Test")
manifest = tmp_path / "Cargo.toml"
manifest.write_text('[package]\nname = "smoke"\nversion = "0.1.0"\nedition = "2021"\n')
git("git", "add", ".")
git("git", "commit", "-q", "-m", "init")
return tmp_path, manifest


@pytest.fixture
def iii_worker_yaml_dir(tmp_path: Path) -> Path:
"""Returns a tmp dir containing a minimal iii.worker.yaml (rust binary)."""
Expand Down
86 changes: 1 addition & 85 deletions .github/scripts/tests/test_manifest_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,15 @@
import sys
from pathlib import Path

import pytest

from _test_helpers import GIT_HERMETIC_ENV

SCRIPT = Path(__file__).resolve().parents[1] / "manifest_version.py"


def run_script(*args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
def run_script(*args: str) -> subprocess.CompletedProcess[str]:
"""Run manifest_version.py with arguments; capture stdout/stderr/exit."""
return subprocess.run(
[sys.executable, str(SCRIPT), *args],
capture_output=True,
text=True,
cwd=cwd,
env=GIT_HERMETIC_ENV,
)


def tag(repo: Path, name: str) -> None:
"""Create an annotated tag in `repo` (the shape create-tag.yml pushes)."""
subprocess.run(
["git", "tag", "-a", name, "-m", f"Release {name}\n\nregistry-tag: next\n"],
cwd=repo, check=True, env=GIT_HERMETIC_ENV,
)


Expand Down Expand Up @@ -82,76 +68,6 @@ def test_bump_rejects_unknown_kind(self, cargo_manifest):
r = run_script("bump", str(cargo_manifest), "--kind", "weird")
assert r.returncode != 0

def test_bump_defaults_to_no_suffix(self, cargo_manifest):
r = run_script("bump", str(cargo_manifest), "--kind", "patch")
assert r.stdout.strip() == "0.1.1"


class TestBumpSuffix:
"""`--suffix` picks the pre-release line; `--kind` still picks the base."""

@pytest.mark.parametrize("suffix", ["alpha", "beta", "rc"])
def test_suffix_starts_counter_at_one(self, cargo_manifest, suffix):
r = run_script("bump", str(cargo_manifest), "--kind", "patch", "--suffix", suffix)
assert r.returncode == 0, r.stderr
assert r.stdout.strip() == f"0.1.1-{suffix}.1"

def test_suffix_with_kind_none_keeps_base(self, cargo_manifest):
"""Iterating a pre-release must not walk the base version forward."""
r = run_script("bump", str(cargo_manifest), "--kind", "none", "--suffix", "alpha")
assert r.stdout.strip() == "0.1.0-alpha.1"

def test_suffix_strips_existing_prerelease_before_bumping(self, cargo_manifest):
run_script("bump", str(cargo_manifest), "--kind", "none", "--suffix", "alpha")
r = run_script("bump", str(cargo_manifest), "--kind", "patch", "--suffix", "beta")
assert r.stdout.strip() == "0.1.1-beta.1"

def test_stable_promotes_prerelease_to_base(self, cargo_manifest):
run_script("bump", str(cargo_manifest), "--kind", "none", "--suffix", "rc")
r = run_script("bump", str(cargo_manifest), "--kind", "none", "--suffix", "stable")
assert r.stdout.strip() == "0.1.0"

def test_stable_on_stable_version_is_a_noop(self, cargo_manifest):
r = run_script("bump", str(cargo_manifest), "--kind", "none", "--suffix", "stable")
assert r.stdout.strip() == "0.1.0"

def test_rejects_unknown_suffix(self, cargo_manifest):
r = run_script("bump", str(cargo_manifest), "--kind", "patch", "--suffix", "gamma")
assert r.returncode != 0


class TestPrereleaseCounter:
"""`--worker` numbers the pre-release from tags already in the repo."""

def test_counter_continues_from_existing_tags(self, git_repo_manifest):
repo, manifest = git_repo_manifest
tag(repo, "smoke/v0.1.1-alpha.1")
tag(repo, "smoke/v0.1.1-alpha.2")
r = run_script("bump", str(manifest), "--kind", "patch",
"--suffix", "alpha", "--worker", "smoke", cwd=repo)
assert r.stdout.strip() == "0.1.1-alpha.3"

def test_counter_ignores_other_suffixes_at_same_base(self, git_repo_manifest):
repo, manifest = git_repo_manifest
tag(repo, "smoke/v0.1.1-alpha.4")
r = run_script("bump", str(manifest), "--kind", "patch",
"--suffix", "beta", "--worker", "smoke", cwd=repo)
assert r.stdout.strip() == "0.1.1-beta.1"

def test_counter_ignores_other_workers(self, git_repo_manifest):
repo, manifest = git_repo_manifest
tag(repo, "other/v0.1.1-alpha.9")
r = run_script("bump", str(manifest), "--kind", "patch",
"--suffix", "alpha", "--worker", "smoke", cwd=repo)
assert r.stdout.strip() == "0.1.1-alpha.1"

def test_counter_ignores_other_base_versions(self, git_repo_manifest):
repo, manifest = git_repo_manifest
tag(repo, "smoke/v0.2.0-alpha.7")
r = run_script("bump", str(manifest), "--kind", "patch",
"--suffix", "alpha", "--worker", "smoke", cwd=repo)
assert r.stdout.strip() == "0.1.1-alpha.1"


class TestVerifySubcommand:
def test_verify_match(self, cargo_manifest):
Expand Down
41 changes: 0 additions & 41 deletions .github/scripts/tests/test_parse_release_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,47 +82,6 @@ def test_prerelease_sets_is_prerelease(self, tmp_path):
assert out["dry_run"] == "false"
assert out["registry_tag"] == "next"

def test_experimental_channel(self, tmp_path):
repo = make_repo_with_tagged_worker(
tmp_path, "smoke/v1.2.3", "1.2.3",
registry_tag_line="registry-tag: experimental")
out_path = tmp_path / "gh_output"
out_path.touch()
r = run_script(repo, "smoke/v1.2.3", out_path)
assert r.returncode == 0, r.stderr
assert parse_outputs(out_path)["registry_tag"] == "experimental"

# A suffixed version is orthogonal to the channel: it ships on whichever
# channel the tag message names, and still marks the GitHub Release as a
# prerelease.
@pytest.mark.parametrize("suffix", ["alpha", "beta", "rc"])
def test_suffixed_version_on_next_channel(self, tmp_path, suffix):
version = f"1.2.3-{suffix}.1"
repo = make_repo_with_tagged_worker(
tmp_path, f"smoke/v{version}", version,
registry_tag_line="registry-tag: next")
out_path = tmp_path / "gh_output"
out_path.touch()
r = run_script(repo, f"smoke/v{version}", out_path)
assert r.returncode == 0, r.stderr
out = parse_outputs(out_path)
assert out["version"] == version
assert out["registry_tag"] == "next"
assert out["is_prerelease"] == "true"

# `alpha` is a version suffix, never a channel; accepting it here would
# publish a channel the registry resolves for nobody.
@pytest.mark.parametrize("bad", ["alpha", "latests", "stable"])
def test_unknown_channel_fails(self, tmp_path, bad):
repo = make_repo_with_tagged_worker(
tmp_path, "smoke/v1.2.3", "1.2.3",
registry_tag_line=f"registry-tag: {bad}")
out_path = tmp_path / "gh_output"
out_path.touch()
r = run_script(repo, "smoke/v1.2.3", out_path)
assert r.returncode == 1
assert "Unknown registry-tag" in r.stderr

def test_dry_run_tag(self, tmp_path):
repo = make_repo_with_tagged_worker(tmp_path, "smoke/v9.9.9-dry-run.1", "9.9.9-dry-run.1")
out_path = tmp_path / "gh_output"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/_container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
required: true
type: string
registry_tag:
description: 'Registry channel to also push as an image tag (latest, next, experimental)'
description: 'Registry tag to also push (latest, next, ...)'
required: false
type: string
default: latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/_publish-registry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
required: true
type: string
registry_tag:
description: 'Registry channel (latest, next, experimental)'
description: 'Registry tag (latest, next, ...)'
required: false
type: string
default: latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/_publish-worker-skills.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
required: true
type: string
version:
description: 'Registry channel (latest, next, experimental)'
description: 'Registry tag channel (latest, next, ...)'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the obsolete “channel” terminology.

Call this a “Registry tag” to match the other workflows and avoid reintroducing the removed registry-channel concept.

Proposed fix
-        description: 'Registry tag channel (latest, next, ...)'
+        description: 'Registry tag (latest, next, ...)'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: 'Registry tag channel (latest, next, ...)'
description: 'Registry tag (latest, next, ...)'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/_publish-worker-skills.yml at line 11, Update the workflow
input description for the registry tag configuration to remove the obsolete
“channel” terminology and describe it simply as “Registry tag,” while retaining
the existing tag examples.

required: true
type: string
api_url:
Expand Down
Loading
Loading