chore: 一键发布。

main
NoahLan 2 weeks ago
parent 1525f35036
commit 6aec02acb6

@ -103,6 +103,13 @@ cd ../my-system-app
./app.sh help ./app.sh help
``` ```
发布框架:
```bash
./scripts/iti.sh release
./scripts/iti.sh release v0.2.1
```
## 文档 ## 文档
- [文档索引](docs/README.md) - [文档索引](docs/README.md)

@ -21,4 +21,5 @@ AI 修改框架时优先读 `.codex/skills/iti-flask-framework/SKILL.md`。
./scripts/iti.sh check ./scripts/iti.sh check
./scripts/iti.sh make-app ../my-business-app my_business_app ./scripts/iti.sh make-app ../my-business-app my_business_app
./scripts/iti.sh make-system-app ../my-system-app my_system_app ./scripts/iti.sh make-system-app ../my-system-app my_system_app
./scripts/iti.sh release
``` ```

@ -28,6 +28,7 @@ if "%COMMAND%"=="migrate" goto migrate
if "%COMMAND%"=="migration" goto migration if "%COMMAND%"=="migration" goto migration
if "%COMMAND%"=="heads" goto heads if "%COMMAND%"=="heads" goto heads
if "%COMMAND%"=="current" goto current if "%COMMAND%"=="current" goto current
if "%COMMAND%"=="release" goto release
if "%COMMAND%"=="make-app" goto make_app if "%COMMAND%"=="make-app" goto make_app
if "%COMMAND%"=="make-system-app" goto make_system_app if "%COMMAND%"=="make-system-app" goto make_system_app
@ -53,6 +54,7 @@ echo heads 查看 Alembic heads
echo current 查看当前 Alembic 版本 echo current 查看当前 Alembic 版本
echo make-app ^<目录^> [包名] 从当前框架仓库模板生成业务项目 echo make-app ^<目录^> [包名] 从当前框架仓库模板生成业务项目
echo make-system-app ^<目录^> [包名] 生成带 iti-system 的业务项目 echo make-system-app ^<目录^> [包名] 生成带 iti-system 的业务项目
echo release [版本] 发布框架:测试、改版本、提交、打 tag、推送
echo. echo.
echo 示例: echo 示例:
echo scripts\iti.cmd install echo scripts\iti.cmd install
@ -108,6 +110,14 @@ goto end
uv run alembic current uv run alembic current
goto end goto end
:release
if "%~1"=="" (
uv run python scripts/release.py
) else (
uv run python scripts/release.py %*
)
goto end
:make_app :make_app
set "INCLUDE_SYSTEM=false" set "INCLUDE_SYSTEM=false"
goto make_project goto make_project

@ -32,6 +32,7 @@ iTi-Flask 开发脚本
current 查看当前 Alembic 版本 current 查看当前 Alembic 版本
make-app <目录> [包名] 从当前框架仓库模板生成业务项目 make-app <目录> [包名] 从当前框架仓库模板生成业务项目
make-system-app <目录> [包名] 生成带 iti-system 的业务项目 make-system-app <目录> [包名] 生成带 iti-system 的业务项目
release [版本] 发布框架:测试、改版本、提交、打 tag、推送
示例: 示例:
./scripts/iti.sh install ./scripts/iti.sh install
@ -84,6 +85,9 @@ case "$command" in
current) current)
uv run alembic current uv run alembic current
;; ;;
release)
uv run python scripts/release.py "$@"
;;
make-app|make-system-app) make-app|make-system-app)
target=${1:-} target=${1:-}
package=${2:-} package=${2:-}

