前端脚手架开发练习:一步一坑式学习清单
(从 0 到能跑起来的 CLI,每完成一条打 √)

──────────────────
阶段 1:热身 & 环境

  1. 全局安装一个现成脚手架(如 @vue/cli、create-react-app)并读完 ‐-help 打印出的所有命令。
  2. 用 npx 调一次 npx create-xxx@latest my-test,观察它生成了哪些文件。
  3. 本地装 Node ≥18、npm ≥9,跑通 node -v && npm -v
  4. 学会用 nvm / fnm 切换 Node 版本。

阶段 2:项目骨架
5. 新建文件夹 my-clinpm init -y
6. 在 package.json 里补一条 "type": "module"(ESM)。
7. 新建 bin/index.js,顶部写 #!/usr/bin/env node,并 chmod +x 给它可执行权限。
8. 在 package.json 加 "bin": { "my-cli": "./bin/index.js" },本地 npm link 验证能在任意目录跑 my-cli

阶段 3:命令行解析
9. 安装 commander:npm i commander
10. 用 commander 写第一个子命令 my-cli create <name>,能打印出 creating <name>
11. 给 create 加 --template 选项,支持 -t 简写,并打印模板名。
12. 用 process.exit(1) 处理非法参数并打印友好提示。

阶段 4:交互式提问
13. 安装 inquirer:npm i inquirer@9(新版只支持 ESM)。
14. 用 inquirer 问用户:项目名、作者、是否用 TypeScript,结果暂存到变量 answers
15. 把问答结果组合成 meta = { projectName, author, useTs }

阶段 5:模板引擎
16. 在 templates/ 下放两个文件夹 vanillatypescript,各含 package.json.ejs
17. 安装 ejs:npm i ejs,用它渲染 package.json.ejs 得到真实 package.json,并写入目标目录。
18. 把模板里出现的 <%= projectName %><%= author %> 替换成问答结果。

阶段 6:文件操作
19. 用 fs-extracopy() 将整个模板目录拷到 ./<projectName>,同时 .gitignore 模板文件。
20. 在拷贝过程中过滤掉 .ejs 后缀,避免把模板源文件暴露给用户。
21. 创建完目录后输出 ✔ Project created in ./<projectName>,并提示 cd <projectName> && npm install.

阶段 7:依赖安装 & git 初始化
22. 用 execa 在目标目录里 npm install,带 spinner 让用户知道进度(可用 ora)。
23. 安装成功后自动 git init && git add . && git commit -m "init"
24. 提供 --no-git 选项跳过 git 初始化。

阶段 8:配置化(可选)
25. 在 ~/.config/my-cli/config.json 存用户默认偏好(作者名、常用模板)。
26. 第一次运行时若找不到配置就引导用户填写并保存。
27. 支持 --reset 子命令清空配置。

阶段 9:发布 & 测试
28. 用 npm pack 打本地包,手动解压检查文件。
29. 注册 npm 账号,npm publish --access public 发布 0.1.0。
30. 在另一台机器 npx my-cli@latest create hello 跑通全流程。

阶段 10:锦上添花(进阶)
31. 用 chalk 给文字加颜色,用 log-symbols 打 √/×。
32. 写单元测试:用 vitest 测试 commander 的解析、inquirer 的默认值。
33. 在 GitHub Actions 里跑 npm test && npm run build,发布前自动打 tag。
34. 支持插件机制:扫描 my-cli-plugin-*,动态加载并追加子命令。
35. 用 update-notifier 提示用户脚手架有新版本。

──────────────────
使用方法
· 以上 35 条每条都很小,建议一天完成 3-5 条。
· 每完成一步就在 README 里打 √,并写一句“我学到了什么”。
· 卡住时直接问:“我在第 X 步遇到 … 报错,如何解决?”