chore: v0.2.2

main
NoahLan 1 week ago
parent 241f1d9575
commit 761446665d

@ -59,6 +59,8 @@ iTi-Flask 是 FastAPI 后端框架基座。
- 生成项目必须保留 `.copier-answers.yml`,否则不能用 `copier update` 同步模板。
- 已生成项目同步框架依赖用 `./app.sh framework-sync`
- 已生成项目检查和同步模板用 `./app.sh template-check`、`./app.sh template-update`。
- 模板项目的 Alembic 命令必须显式使用 `-c migrations/alembic.ini`
- 模板项目的测试命令使用 `uv run --extra dev pytest -q`,避免未安装 dev extra 时找不到 pytest。
## 命令

@ -45,7 +45,7 @@ scripts\iti.cmd install
```toml
dependencies = [
"iti-flask @ git+ssh://git@your-git/iTi/iTi-Flask.git@v0.2.1",
"iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v0.2.2",
]
```
@ -107,7 +107,14 @@ cd ../my-system-app
```bash
./scripts/iti.sh release
./scripts/iti.sh release v0.2.1
./scripts/iti.sh release v0.2.2
```
Windows:
```bat
scripts\iti.cmd release
scripts\iti.cmd release v0.2.2
```
## 文档

@ -4,6 +4,7 @@ __pycache__/
.hatch/
.pytest_cache/
.mypy_cache/
*.egg-info/
.coverage
htmlcov/
*.db

@ -47,10 +47,18 @@ app.cmd init-system
## 开发
```bash
./app.sh serve 8000
./app.sh serve
./app.sh test
```
不同环境直接在命令前设 `APP_ENV`,或把环境名作为 `serve` 的第一个参数:
```bash
./app.sh serve
./app.sh serve test 8000
APP_ENV=prod ./app.sh migrate
```
## 同步更新
同步框架包:
@ -86,4 +94,4 @@ app.cmd init-system
- `migrations/versions` 必须提交。
- migration message 第一个词写作者名。
- 生产只从 `main` 执行 `alembic upgrade head`。
- 生产只从 `main` 执行 `alembic -c migrations/alembic.ini upgrade head`。

