11const fs = require ( 'fs' ) ;
22const { spawn } = require ( 'child_process' ) ;
33const path = require ( 'path' ) ;
4+ const https = require ( 'https' ) ;
5+
6+ // test
47
58// 确保 __dirname 有值,如果没有则使用当前工作目录
69const srcDir = __dirname || "" ;
710// 确保目标目录有值,空字符串会导致解压到当前目录
811const destDir = process . env . AILY_TOOLS_PATH || "" ;
912const _7zaPath = process . env . AILY_7ZA_PATH || "" ;
13+ const zipDownloadBaseUrl = process . env . AILY_ZIP_URL + '/tools' ;
1014
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- }
2015
2116// 重试函数封装
2217async function withRetry ( fn , maxRetries = 3 , retryDelay = 1000 ) {
@@ -39,6 +34,98 @@ async function withRetry(fn, maxRetries = 3, retryDelay = 1000) {
3934 throw new Error ( `经过 ${ maxRetries } 次尝试后操作仍然失败: ${ lastError . message } ` ) ;
4035}
4136
37+
38+ function getZipFileName ( ) {
39+ // 读取package.json文件,获取name和version
40+ const prefix = "@aily-project/tool-" ;
41+ const packageJson = require ( './package.json' ) ;
42+ const packageName = packageJson . name . replace ( prefix , "" ) ;
43+ const packageVersion = packageJson . version ;
44+ return `${ packageName } @${ packageVersion } .7z` ;
45+ }
46+
47+
48+ function getZipFile ( ) {
49+ const zipFileName = getZipFileName ( ) ;
50+ const downloadUrl = `${ zipDownloadBaseUrl } /${ zipFileName } ` ;
51+
52+ return new Promise ( ( resolve , reject ) => {
53+ console . log ( `正在下载: ${ downloadUrl } ` ) ;
54+ const filePath = path . join ( __dirname , zipFileName ) ;
55+
56+ if ( fs . existsSync ( filePath ) ) {
57+ console . log ( `文件已存在: ${ zipFileName } ` ) ;
58+ resolve ( zipFileName ) ;
59+ return ;
60+ }
61+
62+ const fileStream = fs . createWriteStream ( filePath ) ;
63+
64+ https . get ( downloadUrl , ( response ) => {
65+ if ( response . statusCode !== 200 ) {
66+ fileStream . close ( ) ;
67+ fs . unlink ( filePath , ( ) => { } ) ;
68+ reject ( new Error ( `下载失败: 状态码 ${ response . statusCode } ` ) ) ;
69+ return ;
70+ }
71+
72+ // 获取文件总大小
73+ const totalSize = parseInt ( response . headers [ 'content-length' ] || 0 , 10 ) ;
74+ let downloadedSize = 0 ;
75+ let lastPercentage = - 1 ;
76+
77+ // 设置下载进度显示
78+ response . on ( 'data' , ( chunk ) => {
79+ downloadedSize += chunk . length ;
80+
81+ // 计算下载百分比
82+ if ( totalSize > 0 ) {
83+ const percentage = Math . floor ( ( downloadedSize / totalSize ) * 100 ) ;
84+
85+ // 每增加1%才更新进度,避免过多输出
86+ if ( percentage > lastPercentage ) {
87+ lastPercentage = percentage ;
88+ const downloadSizeMB = ( downloadedSize / ( 1024 * 1024 ) ) . toFixed ( 2 ) ;
89+ const totalSizeMB = ( totalSize / ( 1024 * 1024 ) ) . toFixed ( 2 ) ;
90+ process . stdout . write ( `\r下载进度: ${ percentage } % (${ downloadSizeMB } MB / ${ totalSizeMB } MB)` ) ;
91+ }
92+ }
93+ } ) ;
94+
95+ response . pipe ( fileStream ) ;
96+
97+ fileStream . on ( 'finish' , ( ) => {
98+ fileStream . close ( ) ;
99+ // 输出换行,确保后续日志正常显示
100+ if ( totalSize > 0 ) {
101+ console . log ( '' ) ;
102+ }
103+ console . log ( `成功下载 ${ zipFileName } ` ) ;
104+ resolve ( zipFileName ) ;
105+ } ) ;
106+
107+ fileStream . on ( 'error' , ( err ) => {
108+ fs . unlink ( filePath , ( ) => { } ) ;
109+ reject ( err ) ;
110+ } ) ;
111+ } ) . on ( 'error' , ( err ) => {
112+ fs . unlink ( filePath , ( ) => { } ) ;
113+ reject ( err ) ;
114+ } ) ;
115+ } ) ;
116+ }
117+
118+
119+ // 使用传统的回调式 API 并用 Promise 包装
120+ function readdir ( dir ) {
121+ return new Promise ( ( resolve , reject ) => {
122+ fs . readdir ( dir , ( err , files ) => {
123+ if ( err ) reject ( err ) ;
124+ else resolve ( files ) ;
125+ } ) ;
126+ } ) ;
127+ }
128+
42129// 使用 Promise 和 async/await 简化异步操作
43130async function extractArchives ( ) {
44131 try {
@@ -65,44 +152,175 @@ async function extractArchives() {
65152 fs . mkdirSync ( destDir , { recursive : true } ) ;
66153 }
67154
68- // 读取目录并过滤出 .7z 文件
69- const files = await readdir ( srcDir ) ;
70- const archiveFiles = files . filter ( file => path . extname ( file ) . toLowerCase ( ) === '.7z' ) ;
155+ // 确保 ZIP URL 已设置
156+ if ( ! process . env . AILY_ZIP_URL ) {
157+ throw new Error ( '未设置下载基础 URL (AILY_ZIP_URL 环境变量未设置)' ) ;
158+ }
159+
160+ if ( ! fs . existsSync ( destDir ) ) {
161+ console . log ( `目标目录不存在,创建: ${ destDir } ` ) ;
162+ try {
163+ fs . mkdirSync ( destDir , { recursive : true } ) ;
164+ } catch ( mkdirErr ) {
165+ throw new Error ( `无法创建目标目录: ${ destDir } , 错误: ${ mkdirErr . message } ` ) ;
166+ }
167+ }
71168
72- console . log ( `找到 ${ archiveFiles . length } 个 .7z 文件` ) ;
169+ // 下载zip文件
170+ let fileName ;
171+ try {
172+ fileName = await withRetry ( getZipFile , 3 , 2000 ) ;
173+ console . log ( `已下载文件: ${ fileName } ` ) ;
174+ } catch ( downloadErr ) {
175+ throw new Error ( `无法下载zip文件: ${ downloadErr . message } ` ) ;
176+ }
73177
74- // 处理每个压缩文件
75- for ( const file of archiveFiles ) {
76- if ( ! file ) {
77- console . error ( '文件名为空,跳过' ) ;
78- continue ;
178+ // 检查下载的文件是否存在和大小是否正常
179+ const zipFilePath = path . join ( __dirname , fileName ) ;
180+ try {
181+ const stats = fs . statSync ( zipFilePath ) ;
182+ // if (stats.size < 1048576) { // 1MB = 1024 * 1024 bytes
183+ // throw new Error(`下载的文件异常小 (${stats.size} 字节),可能下载不完整或被截断`);
184+ // }
185+ console . log ( `文件大小: ${ ( stats . size / ( 1024 * 1024 ) ) . toFixed ( 2 ) } MB` ) ;
186+ } catch ( statErr ) {
187+ if ( statErr . code === 'ENOENT' ) {
188+ throw new Error ( `下载的文件不存在: ${ zipFilePath } ` ) ;
189+ } else {
190+ throw new Error ( `检查文件失败: ${ statErr . message } ` ) ;
79191 }
192+ }
80193
81- const srcPath = path . join ( srcDir , file ) ;
82- console . log ( `准备解压: ${ srcPath } ` ) ;
194+ // 在解压前检查并清理目标目录中的同名文件夹
195+ try {
196+ await checkAndCleanExistingFolder ( fileName , destDir ) ;
197+ } catch ( cleanErr ) {
198+ throw new Error ( `清理旧文件夹失败: ${ cleanErr . message } ` ) ;
199+ }
200+
201+ // 解压zip文件
202+ try {
203+ await withRetry ( async ( ) => {
204+ await unpack ( zipFilePath , destDir ) ;
205+ } , 3 , 2000 ) ; // 最多重试3次,每次间隔2秒
206+ console . log ( `已解压 ${ fileName } 到 ${ destDir } ` ) ;
83207
208+ // 解压成功后重命名文件夹(将@替换为_)
84209 try {
85- await withRetry ( async ( ) => {
86- await unpack ( srcPath , destDir ) ;
87- } , 3 , 2000 ) ; // 最多重试3次,每次间隔2秒
88- console . log ( `已解压 ${ file } 到 ${ destDir } ` ) ;
89-
90- // // 重命名
91- // const newName = path.basename(file, '.7z');
92- // const destPath = path.join(destDir, newName);
93-
94- // // 将newName中的@替换为_
95- // const newName2 = newName.replace('@', '_');
96- // const newPath = path.join(destDir, newName2);
97- // fs.renameSync(destPath, newPath);
98- // console.log(`已重命名 ${destPath} 为 ${newPath}`);
99-
100- } catch ( error ) {
101- console . error ( `解压 ${ file } 失败:` , error ) ;
210+ await renameExtractedFolder ( fileName , destDir ) ;
211+ } catch ( renameErr ) {
212+ throw new Error ( `重命名文件夹失败: ${ renameErr . message } ` ) ;
102213 }
214+
215+ // 解压成功后可以删除压缩文件
216+ // fs.unlinkSync(zipFilePath);
217+ // console.log(`已删除临时文件: ${fileName}`);
218+ } catch ( unpackErr ) {
219+ throw new Error ( `解压失败: ${ unpackErr . message } ` ) ;
103220 }
104221 } catch ( err ) {
105222 console . error ( '无法读取目录:' , err ) ;
223+ process . exit ( 1 ) ;
224+ }
225+ }
226+
227+ // 检查并清理目标目录中的同名文件夹
228+ async function checkAndCleanExistingFolder ( zipFileName , targetDir ) {
229+ // 从压缩文件名推断解压后的文件夹名
230+ // 例如:[email protected] -> avr_1.0.0 (将@替换为_) 231+ // const folderName = zipFileName.replace(/\.(7z|zip)$/i, '').replace('@', '_');
232+ const folderName = zipFileName . replace ( / \. ( 7 z | z i p ) $ / i, '' ) ;
233+ const targetFolderPath = path . join ( targetDir , folderName ) ;
234+
235+ if ( fs . existsSync ( targetFolderPath ) ) {
236+ console . log ( `检测到已存在的文件夹: ${ targetFolderPath } ` ) ;
237+ console . log ( `正在删除旧文件夹...` ) ;
238+
239+ try {
240+ // 递归删除整个文件夹
241+ await withRetry ( async ( ) => {
242+ if ( typeof fs . rmSync === 'function' ) {
243+ fs . rmSync ( targetFolderPath , { recursive : true , force : true } ) ;
244+ } else {
245+ // 使用备用方法删除文件夹(适用于旧版本 Node.js)
246+ await rimraf ( targetFolderPath ) ;
247+ }
248+ } , 3 , 1000 ) ;
249+ console . log ( `已成功删除旧文件夹: ${ folderName } ` ) ;
250+ } catch ( rmErr ) {
251+ throw new Error ( `删除文件夹失败: ${ rmErr . message } ` ) ;
252+ }
253+ }
254+ }
255+
256+ // 递归删除目录的备用函数(适用于旧版本 Node.js)
257+ async function rimraf ( dir ) {
258+ return new Promise ( ( resolve , reject ) => {
259+ if ( ! fs . existsSync ( dir ) ) {
260+ resolve ( ) ;
261+ return ;
262+ }
263+
264+ try {
265+ const stats = fs . statSync ( dir ) ;
266+ if ( stats . isDirectory ( ) ) {
267+ const files = fs . readdirSync ( dir ) ;
268+ const promises = files . map ( file => {
269+ const filePath = path . join ( dir , file ) ;
270+ return rimraf ( filePath ) ;
271+ } ) ;
272+
273+ Promise . all ( promises )
274+ . then ( ( ) => {
275+ fs . rmdir ( dir , resolve ) ;
276+ } )
277+ . catch ( reject ) ;
278+ } else {
279+ fs . unlink ( dir , resolve ) ;
280+ }
281+ } catch ( err ) {
282+ reject ( err ) ;
283+ }
284+ } ) ;
285+ }
286+
287+ // 重命名解压后的文件夹(将@替换为_)
288+ async function renameExtractedFolder ( zipFileName , targetDir ) {
289+ // 从压缩文件名获取解压后的原始文件夹名
290+ 291+ const originalFolderName = zipFileName . replace ( / \. ( 7 z | z i p ) $ / i, '' ) ;
292+ const newFolderName = originalFolderName . replace ( '@' , '_' ) ;
293+
294+ // 如果文件夹名没有变化,则不需要重命名
295+ if ( originalFolderName === newFolderName ) {
296+ console . log ( `文件夹名无需重命名: ${ originalFolderName } ` ) ;
297+ return ;
298+ }
299+
300+ const originalFolderPath = path . join ( targetDir , originalFolderName ) ;
301+ const newFolderPath = path . join ( targetDir , newFolderName ) ;
302+
303+ if ( fs . existsSync ( originalFolderPath ) ) {
304+ try {
305+ // 使用重试机制进行重命名
306+ await withRetry ( async ( ) => {
307+ return new Promise ( ( resolve , reject ) => {
308+ fs . rename ( originalFolderPath , newFolderPath , ( err ) => {
309+ if ( err ) {
310+ reject ( new Error ( `重命名文件夹失败: ${ err . message } ` ) ) ;
311+ } else {
312+ resolve ( ) ;
313+ }
314+ } ) ;
315+ } ) ;
316+ } , 3 , 1000 ) ;
317+
318+ console . log ( `已重命名文件夹: ${ originalFolderName } -> ${ newFolderName } ` ) ;
319+ } catch ( renameErr ) {
320+ throw new Error ( `无法重命名文件夹 ${ originalFolderName } 为 ${ newFolderName } : ${ renameErr . message } ` ) ;
321+ }
322+ } else {
323+ console . warn ( `未找到需要重命名的文件夹: ${ originalFolderPath } ` ) ;
106324 }
107325}
108326
0 commit comments