1+ #!/usr/bin/env node
2+
3+ import { execSync } from 'child_process' ;
4+ import { existsSync , rmSync , cpSync , mkdirSync , readdirSync , statSync } from 'fs' ;
5+ import { join , dirname } from 'path' ;
6+ import { fileURLToPath } from 'url' ;
7+
8+ const __filename = fileURLToPath ( import . meta. url ) ;
9+ const __dirname = dirname ( __filename ) ;
10+
11+ // 定义路径
12+ const rootDir = __dirname ;
13+ const tempServerDir = join ( rootDir , 'temp-server' ) ;
14+ const distBinaryDir = join ( rootDir , 'dist-binary' ) ;
15+ const distDir = join ( rootDir , 'dist' ) ;
16+
17+ // 配置选项
18+ const config = {
19+ // pkg优化选项
20+ pkg : {
21+ compress : 'Brotli' , // 使用Brotli压缩 (GZip也可选)
22+ publicPackages : '*' , // 将所有包标记为public以减少体积
23+ noBytecode : false , // 保持字节码生成以避免兼容性问题
24+ public : true , // 使用public选项加速打包
25+ options : 'max-old-space-size=512' // 限制内存使用
26+ } ,
27+ // UPX压缩选项
28+ upx : {
29+ enabled : true , // 是否启用UPX压缩
30+ level : 6 , // 压缩级别 (1-9, 降低到6以提高兼容性)
31+ keepBackup : true , // 保留备份文件以便回滚
32+ ultraBrute : false , // 是否使用ultra-brute模式
33+ testAfterCompress : true , // 压缩后测试二进制文件
34+ fallbackOnError : true // 如果压缩后无法运行,则回滚到原文件
35+ }
36+ } ;
37+
38+ console . log ( '🚀 开始优化打包流程...\n' ) ;
39+
40+ // 检查UPX是否可用
41+ function checkUpxAvailable ( ) {
42+ try {
43+ execSync ( 'upx --version' , { stdio : 'pipe' } ) ;
44+ return true ;
45+ } catch ( error ) {
46+ console . log ( '⚠️ UPX未安装或不可用,将跳过UPX压缩步骤' ) ;
47+ console . log ( ' 可以从 https://upx.github.io/ 下载UPX' ) ;
48+ return false ;
49+ }
50+ }
51+
52+ // 格式化文件大小
53+ function formatFileSize ( bytes ) {
54+ return ( bytes / 1024 / 1024 ) . toFixed ( 2 ) + ' MB' ;
55+ }
56+
57+ // 获取文件大小
58+ function getFileSize ( filePath ) {
59+ if ( ! existsSync ( filePath ) ) return 0 ;
60+ return statSync ( filePath ) . size ;
61+ }
62+
63+ try {
64+ // 步骤 1: 前端构建
65+ console . log ( '📦 步骤 1: 构建前端项目...' ) ;
66+ execSync ( 'pnpm build:apps' , {
67+ stdio : 'inherit' ,
68+ cwd : rootDir
69+ } ) ;
70+ console . log ( '✅ 前端构建完成\n' ) ;
71+
72+ // 步骤 2: 清理 temp-server 中的旧文件
73+ console . log ( '🧹 步骤 2: 清理 temp-server 中的旧文件...' ) ;
74+
75+ const tempServerAppsDir = join ( tempServerDir , 'apps' ) ;
76+ if ( existsSync ( tempServerAppsDir ) ) {
77+ rmSync ( tempServerAppsDir , { recursive : true , force : true } ) ;
78+ console . log ( '✅ 已删除旧的 apps 目录' ) ;
79+ }
80+ console . log ( '' ) ;
81+
82+ // 步骤 3: 复制构建文件到 temp-server/apps/drplayer
83+ console . log ( '📁 步骤 3: 复制构建文件到 temp-server...' ) ;
84+ if ( ! existsSync ( distDir ) ) {
85+ throw new Error ( 'dist 目录不存在,请确保前端构建成功' ) ;
86+ }
87+
88+ if ( ! existsSync ( tempServerDir ) ) {
89+ mkdirSync ( tempServerDir , { recursive : true } ) ;
90+ }
91+
92+ const tempServerDrplayerDir = join ( tempServerAppsDir , 'drplayer' ) ;
93+ if ( ! existsSync ( tempServerAppsDir ) ) {
94+ mkdirSync ( tempServerAppsDir , { recursive : true } ) ;
95+ }
96+
97+ cpSync ( distDir , tempServerDrplayerDir , { recursive : true } ) ;
98+ console . log ( '✅ 已将 dist 内容复制到 apps/drplayer' ) ;
99+ console . log ( '' ) ;
100+
101+ // 步骤 4: 在 temp-server 目录中打包二进制文件(使用优化选项)
102+ console . log ( '⚙️ 步骤 4: 使用优化选项打包二进制文件...' ) ;
103+
104+ const tempServerNodeModules = join ( tempServerDir , 'node_modules' ) ;
105+ if ( ! existsSync ( tempServerNodeModules ) ) {
106+ console . log ( '📦 安装 temp-server 依赖...' ) ;
107+ execSync ( 'pnpm install' , {
108+ stdio : 'inherit' ,
109+ cwd : tempServerDir
110+ } ) ;
111+ }
112+
113+ // 构建优化的pkg命令
114+ let pkgCommand = 'pkg . --target node18-win-x64 --output dist-binary/drplayer-server-win.exe' ;
115+
116+ // 添加压缩选项
117+ if ( config . pkg . compress ) {
118+ pkgCommand += ` --compress ${ config . pkg . compress } ` ;
119+ }
120+
121+ // 添加public packages选项
122+ if ( config . pkg . publicPackages ) {
123+ pkgCommand += ` --public-packages "${ config . pkg . publicPackages } "` ;
124+ }
125+
126+ // 添加public选项
127+ if ( config . pkg . public ) {
128+ pkgCommand += ' --public' ;
129+ }
130+
131+ // 添加no-bytecode选项(仅在明确启用时)
132+ if ( config . pkg . noBytecode ) {
133+ pkgCommand += ' --no-bytecode' ;
134+ }
135+
136+ // 添加Node.js选项
137+ if ( config . pkg . options ) {
138+ pkgCommand += ` --options "${ config . pkg . options } "` ;
139+ }
140+
141+ console . log ( `🔧 执行命令: ${ pkgCommand } ` ) ;
142+ execSync ( pkgCommand , {
143+ stdio : 'inherit' ,
144+ cwd : tempServerDir
145+ } ) ;
146+ console . log ( '✅ 优化二进制文件打包完成\n' ) ;
147+
148+ // 步骤 5: 移动二进制文件到 dist-binary 目录
149+ console . log ( '📦 步骤 5: 移动二进制文件到 dist-binary...' ) ;
150+
151+ if ( ! existsSync ( distBinaryDir ) ) {
152+ mkdirSync ( distBinaryDir , { recursive : true } ) ;
153+ }
154+
155+ const tempDistBinaryDir = join ( tempServerDir , 'dist-binary' ) ;
156+ if ( existsSync ( tempDistBinaryDir ) ) {
157+ const files = readdirSync ( tempDistBinaryDir ) ;
158+ for ( const file of files ) {
159+ const srcPath = join ( tempDistBinaryDir , file ) ;
160+ const destPath = join ( distBinaryDir , file ) ;
161+
162+ if ( existsSync ( destPath ) ) {
163+ rmSync ( destPath , { force : true } ) ;
164+ }
165+
166+ cpSync ( srcPath , destPath , { recursive : true } ) ;
167+ console . log ( `✅ 已移动: ${ file } ` ) ;
168+ }
169+
170+ try {
171+ rmSync ( tempDistBinaryDir , { recursive : true , force : true } ) ;
172+ } catch ( error ) {
173+ console . log ( `⚠️ 无法删除临时目录: ${ error . message } ` ) ;
174+ }
175+ }
176+
177+ // 步骤 6: UPX压缩(如果启用且可用)
178+ if ( config . upx . enabled && checkUpxAvailable ( ) ) {
179+ console . log ( '\n🗜️ 步骤 6: 使用UPX进行额外压缩...' ) ;
180+
181+ const files = readdirSync ( distBinaryDir ) . filter ( file => file . endsWith ( '.exe' ) ) ;
182+
183+ for ( const file of files ) {
184+ const filePath = join ( distBinaryDir , file ) ;
185+ const originalSize = getFileSize ( filePath ) ;
186+
187+ // 创建备份(如果启用)
188+ if ( config . upx . keepBackup ) {
189+ const backupPath = join ( distBinaryDir , file + '.backup' ) ;
190+ cpSync ( filePath , backupPath ) ;
191+ console . log ( `📋 已创建备份: ${ file } .backup` ) ;
192+ }
193+
194+ try {
195+ let upxCommand = `upx -${ config . upx . level } ` ;
196+
197+ // 添加额外的UPX选项以获得更好的压缩率
198+ if ( config . upx . ultraBrute ) {
199+ upxCommand += ' --ultra-brute' ; // 尝试所有压缩方法(很慢但压缩率最高)
200+ }
201+
202+ // 添加兼容性选项
203+ upxCommand += ' --force' ; // 强制压缩
204+ upxCommand += ` "${ filePath } "` ;
205+
206+ console . log ( `🔧 压缩 ${ file } ...` ) ;
207+ execSync ( upxCommand , { stdio : 'pipe' } ) ;
208+
209+ const compressedSize = getFileSize ( filePath ) ;
210+ const compressionRatio = ( ( originalSize - compressedSize ) / originalSize * 100 ) . toFixed ( 1 ) ;
211+
212+ console . log ( `✅ ${ file } 压缩完成:` ) ;
213+ console . log ( ` 原始大小: ${ formatFileSize ( originalSize ) } ` ) ;
214+ console . log ( ` 压缩后: ${ formatFileSize ( compressedSize ) } ` ) ;
215+ console . log ( ` 压缩率: ${ compressionRatio } %` ) ;
216+
217+ // 测试压缩后的二进制文件
218+ if ( config . upx . testAfterCompress ) {
219+ console . log ( `🧪 测试压缩后的二进制文件...` ) ;
220+ try {
221+ // 简单的启动测试 - 运行 --help 命令
222+ execSync ( `"${ filePath } " --help` , { timeout : 10000 , stdio : 'pipe' } ) ;
223+ console . log ( `✅ 压缩后的二进制文件测试通过` ) ;
224+ } catch ( testError ) {
225+ console . log ( `⚠️ 压缩后的二进制文件测试失败: ${ testError . message } ` ) ;
226+
227+ if ( config . upx . fallbackOnError ) {
228+ console . log ( `🔄 回滚到原始文件...` ) ;
229+ const backupPath = join ( distBinaryDir , file + '.backup' ) ;
230+ if ( existsSync ( backupPath ) ) {
231+ cpSync ( backupPath , filePath ) ;
232+ console . log ( `✅ 已回滚到原始未压缩文件` ) ;
233+ console . log ( `📝 建议: pkg生成的二进制文件可能与UPX不兼容,使用原始文件` ) ;
234+ }
235+ } else {
236+ throw testError ;
237+ }
238+ }
239+ }
240+
241+ } catch ( error ) {
242+ console . log ( `❌ UPX压缩 ${ file } 失败: ${ error . message } ` ) ;
243+
244+ // 如果压缩失败且有备份,恢复原文件
245+ if ( config . upx . keepBackup ) {
246+ const backupPath = join ( distBinaryDir , file + '.backup' ) ;
247+ if ( existsSync ( backupPath ) ) {
248+ cpSync ( backupPath , filePath ) ;
249+ console . log ( `🔄 已从备份恢复 ${ file } ` ) ;
250+ }
251+ }
252+ }
253+ }
254+ } else if ( config . upx . enabled ) {
255+ console . log ( '\n⚠️ 跳过UPX压缩步骤(UPX不可用)' ) ;
256+ }
257+
258+ // 步骤 7: 清理临时文件
259+ console . log ( '\n🧹 步骤 7: 清理临时文件...' ) ;
260+
261+ const dirsToClean = [
262+ join ( tempServerDir , 'apps' ) ,
263+ join ( tempServerDir , 'dist-binary' )
264+ ] ;
265+
266+ for ( const dirPath of dirsToClean ) {
267+ if ( existsSync ( dirPath ) ) {
268+ try {
269+ rmSync ( dirPath , { recursive : true , force : true } ) ;
270+ console . log ( `✅ 已清理: ${ dirPath . replace ( tempServerDir , 'temp-server' ) } ` ) ;
271+ } catch ( error ) {
272+ console . log ( `⚠️ 无法清理目录: ${ error . message } ` ) ;
273+ }
274+ }
275+ }
276+
277+ // 清理UPX备份文件(可选)
278+ if ( ! config . upx . keepBackup ) {
279+ const backupFiles = readdirSync ( distBinaryDir ) . filter ( file => file . endsWith ( '.backup' ) ) ;
280+ if ( backupFiles . length > 0 ) {
281+ console . log ( '\n🗑️ 清理备份文件...' ) ;
282+ for ( const backupFile of backupFiles ) {
283+ const backupPath = join ( distBinaryDir , backupFile ) ;
284+ rmSync ( backupPath , { force : true } ) ;
285+ console . log ( `✅ 已删除备份: ${ backupFile } ` ) ;
286+ }
287+ }
288+ }
289+
290+ console . log ( '\n🎉 优化打包流程完成!' ) ;
291+ console . log ( `📁 二进制文件位置: ${ distBinaryDir } ` ) ;
292+
293+ // 显示最终结果
294+ if ( existsSync ( distBinaryDir ) ) {
295+ const files = readdirSync ( distBinaryDir ) ;
296+ if ( files . length > 0 ) {
297+ console . log ( '\n📋 最终生成的文件:' ) ;
298+ let totalSize = 0 ;
299+ files . forEach ( file => {
300+ const filePath = join ( distBinaryDir , file ) ;
301+ const stats = statSync ( filePath ) ;
302+ const size = stats . size ;
303+ totalSize += size ;
304+ console . log ( ` - ${ file } (${ formatFileSize ( size ) } )` ) ;
305+ } ) ;
306+ console . log ( `\n📊 总大小: ${ formatFileSize ( totalSize ) } ` ) ;
307+ }
308+ }
309+
310+ } catch ( error ) {
311+ console . error ( '\n❌ 优化打包过程中出现错误:' ) ;
312+ console . error ( error . message ) ;
313+ process . exit ( 1 ) ;
314+ }
0 commit comments