aury-boot 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. aury/boot/__init__.py +66 -0
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +120 -0
  4. aury/boot/application/app/__init__.py +39 -0
  5. aury/boot/application/app/base.py +511 -0
  6. aury/boot/application/app/components.py +434 -0
  7. aury/boot/application/app/middlewares.py +101 -0
  8. aury/boot/application/config/__init__.py +44 -0
  9. aury/boot/application/config/settings.py +663 -0
  10. aury/boot/application/constants/__init__.py +19 -0
  11. aury/boot/application/constants/components.py +50 -0
  12. aury/boot/application/constants/scheduler.py +28 -0
  13. aury/boot/application/constants/service.py +29 -0
  14. aury/boot/application/errors/__init__.py +55 -0
  15. aury/boot/application/errors/chain.py +80 -0
  16. aury/boot/application/errors/codes.py +67 -0
  17. aury/boot/application/errors/exceptions.py +238 -0
  18. aury/boot/application/errors/handlers.py +320 -0
  19. aury/boot/application/errors/response.py +120 -0
  20. aury/boot/application/interfaces/__init__.py +76 -0
  21. aury/boot/application/interfaces/egress.py +224 -0
  22. aury/boot/application/interfaces/ingress.py +98 -0
  23. aury/boot/application/middleware/__init__.py +22 -0
  24. aury/boot/application/middleware/logging.py +451 -0
  25. aury/boot/application/migrations/__init__.py +13 -0
  26. aury/boot/application/migrations/manager.py +685 -0
  27. aury/boot/application/migrations/setup.py +237 -0
  28. aury/boot/application/rpc/__init__.py +63 -0
  29. aury/boot/application/rpc/base.py +108 -0
  30. aury/boot/application/rpc/client.py +294 -0
  31. aury/boot/application/rpc/discovery.py +218 -0
  32. aury/boot/application/scheduler/__init__.py +13 -0
  33. aury/boot/application/scheduler/runner.py +123 -0
  34. aury/boot/application/server/__init__.py +296 -0
  35. aury/boot/commands/__init__.py +30 -0
  36. aury/boot/commands/add.py +76 -0
  37. aury/boot/commands/app.py +105 -0
  38. aury/boot/commands/config.py +177 -0
  39. aury/boot/commands/docker.py +367 -0
  40. aury/boot/commands/docs.py +284 -0
  41. aury/boot/commands/generate.py +1277 -0
  42. aury/boot/commands/init.py +892 -0
  43. aury/boot/commands/migrate/__init__.py +37 -0
  44. aury/boot/commands/migrate/app.py +54 -0
  45. aury/boot/commands/migrate/commands.py +303 -0
  46. aury/boot/commands/scheduler.py +124 -0
  47. aury/boot/commands/server/__init__.py +21 -0
  48. aury/boot/commands/server/app.py +541 -0
  49. aury/boot/commands/templates/generate/api.py.tpl +105 -0
  50. aury/boot/commands/templates/generate/model.py.tpl +17 -0
  51. aury/boot/commands/templates/generate/repository.py.tpl +19 -0
  52. aury/boot/commands/templates/generate/schema.py.tpl +29 -0
  53. aury/boot/commands/templates/generate/service.py.tpl +48 -0
  54. aury/boot/commands/templates/project/CLI.md.tpl +92 -0
  55. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +1397 -0
  56. aury/boot/commands/templates/project/README.md.tpl +111 -0
  57. aury/boot/commands/templates/project/admin_console_init.py.tpl +50 -0
  58. aury/boot/commands/templates/project/config.py.tpl +30 -0
  59. aury/boot/commands/templates/project/conftest.py.tpl +26 -0
  60. aury/boot/commands/templates/project/env.example.tpl +213 -0
  61. aury/boot/commands/templates/project/gitignore.tpl +128 -0
  62. aury/boot/commands/templates/project/main.py.tpl +41 -0
  63. aury/boot/commands/templates/project/modules/api.py.tpl +19 -0
  64. aury/boot/commands/templates/project/modules/exceptions.py.tpl +84 -0
  65. aury/boot/commands/templates/project/modules/schedules.py.tpl +18 -0
  66. aury/boot/commands/templates/project/modules/tasks.py.tpl +20 -0
  67. aury/boot/commands/worker.py +143 -0
  68. aury/boot/common/__init__.py +35 -0
  69. aury/boot/common/exceptions/__init__.py +114 -0
  70. aury/boot/common/i18n/__init__.py +16 -0
  71. aury/boot/common/i18n/translator.py +272 -0
  72. aury/boot/common/logging/__init__.py +716 -0
  73. aury/boot/contrib/__init__.py +10 -0
  74. aury/boot/contrib/admin_console/__init__.py +18 -0
  75. aury/boot/contrib/admin_console/auth.py +137 -0
  76. aury/boot/contrib/admin_console/discovery.py +69 -0
  77. aury/boot/contrib/admin_console/install.py +172 -0
  78. aury/boot/contrib/admin_console/utils.py +44 -0
  79. aury/boot/domain/__init__.py +79 -0
  80. aury/boot/domain/exceptions/__init__.py +132 -0
  81. aury/boot/domain/models/__init__.py +51 -0
  82. aury/boot/domain/models/base.py +69 -0
  83. aury/boot/domain/models/mixins.py +135 -0
  84. aury/boot/domain/models/models.py +96 -0
  85. aury/boot/domain/pagination/__init__.py +279 -0
  86. aury/boot/domain/repository/__init__.py +23 -0
  87. aury/boot/domain/repository/impl.py +423 -0
  88. aury/boot/domain/repository/interceptors.py +47 -0
  89. aury/boot/domain/repository/interface.py +106 -0
  90. aury/boot/domain/repository/query_builder.py +348 -0
  91. aury/boot/domain/service/__init__.py +11 -0
  92. aury/boot/domain/service/base.py +73 -0
  93. aury/boot/domain/transaction/__init__.py +404 -0
  94. aury/boot/infrastructure/__init__.py +104 -0
  95. aury/boot/infrastructure/cache/__init__.py +31 -0
  96. aury/boot/infrastructure/cache/backends.py +348 -0
  97. aury/boot/infrastructure/cache/base.py +68 -0
  98. aury/boot/infrastructure/cache/exceptions.py +37 -0
  99. aury/boot/infrastructure/cache/factory.py +94 -0
  100. aury/boot/infrastructure/cache/manager.py +274 -0
  101. aury/boot/infrastructure/database/__init__.py +39 -0
  102. aury/boot/infrastructure/database/config.py +71 -0
  103. aury/boot/infrastructure/database/exceptions.py +44 -0
  104. aury/boot/infrastructure/database/manager.py +317 -0
  105. aury/boot/infrastructure/database/query_tools/__init__.py +164 -0
  106. aury/boot/infrastructure/database/strategies/__init__.py +198 -0
  107. aury/boot/infrastructure/di/__init__.py +15 -0
  108. aury/boot/infrastructure/di/container.py +393 -0
  109. aury/boot/infrastructure/events/__init__.py +33 -0
  110. aury/boot/infrastructure/events/bus.py +362 -0
  111. aury/boot/infrastructure/events/config.py +52 -0
  112. aury/boot/infrastructure/events/consumer.py +134 -0
  113. aury/boot/infrastructure/events/middleware.py +51 -0
  114. aury/boot/infrastructure/events/models.py +63 -0
  115. aury/boot/infrastructure/monitoring/__init__.py +529 -0
  116. aury/boot/infrastructure/scheduler/__init__.py +19 -0
  117. aury/boot/infrastructure/scheduler/exceptions.py +37 -0
  118. aury/boot/infrastructure/scheduler/manager.py +478 -0
  119. aury/boot/infrastructure/storage/__init__.py +38 -0
  120. aury/boot/infrastructure/storage/base.py +164 -0
  121. aury/boot/infrastructure/storage/exceptions.py +37 -0
  122. aury/boot/infrastructure/storage/factory.py +88 -0
  123. aury/boot/infrastructure/tasks/__init__.py +24 -0
  124. aury/boot/infrastructure/tasks/config.py +45 -0
  125. aury/boot/infrastructure/tasks/constants.py +37 -0
  126. aury/boot/infrastructure/tasks/exceptions.py +37 -0
  127. aury/boot/infrastructure/tasks/manager.py +490 -0
  128. aury/boot/testing/__init__.py +24 -0
  129. aury/boot/testing/base.py +122 -0
  130. aury/boot/testing/client.py +163 -0
  131. aury/boot/testing/factory.py +154 -0
  132. aury/boot/toolkit/__init__.py +21 -0
  133. aury/boot/toolkit/http/__init__.py +367 -0
  134. {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/METADATA +3 -2
  135. aury_boot-0.0.4.dist-info/RECORD +137 -0
  136. aury_boot-0.0.2.dist-info/RECORD +0 -5
  137. {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/WHEEL +0 -0
  138. {aury_boot-0.0.2.dist-info → aury_boot-0.0.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,367 @@
1
+ """Docker 相关命令。
2
+
3
+ 生成 Docker 配置文件:
4
+ - Dockerfile
5
+ - docker-compose.yml
6
+ - .dockerignore
7
+
8
+ 使用示例:
9
+ aury docker init
10
+ aury docker init --force
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+
17
+ from rich.console import Console
18
+ from rich.panel import Panel
19
+ import typer
20
+
21
+ from .config import get_project_config
22
+
23
+ console = Console()
24
+
25
+ # 创建 docker 子应用
26
+ app = typer.Typer(
27
+ name="docker",
28
+ help="Docker 配置文件生成",
29
+ no_args_is_help=True,
30
+ )
31
+
32
+
33
+ # ============================================================
34
+ # 模板
35
+ # ============================================================
36
+
37
+ DOCKERFILE_TEMPLATE = '''# =============================================================================
38
+ # {project_name} Dockerfile
39
+ # =============================================================================
40
+ # 多阶段构建优化镜像大小
41
+
42
+ # 基础镜像
43
+ FROM python:3.13-slim AS base
44
+
45
+ ENV PYTHONDONTWRITEBYTECODE=1 \\
46
+ PYTHONUNBUFFERED=1 \\
47
+ PIP_NO_CACHE_DIR=1 \\
48
+ PIP_DISABLE_PIP_VERSION_CHECK=1
49
+
50
+ WORKDIR /app
51
+
52
+ # 安装系统依赖
53
+ RUN apt-get update && apt-get install -y --no-install-recommends \\
54
+ curl \\
55
+ && rm -rf /var/lib/apt/lists/*
56
+
57
+ # -----------------------------------------------------------------------------
58
+ # 构建阶段:安装依赖
59
+ # -----------------------------------------------------------------------------
60
+ FROM base AS builder
61
+
62
+ # 安装 uv
63
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
64
+
65
+ # 复制依赖文件
66
+ COPY pyproject.toml uv.lock* ./
67
+
68
+ # 安装依赖到虚拟环境
69
+ RUN uv sync --frozen --no-dev --no-install-project
70
+
71
+ # -----------------------------------------------------------------------------
72
+ # 运行阶段
73
+ # -----------------------------------------------------------------------------
74
+ FROM base AS runtime
75
+
76
+ # 从 builder 复制虚拟环境
77
+ COPY --from=builder /app/.venv /app/.venv
78
+
79
+ # 设置 PATH
80
+ ENV PATH="/app/.venv/bin:$PATH"
81
+
82
+ # 复制应用代码
83
+ COPY . .
84
+
85
+ # 创建非 root 用户
86
+ RUN useradd --create-home --shell /bin/bash appuser && \\
87
+ chown -R appuser:appuser /app
88
+ USER appuser
89
+
90
+ # 健康检查
91
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
92
+ CMD curl -f http://localhost:8000/health || exit 1
93
+
94
+ # 默认命令
95
+ CMD ["aury", "server", "prod"]
96
+ '''
97
+
98
+
99
+ DOCKER_COMPOSE_TEMPLATE = '''# =============================================================================
100
+ # {project_name} Docker Compose
101
+ # =============================================================================
102
+ # 服务编排配置
103
+
104
+ services:
105
+ # ---------------------------------------------------------------------------
106
+ # 基础服务(共享配置)
107
+ # ---------------------------------------------------------------------------
108
+ base: &base
109
+ build:
110
+ context: .
111
+ dockerfile: Dockerfile
112
+ env_file:
113
+ - .env
114
+ volumes:
115
+ - ./logs:/app/logs
116
+ restart: unless-stopped
117
+ networks:
118
+ - {project_name_snake}_network
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # API 服务
122
+ # ---------------------------------------------------------------------------
123
+ api:
124
+ <<: *base
125
+ container_name: {project_name_snake}_api
126
+ environment:
127
+ - SERVICE_NAME=api
128
+ - SERVICE_TYPE=api
129
+ - SCHEDULER_ENABLED=false # API 服务不启动内嵌调度器
130
+ ports:
131
+ - "${{API_PORT:-8000}}:8000"
132
+ command: ["aury", "server", "prod"]
133
+ depends_on:
134
+ - redis
135
+ - postgres
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Scheduler 服务(定时任务)
139
+ # ---------------------------------------------------------------------------
140
+ scheduler:
141
+ <<: *base
142
+ container_name: {project_name_snake}_scheduler
143
+ environment:
144
+ - SERVICE_NAME=scheduler
145
+ command: ["aury", "scheduler"]
146
+ depends_on:
147
+ - redis
148
+ - postgres
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # Worker 服务(异步任务)
152
+ # ---------------------------------------------------------------------------
153
+ worker:
154
+ <<: *base
155
+ container_name: {project_name_snake}_worker
156
+ environment:
157
+ - SERVICE_NAME=worker
158
+ command: ["aury", "worker", "-c", "8"]
159
+ depends_on:
160
+ - redis
161
+ - postgres
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # 基础设施服务
165
+ # ---------------------------------------------------------------------------
166
+ postgres:
167
+ image: postgres:16-alpine
168
+ container_name: {project_name_snake}_postgres
169
+ environment:
170
+ POSTGRES_USER: ${{POSTGRES_USER:-postgres}}
171
+ POSTGRES_PASSWORD: ${{POSTGRES_PASSWORD:-postgres}}
172
+ POSTGRES_DB: ${{POSTGRES_DB:-{project_name_snake}}}
173
+ volumes:
174
+ - postgres_data:/var/lib/postgresql/data
175
+ ports:
176
+ - "${{POSTGRES_PORT:-5432}}:5432"
177
+ networks:
178
+ - {project_name_snake}_network
179
+ restart: unless-stopped
180
+
181
+ redis:
182
+ image: redis:7-alpine
183
+ container_name: {project_name_snake}_redis
184
+ command: redis-server --appendonly yes
185
+ volumes:
186
+ - redis_data:/data
187
+ ports:
188
+ - "${{REDIS_PORT:-6379}}:6379"
189
+ networks:
190
+ - {project_name_snake}_network
191
+ restart: unless-stopped
192
+
193
+ # -----------------------------------------------------------------------------
194
+ # 网络和卷
195
+ # -----------------------------------------------------------------------------
196
+ networks:
197
+ {project_name_snake}_network:
198
+ driver: bridge
199
+
200
+ volumes:
201
+ postgres_data:
202
+ redis_data:
203
+ '''
204
+
205
+
206
+ DOCKERIGNORE_TEMPLATE = '''# =============================================================================
207
+ # Docker 忽略文件
208
+ # =============================================================================
209
+
210
+ # Python
211
+ __pycache__/
212
+ *.py[cod]
213
+ *$py.class
214
+ *.so
215
+ .Python
216
+ .venv/
217
+ venv/
218
+ ENV/
219
+ env/
220
+ .eggs/
221
+ *.egg-info/
222
+ *.egg
223
+
224
+ # 开发工具
225
+ .git/
226
+ .gitignore
227
+ .idea/
228
+ .vscode/
229
+ *.swp
230
+ *.swo
231
+
232
+ # 测试
233
+ .pytest_cache/
234
+ .coverage
235
+ htmlcov/
236
+ .tox/
237
+ .nox/
238
+
239
+ # 构建
240
+ dist/
241
+ build/
242
+ *.egg-info/
243
+
244
+ # 文档
245
+ docs/
246
+ *.md
247
+ !README.md
248
+
249
+ # 本地配置
250
+ .env.local
251
+ .env.*.local
252
+ *.local.yml
253
+
254
+ # 日志
255
+ logs/
256
+ *.log
257
+
258
+ # 缓存
259
+ .cache/
260
+ .mypy_cache/
261
+ .ruff_cache/
262
+
263
+ # IDE
264
+ .idea/
265
+ .vscode/
266
+ *.sublime-*
267
+
268
+ # macOS
269
+ .DS_Store
270
+
271
+ # 其他
272
+ Makefile
273
+ docker-compose.override.yml
274
+ '''
275
+
276
+
277
+ # ============================================================
278
+ # 命令
279
+ # ============================================================
280
+
281
+
282
+ def _to_snake_case(name: str) -> str:
283
+ """转换为 snake_case。"""
284
+ import re
285
+ s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name)
286
+ return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_")
287
+
288
+
289
+ @app.command(name="init")
290
+ def docker_init(
291
+ force: bool = typer.Option(
292
+ False,
293
+ "--force",
294
+ "-f",
295
+ help="强制覆盖已存在的文件",
296
+ ),
297
+ ) -> None:
298
+ """生成 Docker 配置文件。
299
+
300
+ 生成:
301
+ - Dockerfile
302
+ - docker-compose.yml
303
+ - .dockerignore
304
+
305
+ 示例:
306
+ aury docker init
307
+ aury docker init --force
308
+ """
309
+ base_path = Path.cwd()
310
+
311
+ # 获取项目名称
312
+ project_name = base_path.name
313
+ project_name_snake = _to_snake_case(project_name)
314
+
315
+ # 读取项目配置,获取包名
316
+ config = get_project_config(base_path)
317
+ package_or_dot = f"{config.package}." if config.has_package else ""
318
+
319
+ console.print(Panel.fit(
320
+ f"[bold cyan]🐳 生成 Docker 配置: {project_name}[/bold cyan]",
321
+ border_style="cyan",
322
+ ))
323
+
324
+ created_files = []
325
+
326
+ # 模板变量
327
+ template_vars = {
328
+ "project_name": project_name,
329
+ "project_name_snake": project_name_snake,
330
+ "package_or_dot": package_or_dot,
331
+ }
332
+
333
+ # 生成文件
334
+ files_to_create = [
335
+ ("Dockerfile", DOCKERFILE_TEMPLATE),
336
+ ("docker-compose.yml", DOCKER_COMPOSE_TEMPLATE),
337
+ (".dockerignore", DOCKERIGNORE_TEMPLATE),
338
+ ]
339
+
340
+ for file_name, template in files_to_create:
341
+ file_path = base_path / file_name
342
+
343
+ if file_path.exists() and not force:
344
+ console.print(f" [dim]⏭️ {file_name} 已存在,跳过[/dim]")
345
+ continue
346
+
347
+ content = template.format(**template_vars)
348
+ file_path.write_text(content, encoding="utf-8")
349
+ created_files.append(file_name)
350
+ console.print(f" [green]✅ {file_name}[/green]")
351
+
352
+ if created_files:
353
+ console.print("\n[bold green]✨ Docker 配置生成完成![/bold green]\n")
354
+ console.print("[bold]使用方法:[/bold]")
355
+ console.print(" 1. 启动所有服务:")
356
+ console.print(" [cyan]docker-compose up -d[/cyan]")
357
+ console.print(" 2. 只启动 API:")
358
+ console.print(" [cyan]docker-compose up -d api[/cyan]")
359
+ console.print(" 3. 查看日志:")
360
+ console.print(" [cyan]docker-compose logs -f api[/cyan]")
361
+ console.print(" 4. 停止服务:")
362
+ console.print(" [cyan]docker-compose down[/cyan]")
363
+ else:
364
+ console.print("\n[dim]所有文件已存在,使用 --force 覆盖[/dim]")
365
+
366
+
367
+ __all__ = ["app"]
@@ -0,0 +1,284 @@
1
+ """文档生成命令。
2
+
3
+ 提供命令行工具用于在现有项目中生成/更新文档:
4
+ - aury docs dev 生成/更新 DEVELOPMENT.md
5
+ - aury docs cli 生成/更新 CLI.md
6
+ - aury docs env 生成/更新 .env.example
7
+ - aury docs all 生成/更新所有文档
8
+
9
+ 使用示例:
10
+ aury docs dev # 生成开发文档
11
+ aury docs cli # 生成 CLI 文档
12
+ aury docs env # 生成环境变量示例
13
+ aury docs all # 生成所有文档
14
+ aury docs all --force # 强制覆盖已存在的文件
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from rich.console import Console
22
+ import typer
23
+
24
+ app = typer.Typer(
25
+ name="docs",
26
+ help="📚 生成/更新项目文档",
27
+ no_args_is_help=True,
28
+ )
29
+
30
+ console = Console()
31
+
32
+ # 模板目录
33
+ TEMPLATES_DIR = Path(__file__).parent / "templates" / "project"
34
+
35
+
36
+ def _detect_project_info(project_dir: Path) -> dict[str, str]:
37
+ """检测项目信息。
38
+
39
+ 从 pyproject.toml 或目录结构中推断项目名称和包名。
40
+ """
41
+ # 尝试从 pyproject.toml 读取
42
+ pyproject_path = project_dir / "pyproject.toml"
43
+ if pyproject_path.exists():
44
+ try:
45
+ import tomllib
46
+ with open(pyproject_path, "rb") as f:
47
+ data = tomllib.load(f)
48
+ project_name = data.get("project", {}).get("name", "")
49
+ if project_name:
50
+ # 转换为 snake_case
51
+ project_name_snake = project_name.replace("-", "_").lower()
52
+ return {
53
+ "project_name": project_name,
54
+ "project_name_snake": project_name_snake,
55
+ "package_name": project_name_snake,
56
+ "import_prefix": project_name_snake,
57
+ }
58
+ except Exception:
59
+ pass
60
+
61
+ # 尝试从目录名推断
62
+ dir_name = project_dir.name
63
+ project_name_snake = dir_name.replace("-", "_").lower()
64
+
65
+ # 检查是否有匹配的 Python 包目录
66
+ package_name = project_name_snake
67
+ for candidate in [project_name_snake, "app", "src"]:
68
+ candidate_path = project_dir / candidate
69
+ if candidate_path.is_dir() and (candidate_path / "__init__.py").exists():
70
+ package_name = candidate
71
+ break
72
+
73
+ return {
74
+ "project_name": dir_name,
75
+ "project_name_snake": project_name_snake,
76
+ "package_name": package_name,
77
+ "import_prefix": package_name,
78
+ }
79
+
80
+
81
+ def _render_template(template_name: str, context: dict[str, str]) -> str:
82
+ """渲染模板。"""
83
+ template_path = TEMPLATES_DIR / template_name
84
+ if not template_path.exists():
85
+ raise FileNotFoundError(f"模板文件不存在: {template_path}")
86
+
87
+ content = template_path.read_text(encoding="utf-8")
88
+ return content.format(**context)
89
+
90
+
91
+ def _write_file(
92
+ output_path: Path,
93
+ content: str,
94
+ force: bool = False,
95
+ dry_run: bool = False,
96
+ ) -> bool:
97
+ """写入文件。
98
+
99
+ Returns:
100
+ bool: 是否成功写入
101
+ """
102
+ if output_path.exists() and not force:
103
+ console.print(f"[yellow]⚠️ 文件已存在,跳过: {output_path}[/yellow]")
104
+ console.print(" 使用 --force 覆盖已存在的文件")
105
+ return False
106
+
107
+ if dry_run:
108
+ console.print(f"[dim]🔍 预览模式,将生成: {output_path}[/dim]")
109
+ return True
110
+
111
+ output_path.parent.mkdir(parents=True, exist_ok=True)
112
+ output_path.write_text(content, encoding="utf-8")
113
+
114
+ action = "覆盖" if output_path.exists() else "创建"
115
+ console.print(f"[green]✅ {action}: {output_path}[/green]")
116
+ return True
117
+
118
+
119
+ @app.command(name="dev")
120
+ def generate_dev_doc(
121
+ project_dir: Path = typer.Argument(
122
+ Path("."),
123
+ help="项目目录路径",
124
+ exists=True,
125
+ file_okay=False,
126
+ dir_okay=True,
127
+ resolve_path=True,
128
+ ),
129
+ force: bool = typer.Option(
130
+ False,
131
+ "--force",
132
+ "-f",
133
+ help="强制覆盖已存在的文件",
134
+ ),
135
+ dry_run: bool = typer.Option(
136
+ False,
137
+ "--dry-run",
138
+ "-n",
139
+ help="预览模式,不实际写入文件",
140
+ ),
141
+ ) -> None:
142
+ """生成/更新 DEVELOPMENT.md 开发文档。"""
143
+ context = _detect_project_info(project_dir)
144
+
145
+ console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
146
+
147
+ try:
148
+ content = _render_template("DEVELOPMENT.md.tpl", context)
149
+ output_path = project_dir / "DEVELOPMENT.md"
150
+ _write_file(output_path, content, force=force, dry_run=dry_run)
151
+ except Exception as e:
152
+ console.print(f"[red]❌ 生成失败: {e}[/red]")
153
+ raise typer.Exit(1)
154
+
155
+
156
+ @app.command(name="cli")
157
+ def generate_cli_doc(
158
+ project_dir: Path = typer.Argument(
159
+ Path("."),
160
+ help="项目目录路径",
161
+ exists=True,
162
+ file_okay=False,
163
+ dir_okay=True,
164
+ resolve_path=True,
165
+ ),
166
+ force: bool = typer.Option(
167
+ False,
168
+ "--force",
169
+ "-f",
170
+ help="强制覆盖已存在的文件",
171
+ ),
172
+ dry_run: bool = typer.Option(
173
+ False,
174
+ "--dry-run",
175
+ "-n",
176
+ help="预览模式,不实际写入文件",
177
+ ),
178
+ ) -> None:
179
+ """生成/更新 CLI.md 命令行文档。"""
180
+ context = _detect_project_info(project_dir)
181
+
182
+ console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
183
+
184
+ try:
185
+ content = _render_template("CLI.md.tpl", context)
186
+ output_path = project_dir / "CLI.md"
187
+ _write_file(output_path, content, force=force, dry_run=dry_run)
188
+ except Exception as e:
189
+ console.print(f"[red]❌ 生成失败: {e}[/red]")
190
+ raise typer.Exit(1)
191
+
192
+
193
+ @app.command(name="env")
194
+ def generate_env_example(
195
+ project_dir: Path = typer.Argument(
196
+ Path("."),
197
+ help="项目目录路径",
198
+ exists=True,
199
+ file_okay=False,
200
+ dir_okay=True,
201
+ resolve_path=True,
202
+ ),
203
+ force: bool = typer.Option(
204
+ False,
205
+ "--force",
206
+ "-f",
207
+ help="强制覆盖已存在的文件",
208
+ ),
209
+ dry_run: bool = typer.Option(
210
+ False,
211
+ "--dry-run",
212
+ "-n",
213
+ help="预览模式,不实际写入文件",
214
+ ),
215
+ ) -> None:
216
+ """生成/更新 .env.example 环境变量示例。"""
217
+ context = _detect_project_info(project_dir)
218
+
219
+ console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
220
+
221
+ try:
222
+ content = _render_template("env.example.tpl", context)
223
+ output_path = project_dir / ".env.example"
224
+ _write_file(output_path, content, force=force, dry_run=dry_run)
225
+ except Exception as e:
226
+ console.print(f"[red]❌ 生成失败: {e}[/red]")
227
+ raise typer.Exit(1)
228
+
229
+
230
+ @app.command(name="all")
231
+ def generate_all_docs(
232
+ project_dir: Path = typer.Argument(
233
+ Path("."),
234
+ help="项目目录路径",
235
+ exists=True,
236
+ file_okay=False,
237
+ dir_okay=True,
238
+ resolve_path=True,
239
+ ),
240
+ force: bool = typer.Option(
241
+ False,
242
+ "--force",
243
+ "-f",
244
+ help="强制覆盖已存在的文件",
245
+ ),
246
+ dry_run: bool = typer.Option(
247
+ False,
248
+ "--dry-run",
249
+ "-n",
250
+ help="预览模式,不实际写入文件",
251
+ ),
252
+ ) -> None:
253
+ """生成/更新所有文档(DEVELOPMENT.md, CLI.md, .env.example)。"""
254
+ context = _detect_project_info(project_dir)
255
+
256
+ console.print(f"[cyan]📚 检测到项目: {context['project_name']}[/cyan]")
257
+ console.print()
258
+
259
+ docs_to_generate = [
260
+ ("DEVELOPMENT.md.tpl", "DEVELOPMENT.md", "开发文档"),
261
+ ("CLI.md.tpl", "CLI.md", "CLI 文档"),
262
+ ("env.example.tpl", ".env.example", "环境变量示例"),
263
+ ]
264
+
265
+ success_count = 0
266
+ for template_name, output_name, description in docs_to_generate:
267
+ try:
268
+ content = _render_template(template_name, context)
269
+ output_path = project_dir / output_name
270
+ if _write_file(output_path, content, force=force, dry_run=dry_run):
271
+ success_count += 1
272
+ except FileNotFoundError:
273
+ console.print(f"[yellow]⚠️ 模板不存在,跳过: {template_name}[/yellow]")
274
+ except Exception as e:
275
+ console.print(f"[red]❌ 生成 {description} 失败: {e}[/red]")
276
+
277
+ console.print()
278
+ if dry_run:
279
+ console.print(f"[dim]🔍 预览模式完成,将生成 {success_count} 个文件[/dim]")
280
+ else:
281
+ console.print(f"[green]✨ 完成!成功生成 {success_count} 个文档[/green]")
282
+
283
+
284
+ __all__ = ["app"]