forked from trent_larson/crowd-funder-for-time-pwa
feat(assets): standardize asset configuration with capacitor-assets
- Replace manual ImageMagick scripts with official capacitor-assets toolchain - Consolidate duplicate asset sources to single resources/ directory - Implement comprehensive asset configuration schema and validation - Add CI safeguards for asset validation and platform asset detection - Convert capacitor.config.json to TypeScript format - Pin Node.js version for deterministic builds - Remove legacy manual asset generation scripts: * generate-icons.sh, generate-ios-assets.sh, generate-android-icons.sh * check-android-resources.sh, check-ios-resources.sh * purge-generated-assets.sh - Add new asset management commands: * assets:config - generate/update configurations * assets:validate - validate configurations * assets:clean - clean generated assets (dev only) * build:native - build with asset generation - Create GitHub Actions workflow for asset validation - Update documentation with new asset management workflow This standardization eliminates asset duplication, improves build reliability, and provides a maintainable asset management system using Capacitor defaults. Breaking Changes: Manual asset generation scripts removed Migration: Assets now sourced from resources/ directory only CI: Automated validation prevents committed platform assets
This commit is contained in:
174
scripts/assets-config.js
Normal file
174
scripts/assets-config.js
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* TimeSafari Asset Configuration Generator
|
||||
* Generates capacitor-assets configuration files with deterministic outputs
|
||||
* Author: Matthew Raymer
|
||||
*
|
||||
* Usage: node scripts/assets-config.js
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Generate deterministic capacitor-assets configuration
|
||||
* @returns {Object} Sorted, stable configuration object
|
||||
*/
|
||||
function generateAssetConfig() {
|
||||
const config = {
|
||||
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 {Object} obj - Object to sort
|
||||
* @returns {Object} Object with sorted keys
|
||||
*/
|
||||
function sortObjectKeys(obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(sortObjectKeys);
|
||||
}
|
||||
|
||||
const sorted = {};
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
.forEach(key => {
|
||||
sorted[key] = sortObjectKeys(obj[key]);
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that required source files exist
|
||||
*/
|
||||
function validateSourceFiles() {
|
||||
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 {Object} config - Configuration object
|
||||
* @param {string} outputPath - Output file path
|
||||
*/
|
||||
function writeConfig(config, outputPath) {
|
||||
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() {
|
||||
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.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
}
|
||||
|
||||
export { generateAssetConfig, sortObjectKeys, validateSourceFiles };
|
||||
Reference in New Issue
Block a user