@@ -13,27 +13,83 @@ const fs = require("fs");
1313
1414function getSubdirectoriesSync ( url ) {
1515 try {
16+ // cache setup
1617 let urlPath = new URL ( url ) . pathname ;
1718 let cacheFilenamePath = urlPath . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '' ) + '.json' ;
1819 let cachePath = `${ __dirname } /../build/requests/${ cacheFilenamePath } ` ;
1920 fs . mkdirSync ( `${ __dirname } /../build/requests/` , { recursive : true } ) ;
2021 const readFromCacheFile = fs . existsSync ( cachePath ) && fs . statSync ( cachePath ) . mtime > new Date ( Date . now ( ) - 1000 * 60 * 60 * 24 ) ;
22+
23+ // read file or make request
2124 const data =
2225 readFromCacheFile ?
2326 fs . readFileSync ( cachePath , 'utf-8' ) :
2427 request ( 'GET' , url ) . getBody ( 'utf-8' )
2528 if ( ! readFromCacheFile ) {
2629 fs . writeFileSync ( cachePath , data ) ;
2730 }
28- const regex = / < a h r e f = " ( [ ^ " ] + ) \/ " > / g;
29- const directories = [ ] ;
31+
32+ // parse entries: name + date (if present)
33+ // Matches: <a href="name/">name/</a> [spaces] 11-Sep-2025 20:51
34+ const entryRe = / < a h r e f = " ( [ ^ " ] + ) \/ " > [ ^ < ] * < \/ a > \s + ( \d { 2 } - [ A - Z a - z ] { 3 } - \d { 4 } \s + \d { 2 } : \d { 2 } ) ? / g;
35+
36+ const month = { Jan :0 , Feb :1 , Mar :2 , Apr :3 , May :4 , Jun :5 , Jul :6 , Aug :7 , Sep :8 , Oct :9 , Nov :10 , Dec :11 } ;
37+ const parseDate = ( s ) => {
38+ if ( ! s ) return null ; // missing or no date in listing
39+ // "11-Sep-2025 20:51"
40+ const m = / ^ ( \d { 2 } ) - ( [ A - Z a - z ] { 3 } ) - ( \d { 4 } ) \s + ( \d { 2 } ) : ( \d { 2 } ) $ / . exec ( s . trim ( ) ) ;
41+ if ( ! m ) return null ;
42+ const [ , d , mon , y , hh , mm ] = m ;
43+ return new Date ( Date . UTC ( + y , month [ mon ] , + d , + hh , + mm ) ) ;
44+ } ;
45+
46+ const isSemver = ( name ) => {
47+ // allow optional leading 'v', ignore pre-release/build metadata for ordering
48+ const m = / ^ v ? ( \d + ) \. ( \d + ) \. ( \d + ) (?: [ - + ] .* ) ? $ / . exec ( name ) ;
49+ if ( ! m ) return null ;
50+ return { major : + m [ 1 ] , minor : + m [ 2 ] , patch : + m [ 3 ] } ;
51+ } ;
52+
53+ const entries = [ ] ;
3054 let match ;
31- while ( ( match = regex . exec ( data ) ) !== null ) {
32- if ( match [ 1 ] !== '..' && match [ 1 ] !== '.' ) {
33- directories . push ( match [ 1 ] ) ;
34- }
55+ while ( ( match = entryRe . exec ( data ) ) !== null ) {
56+ const name = match [ 1 ] ;
57+ if ( name === '.' || name === '..' ) continue ;
58+ const dt = parseDate ( match [ 2 ] || null ) ;
59+ const sv = isSemver ( name ) ;
60+ entries . push ( {
61+ name,
62+ date : dt , // may be null
63+ semver : ! ! sv ,
64+ major : sv ? sv . major : 0 ,
65+ minor : sv ? sv . minor : 0 ,
66+ patch : sv ? sv . patch : 0 ,
67+ } ) ;
3568 }
36- return directories ;
69+
70+ // sort
71+ entries . sort ( ( a , b ) => {
72+ // 1) non-semver first
73+ if ( a . semver !== b . semver ) return a . semver ? 1 : - 1 ;
74+
75+ if ( ! a . semver && ! b . semver ) {
76+ // non-semver: newer date first; if no date, push to end among non-semver
77+ const ad = a . date ? a . date . getTime ( ) : - Infinity ;
78+ const bd = b . date ? b . date . getTime ( ) : - Infinity ;
79+ if ( ad !== bd ) return bd - ad ;
80+ // tie-breaker: name asc for stability
81+ return a . name . localeCompare ( b . name ) ;
82+ }
83+
84+ // semver: major ↓, minor ↓, patch ↓
85+ if ( a . major !== b . major ) return b . major - a . major ;
86+ if ( a . minor !== b . minor ) return b . minor - a . minor ;
87+ if ( a . patch !== b . patch ) return b . patch - a . patch ;
88+ // final tie-breaker: name asc
89+ return a . name . localeCompare ( b . name ) ;
90+ } ) ;
91+
92+ return entries . map ( e => e . name ) ;
3793 } catch ( error ) {
3894 console . error ( 'Error:' , error ) ;
3995 return [ ] ;
0 commit comments