@@ -30,14 +30,95 @@ async function downloadFile(url, dest) {
3030 * 解压 ZIP 文件
3131 * @param {string } zipPath - ZIP 文件路径
3232 * @param {string } extractPath - 解压目录
33+ * @param {string } name - 只解压 zip 包中包含的指定目录
34+ * @param {boolean } preserveStructure - 是否保留原目录结构
35+ *
36+ * 示例用法:
37+ * // 解压整个ZIP文件
38+ * await unzipFile('path/to/file.zip', 'extract/path');
39+ *
40+ * // 只解压名为 'src' 的目录
41+ * await unzipFile('path/to/file.zip', 'extract/path', 'xxx/base_page');
42+ *
43+ * // 解压特定子目录 (解压后会保留子目录结构)
44+ * await unzipFile('path/to/file.zip', 'extract/path', 'xxx/base_page', true);
3345 */
34- async function unzipFile ( zipPath , extractPath ) {
35- return new Promise ( ( resolve , reject ) => {
36- fs . createReadStream ( zipPath )
37- . pipe ( unzipper . Extract ( { path : extractPath } ) )
38- . on ( 'close' , resolve )
39- . on ( 'error' , reject ) ;
40- } ) ;
46+ async function unzipFile ( zipPath , extractPath , name = '' , preserveStructure = false ) {
47+ console . log ( '开始解压模板...' , { zipPath, name, extractPath, preserveStructure } ) ;
48+ if ( name && typeof name === 'string' ) {
49+ return new Promise ( ( resolve , reject ) => {
50+ fs . createReadStream ( zipPath )
51+ . pipe ( unzipper . Parse ( ) )
52+ . on ( 'entry' , function ( entry ) {
53+ let fileName = entry . path . replace ( / \\ / g, '/' ) ;
54+ const type = entry . type ;
55+ const normalizedName = name . replace ( / \\ / g, '/' ) ;
56+ const normalizedDir = normalizedName . endsWith ( '/' ) ? normalizedName : normalizedName + '/' ;
57+ const normalizedPath = fileName . startsWith ( '/' ) ? fileName . substring ( 1 ) : fileName ;
58+ const normalizedNamePath = normalizedName . startsWith ( '/' ) ? normalizedName . substring ( 1 ) : normalizedName ;
59+ // 判断是否需要解压
60+ const shouldExtract =
61+ normalizedPath . startsWith ( normalizedNamePath ) || normalizedPath . startsWith ( normalizedDir ) ;
62+
63+ if ( shouldExtract ) {
64+ // 处理路径:保留结构或扁平化
65+ let relativePath ;
66+ if ( normalizedPath . startsWith ( normalizedNamePath ) ) {
67+ if ( preserveStructure ) {
68+ // 保留结构:保留 name 之后的路径
69+ relativePath = normalizedPath . substring ( normalizedNamePath . length ) ;
70+ relativePath = relativePath . replace ( / ^ \/ + / , '' ) ;
71+ relativePath = path . join ( normalizedNamePath , relativePath ) ;
72+ } else {
73+ // 扁平化:只保留 name 之后的路径
74+ relativePath = normalizedPath . substring ( normalizedNamePath . length ) ;
75+ relativePath = relativePath . replace ( / ^ \/ + / , '' ) ;
76+ }
77+ } else {
78+ relativePath = fileName ;
79+ }
80+ const entryPath = path . join ( extractPath , relativePath ) ;
81+ if ( type === 'Directory' ) {
82+ try {
83+ if ( relativePath ) {
84+ fs . mkdirSync ( entryPath , { recursive : true } ) ;
85+ }
86+ entry . autodrain ( ) ;
87+ } catch ( err ) {
88+ console . error ( `创建目录失败 ${ entryPath } :` , err . message ) ;
89+ reject ( err ) ;
90+ }
91+ } else {
92+ const dir = path . dirname ( entryPath ) ;
93+ try {
94+ if ( dir !== extractPath ) {
95+ fs . mkdirSync ( dir , { recursive : true } ) ;
96+ }
97+ entry . pipe ( fs . createWriteStream ( entryPath ) ) ;
98+ } catch ( err ) {
99+ console . error ( `创建目录失败 ${ dir } :` , err . message ) ;
100+ reject ( err ) ;
101+ }
102+ }
103+ } else {
104+ entry . autodrain ( ) ;
105+ }
106+ } )
107+ . on ( 'close' , resolve )
108+ . on ( 'error' , ( err ) => {
109+ console . error ( '解压过程中发生错误:' , err . message ) ;
110+ reject ( err ) ;
111+ } ) ;
112+ } ) ;
113+ } else {
114+ // 解压整个 zip
115+ return new Promise ( ( resolve , reject ) => {
116+ fs . createReadStream ( zipPath )
117+ . pipe ( unzipper . Extract ( { path : extractPath } ) )
118+ . on ( 'close' , resolve )
119+ . on ( 'error' , reject ) ;
120+ } ) ;
121+ }
41122}
42123
43124/**
@@ -110,8 +191,9 @@ function promptUserInputs(questions) {
110191 * @param {string } zipUrl - ZIP 文件 URL
111192 * @param {string } downloadPath - 下载目录路径
112193 * @param {Array<string> } options - 要收集的替换项
194+ * @param {string } name - 模板名称
113195 */
114- async function downloadTpl ( zipUrl , downloadPath , options ) {
196+ async function downloadTpl ( zipUrl , downloadPath , options , name ) {
115197 let zipFilePath ;
116198
117199 try {
@@ -122,15 +204,17 @@ async function downloadTpl(zipUrl, downloadPath, options) {
122204 }
123205
124206 downloadPath = downloadPath || `./${ answers . PageCode } ` ;
125- const dest = path . resolve ( downloadPath ) ;
207+ const dest = path . resolve (
208+ downloadPath ?. includes ( answers . PageCode ) ? downloadPath : path . join ( downloadPath , answers . PageCode )
209+ ) ;
126210 zipFilePath = path . join ( dest , `template-${ Date . now ( ) } .zip` ) ;
127211
128212 await fsPromises . mkdir ( dest , { recursive : true } ) ; // 使用 Promise API 创建目录
129213
130214 await downloadFile ( zipUrl , zipFilePath ) ;
131215 console . log ( `模板已下载到 ${ zipFilePath } ` ) ;
132216
133- await unzipFile ( zipFilePath , dest ) ;
217+ await unzipFile ( zipFilePath , dest , name ) ;
134218 console . log ( '模板解压完成' ) ;
135219
136220 await traverseDirectory ( dest , answers ) ; // 递归遍历目录
@@ -144,7 +228,7 @@ async function downloadTpl(zipUrl, downloadPath, options) {
144228 }
145229}
146230
147- module . exports = { downloadTpl } ;
231+ module . exports = { downloadTpl, unzipFile } ;
148232
149233// 调用示例
150234// downloadTpl('http://cdn.biugle.cn/umi_page.zip', '', ['PageCode', 'Author']);
0 commit comments