Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

84 lines
2.8 KiB
JavaScript

import { execFileSync } from 'node:child_process'
import { readdirSync, statSync } from 'node:fs'
import { join, relative } from 'node:path'
import process from 'node:process'
const root = process.cwd()
const distDir = join(root, 'dist')
const budgets = {
maxDistBytes: Number(process.env.MAX_DIST_BYTES || 800 * 1024),
maxJsChunkBytes: Number(process.env.MAX_JS_CHUNK_BYTES || 300 * 1024),
maxPackSizeBytes: Number(process.env.MAX_PACK_TGZ_BYTES || 250 * 1024),
maxPackUnpackedBytes: Number(process.env.MAX_PACK_UNPACKED_BYTES || 740 * 1024),
}
function formatBytes(bytes) {
if (bytes < 1024)
return `${bytes} B`
if (bytes < 1024 * 1024)
return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
}
function collectFiles(dir, files = []) {
for (const name of readdirSync(dir)) {
const fullPath = join(dir, name)
const stat = statSync(fullPath)
if (stat.isDirectory()) {
collectFiles(fullPath, files)
}
else {
files.push({
fullPath,
relPath: relative(root, fullPath),
size: stat.size,
})
}
}
return files
}
const allFiles = collectFiles(distDir)
const distBytes = allFiles.reduce((sum, f) => sum + f.size, 0)
const jsFiles = allFiles.filter(f => f.relPath.endsWith('.js'))
const largestJs = jsFiles.sort((a, b) => b.size - a.size)[0]
const packJson = execFileSync('npm', ['pack', '--dry-run', '--json'], {
cwd: root,
encoding: 'utf8',
})
const pack = JSON.parse(packJson)?.[0]
if (!pack)
throw new Error('Failed to read npm pack --dry-run --json output.')
const failures = []
if (distBytes > budgets.maxDistBytes) {
failures.push(`dist total is ${formatBytes(distBytes)}, exceeds budget ${formatBytes(budgets.maxDistBytes)}`)
}
if (largestJs && largestJs.size > budgets.maxJsChunkBytes) {
failures.push(`${largestJs.relPath} is ${formatBytes(largestJs.size)}, exceeds JS chunk budget ${formatBytes(budgets.maxJsChunkBytes)}`)
}
if (pack.size > budgets.maxPackSizeBytes) {
failures.push(`npm tarball is ${formatBytes(pack.size)}, exceeds budget ${formatBytes(budgets.maxPackSizeBytes)}`)
}
if (pack.unpackedSize > budgets.maxPackUnpackedBytes) {
failures.push(`npm unpacked size is ${formatBytes(pack.unpackedSize)}, exceeds budget ${formatBytes(budgets.maxPackUnpackedBytes)}`)
}
console.log(`[size-check] dist total: ${formatBytes(distBytes)}`)
if (largestJs)
console.log(`[size-check] largest JS: ${largestJs.relPath} (${formatBytes(largestJs.size)})`)
console.log(`[size-check] pack tarball: ${formatBytes(pack.size)}`)
console.log(`[size-check] pack unpacked: ${formatBytes(pack.unpackedSize)}`)
if (failures.length > 0) {
console.error('[size-check] Budget check failed:')
for (const line of failures)
console.error(`- ${line}`)
process.exit(1)
}
console.log('[size-check] Budget check passed.')