@@ -47,8 +47,33 @@ function normalizeDomain(domain) {
4747 return domain . replace ( / ^ h t t p s ? : \/ \/ / , '' ) . replace ( / \/ + $ / , '' ) ;
4848}
4949
50+ const UPLOAD_CONCURRENCY = 20 ;
51+
5052/**
51- * Recursively upload a directory to OSS
53+ * Collect all files recursively from a directory
54+ * @returns {Array<{filePath: string, ossKey: string}> }
55+ */
56+ function collectFiles ( localDir , ossPrefix = '' ) {
57+ const result = [ ] ;
58+ if ( ! fs . existsSync ( localDir ) ) return result ;
59+
60+ const entries = fs . readdirSync ( localDir ) ;
61+ for ( const entry of entries ) {
62+ const filePath = path . resolve ( localDir , entry ) ;
63+ const fileStats = fs . statSync ( filePath ) ;
64+ const key = ossPrefix ? `${ ossPrefix } /${ entry } ` : entry ;
65+
66+ if ( fileStats . isDirectory ( ) ) {
67+ result . push ( ...collectFiles ( filePath , key ) ) ;
68+ } else {
69+ result . push ( { filePath, ossKey : key } ) ;
70+ }
71+ }
72+ return result ;
73+ }
74+
75+ /**
76+ * Upload a directory to OSS with concurrent uploads
5277 */
5378async function uploadDirectoryToOSS ( client , localDir , ossPrefix = '' ) {
5479 if ( ! fs . existsSync ( localDir ) ) {
@@ -61,26 +86,26 @@ async function uploadDirectoryToOSS(client, localDir, ossPrefix = '') {
6186 throw new Error ( `${ localDir } is not a directory` ) ;
6287 }
6388
64- const files = fs . readdirSync ( localDir ) ;
89+ const files = collectFiles ( localDir , ossPrefix ) ;
6590 let uploadedCount = 0 ;
6691
67- for ( const file of files ) {
68- const filePath = path . resolve ( localDir , file ) ;
69- const fileStats = fs . statSync ( filePath ) ;
70-
71- if ( fileStats . isDirectory ( ) ) {
72- const subOssPrefix = ossPrefix ? ` ${ ossPrefix } / ${ file } ` : file ;
73- const subCount = await uploadDirectoryToOSS ( client , filePath , subOssPrefix ) ;
74- uploadedCount += subCount ;
75- } else {
76- const ossKey = ossPrefix ? ` ${ ossPrefix } / ${ file } ` : file ;
77- try {
78- await client . put ( ossKey , filePath ) ;
79- uploadedCount ++ ;
80- } catch ( error ) {
81- console . error ( `[error] Failed to upload ${ ossKey } : ${ error . message } ` ) ;
82- throw error ;
83- }
92+ // Upload in batches with concurrency
93+ for ( let i = 0 ; i < files . length ; i += UPLOAD_CONCURRENCY ) {
94+ const batch = files . slice ( i , i + UPLOAD_CONCURRENCY ) ;
95+ const results = await Promise . all (
96+ batch . map ( async ( { filePath , ossKey } ) => {
97+ try {
98+ await client . put ( ossKey , filePath ) ;
99+ return true ;
100+ } catch ( error ) {
101+ console . error ( `[error] Failed to upload ${ ossKey } : ${ error . message } ` ) ;
102+ throw error ;
103+ }
104+ } ) ,
105+ ) ;
106+ uploadedCount += results . length ;
107+ if ( files . length > UPLOAD_CONCURRENCY ) {
108+ console . log ( `[info] Uploaded ${ uploadedCount } / ${ files . length } files...` ) ;
84109 }
85110 }
86111
@@ -254,6 +279,8 @@ function parseArgs() {
254279 } else if ( args [ i ] === '--timestamp' && args [ i + 1 ] ) {
255280 options . timestamp = args [ i + 1 ] ;
256281 i ++ ;
282+ } else if ( args [ i ] === '--skip-upload' ) {
283+ options . skipUpload = true ;
257284 }
258285 }
259286 return options ;
@@ -269,22 +296,23 @@ async function main() {
269296 process . exit ( 1 ) ;
270297 }
271298
272- // 2. Validate --dir
273- if ( ! options . dir ) {
299+ // 2. Validate --dir (not required when --skip-upload)
300+ if ( ! options . skipUpload && ! options . dir ) {
274301 console . error ( '[error] Missing required option: --dir <dir>' ) ;
275302 console . error ( 'Usage: node scripts/upload-docs.js --dir ./dist' ) ;
303+ console . error ( ' node scripts/upload-docs.js --skip-upload --timestamp <ts>' ) ;
276304 process . exit ( 1 ) ;
277305 }
278306
279- const dir = path . resolve ( process . cwd ( ) , options . dir ) ;
280- if ( ! fs . existsSync ( dir ) ) {
281- console . error ( ` [error] Directory does not exist: ${ dir } ` ) ;
307+ // 3. Validate --timestamp is required when --skip-upload
308+ if ( options . skipUpload && ! options . timestamp ) {
309+ console . error ( ' [error] --timestamp is required when using --skip-upload' ) ;
282310 process . exit ( 1 ) ;
283311 }
284312
285313 const domain = normalizeDomain ( process . env . DOCS_ALI_CDN_DOMAIN ) ;
286314
287- // 3 . Create OSS client
315+ // 4 . Create OSS client
288316 const Client = require ( 'ali-oss' ) ;
289317 const ossClient = new Client ( {
290318 accessKeyId : process . env . DOCS_ALI_OSS_ACCESS_KEY_ID ,
@@ -293,35 +321,45 @@ async function main() {
293321 region : process . env . DOCS_ALI_OSS_REGION ,
294322 } ) ;
295323
296- // 4 . Generate or use provided timestamp
324+ // 5 . Generate or use provided timestamp
297325 const timestamp = options . timestamp || generateTimestamp ( ) ;
298326
299- // 5. Upload to OSS
300- console . log ( `[info] Uploading docs from ${ dir } to OSS under ${ timestamp } /...` ) ;
301- try {
302- let uploadedCount = 0 ;
303-
304- // Upload en-US to the root of the timestamp directory (default language)
305- const enUSDir = path . resolve ( dir , 'en-US' ) ;
306- if ( fs . existsSync ( enUSDir ) ) {
307- console . log ( '[info] Uploading en-US as root (default language)...' ) ;
308- uploadedCount += await uploadDirectoryToOSS ( ossClient , enUSDir , timestamp ) ;
327+ // 6. Upload to OSS (skip if --skip-upload)
328+ if ( ! options . skipUpload ) {
329+ const dir = path . resolve ( process . cwd ( ) , options . dir ) ;
330+ if ( ! fs . existsSync ( dir ) ) {
331+ console . error ( `[error] Directory does not exist: ${ dir } ` ) ;
332+ process . exit ( 1 ) ;
309333 }
310334
311- // Upload other language directories (skip en-US since it's already at root)
312- const langDirs = fs . readdirSync ( dir ) ;
313- for ( const lang of langDirs ) {
314- if ( lang === 'en-US' ) continue ;
315- const langDir = path . resolve ( dir , lang ) ;
316- if ( fs . statSync ( langDir ) . isDirectory ( ) ) {
317- uploadedCount += await uploadDirectoryToOSS ( ossClient , langDir , `${ timestamp } /${ lang } ` ) ;
335+ console . log ( `[info] Uploading docs from ${ dir } to OSS under ${ timestamp } /...` ) ;
336+ try {
337+ let uploadedCount = 0 ;
338+
339+ // Upload en-US to the root of the timestamp directory (default language)
340+ const enUSDir = path . resolve ( dir , 'en-US' ) ;
341+ if ( fs . existsSync ( enUSDir ) ) {
342+ console . log ( '[info] Uploading en-US as root (default language)...' ) ;
343+ uploadedCount += await uploadDirectoryToOSS ( ossClient , enUSDir , timestamp ) ;
318344 }
319- }
320345
321- console . log ( `[info] Successfully uploaded ${ uploadedCount } files to OSS under ${ timestamp } /` ) ;
322- } catch ( error ) {
323- console . error ( `[error] Upload failed: ${ error . message } ` ) ;
324- process . exit ( 1 ) ;
346+ // Upload other language directories (skip en-US since it's already at root)
347+ const langDirs = fs . readdirSync ( dir ) ;
348+ for ( const lang of langDirs ) {
349+ if ( lang === 'en-US' ) continue ;
350+ const langDir = path . resolve ( dir , lang ) ;
351+ if ( fs . statSync ( langDir ) . isDirectory ( ) ) {
352+ uploadedCount += await uploadDirectoryToOSS ( ossClient , langDir , `${ timestamp } /${ lang } ` ) ;
353+ }
354+ }
355+
356+ console . log ( `[info] Successfully uploaded ${ uploadedCount } files to OSS under ${ timestamp } /` ) ;
357+ } catch ( error ) {
358+ console . error ( `[error] Upload failed: ${ error . message } ` ) ;
359+ process . exit ( 1 ) ;
360+ }
361+ } else {
362+ console . log ( `[info] Skipping upload (--skip-upload), using timestamp: ${ timestamp } ` ) ;
325363 }
326364
327365 // 6. Update CDN origin rewrite rule
0 commit comments