1+ const fs = require ( 'fs' ) ;
2+ const { spawn } = require ( 'child_process' ) ;
3+ const path = require ( 'path' ) ;
4+
5+ // 确保 __dirname 有值,如果没有则使用当前工作目录
6+ const srcDir = __dirname || "" ;
7+ // 确保目标目录有值,空字符串会导致解压到当前目录
8+ const destDir = process . env . AILY_SDK_PATH || "" ;
9+ const _7zaPath = process . env . AILY_7ZA_PATH || "" ;
10+
11+ // 使用传统的回调式 API 并用 Promise 包装
12+ function readdir ( dir ) {
13+ return new Promise ( ( resolve , reject ) => {
14+ fs . readdir ( dir , ( err , files ) => {
15+ if ( err ) reject ( err ) ;
16+ else resolve ( files ) ;
17+ } ) ;
18+ } ) ;
19+ }
20+
21+ // 重试函数封装
22+ async function withRetry ( fn , maxRetries = 3 , retryDelay = 1000 ) {
23+ let lastError ;
24+
25+ for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
26+ try {
27+ return await fn ( ) ;
28+ } catch ( error ) {
29+ lastError = error ;
30+ console . log ( `操作失败 (尝试 ${ attempt } /${ maxRetries } ): ${ error . message } ` ) ;
31+
32+ if ( attempt < maxRetries ) {
33+ console . log ( `等待 ${ retryDelay / 1000 } 秒后重试...` ) ;
34+ await new Promise ( resolve => setTimeout ( resolve , retryDelay ) ) ;
35+ }
36+ }
37+ }
38+
39+ throw new Error ( `经过 ${ maxRetries } 次尝试后操作仍然失败: ${ lastError . message } ` ) ;
40+ }
41+
42+
43+ // 自定义递归删除函数
44+ function deleteFolderRecursive ( dirPath ) {
45+ if ( fs . existsSync ( dirPath ) ) {
46+ fs . readdirSync ( dirPath ) . forEach ( file => {
47+ const curPath = path . join ( dirPath , file ) ;
48+ if ( fs . lstatSync ( curPath ) . isDirectory ( ) ) {
49+ // 递归删除子目录
50+ deleteFolderRecursive ( curPath ) ;
51+ } else {
52+ // 删除文件,忽略错误
53+ try {
54+ fs . unlinkSync ( curPath ) ;
55+ } catch ( e ) {
56+ console . warn ( `无法删除文件: ${ curPath } ` , e ) ;
57+ }
58+ }
59+ } ) ;
60+ // 删除目录本身
61+ try {
62+ fs . rmdirSync ( dirPath ) ;
63+ } catch ( e ) {
64+ console . warn ( `无法删除目录: ${ dirPath } ` , e ) ;
65+ // 如果删除失败,尝试重命名
66+ const tempPath = path . join ( path . dirname ( dirPath ) , `_temp_${ Date . now ( ) } ` ) ;
67+ fs . renameSync ( dirPath , tempPath ) ;
68+ deleteFolderRecursive ( tempPath ) ;
69+ }
70+ }
71+ }
72+
73+
74+ async function handler ( file ) {
75+ const srcPath = path . join ( srcDir , file ) ;
76+ console . log ( `准备解压: ${ srcPath } ` ) ;
77+
78+ await unpack ( srcPath , destDir ) . catch ( err => {
79+ console . error ( `解压 ${ file } 失败:` , err ) ;
80+ throw err ; // 重新抛出错误以便重试
81+ } ) ;
82+
83+ console . log ( `已解压 ${ file } 到 ${ destDir } ` ) ;
84+
85+ // 重命名
86+ const newName = path . basename ( file , '.7z' ) ;
87+ const destPath = path . join ( destDir , newName ) ;
88+
89+ // 将newName中的@替换为_
90+ const newName2 = newName . replace ( '@' , '_' ) ;
91+ const newPath = path . join ( destDir , newName2 ) ;
92+
93+ // 判断是否存在目标目录,如果存在则删除
94+ if ( fs . existsSync ( newPath ) ) {
95+ try {
96+ deleteFolderRecursive ( newPath ) ;
97+ console . log ( `已删除目录: ${ newPath } ` ) ;
98+ } catch ( err ) {
99+ console . error ( `无法删除目录: ${ newPath } ` , err ) ;
100+ throw new Error ( `删除目录失败: ${ newPath } ` ) ;
101+ }
102+ }
103+
104+ fs . renameSync ( destPath , newPath ) ;
105+ console . log ( `已重命名 ${ destPath } 为 ${ newPath } ` ) ;
106+
107+ // Check if post-install script exists and run it
108+ const isWindows = process . platform === 'win32' ;
109+ const scriptName = isWindows ? 'post_install.bat' : 'post_install.sh' ;
110+ const scriptPath = path . join ( newPath , scriptName ) ;
111+
112+ if ( fs . existsSync ( scriptPath ) ) {
113+ console . log ( `Running post-install script: ${ scriptPath } ` ) ;
114+
115+ // Make sure the script is executable on Unix
116+ if ( ! isWindows ) {
117+ fs . chmodSync ( scriptPath , '755' ) ;
118+ }
119+
120+ // Prepare command and arguments
121+ const command = isWindows ? 'cmd' : 'sh' ;
122+ const args = isWindows ? [ '/c' , scriptPath ] : [ scriptPath ] ;
123+
124+ // Execute the script
125+ await new Promise ( ( resolve , reject ) => {
126+ const proc = spawn ( command , args , {
127+ cwd : newPath ,
128+ stdio : 'inherit' ,
129+ windowsHide : true
130+ } ) ;
131+
132+ proc . on ( 'exit' , ( code ) => {
133+ if ( code === 0 ) {
134+ console . log ( `Post-install script completed successfully.` ) ;
135+ } else {
136+ console . error ( `Post-install script failed with code ${ code } ` ) ;
137+ throw new Error ( `Script exited with code ${ code } ` ) ;
138+ }
139+ } ) ;
140+
141+ proc . on ( 'error' , ( err ) => {
142+ console . error ( 'Failed to run post-install script:' , err ) ;
143+ throw new Error ( `Failed to run post-install script: ${ err . message } ` ) ;
144+ } ) ;
145+ } ) ;
146+ } else {
147+ console . log ( `No post-install script found at ${ scriptPath } ` ) ;
148+ }
149+ }
150+
151+ // 使用 Promise 和 async/await 简化异步操作
152+ async function extractArchives ( ) {
153+ try {
154+ // 确保源目录存在
155+ if ( ! fs . existsSync ( srcDir ) ) {
156+ console . error ( `源目录不存在: ${ srcDir } ` ) ;
157+ return ;
158+ }
159+
160+ // 确保目标目录存在
161+ if ( ! destDir ) {
162+ console . error ( '未设置目标目录' ) ;
163+ return ;
164+ }
165+
166+ // 确保 7za.exe 存在
167+ if ( ! fs . existsSync ( _7zaPath ) ) {
168+ console . error ( `7za.exe 不存在: ${ _7zaPath } ` ) ;
169+ return ;
170+ }
171+
172+ if ( ! fs . existsSync ( destDir ) ) {
173+ console . log ( `目标目录不存在,创建: ${ destDir } ` ) ;
174+ fs . mkdirSync ( destDir , { recursive : true } ) ;
175+ }
176+
177+ // 读取目录并过滤出 .7z 文件
178+ const files = await readdir ( srcDir ) ;
179+ const archiveFiles = files . filter ( file => path . extname ( file ) . toLowerCase ( ) === '.7z' ) ;
180+
181+ console . log ( `找到 ${ archiveFiles . length } 个 .7z 文件` ) ;
182+
183+ // 处理每个压缩文件
184+ for ( const file of archiveFiles ) {
185+ if ( ! file ) {
186+ console . error ( '文件名为空,跳过' ) ;
187+ continue ;
188+ }
189+
190+ try {
191+ await withRetry ( async ( ) => {
192+ await handler ( file ) ;
193+ } , 3 , 2000 ) ; // 重试3次,每次间隔2秒
194+ } catch ( error ) {
195+ console . error ( `解压 ${ file } 失败:` , error ) ;
196+ }
197+ }
198+ } catch ( err ) {
199+ console . error ( '无法读取目录:' , err ) ;
200+ }
201+ }
202+
203+ // 使用 Promise 封装解压函数
204+ function unpack ( archivePath , destination ) {
205+ return new Promise ( ( resolve , reject ) => {
206+ if ( ! archivePath ) {
207+ return reject ( new Error ( '压缩文件路径不能为空' ) ) ;
208+ }
209+ if ( ! destination ) {
210+ return reject ( new Error ( '目标目录不能为空' ) ) ;
211+ }
212+
213+ const args = [ 'x' , archivePath , '-y' , '-o' + destination ] ;
214+ console . log ( `执行命令: ${ _7zaPath } ${ args . join ( ' ' ) } ` ) ;
215+
216+ const proc = spawn ( _7zaPath , args , { windowsHide : true } ) ;
217+
218+ let output = '' ;
219+
220+ proc . stdout . on ( 'data' , function ( chunk ) {
221+ output += chunk . toString ( ) ;
222+ } ) ;
223+ proc . stderr . on ( 'data' , function ( chunk ) {
224+ output += chunk . toString ( ) ;
225+ } ) ;
226+
227+ proc . on ( 'error' , function ( err ) {
228+ console . error ( '7-zip 错误:' , err ) ;
229+ reject ( err ) ;
230+ } ) ;
231+
232+ proc . on ( 'exit' , function ( code ) {
233+ if ( code === 0 ) {
234+ resolve ( ) ;
235+ } else {
236+ const error = new Error ( `7-zip 退出码 ${ code } \n${ output } ` ) ;
237+ reject ( error ) ;
238+ }
239+ } ) ;
240+ } ) ;
241+ }
242+
243+ // 执行主函数
244+ extractArchives ( ) . catch ( function ( err ) {
245+ console . error ( '执行失败:' , err ) ;
246+ } ) ;
0 commit comments