# DevPack 后续开发详细计划 > 基于 ROADMAP.md 路线图,细化到每个文件及其所需第三方库 / 标准库 > 更新日期:2026-03-03 --- ## 依赖库总览 下表列出后续开发中需要引入的**所有第三方库**(go get 命令): | 库 | Import Path | 用途 | 引入阶段 | |---|---|---|---| | zerolog | `github.com/rs/zerolog` | 结构化日志 | M1 | | go-yaml | `gopkg.in/yaml.v3` | Profile YAML 解析 | M1 | | uuid | `github.com/google/uuid` | Pack ID 生成 | M1 | | color | `github.com/fatih/color` | 终端彩色输出 | M1 | | tablewriter | `github.com/olekukonenko/tablewriter` | 终端表格渲染 | M1 | | progressbar | `github.com/schollz/progressbar/v3` | 终端进度条 | M3 | | survey | `github.com/AlecAivazis/survey/v2` | 交互式终端提示 | M4 | | testify | `github.com/stretchr/testify` | 测试断言与 Mock | M1 | | archiver | `github.com/mholt/archiver/v4` | tar.gz 高级归档(可选,也可用标准库) | M3 | | x/crypto | `golang.org/x/crypto` | Argon2id 密钥派生 | v0.3.0 | > 注意:`cobra` 和 `viper` 已在 go.mod 中,无需再添加。 --- ## M1: 项目基础架构(第 1-2 周) ### 任务 1.1 — 日志系统 ✅ 已完成 **新建文件:** | 文件 | 说明 | |---|---| | `internal/logging/logger.go` | 全局 Logger 封装、日志级别控制、文件 + 控制台双输出 | | `internal/logging/logger_test.go` | 日志系统单元测试 | **所需库:** ``` 第三方库: github.com/rs/zerolog — 核心日志引擎 标准库: os — 日志文件创建 io — MultiWriter (控制台+文件) time — 时间戳格式 fmt — 格式化 path/filepath — 日志文件路径 ``` **实现要点:** - 导出全局 `Logger` 实例 - 支持 `--verbose` / `--quiet` / `--log-level` 全局标志 - 支持 `--log-file` 输出到文件 - 集成到 `cmd/devpack/commands/root.go` 的 `PersistentPreRun` **需修改的现有文件:** | 文件 | 改动 | |---|---| | `cmd/devpack/commands/root.go` | 在 `PersistentPreRunE` 中初始化 Logger,传递 verbose/quiet/log-level/log-file 标志 | | `go.mod` | 添加 `github.com/rs/zerolog` 依赖 | **✅ 实际完成情况:** 已实现完整的日志系统,核心结构如下: - `Logger` 结构体:封装 `zerolog.Logger`,支持控制台彩色输出 + JSON 文件输出双通道 - `Options` 配置结构体:`Level`、`LogFile`、`Verbose`、`Quiet`、`NoColor` - 全局单例模式:`Init(opts Options)` 初始化 + `G()` 获取全局实例 - 完整日志级别:`Trace/Debug/Info/Warn/Error/Fatal` 及 `Xxxf` 格式化变体 - 结构化日志方法:`WithField(key, val)`、`WithError(err)`、`WithDuration(d)` - `Close()` 方法:关闭日志文件句柄,释放资源 - 已通过 `root.go` 的 `PersistentPreRunE` 集成 CLI 标志 - 默认日志文件:`~/.devpack/logs/devpack.log` - 测试覆盖:50 个单元测试全部通过 --- ### 任务 1.2 — 配置系统 ✅ 已完成 **新建文件:** | 文件 | 说明 | |---|---| | `internal/config/config.go` | 配置结构体定义 (AppConfig)、加载/保存/默认值逻辑 | | `internal/config/config_test.go` | 配置系统单元测试 | **所需库:** ``` 第三方库: github.com/spf13/viper — 配置文件读取(已有) gopkg.in/yaml.v3 — YAML 序列化/反序列化 标准库: os — 文件操作 path/filepath — 路径拼接 fmt — 格式化 ``` **需修改的现有文件:** | 文件 | 改动 | |---|---| | `internal/config/paths.go` | 增加 `ConfigFilePath()` 方法返回 config.yaml 路径 | | `go.mod` | 添加 `gopkg.in/yaml.v3` 依赖 | **✅ 实际完成情况:** 已实现完整的配置管理系统: - `AppConfig` 结构体:包含 `LogLevel`、`LogFile`、`DefaultProfile` 及嵌套 `RestoreConfig`(`ConflictStrategy`、`CreateRestorePoint`、`DryRunFirst`) - 所有字段使用 `yaml` 标签(而非 `mapstructure`) - `DefaultAppConfig()` 提供合理默认值 - `LoadConfig(path)` 文件不存在时返回默认配置而非报错 - `SaveConfig(path, cfg)` 写入 YAML 格式的配置文件 - `Paths` 结构体:管理 `~/.devpack/` 下的目录结构(Config、Profiles、Packs、Logs、Temp) - `DefaultPaths()` 使用 `os.UserHomeDir()` 解析家目录 - `EnsureDirs()` 递归创建所有必要目录 - `ConfigFilePath()` 支持 viper 覆盖 - 测试覆盖:12 个单元测试全部通过 --- ### 任务 1.3 — Profile 系统核心 **新建文件:** | 文件 | 说明 | |---|---| | `internal/profile/profile.go` | Profile 结构体、加载/保存/验证/列出/默认模板 | | `internal/profile/profile_test.go` | Profile 单元测试 | | `internal/profile/templates.go` | 预设模板: minimal / standard / full | **所需库:** ``` 第三方库: gopkg.in/yaml.v3 — Profile YAML 解析与生成 标准库: os — 文件操作 path/filepath — Profile 文件路径 fmt — 错误信息 strings — 字符串处理 ``` **详细功能描述:** #### 1.3.1 Profile YAML 结构定义 Profile 是 DevPack 的"打包规则定义文件",控制哪些采集器启用、如何过滤数据。完整结构如下: ```yaml # ~/.devpack/profiles/standard.yaml name: "standard" description: "标准开发环境配置,包含常用运行时和编辑器" version: 1 # Profile 格式版本号 # 采集器配置 collectors: enabled: # 启用的采集器列表(空=全部启用) - go - node - python - vscode - powershell - scoop - git - env disabled: # 显式禁用的采集器 - ssh # 敏感数据默认不采集 - font # 按采集器的细粒度设置 settings: go: include_tools: true # 是否采集 GOPATH/bin 下的工具 tools_filter: # 只采集匹配的工具(glob 模式) - "gopls" - "dlv" - "staticcheck" node: include_global_packages: true # 是否采集全局 npm 包 exclude_packages: # 排除的包名 - "npm" # npm 自身不需要采集 vscode: include_settings: true # 采集 settings.json include_keybindings: true # 采集 keybindings.json include_snippets: true # 采集代码片段 exclude_extensions: # 排除的扩展 ID - "ms-vscode.remote-*" # 远程相关扩展不迁移 env: include_patterns: # 要采集的环境变量名模式 - "GOPATH" - "GOROOT" - "JAVA_HOME" - "NODE_*" - "PYTHON*" exclude_patterns: # 排除的变量名模式 - "*SECRET*" - "*TOKEN*" - "*PASSWORD*" - "*KEY*" scoop: include_buckets: true # 是否采集 bucket 列表 exclude_packages: [] # 排除的包名 git: include_aliases: true # 采集 git 别名 skip_credentials: true # 跳过凭据信息 ``` #### 1.3.2 Profile 结构体定义 ```go type Profile struct { Name string `yaml:"name"` Description string `yaml:"description"` Version int `yaml:"version"` Collectors CollectorConfig `yaml:"collectors"` Settings map[string]Setting `yaml:"settings"` // key = 采集器名称 } type CollectorConfig struct { Enabled []string `yaml:"enabled"` // 空列表 = 启用全部 Disabled []string `yaml:"disabled"` // 显式禁用 } type Setting struct { Options map[string]interface{} `yaml:",inline"` // 各采集器自定义选项 } ``` #### 1.3.3 验证规则 | 规则 | 说明 | 错误码 | |---|---|---| | `name` 非空 | 必须是 1-64 字符,仅允许 `[a-zA-Z0-9_-]` | E1010 | | `name` 唯一 | 同一 profiles 目录下不能重名 | E1011 | | `enabled` 合法 | 列表中的名称必须是注册过的采集器名称 | E1012 | | `disabled` 合法 | 同上 | E1012 | | `enabled` 与 `disabled` 不冲突 | 同一采集器不能同时出现在两个列表中 | E1013 | | `settings` 键存在 | settings 下的键必须对应已启用的采集器 | E1014 | | `include/exclude` 模式合法 | glob 模式可被 `filepath.Match` 解析 | E1015 | | `version` 兼容 | Profile 版本 ≤ 当前支持的最大版本 | E1016 | #### 1.3.4 CRUD 操作详细流程 **Create(创建):** 1. 接收 name + template(minimal/standard/full) 2. 检查 name 合法性和唯一性 3. 从 `templates.go` 获取模板内容 4. 填入用户自定义字段 5. 运行 `Validate()` 验证 6. 写入 `~/.devpack/profiles/{name}.yaml` 7. 如果是第一个 Profile,自动设为默认 **Load(加载):** 1. 拼接路径 `~/.devpack/profiles/{name}.yaml` 2. 读取文件内容 3. YAML 反序列化到 `Profile` 结构体 4. 运行 `Validate()` 验证结构完整性 5. 返回 `*Profile` 或 error **Save(保存):** 1. 运行 `Validate()` 验证 2. YAML 序列化 3. 原子写入(先写临时文件,再 rename,避免写入中途断电损坏) **Delete(删除):** 1. 检查该 Profile 是否存在 2. 如果是当前默认 Profile,拒绝删除(或提示用户先切换默认) 3. 删除 `~/.devpack/profiles/{name}.yaml` **List(列出):** 1. 扫描 `~/.devpack/profiles/` 目录 2. 读取每个 `.yaml` 文件的 name + description 字段 3. 返回 `[]ProfileMeta{Name, Description, Path, ModTime}` #### 1.3.5 三种模板内容对比 | 项目 | minimal | standard | full | |---|---|---|---| | 运行时(Go/Node/Python) | ✅ | ✅ | ✅ | | 包管理器(Scoop/Brew) | ✅ | ✅ | ✅ | | 编辑器(VS Code) | ❌ | ✅ | ✅ | | Shell(PowerShell/Bash) | ❌ | ✅ | ✅ | | Git 配置 | ❌ | ✅ | ✅ | | 环境变量 | ❌ | ❌ | ✅ | | SSH/GPG 密钥 | ❌ | ❌ | ✅(加密) | | 字体 | ❌ | ❌ | ✅ | | 自定义脚本 | ❌ | ❌ | ✅ | --- ### 任务 1.4 — 平台适配层完善 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `internal/platform/platform.go` | 添加 `InstallFont()` 和 `RunAsAdmin()` 方法签名 | `os/exec` | | `internal/platform/windows.go` | 实现 `InstallFont()` (注册表写入)、`RunAsAdmin()` (runas 动词)、完善 `SetEnvVar` (使用 `golang.org/x/sys/windows/registry`) | `os/exec`, `golang.org/x/sys/windows/registry` | | `internal/platform/darwin.go` | 实现 `InstallFont()` (~/Library/Fonts)、`RunAsAdmin()` (osascript) | `os/exec`, `os` | | `internal/platform/linux.go` | 实现 `InstallFont()` (~/.local/share/fonts)、`RunAsAdmin()` (pkexec/sudo) | `os/exec`, `os` | **新建文件:** | 文件 | 说明 | |---|---| | `internal/platform/platform_test.go` | 平台层单元测试 (使用 build tags 分平台) | **额外库(仅 Windows 构建时需要):** ``` 第三方库: golang.org/x/sys/windows/registry — Windows 注册表操作 (设置环境变量、安装字体) ``` **详细功能描述:** #### 1.4.1 Platform 接口扩展 在现有 `Platform` 接口中添加以下方法签名: ```go type Platform interface { // 现有方法 Name() string // 返回平台名称 "windows"/"darwin"/"linux" SetEnvVar(key, value string) error GetEnvVar(key string) string // 新增方法 InstallFont(fontPath string) error // 安装字体到系统 RunAsAdmin(cmd string, args ...string) error // 以管理员权限执行命令 HomeDir() string // 返回用户家目录 IsAdmin() bool // 检查当前是否具有管理员权限 CommandExists(name string) bool // 检查命令是否可用 } ``` #### 1.4.2 InstallFont 各平台实现 **Windows:** 1. 复制字体文件到 `C:\Windows\Fonts\`(需要管理员权限) 2. 在注册表 `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts` 添加条目 3. 注册表键名 = 字体显示名称 + " (TrueType)",值 = 文件名 4. 调用 `SendMessage(HWND_BROADCAST, WM_FONTCHANGE)` 通知其他程序 5. 如果非管理员,自动调用 `RunAsAdmin` 提权重试 **macOS:** 1. 复制字体文件到 `~/Library/Fonts/`(用户级,无需管理员) 2. 无需额外注册,macOS 自动识别该目录中的字体文件 3. 如需全局安装:复制到 `/Library/Fonts/`(需管理员权限) **Linux:** 1. 复制字体文件到 `~/.local/share/fonts/`(用户级) 2. 执行 `fc-cache -f -v` 刷新字体缓存 3. 验证:`fc-list | grep ` 确认已安装 **边界情况处理:** - 字体文件不存在或格式无效(仅支持 .ttf / .otf / .woff2)→ 返回明确错误 - 字体已安装(同名文件已存在)→ 跳过并记录日志 - 权限不足 → 尝试 `RunAsAdmin` 提权,失败则返回 `E5001` 错误 - 磁盘空间不足 → 预检查可用空间 #### 1.4.3 RunAsAdmin 各平台实现 **Windows:** ```go // 使用 ShellExecute "runas" 动词 cmd := exec.Command("cmd", "/C", command) cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, } // 或者调用 ShellExecuteEx API 通过 "runas" 触发 UAC 弹窗 ``` - 用户取消 UAC 弹窗 → 返回 `ErrUserCancelled` 错误 - 检测是否已有管理员权限 → 如果已有则直接执行 **macOS:** ```bash osascript -e 'do shell script "" with administrator privileges' ``` - 系统弹窗要求输入密码 - 用户取消 → osascript 退出码非零 **Linux:** ```bash # 优先尝试 pkexec (图形化密码弹窗) pkexec # 回退到 sudo (终端密码输入) sudo ``` - 检测 `pkexec` 是否可用,不可用则回退 `sudo` - `sudo` 需要终端 TTY,如果没有 TTY 则报错 #### 1.4.4 SetEnvVar 增强 **Windows(永久设置):** 1. 打开注册表 `HKCU\Environment` 2. 设置 `REG_SZ` 或 `REG_EXPAND_SZ` 类型的键值 3. 调用 `SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment")` 广播变更 4. PATH 变量特殊处理:追加而非覆盖,使用 `;` 分隔 **macOS / Linux(永久设置):** 1. 检测当前 Shell 类型(bash/zsh/fish) 2. 向对应 profile 文件追加 `export KEY="VALUE"` 行 3. Shell profile 文件优先级: - Bash: `~/.bashrc` > `~/.bash_profile` > `~/.profile` - Zsh: `~/.zshrc` - Fish: `~/.config/fish/config.fish` (语法: `set -gx KEY VALUE`) 4. 检查是否已有同名 export 行,有则替换而非追加 5. PATH 变量:使用 `export PATH="$PATH:"` 追加 --- ### 任务 1.5 — init 命令实现 ✅ 已完成 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/init.go` | 实现完整的 init 逻辑:创建目录、写入默认 config.yaml、创建默认 Profile、支持 `--template` / `--profile` / `--force` | `internal/config`, `internal/profile`, `internal/logging` | **依赖的内部包:** ``` internal/config — DefaultPaths(), EnsureDirs() internal/profile — 创建默认/模板 Profile internal/logging — 日志记录 标准库: os — 文件/目录创建 fmt — 用户输出 path/filepath — 路径 ``` **✅ 实际完成情况:** 已实现完整的 init 命令: - 使用 `config.DefaultPaths()` 获取目录结构,`EnsureDirs()` 创建所有目录 - 检测 `config.yaml` 是否已存在,已存在则提示 "已初始化过" - 支持 `--force` 强制覆盖重新初始化 - `writeDefaultConfig()` 生成包含合理默认值的 config.yaml - `writeDefaultProfile()` 支持三种模板 (`--template minimal|standard|full`) - 所有路径使用 `filepath.Join` 拼接(解决 Windows `\` 问题) - 目录位于用户家目录 `~/.devpack/`(通过 `os.UserHomeDir()` 解析) --- ### 任务 1.6 — 终端 UI 工具 **新建文件:** | 文件 | 说明 | |---|---| | `internal/ui/printer.go` | 统一输出工具:Success/Error/Warn/Info 打印、Verbose 判断 | | `internal/ui/table.go` | 表格渲染封装 (基于 tablewriter) | | `internal/ui/spinner.go` | 加载动画封装 (用于扫描等操作) | **所需库:** ``` 第三方库: github.com/fatih/color — 终端颜色 (绿色✓、红色✗、黄色⚠ 等) github.com/olekukonenko/tablewriter — 终端表格 标准库: fmt — 格式化输出 os — Stdout/Stderr io — Writer 接口 strings — 字符串处理 time — 动画定时 sync — Spinner 并发控制 ``` **需修改的现有文件:** | 文件 | 改动 | |---|---| | `go.mod` | 添加 `github.com/fatih/color`、`github.com/olekukonenko/tablewriter` | **详细功能描述:** #### 1.6.1 Printer — 统一控制台输出 所有用户可见的输出都通过 Printer 统一管理,确保风格一致且受 `--quiet` / `--no-color` 标志控制。 **API 设计:** ```go type Printer struct { stdout io.Writer stderr io.Writer quiet bool // --quiet 模式:抑制 Info/Debug 输出 noColor bool // --no-color 模式:去除 ANSI 颜色码 verbose bool // --verbose 模式:显示 Debug 级别输出 } func NewPrinter(opts PrinterOptions) *Printer // 带图标的输出方法 func (p *Printer) Success(format string, a ...interface{}) // ✓ 绿色 func (p *Printer) Error(format string, a ...interface{}) // ✗ 红色 → stderr func (p *Printer) Warn(format string, a ...interface{}) // ⚠ 黄色 func (p *Printer) Info(format string, a ...interface{}) // ℹ 蓝色(quiet 模式下被抑制) func (p *Printer) Debug(format string, a ...interface{}) // • 灰色(仅 verbose 模式显示) func (p *Printer) Plain(format string, a ...interface{}) // 无图标,纯文本输出 // 辅助方法 func (p *Printer) Newline() // 输出空行 func (p *Printer) Divider() // 输出 ──────── 分隔线 ``` **输出示例:** ``` ✓ 扫描完成,发现 7 个采集器 ℹ Go Runtime — 1.22.1 (3 tools) ℹ Node.js — 20.11.0 (12 global packages) ⚠ Python — 未安装 ✗ SSH Keys — 跳过 (需要 --encrypt 标志) ``` **输出路由规则:** | 方法 | 输出目标 | quiet 模式 | verbose 要求 | |---|---|---|---| | Success | stdout | 正常输出 | 否 | | Error | stderr | 正常输出 | 否 | | Warn | stderr | 正常输出 | 否 | | Info | stdout | 被抑制 | 否 | | Debug | stdout | 被抑制 | 是 | | Plain | stdout | 被抑制 | 否 | #### 1.6.2 Table — 终端表格渲染 封装 `tablewriter` 库,提供简洁的表格构建 API,适配 DevPack 的各种数据展示需求。 **API 设计:** ```go type Table struct { headers []string rows [][]string options TableOptions } type TableOptions struct { MaxColumnWidth int // 单列最大宽度,超出截断并加 "..." MinColumnWidth int // 单列最小宽度 Border bool // 是否显示边框 HeaderColor bool // 表头是否着色(粗体+蓝色) Alignment []int // 每列对齐方式 (LEFT/CENTER/RIGHT) AutoWrap bool // 长文本是否自动换行 } func NewTable(headers ...string) *Table func (t *Table) SetOptions(opts TableOptions) *Table func (t *Table) AddRow(values ...string) *Table func (t *Table) AddRows(rows [][]string) *Table func (t *Table) Render() string // 返回渲染后的字符串 func (t *Table) RenderTo(w io.Writer) // 直接输出到 Writer ``` **输出效果示例:** ``` ┌────────────────────┬──────────┬─────────────┬──────────┐ │ Collector │ Category │ Items │ Size │ ├────────────────────┼──────────┼─────────────┼──────────┤ │ Go Runtime │ runtime │ 3 tools │ 2.1 MB │ │ Node.js Runtime │ runtime │ 12 packages │ 1.5 MB │ │ VS Code │ editor │ 35 ext │ 4.2 MB │ │ PowerShell │ shell │ 5 modules │ 0.3 MB │ └────────────────────┴──────────┴─────────────┴──────────┘ ``` #### 1.6.3 Spinner — 加载动画 用于扫描、打包、还原等耗时操作的等待提示。 **API 设计:** ```go type Spinner struct { message string frames []rune // 动画帧序列 mu sync.Mutex active bool done chan struct{} } func NewSpinner(message string) *Spinner func (s *Spinner) Start() // 启动动画(后台 goroutine) func (s *Spinner) Update(message string) // 更新显示消息 func (s *Spinner) Stop() // 停止动画 func (s *Spinner) StopWithSuccess(message string) // 停止并显示 ✓ 成功消息 func (s *Spinner) StopWithError(message string) // 停止并显示 ✗ 失败消息 ``` **行为规范:** - 动画帧:`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`(Braille 点阵),每 100ms 切换一帧 - 线程安全:`Start/Stop/Update` 通过 `sync.Mutex` 保护 - 非 TTY 检测:CI 环境(无交互式终端)下不显示动画,只打印一次消息 - `--quiet` 模式:完全不显示 spinner - `--no-color` 模式:不显示动画帧,只显示文本 **输出效果:** ``` ⠹ 正在扫描环境... ↓ (100ms 后) ⠼ 正在扫描环境... ↓ (完成后) ✓ 扫描完成 (耗时 2.3s) ``` --- ### 任务 1.7 — 错误处理系统 **新建文件:** | 文件 | 说明 | |---|---| | `internal/errors/errors.go` | DevPackError 结构体、ErrorLevel 枚举、CollectorError、格式化输出 | | `internal/errors/errors_test.go` | 错误处理单元测试 | **所需库:** ``` 标准库: fmt — 错误格式化 errors — errors.Is / errors.As 兼容 strings — 消息拼接 ``` **详细功能描述:** #### 1.7.1 DevPackError 结构体 ```go type ErrorLevel int const ( LevelWarning ErrorLevel = iota // 可继续执行的问题 LevelError // 需要处理但可恢复的错误 LevelFatal // 不可恢复的致命错误 ) type DevPackError struct { Code string // 错误码,如 "E2001" Message string // 用户可读的中文错误消息 Cause error // 被包装的原始错误 Level ErrorLevel // 错误级别 Suggestion string // 建议的修复操作 Context map[string]string // 附加上下文信息 } // 实现 error 接口 func (e *DevPackError) Error() string // 实现 errors.Unwrap func (e *DevPackError) Unwrap() error // 格式化输出(面向用户的友好格式) func (e *DevPackError) Format() string ``` #### 1.7.2 错误码体系 | 范围 | 类别 | 示例 | |---|---|---| | **E1xxx** | 配置错误 | E1001: 配置文件解析失败
E1002: Profile 格式无效
E1003: 路径不存在
E1010: Profile 名称非法
E1011: Profile 名称重复
E1012: 采集器名称未注册
E1013: enabled/disabled 冲突
E1016: Profile 版本不兼容 | | **E2xxx** | 采集器错误 | E2001: 工具未安装(如 `go` 命令不存在)
E2002: 命令执行失败(退出码非零)
E2003: 输出解析错误(JSON 格式异常)
E2004: 操作超时(超过设定的 timeout)
E2005: 权限不足(需要管理员权限)
E2006: 版本不兼容 | | **E3xxx** | 打包错误 | E3001: 归档创建失败
E3002: 校验和不匹配
E3003: Manifest 格式无效
E3004: 压缩失败
E3005: Pack 文件过大 | | **E4xxx** | 还原错误 | E4001: Pack 文件不存在
E4002: 平台不匹配(Windows → macOS)
E4003: 冲突未解决
E4004: 安装失败
E4005: 回滚失败
E4006: 还原点创建失败 | | **E5xxx** | 系统错误 | E5001: 权限不足
E5002: 磁盘空间不足
E5003: 网络连接失败
E5004: 文件系统错误
E5005: 用户取消操作 | #### 1.7.3 辅助构造函数 ```go // 快速创建错误 func New(code, message string) *DevPackError func Newf(code, format string, args ...interface{}) *DevPackError func Wrap(cause error, code, message string) *DevPackError // 特化构造函数(常用场景) func CollectorNotFound(name string) *DevPackError func CommandFailed(cmd string, err error) *DevPackError func ParseError(format string, cause error) *DevPackError func TimeoutError(operation string, duration time.Duration) *DevPackError func PlatformMismatch(source, target string) *DevPackError ``` #### 1.7.4 用户可见的错误输出格式 ``` ✗ E2001: Go 运行时未安装 详情: exec: "go": executable file not found in %PATH% 建议: 请先安装 Go — https://go.dev/dl/ ``` ``` ✗ E4002: 平台不匹配 详情: Pack 来源平台 "darwin/arm64",当前平台 "windows/amd64" 建议: DevPack 目前不支持跨操作系统还原,请在相同 OS 类型的机器上使用 ``` #### 1.7.5 错误聚合器 用于并发扫描/还原等场景,收集多个采集器的错误: ```go type ErrorCollector struct { errors []*DevPackError mu sync.Mutex } func (ec *ErrorCollector) Add(err *DevPackError) func (ec *ErrorCollector) HasErrors() bool func (ec *ErrorCollector) HasFatal() bool // 是否包含致命错误 func (ec *ErrorCollector) Errors() []*DevPackError func (ec *ErrorCollector) Summary() string // "3 个错误,2 个警告" ``` --- ## M2: 核心采集器(第 3-5 周) ### 任务 2.1 — Go 运行时采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/runtime/go_collector.go` | Go 采集器: 扫描版本、GOPATH、GOROOT、go env、GOPATH/bin 下的工具 | | `internal/collector/runtime/go_collector_test.go` | Go 采集器单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `go version`、`go env -json`、`go install` encoding/json — 解析 `go env -json` 输出 context — 命令超时控制 path/filepath — GOPATH/bin 扫描 os — 文件遍历 strings — 输出解析 runtime — 获取当前 Go 信息 fmt — 格式化 ``` **详细功能描述:** #### 2.1.1 IsAvailable 检测逻辑 ``` 1. exec.LookPath("go") 检查 go 命令是否在 PATH 中 2. 如果找到,执行 `go version` 确认可用 3. 返回 true/false ``` #### 2.1.2 Scan 扫描数据结构 扫描结果写入 `ScanResult`,包含以下 `ScanItem` 条目: | ScanItem.Name | Type | 数据来源 | Properties 示例 | |---|---|---|---| | `Go Runtime` | binary | `go version` | `{"version": "1.22.1", "os": "windows", "arch": "amd64"}` | | `GOPATH` | config | `go env GOPATH` | `{"path": "/Users/dev/go"}` | | `GOROOT` | config | `go env GOROOT` | `{"path": "/usr/local/go"}` | | `Go Env (full)` | config | `go env -json` | 完整的 go env 输出(约 30 个键值对) | | `gopls` | binary | GOPATH/bin 扫描 | `{"module": "golang.org/x/tools/gopls", "version": "v0.15.0"}` | | `dlv` | binary | GOPATH/bin 扫描 | `{"module": "github.com/go-delve/delve/cmd/dlv"}` | **go env -json 重要键值:** - `GOPATH` / `GOROOT` / `GOBIN` / `GOCACHE` / `GOMODCACHE` - `GOPROXY` / `GONOSUMDB` / `GONOPROXY`(代理设置对中国大陆用户尤为重要) - `CGO_ENABLED` / `CC` / `CXX` #### 2.1.3 GOPATH/bin 工具检测算法 ``` 1. 获取 GOPATH(go env GOPATH),默认 ~/go 2. 扫描 GOPATH/bin/ 目录(Windows 下为 GOPATH\bin\) 3. 对每个可执行文件: a. 跳过名为 "go" 和 "gofmt" 的文件(运行时自带) b. 尝试 `go version -m ` 获取 module 路径和版本 c. 如果成功,记录 {name, module, version} d. 如果失败(非 Go 编译的二进制),记录 {name, unknown} 4. 将工具列表按名称字母排序 ``` #### 2.1.4 Capture 输出文件 在目标目录下生成两个文件: **metadata.json:** ```json { "version": "1.22.1", "os": "windows", "arch": "amd64", "env": { "GOPATH": "C:\\Users\\dev\\go", "GOROOT": "C:\\Program Files\\Go", "GOPROXY": "https://goproxy.cn,direct", "CGO_ENABLED": "1" } } ``` **tools.json:** ```json [ {"name": "gopls", "module": "golang.org/x/tools/gopls", "version": "v0.15.3"}, {"name": "dlv", "module": "github.com/go-delve/delve/cmd/dlv", "version": "v1.22.1"}, {"name": "staticcheck", "module": "honnef.co/go/tools/cmd/staticcheck", "version": "v0.4.7"} ] ``` #### 2.1.5 Restore 还原流程 ``` 1. 检测当前 Go 版本 ├── 未安装 → 记录 action=INSTALL ├── 版本相同 → 记录 action=SKIP └── 版本不同 → 记录 action=UPGRADE(提示用户手动升级或使用包管理器) 2. 还原环境变量 ├── 设置 GOPROXY(尤其是自定义代理) ├── 设置 GONOSUMDB / GONOPROXY(如有) └── 不设置 GOPATH/GOROOT(使用目标机默认值) 3. 安装工具(并行,最多 4 个 goroutine) ├── 对每个 tool: `go install @` ├── 如果指定版本安装失败 → 尝试 `@latest` └── 记录成功/失败状态 4. 生成还原报告 ``` #### 2.1.6 边界情况 | 场景 | 处理方式 | |---|---| | Go 未安装 | `IsAvailable()` 返回 false,跳过此采集器 | | GOPATH 未设置 | 使用默认值 `~/go` | | GOPATH/bin 目录不存在 | 工具列表为空,不报错 | | `go env -json` 执行超时 | 返回超时错误,使用默认的 30 秒超时 | | GOPATH/bin 中有非 Go 二进制 | 记录为 `{type: "unknown"}`,不纳入还原范围 | | 目标机 Go 版本低于源机 | 生成 UPGRADE 建议但不强制升级 | | `go install` 网络失败 | 记录错误,继续安装下一个工具 | --- ### 任务 2.2 — Node.js 运行时采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/runtime/node_collector.go` | Node.js 采集器: 扫描 node/npm/nvm 版本、全局包 | | `internal/collector/runtime/node_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `node --version`、`npm --version`、`npm list -g --json` encoding/json — 解析 npm list JSON context — 超时 strings — 版本解析 fmt — 格式化 ``` **详细功能描述:** #### 2.2.1 扫描范围 | 检测项 | 命令 | 说明 | |---|---|---| | Node.js 版本 | `node --version` | 输出如 `v20.11.0` | | npm 版本 | `npm --version` | 输出如 `10.2.4` | | 全局包列表 | `npm list -g --json --depth=0` | JSON 格式的全局安装包 | | yarn 版本 | `yarn --version` | 可选检测 | | pnpm 版本 | `pnpm --version` | 可选检测 | | nvm/fnm 检测 | `nvm version` / `fnm current` | 检测版本管理器 | #### 2.2.2 npm 全局包 JSON 解析 `npm list -g --json --depth=0` 输出格式: ```json { "dependencies": { "typescript": { "version": "5.3.3" }, "ts-node": { "version": "10.9.2" }, "nodemon": { "version": "3.0.2" }, "@angular/cli": { "version": "17.1.0" } } } ``` 解析逻辑: 1. 反序列化为 `map[string]interface{}` 2. 遍历 `dependencies` 字典,提取 `name` 和 `version` 3. 过滤掉 `npm` 自身(npm 随 node 安装,不单独还原) 4. 按名称排序 #### 2.2.3 版本管理器检测 按优先级检测以下版本管理器: | 管理器 | 检测命令 | 环境变量 | |---|---|---| | nvm (Windows) | `nvm version` | `NVM_HOME` | | nvm (Unix) | `nvm --version` (shell function) | `NVM_DIR` | | fnm | `fnm --version` | `FNM_DIR` | | Volta | `volta --version` | `VOLTA_HOME` | 如果检测到版本管理器,还原时优先通过版本管理器安装 Node.js,而非直接下载安装包。 #### 2.2.4 Capture 输出文件 **metadata.json:** ```json { "node_version": "20.11.0", "npm_version": "10.2.4", "version_manager": "fnm", "yarn_version": "1.22.21", "pnpm_version": null } ``` **global-packages.json:** ```json [ {"name": "typescript", "version": "5.3.3"}, {"name": "ts-node", "version": "10.9.2"}, {"name": "nodemon", "version": "3.0.2"} ] ``` #### 2.2.5 Restore 还原策略 ``` 1. 检测 Node.js 是否已安装 ├── 已安装且版本匹配 → SKIP ├── 已安装但版本不同 → │ ├── 有版本管理器 → `fnm install ; fnm use ` │ └── 无版本管理器 → 提示用户手动升级,记录 UPGRADE 建议 └── 未安装 → ├── 有版本管理器 → `fnm install ` └── 无版本管理器 → 提示用户安装(提供下载链接) 2. 安装全局包(串行,避免 npm 并发冲突) ├── `npm install -g @` ├── 安装失败 → 尝试不指定版本 `npm install -g ` └── 记录每个包的安装结果 ``` #### 2.2.6 边界情况 | 场景 | 处理方式 | |---|---| | Node.js 未安装 | `IsAvailable()` 返回 false | | npm 损坏 | `npm list -g` 执行失败 → 返回错误信息 | | 全局包为空 | 正常记录空列表 | | 包版本在 npm 上已被移除 | 尝试 `@latest`,仍失败则记录警告 | | nvm 安装了多个版本 | 只采集当前活跃版本 | | yarn/pnpm 的全局包 | 分别执行 `yarn global list --json` / `pnpm list -g --json` | --- ### 任务 2.3 — Python 运行时采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/runtime/python_collector.go` | Python 采集器: 版本、pip 包、virtualenv 配置 | | `internal/collector/runtime/python_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `python --version`、`pip list --format=json` encoding/json — 解析 pip list JSON context — 超时 strings — 解析 fmt — 格式化 ``` **详细功能描述:** #### 2.3.1 Python 命令检测顺序 不同系统中 Python 的命令名不一致,按以下优先级尝试: | 优先级 | 命令 | 说明 | |---|---|---| | 1 | `python3` | Linux/macOS 首选 | | 2 | `python` | Windows 首选,或 Linux 上的别名 | | 3 | `py -3` | Windows Python Launcher | | 4 | `pyenv which python` | 通过 pyenv 管理的 Python | | 5 | `conda info --json` | Conda 环境 | #### 2.3.2 扫描范围 | 检测项 | 命令 | 说明 | |---|---|---| | Python 版本 | `python --version` | 输出如 `Python 3.12.2` | | pip 版本 | `pip --version` | 输出如 `pip 24.0 from ...` | | 全局 pip 包 | `pip list --format=json --user` | 用户级安装的包 | | pyenv 检测 | `pyenv versions` | 检测 pyenv 及已安装版本 | | conda 检测 | `conda info --json` | 检测 conda 环境 | | virtualenv 检测 | 检查 `VIRTUAL_ENV` 环境变量 | 不采集虚拟环境内的包 | #### 2.3.3 pip 包过滤规则 `pip list --format=json` 输出所有已安装包(可能 200+ 个),需要过滤: ``` 默认排除: - pip, setuptools, wheel(安装器自带) - pkg_resources, _distutils_hack(内部工具包) - 所有以 "_" 开头的包 Profile 过滤: - settings.python.include_packages: [glob 模式] - settings.python.exclude_packages: [glob 模式] ``` #### 2.3.4 Restore 还原策略 ``` 1. 检测 Python 是否已安装 ├── 有 pyenv → `pyenv install ; pyenv global ` ├── 有 conda → `conda create -n devpack python=` └── 都没有 → 提示用户手动安装 2. 安装 pip 包(串行,使用 --user 标志) ├── `pip install == --user` ├── 版本安装失败 → 尝试不指定版本 └── 某些包需要编译(如 numpy)→ 超时设为 5 分钟 ``` #### 2.3.5 边界情况 | 场景 | 处理方式 | |---|---| | Python 2 和 3 共存 | 仅采集 Python 3 | | 系统 Python(只读) | `pip list --user` 只列出用户安装的包 | | pip 未安装 | 记录警告,跳过包列表采集 | | conda 环境 | 使用 `conda list --json` 替代 pip list | | 虚拟环境激活状态 | 检测 `VIRTUAL_ENV`,提醒用户虚拟环境内的包不被采集 | | 包依赖冲突 | 还原时使用 `--no-deps` 避免依赖地狱 | --- ### 任务 2.4 — VS Code 编辑器采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/editor/vscode_collector.go` | VS Code 采集器: 版本、扩展列表、settings.json、keybindings.json、代码片段 | | `internal/collector/editor/vscode_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `code --version`、`code --list-extensions --show-versions`、`code --install-extension` encoding/json — 解析/生成 settings.json os — 读取 settings.json、keybindings.json 文件 path/filepath — VS Code 配置文件路径 (不同平台不同) context — 超时 strings — 扩展列表解析 io — 文件复制 fmt — 格式化 ``` **详细功能描述:** #### 2.4.1 配置文件路径(各平台) | 平台 | 用户配置目录 | |---|---| | Windows | `%APPDATA%\Code\User\` | | macOS | `~/Library/Application Support/Code/User/` | | Linux | `~/.config/Code/User/` | Insiders 版本路径:将 `Code` 替换为 `Code - Insiders` #### 2.4.2 扫描内容清单 | 采集项 | 方法 | 文件/命令 | |---|---|---| | VS Code 版本 | `code --version` | 输出三行:版本号/commit hash/架构 | | 扩展列表 | `code --list-extensions --show-versions` | 每行 `publisher.name@version` | | settings.json | 文件读取 | 用户设置(不含默认设置) | | keybindings.json | 文件读取 | 自定义快捷键 | | 代码片段 | 目录读取 | `snippets/` 目录下所有 `.json` 文件 | #### 2.4.3 扩展列表处理 `code --list-extensions --show-versions` 输出示例: ``` ms-python.python@2024.0.1 golang.go@0.41.0 esbenp.prettier-vscode@10.1.0 ``` 解析逻辑: 1. 按行分割,每行格式 `.@` 2. 过滤掉 Profile 中 `exclude_extensions` 匹配的扩展 3. 识别扩展类别:语言支持、主题、工具、远程开发 4. 记录扩展总数和预计还原时间(约 2 秒/个扩展) #### 2.4.4 settings.json 处理策略 settings.json 可能包含机器特定路径(如 `terminal.integrated.defaultProfile`),需要智能处理: **Capture(采集)时:** - 完整复制 settings.json - 标记机器特定设置(含绝对路径的设置项) - 记录文件大小和设置项数量 **Restore(还原)时的合并策略:** | 策略 | 说明 | 适用场景 | |---|---|---| | `overwrite` | 完全覆盖目标 settings.json | 新机器(无个性化设置) | | `merge` | 深度合并:源设置覆盖同名键,保留目标独有键 | 已有部分设置 | | `skip` | 不触碰 settings.json | 用户不想改变设置 | | `prompt` | 列出冲突的键,让用户逐个选择 | 交互式还原 | 深度合并算法: ``` 对于每个键 K: if K 只在源中存在 → 添加到目标 if K 只在目标中存在 → 保留不变 if K 两者都有: if 值相同 → 保留 if 值不同: if 值是 object → 递归合并 if 值是其他类型 → 使用源的值(优先还原) ``` #### 2.4.5 Capture 输出文件 ``` vscode/ ├── extensions.json # 扩展列表 ├── settings.json # 用户设置(原样复制) ├── keybindings.json # 快捷键(原样复制) └── snippets/ # 代码片段目录 ├── go.json ├── python.json └── markdown.json ``` #### 2.4.6 Restore 还原流程 ``` 1. 检测 VS Code 是否已安装 ├── 命令 `code --version` 可执行 → 已安装 └── 不可执行 → 提示用户安装,跳过此采集器 2. 安装扩展(串行,每个约 2-10 秒) ├── `code --install-extension @ --force` ├── 安装失败 → 尝试不指定版本 ├── 使用进度条显示 "安装扩展 [15/35]" └── 记录每个扩展的安装结果 3. 还原配置文件 ├── 根据冲突策略处理 settings.json ├── 复制 keybindings.json(如果目标不存在直接创建,否则按策略处理) └── 复制 snippets/ 目录下所有文件 4. 验证 └── `code --list-extensions --show-versions` 检查扩展是否安装成功 ``` #### 2.4.7 边界情况 | 场景 | 处理方式 | |---|---| | VS Code 未安装 | `IsAvailable()` 返回 false | | `code` 不在 PATH 中 | 检查常见安装路径(Program Files、/Applications 等) | | Insiders 版本 | 检测 `code-insiders` 命令,使用 Insiders 路径 | | settings.json 不存在 | 正常处理,跳过设置采集 | | settings.json 有语法错误 | 原样复制,不尝试解析 | | 扩展依赖其他扩展 | VS Code 自动处理依赖,无需额外逻辑 | | 扩展已被弃用或下架 | 安装失败后记录警告 | | 同步设置已启用 | 提醒用户 Settings Sync 可能覆盖还原的设置 | --- ### 任务 2.5 — PowerShell Shell 采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/shell/powershell_collector.go` | PowerShell 采集器: 版本、已安装模块、Profile 脚本 | | `internal/collector/shell/powershell_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `pwsh -Command "Get-Module -ListAvailable | ConvertTo-Json"` encoding/json — 解析 PowerShell JSON 输出 os — 读取 $PROFILE 文件 path/filepath — Profile 路径 context — 超时 strings — 解析 io — 文件复制 ``` **详细功能描述:** #### 2.5.1 PowerShell 版本检测 优先检测 PowerShell 7+(`pwsh`),回退到 Windows PowerShell 5.1(`powershell`): | 命令 | 版本 | 说明 | |---|---|---| | `pwsh --version` | 7.x | 跨平台的 PowerShell Core | | `powershell -Command "$PSVersionTable.PSVersion.ToString()"` | 5.1 | Windows 内置 | #### 2.5.2 Profile 文件路径 PowerShell 有四个 Profile 文件,按加载顺序: | Profile 变量 | Windows 路径示例 | 说明 | |---|---|---| | `$PROFILE.AllUsersAllHosts` | `C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1` | 所有用户所有主机 | | `$PROFILE.AllUsersCurrentHost` | `...Microsoft.PowerShell_profile.ps1` | 所有用户当前主机 | | `$PROFILE.CurrentUserAllHosts` | `~\Documents\PowerShell\profile.ps1` | 当前用户所有主机 | | `$PROFILE.CurrentUserCurrentHost` | `~\Documents\PowerShell\Microsoft.PowerShell_profile.ps1` | 当前用户当前主机 | **采集策略:** 只采集 `CurrentUserAllHosts` 和 `CurrentUserCurrentHost` 两个用户级 Profile。 #### 2.5.3 模块扫描 ```powershell Get-Module -ListAvailable | Where-Object { $_.ModuleBase -like "$env:USERPROFILE*" } | Select-Object Name, Version, ModuleBase | ConvertTo-Json ``` 过滤逻辑:只采集用户级安装的模块(路径在用户目录下),排除系统预装模块。 常见用户安装模块示例: - `posh-git` — Git 集成 - `oh-my-posh` — 终端美化 - `PSReadLine` — 命令行编辑增强 - `Terminal-Icons` — 文件图标 - `z` — 目录快速跳转 #### 2.5.4 Restore 还原流程 ``` 1. 检测 PowerShell 版本 └── 如果源机使用 pwsh 7.x 但目标机只有 5.1 → 提示安装 PowerShell 7 2. 安装模块 ├── `Install-Module -Name -RequiredVersion -Force -Scope CurrentUser` ├── 如果 PSGallery 仓库未注册 → 先 `Register-PSRepository` ├── 如果受 ExecutionPolicy 限制 → 提示修改执行策略 └── 记录安装结果 3. 还原 Profile 脚本 ├── 备份现有 Profile(如果存在)为 `.bak` ├── 写入采集的 Profile 内容 └── 提示用户重新打开终端生效 ``` #### 2.5.5 边界情况 | 场景 | 处理方式 | |---|---| | 仅有 Windows PowerShell 5.1 | 使用 `powershell` 命令 | | ExecutionPolicy = Restricted | 提示用户执行 `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser` | | PSGallery 不受信任 | 自动 `Set-PSRepository -Name PSGallery -InstallationPolicy Trusted` | | Profile 文件不存在 | 正常处理,跳过 Profile 采集 | | 模块版本在 PSGallery 不可用 | 尝试安装最新版本 | --- ### 任务 2.6 — Scoop 包管理器采集器 (Windows) **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/package/scoop_collector.go` | Scoop 采集器: 已安装包、bucket 列表 | | `internal/collector/package/scoop_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `scoop list`、`scoop bucket list`、`scoop install` encoding/json — 解析 scoop export JSON context — 超时 strings — 输出解析 os — Scoop 目录检测 fmt — 格式化 ``` **详细功能描述:** #### 2.6.1 Scoop 检测 ``` 1. exec.LookPath("scoop") 检查命令 2. 或检查 %USERPROFILE%\scoop\shims\ 目录是否存在 3. 或检查 %SCOOP% 环境变量 ``` #### 2.6.2 扫描数据格式 **Bucket 列表(`scoop bucket list`):** ``` main extras versions nerd-fonts java ``` **包列表(`scoop export`)JSON 格式:** ```json { "apps": [ {"Name": "7zip", "Version": "23.01", "Source": "main"}, {"Name": "git", "Version": "2.43.0", "Source": "main"}, {"Name": "vscode", "Version": "1.85.1", "Source": "extras"}, {"Name": "firacode-nf", "Version": "3.1.1", "Source": "nerd-fonts"} ], "buckets": [ {"Name": "main", "Source": "https://github.com/ScoopInstaller/Main"}, {"Name": "extras", "Source": "https://github.com/ScoopInstaller/Extras"}, {"Name": "nerd-fonts", "Source": "https://github.com/matthewjberger/scoop-nerd-fonts"} ] } ``` #### 2.6.3 Restore 还原流程 ``` 1. 检测 Scoop 是否安装 └── 未安装 → 提示用户安装 (irm get.scoop.sh | iex),跳过此采集器 2. 添加 Bucket(串行,避免冲突) ├── `scoop bucket add []` ├── Bucket 已存在 → 跳过 └── URL 无效 → 记录警告,部分包可能安装失败 3. 安装包(串行) ├── `scoop install ` ├── 不指定版本(Scoop 总是安装最新版本,版本管理通过 scoop reset 处理) ├── 安装失败 → 记录错误并继续 └── 使用进度条 "安装 Scoop 包 [15/42]" 4. 验证 └── `scoop list` 检查包是否安装 ``` #### 2.6.4 Capture 输出文件 **packages.json:** ```json [ {"name": "7zip", "version": "23.01", "bucket": "main"}, {"name": "git", "version": "2.43.0", "bucket": "main"}, {"name": "vscode", "version": "1.85.1", "bucket": "extras"} ] ``` **buckets.json:** ```json [ {"name": "main", "url": "https://github.com/ScoopInstaller/Main"}, {"name": "extras", "url": "https://github.com/ScoopInstaller/Extras"} ] ``` #### 2.6.5 边界情况 | 场景 | 处理方式 | |---|---| | Scoop 未安装 | `IsAvailable()` 返回 false(仅 Windows) | | 非 Windows 平台 | 不注册此采集器 | | 自定义 Bucket URL 失效 | 记录警告,尝试跳过该 Bucket 的包 | | 包被 hold(锁定版本) | 记录 hold 状态,还原时提示 | | 包依赖其他包 | Scoop 自动处理依赖 | | Scoop 目录为自定义路径 | 从 `%SCOOP%` 或 `scoop prefix scoop` 获取 | --- ### 任务 2.7 — Git 配置采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/git/git_collector.go` | Git 采集器: 全局配置、别名 | | `internal/collector/git/git_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os/exec — 执行 `git config --global --list`、`git config --global ` context — 超时 strings — key=value 解析 bufio — 逐行读取 .gitconfig os — 读取 ~/.gitconfig 文件 path/filepath — 路径 fmt — 格式化 ``` **详细功能描述:** #### 2.7.1 扫描配置分区 `git config --global --list` 输出所有全局配置,按分区(section)分组采集: | 分区 | 采集 | 说明 | |---|---|---| | `user.*` | ✅ | user.name, user.email | | `alias.*` | ✅ | 所有 git 别名 | | `core.*` | ✅ | core.editor, core.autocrlf, core.eol | | `push.*` | ✅ | push.default, push.autoSetupRemote | | `pull.*` | ✅ | pull.rebase | | `diff.*` | ✅ | diff.tool | | `merge.*` | ✅ | merge.tool, merge.conflictstyle | | `init.*` | ✅ | init.defaultBranch | | `credential.*` | ❌ | 跳过凭据信息(安全敏感) | | `http.*` | ⚠️ | 保留 proxy 设置,跳过 sslVerify=false | | `url.*` | ⚠️ | 保留 insteadOf 重写规则 | #### 2.7.2 Capture 输出文件 **gitconfig.json:** ```json { "user": { "name": "开发者", "email": "dev@example.com" }, "alias": { "st": "status", "co": "checkout", "br": "branch", "lg": "log --oneline --graph --all" }, "core": { "editor": "code --wait", "autocrlf": "true" }, "push": { "default": "current", "autoSetupRemote": "true" }, "init": { "defaultBranch": "main" } } ``` #### 2.7.3 Restore 还原流程 ``` 1. 检测 Git 是否安装 └── `git --version` 可执行 2. 逐条设置 Git 配置 ├── `git config --global user.name "开发者"` ├── `git config --global alias.st "status"` ├── ... └── 使用冲突策略决定是否覆盖已有配置 3. 注意事项 ├── user.email / user.name 如果目标机已有 → 默认 SKIP(用户可能有不同身份) ├── alias → 默认 MERGE(追加不存在的别名) └── core/push/pull → 默认 OVERWRITE ``` #### 2.7.4 边界情况 | 场景 | 处理方式 | |---|---| | Git 未安装 | `IsAvailable()` 返回 false | | ~/.gitconfig 不存在 | 正常返回空配置 | | 配置值包含特殊字符 | 使用 `git config --global --get ` 逐个读取 | | include.path 指令 | 记录但不递归展开(可能引用不存在的文件) | | 凭据管理器配置 | 跳过 `credential.*` 下的所有配置 | | user.signingkey | 跳过(GPG 密钥不通过此采集器迁移) | --- ### 任务 2.8 — 环境变量采集器 **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/env/env_collector.go` | 环境变量采集器: 按 Profile 中定义的 include/exclude pattern 过滤 | | `internal/collector/env/env_collector_test.go` | 单元测试 | **所需库:** ``` 标准库: os — os.Environ() 获取所有环境变量 strings — key=value 拆分、pattern 匹配 path/filepath — filepath.Match 用于 glob 匹配 context — 超时 fmt — 格式化 encoding/json — 序列化 内部依赖: internal/platform — SetEnvVar() 设置环境变量 (不同平台不同) ``` **详细功能描述:** #### 2.8.1 环境变量过滤算法 ``` 输入: os.Environ() → ["GOPATH=C:\Users\dev\go", "PATH=...", "SECRET_KEY=abc", ...] 算法: for each env in environ: key, value = split(env, "=", 2) // 第一层: 敏感检测(硬编码规则) if containsAny(key, ["SECRET", "TOKEN", "PASSWORD", "KEY", "CREDENTIAL", "AUTH"]): skip(安全原因) continue // 第二层: 系统变量排除(硬编码) if key in ["PATH", "HOME", "USER", "SHELL", "TERM", "DISPLAY", "LOGNAME", "HOSTNAME", "PWD", "OLDPWD", "SHLVL", "LANG", "LC_*", "XDG_*", "SESSION_*", "DBUS_*", "WINDOWID", "COLORTERM"]: skip(系统/会话相关) continue // 第三层: Profile include 模式匹配 if profile.settings.env.include_patterns 不为空: if 没有任何 include pattern 匹配 key: skip continue // 第四层: Profile exclude 模式匹配 if 有任何 exclude pattern 匹配 key: skip continue // 通过所有过滤 → 采集此变量 result.append({key, value}) ``` #### 2.8.2 PATH 变量特殊处理 PATH 变量不直接采集整个值(太长且含系统路径),而是提取开发相关的路径段: ``` PATH 提取规则: 1. 按分隔符拆分 PATH(Windows: ";", Unix: ":") 2. 过滤出开发相关路径: - 包含 "go/bin" 或 "Go\bin" - 包含 ".cargo/bin" - 包含 ".npm" 或 "node_modules" - 包含 "pyenv" 或 ".local/bin" - 包含 "scoop" 或 "Scoop" - 包含 "maven" 或 "gradle" 3. 将匹配的路径段记录为 PATH_ADDITIONS 4. 还原时追加到目标机的 PATH 中 ``` #### 2.8.3 Capture 输出文件 **variables.json:** ```json { "variables": [ {"key": "GOPATH", "value": "C:\\Users\\dev\\go"}, {"key": "GOROOT", "value": "C:\\Program Files\\Go"}, {"key": "JAVA_HOME", "value": "C:\\Program Files\\Java\\jdk-21"}, {"key": "GOPROXY", "value": "https://goproxy.cn,direct"} ], "path_additions": [ "C:\\Users\\dev\\go\\bin", "C:\\Users\\dev\\.cargo\\bin", "C:\\Users\\dev\\scoop\\shims" ] } ``` #### 2.8.4 Restore 还原流程 ``` 1. 逐个设置环境变量 ├── 调用 platform.SetEnvVar(key, value) ├── 如果变量已存在且值不同 → 按冲突策略处理 └── 记录每个变量的设置结果 2. 处理 PATH 追加 ├── 获取当前 PATH ├── 检查每个 path_additions 是否已在 PATH 中 ├── 不在的追加到 PATH 末尾 └── 调用 platform.SetEnvVar("PATH", newPath) ``` #### 2.8.5 边界情况 | 场景 | 处理方式 | |---|---| | 环境变量值包含特殊字符 | 保持原样,shell 转义由 platform 层处理 | | PATH 极长(>8000 字符) | Windows 注册表有 PATH 长度限制,记录警告 | | 变量引用其他变量(`$HOME/go`) | 记录原始值,不展开引用 | | Windows 系统变量 vs 用户变量 | 只操作用户级变量 (HKCU) | --- ### 任务 2.9 — 采集器自动注册 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `internal/collector/registry.go` | 无需修改,现有注册机制已可用 | — | | `cmd/devpack/main.go` | 添加 `registerCollectors()` 函数,创建 Registry 实例并注册所有内置采集器 | 所有 `internal/collector/*` 子包 | **新建文件:** | 文件 | 说明 | |---|---| | `internal/collector/register.go` | `RegisterBuiltins(registry)` — 一次性注册所有内置采集器,集中管理 | **详细功能描述:** #### 2.9.1 注册机制设计 ```go // internal/collector/register.go func RegisterBuiltins(r *Registry) { // 运行时采集器 r.Register(runtime.NewGoCollector()) r.Register(runtime.NewNodeCollector()) r.Register(runtime.NewPythonCollector()) // 编辑器采集器 r.Register(editor.NewVSCodeCollector()) // Shell 采集器 r.Register(shell.NewPowerShellCollector()) // 包管理器采集器(平台相关) if goruntime.GOOS == "windows" { r.Register(pkg.NewScoopCollector()) } // if goruntime.GOOS == "darwin" { // r.Register(pkg.NewBrewCollector()) // 未来扩展 // } // 配置采集器 r.Register(git.NewGitCollector()) r.Register(env.NewEnvCollector()) } ``` #### 2.9.2 注册顺序与分类 注册顺序决定了默认的扫描和显示顺序: | 顺序 | 分类 | 采集器 | 说明 | |---|---|---|---| | 1 | `runtime` | Go, Node.js, Python | 运行时环境优先 | | 2 | `editor` | VS Code | 编辑器配置 | | 3 | `shell` | PowerShell | Shell 配置 | | 4 | `package` | Scoop | 包管理器 | | 5 | `config` | Git, Env | 通用配置 | #### 2.9.3 可用性过滤 `Registry.Available()` 方法对所有注册的采集器调用 `IsAvailable()`,只返回当前环境可用的采集器。 例如在未安装 Python 的机器上,Python 采集器不会出现在可用列表中。 --- ### 任务 2.10 — scan 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/scan.go` | 实现完整扫描逻辑:解析 flags → 加载 Profile → 选择采集器 → 并发扫描 → 格式化输出 (table/json/yaml) | `internal/collector`, `internal/profile`, `internal/ui`, `internal/logging` | **依赖的内部包:** ``` internal/collector — Registry.ScanAll() / ListByCategory() internal/profile — 加载 Profile 确定启用的采集器 internal/ui — 表格渲染、颜色输出 internal/logging — 日志 internal/config — 路径 标准库: encoding/json — --output json context — 超时控制 fmt — 输出 strings — 逗号分隔解析 time — 扫描耗时 ``` **详细功能描述:** #### 2.10.1 命令完整执行流程 ``` 1. 解析命令行参数 ├── --profile 指定 Profile(默认使用 config.yaml 中的 default_profile) ├── --collectors 逗号分隔的采集器名(覆盖 Profile 配置) ├── --output 输出格式: table (默认) | json | yaml ├── --detailed 显示详细信息(每个 ScanItem 的 Properties) └── --timeout 超时时间(默认 30s) 2. 初始化采集器 ├── 创建 Registry 实例 ├── RegisterBuiltins(registry) └── 获取可用采集器列表 3. 应用 Profile 过滤 ├── 加载 Profile ├── 按 enabled/disabled 列表过滤 └── 如果指定了 --collectors,以其为准 4. 并发扫描 ├── 启动 Spinner "正在扫描环境..." ├── 为每个采集器创建 goroutine ├── 每个 goroutine: ctx, cancel := context.WithTimeout(ctx, timeout) ├── 收集 []ScanResult 到 channel └── 等待全部完成或超时 5. 输出结果 ├── table: 使用 ui.Table 渲染,按 Category 分组 ├── json: json.MarshalIndent 输出 ├── yaml: yaml.Marshal 输出 └── 底部显示汇总: "发现 X 个采集器,Y 个项目,耗时 Z 秒" ``` #### 2.10.2 并发扫描控制 ```go type scanJob struct { collector Collector result *ScanResult err error duration time.Duration } // 使用带缓冲的 channel 控制并发 results := make(chan scanJob, len(collectors)) sem := make(chan struct{}, maxConcurrency) // 最大并发数 = 4 for _, c := range collectors { go func(col Collector) { sem <- struct{}{} // 获取信号量 defer func() { <-sem }() // 释放信号量 start := time.Now() ctx, cancel := context.WithTimeout(parentCtx, timeout) defer cancel() result, err := col.Scan(ctx, opts) results <- scanJob{col, result, err, time.Since(start)} }(c) } ``` #### 2.10.3 输出格式示例 **Table 格式(默认):** ``` 🔍 环境扫描结果 运行时: ✓ Go Runtime 1.22.1 3 tools 2.1 MB ✓ Node.js Runtime 20.11.0 12 packages 1.5 MB ⚠ Python Runtime 未安装 — — 编辑器: ✓ VS Code 1.85.1 35 extensions 4.2 MB Shell: ✓ PowerShell 7.4.1 5 modules 0.3 MB 包管理器: ✓ Scoop installed 42 packages 0.1 MB 配置: ✓ Git 2.43.0 12 aliases 0.01 MB ✓ Environment Vars — 8 variables 0.001 MB ─────────────────────────────────── 合计: 7 采集器 | 115 项目 | 8.2 MB | 耗时 2.3s ``` --- ## M3: 打包引擎(第 6-7 周) ### 任务 3.1 — Pack 引擎核心 **新建文件:** | 文件 | 说明 | |---|---| | `internal/pack/engine.go` | PackEngine 结构体:创建 Pack 的核心流程控制 | | `internal/pack/engine_test.go` | Pack 引擎单元测试 | | `internal/pack/archive.go` | tar.gz 打包/解包实现 | | `internal/pack/archive_test.go` | 归档单元测试 | | `internal/pack/checksum.go` | SHA-256 校验和计算与验证 | | `internal/pack/checksum_test.go` | 校验和单元测试 | **所需库:** ``` 第三方库: github.com/google/uuid — 生成 Pack UUID 标准库: archive/tar — tar 格式写入/读取 compress/gzip — gzip 压缩/解压 crypto/sha256 — SHA-256 校验和 encoding/hex — 校验和 hex 编码 encoding/json — manifest.json 写入 io — 数据流复制 os — 文件操作 path/filepath — 路径遍历 time — 时间戳 fmt — 格式化 strings — 路径处理 ``` **需修改的现有文件:** | 文件 | 改动 | |---|---| | `pkg/manifest/manifest.go` | 添加 `Validate()` 方法、`NewManifest()` 构造函数 | | `go.mod` | 添加 `github.com/google/uuid`、`github.com/schollz/progressbar/v3` | **详细功能描述:** #### 3.1.1 .devpack 文件格式规范 `.devpack` 文件本质上是一个 `.tar.gz` 归档,内部目录结构如下: ``` .devpack (tar.gz) │ ├── manifest.json # Pack 元数据清单(必须) ├── checksum.sha256 # 所有文件的 SHA-256 校验和(必须) ├── profile.yaml # 打包时使用的 Profile 副本(必须) │ └── collectors/ # 各采集器的数据目录 ├── go/ │ ├── metadata.json # Go 版本、go env 等元数据 │ └── tools.json # GOPATH/bin 工具列表 ├── node/ │ ├── metadata.json # Node.js/npm 版本 │ └── global-packages.json ├── vscode/ │ ├── extensions.json # 扩展列表 │ ├── settings.json # 用户设置 │ ├── keybindings.json # 快捷键绑定 │ └── snippets/ # 代码片段目录 │ ├── go.json │ └── python.json ├── powershell/ │ ├── metadata.json # PowerShell 版本 │ ├── modules.json # 已安装模块列表 │ └── profile.ps1 # Profile 脚本内容 ├── scoop/ │ ├── packages.json # 已安装包列表 │ └── buckets.json # Bucket 列表及 URL ├── git/ │ └── gitconfig.json # Git 全局配置(JSON 格式) └── env/ └── variables.json # 环境变量列表 + PATH 追加段 ``` #### 3.1.2 manifest.json 完整 Schema ```json { "schema_version": 1, "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "my-dev-env", "description": "我的开发环境 - 2026年3月", "version": "1.0.0", "created_at": "2026-03-03T10:30:00+08:00", "created_by": "Administrator@DESKTOP-ABC123", "devpack_version": "0.1.0", "platform": { "os": "windows", "arch": "amd64", "os_version": "10.0.22631", "hostname": "DESKTOP-ABC123" }, "collectors": [ { "name": "go", "display_name": "Go Runtime", "category": "runtime", "item_count": 5, "size_bytes": 2100000, "files": ["collectors/go/metadata.json", "collectors/go/tools.json"] } ], "summary": { "total_collectors": 7, "total_items": 115, "total_size_bytes": 8200000 }, "checksum": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } ``` #### 3.1.3 PackEngine 创建流程 ``` PackEngine.Create(ctx, opts) 流程: 1. 验证参数 ├── Pack 名称合法(非空、无特殊字符) ├── Profile 存在且有效 └── 输出路径可写 2. 创建临时工作目录 └── os.MkdirTemp("", "devpack-capture-*") 3. 并发执行 Capture(信号量控制最大并发=4) ├── 为每个启用的采集器创建 collectors// 子目录 ├── collector.Capture(ctx, targetDir, captureOpts) ├── 记录每个采集器的状态(成功/失败/跳过) └── 收集文件大小和项目数量 4. 复制 Profile 到临时目录 └── cp profile.yaml → tempDir/profile.yaml 5. 生成 manifest.json ├── 填充所有元数据字段 ├── 计算 summary(总采集器数、总项目数、总大小) └── 写入 tempDir/manifest.json 6. 计算校验和 ├── 遍历 tempDir 下所有文件 ├── 对每个文件计算 SHA-256 ├── 格式: "sha256: "(每行一个文件) └── 写入 tempDir/checksum.sha256 7. 打包为 tar.gz ├── 创建 .devpack 文件 ├── 写入 gzip → tar 数据流 ├── 遍历临时目录,逐个添加文件/目录到 tar └── 关闭 tar/gzip writer 8. 移动到 Packs 目录 └── mv .devpack → ~/.devpack/packs/.devpack 9. 清理临时目录 └── os.RemoveAll(tempDir) 10. 返回 PackResult └── {Name, Path, Size, CollectorCount, ItemCount, Duration} ``` #### 3.1.4 checksum.sha256 文件格式 ``` sha256:a1b2c3d4e5f6... manifest.json sha256:f6e5d4c3b2a1... profile.yaml sha256:1234567890ab... collectors/go/metadata.json sha256:abcdef012345... collectors/go/tools.json sha256:fedcba987654... collectors/vscode/extensions.json sha256:098765432abc... collectors/vscode/settings.json ... ``` 校验算法:逐行读取,对每个文件重新计算 SHA-256 并与记录值对比。 #### 3.1.5 archive.go 核心函数 ```go // 创建 tar.gz 归档 func CreateArchive(sourceDir, outputPath string) error // 解压 tar.gz 归档 func ExtractArchive(archivePath, outputDir string) error // 遍历目录树,逐个添加到 tar writer func addToTar(tw *tar.Writer, basePath, relativePath string) error ``` **安全处理:** - 解压时检查路径穿越攻击(`../` 开头的路径) - 限制单个文件最大大小(默认 100MB) - 限制总文件数(默认 10000 个) --- ### 任务 3.2 — capture 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/capture.go` | 实现完整捕获流程:解析 flags → 加载 Profile → 创建临时目录 → 并发运行 Capture() → 生成 Manifest → 打包 → 输出结果 | `internal/pack`, `internal/collector`, `internal/profile`, `internal/ui`, `internal/logging` | **依赖的内部包:** ``` internal/pack — PackEngine.CreatePack() internal/collector — Registry + 各采集器 internal/profile — 加载 Profile internal/ui — 进度条、表格 internal/logging — 日志 internal/config — 路径 (Packs 目录) pkg/manifest — Manifest 构建 标准库: os — 临时目录、文件 path/filepath — 路径 context — 超时 fmt — 输出 time — 时间戳 ``` **详细功能描述:** #### 3.2.1 命令执行流程 ``` devpack capture [name] [flags] 1. 解析参数 ├── name: Pack 名称(可选,默认 "env-{date}") ├── --profile: 使用的 Profile ├── --description: Pack 描述信息 ├── --collectors: 逗号分隔覆盖 Profile ├── --exclude: 排除的采集器 ├── --encrypt: 启用加密(v0.3.0+) └── --output: 输出路径(默认 ~/.devpack/packs/) 2. 预扫描(快速检测哪些采集器可用) ├── 对每个启用的采集器调用 IsAvailable() ├── 不可用的自动标记为 SKIP └── 输出计划摘要 3. 用户确认 └── "即将采集 7 个采集器的数据,是否继续?[Y/n]" 4. 执行采集 ├── 启动进度条 "采集中 [0/7]" ├── 调用 PackEngine.Create(ctx, opts) ├── 每完成一个采集器更新进度 └── 失败的采集器记录警告但不中断 5. 输出结果 └── 显示 Pack 信息(名称、路径、大小、采集器数量) ``` #### 3.2.2 自动命名规则 如果用户不指定 Pack 名称,自动生成: - 格式:`env-{YYYYMMDD}`(如 `env-20260303`) - 同名冲突:追加序号 `env-20260303-2` --- ### 任务 3.3 — export / import 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/export.go` | export: 从内部 Packs 目录复制 .devpack 到指定路径; import: 从外部路径复制 .devpack 到 Packs 目录并验证 | `internal/pack`, `internal/config`, `internal/ui` | **所需库:** ``` 标准库: os — 文件复制 io — 数据流 path/filepath — 路径 fmt — 输出 ``` **详细功能描述:** #### 3.3.1 export 流程 ``` devpack export [-o ] 1. 查找 Pack └── 在 ~/.devpack/packs/ 中查找 .devpack 2. 验证完整性 └── 可选: 重新计算校验和确认文件未损坏 3. 复制文件 ├── 默认输出到当前目录 ./.devpack ├── 大文件使用 io.Copy 带进度条 └── 复制完成后显示文件大小和路径 ``` #### 3.3.2 import 流程 ``` devpack import [-n ] [--verify] 1. 验证文件 ├── 检查文件存在且可读 ├── 检查是否为有效的 tar.gz ├── 解析 manifest.json 确认格式正确 └── --verify: 验证 checksum.sha256 中所有文件的校验和 2. 检查命名冲突 ├── 使用 manifest.name 或 --name 指定的名称 └── 同名已存在 → 提示用户覆盖或改名 3. 复制到 Packs 目录 └── cp → ~/.devpack/packs/.devpack 4. 输出结果 └── 显示 Pack 信息(来源平台、采集器数量、大小) ``` --- ### 任务 3.4 — 进度条组件 **新建文件或修改:** | 文件 | 说明 | |---|---| | `internal/ui/progress.go` | 基于 progressbar 库的进度条封装、适配打包/还原等长时操作 | **所需库:** ``` 第三方库: github.com/schollz/progressbar/v3 — 进度条渲染 标准库: os — Stderr 输出 fmt — 格式化 ``` **详细功能描述:** #### 3.4.1 ProgressTracker 接口 ```go type ProgressTracker interface { SetTotal(total int) // 设置总步骤数 Increment() // 增加一步 SetMessage(message string) // 更新当前步骤描述 SetCurrent(step int, message string) // 设置当前步骤(用于多阶段) Done() // 完成 Fail(message string) // 失败 } ``` #### 3.4.2 使用示例 ```go // 打包进度 progress := ui.NewProgress(len(collectors)) for i, c := range collectors { progress.SetCurrent(i+1, fmt.Sprintf("采集 %s", c.DisplayName())) err := c.Capture(ctx, dir, opts) progress.Increment() } progress.Done() ``` **输出效果:** ``` 采集中 [████████████░░░░░░░░] 60% | 4/7 | 采集 PowerShell ``` --- ## M4: 还原引擎(第 8-10 周) ### 任务 4.1 — 还原引擎核心 **新建文件:** | 文件 | 说明 | |---|---| | `internal/restore/engine.go` | RestoreEngine: 解包 → 校验 → 解析 Manifest → 平台检查 → 冲突检测 → 执行还原 → 验证 → 报告 | | `internal/restore/engine_test.go` | 还原引擎单元测试 | | `internal/restore/plan.go` | RestorePlan: 还原计划生成、依赖排序 (拓扑排序) | | `internal/restore/plan_test.go` | 还原计划单元测试 | | `internal/restore/conflict.go` | 冲突检测器: 版本对比、已存在检测、策略应用 | | `internal/restore/conflict_test.go` | 冲突检测单元测试 | | `internal/restore/report.go` | 还原报告: 成功/失败/跳过/警告 的汇总和输出 | **所需库:** ``` 第三方库: github.com/AlecAivazis/survey/v2 — 冲突时交互式提示 (ConflictPrompt 策略) 标准库: archive/tar — 解包 compress/gzip — 解压 crypto/sha256 — 校验和验证 encoding/hex — hex 解码 encoding/json — Manifest 解析 os — 文件操作 path/filepath — 路径 context — 超时 fmt — 格式化 sort — 依赖排序 strings — 版本对比 time — 时间 sync — 并发控制 ``` **详细功能描述:** #### 4.1.1 还原引擎完整流水线(10 步) ``` RestoreEngine.Restore(ctx, packPath, opts) 流程: ┌─────────────────────────────────────────────────────────────────┐ │ 步骤 1: 解包 │ │ ExtractArchive(packPath, tempDir) │ │ 失败 → 返回 E3001 错误 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 2: 校验 │ │ 验证 checksum.sha256 中所有文件的 SHA-256 │ │ 失败 → 返回 E3002 错误(文件可能被篡改) │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 3: 解析 Manifest │ │ 读取 manifest.json → Manifest 结构体 │ │ 验证 schema_version 兼容性 │ │ 失败 → 返回 E3003 错误 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 4: 平台检查 │ │ 比较 manifest.platform.os 与当前 runtime.GOOS │ │ 不匹配 → 除非 --force,返回 E4002 错误 │ │ 架构不匹配 → 警告但不阻止 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 5: 生成还原计划 │ │ 对每个采集器调用 IsAvailable() + 扫描当前状态 │ │ 与 Pack 数据对比,生成 RestoreAction 列表 │ │ 按依赖关系拓扑排序 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 6: 冲突检测 │ │ 对每个 RestoreAction 检查目标是否已存在 │ │ 应用 ConflictStrategy (skip/overwrite/merge/prompt/newest) │ │ prompt 策略 → 使用 survey 交互式提问 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 7: 用户确认 │ │ 显示还原计划摘要(除非 --yes 跳过确认) │ │ 用户选择 N → 取消还原 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 8: 创建还原点(除非 --no-rollback) │ │ 对每个将被修改的项目,快照当前状态 │ │ 保存到 ~/.devpack/temp/restore-point-{timestamp}/ │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 9: 执行还原 │ │ 按依赖序逐个调用 collector.Restore() │ │ 使用进度条显示 "还原中 [3/7] VS Code" │ │ 某个采集器失败: │ │ ├── Fatal 级别 → 停止并回滚 │ │ └── Error 级别 → 记录错误,继续下一个 │ ├─────────────────────────────────────────────────────────────────┤ │ 步骤 10: 验证 & 报告 │ │ 对每个已还原的采集器调用 collector.Verify() │ │ 生成还原报告(成功/失败/跳过/警告) │ │ 清理临时文件 │ └─────────────────────────────────────────────────────────────────┘ ``` #### 4.1.2 RestoreAction 数据结构 ```go type ActionType string const ( ActionInstall ActionType = "INSTALL" // 全新安装 ActionUpgrade ActionType = "UPGRADE" // 版本升级 ActionDowngrade ActionType = "DOWNGRADE" // 版本降级 ActionMerge ActionType = "MERGE" // 合并配置 ActionOverwrite ActionType = "OVERWRITE" // 覆盖 ActionSkip ActionType = "SKIP" // 跳过(已存在且一致) ) type RestoreAction struct { Collector string // 采集器名称 Item string // 项目名称(如 "Go 1.22.1") Action ActionType // 动作类型 Details string // 说明(如 "Current: 1.21.0 → 1.22.1") Priority int // 执行优先级(数字越小越先执行) DependsOn []string // 依赖的其他采集器 } ``` #### 4.1.3 依赖排序(拓扑排序) 还原顺序有依赖关系,必须按拓扑序执行: ``` 依赖图: env vars ← 无依赖(最先执行,设置 PATH 等) runtimes ← 依赖 env vars(GOPATH 等需要先设置) packages ← 依赖 runtimes(scoop install 可能需要 git) editors ← 依赖 runtimes(VS Code 扩展可能需要语言运行时) shells ← 依赖 packages(Profile 脚本可能引用已安装的工具) git config ← 依赖 runtimes(git 需要先安装) 拓扑序(默认): 1. env → 2. go/node/python → 3. scoop → 4. vscode → 5. powershell → 6. git ``` #### 4.1.4 还原点结构 ``` ~/.devpack/temp/restore-point-20260303-103000/ ├── manifest.json # 还原点元数据(时间、Pack 来源) ├── vscode/ │ └── settings.json # 备份的 settings.json ├── powershell/ │ └── profile.ps1 # 备份的 Profile 脚本 ├── git/ │ └── gitconfig-backup.json # 备份的 Git 配置 └── env/ └── variables-backup.json # 备份的环境变量值 ``` --- ### 任务 4.2 — 干运行模式 **涉及文件:** | 文件 | 改动 | |---|---| | `internal/restore/engine.go` | 在 `Restore()` 中根据 `DryRun` 标志只模拟操作、输出计划但不执行 | | `internal/restore/report.go` | DryRun 专用报告格式 | **所需库:** 无额外依赖,使用已有标准库 **详细功能描述:** #### 4.2.1 DryRun 执行范围 DryRun 模式执行流水线的步骤 1-6(解包、校验、解析、平台检查、生成计划、冲突检测),但**不执行**步骤 7-10(确认、还原点、还原、验证)。 #### 4.2.2 DryRun 输出格式 ``` 🔍 Dry Run — "my-env" 还原计划 来源: DESKTOP-ABC123 (Windows 11, amd64) 目标: LAPTOP-XYZ789 (Windows 11, amd64) ✓ 兼容 ┌─────────────────────┬─────────────┬──────────────────────────────────────┐ │ 项目 │ 动作 │ 详情 │ ├─────────────────────┼─────────────┼──────────────────────────────────────┤ │ GOPATH │ SET │ C:\Users\dev\go │ │ GOPROXY │ SET │ https://goproxy.cn,direct │ │ Go 1.22.1 │ INSTALL │ 从 golang.org 下载 │ │ gopls │ INSTALL │ go install golang.org/x/tools/gopls │ │ Node.js 20.11.0 │ SKIP │ 已安装 (20.11.0) │ │ npm 全局包 (12) │ INSTALL 8 │ 4 个已存在 │ │ Python 3.12.2 │ UPGRADE │ 当前: 3.11.7 → 3.12.2 │ │ VS Code 扩展 (35) │ INSTALL 12 │ 23 个已存在 │ │ VS Code settings │ MERGE │ 3 个设置项冲突 │ │ PowerShell 模块 (5) │ INSTALL 3 │ 2 个已存在 │ │ PowerShell Profile │ OVERWRITE │ 备份为 profile.bak │ │ Scoop 包 (42) │ INSTALL 8 │ 34 个已存在 │ │ Git aliases (12) │ MERGE │ 添加 5 个新别名 │ │ Git user.name │ SKIP │ 保留目标机的用户名 │ └─────────────────────┴─────────────┴──────────────────────────────────────┘ 汇总: 安装 31 | 升级 1 | 合并 2 | 覆盖 1 | 跳过 3 预计耗时: 约 5 分钟(取决于网络速度) 预计磁盘空间: 约 150 MB ⚠ 这是干运行模式,未执行任何更改。 运行 devpack restore my-env 以实际执行。 ``` --- ### 任务 4.3 — restore 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/restore.go` | 实现完整还原流程:解析 flags → 加载 Pack → 调用 RestoreEngine → 处理冲突 → 输出报告 | `internal/restore`, `internal/pack`, `internal/ui`, `internal/logging`, `internal/collector` | **依赖的内部包:** ``` internal/restore — RestoreEngine internal/pack — Pack 解包、校验和验证 internal/collector — 各采集器 Restore() / Verify() internal/ui — 进度条、表格、确认提示 internal/logging — 日志 internal/config — 路径 internal/profile — Profile 加载 pkg/manifest — Manifest 解析 ``` **详细功能描述:** #### 4.3.1 命令参数 ``` devpack restore [flags] 必选参数: pack-name Pack 名称或 .devpack 文件路径 可选标志: --dry-run 预览更改(不实际执行) --conflict 冲突处理策略 skip — 跳过冲突项 overwrite — 强制覆盖 merge — 尝试合并(仅配置文件) prompt — 逐个交互式询问 newest — 保留更新的版本 --no-rollback 不创建还原点 -c, --collectors 只还原指定采集器 --exclude 排除指定采集器 --parallel 并行还原数(默认: 1,串行执行更安全) -y, --yes 跳过确认提示 --force 忽略平台不匹配警告 --password 解密密码(加密 Pack 用) ``` #### 4.3.2 交互式冲突解决(prompt 策略) 使用 `survey` 库逐个询问冲突项: ``` ⚠ 冲突: VS Code settings.json 当前值 (本机): "editor.fontSize": 14 "editor.tabSize": 4 Pack 中的值: "editor.fontSize": 16 "editor.tabSize": 2 ? 选择处理方式: (Use arrow keys) > 跳过 (保留本机) 覆盖 (使用 Pack 中的) 合并 (深度合并) 查看完整差异 ``` #### 4.3.3 还原报告 ``` ✅ 还原完成 — "my-env" ┌─────────────────────┬──────────┬──────────────────────────┬──────────┐ │ 采集器 │ 状态 │ 详情 │ 耗时 │ ├─────────────────────┼──────────┼──────────────────────────┼──────────┤ │ Environment Vars │ ✓ 成功 │ 设置 4 个变量 │ 0.5s │ │ Go Runtime │ ✓ 成功 │ 安装 3 个工具 │ 45.2s │ │ Node.js Runtime │ ⊘ 跳过 │ 版本一致 │ — │ │ Python Runtime │ ⚠ 部分 │ 2/8 包安装失败 │ 120.3s │ │ VS Code │ ✓ 成功 │ 安装 12 个扩展 │ 30.1s │ │ PowerShell │ ✓ 成功 │ 安装 3 个模块 │ 15.7s │ │ Scoop │ ✓ 成功 │ 安装 8 个包 │ 180.5s │ │ Git Config │ ✓ 成功 │ 添加 5 个别名 │ 0.3s │ └─────────────────────┴──────────┴──────────────────────────┴──────────┘ 成功: 6 | 跳过: 1 | 部分: 1 | 失败: 0 总耗时: 6 分 32 秒 ⚠ Python: numpy 和 scipy 安装失败(需要 C 编译器) 建议: pip install numpy scipy --only-binary=:all: ``` --- ### 任务 4.4 — 各采集器的 Restore 逻辑 此阶段需**回到每个采集器文件**实现完整的 `Restore()` 和 `Verify()` 方法: | 文件 | Restore 逻辑 | 所需命令 | |---|---|---| | `internal/collector/runtime/go_collector.go` | `go install @latest` 安装工具 | `os/exec` | | `internal/collector/runtime/node_collector.go` | `npm install -g @` 安装全局包 | `os/exec` | | `internal/collector/runtime/python_collector.go` | `pip install ==` 安装包 | `os/exec` | | `internal/collector/editor/vscode_collector.go` | `code --install-extension ` + 复制配置文件 | `os/exec`, `io`, `os` | | `internal/collector/shell/powershell_collector.go` | `Install-Module -Name ` + 写入 Profile | `os/exec`, `os` | | `internal/collector/package/scoop_collector.go` | `scoop bucket add` + `scoop install` | `os/exec` | | `internal/collector/git/git_collector.go` | `git config --global ` | `os/exec` | | `internal/collector/env/env_collector.go` | 调用 `platform.SetEnvVar()` | `internal/platform` | **详细功能描述:** #### 4.4.1 每个采集器的 Restore 返回值 ```go type RestoreResult struct { Collector string // 采集器名称 Status RestoreStatus // Success / Partial / Skipped / Failed Items []ItemResult // 每个子项的结果 Duration time.Duration // 总耗时 Error error // 整体错误(如有) } type ItemResult struct { Name string // 如 "gopls", "typescript" Action ActionType // INSTALL / SKIP / etc. Status string // "ok" / "failed" / "skipped" Message string // 补充信息 Duration time.Duration // 此项耗时 } ``` #### 4.4.2 Verify 验证逻辑(通用模式) 每个采集器的 `Verify()` 方法检查还原后的实际状态是否符合预期: | 采集器 | 验证方法 | 预期结果 | |---|---|---| | Go | `go version` + 检查 GOPATH/bin | 版本正确,工具存在 | | Node.js | `node --version` + `npm list -g --json` | 版本正确,全局包存在 | | Python | `python --version` + `pip list --format=json` | 版本正确,包存在 | | VS Code | `code --list-extensions --show-versions` | 扩展已安装 | | PowerShell | `Get-Module -ListAvailable` | 模块已安装 | | Scoop | `scoop list` | 包已安装 | | Git | `git config --global --list` | 配置项存在 | | Env | `os.Getenv(key)` | 变量值正确 | #### 4.4.3 回滚支持 每个采集器可选实现 `Rollback()` 方法: ```go // 可选接口,不强制实现 type Rollbackable interface { Rollback(ctx context.Context, restorePointDir string) error } ``` 支持回滚的采集器: - VS Code: 恢复备份的 settings.json / keybindings.json - PowerShell: 恢复备份的 Profile 脚本 - Git: 恢复备份的 .gitconfig - Env: 恢复备份的环境变量值 不支持回滚的采集器(只能手动卸载): - Go tools: `go install` 安装的工具无自动卸载 - Node.js packages: 需要 `npm uninstall -g` - Scoop packages: 需要 `scoop uninstall` --- ## M5: 配置与打磨(第 11-12 周) ### 任务 5.1 — profile 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/profile.go` | 实现 create/show/list/delete/use 五个子命令的完整逻辑 | `internal/profile`, `internal/config`, `internal/ui`, `internal/logging` | **依赖的内部包:** ``` internal/profile — Profile CRUD 操作 internal/config — 路径 internal/ui — 表格、颜色输出 internal/logging — 日志 标准库: fmt — 输出 os — 文件操作 path/filepath — 路径 ``` **详细功能描述:** #### 5.1.1 五个子命令 **`devpack profile create `:** ``` --template 模板类型 (minimal/standard/full),默认 standard --description 描述信息 流程: 1. 验证 name 合法性 2. 检查同名 Profile 是否已存在 3. 从模板创建 Profile 4. 填入用户提供的 description 5. 保存到 ~/.devpack/profiles/.yaml 6. 如果是唯一 Profile,自动设为默认 输出: "✓ Profile 'web-dev' 已创建" ``` **`devpack profile show `:** ``` 流程: 1. 加载 Profile 2. 输出 YAML 内容(带语法高亮:键=蓝色,值=白色,注释=灰色) ``` **`devpack profile list`:** ``` 输出格式: ┌────────────┬────────────────────────┬──────────┬──────────────┐ │ Name │ Description │ Collectors │ Modified │ ├────────────┼────────────────────────┼──────────┼──────────────┤ │ * standard │ 标准开发环境配置 │ 8 │ 2026-03-03 │ │ minimal │ 最小环境(仅运行时+包) │ 3 │ 2026-03-01 │ │ full │ 完整环境(含加密数据) │ 12 │ 2026-03-02 │ └────────────┴────────────────────────┴──────────┴──────────────┘ * 表示当前默认 Profile ``` **`devpack profile delete `:** ``` 流程: 1. 检查 Profile 是否存在 2. 检查是否为当前默认 → 拒绝删除,提示先切换默认 3. 确认提示 "确定删除 Profile 'web-dev'?[y/N]" 4. 删除文件 输出: "✓ Profile 'web-dev' 已删除" ``` **`devpack profile use `:** ``` 流程: 1. 检查 Profile 存在 2. 更新 config.yaml 的 default_profile 字段 3. 保存配置 输出: "✓ 默认 Profile 已切换到 'web-dev'" ``` --- ### 任务 5.2 — list 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/list.go` | 实现 packs/profiles/collectors 列表展示,支持 `--format` 标志 | `internal/pack`, `internal/profile`, `internal/collector`, `internal/ui` | **所需标准库:** ``` encoding/json — --format json 输出 fmt — 格式化 path/filepath — 扫描 Packs 目录 os — 读取目录 time — Pack 创建时间 ``` **详细功能描述:** #### 5.2.1 三个子命令 **`devpack list packs`:** ``` ┌──────────────┬──────────────┬──────────┬──────────┬────────────────┐ │ Name │ Created │ Size │ Items │ Platform │ ├──────────────┼──────────────┼──────────┼──────────┼────────────────┤ │ my-env │ 2026-03-03 │ 8.2 MB │ 115 │ windows/amd64 │ │ work-env │ 2026-03-01 │ 5.1 MB │ 67 │ windows/amd64 │ │ home-setup │ 2026-02-28 │ 12.3 MB │ 180 │ windows/amd64 │ └──────────────┴──────────────┴──────────┴──────────┴────────────────┘ 3 个 Pack ``` **`devpack list profiles`:** 同 `devpack profile list` **`devpack list collectors`:** ``` ┌────────────────┬──────────┬──────────────────────────────┬──────────┐ │ Name │ Category │ Description │ Status │ ├────────────────┼──────────┼──────────────────────────────┼──────────┤ │ go │ runtime │ Go 运行时和工具 │ ✓ 可用 │ │ node │ runtime │ Node.js 和全局 npm 包 │ ✓ 可用 │ │ python │ runtime │ Python 和 pip 包 │ ✗ 不可用 │ │ vscode │ editor │ VS Code 扩展和设置 │ ✓ 可用 │ │ powershell │ shell │ PowerShell 模块和 Profile │ ✓ 可用 │ │ scoop │ package │ Scoop 包管理器 │ ✓ 可用 │ │ git │ config │ Git 全局配置 │ ✓ 可用 │ │ env │ config │ 环境变量 │ ✓ 可用 │ └────────────────┴──────────┴──────────────────────────────┴──────────┘ 7/8 个采集器可用 ``` --- ### 任务 5.3 — diff 命令实现 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/diff.go` | 实现环境对比逻辑:Pack vs 当前环境 / Pack vs Pack | `internal/pack`, `internal/collector`, `internal/ui` | **新建文件:** | 文件 | 说明 | |---|---| | `internal/diff/differ.go` | 差异计算引擎:逐项对比、输出 added/removed/changed | | `internal/diff/differ_test.go` | 差异引擎单元测试 | **所需库:** ``` 标准库: encoding/json — 数据加载 fmt — 输出 strings — 版本对比 sort — 排序 内部依赖: internal/ui — 彩色差异输出 (红色-/绿色+/黄色~) ``` **详细功能描述:** #### 5.3.1 差异计算算法 ``` 输入: sourceItems []ScanItem, targetItems []ScanItem 算法: 1. 建立两个 Map: sourceMap[name] = item, targetMap[name] = item 2. 遍历 sourceMap: if name 不在 targetMap → DiffType = REMOVED (红色 -) if name 在 targetMap: if version 相同 → DiffType = SAME (不显示或灰色 =) if version 不同 → DiffType = CHANGED (黄色 ~) 3. 遍历 targetMap: if name 不在 sourceMap → DiffType = ADDED (绿色 +) 4. 按 DiffType 分组排序 输出: []DiffItem{Name, Type, SourceVersion, TargetVersion} ``` #### 5.3.2 版本比较 版本比较支持语义化版本(SemVer): - `1.2.3` < `1.3.0` < `2.0.0` - 非 SemVer 格式(如日期版本 `20240101`)→ 字符串比较 - 版本带前缀 `v`(如 `v1.22.1`)→ 去掉 `v` 后比较 #### 5.3.3 diff 输出格式 ``` 📊 环境对比: "my-env" vs 当前环境 运行时: = Go Runtime 1.22.1 → 1.22.1 (一致) ~ Node.js Runtime 20.11.0 → 20.12.0 (已升级) - Python Runtime 3.12.2 → 未安装 (缺失) VS Code 扩展: + ms-vscode.live-server — → 0.4.13 (新增) - ms-azuretools.vscode-docker 0.39.0 → — (缺失) ~ golang.go 0.40.0 → 0.41.0 (已升级) = ms-python.python 2024.0.1 (一致) Scoop 包: + neovim — → 0.9.5 (新增) - telegram 4.14.0 → — (缺失) 汇总: 一致: 85 | 已变更: 8 | 新增: 12 | 缺失: 10 ``` --- ### 任务 5.4 — 测试覆盖率提升 **新建文件:** | 文件 | 说明 | |---|---| | `internal/pack/engine_integration_test.go` | Pack 引擎集成测试:完整的打包→解包流程 | | `internal/restore/engine_integration_test.go` | 还原引擎集成测试 | | `test/e2e/capture_restore_test.go` | 端到端测试:capture → export → import → restore | **所需库:** ``` 第三方库: github.com/stretchr/testify/assert — 断言 github.com/stretchr/testify/require — 必要条件断言 github.com/stretchr/testify/mock — Mock (模拟外部命令) 标准库: testing — Go 测试框架 os — 临时目录 path/filepath — 路径 io/fs — 文件遍历 ``` **详细功能描述:** #### 5.4.1 测试策略 | 测试层级 | 覆盖目标 | 方法 | |---|---|---| | **单元测试** | 每个模块的核心逻辑 | Mock 外部命令(os/exec) | | **集成测试** | 模块间交互 | 使用 mock collector,真实 Pack 引擎 | | **端到端测试** | 完整 capture → restore 流程 | 使用测试用 mock collector | #### 5.4.2 Mock 外部命令策略 通过接口抽象 `os/exec` 调用,测试时注入 mock: ```go // 命令执行器接口 type CommandRunner interface { Run(ctx context.Context, name string, args ...string) (stdout string, stderr string, err error) } // 真实实现 type ExecRunner struct{} // Mock 实现 type MockRunner struct { responses map[string]mockResponse // key = "go version", value = {stdout, stderr, err} } ``` #### 5.4.3 目标覆盖率 | 包 | 目标覆盖率 | 说明 | |---|---|---| | `internal/logging` | > 90% | ✅ 已达成(50 个测试) | | `internal/config` | > 90% | ✅ 已达成(12 个测试) | | `internal/profile` | > 80% | CRUD + 验证 | | `internal/collector/*` | > 70% | 主要测试解析逻辑,外部命令通过 Mock | | `internal/pack` | > 80% | 归档/校验和核心逻辑 | | `internal/restore` | > 80% | 计划生成/冲突检测 | | `internal/diff` | > 85% | 差异计算算法 | | `internal/ui` | > 60% | UI 组件难以完全测试 | --- ### 任务 5.5 — version 命令增强 **需修改的现有文件:** | 文件 | 改动 | 所需库 | |---|---|---| | `cmd/devpack/commands/version.go` | 添加 `--check-update` 标志,从 GitHub API 检查最新版本 | `net/http`, `encoding/json` | | `pkg/version/version.go` | 添加 `CheckLatest()` 方法 | `net/http`, `encoding/json`, `fmt` | **详细功能描述:** #### 5.5.1 版本检查逻辑 ```go func CheckLatest() (*UpdateInfo, error) { // 1. GET https://api.github.com/repos/user/devpack/releases/latest // 2. 解析 JSON 响应,提取 tag_name // 3. 比较当前版本和最新版本(语义化版本对比) // 4. 返回 UpdateInfo{Available bool, CurrentVersion, LatestVersion, ReleaseURL} } ``` #### 5.5.2 输出示例 ``` devpack version v0.1.0 (commit: abc1234, built: 2026-03-03T10:00:00Z) ✓ 已是最新版本 ``` 或: ``` devpack version v0.1.0 (commit: abc1234, built: 2026-03-03T10:00:00Z) ⚠ 有新版本可用: v0.2.0 下载: https://github.com/user/devpack/releases/tag/v0.2.0 更新: scoop update devpack ``` --- ## 文件总览与新增文件清单 ### 新建文件列表 (按模块分组) ``` internal/ ├── logging/ │ ├── logger.go ✅ zerolog 封装(已完成) │ └── logger_test.go ✅ 50 个测试(已完成) ├── config/ │ ├── config.go ✅ AppConfig 配置结构(已完成) │ ├── config_test.go ✅ 12 个测试(已完成) │ └── paths.go ✅ 路径管理(已完成) ├── profile/ │ ├── profile.go # Profile CRUD │ ├── profile_test.go │ └── templates.go # 预设模板 (minimal/standard/full) ├── ui/ │ ├── printer.go # 统一输出工具 (Success/Error/Warn/Info) │ ├── table.go # 表格渲染 │ ├── spinner.go # 加载动画 (Braille 点阵) │ └── progress.go # 进度条 ├── errors/ │ ├── errors.go # DevPackError + 错误码体系 │ └── errors_test.go ├── collector/ │ ├── register.go # RegisterBuiltins() 集中注册 │ ├── runtime/ │ │ ├── go_collector.go # Go: 版本/env/GOPATH/bin 工具 │ │ ├── go_collector_test.go │ │ ├── node_collector.go # Node.js: 版本/npm全局包/nvm │ │ ├── node_collector_test.go │ │ ├── python_collector.go # Python: 版本/pip包/pyenv │ │ └── python_collector_test.go │ ├── editor/ │ │ ├── vscode_collector.go # VS Code: 扩展/settings/snippets │ │ └── vscode_collector_test.go │ ├── shell/ │ │ ├── powershell_collector.go # PowerShell: 模块/Profile │ │ └── powershell_collector_test.go │ ├── package/ │ │ ├── scoop_collector.go # Scoop: 包/bucket (Windows) │ │ └── scoop_collector_test.go │ ├── git/ │ │ ├── git_collector.go # Git: 全局配置/别名 │ │ └── git_collector_test.go │ └── env/ │ ├── env_collector.go # 环境变量: 过滤/PATH段提取 │ └── env_collector_test.go ├── pack/ │ ├── engine.go # PackEngine (10步创建流程) │ ├── engine_test.go │ ├── engine_integration_test.go │ ├── archive.go # tar.gz 归档 (安全解压) │ ├── archive_test.go │ ├── checksum.go # SHA-256 校验和 │ └── checksum_test.go ├── restore/ │ ├── engine.go # RestoreEngine (10步还原流水线) │ ├── engine_test.go │ ├── engine_integration_test.go │ ├── plan.go # RestorePlan + 拓扑排序 │ ├── plan_test.go │ ├── conflict.go # 冲突检测 (5种策略) │ ├── conflict_test.go │ └── report.go # 还原报告 ├── diff/ │ ├── differ.go # 差异引擎 (added/removed/changed) │ └── differ_test.go ├── platform/ │ └── platform_test.go # 平台层测试 test/ └── e2e/ └── capture_restore_test.go # 端到端测试 ``` **新增文件总计:约 45 个** --- ## go.mod 最终依赖 完成 MVP 后,`go.mod` 应包含以下直接依赖: ```go require ( // CLI 框架 (已有) github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 // 日志 github.com/rs/zerolog v1.32.0 // 终端 UI github.com/fatih/color v1.16.0 github.com/olekukonenko/tablewriter v0.0.5 github.com/schollz/progressbar/v3 v3.14.2 // 交互式提示 github.com/AlecAivazis/survey/v2 v2.3.7 // 数据格式 gopkg.in/yaml.v3 v3.0.1 // 工具 github.com/google/uuid v1.6.0 // 测试 github.com/stretchr/testify v1.9.0 ) ``` v0.3.0 版本额外添加: ```go require ( golang.org/x/crypto v0.21.0 // Argon2id 密钥派生 golang.org/x/sys v0.18.0 // Windows 注册表操作 ) ``` --- ## 开发顺序建议 ``` Week 1-2: M1 (logging ✅ → config ✅ → profile → ui → errors → init ✅ → 平台完善) ↓ Week 3-5: M2 (go → node → python → vscode → powershell → scoop → git → env → scan) ↓ Week 6-7: M3 (archive → checksum → pack engine → capture → export/import) ↓ Week 8-10: M4 (plan → conflict → restore engine → 各采集器 restore → restore cmd) ↓ Week 11-12: M5 (profile cmd → list → diff → tests → 打磨) ``` 每完成一个模块就跑 `go test ./...` 确保不引入回归。