Files
crowd-funder-from-jason/scripts/assets-config.js
Matthew Raymer a284067522 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
2025-08-14 07:22:26 +00:00

175 lines
4.5 KiB
JavaScript

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