|
|
|
|
@ -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
|
|
|
|
|
|