1+ #!/usr/bin/env node
2+
3+ const fs = require ( 'fs' ) ;
4+ const path = require ( 'path' ) ;
5+
6+ // Function to recursively find all metadata.json files
7+ function findMetadataFiles ( dir ) {
8+ const metadataFiles = [ ] ;
9+
10+ if ( ! fs . existsSync ( dir ) ) {
11+ return metadataFiles ;
12+ }
13+
14+ const items = fs . readdirSync ( dir , { withFileTypes : true } ) ;
15+
16+ for ( const item of items ) {
17+ const fullPath = path . join ( dir , item . name ) ;
18+
19+ if ( item . isDirectory ( ) ) {
20+ // Recursively search subdirectories
21+ metadataFiles . push ( ...findMetadataFiles ( fullPath ) ) ;
22+ } else if ( item . name === 'metadata.json' ) {
23+ metadataFiles . push ( fullPath ) ;
24+ }
25+ }
26+
27+ return metadataFiles ;
28+ }
29+
30+ // Function to load and validate metadata
31+ function loadMetadata ( filePath ) {
32+ try {
33+ const content = fs . readFileSync ( filePath , 'utf8' ) ;
34+ const metadata = JSON . parse ( content ) ;
35+
36+ // Basic validation - ensure required fields exist
37+ const requiredFields = [ 'name' , 'category' , 'description' , 'version' , 'commit' , 'owner' , 'repo' , 'path' ] ;
38+ for ( const field of requiredFields ) {
39+ if ( ! ( field in metadata ) || metadata [ field ] === null || metadata [ field ] === undefined || metadata [ field ] === '' ) {
40+ console . warn ( `⚠️ Skipping ${ filePath } : missing or empty field '${ field } '` ) ;
41+ return null ;
42+ }
43+ }
44+
45+ // Add file path for reference
46+ metadata . filePath = path . dirname ( filePath ) . replace ( / \\ / g, '/' ) ;
47+
48+ return metadata ;
49+ } catch ( error ) {
50+ console . warn ( `⚠️ Skipping ${ filePath } : ${ error . message } ` ) ;
51+ return null ;
52+ }
53+ }
54+
55+ // Function to load valid categories
56+ function loadValidCategories ( ) {
57+ try {
58+ const categoriesPath = path . join ( __dirname , '../../' , 'categories.json' ) ;
59+ const categoriesContent = fs . readFileSync ( categoriesPath , 'utf8' ) ;
60+ return JSON . parse ( categoriesContent ) ;
61+ } catch ( error ) {
62+ console . warn ( `⚠️ Could not load categories.json: ${ error . message } ` ) ;
63+ return [ ] ;
64+ }
65+ }
66+
67+ // Main function
68+ async function main ( ) {
69+ console . log ( '🔄 Generating minified category files...' ) ;
70+
71+ // Load valid categories
72+ const validCategories = loadValidCategories ( ) ;
73+ console . log ( `📋 Valid categories: ${ validCategories . join ( ', ' ) } ` ) ;
74+
75+ // Find all metadata files
76+ const repositoriesDir = path . join ( __dirname , '../..' , 'repositories' ) ;
77+ const metadataFiles = findMetadataFiles ( repositoriesDir ) ;
78+ console . log ( `📁 Found ${ metadataFiles . length } metadata files` ) ;
79+
80+ if ( metadataFiles . length === 0 ) {
81+ console . log ( 'ℹ️ No metadata files found. No category files will be generated.' ) ;
82+ return ;
83+ }
84+
85+ // Group metadata by category
86+ const categorizedApps = { } ;
87+ let processedCount = 0 ;
88+ let skippedCount = 0 ;
89+
90+ for ( const filePath of metadataFiles ) {
91+ const metadata = loadMetadata ( filePath ) ;
92+ if ( metadata ) {
93+ const category = metadata . category ;
94+
95+ if ( ! categorizedApps [ category ] ) {
96+ categorizedApps [ category ] = [ ] ;
97+ }
98+
99+ categorizedApps [ category ] . push ( metadata ) ;
100+ processedCount ++ ;
101+ console . log ( `✅ Added ${ metadata . name } to category '${ category } '` ) ;
102+ } else {
103+ skippedCount ++ ;
104+ }
105+ }
106+
107+ console . log ( `📊 Processed: ${ processedCount } , Skipped: ${ skippedCount } ` ) ;
108+
109+ // Create releases directory if it doesn't exist
110+ const releasesDir = path . join ( __dirname , '../..' , 'releases' ) ;
111+ if ( ! fs . existsSync ( releasesDir ) ) {
112+ fs . mkdirSync ( releasesDir , { recursive : true } ) ;
113+ console . log ( `📁 Created releases directory` ) ;
114+ }
115+
116+ // Generate category files
117+ const generatedFiles = [ ] ;
118+ const categoriesWithReleases = [ ] ;
119+ for ( const [ category , apps ] of Object . entries ( categorizedApps ) ) {
120+ const releaseFileName = `category-${ category . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '-' ) } .min.json` ;
121+ const releaseFilePath = path . join ( releasesDir , releaseFileName ) ;
122+
123+ // Sort apps by name for consistent ordering
124+ apps . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
125+
126+ // Filter out unwanted fields from apps for category files
127+ const filteredApps = apps . map ( app => {
128+ const { commit, owner, repo, path, filePath, category, files, name, description, version, 'supported-devices' : supportedDevices , ...cleanApp } = app ;
129+ // Add slug in format: owner/repo/subfolder_name
130+ const subfolderName = filePath . split ( '/' ) . pop ( ) ;
131+ const slug = `${ owner } /${ repo } /${ subfolderName } ` ;
132+
133+ // Add only shortened field names
134+ cleanApp . n = name ; // name -> n
135+ cleanApp . d = description ; // description -> d
136+ cleanApp . v = version ; // version -> v
137+ cleanApp . s = slug ; // slug -> s
138+
139+ // Include supported-devices if present (apps/scripts only, not themes)
140+ const isTheme = app . category === 'Themes' ;
141+ if ( supportedDevices && ! isTheme ) {
142+ cleanApp [ 'sd' ] = supportedDevices ;
143+ }
144+
145+ // Include supported-screen-size if present (themes only)
146+ if ( app [ 'supported-screen-size' ] && isTheme ) {
147+ cleanApp [ 'sss' ] = app [ 'supported-screen-size' ] ;
148+ }
149+
150+ return cleanApp ;
151+ } ) ;
152+
153+ const releaseData = {
154+ category : category ,
155+ count : apps . length ,
156+ apps : filteredApps
157+ } ;
158+
159+ try {
160+ // Write without pretty formatting (minified)
161+ fs . writeFileSync ( releaseFilePath , JSON . stringify ( releaseData ) , 'utf8' ) ;
162+ generatedFiles . push ( releaseFileName ) ;
163+ categoriesWithReleases . push ( category ) ;
164+ console . log ( `📄 Generated ${ releaseFileName } with ${ apps . length } apps (minified)` ) ;
165+ } catch ( error ) {
166+ console . error ( `❌ Failed to write ${ releaseFileName } : ${ error . message } ` ) ;
167+ }
168+ }
169+
170+ // Clean up old minified category files that are no longer needed
171+ const existingReleaseFiles = fs . readdirSync ( releasesDir )
172+ . filter ( file => file . startsWith ( 'category-' ) && file . endsWith ( '.min.json' ) ) ;
173+
174+ for ( const existingFile of existingReleaseFiles ) {
175+ if ( ! generatedFiles . includes ( existingFile ) ) {
176+ try {
177+ fs . unlinkSync ( path . join ( releasesDir , existingFile ) ) ;
178+ console . log ( `🗑️ Removed obsolete file: ${ existingFile } ` ) ;
179+ } catch ( error ) {
180+ console . warn ( `⚠️ Could not remove ${ existingFile } : ${ error . message } ` ) ;
181+ }
182+ }
183+ }
184+
185+ // Generate summary
186+ console . log ( '' ) ;
187+ console . log ( '📋 Summary:' ) ;
188+ console . log ( ` Categories: ${ Object . keys ( categorizedApps ) . length } ` ) ;
189+ console . log ( ` Total apps: ${ processedCount } ` ) ;
190+ console . log ( ` Minified category files: ${ generatedFiles . length } ` ) ;
191+
192+ if ( generatedFiles . length > 0 ) {
193+ console . log ( '' ) ;
194+ console . log ( '📄 Generated minified category files:' ) ;
195+ for ( const file of generatedFiles ) {
196+ console . log ( ` - ${ file } ` ) ;
197+ }
198+ }
199+
200+ console . log ( '✅ Minified category file generation complete!' ) ;
201+ }
202+
203+ // Run the script
204+ main ( ) . catch ( error => {
205+ console . error ( '❌ Script failed:' , error ) ;
206+ process . exit ( 1 ) ;
207+ } ) ;
0 commit comments