From 1688647421b336636de442320aba9319ed7f5dac Mon Sep 17 00:00:00 2001 From: NoahLan <6995syu@163.com> Date: Sun, 17 May 2026 13:29:42 +0800 Subject: [PATCH] chore: iti-system --- iticli/cli.py | 86 ++++++++++++++++++++++++++++++++++++++++++++--- tests/test_cli.py | 17 ++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/iticli/cli.py b/iticli/cli.py index 5097a17..b29415a 100644 --- a/iticli/cli.py +++ b/iticli/cli.py @@ -236,7 +236,9 @@ def handle_create(args: argparse.Namespace) -> int: def handle_release(args: argparse.Namespace) -> int: - ctx = require_project({"framework"}) + ctx = require_project({"framework", "system"}) + if ctx.kind == "system": + return release_system(ctx.root, args.version) return release_framework(ctx.root, args.version) @@ -340,9 +342,16 @@ def detect_project(cwd: Path) -> ProjectContext: and (root / "app").is_dir() and (root / "migrations" / "alembic.ini").is_file() ) + is_system = ( + project_name == "iti-system" + and (root / "iti_system" / "module.py").is_file() + and (root / "iti_system" / "cli.py").is_file() + ) if is_framework: kind = "framework" + elif is_system: + kind = "system" elif is_business: kind = "business" else: @@ -474,7 +483,7 @@ def project_env(root: Path, extra_env: dict[str, str] | None = None) -> dict[str def release_framework(root: Path, version_arg: str | None) -> int: - current_version = read_current_version(root) + current_version = read_current_version(root / "iti" / "__about__.py") target_version = normalize_version(version_arg) if version_arg else bump_patch(current_version) target_tag = f"v{target_version}" @@ -524,6 +533,47 @@ def release_framework(root: Path, version_arg: str | None) -> int: return 0 +def release_system(root: Path, version_arg: str | None) -> int: + current_version = read_current_version(root / "iti_system" / "__about__.py") + target_version = normalize_version(version_arg) if version_arg else bump_patch(current_version) + target_tag = f"v{target_version}" + + if target_version == current_version: + raise CliError(f"version already set to {target_version}") + if not version_is_newer(current_version, target_version): + raise CliError(f"target version must be newer than {current_version}") + if git_output(root, ["status", "--porcelain"]).strip(): + raise CliError("working tree is not clean") + + branch = git_output(root, ["symbolic-ref", "--quiet", "--short", "HEAD"]).strip() + if not branch: + raise CliError("release requires a branch checkout") + if subprocess.run(["git", "rev-parse", "--verify", "--quiet", f"refs/tags/{target_tag}"], cwd=root).returncode == 0: + raise CliError(f"tag already exists: {target_tag}") + + code = run(["uv", "run", "pytest", "-q"], root) + if code: + return code + + write_system_release_files(root, target_version) + + code = run(["git", "add", "iti_system/__about__.py", "pyproject.toml", "README.md"], root) + if code: + return code + code = run(["git", "commit", "-m", f"chore: release {target_tag}"], root) + if code: + return code + code = run(["git", "tag", "-a", target_tag, "-m", f"release {target_tag}"], root) + if code: + return code + code = run(["git", "push", "origin", branch, target_tag], root) + if code: + return code + + print(f"released {target_tag}") + return 0 + + def git_output(root: Path, args: Sequence[str]) -> str: completed = subprocess.run( ["git", *args], @@ -538,8 +588,7 @@ def git_output(root: Path, args: Sequence[str]) -> str: return completed.stdout -def read_current_version(root: Path) -> str: - about_file = root / "iti" / "__about__.py" +def read_current_version(about_file: Path) -> str: match = re.search(r'^__version__\s*=\s*"([^"]+)"', about_file.read_text(encoding="utf-8"), re.MULTILINE) if not match: raise CliError(f"version not found in {about_file}") @@ -572,6 +621,35 @@ def write_framework_release_files(root: Path, target_version: str) -> None: ) +def write_system_release_files(root: Path, target_version: str) -> None: + replace_first_line( + root / "iti_system" / "__about__.py", + lambda line: line.startswith("__version__ = "), + f'__version__ = "{target_version}"', + ) + replace_first_line( + root / "pyproject.toml", + lambda line: line.startswith('iti-flask = { git = "https://git.noahlan.cn/iti-framework/iTi-Flask.git", tag = "'), + f'iti-flask = {{ git = "https://git.noahlan.cn/iti-framework/iTi-Flask.git", tag = "v{target_version}" }}', + ) + replace_first_line( + root / "README.md", + lambda line: line.startswith(' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v'), + f' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v{target_version}",', + ) + replace_first_line( + root / "README.md", + lambda line: line.startswith(' "iti-system @ git+https://git.noahlan.cn/iti-framework/iTi-System.git@v'), + f' "iti-system @ git+https://git.noahlan.cn/iti-framework/iTi-System.git@v{target_version}",', + ) + replace_first_line( + root / "README.md", + lambda line: line.startswith("iticli release v"), + f"iticli release v{target_version}", + count=2, + ) + + def replace_first_line(path: Path, predicate: Any, new_line: str, count: int = 1) -> None: lines = path.read_text(encoding="utf-8").splitlines() updated = 0 diff --git a/tests/test_cli.py b/tests/test_cli.py index 499ae3f..bdf0b87 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -59,6 +59,23 @@ dependencies = [ assert ctx.has_system is True +def test_detect_system_project(tmp_path: Path) -> None: + write( + tmp_path / "pyproject.toml", + """ +[project] +name = "iti-system" +dependencies = ["iti-flask"] +""", + ) + write(tmp_path / "iti_system" / "module.py", "") + write(tmp_path / "iti_system" / "cli.py", "") + + ctx = detect_project(tmp_path) + + assert ctx.kind == "system" + + def test_detect_from_child_directory(tmp_path: Path) -> None: write( tmp_path / "pyproject.toml",