@@ -68,6 +68,27 @@ const STAGE5_ALLOWED_KEYS = [
6868 "packageJson"
6969] ;
7070
71+ const FINAL_ALLOWED_KEYS = [
72+ "name" ,
73+ "category" ,
74+ "url" ,
75+ "id" ,
76+ "maintainer" ,
77+ "maintainerURL" ,
78+ "description" ,
79+ "outdated" ,
80+ "issues" ,
81+ "stars" ,
82+ "license" ,
83+ "hasGithubIssues" ,
84+ "isArchived" ,
85+ "tags" ,
86+ "image" ,
87+ "defaultSortWeight" ,
88+ "lastCommit" ,
89+ "keywords"
90+ ] ;
91+
7192function toStage5Module ( module ) {
7293 const entry = { } ;
7394
@@ -84,6 +105,103 @@ function toStage5Module(module) {
84105 return entry ;
85106}
86107
108+ function isValidDateTime ( value ) {
109+ return typeof value === "string" && value . length > 0 && ! Number . isNaN ( Date . parse ( value ) ) ;
110+ }
111+
112+ function getRepositoryHost ( moduleUrl ) {
113+ if ( typeof moduleUrl !== "string" ) {
114+ return "unknown" ;
115+ }
116+
117+ try {
118+ const firstSegment = moduleUrl . split ( "." ) [ 0 ] ;
119+ const segments = firstSegment . split ( "/" ) ;
120+ return segments [ 2 ] ?? "unknown" ;
121+ }
122+ catch {
123+ return "unknown" ;
124+ }
125+ }
126+
127+ function toFinalModule ( module , fallbackTimestamp ) {
128+ const issueList = Array . isArray ( module . issues ) ? module . issues : [ ] ;
129+ const stars = typeof module . stars === "number" ? module . stars : 0 ;
130+
131+ let defaultSortWeight = issueList . length - Math . floor ( stars / 20 ) ;
132+ if ( stars < 3 ) {
133+ defaultSortWeight = Math . max ( defaultSortWeight , 1 ) ;
134+ }
135+
136+ if ( module . outdated || module . category === "Outdated Modules" ) {
137+ defaultSortWeight += 900 ;
138+ }
139+
140+ const candidate = {
141+ ...module ,
142+ description :
143+ typeof module . description === "string" && module . description . length > 0
144+ ? module . description
145+ : "No description provided." ,
146+ issues : issueList . length > 0 ,
147+ defaultSortWeight,
148+ lastCommit : isValidDateTime ( module . lastCommit )
149+ ? module . lastCommit
150+ : fallbackTimestamp
151+ } ;
152+
153+ if ( typeof candidate . license !== "string" || candidate . license . length === 0 ) {
154+ delete candidate . license ;
155+ }
156+
157+ if ( ! Array . isArray ( candidate . tags ) || candidate . tags . length === 0 ) {
158+ delete candidate . tags ;
159+ }
160+
161+ if ( ! Array . isArray ( candidate . keywords ) || candidate . keywords . length === 0 ) {
162+ delete candidate . keywords ;
163+ }
164+
165+ const entry = { } ;
166+ for ( const key of FINAL_ALLOWED_KEYS ) {
167+ if ( Object . hasOwn ( candidate , key ) && typeof candidate [ key ] !== "undefined" ) {
168+ entry [ key ] = candidate [ key ] ;
169+ }
170+ }
171+
172+ return entry ;
173+ }
174+
175+ function buildStats ( stage5Modules , finalModules , timestamp ) {
176+ const repositoryHoster = { } ;
177+ const maintainer = { } ;
178+
179+ for ( const module of finalModules ) {
180+ const hoster = getRepositoryHost ( module . url ) ;
181+ repositoryHoster [ hoster ] = ( repositoryHoster [ hoster ] ?? 0 ) + 1 ;
182+ maintainer [ module . maintainer ] = ( maintainer [ module . maintainer ] ?? 0 ) + 1 ;
183+ }
184+
185+ const issueCounter = stage5Modules . reduce ( ( count , module ) => {
186+ if ( Array . isArray ( module . issues ) ) {
187+ return count + module . issues . length ;
188+ }
189+ return count ;
190+ } , 0 ) ;
191+
192+ return {
193+ moduleCounter : finalModules . length ,
194+ modulesWithImageCounter : finalModules . filter ( module => typeof module . image === "string" && module . image . length > 0 ) . length ,
195+ modulesWithIssuesCounter : finalModules . filter ( module => module . issues === true ) . length ,
196+ issueCounter,
197+ lastUpdate : timestamp ,
198+ repositoryHoster,
199+ maintainer : Object . fromEntries (
200+ Object . entries ( maintainer ) . sort ( ( [ , left ] , [ , right ] ) => right - left )
201+ )
202+ } ;
203+ }
204+
87205async function main ( ) {
88206 const startTime = Date . now ( ) ;
89207
@@ -170,10 +288,25 @@ async function main() {
170288 } ) ;
171289 const stage5Modules = mergedModules . map ( toStage5Module ) ;
172290
173- // Write results to stage 5 output
291+ // Write stage 5 output and final public artefacts.
174292 const stage5Path = resolve ( PROJECT_ROOT , "website/data/modules.stage.5.json" ) ;
293+ const modulesJsonPath = resolve ( PROJECT_ROOT , "website/data/modules.json" ) ;
294+ const modulesMinPath = resolve ( PROJECT_ROOT , "website/data/modules.min.json" ) ;
295+ const statsPath = resolve ( PROJECT_ROOT , "website/data/stats.json" ) ;
296+
297+ const lastUpdate = new Date ( ) . toISOString ( ) ;
298+ const finalModules = stage5Modules . map ( module => toFinalModule ( module , lastUpdate ) ) ;
299+ const stats = buildStats ( stage5Modules , finalModules , lastUpdate ) ;
300+
175301 const stage5Data = stringifyDeterministic ( { modules : stage5Modules } ) ;
302+ const modulesData = stringifyDeterministic ( { modules : finalModules } ) ;
303+ const modulesMinData = stringifyDeterministic ( { modules : finalModules } , 0 ) ;
304+ const statsData = stringifyDeterministic ( stats ) ;
305+
176306 await writeFile ( stage5Path , stage5Data , "utf-8" ) ;
307+ await writeFile ( modulesJsonPath , modulesData , "utf-8" ) ;
308+ await writeFile ( modulesMinPath , modulesMinData , "utf-8" ) ;
309+ await writeFile ( statsPath , statsData , "utf-8" ) ;
177310
178311 const duration = Date . now ( ) - startTime ;
179312 const avgTime = Math . round ( duration / results . length ) ;
0 commit comments