@ -4,6 +4,7 @@ setlocal enabledelayedexpansion
set "SCRIPT_DIR=%~dp0"
set "ROOT_DIR=%SCRIPT_DIR%"
pushd "%ROOT_DIR%" >nul
set "UV_NO_SOURCES_PACKAGE=iti-flask"
set "PROJECT_VENV=%ROOT_DIR%.venv"
if defined VIRTUAL_ENV (
@ -50,8 +51,8 @@ echo install 安装开发依赖uv sync --extra dev
echo framework-sync 同步 iTi-Flask{% if include_system %} / iTi-System{% endif %} 依赖
echo template-check 检查 Copier 模板是否有更新
echo template-update 按 Copier 模板更新项目骨架
echo test 运行测试uv run pytest -q
echo serve [端口] 本地启动,默认 8000
echo test 运行测试uv run --extra dev pytest -q
echo serve [环境] [端口] 本地启动,默认 dev / 8000
echo migrate 执行 Alembic upgrade head
echo migration ^<说明^> 生成 migration说明建议以作者名开头
echo heads 查看 Alembic heads
@ -63,7 +64,9 @@ echo init-system system-sync + migrate + system-seed
echo.
echo 示例:
echo app.cmd install
echo app.cmd serve 8000
echo app.cmd serve
echo app.cmd serve test 8000
echo set APP_ENV=prod ^&^& app.cmd migrate
echo app.cmd migration "alice add order table"
{% if include_system %}echo app.cmd init-system
{% endif %}popd >nul
@ -90,17 +93,44 @@ uvx copier update --defaults --vcs-ref HEAD "%ROOT_DIR%"
goto end
:test
uv run pytest -q
uv run --extra dev pytest -q
goto end
:serve
set "PORT=%~1"
set "ENV_NAME=%APP_ENV%"
if not defined ENV_NAME set "ENV_NAME=%ITI_ENV%"
if not defined ENV_NAME set "ENV_NAME=dev"
set "PORT=8000"
set "ARG1=%~1"
if /I "%ARG1%"=="dev" (
set "ENV_NAME=dev"
set "PORT=%~2"
) else if /I "%ARG1%"=="test" (
set "ENV_NAME=test"
set "PORT=%~2"
) else if /I "%ARG1%"=="prod" (
set "ENV_NAME=prod"
set "PORT=%~2"
) else if /I "%ARG1%"=="default" (
set "ENV_NAME=dev"
set "PORT=%~2"
) else if not "%ARG1%"=="" (
echo(%ARG1%| findstr /r "^[0-9][0-9]*$" >nul
if errorlevel 1 (
set "ENV_NAME=%ARG1%"
set "PORT=%~2"
) else (
set "PORT=%ARG1%"
)
)
if "%PORT%"=="" set "PORT=8000"
set "APP_ENV=%ENV_NAME%"
set "ITI_ENV=%ENV_NAME%"
uv run uvicorn app:app --reload --port "%PORT%"
goto end
:migrate
uv run alembic upgrade head
uv run alembic -c migrations/alembic.ini upgrade head
goto end
:migration
@ -109,15 +139,15 @@ if "%MESSAGE%"=="" (
echo 缺少 migration 说明。示例app.cmd migration "alice add order table" 1>&2
exit /b 2
)
uv run alembic revision --autogenerate -m "%MESSAGE%"
uv run alembic -c migrations/alembic.ini revision --autogenerate -m "%MESSAGE%"
goto end
:heads
uv run alembic heads
uv run alembic -c migrations/alembic.ini heads
goto end
:current
uv run alembic current
uv run alembic -c migrations/alembic.ini current
goto end
{% if include_system %}:system_sync
@ -131,7 +161,7 @@ goto end
:init_system
uv run iti-system migrations sync --target migrations/versions
if errorlevel 1 goto end
uv run alembic upgrade head
uv run alembic -c migrations/alembic.ini upgrade head
if errorlevel 1 goto end
uv run iti-system seed system app:app
goto end
@ -141,7 +171,7 @@ uv sync --extra dev
if errorlevel 1 goto end
{% if include_system %}uv run iti-system migrations sync --target migrations/versions
if errorlevel 1 goto end
{% endif %}uv run alembic upgrade head
{% endif %}uv run alembic -c migrations/alembic.ini upgrade head
if errorlevel 1 goto end
{% if include_system %}uv run iti-system seed system app:app
{% endif %}goto end

@ -1,3 +1,5 @@
import os
from iti import create_app
{% if include_system %}from iti_system import create_system_module
@ -13,4 +15,8 @@ modules = [
]
app = create_app(config_mapping=config, modules=modules)
app = create_app(
config_name=os.getenv("APP_ENV", os.getenv("ITI_ENV", "dev")),
config_mapping=config,
modules=modules,
)

@ -3,6 +3,7 @@ set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "$ROOT_DIR"
export UV_NO_SOURCES_PACKAGE=iti-flask
PROJECT_VENV="$ROOT_DIR/.venv"
case "${VIRTUAL_ENV:-}" in
@ -26,8 +27,8 @@ show_help() {
framework-sync 同步 iTi-Flask{% if include_system %} / iTi-System{% endif %} 依赖
template-check 检查 Copier 模板是否有更新
template-update 按 Copier 模板更新项目骨架
test 运行测试uv run pytest -q
serve [端口] 本地启动,默认 8000
test 运行测试uv run --extra dev pytest -q
serve [环境] [端口] 本地启动,默认 dev / 8000
migrate 执行 Alembic upgrade head
migration <说明> 生成 migration说明建议以作者名开头
heads 查看 Alembic heads
@ -39,7 +40,9 @@ show_help() {
示例:
./app.sh install
./app.sh serve 8000
./app.sh serve
./app.sh serve test 8000
APP_ENV=prod ./app.sh migrate
./app.sh migration "alice add order table"
{% if include_system %} ./app.sh init-system
{% endif %}
@ -66,14 +69,33 @@ case "$command" in
uvx copier update --defaults --vcs-ref HEAD "$ROOT_DIR"
;;
test)
uv run pytest -q
uv run --extra dev pytest -q
;;
serve)
port=${1:-8000}
uv run uvicorn app:app --reload --port "$port"
env_name=${APP_ENV:-${ITI_ENV:-dev}}
port=8000
if [ $# -gt 0 ]; then
case "$1" in
dev|test|prod|default)
env_name=$1
port=${2:-8000}
;;
''|*[!0-9]*)
env_name=$1
port=${2:-8000}
;;
*)
port=$1
;;
esac
fi
if [ "$env_name" = default ]; then
env_name=dev
fi
APP_ENV="$env_name" ITI_ENV="$env_name" uv run uvicorn app:app --reload --port "$port"
;;
migrate)
uv run alembic upgrade head
uv run alembic -c migrations/alembic.ini upgrade head
;;
migration)
message=${1:-}
@ -81,13 +103,13 @@ case "$command" in
echo "缺少 migration 说明。示例:./app.sh migration \"alice add order table\"" >&2
exit 2
fi
uv run alembic revision --autogenerate -m "$message"
uv run alembic -c migrations/alembic.ini revision --autogenerate -m "$message"
;;
heads)
uv run alembic heads
uv run alembic -c migrations/alembic.ini heads
;;
current)
uv run alembic current
uv run alembic -c migrations/alembic.ini current
;;
{% if include_system %} system-sync)
uv run iti-system migrations sync --target migrations/versions
@ -97,13 +119,13 @@ case "$command" in
;;
init-system)
uv run iti-system migrations sync --target migrations/versions
uv run alembic upgrade head
uv run alembic -c migrations/alembic.ini upgrade head
uv run iti-system seed system app:app
;;
{% endif %} init)
uv sync --extra dev
{% if include_system %} uv run iti-system migrations sync --target migrations/versions
{% endif %} uv run alembic upgrade head
{% endif %} uv run alembic -c migrations/alembic.ini upgrade head
{% if include_system %} uv run iti-system seed system app:app
{% endif %} ;;
*)

