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 };
|
|
|