feat(init): 重构初始化流程以确保项目结构完整并支持交互式配置#13
Conversation
There was a problem hiding this comment.
Pull request overview
该 PR 将项目初始化流程从单独的 init 命令中抽取为可复用的“确保已初始化”能力,并在 run/dev 启动前自动补齐基础项目结构,以在缺少 config.yaml 时支持交互式配置并避免目录结构不完整导致的运行问题。
Changes:
- 新增
ensure_project_initialized():检测并创建config.yaml与plugins/,必要时执行交互式初始化。 init命令重构:保留覆盖逻辑,并在非覆盖场景复用ensure_project_initialized()。run/dev启动前调用ensure_project_initialized(".")以确保项目结构完整。
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| ncatbot/cli/commands/run.py | run/dev 启动前自动触发项目初始化保障逻辑 |
| ncatbot/cli/commands/init.py | 抽取并复用初始化保障函数,调整 init 行为与覆盖流程 |
Comments suppressed due to low confidence (1)
ncatbot/cli/commands/init.py:33
ensure_project_initialized的 docstring 声明“用户取消初始化返回 None”,但当前实现没有捕获click.Abort/ 取消路径:click.prompt取消会直接抛异常而不是返回None。建议要么更新 docstring 以反映真实行为,要么捕获取消并返回None(并让调用方决定如何退出)。
def ensure_project_initialized(target_dir: str = ".") -> Optional[Path]:
"""确保项目基础结构存在,必要时执行交互式初始化。
返回创建/确认后的 config.yaml 路径;若用户取消初始化则返回 None。
"""
target = Path(target_dir).resolve()
config_path = target / "config.yaml"
plugins_path = target / "plugins"
if config_path.exists():
_ensure_plugins_dir(plugins_path)
_generate_template_plugin(plugins_path)
return config_path
click.echo(warning(f"未检测到 config.yaml: {config_path}"))
click.echo(info("将执行初始化流程。"))
bot_uin = click.prompt("请输入机器人 QQ 号", type=str)
root = click.prompt("请输入管理员 QQ 号", type=str)
| if config_path.exists(): | ||
| click.echo(warning(f"config.yaml 已存在: {config_path}")) | ||
| if not click.confirm("是否覆盖?"): | ||
| click.echo(info("已跳过 config.yaml")) | ||
| _ensure_plugins_dir(plugins_path) | ||
| _generate_template_plugin(plugins_path) | ||
| return | ||
| _ensure_plugins_dir(plugins_path) | ||
| _generate_template_plugin(plugins_path) | ||
| return config_path |
There was a problem hiding this comment.
ensure_project_initialized 在 config 已存在时也会无条件生成模板插件(_generate_template_plugin)。这会导致 ncatbot run/dev 仅仅启动机器人也可能在工作目录写入新插件文件,属于比较意外的副作用。建议将“确保 plugins/ 存在”和“生成模板插件”拆分,或仅在显式 init 命令中生成模板插件。
| if config_path.exists(): | ||
| click.echo(warning(f"config.yaml 已存在: {config_path}")) | ||
| if click.confirm("是否覆盖?"): | ||
| bot_uin = click.prompt("请输入机器人 QQ 号", type=str) | ||
| root = click.prompt("请输入管理员 QQ 号", type=str) | ||
| config_data = _build_default_config(bot_uin=bot_uin, root=root) | ||
| with open(config_path, "w", encoding="utf-8") as f: | ||
| yaml.dump( | ||
| config_data, | ||
| f, | ||
| allow_unicode=True, | ||
| default_flow_style=False, | ||
| sort_keys=False, | ||
| ) | ||
| click.echo(success(f"config.yaml 已覆盖: {config_path}")) | ||
| _ensure_plugins_dir(target / "plugins") | ||
| _generate_template_plugin(target / "plugins") | ||
| click.echo() |
There was a problem hiding this comment.
init 的覆盖分支里重复了与 ensure_project_initialized 相同的 prompt/写文件/创建 plugins/ 与模板插件逻辑,后续默认配置结构变更时容易出现两处实现不一致。建议抽象一个“写入 config(可覆盖)”的内部函数,或让 ensure_project_initialized 支持 overwrite=True 参数,init 仅负责处理交互选择。
| ensure_project_initialized(".") | ||
| bot = BotClient(debug=debug, plugin_dir=plugin_dir) | ||
| bot.run() |
There was a problem hiding this comment.
run/dev 调用 ensure_project_initialized 但忽略了返回值/取消结果;如果未来按 docstring 返回 None(或捕获 click.Abort 后返回 None),这里会继续启动 BotClient,导致在未完成初始化时运行。建议检查返回值并在未初始化时以明确的方式退出(例如抛 click.Abort 或 click.ClickException)。
| ensure_project_initialized(".") | ||
| bot = BotClient(debug=True, plugin_dir=plugin_dir) | ||
| bot.run() |
There was a problem hiding this comment.
dev 同样调用 ensure_project_initialized 但未处理取消/失败结果;如果初始化未完成仍会继续启动 BotClient。建议与 run 一样检查返回值并在未初始化时退出。
No description provided.