96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
#!/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 <name> # 生成新插件骨架")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|