@ -0,0 +1,146 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ABOUT_FILE = ROOT / "iti" / "__about__.py"
COPIER_FILE = ROOT / "copier.yml"
README_FILE = ROOT / "README.md"
VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
ABOUT_VERSION_RE = re.compile(r'(__version__\s*=\s*")([^"]+)(")')
README_DEP_PREFIX = r"(iti-flask @ git\+ssh://git@your-git/iTi/iTi-Flask\.git@)"
def run(cmd: list[str], *, capture_output: bool = False) -> subprocess.CompletedProcess[str]:
return subprocess.run(
cmd,
cwd=ROOT,
text=True,
capture_output=capture_output,
check=True,
)
def normalize_version(value: str) -> str:
version = value[1:] if value.startswith("v") else value
if not VERSION_RE.fullmatch(version):
raise SystemExit(f"invalid version: {value}")
return version
def version_tuple(version: str) -> tuple[int, int, int]:
major, minor, patch = version.split(".")
return int(major), int(minor), int(patch)
def bump_patch(version: str) -> str:
major, minor, patch = version_tuple(version)
return f"{major}.{minor}.{patch + 1}"
def read_current_version() -> str:
text = ABOUT_FILE.read_text(encoding="utf-8")
match = ABOUT_VERSION_RE.search(text)
if match is None:
raise SystemExit(f"version not found in {ABOUT_FILE}")
return match.group(2)
def write_about_version(version: str) -> None:
text = ABOUT_FILE.read_text(encoding="utf-8")
new_text, count = ABOUT_VERSION_RE.subn(rf"\g<1>{version}\g<3>", text, count=1)
if count != 1:
raise SystemExit(f"version line not updated in {ABOUT_FILE}")
ABOUT_FILE.write_text(new_text, encoding="utf-8")
def write_copier_tag(current_tag: str, new_tag: str) -> None:
text = COPIER_FILE.read_text(encoding="utf-8")
pattern = re.compile(r"(framework_tag:\n(?: .*\n)*? default: )" + re.escape(current_tag))
new_text, count = pattern.subn(rf"\g<1>{new_tag}", text, count=1)
if count != 1:
raise SystemExit(f"framework_tag default not updated in {COPIER_FILE}")
COPIER_FILE.write_text(new_text, encoding="utf-8")
def write_readme_tag(current_tag: str, new_tag: str) -> None:
text = README_FILE.read_text(encoding="utf-8")
pattern = re.compile(README_DEP_PREFIX + re.escape(current_tag))
new_text, count = pattern.subn(rf"\g<1>{new_tag}", text, count=1)
if count != 1:
raise SystemExit(f"framework dependency example not updated in {README_FILE}")
README_FILE.write_text(new_text, encoding="utf-8")
def ensure_clean_tree() -> None:
result = run(["git", "status", "--porcelain"], capture_output=True)
if result.stdout.strip():
raise SystemExit("working tree is not clean")
def ensure_branch() -> str:
result = run(["git", "symbolic-ref", "--quiet", "--short", "HEAD"], capture_output=True)
branch = result.stdout.strip()
if not branch:
raise SystemExit("release requires a branch checkout")
return branch
def ensure_tag_absent(tag: str) -> None:
result = subprocess.run(
["git", "rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"],
cwd=ROOT,
text=True,
capture_output=True,
)
if result.returncode == 0:
raise SystemExit(f"tag already exists: {tag}")
def main() -> int:
parser = argparse.ArgumentParser(description="Release the iTi-Flask framework")
parser.add_argument(
"version",
nargs="?",
help="target version, for example 0.2.1 or v0.2.1; defaults to patch bump",
)
args = parser.parse_args()
current_version = read_current_version()
target_version = normalize_version(args.version) if args.version else bump_patch(current_version)
target_tag = f"v{target_version}"
current_tag = f"v{current_version}"
if target_version == current_version:
raise SystemExit(f"version already set to {target_version}")
if version_tuple(target_version) <= version_tuple(current_version):
raise SystemExit(f"target version must be newer than {current_version}")
ensure_clean_tree()
ensure_branch()
ensure_tag_absent(target_tag)
run(["uv", "run", "pytest", "-q"])
write_about_version(target_version)
write_copier_tag(current_tag, target_tag)
write_readme_tag(current_tag, target_tag)
run(["git", "add", "iti/__about__.py", "copier.yml", "README.md"])
run(["git", "commit", "-m", f"chore: release {target_tag}"])
run(["git", "tag", "-a", target_tag, "-m", f"release {target_tag}"])
branch = ensure_branch()
run(["git", "push", "origin", branch, target_tag])
print(f"released {target_tag}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Loading…
Cancel
Save