Skip to content

feat: update template #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .github/workflows/ci-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
9 changes: 7 additions & 2 deletions repo_scaffold/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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, # 根据用户选择决定是否启用交互式输入
)


Expand Down
18 changes: 7 additions & 11 deletions repo_scaffold/templates/template-python/cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "是否添加命令行界面?"
}
}
195 changes: 139 additions & 56 deletions repo_scaffold/templates/template-python/hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -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()
main()

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 %}
Loading