153 lines
5.9 KiB
JavaScript
153 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
// Windows 外壳一键构建 (开发用): 安装包 (NSIS) + 便携版 (portable zip)
|
|
//
|
|
// 用法:
|
|
// npm run build:win # 完整流程
|
|
// node scripts/build-windows.mjs --skip-check # 跳过版本一致性校验
|
|
// node scripts/build-windows.mjs --debug # debug 构建 (快速验证, 产物带 -debug 后缀)
|
|
//
|
|
// 流程:
|
|
// 1) 版本一致性校验 (scripts/ci/check-version.mjs, 外壳三件套)
|
|
// 2) 前端构建 (build:shell, 注入 VITE_API_BASE 指向远端后端)
|
|
// 3) tauri build --bundles nsis → 编译一次 release exe + NSIS 安装包
|
|
// 4) 复用同一个 exe 组装便携版目录并压成 zip (不二次编译)
|
|
//
|
|
// 产物 (artifacts/windows/):
|
|
// iBoard_<ver>_x64-setup.exe 安装包 (当前用户安装, 免管理员)
|
|
// iBoard_<ver>_x64-portable.zip 便携版 (解压即用)
|
|
// portable/ 便携版解压目录 (iBoard.exe + README.txt)
|
|
//
|
|
// 说明: 便携版不内置 WebView2 —— 目标机需 Win10 19041+ / Win11 (系统自带 WebView2 Runtime)。
|
|
import { spawnSync } from 'node:child_process'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
const args = process.argv.slice(2)
|
|
const skipCheck = args.includes('--skip-check')
|
|
const isDebug = args.includes('--debug')
|
|
const profile = isDebug ? 'debug' : 'release'
|
|
|
|
const conf = JSON.parse(fs.readFileSync(path.join(root, 'src-tauri', 'tauri.conf.json'), 'utf8'))
|
|
const version = String(conf.version || '0.0.0')
|
|
const product = conf.productName || 'iBoard'
|
|
const suffix = isDebug ? '-debug' : ''
|
|
|
|
const artifactsDir = path.join(root, 'artifacts', 'windows')
|
|
const targetDir = path.join(root, 'src-tauri', 'target', profile)
|
|
|
|
function log(msg) {
|
|
console.log(`\x1b[36m[build-win]\x1b[0m ${msg}`)
|
|
}
|
|
function fail(msg) {
|
|
console.error(`\x1b[31m[build-win] 失败: ${msg}\x1b[0m`)
|
|
process.exit(1)
|
|
}
|
|
function run(cmd, cmdArgs, opts = {}) {
|
|
log(`$ ${cmd} ${cmdArgs.join(' ')}`)
|
|
const res = spawnSync(cmd, cmdArgs, {
|
|
cwd: root,
|
|
stdio: 'inherit',
|
|
shell: process.platform === 'win32',
|
|
...opts,
|
|
})
|
|
if (res.status !== 0) fail(`${cmd} ${cmdArgs.join(' ')} 退出码 ${res.status}`)
|
|
}
|
|
|
|
// ---------- 0. 前置检查 ----------
|
|
if (process.platform !== 'win32') fail('本脚本仅支持 Windows')
|
|
if (!fs.existsSync(path.join(root, 'node_modules'))) fail('缺少 node_modules, 请先运行 npm ci')
|
|
|
|
log(`项目 ${product} 外壳版本 ${version} (${profile})`)
|
|
|
|
// ---------- 1. 版本一致性 ----------
|
|
if (!skipCheck) {
|
|
log('校验版本一致性 (外壳 tauri.conf.json / Cargo.toml / Cargo.lock)')
|
|
run('node', ['scripts/ci/check-version.mjs'], {
|
|
env: { ...process.env, BRANCH: process.env.BRANCH || '' },
|
|
})
|
|
} else {
|
|
log('跳过版本一致性校验 (--skip-check)')
|
|
}
|
|
|
|
// ---------- 2. 前端构建 ----------
|
|
log('构建前端 (build:shell, VITE_API_BASE 指向远端后端)')
|
|
run('npm', ['run', 'build:shell'])
|
|
|
|
// ---------- 3. 安装包 (NSIS) ----------
|
|
const tauriArgs = ['tauri', 'build']
|
|
if (isDebug) tauriArgs.push('--debug')
|
|
// 只出 NSIS: 避免 bundle.targets="all" 在 Windows 上拉 WiX 打 MSI
|
|
tauriArgs.push('--bundles', 'nsis')
|
|
log('编译外壳并生成 NSIS 安装包')
|
|
run('npx', tauriArgs)
|
|
|
|
// ---------- 4. 便携版 ----------
|
|
const exePath = path.join(targetDir, `iboard${suffix}.exe`)
|
|
if (!fs.existsSync(exePath)) fail(`未找到构建产物 ${exePath}`)
|
|
|
|
fs.rmSync(artifactsDir, { recursive: true, force: true })
|
|
fs.mkdirSync(artifactsDir, { recursive: true })
|
|
|
|
const portableDir = path.join(artifactsDir, 'portable')
|
|
fs.mkdirSync(portableDir, { recursive: true })
|
|
const portableExe = path.join(portableDir, `${product}.exe`)
|
|
fs.copyFileSync(exePath, portableExe)
|
|
|
|
const readme = `${product} ${version} 便携版 (Windows x64)
|
|
=================================================
|
|
|
|
免安装: 双击 ${product}.exe 即可运行。
|
|
|
|
系统要求
|
|
- Windows 10 19041+ 或 Windows 11 (x64)
|
|
- WebView2 Runtime: Win10/11 系统自带; 若提示缺失, 请安装
|
|
https://developer.microsoft.com/microsoft-edge/webview2/
|
|
|
|
使用说明
|
|
- 关闭窗口 = 隐藏到托盘, 不会退出; 退出请用托盘右键菜单「退出」。
|
|
- 窗口贴屏幕边缘会自动收起为半圆悬浮球, 鼠标停留或单击唤回。
|
|
- 托盘菜单可开关「开机启动」「边缘吸附」。
|
|
- 配置保存在 %APPDATA%\\com.iboard.app\\shell-config.json (删除即恢复默认)。
|
|
|
|
注意: 便携版不写注册表、不装服务; 但若开启了「开机启动」, 会在
|
|
HKCU\\...\\Run 写入自启动项, 关闭开关即可移除。
|
|
`
|
|
fs.writeFileSync(path.join(portableDir, 'README.txt'), readme, 'utf8')
|
|
|
|
const zipName = `${product}_${version}_x64-portable${suffix}.zip`
|
|
const zipPath = path.join(artifactsDir, zipName)
|
|
log('打包便携版 zip')
|
|
const psZip = `Compress-Archive -Path '${portableDir}\\*' -DestinationPath '${zipPath}' -Force`
|
|
const res = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', psZip], {
|
|
cwd: root,
|
|
stdio: 'inherit',
|
|
})
|
|
if (res.status !== 0) fail('压缩便携版失败 (Compress-Archive)')
|
|
|
|
// ---------- 5. 汇总 ----------
|
|
const setupExe = path.join(
|
|
targetDir,
|
|
'bundle',
|
|
'nsis',
|
|
`${product}_${version}_x64-setup${suffix}.exe`,
|
|
)
|
|
const outputs = []
|
|
if (fs.existsSync(setupExe)) {
|
|
const dest = path.join(artifactsDir, path.basename(setupExe))
|
|
fs.copyFileSync(setupExe, dest)
|
|
outputs.push(['安装包 (NSIS)', dest])
|
|
} else {
|
|
outputs.push(['安装包 (NSIS)', `未找到: ${setupExe}`])
|
|
}
|
|
outputs.push(['便携版 (zip)', zipPath])
|
|
outputs.push(['便携版 (目录)', `${portableDir} (${fs.readdirSync(portableDir).length} 个文件)`])
|
|
|
|
log('构建完成:')
|
|
for (const [label, p] of outputs) {
|
|
const size = fs.existsSync(p) ? `${(fs.statSync(p).size / 1024 / 1024).toFixed(1)} MB` : ''
|
|
console.log(` ${label.padEnd(14)} ${p}${size ? ' (' + size + ')' : ''}`)
|
|
}
|
|
process.exit(0)
|