You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							224 lines
						
					
					
						
							5.4 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							224 lines
						
					
					
						
							5.4 KiB
						
					
					
				| #!/usr/bin/env tsx | |
| 
 | |
| /** | |
|  * TimeSafari Asset Configuration Generator | |
|  * Generates capacitor-assets configuration files with deterministic outputs | |
|  * Author: Matthew Raymer | |
|  *  | |
|  * Usage: tsx scripts/assets-config.ts | |
|  */ | |
| 
 | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| 
 | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| const PROJECT_ROOT = path.dirname(__dirname); | |
| 
 | |
| // TypeScript interfaces for asset configuration | |
| interface AdaptiveIconConfig { | |
|   foreground: string; | |
|   background: string; | |
|   monochrome: string; | |
| } | |
| 
 | |
| interface AndroidIconConfig { | |
|   adaptive: AdaptiveIconConfig; | |
|   target: string; | |
| } | |
| 
 | |
| interface IOSIconConfig { | |
|   padding: number; | |
|   target: string; | |
| } | |
| 
 | |
| interface WebIconConfig { | |
|   target: string; | |
| } | |
| 
 | |
| interface IconConfig { | |
|   source: string; | |
|   android: AndroidIconConfig; | |
|   ios: IOSIconConfig; | |
|   web: WebIconConfig; | |
| } | |
| 
 | |
| interface AndroidSplashConfig { | |
|   scale: string; | |
|   target: string; | |
| } | |
| 
 | |
| interface IOSSplashConfig { | |
|   useStoryBoard: boolean; | |
|   target: string; | |
| } | |
| 
 | |
| interface SplashConfig { | |
|   source: string; | |
|   darkSource: string; | |
|   android: AndroidSplashConfig; | |
|   ios: IOSSplashConfig; | |
| } | |
| 
 | |
| interface AssetConfig { | |
|   icon: IconConfig; | |
|   splash: SplashConfig; | |
| } | |
| 
 | |
| /** | |
|  * Generate deterministic capacitor-assets configuration | |
|  * @returns Sorted, stable configuration object | |
|  */ | |
| function generateAssetConfig(): AssetConfig { | |
|   const config: AssetConfig = { | |
|     icon: { | |
|       source: "resources/icon.png", | |
|       android: { | |
|         adaptive: { | |
|           foreground: "resources/icon.png", | |
|           background: "#121212", | |
|           monochrome: "resources/icon.png" | |
|         }, | |
|         target: "android/app/src/main/res" | |
|       }, | |
|       ios: { | |
|         padding: 0, | |
|         target: "ios/App/App/Assets.xcassets/AppIcon.appiconset" | |
|       }, | |
|       web: { | |
|         target: "public/img/icons" | |
|       } | |
|     }, | |
|     splash: { | |
|       source: "resources/splash.png", | |
|       darkSource: "resources/splash_dark.png", | |
|       android: { | |
|         scale: "cover", | |
|         target: "android/app/src/main/res" | |
|       }, | |
|       ios: { | |
|         useStoryBoard: true, | |
|         target: "ios/App/App/Assets.xcassets" | |
|       } | |
|     } | |
|   }; | |
| 
 | |
|   return sortObjectKeys(config); | |
| } | |
| 
 | |
| /** | |
|  * Sort object keys recursively for deterministic output | |
|  * @param obj - Object to sort | |
|  * @returns Object with sorted keys | |
|  */ | |
| function sortObjectKeys(obj: any): any { | |
|   if (obj === null || typeof obj !== 'object') { | |
|     return obj; | |
|   } | |
| 
 | |
|   if (Array.isArray(obj)) { | |
|     return obj.map(sortObjectKeys); | |
|   } | |
| 
 | |
|   const sorted: any = {}; | |
|   Object.keys(obj) | |
|     .sort() | |
|     .forEach(key => { | |
|       sorted[key] = sortObjectKeys(obj[key]); | |
|     }); | |
| 
 | |
|   return sorted; | |
| } | |
| 
 | |
