| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- /**
- * 生产产物校验与精简(驾驶舱)。
- *
- * 用法:
- * node tools/pack-cockpit-dist.mjs # 只检查并报告(默认 dry-run)
- * node tools/pack-cockpit-dist.mjs --dist dist # 指定产物目录
- * node tools/pack-cockpit-dist.mjs --prune # 顺手删掉驾驶舱不需要的项
- *
- * 背景(维护者 2026-11-03):生产打包必须精简,工具与中间产物不得进入产物。
- * 事实依据:`fes build` 的产物 = `src/` 构建结果 + `public/` 整拷(+ `public/cesium`),
- * 仓库根的 `tools/`、`docs/`、`.scratch/`、`DesignAssets/`、`openspec/`、`skills/`
- * 都不在 `public/` 下,按构建配置不可能进入 `dist/`;本脚本把这条约束变成可执行断言,
- * 防止将来有人把生成物放进 `public/` 或改 publicDir 后无声污染产物。
- *
- * `--prune` 只删**确认与驾驶舱无关**的项:
- * · `plugins/`:TvT 插件体系的静态资源(108 MB)。驾驶舱不依赖(票 06 验收第 1 条);
- * 该部署若同时要跑插件页面则不要加 --prune。
- */
- import { existsSync, readdirSync, rmSync, statSync } from 'node:fs'
- import path from 'node:path'
- const argv = process.argv
- const arg = (name, fallback) => {
- const i = argv.indexOf(name)
- return i >= 0 && argv[i + 1] ? argv[i + 1] : fallback
- }
- const DIST = path.resolve(arg('--dist', 'dist'))
- const PRUNE = argv.includes('--prune')
- /** 产物里出现即视为污染:工具、中间产物、源文档、审计脚本。 */
- const FORBIDDEN = [
- 'tools', 'docs', 'skills', 'openspec', 'DesignAssets', 'after-deploy',
- 'city-massing', 'cad-bim', 'demo', 'verify', '.scratch',
- ]
- /** 驾驶舱不依赖、可安全精简的产物项(--prune 时才删)。 */
- const PRUNABLE = ['plugins', 'cockpit/prototypes']
- const mb = (bytes) => `${(bytes / 1048576).toFixed(1)} MB`
- function dirSize(p) {
- let total = 0
- for (const entry of readdirSync(p, { withFileTypes: true })) {
- const child = path.join(p, entry.name)
- if (entry.isDirectory()) total += dirSize(child)
- else if (entry.isFile()) { try { total += statSync(child).size } catch {} }
- }
- return total
- }
- if (!existsSync(DIST)) {
- console.error(`产物目录不存在:${DIST}\n先跑 yarn build,或用 --dist 指定实际产物路径。`)
- process.exit(2)
- }
- const top = readdirSync(DIST, { withFileTypes: true })
- const rows = top
- .map((e) => {
- const p = path.join(DIST, e.name)
- const size = e.isDirectory() ? dirSize(p) : (() => { try { return statSync(p).size } catch { return 0 } })()
- return { name: e.name, dir: e.isDirectory(), size }
- })
- .sort((a, b) => b.size - a.size)
- const total = rows.reduce((s, r) => s + r.size, 0)
- console.log(`产物:${DIST}`)
- console.log(`合计:${mb(total)}\n`)
- console.log('按体积排序:')
- for (const r of rows.slice(0, 12)) console.log(` ${mb(r.size).padStart(10)} ${r.name}${r.dir ? '/' : ''}`)
- const leaked = rows.filter((r) => FORBIDDEN.includes(r.name))
- console.log('')
- if (leaked.length) {
- console.error(`✗ 产物含不该有的项:${leaked.map((r) => r.name).join(', ')}`)
- } else {
- console.log('✓ 未发现工具/中间产物/源文档(tools、city-massing、docs 等)')
- }
- const prunable = rows.filter((r) => PRUNABLE.includes(r.name))
- if (prunable.length) {
- const sum = prunable.reduce((s, r) => s + r.size, 0)
- console.log(`\n可精简项:${prunable.map((r) => `${r.name}(${mb(r.size)})`).join(', ')} 合计 ${mb(sum)}`)
- if (PRUNE) {
- for (const r of prunable) {
- rmSync(path.join(DIST, r.name), { recursive: true, force: true })
- console.log(` 已删除 ${r.name}`)
- }
- console.log(` 精简后合计:${mb(total - sum)}`)
- } else {
- console.log(' (加 --prune 才会删除;该部署若需要 TvT 插件页面请忽略此项)')
- }
- }
- process.exit(leaked.length ? 1 : 0)
|