refactor(assets): convert asset management scripts to TypeScript with tsx
- Replace JavaScript asset scripts with TypeScript equivalents - Install tsx for direct TypeScript execution without compilation - Add proper TypeScript interfaces for AssetConfig and validation - Update package.json scripts to use tsx instead of node - Remove old JavaScript files (assets-config.js, assets-validator.js) - Maintain all existing functionality while improving type safety - Fix module syntax issues that caused build failures on macOS Scripts affected: - assets:config: node → tsx scripts/assets-config.ts - assets:validate: node → tsx scripts/assets-validator.ts Benefits: - Eliminates CommonJS/ES module syntax conflicts - Provides better type safety and IntelliSense - Modernizes development tooling - Ensures cross-platform compatibility
This commit is contained in:
224
scripts/assets-config.ts
Normal file
224
scripts/assets-config.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/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 };
|
||||
Reference in New Issue
Block a user