@ -1,6 +1,12 @@
import os
from pathlib import Path
from iti.config import BaseConfig, DevConfig as BaseDevConfig, ProdConfig as BaseProdConfig
from iti.config import (
BaseConfig,
DevConfig as BaseDevConfig,
ProdConfig as BaseProdConfig,
)
BASE_DIR = Path(__file__).resolve().parent
@ -21,11 +27,21 @@ class TestConfig(BaseConfig):
app_name="{{ project_name }}",
app_env="test",
testing=True,
database_url="sqlite+pysqlite:///:memory:",
database_url=os.getenv(
"DATABASE_URL",
f"mysql+pymysql://{os.getenv('MYSQL_USER', 'root')}:"
f"{os.getenv('MYSQL_PASSWORD', 'password')}@"
f"{os.getenv('MYSQL_HOST', '127.0.0.1')}:"
f"{os.getenv('MYSQL_PORT', '3306')}/"
f"{os.getenv('MYSQL_DATABASE', 'hsyh_mes_phase2_test')}?charset=utf8mb4",
),
base_dir=BASE_DIR,
ratelimit_enabled=False,
log_file_enabled=False,
)
self.app_name = "{{ project_name }}"
self.base_dir = BASE_DIR
self.log_dir = str(BASE_DIR / "runtime" / "logs")
class ProdConfig(BaseProdConfig):

@ -3,8 +3,8 @@
本目录是业务项目唯一的 Alembic migration 流。
```bash
uv run alembic revision --autogenerate -m "alice add example table"
uv run alembic upgrade head
uv run alembic -c migrations/alembic.ini revision --autogenerate -m "alice add example table"
uv run alembic -c migrations/alembic.ini upgrade head
```
`versions/` 下的 migration 文件必须提交到 Git。

@ -1,11 +1,17 @@
from __future__ import annotations
import os
import sys
from logging.config import fileConfig
from pathlib import Path
from alembic import context
from sqlalchemy import engine_from_config, pool
ROOT_DIR = Path(__file__).resolve().parents[1]
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from config import config as app_config
from iti.db import Base
from iti.exchange import models as _exchange_models

