#!/usr/bin/env python3 """chongming 项目模板初始化。 用法: python init.py <目标目录> [--name 项目名] [--app-name 应用名] [--no-sync] 行为: 1. 拒绝覆盖非空目录 2. 复制模板内容(不含 init.py 与模板自身 README;README.project.md 重命名为 README.md) 3. 替换占位符 {{project_name}}(发行名)/ {{app_name}}(应用显示名) 4. 默认在目标目录执行 uv sync(--no-sync 跳过) 示例: python init.py ~/proj/my-gateway python init.py ../demo --name demo --app-name "演示网关" """ from __future__ import annotations import argparse import shutil import subprocess import sys from pathlib import Path TEMPLATE_DIR = Path(__file__).resolve().parent # 模板自身文件 / 生成物:不复制 SKIP_NAMES = {"init.py", "README.md", ".git", "__pycache__", ".venv", "dist", "build"} # 做占位符替换的文本文件 TEXT_SUFFIXES = {".py", ".toml", ".md", ".json5", ".json", ".yaml", ".yml", ".sh", ".txt"} TEXT_NAMES = {".gitignore", ".env.example"} # 生成项目里该文件重命名为 README.md PROJECT_README = "README.project.md" def main() -> int: parser = argparse.ArgumentParser(description="chongming 项目模板初始化") parser.add_argument("target", help="目标项目目录(不存在则创建;必须为空)") parser.add_argument("--name", help="项目名(pyproject 的 name,默认取目标目录名)") parser.add_argument("--app-name", help="应用显示名(config.toml 的 app.name,默认同项目名)") parser.add_argument("--no-sync", action="store_true", help="跳过 uv sync") args = parser.parse_args() target = Path(args.target).expanduser().resolve() project_name = args.name or target.name app_name = args.app_name or project_name if target.exists() and any(target.iterdir()): print(f"❌ 目标目录非空,拒绝覆盖: {target}") return 1 replacements = {"{{project_name}}": project_name, "{{app_name}}": app_name} copied = 0 for src in sorted(TEMPLATE_DIR.rglob("*")): rel = src.relative_to(TEMPLATE_DIR) if any(part in SKIP_NAMES for part in rel.parts): continue dst = target / ("README.md" if rel.as_posix() == PROJECT_README else rel) if src.is_dir(): dst.mkdir(parents=True, exist_ok=True) continue dst.parent.mkdir(parents=True, exist_ok=True) if src.suffix in TEXT_SUFFIXES or src.name in TEXT_NAMES: text = src.read_text(encoding="utf-8") for key, value in replacements.items(): text = text.replace(key, value) dst.write_text(text, encoding="utf-8") else: shutil.copy2(src, dst) copied += 1 print(f"✅ 已初始化: {target}") print(f" 项目名={project_name} 应用名={app_name} 文件数={copied}") if args.no_sync: print("(--no-sync:跳过 uv sync)") elif shutil.which("uv") is None: print("⚠️ 未找到 uv,跳过依赖安装;请在目标目录自行执行 uv sync") else: print("→ uv sync(首次安装依赖,可能需要几分钟)") if subprocess.call(["uv", "sync"], cwd=target) != 0: print("⚠️ uv sync 未成功,请在目标目录手动重试") print("\n下一步:") print(f" cd {target}") print(" uv run serve # 启动(http://localhost:8000/docs)") print(" curl 'http://localhost:8000/api/v1/example/hello?name=world'") print(" uv run gen_plugin # 生成新插件骨架") return 0 if __name__ == "__main__": sys.exit(main())