package-x64.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. const { execSync, spawn } = require('child_process')
  2. const path = require('path')
  3. const fs = require('fs')
  4. const crypto = require('crypto')
  5. const projectRoot = path.resolve(__dirname, '..', '..')
  6. const buildDir = path.join(projectRoot, 'package', 'build')
  7. /** 所有构建资源直接引用 package/x64 下的本地目录,不下载 */
  8. const x64Dir = path.join(projectRoot, 'package', 'x64')
  9. /** Node 目录(源码执行与打包均使用),需先下载 Node 到 nodejs/node */
  10. const NODE_PORTABLE_DIR = path.join(projectRoot, 'nodejs', 'node')
  11. const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR
  12. /** 构建前校验:所需资源必须全部存在于 package/x64,缺一不可。不通过则直接退出,绝不下载。 */
  13. function checkLocalResourcesOrExit() {
  14. const version = getElectronVersion()
  15. const missing = []
  16. if (!version) {
  17. missing.push('无法读取 Electron 版本(node_modules/electron/package.json)')
  18. } else {
  19. const electronDir = path.join(x64Dir, `electron-v${version}-win32-x64`)
  20. const electronZip = path.join(x64Dir, `electron-v${version}-win32-x64.zip`)
  21. if (!fs.existsSync(electronDir) && !fs.existsSync(electronZip)) {
  22. missing.push(`Electron: 需要目录或 zip → ${path.basename(electronDir)} 或 ${path.basename(electronZip)}`)
  23. }
  24. }
  25. const nsisDir = path.join(x64Dir, 'nsis-3.0.4.1')
  26. if (!fs.existsSync(nsisDir) || !fs.statSync(nsisDir).isDirectory()) {
  27. missing.push('NSIS: 需要目录 → package/x64/nsis-3.0.4.1')
  28. }
  29. const winCodeSignDir = path.join(x64Dir, 'winCodeSign')
  30. if (!fs.existsSync(winCodeSignDir) || !fs.statSync(winCodeSignDir).isDirectory()) {
  31. missing.push('winCodeSign: 需要目录 → package/x64/winCodeSign')
  32. }
  33. if (process.platform === 'win32') {
  34. const portableExe = path.join(NODE_PORTABLE_DIR, 'node.exe')
  35. if (!fs.existsSync(portableExe)) {
  36. missing.push('Node: 需要 nodejs/node/node.exe,请先下载 Node 到 nodejs/node')
  37. }
  38. }
  39. if (missing.length === 0) return
  40. console.error('\n[错误] 本地资源不完整,禁止下载,已停止打包。请将以下资源放入 package/x64 后重试:')
  41. missing.forEach((m) => console.error(' - ' + m))
  42. process.exit(1)
  43. }
  44. /** 确保 package/x64/winCodeSign 存在(app-builder 通过 getBin("winCodeSign") 使用),缺则退出。 */
  45. function ensureWinCodeSignInX64OrExit() {
  46. const targetDir = path.join(x64Dir, 'winCodeSign')
  47. if (fs.existsSync(targetDir) && fs.statSync(targetDir).isDirectory()) return
  48. console.error('\n[错误] 缺少 package/x64/winCodeSign,已停止打包(禁止下载)。')
  49. process.exit(1)
  50. }
  51. /** 取当前 Electron 版本 */
  52. function getElectronVersion() {
  53. try {
  54. return require(path.join(projectRoot, 'node_modules', 'electron', 'package.json')).version
  55. } catch (e) {
  56. return null
  57. }
  58. }
  59. /** 若存在已解压目录 package/x64/electron-vX.Y.Z-win32-x64,返回其绝对路径,供 electronDist 直接引用 */
  60. function getUnpackedElectronDir() {
  61. const version = getElectronVersion()
  62. if (!version) return null
  63. const dir = path.join(x64Dir, `electron-v${version}-win32-x64`)
  64. return fs.existsSync(dir) ? dir : null
  65. }
  66. /** 与 @electron/get 的 Cache.getCacheDirectory 一致:用 URL 目录部分的 sha256 作为缓存子目录 */
  67. function getElectronCacheSubdir(version) {
  68. const base = (ELECTRON_MIRROR || 'https://github.com/electron/electron/releases/download').replace(/\/$/, '')
  69. const strippedUrl = `${base}/v${version}`
  70. return crypto.createHash('sha256').update(strippedUrl).digest('hex')
  71. }
  72. /** 若未使用解压目录,则确保 package/x64 下的 Electron zip 已放入 @electron/get 使用的缓存路径 */
  73. function ensureElectronZipInCache() {
  74. const unpacked = getUnpackedElectronDir()
  75. if (unpacked) return
  76. const version = getElectronVersion()
  77. if (!version) return
  78. const zipName = `electron-v${version}-win32-x64.zip`
  79. const zipInX64 = path.join(x64Dir, zipName)
  80. const cacheSubdir = getElectronCacheSubdir(version)
  81. const cacheDir = path.join(x64Dir, cacheSubdir)
  82. const zipInCache = path.join(cacheDir, zipName)
  83. if (!fs.existsSync(zipInX64)) return
  84. if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true })
  85. if (fs.existsSync(zipInCache)) return
  86. try {
  87. fs.copyFileSync(zipInX64, zipInCache)
  88. } catch (_) {}
  89. }
  90. function getBuilderEnv() {
  91. ensureElectronZipInCache()
  92. ensureWinCodeSignInX64OrExit()
  93. const electronCacheRoot = x64Dir + path.sep
  94. const builderCache = path.resolve(x64Dir)
  95. process.env.ELECTRON_BUILDER_CACHE = builderCache
  96. return {
  97. ...process.env,
  98. ELECTRON_MIRROR,
  99. ELECTRON_CACHE: electronCacheRoot,
  100. electron_config_cache: electronCacheRoot,
  101. /** 直接引用 package/x64:nsis-3.0.4.1、winCodeSign 等均从本目录读取,禁止下载 */
  102. ELECTRON_BUILDER_CACHE: builderCache,
  103. }
  104. }
  105. /** 输出到 package/build */
  106. const X64_OUTPUT_DIR = 'package/build'
  107. const X64_UNPACKED_DIR = 'win-unpacked'
  108. /** Windows:结束可能占用输出目录的进程,避免 EBUSY。 */
  109. function killProcessesLockingOutput() {
  110. if (process.platform !== 'win32') return
  111. const exeNames = ['AndroidRemoteController.exe', 'electron-react-vite-app.exe']
  112. for (const name of exeNames) {
  113. try {
  114. execSync(`taskkill /F /IM "${name}" 2>nul`, { stdio: 'ignore', windowsHide: true })
  115. } catch (_) {}
  116. }
  117. }
  118. function sleep(ms) {
  119. return new Promise((r) => setTimeout(r, ms))
  120. }
  121. /** 生成临时配置:仅打 dir、输出到 package/build、用本地 electronDist、不签名不 rcedit;显式包含 files/asarUnpack 确保 nodejs 打进包 */
  122. function getElectronDistConfigPath() {
  123. const unpacked = getUnpackedElectronDir()
  124. const config = {
  125. electronDist: unpacked || undefined,
  126. directories: { output: X64_OUTPUT_DIR },
  127. files: [
  128. 'electron/**',
  129. 'configs/**',
  130. 'nodejs/**',
  131. '!nodejs/node/**',
  132. 'python/scripts/**',
  133. 'lib/**'
  134. ],
  135. asarUnpack: ['nodejs/**', 'configs/**', 'lib/**', 'python/scripts/**'],
  136. win: {
  137. target: [{ target: 'dir', arch: ['x64'] }],
  138. signAndEditExecutable: false
  139. }
  140. }
  141. if (!config.electronDist) delete config.electronDist
  142. const configPath = path.join(x64Dir, 'electron-builder-x64-tmp.json')
  143. fs.writeFileSync(configPath, JSON.stringify(config, null, 0), 'utf8')
  144. return configPath
  145. }
  146. function runElectronBuilder(cwd, env) {
  147. const cliPath = path.join(cwd, 'node_modules', 'electron-builder', 'out', 'cli', 'cli.js')
  148. if (!fs.existsSync(cliPath)) {
  149. return Promise.reject(new Error('未找到 electron-builder,请先在项目根目录执行 npm install。'))
  150. }
  151. const args = ['--win', '--x64', '--config', getElectronDistConfigPath()]
  152. const builderCache = path.resolve(x64Dir)
  153. const envCopy = { ...process.env, ...env }
  154. envCopy.ELECTRON_BUILDER_CACHE = builderCache
  155. if (process.platform === 'win32') {
  156. const quoted = (s) => (s.includes(' ') || s.includes('"') ? `"${String(s).replace(/"/g, '""')}"` : s)
  157. const cmdLine = `set "ELECTRON_BUILDER_CACHE=${builderCache}" && ${quoted(process.execPath)} ${quoted(cliPath)} ${args.map(quoted).join(' ')}`
  158. const opts = { cwd, stdio: ['inherit', 'inherit', 'inherit'], env: envCopy, windowsHide: false, shell: false }
  159. return new Promise((resolve, reject) => {
  160. const child = spawn('cmd', ['/c', cmdLine], opts)
  161. child.on('close', (code) => {
  162. const tmpConfig = path.join(x64Dir, 'electron-builder-x64-tmp.json')
  163. if (fs.existsSync(tmpConfig)) try { fs.unlinkSync(tmpConfig) } catch (_) {}
  164. if (code !== 0) {
  165. reject(new Error(`electron-builder 退出码: ${code}`))
  166. } else resolve()
  167. })
  168. child.on('error', (err) => reject(err))
  169. })
  170. }
  171. const opts = { cwd, stdio: ['inherit', 'inherit', 'inherit'], env: envCopy, windowsHide: false, shell: false }
  172. return new Promise((resolve, reject) => {
  173. const child = spawn(process.execPath, [cliPath, ...args], opts)
  174. child.on('close', (code) => {
  175. const tmpConfig = path.join(x64Dir, 'electron-builder-x64-tmp.json')
  176. if (fs.existsSync(tmpConfig)) try { fs.unlinkSync(tmpConfig) } catch (_) {}
  177. if (code !== 0) reject(new Error(`electron-builder 退出码: ${code}`))
  178. else resolve()
  179. })
  180. child.on('error', (err) => reject(err))
  181. })
  182. }
  183. function cleanDist(outputDir, keepDir) {
  184. const dir = path.join(projectRoot, outputDir)
  185. if (!fs.existsSync(dir)) return
  186. for (const name of fs.readdirSync(dir)) {
  187. if (name === keepDir) continue
  188. const full = path.join(dir, name)
  189. try {
  190. if (fs.statSync(full).isDirectory()) fs.rmSync(full, { recursive: true })
  191. else fs.unlinkSync(full)
  192. } catch (e) {
  193. console.warn('clean:', e.message)
  194. }
  195. }
  196. }
  197. async function main() {
  198. killProcessesLockingOutput()
  199. await sleep(400)
  200. checkLocalResourcesOrExit()
  201. try {
  202. console.log('[1/3] Vite build...')
  203. execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' })
  204. } catch (e) {
  205. console.error('\n[错误] 第 1 步 Vite build 失败,已停止打包。')
  206. console.error(e.message || e)
  207. process.exit(1)
  208. }
  209. try {
  210. console.log('[2/3] Electron builder win x64...')
  211. await runElectronBuilder(projectRoot, getBuilderEnv())
  212. } catch (e) {
  213. console.error('\n[错误] 第 2 步 electron-builder 失败。')
  214. console.error(e.message || e)
  215. process.exit(1)
  216. }
  217. try {
  218. const unpackedDir = path.join(projectRoot, X64_OUTPUT_DIR, X64_UNPACKED_DIR)
  219. const cp = require('child_process')
  220. // 先复制 web 到 win-unpacked/dist,再清理,避免删掉 package/build/web
  221. const webSrc = path.join(buildDir, 'web')
  222. const webDest = path.join(unpackedDir, 'dist')
  223. if (fs.existsSync(webSrc) && fs.statSync(webSrc).isDirectory()) {
  224. try {
  225. if (!fs.existsSync(webDest)) fs.mkdirSync(webDest, { recursive: true })
  226. if (process.platform === 'win32') {
  227. cp.execSync(`xcopy /E /I /Y "${webSrc}\\*" "${webDest}"`, { stdio: 'ignore' })
  228. } else {
  229. cp.execSync(`cp -R "${webSrc}"/* "${webDest}"`, { stdio: 'ignore' })
  230. }
  231. } catch (_) {}
  232. }
  233. const configsSrc = path.join(projectRoot, 'configs')
  234. const configsDest = path.join(unpackedDir, 'configs')
  235. if (fs.existsSync(configsSrc) && fs.statSync(configsSrc).isDirectory()) {
  236. try {
  237. if (process.platform === 'win32') {
  238. cp.execSync(`xcopy /E /I /Y "${configsSrc}" "${configsDest}"`, { stdio: 'ignore' })
  239. } else {
  240. cp.execSync(`cp -R "${configsSrc}" "${configsDest}"`, { stdio: 'ignore' })
  241. }
  242. } catch (_) {}
  243. }
  244. console.log('[3/3] 清理 ' + X64_OUTPUT_DIR + '...')
  245. cleanDist(X64_OUTPUT_DIR, X64_UNPACKED_DIR)
  246. // 将 nodejs/node 打包进输出,复制到 win-unpacked/node/ 供运行时使用
  247. const nodeDir = path.join(unpackedDir, 'node')
  248. const nodeExeName = process.platform === 'win32' ? 'node.exe' : 'node'
  249. const portableNodeExe = path.join(NODE_PORTABLE_DIR, nodeExeName)
  250. if (fs.existsSync(portableNodeExe)) {
  251. try {
  252. if (!fs.existsSync(nodeDir)) fs.mkdirSync(nodeDir, { recursive: true })
  253. if (process.platform === 'win32') {
  254. cp.execSync(`xcopy /E /I /Y "${NODE_PORTABLE_DIR}\\*" "${nodeDir}"`, { stdio: 'ignore' })
  255. } else {
  256. cp.execSync(`cp -R "${NODE_PORTABLE_DIR}"/* "${nodeDir}"`, { stdio: 'ignore' })
  257. }
  258. console.log('[3/3] 已打包 nodejs/node → ' + X64_UNPACKED_DIR + '/node/')
  259. } catch (e) {
  260. console.warn('[3/3] 复制 nodejs/node 失败:', e.message)
  261. }
  262. }
  263. // adb:拷贝 lib/scrcpy-adb 到打包文件夹下,config 里配置为 projectRoot/scrcpy-adb/adb.exe
  264. const scrcpyAdbSrc = path.join(projectRoot, 'lib', 'scrcpy-adb')
  265. const scrcpyAdbDest = path.join(unpackedDir, 'scrcpy-adb')
  266. if (fs.existsSync(scrcpyAdbSrc) && fs.statSync(scrcpyAdbSrc).isDirectory()) {
  267. try {
  268. if (!fs.existsSync(scrcpyAdbDest)) fs.mkdirSync(scrcpyAdbDest, { recursive: true })
  269. if (process.platform === 'win32') {
  270. cp.execSync(`xcopy /E /I /Y "${scrcpyAdbSrc}\\*" "${scrcpyAdbDest}"`, { stdio: 'ignore' })
  271. } else {
  272. cp.execSync(`cp -R "${scrcpyAdbSrc}"/* "${scrcpyAdbDest}"`, { stdio: 'ignore' })
  273. }
  274. console.log('[3/3] 已打包 lib/scrcpy-adb → ' + X64_UNPACKED_DIR + '/scrcpy-adb/')
  275. } catch (e) {
  276. console.warn('[3/3] 复制 scrcpy-adb 失败:', e.message)
  277. }
  278. }
  279. const staticSrc = path.join(x64Dir, 'static')
  280. const staticDest = path.join(unpackedDir, 'static')
  281. if (fs.existsSync(staticSrc) && fs.statSync(staticSrc).isDirectory()) {
  282. try {
  283. if (process.platform === 'win32') {
  284. cp.execSync(`xcopy /E /I /Y "${staticSrc}\\*" "${staticDest}"`, { stdio: 'ignore' })
  285. } else {
  286. if (!fs.existsSync(staticDest)) fs.mkdirSync(staticDest, { recursive: true })
  287. cp.execSync(`cp -R "${staticSrc}"/* "${staticDest}"`, { stdio: 'ignore' })
  288. }
  289. console.log('[3/3] 已打包 package/x64/static → ' + X64_UNPACKED_DIR + '/static/')
  290. } catch (e) {
  291. console.warn('[3/3] 复制 static 失败:', e.message)
  292. }
  293. }
  294. // Python 整个目录拷贝到 win-unpacked/python/(含 scripts、x64、虚拟环境 x64/env)。用 robocopy 避免路径超长(260)静默失败
  295. const pythonSrcRoot = path.join(projectRoot, 'python')
  296. const pythonDestRoot = path.join(unpackedDir, 'python')
  297. if (fs.existsSync(pythonSrcRoot) && fs.statSync(pythonSrcRoot).isDirectory()) {
  298. try {
  299. if (!fs.existsSync(pythonDestRoot)) fs.mkdirSync(pythonDestRoot, { recursive: true })
  300. if (process.platform === 'win32') {
  301. // robocopy 支持长路径,/E 含子目录,虚拟环境 env 会一并复制
  302. const rc = cp.spawnSync('robocopy', [
  303. pythonSrcRoot,
  304. pythonDestRoot,
  305. '/E', '/R:1', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS'
  306. ], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] })
  307. const exitCode = rc.status
  308. if (exitCode >= 8) {
  309. const err = (rc.stderr || rc.stdout || '').trim()
  310. throw new Error(`robocopy 退出码 ${exitCode}${err ? ': ' + err : ''}`)
  311. }
  312. } else {
  313. cp.execSync(`cp -R "${pythonSrcRoot}"/* "${pythonDestRoot}"`, { stdio: 'inherit' })
  314. }
  315. console.log('[3/3] 已打包 python(含 scripts、x64、虚拟环境 env)→ ' + X64_UNPACKED_DIR + '/python/')
  316. const x64EnvPy = path.join(unpackedDir, 'python', 'x64', 'env', 'Scripts', 'python.exe')
  317. const x64Py = path.join(unpackedDir, 'python', 'x64', 'py', 'python.exe')
  318. if (!fs.existsSync(x64EnvPy)) {
  319. console.warn('[3/3] 警告: 未找到 python/x64/env(虚拟环境),image-match 需 opencv,请确保源码下 python/x64/env 存在并已安装依赖后重新打包')
  320. }
  321. if (!fs.existsSync(x64Py) && !fs.existsSync(x64EnvPy)) {
  322. console.warn('[3/3] 警告: 打包后未找到 python.exe(请检查 python/x64/env/Scripts 或 python/x64/py 是否已复制)')
  323. }
  324. } catch (e) {
  325. console.warn('[3/3] 复制 python 失败:', e.message)
  326. }
  327. }
  328. // 写入打包用 config:python 路径为 exe 同目录 python/x64,别人电脑无需安装 Python
  329. const packagedConfigPath = path.join(unpackedDir, 'configs', 'config.js')
  330. if (fs.existsSync(path.join(unpackedDir, 'configs'))) {
  331. const packagedConfig = `// 打包后使用:python 在 exe 同目录 python/x64,不依赖系统 Python
  332. const path = require('path')
  333. const projectRoot = path.dirname(process.execPath)
  334. const pythonDir = 'x64'
  335. const pythonVenvPath = path.join(projectRoot, 'python', pythonDir, 'env')
  336. module.exports = {
  337. projectRoot,
  338. window: { width: 800, height: 600, autoHideMenuBar: true },
  339. devTools: { enabled: false },
  340. vite: { port: 9527, host: 'localhost' },
  341. pythonPath: { path: path.join(projectRoot, 'python', pythonDir) },
  342. pythonVenvPath,
  343. adbPath: { path: path.join(projectRoot, 'scrcpy-adb', process.platform === 'win32' ? 'adb.exe' : 'adb') },
  344. nodejsPath: path.join(projectRoot, 'node', process.platform === 'win32' ? 'node.exe' : 'node')
  345. }
  346. `
  347. try {
  348. fs.writeFileSync(packagedConfigPath, packagedConfig, 'utf8')
  349. console.log('[3/3] 已写入 ' + X64_UNPACKED_DIR + '/configs/config.js(python 路径: python/x64)')
  350. } catch (e) {
  351. console.warn('[3/3] 写入 configs/config.js 失败:', e.message)
  352. }
  353. }
  354. const readmePath = path.join(unpackedDir, '使用说明.txt')
  355. const readme = [
  356. 'AndroidRemoteController - 开包即用',
  357. '',
  358. '使用:直接双击运行 AndroidRemoteController.exe',
  359. '首次运行会在本目录自动创建 static 文件夹(用于存放运行时数据)。',
  360. '',
  361. '分发:可将本「win-unpacked」整个文件夹复制到任意 Windows 电脑使用,无需安装。',
  362. '请勿单独只复制 .exe,必须复制整个文件夹。',
  363. ''
  364. ].join('\r\n')
  365. try {
  366. fs.writeFileSync(readmePath, readme, 'utf8')
  367. } catch (_) {}
  368. const runJs = `/**
  369. * 在本目录启动 exe,若有报错则写入 启动报错.txt
  370. * 用法:node run.js 或双击 run.bat
  371. */
  372. const path = require('path')
  373. const fs = require('fs')
  374. const { spawn } = require('child_process')
  375. const dir = __dirname
  376. const errorLogPath = path.join(dir, '启动报错.txt')
  377. const exeNames = ['AndroidRemoteController.exe', 'electron-react-vite-app.exe']
  378. let exePath = null
  379. for (const name of exeNames) {
  380. const p = path.join(dir, name)
  381. if (fs.existsSync(p)) {
  382. exePath = p
  383. break
  384. }
  385. }
  386. if (!exePath) {
  387. const msg = \`[\${new Date().toISOString()}] 未找到 exe(\${exeNames.join(' / ')})\n\`
  388. fs.writeFileSync(errorLogPath, msg, 'utf8')
  389. console.error(msg.trim())
  390. process.exit(1)
  391. }
  392. const chunks = []
  393. function writeErrorLog(extra) {
  394. const header = \`[\${new Date().toISOString()}] 运行 \${path.basename(exePath)} 异常\n\`
  395. const body = chunks.length ? Buffer.concat(chunks).toString('utf8') : ''
  396. const tail = extra ? \`\n\${extra}\` : ''
  397. fs.writeFileSync(errorLogPath, header + body + tail, 'utf8')
  398. }
  399. const child = spawn(exePath, [], {
  400. cwd: dir,
  401. stdio: ['ignore', 'pipe', 'pipe'],
  402. windowsHide: false
  403. })
  404. child.stdout.on('data', (data) => {
  405. process.stdout.write(data)
  406. })
  407. child.stderr.on('data', (data) => {
  408. chunks.push(data)
  409. process.stderr.write(data)
  410. })
  411. child.on('error', (err) => {
  412. writeErrorLog(\`进程启动失败: \${err.message}\`)
  413. console.error(err)
  414. process.exit(1)
  415. })
  416. child.on('exit', (code, signal) => {
  417. if (code !== 0 && code != null) {
  418. writeErrorLog(\`退出码: \${code}\${signal ? \` 信号: \${signal}\` : ''}\`)
  419. }
  420. })
  421. `
  422. // 使用同目录下 node/node.exe,不依赖系统 PATH 的 node(别人电脑未装 Node 也能用)
  423. const runBat = '@echo off\r\ncd /d "%~dp0"\r\n"%~dp0node\\node.exe" run.js\r\npause\r\n'
  424. try {
  425. fs.writeFileSync(path.join(unpackedDir, 'run.js'), runJs, 'utf8')
  426. fs.writeFileSync(path.join(unpackedDir, 'run.bat'), runBat, 'utf8')
  427. } catch (_) {}
  428. // 若 package/x64 下存在 run.js、run.bat、config.js,直接拷贝到打包文件夹,覆盖上面生成的内容
  429. const x64RunJs = path.join(x64Dir, 'run.js')
  430. const x64RunBat = path.join(x64Dir, 'run.bat')
  431. const x64ConfigJs = path.join(x64Dir, 'config.js')
  432. if (fs.existsSync(x64RunJs)) {
  433. try {
  434. fs.copyFileSync(x64RunJs, path.join(unpackedDir, 'run.js'))
  435. console.log('[3/3] 已拷贝 package/x64/run.js → ' + X64_UNPACKED_DIR + '/run.js')
  436. } catch (e) { console.warn('[3/3] 拷贝 run.js 失败:', e.message) }
  437. }
  438. if (fs.existsSync(x64RunBat)) {
  439. try {
  440. fs.copyFileSync(x64RunBat, path.join(unpackedDir, 'run.bat'))
  441. console.log('[3/3] 已拷贝 package/x64/run.bat → ' + X64_UNPACKED_DIR + '/run.bat')
  442. } catch (e) { console.warn('[3/3] 拷贝 run.bat 失败:', e.message) }
  443. }
  444. if (fs.existsSync(x64ConfigJs) && fs.existsSync(path.join(unpackedDir, 'configs'))) {
  445. try {
  446. fs.copyFileSync(x64ConfigJs, path.join(unpackedDir, 'configs', 'config.js'))
  447. console.log('[3/3] 已拷贝 package/x64/config.js → ' + X64_UNPACKED_DIR + '/configs/config.js')
  448. } catch (e) { console.warn('[3/3] 拷贝 config.js 失败:', e.message) }
  449. }
  450. console.log('[完成] ' + X64_OUTPUT_DIR + '\\' + X64_UNPACKED_DIR)
  451. } catch (e) {
  452. console.error('\n[错误] 第 3 步 清理 ' + X64_OUTPUT_DIR + ' 失败。')
  453. console.error(e.message || e)
  454. process.exit(1)
  455. }
  456. }
  457. if (require.main === module) {
  458. main().catch((e) => {
  459. console.error(e)
  460. process.exit(1)
  461. })
  462. } else {
  463. module.exports = { cleanDist }
  464. }