| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- const { execSync, spawn } = require('child_process')
- const path = require('path')
- const fs = require('fs')
- const crypto = require('crypto')
- const projectRoot = path.resolve(__dirname, '..', '..')
- const buildDir = path.join(projectRoot, 'package', 'build')
- /** 所有构建资源直接引用 package/x64 下的本地目录,不下载 */
- const x64Dir = path.join(projectRoot, 'package', 'x64')
- /** Node 目录(源码执行与打包均使用),需先下载 Node 到 nodejs/node */
- const NODE_PORTABLE_DIR = path.join(projectRoot, 'nodejs', 'node')
- const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR
- /** 构建前校验:所需资源必须全部存在于 package/x64,缺一不可。不通过则直接退出,绝不下载。 */
- function checkLocalResourcesOrExit() {
- const version = getElectronVersion()
- const missing = []
- if (!version) {
- missing.push('无法读取 Electron 版本(node_modules/electron/package.json)')
- } else {
- const electronDir = path.join(x64Dir, `electron-v${version}-win32-x64`)
- const electronZip = path.join(x64Dir, `electron-v${version}-win32-x64.zip`)
- if (!fs.existsSync(electronDir) && !fs.existsSync(electronZip)) {
- missing.push(`Electron: 需要目录或 zip → ${path.basename(electronDir)} 或 ${path.basename(electronZip)}`)
- }
- }
- const nsisDir = path.join(x64Dir, 'nsis-3.0.4.1')
- if (!fs.existsSync(nsisDir) || !fs.statSync(nsisDir).isDirectory()) {
- missing.push('NSIS: 需要目录 → package/x64/nsis-3.0.4.1')
- }
- const winCodeSignDir = path.join(x64Dir, 'winCodeSign')
- if (!fs.existsSync(winCodeSignDir) || !fs.statSync(winCodeSignDir).isDirectory()) {
- missing.push('winCodeSign: 需要目录 → package/x64/winCodeSign')
- }
- if (process.platform === 'win32') {
- const portableExe = path.join(NODE_PORTABLE_DIR, 'node.exe')
- if (!fs.existsSync(portableExe)) {
- missing.push('Node: 需要 nodejs/node/node.exe,请先下载 Node 到 nodejs/node')
- }
- }
- if (missing.length === 0) return
- console.error('\n[错误] 本地资源不完整,禁止下载,已停止打包。请将以下资源放入 package/x64 后重试:')
- missing.forEach((m) => console.error(' - ' + m))
- process.exit(1)
- }
- /** 确保 package/x64/winCodeSign 存在(app-builder 通过 getBin("winCodeSign") 使用),缺则退出。 */
- function ensureWinCodeSignInX64OrExit() {
- const targetDir = path.join(x64Dir, 'winCodeSign')
- if (fs.existsSync(targetDir) && fs.statSync(targetDir).isDirectory()) return
- console.error('\n[错误] 缺少 package/x64/winCodeSign,已停止打包(禁止下载)。')
- process.exit(1)
- }
- /** 取当前 Electron 版本 */
- function getElectronVersion() {
- try {
- return require(path.join(projectRoot, 'node_modules', 'electron', 'package.json')).version
- } catch (e) {
- return null
- }
- }
- /** 若存在已解压目录 package/x64/electron-vX.Y.Z-win32-x64,返回其绝对路径,供 electronDist 直接引用 */
- function getUnpackedElectronDir() {
- const version = getElectronVersion()
- if (!version) return null
- const dir = path.join(x64Dir, `electron-v${version}-win32-x64`)
- return fs.existsSync(dir) ? dir : null
- }
- /** 与 @electron/get 的 Cache.getCacheDirectory 一致:用 URL 目录部分的 sha256 作为缓存子目录 */
- function getElectronCacheSubdir(version) {
- const base = (ELECTRON_MIRROR || 'https://github.com/electron/electron/releases/download').replace(/\/$/, '')
- const strippedUrl = `${base}/v${version}`
- return crypto.createHash('sha256').update(strippedUrl).digest('hex')
- }
- /** 若未使用解压目录,则确保 package/x64 下的 Electron zip 已放入 @electron/get 使用的缓存路径 */
- function ensureElectronZipInCache() {
- const unpacked = getUnpackedElectronDir()
- if (unpacked) return
- const version = getElectronVersion()
- if (!version) return
- const zipName = `electron-v${version}-win32-x64.zip`
- const zipInX64 = path.join(x64Dir, zipName)
- const cacheSubdir = getElectronCacheSubdir(version)
- const cacheDir = path.join(x64Dir, cacheSubdir)
- const zipInCache = path.join(cacheDir, zipName)
- if (!fs.existsSync(zipInX64)) return
- if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true })
- if (fs.existsSync(zipInCache)) return
- try {
- fs.copyFileSync(zipInX64, zipInCache)
- } catch (_) {}
- }
- function getBuilderEnv() {
- ensureElectronZipInCache()
- ensureWinCodeSignInX64OrExit()
- const electronCacheRoot = x64Dir + path.sep
- const builderCache = path.resolve(x64Dir)
- process.env.ELECTRON_BUILDER_CACHE = builderCache
- return {
- ...process.env,
- ELECTRON_MIRROR,
- ELECTRON_CACHE: electronCacheRoot,
- electron_config_cache: electronCacheRoot,
- /** 直接引用 package/x64:nsis-3.0.4.1、winCodeSign 等均从本目录读取,禁止下载 */
- ELECTRON_BUILDER_CACHE: builderCache,
- }
- }
- /** 输出到 package/build */
- const X64_OUTPUT_DIR = 'package/build'
- const X64_UNPACKED_DIR = 'win-unpacked'
- /** Windows:结束可能占用输出目录的进程,避免 EBUSY。 */
- function killProcessesLockingOutput() {
- if (process.platform !== 'win32') return
- const exeNames = ['AndroidRemoteController.exe', 'electron-react-vite-app.exe']
- for (const name of exeNames) {
- try {
- execSync(`taskkill /F /IM "${name}" 2>nul`, { stdio: 'ignore', windowsHide: true })
- } catch (_) {}
- }
- }
- function sleep(ms) {
- return new Promise((r) => setTimeout(r, ms))
- }
- /** 生成临时配置:仅打 dir、输出到 package/build、用本地 electronDist、不签名不 rcedit;显式包含 files/asarUnpack 确保 nodejs 打进包 */
- function getElectronDistConfigPath() {
- const unpacked = getUnpackedElectronDir()
- const config = {
- electronDist: unpacked || undefined,
- directories: { output: X64_OUTPUT_DIR },
- files: [
- 'electron/**',
- 'configs/**',
- 'nodejs/**',
- '!nodejs/node/**',
- 'python/scripts/**',
- 'lib/**'
- ],
- asarUnpack: ['nodejs/**', 'configs/**', 'lib/**', 'python/scripts/**'],
- win: {
- target: [{ target: 'dir', arch: ['x64'] }],
- signAndEditExecutable: false
- }
- }
- if (!config.electronDist) delete config.electronDist
- const configPath = path.join(x64Dir, 'electron-builder-x64-tmp.json')
- fs.writeFileSync(configPath, JSON.stringify(config, null, 0), 'utf8')
- return configPath
- }
- function runElectronBuilder(cwd, env) {
- const cliPath = path.join(cwd, 'node_modules', 'electron-builder', 'out', 'cli', 'cli.js')
- if (!fs.existsSync(cliPath)) {
- return Promise.reject(new Error('未找到 electron-builder,请先在项目根目录执行 npm install。'))
- }
- const args = ['--win', '--x64', '--config', getElectronDistConfigPath()]
- const builderCache = path.resolve(x64Dir)
- const envCopy = { ...process.env, ...env }
- envCopy.ELECTRON_BUILDER_CACHE = builderCache
- if (process.platform === 'win32') {
- const quoted = (s) => (s.includes(' ') || s.includes('"') ? `"${String(s).replace(/"/g, '""')}"` : s)
- const cmdLine = `set "ELECTRON_BUILDER_CACHE=${builderCache}" && ${quoted(process.execPath)} ${quoted(cliPath)} ${args.map(quoted).join(' ')}`
- const opts = { cwd, stdio: ['inherit', 'inherit', 'inherit'], env: envCopy, windowsHide: false, shell: false }
- return new Promise((resolve, reject) => {
- const child = spawn('cmd', ['/c', cmdLine], opts)
- child.on('close', (code) => {
- const tmpConfig = path.join(x64Dir, 'electron-builder-x64-tmp.json')
- if (fs.existsSync(tmpConfig)) try { fs.unlinkSync(tmpConfig) } catch (_) {}
- if (code !== 0) {
- reject(new Error(`electron-builder 退出码: ${code}`))
- } else resolve()
- })
- child.on('error', (err) => reject(err))
- })
- }
- const opts = { cwd, stdio: ['inherit', 'inherit', 'inherit'], env: envCopy, windowsHide: false, shell: false }
- return new Promise((resolve, reject) => {
- const child = spawn(process.execPath, [cliPath, ...args], opts)
- child.on('close', (code) => {
- const tmpConfig = path.join(x64Dir, 'electron-builder-x64-tmp.json')
- if (fs.existsSync(tmpConfig)) try { fs.unlinkSync(tmpConfig) } catch (_) {}
- if (code !== 0) reject(new Error(`electron-builder 退出码: ${code}`))
- else resolve()
- })
- child.on('error', (err) => reject(err))
- })
- }
- function cleanDist(outputDir, keepDir) {
- const dir = path.join(projectRoot, outputDir)
- if (!fs.existsSync(dir)) return
- for (const name of fs.readdirSync(dir)) {
- if (name === keepDir) continue
- const full = path.join(dir, name)
- try {
- if (fs.statSync(full).isDirectory()) fs.rmSync(full, { recursive: true })
- else fs.unlinkSync(full)
- } catch (e) {
- console.warn('clean:', e.message)
- }
- }
- }
- async function main() {
- killProcessesLockingOutput()
- await sleep(400)
- checkLocalResourcesOrExit()
- try {
- console.log('[1/3] Vite build...')
- execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' })
- } catch (e) {
- console.error('\n[错误] 第 1 步 Vite build 失败,已停止打包。')
- console.error(e.message || e)
- process.exit(1)
- }
- try {
- console.log('[2/3] Electron builder win x64...')
- await runElectronBuilder(projectRoot, getBuilderEnv())
- } catch (e) {
- console.error('\n[错误] 第 2 步 electron-builder 失败。')
- console.error(e.message || e)
- process.exit(1)
- }
- try {
- const unpackedDir = path.join(projectRoot, X64_OUTPUT_DIR, X64_UNPACKED_DIR)
- const cp = require('child_process')
- // 先复制 web 到 win-unpacked/dist,再清理,避免删掉 package/build/web
- const webSrc = path.join(buildDir, 'web')
- const webDest = path.join(unpackedDir, 'dist')
- if (fs.existsSync(webSrc) && fs.statSync(webSrc).isDirectory()) {
- try {
- if (!fs.existsSync(webDest)) fs.mkdirSync(webDest, { recursive: true })
- if (process.platform === 'win32') {
- cp.execSync(`xcopy /E /I /Y "${webSrc}\\*" "${webDest}"`, { stdio: 'ignore' })
- } else {
- cp.execSync(`cp -R "${webSrc}"/* "${webDest}"`, { stdio: 'ignore' })
- }
- } catch (_) {}
- }
- const configsSrc = path.join(projectRoot, 'configs')
- const configsDest = path.join(unpackedDir, 'configs')
- if (fs.existsSync(configsSrc) && fs.statSync(configsSrc).isDirectory()) {
- try {
- if (process.platform === 'win32') {
- cp.execSync(`xcopy /E /I /Y "${configsSrc}" "${configsDest}"`, { stdio: 'ignore' })
- } else {
- cp.execSync(`cp -R "${configsSrc}" "${configsDest}"`, { stdio: 'ignore' })
- }
- } catch (_) {}
- }
- console.log('[3/3] 清理 ' + X64_OUTPUT_DIR + '...')
- cleanDist(X64_OUTPUT_DIR, X64_UNPACKED_DIR)
- // 将 nodejs/node 打包进输出,复制到 win-unpacked/node/ 供运行时使用
- const nodeDir = path.join(unpackedDir, 'node')
- const nodeExeName = process.platform === 'win32' ? 'node.exe' : 'node'
- const portableNodeExe = path.join(NODE_PORTABLE_DIR, nodeExeName)
- if (fs.existsSync(portableNodeExe)) {
- try {
- if (!fs.existsSync(nodeDir)) fs.mkdirSync(nodeDir, { recursive: true })
- if (process.platform === 'win32') {
- cp.execSync(`xcopy /E /I /Y "${NODE_PORTABLE_DIR}\\*" "${nodeDir}"`, { stdio: 'ignore' })
- } else {
- cp.execSync(`cp -R "${NODE_PORTABLE_DIR}"/* "${nodeDir}"`, { stdio: 'ignore' })
- }
- console.log('[3/3] 已打包 nodejs/node → ' + X64_UNPACKED_DIR + '/node/')
- } catch (e) {
- console.warn('[3/3] 复制 nodejs/node 失败:', e.message)
- }
- }
- // adb:拷贝 lib/scrcpy-adb 到打包文件夹下,config 里配置为 projectRoot/scrcpy-adb/adb.exe
- const scrcpyAdbSrc = path.join(projectRoot, 'lib', 'scrcpy-adb')
- const scrcpyAdbDest = path.join(unpackedDir, 'scrcpy-adb')
- if (fs.existsSync(scrcpyAdbSrc) && fs.statSync(scrcpyAdbSrc).isDirectory()) {
- try {
- if (!fs.existsSync(scrcpyAdbDest)) fs.mkdirSync(scrcpyAdbDest, { recursive: true })
- if (process.platform === 'win32') {
- cp.execSync(`xcopy /E /I /Y "${scrcpyAdbSrc}\\*" "${scrcpyAdbDest}"`, { stdio: 'ignore' })
- } else {
- cp.execSync(`cp -R "${scrcpyAdbSrc}"/* "${scrcpyAdbDest}"`, { stdio: 'ignore' })
- }
- console.log('[3/3] 已打包 lib/scrcpy-adb → ' + X64_UNPACKED_DIR + '/scrcpy-adb/')
- } catch (e) {
- console.warn('[3/3] 复制 scrcpy-adb 失败:', e.message)
- }
- }
- const staticSrc = path.join(x64Dir, 'static')
- const staticDest = path.join(unpackedDir, 'static')
- if (fs.existsSync(staticSrc) && fs.statSync(staticSrc).isDirectory()) {
- try {
- if (process.platform === 'win32') {
- cp.execSync(`xcopy /E /I /Y "${staticSrc}\\*" "${staticDest}"`, { stdio: 'ignore' })
- } else {
- if (!fs.existsSync(staticDest)) fs.mkdirSync(staticDest, { recursive: true })
- cp.execSync(`cp -R "${staticSrc}"/* "${staticDest}"`, { stdio: 'ignore' })
- }
- console.log('[3/3] 已打包 package/x64/static → ' + X64_UNPACKED_DIR + '/static/')
- } catch (e) {
- console.warn('[3/3] 复制 static 失败:', e.message)
- }
- }
- // Python 整个目录拷贝到 win-unpacked/python/(含 scripts、x64、虚拟环境 x64/env)。用 robocopy 避免路径超长(260)静默失败
- const pythonSrcRoot = path.join(projectRoot, 'python')
- const pythonDestRoot = path.join(unpackedDir, 'python')
- if (fs.existsSync(pythonSrcRoot) && fs.statSync(pythonSrcRoot).isDirectory()) {
- try {
- if (!fs.existsSync(pythonDestRoot)) fs.mkdirSync(pythonDestRoot, { recursive: true })
- if (process.platform === 'win32') {
- // robocopy 支持长路径,/E 含子目录,虚拟环境 env 会一并复制
- const rc = cp.spawnSync('robocopy', [
- pythonSrcRoot,
- pythonDestRoot,
- '/E', '/R:1', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS'
- ], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] })
- const exitCode = rc.status
- if (exitCode >= 8) {
- const err = (rc.stderr || rc.stdout || '').trim()
- throw new Error(`robocopy 退出码 ${exitCode}${err ? ': ' + err : ''}`)
- }
- } else {
- cp.execSync(`cp -R "${pythonSrcRoot}"/* "${pythonDestRoot}"`, { stdio: 'inherit' })
- }
- console.log('[3/3] 已打包 python(含 scripts、x64、虚拟环境 env)→ ' + X64_UNPACKED_DIR + '/python/')
- const x64EnvPy = path.join(unpackedDir, 'python', 'x64', 'env', 'Scripts', 'python.exe')
- const x64Py = path.join(unpackedDir, 'python', 'x64', 'py', 'python.exe')
- if (!fs.existsSync(x64EnvPy)) {
- console.warn('[3/3] 警告: 未找到 python/x64/env(虚拟环境),image-match 需 opencv,请确保源码下 python/x64/env 存在并已安装依赖后重新打包')
- }
- if (!fs.existsSync(x64Py) && !fs.existsSync(x64EnvPy)) {
- console.warn('[3/3] 警告: 打包后未找到 python.exe(请检查 python/x64/env/Scripts 或 python/x64/py 是否已复制)')
- }
- } catch (e) {
- console.warn('[3/3] 复制 python 失败:', e.message)
- }
- }
- // 写入打包用 config:python 路径为 exe 同目录 python/x64,别人电脑无需安装 Python
- const packagedConfigPath = path.join(unpackedDir, 'configs', 'config.js')
- if (fs.existsSync(path.join(unpackedDir, 'configs'))) {
- const packagedConfig = `// 打包后使用:python 在 exe 同目录 python/x64,不依赖系统 Python
- const path = require('path')
- const projectRoot = path.dirname(process.execPath)
- const pythonDir = 'x64'
- const pythonVenvPath = path.join(projectRoot, 'python', pythonDir, 'env')
- module.exports = {
- projectRoot,
- window: { width: 800, height: 600, autoHideMenuBar: true },
- devTools: { enabled: false },
- vite: { port: 9527, host: 'localhost' },
- pythonPath: { path: path.join(projectRoot, 'python', pythonDir) },
- pythonVenvPath,
- adbPath: { path: path.join(projectRoot, 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb') },
- nodejsPath: path.join(projectRoot, 'node', process.platform === 'win32' ? 'node.exe' : 'node')
- }
- `
- try {
- fs.writeFileSync(packagedConfigPath, packagedConfig, 'utf8')
- console.log('[3/3] 已写入 ' + X64_UNPACKED_DIR + '/configs/config.js(python 路径: python/x64)')
- } catch (e) {
- console.warn('[3/3] 写入 configs/config.js 失败:', e.message)
- }
- }
- const readmePath = path.join(unpackedDir, '使用说明.txt')
- const readme = [
- 'AndroidRemoteController - 开包即用',
- '',
- '使用:直接双击运行 AndroidRemoteController.exe',
- '首次运行会在本目录自动创建 static 文件夹(用于存放运行时数据)。',
- '',
- '分发:可将本「win-unpacked」整个文件夹复制到任意 Windows 电脑使用,无需安装。',
- '请勿单独只复制 .exe,必须复制整个文件夹。',
- ''
- ].join('\r\n')
- try {
- fs.writeFileSync(readmePath, readme, 'utf8')
- } catch (_) {}
- const runJs = `/**
- * 在本目录启动 exe,若有报错则写入 启动报错.txt
- * 用法:node run.js 或双击 run.bat
- */
- const path = require('path')
- const fs = require('fs')
- const { spawn } = require('child_process')
- const dir = __dirname
- const errorLogPath = path.join(dir, '启动报错.txt')
- const exeNames = ['AndroidRemoteController.exe', 'electron-react-vite-app.exe']
- let exePath = null
- for (const name of exeNames) {
- const p = path.join(dir, name)
- if (fs.existsSync(p)) {
- exePath = p
- break
- }
- }
- if (!exePath) {
- const msg = \`[\${new Date().toISOString()}] 未找到 exe(\${exeNames.join(' / ')})\n\`
- fs.writeFileSync(errorLogPath, msg, 'utf8')
- console.error(msg.trim())
- process.exit(1)
- }
- const chunks = []
- function writeErrorLog(extra) {
- const header = \`[\${new Date().toISOString()}] 运行 \${path.basename(exePath)} 异常\n\`
- const body = chunks.length ? Buffer.concat(chunks).toString('utf8') : ''
- const tail = extra ? \`\n\${extra}\` : ''
- fs.writeFileSync(errorLogPath, header + body + tail, 'utf8')
- }
- const child = spawn(exePath, [], {
- cwd: dir,
- stdio: ['ignore', 'pipe', 'pipe'],
- windowsHide: false
- })
- child.stdout.on('data', (data) => {
- process.stdout.write(data)
- })
- child.stderr.on('data', (data) => {
- chunks.push(data)
- process.stderr.write(data)
- })
- child.on('error', (err) => {
- writeErrorLog(\`进程启动失败: \${err.message}\`)
- console.error(err)
- process.exit(1)
- })
- child.on('exit', (code, signal) => {
- if (code !== 0 && code != null) {
- writeErrorLog(\`退出码: \${code}\${signal ? \` 信号: \${signal}\` : ''}\`)
- }
- })
- `
- // 使用同目录下 node/node.exe,不依赖系统 PATH 的 node(别人电脑未装 Node 也能用)
- const runBat = '@echo off\r\ncd /d "%~dp0"\r\n"%~dp0node\\node.exe" run.js\r\npause\r\n'
- try {
- fs.writeFileSync(path.join(unpackedDir, 'run.js'), runJs, 'utf8')
- fs.writeFileSync(path.join(unpackedDir, 'run.bat'), runBat, 'utf8')
- } catch (_) {}
- // 若 package/x64 下存在 run.js、run.bat、config.js,直接拷贝到打包文件夹,覆盖上面生成的内容
- const x64RunJs = path.join(x64Dir, 'run.js')
- const x64RunBat = path.join(x64Dir, 'run.bat')
- const x64ConfigJs = path.join(x64Dir, 'config.js')
- if (fs.existsSync(x64RunJs)) {
- try {
- fs.copyFileSync(x64RunJs, path.join(unpackedDir, 'run.js'))
- console.log('[3/3] 已拷贝 package/x64/run.js → ' + X64_UNPACKED_DIR + '/run.js')
- } catch (e) { console.warn('[3/3] 拷贝 run.js 失败:', e.message) }
- }
- if (fs.existsSync(x64RunBat)) {
- try {
- fs.copyFileSync(x64RunBat, path.join(unpackedDir, 'run.bat'))
- console.log('[3/3] 已拷贝 package/x64/run.bat → ' + X64_UNPACKED_DIR + '/run.bat')
- } catch (e) { console.warn('[3/3] 拷贝 run.bat 失败:', e.message) }
- }
- if (fs.existsSync(x64ConfigJs) && fs.existsSync(path.join(unpackedDir, 'configs'))) {
- try {
- fs.copyFileSync(x64ConfigJs, path.join(unpackedDir, 'configs', 'config.js'))
- console.log('[3/3] 已拷贝 package/x64/config.js → ' + X64_UNPACKED_DIR + '/configs/config.js')
- } catch (e) { console.warn('[3/3] 拷贝 config.js 失败:', e.message) }
- }
- console.log('[完成] ' + X64_OUTPUT_DIR + '\\' + X64_UNPACKED_DIR)
- } catch (e) {
- console.error('\n[错误] 第 3 步 清理 ' + X64_OUTPUT_DIR + ' 失败。')
- console.error(e.message || e)
- process.exit(1)
- }
- }
- if (require.main === module) {
- main().catch((e) => {
- console.error(e)
- process.exit(1)
- })
- } else {
- module.exports = { cleanDist }
- }
|