项目:webCommand | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
本篇把 package.json(相对 webCommand/ 根)声明的全部 29 个依赖——16 个 dependencies、13 个 devDependencies——逐个过堂:它是干什么的、被谁 import(给出可直接 grep 的使用位置)、为什么选它而不是隔壁、升级时会碰到什么。版本给两列口径:声明版本(package.json 的语义化范围)与锁定版本(pnpm-lock.yaml 实际解析值),后者才是真正跑在产物里的东西。与既有篇目的分工:《00-项目总览》§3 的技术栈速览只回答"用了什么",本篇回答"每个依赖的来龙去脉与风险";依赖升级的操作步骤与 pnpm-lock.yaml 的维护纪律见《14-配置与环境详解》,本文不重复。
读表前先交代本仓库依赖管理的三条事实。第一,锁定纪律真实生效:pnpm-lock.yaml 在仓库里且与 package.json 同步(例如 axios 声明 ^1.18.1、锁定 1.20.0,vue 声明 ^3.5.40、锁定 3.5.42),.npmrc 里 auto-install-peers=true、shamefully-hoist=false,是严格 pnpm 语义。第二,依赖面刻意收窄:29 个直接依赖对"前端框架 + 后端框架 + 数据库 + 邮件 + 测试双轨"的功能面而言相当克制,app/ 与 server/ 里没有任何"为了一个小函数引一个库"的痕迹(合并逻辑、准入策略、迁移器全是手写)。第三,有两处"声明的依赖不是 import 的依赖":mysql2 无一行直接 import(Sequelize 运行时动态加载,Dockerfile 还要单独补装)、ant-design-vue 无一行显式 import(unplugin-vue-components 自动按需引入)——这两处是本篇第 3、4 节展开的重点。
| 依赖 | 版本(声明/锁定) | 用途 | 使用位置(grep 证据) | 选型理由 | 替代方案 | 升级风险 |
|---|---|---|---|---|---|---|
| nuxt | ^4.5.2 / 4.5.2 | 元框架:SPA + Nitro API + 构建 + 模块体系 | nuxt.config.ts;全局函数 defineNuxtConfig/defineEventHandler/useRuntimeConfig 由其注入 | 单工程承载前后端(见《16》ADR-001) | Vite SPA + 独立后端 | 主版本升级动目录约定(app/server)与 Nitro API |
| vue | ^3.5.40 / 3.5.42 | 视图框架 | app/** 全部组件;markRaw/ref/watch 组合式 API | Nuxt 强制同源 | 无(随 Nuxt) | 与 Nuxt 版本矩阵耦合,不可单独升级 |
| vue-router | ^5.3.0 / 5.3.0 | 路由(Nuxt 必带) | 仅生成物 app/components.d.ts;源码零路由用法 | Nuxt 内核依赖,声明为显式约束 | 删除声明(随 Nuxt 自动装) | 无实际风险;《13》⚠️-22 登记为语义噪音 |
| @pinia/nuxt | ^1.0.2 / 1.0.2 | Pinia 的 Nuxt 模块封装 | nuxt.config.ts modules 数组 | 官方集成,自动注入 pinia 实例 | 手动建 pinia 并 provide | 随 pinia 大版本联动 |
| pinia | ^4.0.3 / 4.0.3 | 状态管理(8 个 store) | app/core/commands/**/*Store.ts 七处 defineStore;index.vue 的 storeToRefs | 组合式/选项式双写法、devtools | Vuex(已过时)、zustand(非 Vue 生态) | 4.x 内安全;$subscribe/$reset 行为是同步链路依赖 |
| pinia-plugin-persistedstate | ^4.7.1 / 4.7.1 | store 持久化到 localStorage | app/plugins/pinia-persistedstate.client.ts;六个 store 的 persist.key | 八键结构的地基(见《16》ADR-007) | 手写 watch + localStorage | beforeHydrate 钩子属插件 API,升级需回归 |
| ant-design-vue | ^4.2.6 / 4.2.6 | UI 组件库(Collapse/Input/Tag 等) | 无显式 import(自动按需);nuxt.config.ts 全局引 dist/reset.css | css-in-js 按需加载,包体小 | element-plus、naive-ui | 4.x 内安全;css-in-js 时序是弃 SSR 的原因之一 |
| axios | ^1.18.1 / 1.20.0 | HTTP 客户端(前端 + 一处服务端) | app/utils/myAxios.ts、pingCommand.ts、varbook/hooks/api/index.ts、server/thirdparty/background.ts | 拦截器统一 ApiResult 形态 | 原生 fetch(服务端已在用,见 httpClient.ts) | 1.x 内安全;withCredentials 语义不变 |
| getopts | ^2.3.0 / 2.3.0 | 命令行参数解析 | app/core/commandExecutor.ts、command.d.ts、space 组命令 | 极小(单文件级)、API 贴近真实 shell | yargs-parser(重)、minimist(弱类型) | 已停更(作者归档);行为稳定,风险低 |
| bcryptjs | ^2.4.3 / 2.4.3 | 口令哈希 | server/services/userService.ts(BCRYPT_ROUNDS = 10) | 纯 JS,跨机部署无需编译原生模块 | 原生 bcrypt(快 2-3 倍,需编译) | 《10》§6-6 建议阶段 2 换原生 |
| sequelize | ^6.37.8 / 6.37.8 | ORM | server/utils/db.ts、server/models/*.ts、userService.ts(Op/UniqueConstraintError/transaction) | 团队熟、模型即文档、事务可用 | Prisma(重生成物)、Knex(偏裸 SQL) | 7.x 是 TypeScript 重写,主版本升级成本高 |
| mysql2 | ^3.24.2 / 3.24.2 | MySQL 驱动(被 sequelize 动态加载) | 源码零 import;Dockerfile 第 10-14 行单独补装 | sequelize 的 mysql 方言依赖它 | ——(随 sequelize) | Nitro 不打包它,换 node 基础镜像需重验 Dockerfile |
| nodemailer | ^6.10.1 / 6.10.1 | SMTP 发信 | server/thirdparty/mailer.ts(transporter 模块级缓存单例) | 事实标准、纯配置式 | smtp-client(裸协议) | 7.x 主版本未到;API 稳定 |
| nuxt-auth-utils | ^0.5.30 / 0.5.30 | 会话:加密 cookie + 全局函数 | 全局函数(无 import):setUserSession 等;类型扩展在 auth.d.ts | 轻量(非 OAuth 全家桶)、Nuxt 原生集成 | lucia(已停维护)、手写加密 cookie | 0.x 版本号意味着 API 可能微调;会话格式不兼容升级会踢掉全部登录态 |
| lodash | ^4.18.1 / 4.18.1 | 工具函数 | 仅 app/components/hint.ts(debounce/trim) | 稳定、全量包按名引入 | 原生 setTimeout 防抖手写 | 4.x 永久稳定;实际是"可以删的依赖" |
| dayjs | ^1.11.23 / 1.11.23 | 日期处理 | 仅 app/utils/myDayjs.ts 封装 | 体积小、不可变 API | 原生 Date/Intl | 1.x 内安全 |
nuxt 4.5.2 是整个工程的地基,但它在本项目里被用得非常"薄":模块只挂三个(@pinia/nuxt、nuxt-auth-utils、@nuxt/eslint),没有用数据获取层(无 useFetch/useAsyncData,前端请求全走自封装 axios),没有用 NuxtLink 与多页面(唯一页面 app/pages/index.vue),没有用 server routes 以外的 Nitro 高级特性(无 WebSocket、无 storage、无 task)。它的核心价值集中在三件事:app/ + server/ 的目录约定与一次构建、Nitro 运行时全局函数的自动注入(这让 server/api/** 的路由文件干净到没有 import h3 的必要)、以及 runtimeConfig 的构建期/运行期双轨机制(本项目用 ADR-004 的方式部分绕开了它,见《14》§1)。nitropack 2.13.4(Nuxt 内置)负责把 server/ 打成 .output/server/index.mjs,compatibilityDate: '2026-09-01' 钉住行为基线;h3 在 lock 里同时出现 1.15.11 与 2.0.1 两个主版本(不同子树各持一份),本项目路由层实际使用的 defineEventHandler/getQuery/setResponseStatus 等全局函数来自 Nitro 绑定的那份 h3——双主版本并存是 pnpm 严格隔离的正常现象,不是错误,但升级 nuxt 时要留意 h3 2.x 的 handler 签名变化是否波及。vue 3.5.42 的使用深度比 nuxt 深:Terminal.vue 用到了 markRaw(组件型输出防 Proxy 化)、watchEffect、defineExpose,themeStore 用 setup 风格 store 混用 ref/watch,这些都属于 Vue 核心运行时 API,升级 vue 时是真实的回归面。
升级风险综述:nuxt 主版本升级(4→5)将同时触碰目录约定、Nitro 版本、h3 大版本与 Nuxt 模块生态四件事,对本仓库"注释与实现高度耦合"的现状(《13》⚠️-18 记录过注释指错文件)是高风险操作;建议策略是跟随 4.x 补丁版本(^4.5.2 的范围已允许),主版本升级留待有完整回归窗口时专项做。compatibilityDate 是 Nuxt 官方提供的行为钉子,升级后应显式推进而不是被动漂移。
这对依赖是全仓库最容易被误解的。package.json 里 mysql2 名正言顺列在 dependencies,但 grep -r "from 'mysql2'" 在 app/、server/、test/ 里一无所获——它是 Sequelize 的方言驱动,由 server/utils/db.ts 里 new Sequelize(..., { dialect: 'mysql' }) 在运行时动态 require。这个间接性产生了一条部署链路上的真实坑:Nitro 打包时会静态分析 import 并把依赖打进 .output/server/node_modules,动态 require 的 mysql2 不在静态分析范围内,产物里就没有它——Dockerfile 第 10-14 行为此专门写了一段"在临时目录 npm install mysql2@3.24.2 再把 node_modules 合并进 /app/node_modules"的逻辑,注释原文:"sequelize 动态加载 mysql2,Nitro 未将其打包进 .output/server/node_modules,需单独安装"。临时目录安装而非直接在 /app 里 npm install,是为了避免 npm 与现有 node_modules 树(pnpm 符号链接结构)冲突——这是《06》§5 详细拆过的指令。
选型层面,Sequelize 的收益在本项目体现为三点:模型定义即表结构文档(User.ts/UserData.ts 与 migrations.ts 的 DDL 逐列对齐,双份登记互为校验);UniqueConstraintError 与 transaction 两个高级能力被 userService.ts 直接消费(注册查重的并发兜底、注销账号的两步事务);连接单例 useDb() 的懒加载模式让模型可以在 import 时安全引用("避免在 Nitro 运行时配置就绪前实例化")。代价也有两条:其一,Sequelize 6 对 MySQL 的 update/upsert 在布尔/时间字段上有自己的转换规则,timestamps: false 正是为绕开它对 createTime/updateTime 的接管(《13》⚠️-17 的 NULL 时间列与此相关);其二,Sequelize 7 已发布路线且是 TypeScript 重写,6→7 的 API 面变化大,^6.37.8 的升级策略应止步于 6.x 内。替代方案里,Prisma 的生成物模型与本项目"零构建期凭证"纪律冲突(引擎二进制 + schema 打包),Knex 则要手写全部行映射——对本项目两表规模,Sequelize 是"恰好够用"的那一档。
pinia 4.0.3 + @pinia/nuxt 1.0.2 + pinia-plugin-persistedstate 4.7.1 构成本项目的状态层。本仓库对 pinia 的用法有一个特色:store 与命令同域共存——七个 defineStore 全部放在 app/core/commands/<域>/*Store.ts,而不是集中到 app/stores/。这使"新增一类云同步数据"的改动半径缩小到单个目录(《11》SOP-3)。三个技术细节值得登记:一是持久化插件在客户端插件里注册且带 _isPersistedstate 防重复标记(pinia-persistedstate.client.ts,HMR 下重复 pinia.use 会导致 hydrate 行为异常);二是 todoStore 的 initCloudSync 依赖 this.$subscribe——订阅回调在每次 mutation 后触发,云同步的"变更即上传"(防抖 800ms)建立在这个语义上,pinia 升级若改变 $subscribe 的触发时机(如批量合并 mutation),同步延迟特性会随之改变;三是 userStore 的 $reset 被用作"会话对账失败后的回滚",这是选项式 store 才有的能力,若未来把 store 改成 setup 风格会失去 $reset(themeStore 已经是 setup 风格,它就没用 $reset)。persistedstate 的 beforeHydrate 钩子在 terminalConfigStore 里有实际使用(升级时需回归该路径)。
ant-design-vue 4.2.6 的引入方式是本项目前端包体可控的关键:nuxt.config.ts 里 Components({ resolvers: [AntDesignVueResolver({ importStyle: 'css-in-js' })] }) 让模板里 <a-collapse> 这类标签在编译期自动换成按需 import,运行时样式走 css-in-js 注入,全局只引一份 dist/reset.css。所以 grep 找不到 from 'ant-design-vue' 不是没用,而是"用了但看不见"。实际用到的组件面很窄(Collapse、Input、Tag、Tooltip 等少数几个,清单见《02》§2),如果未来只用纯文本输出,理论上有"删掉组件库"的选项——但输入框聚焦行为与折叠面板的交互细节已经深度依赖 antd 的行为语义,替换成本高于收益。css-in-js 的注入时序是 ADR-002 弃 SSR 的两个原因之一,若升级到 antd-vue 5.x(若有)需重新评估该前提。
nodemailer 6.10.1 只服务找回密码一条链路(server/thirdparty/mailer.ts),但用法上有三处值得注意:transporter 模块级缓存单例(let cached: Transporter | null,避免每请求握手 SMTP);secure: smtp.port === 465 的端口隐含协议约定;SMTP 配置同样走 setting() 运行期解析(NUXT_SMTP_* > 裸名 SMTP_*),未配置时 isMailConfigured() 返回 false,reset-code 路由据此抛出 ERROR_CODE.THIRD_PART——依赖"未配置"被建模成业务态而不是崩溃。bcryptjs 2.4.3 的取舍在《13》§4 登记过:纯 JS 换来跨机部署零编译(NAS/容器/CI 都不需要 node-gyp 工具链),代价是每请求 CPU 约为原生 bcrypt 的 2-3 倍;当前 10 轮哈希在个位数用户量下无感,阶段 2 触发时换原生(届时 Dockerfile 需加编译工具链,@types/bcryptjs 也可一并退役)。axios 1.20.0 的分工值得说明:前端统一走 myAxios.ts 封装(baseURL 同源 /api、withCredentials: true、拦截器把响应规整为 ApiResult);但 server/thirdparty/background.ts 在服务端也用了 axios(5 秒超时抓 btstu 壁纸 API)——这与 server/utils/httpClient.ts 的原生 fetch(8 秒超时)形成"服务端两套出站 HTTP 并存"的局面:httpClient 是新规范(AbortSignal.timeout、UA 伪装、状态码校验),background.ts 的 axios 是历史遗留,功能等价,属于可收敛项(⚠️ 待确认 是否值得统一到 httpClient)。
devDependencies 只影响开发与构建链路,不影响产物体积,但有两个"声明与现实的落差"要点名。
| 依赖 | 版本(声明/锁定) | 用途 | 使用位置(grep 证据) | 备注 |
|---|---|---|---|---|
| @nuxt/eslint | ^1.17.0 / 1.17.0 | Nuxt ESLint 集成(flat config 生成) | nuxt.config.ts modules;eslint.config.mjs 的 withNuxt | stylistic 关闭,格式让位 Prettier |
| eslint | ^10.9.1 / 10.9.1 | 代码质量门禁 | package.json scripts lint | no-explicit-any 恢复为 error(eslint.config.mjs 注释) |
| eslint-config-prettier | ^10.1.8 / 10.1.8 | 关闭与 Prettier 冲突的规则 | eslint.config.mjs | "双规则冲突"治理的粘合剂 |
| prettier | ^3.9.6 / 3.9.6 | 格式化 | scripts/check-all.sh 第一步 format:check | .prettierrc.json + .prettierignore |
| typescript | ^5.9.3 / 5.9.3 | 类型检查 | nuxt typecheck(vue-tsc 底层) | strict: true(nuxt.config.ts) |
| vue-tsc | ^3.3.11 / 3.3.11 | Vue SFC 类型检查 | pnpm typecheck | 与 vue/typescript 版本矩阵耦合 |
| vitest | ^2.1.9 / 2.1.9 | 路由层 + 单元测试第二轨 | vitest.config.ts、test/routes/**、test/unit/** | include 收窄防"假通过"(见《16》ADR-011) |
| happy-dom | ^17.6.3 / 17.6.3 | DOM 测试环境 | 零使用(environment: 'node') | 死依赖,《13》⚠️-8:删或用二选一 |
| unplugin-vue-components | ^32.1.0 / 32.1.0 | 组件自动按需引入 | nuxt.config.ts vite.plugins;生成 app/components.d.ts | 与 AntDesignVueResolver 配套 |
| @types/node | ^22.15.0 / 22.20.1 | Node 类型 | 全仓 | 运行时是 node:22-slim,类型对齐 22.x |
| @types/bcryptjs、@types/lodash、@types/nodemailer | 2.4.6 / 4.17.25 / 6.4.24 | 对应运行时依赖的类型 | 各自唯一使用点 | bcryptjs 若换原生需同步换 @types |
落差之外的观察:ESLint 10 是相当新的主版本,eslint.config.mjs 走 flat config 且只有一条自定义规则(no-explicit-any: error,注释说明前后端 any 已全部收敛);Prettier 3.9.6 与 .prettierignore(排除产物目录)配合 check-all.sh 的五步门禁。测试侧的双轨分工(node:test 是 Node 内置不占依赖位,vitest 管第二轨)在《09》§1 有完整叙述。
在给出评估之前,先补一条本仓库事实上的"依赖准入规则",因为它正是健康度良好的原因。观察 16 个运行时依赖的来源可以发现一个规律:每个依赖的引入都对应一个"手写会错"的场景——bcrypt 的盐与比较时序、SQL 迁移的水位管理、SMTP 的协议握手、getopts 的选项语义,这些属于"自己写必有安全或正确性风险"的范畴;而本仓库同样清楚哪些场景不值得引依赖——合并算法、限流器、准入判定、配置解析全部手写,因为它们的正确性恰恰是项目的核心资产,外包出去反而失去测试锚点(ADR-011 的纯函数测试对象都来自这些手写模块)。把这条规则显式化就是:新依赖的准入条件是"它挡住的风险大于它带来的供应链与升级面",新增前先问"这个能力错了会怎样、我能为它写测试吗"。
总量与结构:16 个运行时依赖支撑"框架(nuxt/vue/vue-router)+ 状态(pinia 三件套)+ UI(antd)+ HTTP(axios/getopts)+ 服务端(sequelize/mysql2/bcryptjs/nodemailer/nuxt-auth-utils)+ 工具(lodash/dayjs)"六个面,平均每个依赖承担一个清晰职责,无重复造轮子(除 axios/httpClient 双轨这种历史遗留)。风险矩阵按"触发概率 × 影响面"排序如下——高风险一格:getopts 已停止维护(作者归档仓库),但它是纯解析器、行为冻结、无安全面,冻结即安全,唯一动作是"永远不要指望它修 bug";中风险三格:nuxt 主版本升级(四件套联动)、sequelize 6→7 重写、nuxt-auth-utils 0.x 会话格式变更(升级若换加密/序列化格式,全部在线会话失效,表现为全员被登出,重新登录即愈——影响可控但要挑时间窗);低风险若干:pinia/antd/axios/nodemailer 均处于稳定大版本内。安全面盘点:bcryptjs(哈希)、nuxt-auth-utils(会话加密,密钥由 check-env 守门)、mysql2(连接串全走运行期配置)、nodemailer(凭证同上)——四个涉敏感面依赖的输入都来自环境变量而非构建产物,与 ADR-004 的纪律一致;依赖树里未发现已知高危传递依赖(⚠️ 待确认:本篇未执行 pnpm audit 实测,该结论基于版本常识而非扫描,发布前建议补跑一次——一致性回刷时实测过一次:本仓库 .npmrc 指向 registry.npmmirror.com,其未实现 audit 端点,pnpm audit 直接报 ERR_PNPM_AUDIT_ENDPOINT_NOT_EXISTS,补跑需临时以 npm_config_registry=https://registry.npmjs.org 切官方源)。卫生项:happy-dom 死依赖应删;lodash(仅 debounce/trim)与 dayjs(仅一层封装)都可以用十行原生代码替换,但替换收益仅是减少两个安装包,优先级极低;vue-router 的显式声明保留即可(《13》⚠️-22 的结论)。总体评价:这是一个依赖纪律良好的仓库——锁文件生效、面窄、每个依赖有明确的一等公民身份,真正的升级债务集中在"框架内核版本滞后"这一类可计划的风险上,不存在失控的传递依赖或影子依赖。