From 9b675cd77b426dd3f22d76a081d64ea7ba9d4365 Mon Sep 17 00:00:00 2001 From: ShawnDeng-code Date: Sat, 26 Jul 2025 17:35:23 +0800 Subject: [PATCH 1/2] feat: update template --- repo_scaffold/cli.py | 9 +- .../template-python/cookiecutter.json | 18 +- .../template-python/hooks/post_gen_project.py | 195 +++++++++++++----- .../.github/workflows/bump_version.yaml | 25 --- .../.github/workflows/ci-tests.yaml | 66 ++++++ .../.github/workflows/container-release.yaml | 37 ++++ .../.github/workflows/docker_release.yaml | 51 ----- .../{deploy_docs.yaml => docs-deploy.yaml} | 12 +- .../.github/workflows/lint_and_unittest.yaml | 27 --- .../.github/workflows/package-release.yaml | 92 +++++++++ .../.github/workflows/release_build.yaml | 37 ---- .../.github/workflows/version-bump.yaml | 38 ++++ .../{{cookiecutter.project_slug}}/.gitignore | 3 +- .../.vscode/settings.json | 3 + .../Taskfile.yml | 104 +++++++--- .../container/Dockerfile | 26 +++ .../container/compose.yaml | 13 ++ .../docker/Dockerfile | 1 - 18 files changed, 510 insertions(+), 247 deletions(-) delete mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/ci-tests.yaml create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/container-release.yaml delete mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docker_release.yaml rename repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/{deploy_docs.yaml => docs-deploy.yaml} (51%) delete mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint_and_unittest.yaml create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/package-release.yaml delete mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/version-bump.yaml create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.vscode/settings.json create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/Dockerfile create mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/compose.yaml delete mode 100644 repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docker/Dockerfile diff --git a/repo_scaffold/cli.py b/repo_scaffold/cli.py index 1f630ad..19f5db4 100644 --- a/repo_scaffold/cli.py +++ b/repo_scaffold/cli.py @@ -120,7 +120,12 @@ def list(): type=click.Path(file_okay=False, dir_okay=True, path_type=Path), help="Directory where the project will be created", ) -def create(template: str, output_dir: Path): +@click.option( + "--no-input", + is_flag=True, + help="Do not prompt for parameters and only use cookiecutter.json file content", +) +def create(template: str, output_dir: Path, no_input: bool): # noqa: D417 """Create a new project from a template. Creates a new project based on the specified template. If no template is specified, @@ -176,7 +181,7 @@ def create(template: str, output_dir: Path): cookiecutter( template=template_path, output_dir=str(output_dir), - no_input=False, # 启用交互式输入,让 cookiecutter 处理所有选项 + no_input=no_input, # 根据用户选择决定是否启用交互式输入 ) diff --git a/repo_scaffold/templates/template-python/cookiecutter.json b/repo_scaffold/templates/template-python/cookiecutter.json index cc338ba..d795ad8 100644 --- a/repo_scaffold/templates/template-python/cookiecutter.json +++ b/repo_scaffold/templates/template-python/cookiecutter.json @@ -5,22 +5,18 @@ "github_username": "ShawnDen-coder", "project_slug": "{{ cookiecutter.repo_name.strip().lower().replace('-', '_') }}", "description": "A short description of the project.", - "min_python_version": ["3.9", "3.10", "3.11", "3.12", "3.13","3.14"], - "max_python_version": ["3.9", "3.10", "3.11", "3.12", "3.13","3.14"], - "use_docker": ["yes", "no"], + "min_python_version": ["3.10", "3.9", "3.11", "3.12", "3.13","3.14"], + "max_python_version": ["3.12", "3.9", "3.10", "3.11", "3.13","3.14"], + "use_podman": ["yes", "no"], "include_cli": ["yes", "no"], - "use_github_actions": ["yes", "no"], + "use_github_actions": "yes", + "pypi_server_url": "https://pypiserver.shawndeng.cc/simple/", "__prompts__": { "repo_name": "项目名称 (my-awesome-project)", - "full_name": "你的全名", - "email": "你的邮箱地址", - "github_username": "你的 GitHub 用户名", - "project_slug": "Python 包名 (用于 import)", "description": "项目简短描述", "min_python_version": "最低支持的 Python 版本 (这将影响测试范围和依赖兼容性)", "max_python_version": "最高支持的 Python 版本", - "use_docker": "是否添加 Docker 支持?", - "include_cli": "是否添加命令行界面?", - "use_github_actions": "是否添加 GitHub Actions 支持?" + "use_podman": "是否添加 Podman 支持?", + "include_cli": "是否添加命令行界面?" } } diff --git a/repo_scaffold/templates/template-python/hooks/post_gen_project.py b/repo_scaffold/templates/template-python/hooks/post_gen_project.py index d0ba07e..93898c4 100644 --- a/repo_scaffold/templates/template-python/hooks/post_gen_project.py +++ b/repo_scaffold/templates/template-python/hooks/post_gen_project.py @@ -1,64 +1,147 @@ +"""Post-generation project setup and cleanup script. + +This script runs after cookiecutter generates the project template. +It removes unnecessary files based on user choices and initializes the project. +""" + import os import shutil import subprocess +import sys +from pathlib import Path +from typing import List, Union + + +class ProjectCleaner: + """Handles removal of unnecessary files and directories based on cookiecutter choices.""" + + def __init__(self): + self.project_slug = "{{cookiecutter.project_slug}}" + self.use_cli = "{{cookiecutter.include_cli}}" == "yes" + self.use_github_actions = "{{cookiecutter.use_github_actions}}" == "yes" + self.use_podman = "{{cookiecutter.use_podman}}" == "yes" + + def _safe_remove(self, path: Union[str, Path]) -> bool: + """Safely remove a file or directory. + + Args: + path: Path to remove + + Returns: + bool: True if removed successfully, False otherwise + """ + try: + path = Path(path) + if not path.exists(): + return False + + if path.is_file(): + path.unlink() + print(f"Removed file: {path}") + elif path.is_dir(): + shutil.rmtree(path) + print(f"Removed directory: {path}") + return True + except Exception as e: + print(f"Warning: Failed to remove {path}: {e}") + return False + + def _remove_files(self, files: List[Union[str, Path]]) -> None: + """Remove multiple files or directories. + + Args: + files: List of file/directory paths to remove + """ + for file_path in files: + self._safe_remove(file_path) + + def clean_cli_files(self) -> None: + """Remove CLI related files if CLI is not needed.""" + if self.use_cli: + return + + cli_files = [ + Path(self.project_slug) / "cli.py" + ] + print("Removing CLI files...") + self._remove_files(cli_files) + + def clean_container_files(self) -> None: + """Remove container related files if Podman is not used.""" + if self.use_podman: + return + + container_files = [ + ".dockerignore", + "container", + Path(".github") / "workflows" / "container-release.yaml" + ] + print("Removing container files...") + self._remove_files(container_files) + + def clean_github_actions_files(self) -> None: + """Remove GitHub Actions and documentation files if not needed.""" + if self.use_github_actions: + return + + github_files = [ + ".github", + "mkdocs.yml", + "docs" + ] + print("Removing GitHub Actions and documentation files...") + self._remove_files(github_files) + + +class ProjectInitializer: + """Handles project initialization tasks.""" + + def __init__(self): + self.project_slug = "{{cookiecutter.project_slug}}" + + def setup_environment(self) -> None: + """Initialize project dependencies and environment.""" + project_dir = Path(self.project_slug).resolve() + + if not project_dir.exists(): + print(f"Error: Project directory {project_dir} does not exist") + sys.exit(1) + + print(f"Changing to project directory: {project_dir}") + os.chdir(project_dir) + + try: + print("Installing project dependencies...") + subprocess.run(["task", "init"], check=True) + print("✅ Dependencies installed successfully") + except subprocess.CalledProcessError as e: + print(f"❌ Failed to install dependencies: {e}") + sys.exit(1) + except FileNotFoundError: + print("❌ uv not found. Please install uv first: https://docs.astral.sh/uv/") + sys.exit(1) + + +def main() -> None: + """Main execution function.""" + print("🚀 Starting post-generation project setup...") + + # Initialize cleaner and perform cleanup + cleaner = ProjectCleaner() + + print("\n📁 Cleaning up unnecessary files...") + cleaner.clean_cli_files() + cleaner.clean_container_files() + cleaner.clean_github_actions_files() + # Initialize project + print("\n🔧 Initializing project...") + initializer = ProjectInitializer() + initializer.setup_environment() -def remove_cli(): - """Remove CLI related files if not needed.""" - cli_file = os.path.join("{{cookiecutter.project_slug}}", "cli.py") - if os.path.exists(cli_file): - os.remove(cli_file) - -def remove_docker(): - """Remove GitHub Actions configuration if not needed.""" - file_name = [".dockerignore", "docker", ".github/workflows/docker_release.yaml"] - if "{{cookiecutter.use_github_actions}}" == "no": - for item in file_name: - if os.path.exists(item): - if os.path.isfile(item): - os.remove(item) - elif os.path.isdir(item): - shutil.rmtree(item) - - -def remove_github_actions(): - """Remove GitHub Actions configuration if not needed.""" - if "{{cookiecutter.use_github_actions}}" == "no": - github_dir = os.path.join(".github") - if os.path.exists(github_dir): - shutil.rmtree(github_dir) - - -def remove_docs(): - """Remove documentation related files if GitHub Actions is not used.""" - if "{{cookiecutter.use_github_actions}}" == "no": - # 删除 mkdocs.yml - if os.path.exists("mkdocs.yml"): - os.remove("mkdocs.yml") - # 删除 docs 目录 - docs_dir = "docs" - if os.path.exists(docs_dir): - shutil.rmtree(docs_dir) - - -def init_project_depends(): - """Initialize project dependencies using uv.""" - project_dir = os.path.abspath("{{cookiecutter.project_slug}}") - os.chdir(project_dir) - - # 安装基础开发依赖 - subprocess.run(["uv", "sync"], check=True) + print("\n✨ Project setup completed successfully!") + print(f"📂 Your project is ready at: {{cookiecutter.project_slug}}") if __name__ == "__main__": - if "{{cookiecutter.include_cli}}" == "no": - remove_cli() - - if "{{cookiecutter.use_github_actions}}" == "no": - remove_github_actions() - remove_docs() - - if "{{cookiecutter.use_docker}}" == "no": - remove_docker() - - init_project_depends() \ No newline at end of file + main() \ No newline at end of file diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml deleted file mode 100644 index 6dd8370..0000000 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/bump_version.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Bump version -on: - push: - branches: - - master - - main -permissions: - contents: write # 用于创建和推送标签 - pull-requests: write # 用于创建 PR -jobs: - bump-version: - if: {% raw %}${{ !startsWith(github.event.head_commit.message, 'bump:') }}{% endraw %} - runs-on: ubuntu-latest - name: Bump version and create changelog with commitizen - steps: - - name: Check out - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: {% raw %}${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} - - name: Create bump and changelog - uses: commitizen-tools/commitizen-action@master - with: - github_token: {% raw %}${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} - branch: master diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/ci-tests.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/ci-tests.yaml new file mode 100644 index 0000000..36b4a38 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/ci-tests.yaml @@ -0,0 +1,66 @@ +name: ✅ Code Quality & Tests +on: + push: + branches: + - main + - master + pull_request: + workflow_dispatch: +permissions: + contents: read +env: + {% raw %}UV_INDEX_HOMELAB_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + UV_INDEX_HOMELAB_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }} + PYPI_SERVER_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + PYPI_SERVER_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }}{% endraw %} +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Install dependencies + run: task init + - name: Run lint checks + id: lint + run: task lint + - name: Run test all versions + run: task test:all +{%- if cookiecutter.use_podman == 'yes' %} + + container-build-test: + name: 🐳 Multi-platform Container Build Test + needs: check + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Check if Dockerfile exists + id: dockerfile-check + run: | + if [ -f "./container/Dockerfile" ]; then + echo "dockerfile-exists=true" >> $GITHUB_OUTPUT + else + echo "dockerfile-exists=false" >> $GITHUB_OUTPUT + echo "Dockerfile not found, skipping container build test" + fi + - name: Set up QEMU + if: steps.dockerfile-check.outputs.dockerfile-exists == 'true' + uses: docker/setup-qemu-action@v3 + - name: Test Build Container Image (no push) + if: steps.dockerfile-check.outputs.dockerfile-exists == 'true' + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + {% raw %}image: ${{ github.repository }}-test + tags: test-${{ github.sha }}{% endraw %} + containerfiles: ./container/Dockerfile + platforms: linux/amd64,linux/arm64 + build-args: |- + {% raw %}PYPI_SERVER_USERNAME=${{ env.PYPI_SERVER_USERNAME }} + PYPI_SERVER_PASSWORD=${{ env.PYPI_SERVER_PASSWORD }}{% endraw %} +{%- endif %} diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/container-release.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/container-release.yaml new file mode 100644 index 0000000..3bf5d22 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/container-release.yaml @@ -0,0 +1,37 @@ +name: 🐳 Docker Release +on: + release: + types: [published] + workflow_dispatch: +jobs: + push_to_registry: + name: Push Container image + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Login to GitHub Container Registry + uses: redhat-actions/podman-login@v1 + with: + registry: ghcr.io + {% raw %}username: ${{ github.actor }} + password: ${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} + - name: Build Image + id: build-image + uses: redhat-actions/buildah-build@v2 + with: + {% raw %}image: ${{ github.repository }} + tags: latest ${{ github.sha }} ${{ github.ref_name }}{% endraw %} + containerfiles: ./container/Dockerfile + platforms: linux/amd64,linux/arm64 + build-args: | + {% raw %}PYPI_SERVER_USERNAME=${{ secrets.PYPI_SERVER_USERNAME }} + PYPI_SERVER_PASSWORD=${{ secrets.PYPI_SERVER_PASSWORD }}{% endraw %} + - name: Push To Registry + uses: redhat-actions/push-to-registry@v2 + with: + {% raw %}image: ${{ steps.build-image.outputs.image }} + tags: ${{ steps.build-image.outputs.tags }}{% endraw %} + registry: ghcr.io diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docker_release.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docker_release.yaml deleted file mode 100644 index 82fa11b..0000000 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docker_release.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: docker release -on: - release: - types: [published] - workflow_dispatch: -env: - DOCKERHUB_IMAGE_NAME: shawndengdocker/homelab_airflow_dags - GITHUB_IMAGE_NAME: ghcr.io/shawnden-coder/homelab_airflow_dags -jobs: - push_to_registry: - name: Push Docker image to Docker Hub and GHCR - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v4 - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: {% raw %}${{ secrets.DOCKERHUB_USERNAME }}{% endraw %} - password: {% raw %}${{ secrets.DOCKERHUB_TOKEN }}{% endraw %} - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: {% raw %}${{ github.actor }}{% endraw %} - password: {% raw %}${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - {% raw %}${{ env.DOCKERHUB_IMAGE_NAME }}{% endraw %} - {% raw %}${{ env.GITHUB_IMAGE_NAME }}{% endraw %} - tags: | - {% raw %}type=raw,value=latest,enable={{is_default_branch}}{% endraw %} - type=ref,event=tag - type=ref,event=pr - type=sha,format=short - - name: Build and push Docker images - uses: docker/build-push-action@v6 - with: - context: . - file: ./docker/Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: {% raw %}${{ steps.meta.outputs.tags }}{% endraw %} - labels: {% raw %}${{ steps.meta.outputs.labels }}{% endraw %} diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docs-deploy.yaml similarity index 51% rename from repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml rename to repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docs-deploy.yaml index 392402e..d6daef3 100644 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/deploy_docs.yaml +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/docs-deploy.yaml @@ -1,11 +1,11 @@ -name: Deploy Docs +name: 📚 Deploy Documentation on: push: tags: - - '[0-9]+.[0-9]+.[0-9]+' # 匹配类似 1.0.0, 2.1.3 等格式的标签 + - '[0-9]+.[0-9]+.[0-9]+' # 匹配类似 1.0.0, 2.1.3 等格式的标签 workflow_dispatch: permissions: - contents: write # 用于部署到 GitHub Pages + contents: write # 用于部署到 GitHub Pages jobs: deploy: runs-on: ubuntu-latest @@ -17,6 +17,8 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 - name: Build and deploy documentation - run: task deploy:gh-pages env: - GITHUB_TOKEN: {% raw %}${{ github.token }}{% endraw %} + {% raw %}GITHUB_TOKEN: ${{ github.token }} + UV_INDEX_HOMELAB_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + UV_INDEX_HOMELAB_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }}{% endraw %} + run: task deploy:gh-pages diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint_and_unittest.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint_and_unittest.yaml deleted file mode 100644 index 5bc130c..0000000 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/lint_and_unittest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: lint_and_unittest -on: - push: - branches: - - main - - master - pull_request: - workflow_dispatch: -permissions: - contents: read -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Task - uses: arduino/setup-task@v2 - - name: Install uv - uses: astral-sh/setup-uv@v5 - - name: Install dependencies - run: task init - - name: Run lint checks - id: lint - run: task lint - - name: Run test all versions - run: task test:all diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/package-release.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/package-release.yaml new file mode 100644 index 0000000..08aa0e7 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/package-release.yaml @@ -0,0 +1,92 @@ +name: 🚀 Release Build & Publish +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' # 匹配类似 1.0.0, 2.1.3 等格式的标签 +permissions: + contents: write # 用于创建 release + id-token: write # 用于发布到 PyPI + +jobs: + test: + runs-on: ubuntu-latest + env: + {% raw %}UV_INDEX_HOMELAB_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + UV_INDEX_HOMELAB_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }} + PYPI_SERVER_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + PYPI_SERVER_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }}{% endraw %} + steps: + - uses: actions/checkout@v4 + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: init environment and test + run: | + task init # 初始化项目环境 + task lint # 运行代码检查 + - name: Run tests + run: task test:all # 运行所有测试 + + publish-pypi: + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Build package + run: task build + - name: Publish to PyPI + env: + {% raw %}UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}{% endraw %} + run: task publish:pypi + + publish-private-pypi: + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + env: + {% raw %}UV_INDEX_HOMELAB_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + UV_INDEX_HOMELAB_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }} + PYPI_SERVER_USERNAME: ${{ secrets.PYPI_SERVER_USERNAME }} + PYPI_SERVER_PASSWORD: ${{ secrets.PYPI_SERVER_PASSWORD }}{% endraw %} + steps: + - uses: actions/checkout@v4 + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Build and publish to PyPI Server + run: task deploy:pypi-server + + release: + needs: [test, publish-pypi, publish-private-pypi] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Install Task + uses: arduino/setup-task@v2 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Build package for release + run: task build + - name: Release + uses: softprops/action-gh-release@v2 + with: + {% raw %}tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }}{% endraw %} + files: | + dist/*.tar.gz + dist/*.whl + generate_release_notes: true + env: + {% raw %}GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml deleted file mode 100644 index 083f64f..0000000 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/release_build.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: release-build -on: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' # 匹配类似 1.0.0, 2.1.3 等格式的标签 -permissions: - contents: write # 用于创建 release - id-token: write # 用于发布到 PyPI -jobs: - release-build: - runs-on: ubuntu-latest - permissions: - contents: write # 用于创建 GitHub Release - steps: - - uses: actions/checkout@v4 - - name: Install Task - uses: arduino/setup-task@v2 - - name: Install uv - uses: astral-sh/setup-uv@v5 - - name: init environment and test - run: | - task init # 初始化项目环境 - task lint # 运行代码检查 - task test:all # 运行所有测试 - - name: Publish to PyPI - env: - UV_PUBLISH_TOKEN: {% raw %}${{ secrets.PYPI_TOKEN }}{% endraw %} - run: task deploy:pypi - - name: Release - uses: softprops/action-gh-release@v2 - with: - files: | - dist/*.tar.gz - dist/*.whl - generate_release_notes: true - env: - GITHUB_TOKEN: {% raw %}${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/version-bump.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/version-bump.yaml new file mode 100644 index 0000000..cd2f5c1 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.github/workflows/version-bump.yaml @@ -0,0 +1,38 @@ +name: 🔄 Auto Version Bump +on: + push: + branches: + - master + - main + workflow_dispatch: + inputs: + increment: + description: 'Version increment type' + required: false + default: 'MAJOR' + type: choice + options: + - 'MAJOR' + - 'MINOR' + - 'PATCH' +permissions: + contents: write # 用于创建和推送标签 + pull-requests: write # 用于创建 PR +jobs: + bump-version: + {% raw %}if: ${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'bump:') }}{% endraw %} + runs-on: ubuntu-latest + name: Bump version and create changelog with commitizen + steps: + - name: Check out + uses: actions/checkout@v4 + with: + fetch-depth: 0 + {% raw %}token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}{% endraw %} + - name: Create bump and changelog + uses: commitizen-tools/commitizen-action@master + with: + {% raw %}github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + branch: master + increment: ${{ github.event.inputs.increment || '' }}{% endraw %} + no_raise: '21' diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore index 012cb2f..f3bc223 100644 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.gitignore @@ -10,4 +10,5 @@ **/__pycache__/ *.pyc # MkDocs build output -/site/ \ No newline at end of file +/site/ +.env \ No newline at end of file diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.vscode/settings.json b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.vscode/settings.json new file mode 100644 index 0000000..ebfefd4 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.envFile": "${workspaceFolder}/.env", +} \ No newline at end of file diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/Taskfile.yml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/Taskfile.yml index 89fe876..76b0ce6 100644 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/Taskfile.yml +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/Taskfile.yml @@ -3,10 +3,12 @@ version: '3' vars: package_name: {{ cookiecutter.project_slug }} - python_min_version: '{{ cookiecutter.min_python_version }}' - python_max_version: &dev_version '{{ cookiecutter.max_python_version }}' - # 默认的开发版本 + python_min_version: &dev_version '{{ cookiecutter.min_python_version }}' + python_max_version: '{{ cookiecutter.max_python_version }}' python_dev_version: *dev_version + pypi_server_url: {% raw %}'{{.PYPI_SERVER_URL | default "{{ cookiecutter.pypi_server_url }}"}}'{% endraw %} + +dotenv: ['.env'] tasks: init: @@ -25,9 +27,9 @@ tasks: lint:add-noqa: desc: Add noqa comments to files cmds: - - uvx ruff check --fix --add-noqa . # 添加 noqa 注释以忽略特定行的检查 - - task: lint:pre-commit # 运行 pre-commit 钩子以确保代码风格一致 - - task: lint # 重新运行 ruff 检查以确保没有遗漏的错误 + - uvx ruff check --add-noqa . # 添加 noqa 注释以忽略特定行的检查 + - task: lint:pre-commit # 运行 pre-commit 钩子以确保代码风格一致 + - task: lint # 重新运行 ruff 检查以确保没有遗漏的错误 lint:pre-commit: desc: Run pre-commit hooks @@ -67,64 +69,104 @@ tasks: desc: Run tests with specific Python version cmds: - echo "Testing with Python {% raw %}{{.version}}{% endraw %}..." - - uvx --python {% raw %}{{.version}}{% endraw %} --with ".[dev]" pytest --cov={% raw %}{{.package_name}}{% endraw %} --cov-report=xml --cov-report=term-missing -v tests/ + - uv run --extra dev --python {% raw %}{{.version}}{% endraw %} pytest --cov={% raw %}{{.package_name}}{% endraw %} --cov-report=xml --cov-report=term-missing -v tests/ test:watch: desc: Watch files and run tests cmds: - - uvx --with ".[dev]" ptw --runner "pytest -vx" + - uv run --extra dev ptw --runner "pytest -vx" +{%- if cookiecutter.use_github_actions == 'yes' %} docs: desc: Build and view documentation cmds: - - uvx --with ".[docs]" mkdocs serve + - uv run --extra docs mkdocs serve docs:build: desc: Build documentation cmds: - - uvx --with ".[docs]" mkdocs build + - uv run --extra docs mkdocs build -{%- if cookiecutter.use_github_actions == 'yes' %} deploy:gh-pages: desc: Deploy documentation to GitHub Pages cmds: - - uvx --with ".[docs]" mkdocs gh-deploy --force + - uv run --extra docs mkdocs gh-deploy --force {%- endif %} - deploy:pypi: - desc: Deploy package to PyPI + # 构建相关任务 + build: + desc: Build package distribution files cmds: - uv build + + # 发布相关任务 - 使用已构建的包 + publish:pypi: + desc: Publish existing package to PyPI + cmds: - uv publish + publish:pypi-server: + desc: Publish existing package to PyPI Server + env: + UV_PUBLISH_USERNAME: {% raw %}'{{.PYPI_SERVER_USERNAME}}'{% endraw %} + UV_PUBLISH_PASSWORD: {% raw %}'{{.PYPI_SERVER_PASSWORD}}'{% endraw %} + UV_PUBLISH_URL: {% raw %}'{{.pypi_server_url}}'{% endraw %} + cmds: + - uv publish dist/*.whl + + publish:all: + desc: Publish existing package to both PyPI and PyPI Server + cmds: + - task: publish:pypi + - task: publish:pypi-server + + # 组合任务 - 构建并发布 + deploy:pypi: + desc: Build and deploy to PyPI + cmds: + - task: build + - task: publish:pypi + + deploy:pypi-server: + desc: Build and deploy to PyPI Server + cmds: + - task: build + - task: publish:pypi-server + + deploy:all: + desc: Build and deploy to all repositories + cmds: + - task: build + - task: publish:all + export:deps: desc: Export dependencies to requirements.txt cmds: - uv pip compile pyproject.toml --no-deps --output-file requirements.txt -{%- if cookiecutter.use_docker == 'yes' %} - docker:build: - desc: Build Docker image +{%- if cookiecutter.use_podman == 'yes' %} + compose:up: + desc: Start services using podman compose cmds: - - docker build -t {{ cookiecutter.repo_name }}:latest . + - podman compose -f container/compose.yaml up - docker:run: - desc: Run Docker container + compose:down: + desc: Stop services using podman compose cmds: - - docker run --rm -it {{ cookiecutter.repo_name }}:latest + - podman compose -f container/compose.yaml down - docker-compose:up: - desc: Start services with docker-compose + compose:build: + desc: Build container images using podman compose cmds: - - docker-compose -f docker/docker-compose.yaml up + - podman compose -f container/compose.yaml build - docker-compose:down: - desc: Stop services with docker-compose + compose:logs: + desc: View compose logs cmds: - - docker-compose -f docker/docker-compose.yaml down + - podman compose -f container/compose.yaml logs -f - docker-compose:logs: - desc: View docker-compose logs + compose:shell: + desc: Open shell in the container cmds: - - docker-compose -f docker/docker-compose.yaml logs -f -{%- endif %} \ No newline at end of file + - podman compose -f container/compose.yaml exec {{cookiecutter.project_slug}} /bin/bash +{%- endif %} diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/Dockerfile b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/Dockerfile new file mode 100644 index 0000000..b1ec48d --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/Dockerfile @@ -0,0 +1,26 @@ +FROM python:{{cookiecutter.max_python_version}}-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ + +# Copy project files +COPY pyproject.toml uv.lock ./ +COPY {{cookiecutter.project_slug}}/ ./{{cookiecutter.project_slug}}/ + +# Install dependencies +RUN uv sync --frozen --no-cache + +# Expose port +EXPOSE 8000 + +# Set default command +CMD ["uv", "run", "python", "-m", "{{cookiecutter.project_slug}}"] diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/compose.yaml b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/compose.yaml new file mode 100644 index 0000000..df7b7e2 --- /dev/null +++ b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/container/compose.yaml @@ -0,0 +1,13 @@ +services: + {{cookiecutter.project_slug}}: + build: + context: .. + dockerfile: container/Dockerfile + ports: + - "8000:8000" + environment: + - PYTHONPATH=/app + volumes: + - ..:/app + working_dir: /app + command: ["python", "-m", "{{cookiecutter.project_slug}}"] diff --git a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docker/Dockerfile b/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docker/Dockerfile deleted file mode 100644 index 6b08c9f..0000000 --- a/repo_scaffold/templates/template-python/{{cookiecutter.project_slug}}/docker/Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM python:{{cookiecutter.max_python_version}} From 125b1c6359790dd5c2e41244bf5a192745fa1193 Mon Sep 17 00:00:00 2001 From: ShawnDeng-code Date: Sat, 26 Jul 2025 17:37:50 +0800 Subject: [PATCH 2/2] ci: update github action --- .github/workflows/ci-tests.yaml | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/.github/workflows/ci-tests.yaml b/.github/workflows/ci-tests.yaml index 1eab225..e817e35 100644 --- a/.github/workflows/ci-tests.yaml +++ b/.github/workflows/ci-tests.yaml @@ -30,35 +30,3 @@ jobs: run: task lint - name: Run test all versions run: task test:all - - docker-build-test: - name: 🐳 Multi-platform Docker Build Test - needs: check - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v4 - - name: Check if Dockerfile exists - id: dockerfile-check - run: | - if [ -f "./docker/Dockerfile" ]; then - echo "dockerfile-exists=true" >> $GITHUB_OUTPUT - else - echo "dockerfile-exists=false" >> $GITHUB_OUTPUT - echo "Dockerfile not found, skipping Docker build test" - fi - - name: Set up QEMU - if: steps.dockerfile-check.outputs.dockerfile-exists == 'true' - uses: docker/setup-qemu-action@v3 - - name: Test Build Image (no push) - if: steps.dockerfile-check.outputs.dockerfile-exists == 'true' - id: build-image - uses: redhat-actions/buildah-build@v2 - with: - image: ${{ github.repository }}-test - tags: test-${{ github.sha }} - containerfiles: ./docker/Dockerfile - platforms: linux/amd64,linux/arm64 - build-args: |- - PYPI_SERVER_USERNAME=${{ env.PYPI_SERVER_USERNAME }} - PYPI_SERVER_PASSWORD=${{ env.PYPI_SERVER_PASSWORD }}