@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "{{ project_slug | replace('_', '-') }}"
version = "0.2.0"
version = "0.2.2"
description = "{{ project_name }}"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"iti-flask @ {{ framework_git }}{% if framework_tag %}@{{ framework_tag }}{% endif %}",
{% if include_system %} "iti-system @ {{ system_git }}{% if system_tag %}@{{ system_tag }}{% endif %}",
"iti-flask @ git+{{ framework_git }}{% if framework_tag %}@{{ framework_tag }}{% endif %}",
{% if include_system %} "iti-system @ git+{{ system_git }}{% if system_tag %}@{{ system_tag }}{% endif %}",
{% endif -%}
]
@ -25,3 +25,8 @@ include = ["{{ project_slug }}*"]
[tool.pytest.ini_options]
pythonpath = ["."]
[tool.uv.sources]
iti-flask = { git = "{{ framework_git }}"{% if framework_tag %}, tag = "{{ framework_tag }}"{% endif %} }
{% if include_system %}iti-system = { git = "{{ system_git }}"{% if system_tag %}, tag = "{{ system_tag }}"{% endif %} }
{% endif -%}

@ -21,12 +21,12 @@ project_slug:
framework_git:
type: str
help: iTi-Flask Git 地址
default: git+ssh://git@your-git/iTi/iTi-Flask.git
default: https://git.noahlan.cn/iti-framework/iTi-Flask.git
framework_tag:
type: str
help: iTi-Flask Git tag
default: v0.2.1
default: v0.2.2
include_system:
type: bool
@ -36,9 +36,9 @@ include_system:
system_git:
type: str
help: iTi-System Git 地址
default: git+ssh://git@your-git/iTi/iTi-System.git
default: https://git.noahlan.cn/iti-framework/iTi-System.git
system_tag:
type: str
help: iTi-System Git tag
default: v0.2.0
default: v0.2.2

@ -98,6 +98,8 @@ app.cmd init-system
./app.sh migrate
```
`app.sh` / `app.cmd` 内部使用项目自己的 `migrations/alembic.ini`
`iti-system` 的项目还会有:
```bash

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2025-present NoahLan <6995syu@163.com>
#
# SPDX-License-Identifier: MIT
__version__ = "0.2.1"
__version__ = "0.2.2"

@ -111,10 +111,19 @@ uv run alembic current
goto end
:release
set "SHELL_CMD="
where sh >nul 2>nul && set "SHELL_CMD=sh"
if not defined SHELL_CMD (
where bash >nul 2>nul && set "SHELL_CMD=bash"
)
if not defined SHELL_CMD (
echo 找不到 sh 或 bash无法运行 release 脚本 1>&2
exit /b 1
)
if "%~1"=="" (
uv run python scripts/release.py
"%SHELL_CMD%" scripts/release.sh
) else (
uv run python scripts/release.py %*
"%SHELL_CMD%" scripts/release.sh %*
)
goto end

@ -86,7 +86,7 @@ case "$command" in
uv run alembic current
;;
release)
uv run python scripts/release.py "$@"
sh scripts/release.sh "$@"
;;
make-app|make-system-app)
target=${1:-}

@ -1,146 +0,0 @@
#!/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())

