Files
DevPack/docs/DEVELOPMENT-PLAN.md
2026-03-05 13:43:11 +08:00

105 KiB
Raw Permalink Blame History

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

注意:cobraviper 已在 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.goPersistentPreRun

需修改的现有文件:

文件 改动
cmd/devpack/commands/root.go PersistentPreRunE 中初始化 Logger传递 verbose/quiet/log-level/log-file 标志
go.mod 添加 github.com/rs/zerolog 依赖

实际完成情况:

已实现完整的日志系统,核心结构如下:

  • Logger 结构体:封装 zerolog.Logger,支持控制台彩色输出 + JSON 文件输出双通道
  • Options 配置结构体:LevelLogFileVerboseQuietNoColor
  • 全局单例模式:Init(opts Options) 初始化 + G() 获取全局实例
  • 完整日志级别:Trace/Debug/Info/Warn/Error/FatalXxxf 格式化变体
  • 结构化日志方法:WithField(key, val)WithError(err)WithDuration(d)
  • Close() 方法:关闭日志文件句柄,释放资源
  • 已通过 root.goPersistentPreRunE 集成 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 结构体:包含 LogLevelLogFileDefaultProfile 及嵌套 RestoreConfigConflictStrategyCreateRestorePointDryRunFirst
  • 所有字段使用 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 的"打包规则定义文件",控制哪些采集器启用、如何过滤数据。完整结构如下:

# ~/.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 结构体定义

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
enableddisabled 不冲突 同一采集器不能同时出现在两个列表中 E1013
settings 键存在 settings 下的键必须对应已启用的采集器 E1014
include/exclude 模式合法 glob 模式可被 filepath.Match 解析 E1015
version 兼容 Profile 版本 ≤ 当前支持的最大版本 E1016

1.3.4 CRUD 操作详细流程

Create创建

  1. 接收 name + templateminimal/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
ShellPowerShell/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 接口中添加以下方法签名:

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 <font-name> 确认已安装

