#!/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 };