@ -0,0 +1,202 @@
#!/usr/bin/env sh
set -eu
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT_DIR"
ABOUT_FILE="$ROOT_DIR/iti/__about__.py"
COPIER_FILE="$ROOT_DIR/copier.yml"
README_FILE="$ROOT_DIR/README.md"
die() {
printf '%s\n' "$*" >&2
exit 1
}
parse_version() {
raw=$1
version=${raw#v}
old_ifs=$IFS
IFS=.
set -- $version
IFS=$old_ifs
if [ "$#" -ne 3 ]; then
die "invalid version: $raw"
fi
for part in "$@"; do
case $part in
''|*[!0-9]*)
die "invalid version: $raw"
;;
esac
done
printf '%s %s %s\n' "$1" "$2" "$3"
}
normalize_version() {
set -- $(parse_version "$1")
printf '%s.%s.%s\n' "$1" "$2" "$3"
}
bump_patch() {
set -- $(parse_version "$1")
printf '%s.%s.%s\n' "$1" "$2" "$(( $3 + 1 ))"
}
version_is_newer() {
current_parts=$(parse_version "$1")
set -- $current_parts
current_major=$1
current_minor=$2
current_patch=$3
target_parts=$(parse_version "$2")
set -- $target_parts
target_major=$1
target_minor=$2
target_patch=$3
if [ "$target_major" -gt "$current_major" ]; then
return 0
fi
if [ "$target_major" -lt "$current_major" ]; then
return 1
fi
if [ "$target_minor" -gt "$current_minor" ]; then
return 0
fi
if [ "$target_minor" -lt "$current_minor" ]; then
return 1
fi
[ "$target_patch" -gt "$current_patch" ]
}
read_current_version() {
current_version=$(sed -n 's/^__version__[[:space:]]*=[[:space:]]*"\([^"]*\)".*$/\1/p' "$ABOUT_FILE" | sed -n '1p')
[ -n "$current_version" ] || die "version not found in $ABOUT_FILE"
printf '%s\n' "$current_version"
}
replace_first_exact_line() {
file=$1
old_line=$2
new_line=$3
tmp_file=$(mktemp)
if awk -v old="$old_line" -v new="$new_line" '
BEGIN { done = 0 }
{
if (!done && $0 == old) {
print new
done = 1
} else {
print
}
}
END { if (!done) exit 1 }
' "$file" >"$tmp_file"; then
mv "$tmp_file" "$file"
else
rm -f "$tmp_file"
die "line not updated in $file"
fi
}
write_about_version() {
current_version=$1
target_version=$2
replace_first_exact_line "$ABOUT_FILE" "__version__ = \"$current_version\"" "__version__ = \"$target_version\""
}
write_copier_tag() {
current_version=$1
target_version=$2
tmp_file=$(mktemp)
if awk -v current="$current_version" -v target="$target_version" '
BEGIN { in_framework = 0; done = 0 }
/^framework_tag:[[:space:]]*$/ { in_framework = 1; print; next }
in_framework && /^[^[:space:]]/ { in_framework = 0 }
in_framework && !done && $0 == " default: v" current {
print " default: v" target
done = 1
next
}
{ print }
END { if (!done) exit 1 }
' "$COPIER_FILE" >"$tmp_file"; then
mv "$tmp_file" "$COPIER_FILE"
else
rm -f "$tmp_file"
die "framework_tag default not updated in $COPIER_FILE"
fi
}
write_readme_tag() {
current_version=$1
target_version=$2
old_line=' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v'"$current_version"'",'
new_line=' "iti-flask @ git+https://git.noahlan.cn/iti-framework/iTi-Flask.git@v'"$target_version"'",'
replace_first_exact_line "$README_FILE" "$old_line" "$new_line"
}
ensure_clean_tree() {
if [ -n "$(git status --porcelain)" ]; then
die "working tree is not clean"
fi
}
ensure_branch() {
if branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null); then
[ -n "$branch" ] || die "release requires a branch checkout"
printf '%s\n' "$branch"
else
die "release requires a branch checkout"
fi
}
ensure_tag_absent() {
tag=$1
if git rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then
die "tag already exists: $tag"
fi
}
main() {
version_arg=${1:-}
current_version=$(read_current_version)
if [ -n "$version_arg" ]; then
target_version=$(normalize_version "$version_arg")
else
target_version=$(bump_patch "$current_version")
fi
target_tag="v$target_version"
if [ "$target_version" = "$current_version" ]; then
die "version already set to $target_version"
fi
if ! version_is_newer "$current_version" "$target_version"; then
die "target version must be newer than $current_version"
fi
ensure_clean_tree
branch=$(ensure_branch)
ensure_tag_absent "$target_tag"
uv run pytest -q
write_about_version "$current_version" "$target_version"
write_copier_tag "$current_version" "$target_version"
write_readme_tag "$current_version" "$target_version"
git add iti/__about__.py copier.yml README.md
git commit -m "chore: release $target_tag"
git tag -a "$target_tag" -m "release $target_tag"
git push origin "$branch" "$target_tag"
printf 'released %s\n' "$target_tag"
}
main "$@"
Loading…
Cancel
Save