边界情况处理:

  • 字体文件不存在或格式无效(仅支持 .ttf / .otf / .woff2→ 返回明确错误
  • 字体已安装(同名文件已存在)→ 跳过并记录日志
  • 权限不足 → 尝试 RunAsAdmin 提权,失败则返回 E5001 错误
  • 磁盘空间不足 → 预检查可用空间

1.4.3 RunAsAdmin 各平台实现

Windows

// 使用 ShellExecute "runas" 动词
cmd := exec.Command("cmd", "/C", command)
cmd.SysProcAttr = &syscall.SysProcAttr{
    HideWindow: true,
}
// 或者调用 ShellExecuteEx API 通过 "runas" 触发 UAC 弹窗
  • 用户取消 UAC 弹窗 → 返回 ErrUserCancelled 错误
  • 检测是否已有管理员权限 → 如果已有则直接执行

macOS

osascript -e 'do shell script "<command>" with administrator privileges'
  • 系统弹窗要求输入密码
  • 用户取消 → osascript 退出码非零

Linux

# 优先尝试 pkexec (图形化密码弹窗)
pkexec <command>
# 回退到 sudo (终端密码输入)
sudo <command>
  • 检测 pkexec 是否可用,不可用则回退 sudo
  • sudo 需要终端 TTY如果没有 TTY 则报错

1.4.4 SetEnvVar 增强

Windows永久设置

  1. 打开注册表 HKCU\Environment
  2. 设置 REG_SZREG_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:<value>" 追加

任务 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/colorgithub.com/olekukonenko/tablewriter

详细功能描述:

1.6.1 Printer — 统一控制台输出

所有用户可见的输出都通过 Printer 统一管理,确保风格一致且受 --quiet / --no-color 标志控制。

API 设计:

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 设计:

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 设计:

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 结构体

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 辅助构造函数

// 快速创建错误
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 错误聚合器

用于并发扫描/还原等场景,收集多个采集器的错误:

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. 获取 GOPATHgo env GOPATH默认 ~/go
2. 扫描 GOPATH/bin/ 目录Windows 下为 GOPATH\bin\
3. 对每个可执行文件:
   a. 跳过名为 "go" 和 "gofmt" 的文件(运行时自带)
   b. 尝试 `go version -m <binary>` 获取 module 路径和版本
   c. 如果成功,记录 {name, module, version}
   d. 如果失败(非 Go 编译的二进制),记录 {name, unknown}
4. 将工具列表按名称字母排序

2.1.4 Capture 输出文件

在目标目录下生成两个文件:

metadata.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

[
  {"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 <module>@<version>`
   ├── 如果指定版本安装失败 → 尝试 `@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 输出格式:

{
  "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 字典,提取 nameversion
  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

{
  "node_version": "20.11.0",
  "npm_version": "10.2.4",
  "version_manager": "fnm",
  "yarn_version": "1.22.21",
  "pnpm_version": null
}

global-packages.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 <ver>; fnm use <ver>`
   │   └── 无版本管理器 → 提示用户手动升级,记录 UPGRADE 建议
   └── 未安装 →
       ├── 有版本管理器 → `fnm install <ver>`
       └── 无版本管理器 → 提示用户安装(提供下载链接)
2. 安装全局包(串行,避免 npm 并发冲突)
   ├── `npm install -g <name>@<version>`
   ├── 安装失败 → 尝试不指定版本 `npm install -g <name>`
   └── 记录每个包的安装结果

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 <ver>; pyenv global <ver>`
   ├── 有 conda → `conda create -n devpack python=<ver>`
   └── 都没有 → 提示用户手动安装
2. 安装 pip 包(串行,使用 --user 标志)
   ├── `pip install <name>==<version> --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. 按行分割,每行格式 <publisher>.<name>@<version>
  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 <publisher.name>@<version> --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.1powershell

命令 版本 说明
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 当前用户当前主机

采集策略: 只采集 CurrentUserAllHostsCurrentUserCurrentHost 两个用户级 Profile。

2.5.3 模块扫描

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 <name> -RequiredVersion <ver> -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 exportJSON 格式:

{
  "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 <name> [<url>]`
   ├── Bucket 已存在 → 跳过
   └── URL 无效 → 记录警告,部分包可能安装失败
3. 安装包(串行)
   ├── `scoop install <name>`
   ├── 不指定版本Scoop 总是安装最新版本,版本管理通过 scoop reset 处理)
   ├── 安装失败 → 记录错误并继续
   └── 使用进度条 "安装 Scoop 包 [15/42]"
4. 验证
   └── `scoop list` 检查包是否安装

2.6.4 Capture 输出文件

packages.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

[
  {"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 <key> <value>`
  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

{
  "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 <key> 逐个读取
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. 按分隔符拆分 PATHWindows: ";", 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

{
  "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 注册机制设计

// 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 <name>      指定 Profile默认使用 config.yaml 中的 default_profile
   ├── --collectors <list>   逗号分隔的采集器名(覆盖 Profile 配置)
   ├── --output <format>     输出格式: table (默认) | json | yaml
   ├── --detailed            显示详细信息(每个 ScanItem 的 Properties
   └── --timeout <duration>  超时时间(默认 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 并发扫描控制

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/uuidgithub.com/schollz/progressbar/v3

详细功能描述:

3.1.1 .devpack 文件格式规范

.devpack 文件本质上是一个 .tar.gz 归档,内部目录结构如下:

<pack-name>.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

{
  "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/<name>/ 子目录
     ├── 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:<hex>  <relative-path>"(每行一个文件)
     └── 写入 tempDir/checksum.sha256

  7. 打包为 tar.gz
     ├── 创建 <pack-name>.devpack 文件
     ├── 写入 gzip → tar 数据流
     ├── 遍历临时目录,逐个添加文件/目录到 tar
     └── 关闭 tar/gzip writer

  8. 移动到 Packs 目录
     └── mv <pack-name>.devpack → ~/.devpack/packs/<pack-name>.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 核心函数

// 创建 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 <pack-name> [-o <output-path>]

1. 查找 Pack
   └── 在 ~/.devpack/packs/ 中查找 <pack-name>.devpack
2. 验证完整性
   └── 可选: 重新计算校验和确认文件未损坏
3. 复制文件
   ├── 默认输出到当前目录 ./<pack-name>.devpack
   ├── 大文件使用 io.Copy 带进度条
   └── 复制完成后显示文件大小和路径

3.3.2 import 流程

devpack import <file-path> [-n <name>] [--verify]

1. 验证文件
   ├── 检查文件存在且可读
   ├── 检查是否为有效的 tar.gz
   ├── 解析 manifest.json 确认格式正确
   └── --verify: 验证 checksum.sha256 中所有文件的校验和
2. 检查命名冲突
   ├── 使用 manifest.name 或 --name 指定的名称
   └── 同名已存在 → 提示用户覆盖或改名
3. 复制到 Packs 目录
   └── cp <file-path> → ~/.devpack/packs/<name>.devpack
4. 输出结果
   └── 显示 Pack 信息(来源平台、采集器数量、大小)

任务 3.4 — 进度条组件

新建文件或修改:

文件 说明
internal/ui/progress.go 基于 progressbar 库的进度条封装、适配打包/还原等长时操作

所需库:

第三方库:
  github.com/schollz/progressbar/v3  — 进度条渲染

标准库:
  os               — Stderr 输出
  fmt              — 格式化

详细功能描述:

3.4.1 ProgressTracker 接口

type ProgressTracker interface {
    SetTotal(total int)                     // 设置总步骤数
    Increment()                             // 增加一步
    SetMessage(message string)              // 更新当前步骤描述
    SetCurrent(step int, message string)    // 设置当前步骤(用于多阶段)
    Done()                                  // 完成
    Fail(message string)                    // 失败
}

3.4.2 使用示例

// 打包进度
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 数据结构

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 varsGOPATH 等需要先设置)
  packages     ← 依赖 runtimesscoop install 可能需要 git
  editors      ← 依赖 runtimesVS Code 扩展可能需要语言运行时)
  shells       ← 依赖 packagesProfile 脚本可能引用已安装的工具)
  git config   ← 依赖 runtimesgit 需要先安装)

拓扑序(默认):
  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 <pack-name> [flags]

必选参数:
  pack-name              Pack 名称或 .devpack 文件路径

可选标志:
  --dry-run              预览更改(不实际执行)
  --conflict <strategy>  冲突处理策略
                         skip     — 跳过冲突项
                         overwrite — 强制覆盖
                         merge    — 尝试合并(仅配置文件)
                         prompt   — 逐个交互式询问
                         newest   — 保留更新的版本
  --no-rollback          不创建还原点
  -c, --collectors       只还原指定采集器
  --exclude              排除指定采集器
  --parallel <n>         并行还原数(默认: 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 <tool>@latest 安装工具 os/exec
internal/collector/runtime/node_collector.go npm install -g <pkg>@<ver> 安装全局包 os/exec
internal/collector/runtime/python_collector.go pip install <pkg>==<ver> 安装包 os/exec
internal/collector/editor/vscode_collector.go code --install-extension <id> + 复制配置文件 os/exec, io, os
internal/collector/shell/powershell_collector.go Install-Module -Name <mod> + 写入 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 <key> <value> os/exec
internal/collector/env/env_collector.go 调用 platform.SetEnvVar() internal/platform

详细功能描述:

4.4.1 每个采集器的 Restore 返回值

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() 方法:

// 可选接口,不强制实现
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 <name>

--template <type>     模板类型 (minimal/standard/full),默认 standard
--description <desc>  描述信息

流程:
  1. 验证 name 合法性
  2. 检查同名 Profile 是否已存在
  3. 从模板创建 Profile
  4. 填入用户提供的 description
  5. 保存到 ~/.devpack/profiles/<name>.yaml
  6. 如果是唯一 Profile自动设为默认
  输出: "✓ Profile 'web-dev' 已创建"

devpack profile show <name>

流程:
  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 <name>

流程:
  1. 检查 Profile 是否存在
  2. 检查是否为当前默认 → 拒绝删除,提示先切换默认
  3. 确认提示 "确定删除 Profile 'web-dev'[y/N]"
  4. 删除文件
  输出: "✓ Profile 'web-dev' 已删除"

devpack profile use <name>

流程:
  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 profilesdevpack 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

// 命令执行器接口
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 版本检查逻辑

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 应包含以下直接依赖:

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 版本额外添加:

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 ./... 确保不引入回归。