| /** | |
|  * Validate that required source files exist | |
|  */ | |
| function validateSourceFiles(): void { | |
|   const requiredFiles = [ | |
|     'resources/icon.png', | |
|     'resources/splash.png', | |
|     'resources/splash_dark.png' | |
|   ]; | |
| 
 | |
|   const missingFiles = requiredFiles.filter(file => { | |
|     const filePath = path.join(PROJECT_ROOT, file); | |
|     return !fs.existsSync(filePath); | |
|   }); | |
| 
 | |
|   if (missingFiles.length > 0) { | |
|     console.error('❌ Missing required source files:'); | |
|     missingFiles.forEach(file => console.error(`   ${file}`)); | |
|     process.exit(1); | |
|   } | |
| 
 | |
|   console.log('✅ All required source files found'); | |
| } | |
| 
 | |
| /** | |
|  * Write configuration to file with consistent formatting | |
|  * @param config - Configuration object | |
|  * @param outputPath - Output file path | |
|  */ | |
| function writeConfig(config: AssetConfig, outputPath: string): void { | |
|   const jsonString = JSON.stringify(config, null, 2); | |
|    | |
|   // Ensure consistent line endings and no trailing whitespace | |
|   const cleanJson = jsonString | |
|     .split('\n') | |
|     .map(line => line.trimEnd()) | |
|     .join('\n') + '\n'; | |
| 
 | |
|   fs.writeFileSync(outputPath, cleanJson, 'utf8'); | |
|   console.log(`✅ Configuration written to: ${outputPath}`); | |
| } | |
| 
 | |
| /** | |
|  * Main execution function | |
|  */ | |
| function main(): void { | |
|   console.log('🔄 Generating TimeSafari asset configuration...'); | |
|   console.log(`📁 Project root: ${PROJECT_ROOT}`); | |
|   console.log(`📅 Generated: ${new Date().toISOString()}`); | |
| 
 | |
|   try { | |
|     // Validate source files exist | |
|     validateSourceFiles(); | |
| 
 | |
|     // Generate configuration | |
|     const config = generateAssetConfig(); | |
| 
 | |
|     // Ensure config directory exists | |
|     const configDir = path.join(PROJECT_ROOT, 'config', 'assets'); | |
|     if (!fs.existsSync(configDir)) { | |
|       fs.mkdirSync(configDir, { recursive: true }); | |
|     } | |
| 
 | |
|     // Write configuration files | |
|     const capacitorAssetsConfigPath = path.join(configDir, 'capacitor-assets.config.json'); | |
|     writeConfig(config, capacitorAssetsConfigPath); | |
| 
 | |
|     // Copy to root for capacitor-assets discovery | |
|     const rootConfigPath = path.join(PROJECT_ROOT, 'capacitor-assets.config.json'); | |
|     writeConfig(config, rootConfigPath); | |
| 
 | |
|     console.log('🎉 Asset configuration generation completed successfully!'); | |
|     console.log(''); | |
|     console.log('📋 Next steps:'); | |
|     console.log('   1. Review the generated configuration'); | |
|     console.log('   2. Commit the configuration files'); | |
|     console.log('   3. Run "npm run assets:validate" to verify'); | |
|     console.log('   4. Use "npm run build:native" for builds'); | |
| 
 | |
|   } catch (error) { | |
|     console.error('❌ Configuration generation failed:', error instanceof Error ? error.message : String(error)); | |
|     process.exit(1); | |
|   } | |
| } | |
| 
 | |
| // Run if called directly | |
| if (import.meta.url === `file://${process.argv[1]}`) { | |
|   main(); | |
| } | |
| 
 | |
| export { generateAssetConfig, sortObjectKeys, validateSourceFiles, AssetConfig };
 | |
| 
 |