Skip to content

CLI 工具参考 ​

本页面汇总 ClassIntra 常用命令行工具,覆盖 pnpm、PM2、构建脚本、版本管理与数据库迁移。

pnpm 命令 ​

ClassIntra 使用 pnpm workspace 管理 monorepo,根目录 package.json 中定义了快捷脚本。

主要命令 ​

命令实际执行说明
pnpm devpnpm --parallel --filter server --filter client run dev并行启动前后端开发服务器
pnpm dev:serverpnpm --filter server run dev仅启动后端
pnpm dev:clientpnpm --filter client run dev仅启动前端(Vite dev)
pnpm build更新 version.json + cd client && pnpm run build构建前端生产产物
pnpm startpnpm --filter server run start启动后端生产服务(node src/app.js)
pnpm install安装全部 workspace 依赖含 server / client / apps/*

各 workspace 内部脚本 ​

Workspace命令说明
serverpnpm dev / pnpm startnode src/app.js(同义)
clientpnpm devvite dev server,端口 5001
clientpnpm prebuildnode scripts/prebuild.js(版本号递增)
clientpnpm buildvite build(构建产物到 dist/)
clientpnpm previewvite preview(预览构建产物)

用法示例 ​

bash
# 1. 全新部署
pnpm install
cp server/.env.example server/.env
# 编辑 .env 至少设置 JWT_SECRET

# 2. 开发
pnpm dev
# → 前端 http://localhost:5001
# → 后端 http://localhost:9001
# → WebSocket ws://localhost:10001

# 3. 生产构建
pnpm build

# 4. 启动生产服务
pnpm start
# → http://localhost:9001

# 5. 仅启动后端调试
pnpm dev:server

# 6. 仅启动前端调试(需后端已运行)
pnpm dev:client

# 7. 单独安装某 workspace 依赖
pnpm --filter server add <pkg>
pnpm --filter client add <pkg>

pnpm 加速

国内网络可在 .npmrc 配置淘宝镜像:

ini
registry=https://registry.npmmirror.com

PM2 命令 ​

PM2 用于生产环境进程守护,项目自带 ecosystem.config.js 配置。

进程管理 ​

命令说明
pm2 start ecosystem.config.js启动 ClassIntra 服务
pm2 start ecosystem.config.js --update-env重启并刷新环境变量
pm2 stop classintra-server停止进程
pm2 restart classintra-server重启进程
pm2 reload classintra-server零停机重启(cluster 模式)
pm2 delete classintra-server从 PM2 列表移除
pm2 save保存进程列表(用于 resurrect)
pm2 resurrect恢复上次保存的进程列表
pm2 startup生成开机自启命令

查看状态 ​

命令说明
pm2 status / pm2 list / pm2 ls进程列表
pm2 describe classintra-server详细信息(路径、env、重启次数等)
pm2 monit实时监控面板(CPU/内存/日志)
pm2 info classintra-server进程元信息

日志 ​

命令说明
pm2 logs classintra-server实时查看 stdout + stderr
pm2 logs classintra-server --lines 200最近 200 行
pm2 logs classintra-server --err仅错误日志
pm2 logs classintra-server --out仅标准输出
pm2 flush classintra-server清空日志文件
pm2 reloadLogs重新加载日志

完整流程示例 ​

bash
# 1. 启动
pm2 start ecosystem.config.js

# 2. 验证
pm2 status
pm2 logs classintra-server --lines 30

# 3. 保存(用于开机自启)
pm2 save

# 4. 配置开机自启
pm2 startup
# 按 PM2 返回的命令执行:
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u <user> --hp /home/<user>

# 5. 重启并刷新环境变量(修改 .env 后)
pm2 restart ecosystem.config.js --update-env

# 6. 停止
pm2 stop classintra-server

# 7. 完全移除
pm2 delete classintra-server

ecosystem.config.js 与 .env

ecosystem.config.js 会自动读取 server/.env 并注入到 PM2 进程环境变量。修改 .env 后必须 --update-env 重启,否则旧值仍生效。

构建脚本 ​

项目根目录提供多个一键脚本,简化部署操作。

build.bat(Windows 一键构建) ​

cmd
build.bat

执行流程:

  1. 安装依赖(server 用 --prod,client 正常安装)
  2. 更新 server/version.json(写入 buildHash 与 buildTime)
  3. 在 client/ 目录执行 npx vite build
  4. 产物输出到 client/dist/

build.bat 用途

适合无 pnpm 的 Windows 服务器,自动降级到 npm。

start.sh(Linux 开发启动) ​

bash
./start.sh

执行流程:

  1. 初始化数据库(node src/utils/init-db.js)
  2. 后台启动后端(node src/app.js,端口 9001)
  3. 后台启动前端(npx vite --host,端口 5001)
  4. Ctrl+C 优雅停止两个进程

start.sh 仅开发

start.sh 使用 Vite dev server,仅用于开发。生产环境请使用 pnpm build && pnpm start 或 PM2。

start.bat(Windows 开发启动) ​

cmd
start.bat

在新窗口分别启动后端与前端 dev server。

start-prod.bat(Windows 生产启动) ​

cmd
start-prod.bat

生产模式一键启动(仅后端,无 Vite dev server)。

CI.bat(CI 构建) ​

cmd
CI.bat

CI 环境使用,自动安装依赖、构建、跑测试。

build-better-sqlite3.bat(原生模块编译) ​

cmd
build-better-sqlite3.bat

仅在预编译包不可用时手动编译 better-sqlite3,正常情况下 .npmrc 的 build_from_source=false 已自动处理。

构建产物回收与耗时 ​

为什么 dist/assets 会越滚越大 ​

client/vite.config.mjs 关掉了 emptyOutDir(本机构建环境禁止构建前批量删目录, 否则 Vite 清空 outDir 会被安全删除守卫拦下,构建中途失败且线上产物已被删残)。 副作用是旧的带哈希 chunk 永不回收 —— 实测曾堆到 11920 个文件 / 786MB(index-*.js 就有 320 份)。

client/scripts/prune-dist.js 负责在构建后精确回收,由 pruneDistPlugin 自动调用:

bash
cd client
node scripts/prune-dist.js                      # 回收(默认只删 24h 之前的残留)
node scripts/prune-dist.js --dry                # 只报告不删
node scripts/prune-dist.js --keep-hours 0       # 水位放宽到 0 小时(激进)
SKIP_PRUNE=1 npx vite build                     # 本次构建跳过回收

存活判定有四道(任意一道判「还在用」就保留):

  1. 本轮构建的 dist/.vite/manifest.json(file / css / assets)
  2. dist 根目录所有 html 及其正文引用
  3. 不动点扫描:存活文件正文里的资源名迭代入队(覆盖 ./chunk-xxx.js 这类相对引用、 html 内联脚本、css 的 url() 字体),保证 legacy chunk 图与字体整条链路不被误删
  4. 24 小时水位:兜住「用户已打开但尚未刷新的旧页面」——它仍可能去拉上一代的懒加载 chunk

首次回收实测

--dry 报告可回收 10490 个 / 662.4 MB,执行后 dist 从 786MB 降到 104MB(余量为当天的历史代际,次日构建自动清掉)。

构建耗时构成(2026-09-29 实测,2663 个模块) ​

阶段耗时
模块转换(Rollup transform)~18s
打包 + 压缩 + legacy 转译~160s
收尾(体积统计/写盘)~20s
合计~3 分 20 秒

插件级归因:vite:legacy-post-process 累计 1,271,129 ms(208 次调用、单次最高 86s,并行执行故累计远大于墙钟), 即 @vitejs/plugin-legacy 用 Babel 把所有 chunk(含 1MB 的 mermaid / video.js)再转译一遍,占构建时间约 85%。

LEGACY_CHUNKS=0 快速构建 ​

bash
cd client && LEGACY_CHUNKS=0 npx vite build     # 3m20s → 35s(5.6 倍)

代价:不再产出 nomodule 的 legacy bundle,旧浏览器没有兜底(现代 bundle 与 polyfills 不受影响)。 校园平板基线是 Chrome 80,原生支持 ESM、加载的是 type="module" 的现代 bundle, 所以理论上可关;但是否切换应由真机实测后决定,因此默认仍为开启。

产物冒烟检查 ​

回收会删掉上千个旧文件,「删了不该删的」只有真实页面才能验出来:

bash
cd client
node scripts/smoke-dist.mjs                     # 断言启动屏退场 + #app 挂载 + 无产物 404

脚本用 CDP 驱动无头 Chrome 打开首页,区分「产物失败」(必须为 0,影响退出码) 与「接口 4xx」(未登录时的 401,预期内)。截图输出到 logs/smoke/。

版本管理 ​

ClassIntra 使用 server/version.json 管理版本,由 client/scripts/prebuild.js 在每次 pnpm build 时自动维护。

version.json 结构 ​

json
{
  "version": "1.0.5",
  "lastBuiltVersion": "1.0.4",
  "buildHash": "l1a2b3c4d5e6f7g8",
  "buildTime": "2025-08-21T08:00:00.000Z",
  "changelog": "fix: 修复 WebSocket 断线重连\nfeat: 添加主题切换快捷键",
  "minClientVersion": "1.0.0",
  "forceUpdate": false,
  "updateUrl": ""
}
字段说明
version当前版本号(MAJOR.MINOR.PATCH)
lastBuiltVersion上次构建版本号
buildHash构建哈希(8 字符随机串)
buildTime构建时间(ISO 8601)
changelog自动从 git log 生成
minClientVersion客户端最低兼容版本
forceUpdate是否强制更新(前端检测后自动刷新)
updateUrl更新下载地址(可选)

自动递增规则 ​

prebuild.js 在每次 pnpm build 时执行:

  1. 读取 version.json 当前版本
  2. PATCH 自动 +1(如 1.0.4 → 1.0.5)
  3. 若 MAJOR 或 MINOR 变化(手动修改),PATCH 归零
  4. 从 git log 提取自上次构建以来的 commit message 生成 changelog
  5. 写入 buildHash、buildTime、changelog
  6. 同步更新根目录 CHANGELOG.md

手动升版本

要发布大版本时,手动编辑 version.json 改 MAJOR/MINOR:

json
{ "version": "2.0.0", ... }

下次 pnpm build 时,prebuild 会识别到 MAJOR/MINOR 已变化,PATCH 归零并保留新版本号。

查询当前版本 ​

bash
# 命令行
cat server/version.json | python -m json.tool

# API
curl http://localhost:9001/api/system/version

# 前端
# 桌面 → 设置 → 关于

强制更新 ​

将 version.json 的 forceUpdate 设为 true,前端通过 /api/system/heartbeat 检测后强制刷新:

bash
# 通过 API 设置(仅班管)
curl -X POST http://localhost:9001/api/system/set-version \
  -H "Authorization: Bearer <admin-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.0.5",
    "forceUpdate": true,
    "changelog": "紧急修复:修复 JWT 泄露漏洞,请立即更新",
    "minClientVersion": "1.0.5"
  }'

强制更新影响

设为 true 后,所有客户端会在下一次心跳检测时自动刷新页面。建议仅在紧急修复时使用,避免上课期间强制刷新。

数据库迁移 ​

自动迁移 ​

服务启动时,server/src/utils/migration-runner.js 自动扫描 server/src/migrations/ 目录,按文件名顺序执行未应用的迁移。

bash
# 启动服务即触发迁移
pnpm start
# 或
pm2 restart classintra-server

# 日志会输出:
# [migration-runner] 当前版本: 0
# [migration-runner] 执行迁移 001_baseline (version 1)
# [migration-runner] 执行迁移 002_add_index (version 2)
# [migration-runner] 迁移完成,当前版本: 2

手动执行 ​

bash
# 单独运行迁移脚本
cd server
node -e "
var runner = require('./src/utils/migration-runner');
runner.runAll().then(function() {
  console.log('迁移完成');
  process.exit(0);
}).catch(function(e) {
  console.error('迁移失败:', e);
  process.exit(1);
});
"

查看迁移状态 ​

bash
# 已执行的迁移
sqlite3 server/database/classintra.db "SELECT * FROM schema_version ORDER BY version;"

# 输出示例:
# 1|baseline|2025-08-01 08:00:00
# 2|add-posts-index|2025-08-15 14:30:00

重置数据库 ​

危险操作

以下操作会清除所有数据,仅在开发环境或确认数据已备份时使用。

bash
# 方式一:删除数据库文件后重启(自动重建)
cd server
rm -f database/classintra.db database/classintra.db-wal database/classintra.db-shm
node src/app.js
# 自动执行 init-db.js 与所有迁移

# 方式二:使用 reset-db.js(如存在)
node src/utils/reset-db.js

# 方式三:通过迁移 down 回滚(如迁移文件提供 down)
sqlite3 server/database/classintra.db "DELETE FROM schema_version WHERE version >= 2;"
# 删除迁移 2 创建的表/索引
# 再启动服务,会重新执行迁移 2

编写迁移文件 ​

javascript
// server/src/migrations/003_add_reminders.js
module.exports = {
  version: 3,
  name: 'add-reminders-table',
  up: function(db) {
    db.exec(`
      CREATE TABLE IF NOT EXISTS reminders (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id TEXT NOT NULL,
        title TEXT NOT NULL,
        remind_at INTEGER NOT NULL,
        created_at INTEGER NOT NULL,
        done INTEGER DEFAULT 0
      );
      CREATE INDEX IF NOT EXISTS idx_reminders_user ON reminders(user_id);
      CREATE INDEX IF NOT EXISTS idx_reminders_time ON reminders(remind_at);
    `);
  },
  down: function(db) {
    db.exec('DROP TABLE IF EXISTS reminders;');
  }
};

迁移规范

  • 必须幂等:使用 CREATE TABLE IF NOT EXISTS、CREATE INDEX IF NOT EXISTS
  • 必须可回滚:提供 down 函数(虽然目前 migration-runner 不会自动调用,但保留备用)
  • 小步前进:一个迁移只做一件事
  • 不修改现有列:使用新迁移加列(ALTER TABLE ... ADD COLUMN),避免数据迁移

质量门与生态工具 ​

ClassIntra 提供一组验证脚本,覆盖模块化、动效规范、manifest schema 与市场应用审查。

质量门 ​

命令脚本检查内容
pnpm verify:modulesscripts/modularity-verify.js删除式自测:无插件 / 无可选业务模块时仍能 build + boot + 冒烟
pnpm verify:motionscripts/motion-verify.js动效与视觉规范(E1–E8,扫描 client/src + apps/)
pnpm verify:schemascripts/schema-verify.mjsmanifest schema 前后端一致性 + 42 项断言
pnpm verify:all—依次跑上述三项
bash
# 一次跑完全部质量门(提交前建议执行)
pnpm verify:all

市场应用审查 ​

bash
# 审查单个市场应用
pnpm review:market market-apps/gomoku

# 机器可读输出(CI 集成用)
node scripts/market-review.mjs market-apps/gomoku --json

按五组检查:A manifest 合规 · B Chrome 80 语法 · C CSS 兼容 · D 危险 API · E 资源回收。 存在错误时退出码为 1,可直接用于 CI 门禁。

应用脚手架 ​

bash
# 生成市场应用(纯 JS,第三方分发)
pnpm create:app my-tool --label "我的工具"

# 带后端路由与建表 SQL
pnpm create:app my-tool --label "我的工具" --with-backend

# 生成官方内置应用(.vue,进 apps/)
pnpm create:app my-app --label "我的应用" --kind official --dir apps
选项说明
--label <名称>显示名称(默认由应用名推导)
--dir <路径>输出目录(默认 market-apps/)
--kind <类型>market(默认)或 official
--with-backend生成后端路由、建表 SQL 与后端 README
--color <hex>主题色(默认 #007AFF)
--force目标目录已存在时覆盖

生成的模板本身就是「正确示例」——已预置 ES5 语法、--ci-* 令牌消费、context.app.onDestroy 四类资源回收,可直接通过 review:market 审查(0 错误 0 警告)。

应用 / 主题 / 小组件脚手架(三合一) ​

create:app 面向市场应用;官方内置应用(apps/)、主题(themes/)与桌面小组件用 scripts/scaffold.js 三合一脚手架:

bash
# 官方内置应用(自动附满铺图标模板 icon.svg)
node scripts/scaffold.js app my-app --label "我的应用" [--route /my-app]

# 主题(type 必须为 light|dark,theme-loader 硬性契约)
node scripts/scaffold.js theme my-theme --name "我的主题" --type dark

# 桌面小组件(自动把声明合并进应用 manifest 的 frontend.widgets[],无需手动编辑)
node scripts/scaffold.js widget my-app my-clock --name "时钟"

生成的 icon.svg 是直角满铺底(根 rect 无 rx/ry)——圆角由 AppIcon 容器 CSS 统一裁切,资产内烘焙圆角会在四角露出透明缝隙(详见 第三方应用开发 § 图标满铺规范)。主题 tokens 与小组件契约详见 主题开发 与 小组件。

校验器 diag ​

与运行时同一套 manifest schema 校验器,覆盖三类模块 + 兼容性 lint:

命令检查内容
node scripts/diag.js app [name]应用 manifest 规范 + 文件完整性 + 路由冲突(含 widgets[].component 存在性)
node scripts/diag.js plugin [name]插件校验(backend 必有 + mountPath 跨类冲突检测)
node scripts/diag.js theme [id]主题硬性契约(type = light/dark + TOKENS 导出)+ tokens 冒烟
node scripts/diag.js compat [name]Chrome 80 兼容性 lint(扫描 apps/* + market-apps/* 前端)
node scripts/diag.js all全部(自动附带 compat)

compat 分级规则(apps/ 经 vite 构建可转译语法,market-apps/ 直出无任何兜底):

级别规则
FAIL(直出型专属)?. ?? &&= ||= ??=(语法类,Chrome 80 无法解析)
FAIL(一律)structuredClone replaceChildren .at() .findLast(运行时 API,构建也不可转译)
FAIL(一律)CSS aspect-ratio / inset / dvh / :is() / :where()
FAIL(直出)/ WARN(构建型)CSS gap(构建型 flex gap 有 polyfill,grid 需 grid-gap)
WARNbackdrop-filter 缺 -webkit- 前缀

扫描自动跳过块注释与 // 行注释(防文档误报),输出 文件:行号 + 违规内容,FAIL 影响退出码(pre-commit 钩子同标准)。背景与替代方案详见 Chrome 80 兼容约束。

按需构建 build-app ​

bash
node scripts/build-app.js <name>           # 兼容 lint +(apps/)vite 构建 /(market-apps/)直出检查
node scripts/build-app.js <name> --watch   # market-apps/ 专用:文件变化自动 lint + 提示刷新

自动识别应用类型(依次查 apps/ 与 market-apps/ 的 manifest.json)。两类应用的「改动生效」机制完全不同:

  • 先跑 diag.js compat <name>,FAIL 即终止,避免把带伤产物构建出去
  • apps/(构建型):vite 全量构建(单页架构无法单应用构建,vendor chunk 共享)。开发期迭代用 cd client && npx vite dev HMR 免全量构建
  • market-apps/(直出型):检查 entry/style/icon 文件存在性;改完刷新浏览器即生效(no-cache 直出,无需构建)。--watch 防抖 300ms 自动重跑 lint,backend 变更单独提示需重启服务器

后端热重载(开发机可选)

CLASSINTRA_HOT_RELOAD=1 启动服务器时,apps/*/backend 与 plugins/*/backend 改动自动热重载免重启(fs.watch + 防抖 300ms,语法错误时保留旧代码服务不中断)。只覆盖各模块 backend/ 目录内文件,宿主层(server/src/)与前端不适用。

图标满铺体检 icon-trim ​

项目硬性规范:图标资产不留白——内容顶格铺满 100% 画布,圆角由 AppIcon 容器 CSS 统一裁切(72px + radius 20px + object-fit cover)。

powershell
.\scripts\icon-trim.ps1              # 处理 Resources/public/icons(默认)
.\scripts\icon-trim.ps1 -Dir <path>  # 指定其他图标目录

检测 alpha 包围盒 → 裁剪 → 高质量重采样回满画布;支持 PNG 与内嵌 base64 位图的 SVG。全透明、已满铺、>480KB 的文件自动跳过(pre-commit 有 500KB 单文件上限,历史超大资产应先降采样重嵌而非重编码)。

市场双仓同步 sync-market(市场维护者) ​

主仓 .gitignore 忽略 plugins/ 与 market-apps/,插件与市场应用文件只进 market 仓。在主仓工作区开发后必须立即同步,防双仓漂移:

powershell
.\scripts\sync-market.ps1                      # 同步 plugins/ → market 仓 + 显示变更
.\scripts\sync-market.ps1 -Commit "feat: xxx"  # 同步 + 自动提交 market 仓
.\scripts\sync-market.ps1 -Health              # 附带主仓 git health(b→o 字符损坏体检)
.\scripts\sync-market.ps1 -App gomoku          # 额外镜像 market-apps/gomoku → market/apps/gomoku

-App 用 robocopy /MIR 镜像(退出码 0–7 视为成功);-Commit 时自动 git add apps/。插件 manifest 的 version 独立语义化递增,与主仓 server/version.json 无关。

常用组合命令 ​

一键开发环境 ​

bash
# Linux / macOS
git pull && pnpm install && pnpm dev

# Windows
git pull & pnpm install & pnpm dev

一键生产部署 ​

bash
# 拉取最新代码
git pull

# 安装依赖
pnpm install

# 构建前端
pnpm build

# 重启 PM2
pm2 restart ecosystem.config.js --update-env

# 验证
sleep 3
curl http://localhost:9001/api/system/health

升级后回滚 ​

bash
# 查看历史版本
git log --oneline -10

# 回滚到某 commit
git reset --hard <commit-hash>

# 重装依赖(依赖可能变化)
pnpm install

# 重建前端
pnpm build

# 重启
pm2 restart classintra-server

数据库备份脚本 ​

bash
#!/bin/bash
# backup-db.sh
set -e
cd /opt/ClassIntra

DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR=/backup/classintra
mkdir -p $BACKUP_DIR

# 在线备份(无需停服)
sqlite3 server/database/classintra.db ".backup '$BACKUP_DIR/classintra-$DATE.db'"

# 保留最近 30 天
find $BACKUP_DIR -name "classintra-*.db" -mtime +30 -delete

echo "[$DATE] 备份完成: $BACKUP_DIR/classintra-$DATE.db"

加入 crontab:

bash
# 每日凌晨 3 点备份
0 3 * * * /opt/ClassIntra/scripts/backup-db.sh >> /var/log/classintra-backup.log 2>&1

下一步 ​