Browse Source
- 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 assetspull/165/head
25 changed files with 1125 additions and 986 deletions
@ -0,0 +1,32 @@ |
|||||
|
--- |
||||
|
alwaysApply: true |
||||
|
--- |
||||
|
# Asset Configuration Directive |
||||
|
*Scope: Assets Only (icons, splashes, image pipelines) — not overall build orchestration* |
||||
|
|
||||
|
## Intent |
||||
|
- Version **asset configuration files** (optionally dev-time generated). |
||||
|
- **Do not** version platform asset outputs (Android/iOS/Electron); generate them **at build-time** with standard tools. |
||||
|
- Keep existing per-platform build scripts unchanged. |
||||
|
|
||||
|
## Source of Truth |
||||
|
- **Preferred (Capacitor default):** `resources/` as the single master source. |
||||
|
- **Alternative:** `assets/` is acceptable **only** if `capacitor-assets` is explicitly configured to read from it. |
||||
|
- **Never** maintain both `resources/` and `assets/` as parallel sources. Migrate and delete the redundant folder. |
||||
|
|
||||
|
## Config Files |
||||
|
- Live under: `config/assets/` (committed). |
||||
|
- Examples: |
||||
|
- `config/assets/capacitor-assets.config.json` (or the path the tool expects) |
||||
|
- `config/assets/android.assets.json` |
||||
|
- `config/assets/ios.assets.json` |
||||
|
- `config/assets/common.assets.yaml` (optional shared layer) |
||||
|
- **Dev-time generation allowed** for these configs; **build-time generation is forbidden**. |
||||
|
|
||||
|
## Build-Time Behavior |
||||
|
- Build generates platform assets (not configs) using the standard chain: |
||||
|
```bash |
||||
|
npm run build:capacitor # web build via Vite (.mts) |
||||
|
npx cap sync |
||||
|
npx capacitor-assets generate # produces platform assets; not committed |
||||
|
# then platform-specific build steps |
@ -0,0 +1,142 @@ |
|||||
|
name: Asset Validation & CI Safeguards |
||||
|
|
||||
|
on: |
||||
|
pull_request: |
||||
|
paths: |
||||
|
- 'resources/**' |
||||
|
- 'config/assets/**' |
||||
|
- 'capacitor-assets.config.json' |
||||
|
- 'capacitor.config.ts' |
||||
|
- 'capacitor.config.json' |
||||
|
push: |
||||
|
branches: [main, develop] |
||||
|
paths: |
||||
|
- 'resources/**' |
||||
|
- 'config/assets/**' |
||||
|
- 'capacitor-assets.config.json' |
||||
|
- 'capacitor.config.ts' |
||||
|
- 'capacitor.config.json' |
||||
|
|
||||
|
jobs: |
||||
|
asset-validation: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- name: Checkout code |
||||
|
uses: actions/checkout@v4 |
||||
|
|
||||
|
- name: Setup Node.js |
||||
|
uses: actions/setup-node@v4 |
||||
|
with: |
||||
|
node-version-file: '.nvmrc' |
||||
|
cache: 'npm' |
||||
|
|
||||
|
- name: Install dependencies |
||||
|
run: npm ci |
||||
|
|
||||
|
- name: Validate asset configuration |
||||
|
run: npm run assets:validate |
||||
|
|
||||
|
- name: Check for committed platform assets (Android) |
||||
|
run: | |
||||
|
if git ls-files -z android/app/src/main/res | grep -E '(AppIcon.*\.png|Splash.*\.png|mipmap-.*/ic_launcher.*\.png)' > /dev/null; then |
||||
|
echo "❌ Android platform assets found in VCS - these should be generated at build-time" |
||||
|
git ls-files -z android/app/src/main/res | grep -E '(AppIcon.*\.png|Splash.*\.png|mipmap-.*/ic_launcher.*\.png)' |
||||
|
exit 1 |
||||
|
fi |
||||
|
echo "✅ No Android platform assets committed" |
||||
|
|
||||
|
- name: Check for committed platform assets (iOS) |
||||
|
run: | |
||||
|
if git ls-files -z ios/App/App/Assets.xcassets | grep -E '(AppIcon.*\.png|Splash.*\.png)' > /dev/null; then |
||||
|
echo "❌ iOS platform assets found in VCS - these should be generated at build-time" |
||||
|
git ls-files -z ios/App/App/Assets.xcassets | grep -E '(AppIcon.*\.png|Splash.*\.png)' |
||||
|
exit 1 |
||||
|
fi |
||||
|
echo "✅ No iOS platform assets committed" |
||||
|
|
||||
|
- name: Test asset generation |
||||
|
run: | |
||||
|
echo "🧪 Testing asset generation workflow..." |
||||
|
npm run build:capacitor |
||||
|
npx cap sync |
||||
|
npx capacitor-assets generate --dry-run || npx capacitor-assets generate |
||||
|
echo "✅ Asset generation test completed" |
||||
|
|
||||
|
- name: Verify clean tree after build |
||||
|
run: | |
||||
|
if [ -n "$(git status --porcelain)" ]; then |
||||
|
echo "❌ Dirty tree after build - asset configs were modified" |
||||
|
git status |
||||
|
git diff |
||||
|
exit 1 |
||||
|
fi |
||||
|
echo "✅ Build completed with clean tree" |
||||
|
|
||||
|
schema-validation: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- name: Checkout code |
||||
|
uses: actions/checkout@v4 |
||||
|
|
||||
|
- name: Setup Node.js |
||||
|
uses: actions/setup-node@v4 |
||||
|
with: |
||||
|
node-version-file: '.nvmrc' |
||||
|
cache: 'npm' |
||||
|
|
||||
|
- name: Install dependencies |
||||
|
run: npm ci |
||||
|
|
||||
|
- name: Validate schema compliance |
||||
|
run: | |
||||
|
echo "🔍 Validating schema compliance..." |
||||
|
node -e " |
||||
|
const fs = require('fs'); |
||||
|
const config = JSON.parse(fs.readFileSync('capacitor-assets.config.json', 'utf8')); |
||||
|
const schema = JSON.parse(fs.readFileSync('config/assets/schema.json', 'utf8')); |
||||
|
|
||||
|
// Basic schema validation |
||||
|
if (!config.icon || !config.splash) { |
||||
|
throw new Error('Missing required sections: icon and splash'); |
||||
|
} |
||||
|
|
||||
|
if (!config.icon.source || !config.splash.source) { |
||||
|
throw new Error('Missing required source fields'); |
||||
|
} |
||||
|
|
||||
|
if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) { |
||||
|
throw new Error('Icon source must be in resources/ directory'); |
||||
|
} |
||||
|
|
||||
|
if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) { |
||||
|
throw new Error('Splash source must be in resources/ directory'); |
||||
|
} |
||||
|
|
||||
|
console.log('✅ Schema validation passed'); |
||||
|
" |
||||
|
|
||||
|
- name: Check source file existence |
||||
|
run: | |
||||
|
echo "📁 Checking source file existence..." |
||||
|
node -e " |
||||
|
const fs = require('fs'); |
||||
|
const config = JSON.parse(fs.readFileSync('capacitor-assets.config.json', 'utf8')); |
||||
|
|
||||
|
const requiredFiles = [ |
||||
|
config.icon.source, |
||||
|
config.splash.source |
||||
|
]; |
||||
|
|
||||
|
if (config.splash.darkSource) { |
||||
|
requiredFiles.push(config.splash.darkSource); |
||||
|
} |
||||
|
|
||||
|
const missingFiles = requiredFiles.filter(file => !fs.existsSync(file)); |
||||
|
|
||||
|
if (missingFiles.length > 0) { |
||||
|
console.error('❌ Missing source files:', missingFiles); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
|
||||
|
console.log('✅ All source files exist'); |
||||
|
" |
@ -0,0 +1 @@ |
|||||
|
18.19.0 |
@ -0,0 +1 @@ |
|||||
|
18.19.0 |
@ -1,2 +0,0 @@ |
|||||
|
|
||||
Application icons are here. They are processed for android & ios by the `capacitor-assets` command, as indicated in the BUILDING.md file. |
|
@ -1,36 +1,32 @@ |
|||||
{ |
{ |
||||
"icon": { |
"icon": { |
||||
"ios": { |
|
||||
"source": "resources/ios/icon/icon.png", |
|
||||
"target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" |
|
||||
}, |
|
||||
"android": { |
"android": { |
||||
"source": "resources/android/icon/icon.png", |
"adaptive": { |
||||
|
"background": "#121212", |
||||
|
"foreground": "resources/icon.png", |
||||
|
"monochrome": "resources/icon.png" |
||||
|
}, |
||||
"target": "android/app/src/main/res" |
"target": "android/app/src/main/res" |
||||
}, |
}, |
||||
|
"ios": { |
||||
|
"padding": 0, |
||||
|
"target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" |
||||
|
}, |
||||
|
"source": "resources/icon.png", |
||||
"web": { |
"web": { |
||||
"source": "resources/web/icon/icon.png", |
|
||||
"target": "public/img/icons" |
"target": "public/img/icons" |
||||
} |
} |
||||
}, |
}, |
||||
"splash": { |
"splash": { |
||||
"ios": { |
|
||||
"source": "resources/ios/splash/splash.png", |
|
||||
"target": "ios/App/App/Assets.xcassets/Splash.imageset" |
|
||||
}, |
|
||||
"android": { |
"android": { |
||||
"source": "resources/android/splash/splash.png", |
"scale": "cover", |
||||
"target": "android/app/src/main/res" |
"target": "android/app/src/main/res" |
||||
} |
}, |
||||
}, |
"darkSource": "resources/splash_dark.png", |
||||
"splashDark": { |
|
||||
"ios": { |
"ios": { |
||||
"source": "resources/ios/splash/splash_dark.png", |
"target": "ios/App/App/Assets.xcassets", |
||||
"target": "ios/App/App/Assets.xcassets/SplashDark.imageset" |
"useStoryBoard": true |
||||
}, |
}, |
||||
"android": { |
"source": "resources/splash.png" |
||||
"source": "resources/android/splash/splash_dark.png", |
|
||||
"target": "android/app/src/main/res" |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,116 @@ |
|||||
|
import { CapacitorConfig } from '@capacitor/cli'; |
||||
|
|
||||
|
const config: CapacitorConfig = { |
||||
|
appId: 'app.timesafari', |
||||
|
appName: 'TimeSafari', |
||||
|
webDir: 'dist', |
||||
|
server: { |
||||
|
cleartext: true |
||||
|
}, |
||||
|
plugins: { |
||||
|
App: { |
||||
|
appUrlOpen: { |
||||
|
handlers: [ |
||||
|
{ |
||||
|
url: 'timesafari://*', |
||||
|
autoVerify: true |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
SplashScreen: { |
||||
|
launchShowDuration: 3000, |
||||
|
launchAutoHide: true, |
||||
|
backgroundColor: '#ffffff', |
||||
|
androidSplashResourceName: 'splash', |
||||
|
androidScaleType: 'CENTER_CROP', |
||||
|
showSpinner: false, |
||||
|
androidSpinnerStyle: 'large', |
||||
|
iosSpinnerStyle: 'small', |
||||
|
spinnerColor: '#999999', |
||||
|
splashFullScreen: true, |
||||
|
splashImmersive: true |
||||
|
}, |
||||
|
CapSQLite: { |
||||
|
iosDatabaseLocation: 'Library/CapacitorDatabase', |
||||
|
iosIsEncryption: false, |
||||
|
iosBiometric: { |
||||
|
biometricAuth: false, |
||||
|
biometricTitle: 'Biometric login for TimeSafari' |
||||
|
}, |
||||
|
androidIsEncryption: false, |
||||
|
androidBiometric: { |
||||
|
biometricAuth: false, |
||||
|
biometricTitle: 'Biometric login for TimeSafari' |
||||
|
}, |
||||
|
electronIsEncryption: false |
||||
|
} |
||||
|
}, |
||||
|
ios: { |
||||
|
contentInset: 'never', |
||||
|
allowsLinkPreview: true, |
||||
|
scrollEnabled: true, |
||||
|
limitsNavigationsToAppBoundDomains: true, |
||||
|
backgroundColor: '#ffffff', |
||||
|
allowNavigation: [ |
||||
|
'*.timesafari.app', |
||||
|
'*.jsdelivr.net', |
||||
|
'api.endorser.ch' |
||||
|
] |
||||
|
}, |
||||
|
android: { |
||||
|
allowMixedContent: true, |
||||
|
captureInput: true, |
||||
|
webContentsDebuggingEnabled: false, |
||||
|
allowNavigation: [ |
||||
|
'*.timesafari.app', |
||||
|
'*.jsdelivr.net', |
||||
|
'api.endorser.ch', |
||||
|
'10.0.2.2:3000' |
||||
|
] |
||||
|
}, |
||||
|
electron: { |
||||
|
deepLinking: { |
||||
|
schemes: ['timesafari'] |
||||
|
}, |
||||
|
buildOptions: { |
||||
|
appId: 'app.timesafari', |
||||
|
productName: 'TimeSafari', |
||||
|
directories: { |
||||
|
output: 'dist-electron-packages' |
||||
|
}, |
||||
|
files: [ |
||||
|
'dist/**/*', |
||||
|
'electron/**/*' |
||||
|
], |
||||
|
mac: { |
||||
|
category: 'public.app-category.productivity', |
||||
|
target: [ |
||||
|
{ |
||||
|
target: 'dmg', |
||||
|
arch: ['x64', 'arm64'] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
win: { |
||||
|
target: [ |
||||
|
{ |
||||
|
target: 'nsis', |
||||
|
arch: ['x64'] |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
linux: { |
||||
|
target: [ |
||||
|
{ |
||||
|
target: 'AppImage', |
||||
|
arch: ['x64'] |
||||
|
} |
||||
|
], |
||||
|
category: 'Utility' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default config; |
@ -0,0 +1,32 @@ |
|||||
|
{ |
||||
|
"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" |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,119 @@ |
|||||
|
{ |
||||
|
"$schema": "http://json-schema.org/draft-07/schema#", |
||||
|
"title": "Capacitor Assets Configuration Schema", |
||||
|
"description": "Schema for validating capacitor-assets configuration files", |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"icon": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"source": { |
||||
|
"type": "string", |
||||
|
"pattern": "^resources/.*\\.(png|svg)$", |
||||
|
"description": "Source icon file path relative to project root" |
||||
|
}, |
||||
|
"android": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"adaptive": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"foreground": { |
||||
|
"type": "string", |
||||
|
"pattern": "^resources/.*\\.(png|svg)$", |
||||
|
"description": "Foreground icon for Android adaptive icons" |
||||
|
}, |
||||
|
"background": { |
||||
|
"type": ["string", "object"], |
||||
|
"description": "Background color or image for adaptive icons" |
||||
|
}, |
||||
|
"monochrome": { |
||||
|
"type": "string", |
||||
|
"pattern": "^resources/.*\\.(png|svg)$", |
||||
|
"description": "Monochrome icon for Android 13+" |
||||
|
} |
||||
|
}, |
||||
|
"required": ["foreground", "background"] |
||||
|
}, |
||||
|
"target": { |
||||
|
"type": "string", |
||||
|
"description": "Android target directory for generated icons" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"ios": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"padding": { |
||||
|
"type": "number", |
||||
|
"minimum": 0, |
||||
|
"maximum": 1, |
||||
|
"description": "Padding ratio for iOS icons" |
||||
|
}, |
||||
|
"target": { |
||||
|
"type": "string", |
||||
|
"description": "iOS target directory for generated icons" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"web": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"target": { |
||||
|
"type": "string", |
||||
|
"description": "Web target directory for generated icons" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"required": ["source"], |
||||
|
"additionalProperties": false |
||||
|
}, |
||||
|
"splash": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"source": { |
||||
|
"type": "string", |
||||
|
"pattern": "^resources/.*\\.(png|svg)$", |
||||
|
"description": "Source splash screen file" |
||||
|
}, |
||||
|
"darkSource": { |
||||
|
"type": "string", |
||||
|
"pattern": "^resources/.*\\.(png|svg)$", |
||||
|
"description": "Dark mode splash screen file" |
||||
|
}, |
||||
|
"android": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"scale": { |
||||
|
"type": "string", |
||||
|
"enum": ["cover", "contain", "fill"], |
||||
|
"description": "Android splash screen scaling mode" |
||||
|
}, |
||||
|
"target": { |
||||
|
"type": "string", |
||||
|
"description": "Android target directory for splash screens" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"ios": { |
||||
|
"type": "object", |
||||
|
"properties": { |
||||
|
"useStoryBoard": { |
||||
|
"type": "boolean", |
||||
|
"description": "Use LaunchScreen storyboard instead of splash assets" |
||||
|
}, |
||||
|
"target": { |
||||
|
"type": "string", |
||||
|
"description": "iOS target directory for splash screens" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"required": ["source"], |
||||
|
"additionalProperties": false |
||||
|
} |
||||
|
}, |
||||
|
"required": ["icon", "splash"], |
||||
|
"additionalProperties": false |
||||
|
} |
@ -0,0 +1,214 @@ |
|||||
|
# TimeSafari Asset Configuration Migration Plan |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Date**: 2025-08-14 |
||||
|
**Status**: 🎯 **IMPLEMENTATION** - Ready for Execution |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This document outlines the migration from the current mixed asset management |
||||
|
system to a standardized, single-source asset configuration approach using |
||||
|
`capacitor-assets` as the standard generator. |
||||
|
|
||||
|
## Current State Analysis |
||||
|
|
||||
|
### Asset Sources (Duplicated) |
||||
|
|
||||
|
- **`assets/` directory**: Contains `icon.png`, `splash.png`, `splash_dark.png` |
||||
|
- **`resources/` directory**: Contains identical files in platform-specific subdirectories |
||||
|
- **Result**: Duplicate storage, confusion about source of truth |
||||
|
|
||||
|
### Asset Generation (Manual) |
||||
|
|
||||
|
- **Custom scripts**: `generate-icons.sh`, `generate-ios-assets.sh`, `generate-android-icons.sh` |
||||
|
- **Bypass capacitor-assets**: Manual ImageMagick-based generation |
||||
|
- **Inconsistent outputs**: Different generation methods for each platform |
||||
|
|
||||
|
### Configuration (Scattered) |
||||
|
|
||||
|
- **`capacitor-assets.config.json`**: Basic configuration at root |
||||
|
- **Platform-specific configs**: Mixed in various build scripts |
||||
|
- **No validation**: No schema or consistency checks |
||||
|
|
||||
|
## Target State |
||||
|
|
||||
|
### Single Source of Truth |
||||
|
|
||||
|
- **`resources/` directory**: Capacitor default location for source assets |
||||
|
- **Eliminate duplication**: Remove `assets/` directory after migration |
||||
|
- **Standardized paths**: All tools read from `resources/` |
||||
|
|
||||
|
### Standardized Generation |
||||
|
|
||||
|
- **`capacitor-assets`**: Single tool for all platform asset generation |
||||
|
- **Build-time generation**: Assets generated during build, not committed |
||||
|
- **Deterministic outputs**: Same inputs → same outputs every time |
||||
|
|
||||
|
### Centralized Configuration |
||||
|
|
||||
|
- **`config/assets/`**: All asset-related configuration files |
||||
|
- **Schema validation**: JSON schema for configuration validation |
||||
|
- **CI safeguards**: Automated validation and compliance checks |
||||
|
|
||||
|
## Migration Steps |
||||
|
|
||||
|
### Phase 1: Foundation Setup ✅ |
||||
|
|
||||
|
- [x] Create `config/assets/` directory structure |
||||
|
- [x] Create asset configuration schema (`schema.json`) |
||||
|
- [x] Create enhanced capacitor-assets configuration |
||||
|
- [x] Convert `capacitor.config.json` to `capacitor.config.ts` |
||||
|
- [x] Pin Node.js version (`.nvmrc`, `.node-version`) |
||||
|
- [x] Create dev-time asset configuration generator |
||||
|
- [x] Create asset configuration validator |
||||
|
- [x] Add npm scripts for asset management |
||||
|
- [x] Update `.gitignore` with proper asset exclusions |
||||
|
- [x] Create CI workflow for asset validation |
||||
|
|
||||
|
### Phase 2: Validation & Testing |
||||
|
|
||||
|
- [ ] Run `npm run assets:config` to generate new configuration |
||||
|
- [ ] Run `npm run assets:validate` to verify configuration |
||||
|
- [ ] Test `npm run build:native` workflow |
||||
|
- [ ] Verify CI workflow passes all checks |
||||
|
- [ ] Confirm no platform assets are committed to VCS |
||||
|
|
||||
|
### Phase 3: Cleanup & Removal |
||||
|
|
||||
|
- [ ] Remove `assets/` directory (after validation) |
||||
|
- [ ] Remove manual asset generation scripts |
||||
|
- [ ] Remove asset checking scripts |
||||
|
- [ ] Update documentation references |
||||
|
- [ ] Final validation of clean state |
||||
|
|
||||
|
## Implementation Details |
||||
|
|
||||
|
### File Structure |
||||
|
|
||||
|
``` |
||||
|
resources/ # Image sources ONLY |
||||
|
icon.png |
||||
|
splash.png |
||||
|
splash_dark.png |
||||
|
|
||||
|
config/assets/ # Versioned config & schema |
||||
|
capacitor-assets.config.json |
||||
|
schema.json |
||||
|
|
||||
|
scripts/ |
||||
|
assets-config.js # Dev-time config generator |
||||
|
assets-validator.js # Schema validator |
||||
|
``` |
||||
|
|
||||
|
### Configuration Schema |
||||
|
|
||||
|
The schema enforces: |
||||
|
- Source files must be in `resources/` directory |
||||
|
- Required fields for icon and splash sections |
||||
|
- Android adaptive icon support (foreground/background/monochrome) |
||||
|
- iOS LaunchScreen preferences |
||||
|
- Target directory validation |
||||
|
|
||||
|
### CI Safeguards |
||||
|
|
||||
|
- **Schema validation**: Configuration must comply with schema |
||||
|
- **Source file validation**: All referenced files must exist |
||||
|
- **Platform asset denial**: Reject commits with generated assets |
||||
|
- **Clean tree enforcement**: Build must not modify committed configs |
||||
|
|
||||
|
## Testing Strategy |
||||
|
|
||||
|
### Local Validation |
||||
|
|
||||
|
```bash |
||||
|
# Generate configuration |
||||
|
npm run assets:config |
||||
|
|
||||
|
# Validate configuration |
||||
|
npm run assets:validate |
||||
|
|
||||
|
# Test build workflow |
||||
|
npm run build:native |
||||
|
|
||||
|
# Clean generated assets |
||||
|
npm run assets:clean |
||||
|
``` |
||||
|
|
||||
|
### CI Validation |
||||
|
|
||||
|
- **Asset validation workflow**: Runs on asset-related changes |
||||
|
- **Schema compliance**: Ensures configuration follows schema |
||||
|
- **Source file existence**: Verifies all referenced files exist |
||||
|
- **Platform asset detection**: Prevents committed generated assets |
||||
|
- **Build tree verification**: Ensures clean tree after build |
||||
|
|
||||
|
## Risk Mitigation |
||||
|
|
||||
|
### Data Loss Prevention |
||||
|
|
||||
|
- **Backup branch**: Create backup before removing `assets/` |
||||
|
- **Validation checks**: Multiple validation steps before removal |
||||
|
- **Gradual migration**: Phase-by-phase approach with rollback capability |
||||
|
|
||||
|
### Build Continuity |
||||
|
|
||||
|
- **Per-platform scripts unchanged**: All existing build orchestration preserved |
||||
|
- **Standard toolchain**: Uses capacitor-assets, not custom scripts |
||||
|
- **Fallback support**: Manual scripts remain until migration complete |
||||
|
|
||||
|
### Configuration Consistency |
||||
|
|
||||
|
- **Schema enforcement**: JSON schema prevents invalid configurations |
||||
|
- **CI validation**: Automated checks catch configuration issues |
||||
|
- **Documentation updates**: Clear guidance for future changes |
||||
|
|
||||
|
## Success Criteria |
||||
|
|
||||
|
### Technical Requirements |
||||
|
|
||||
|
- [ ] Single source of truth in `resources/` directory |
||||
|
- [ ] All platform assets generated via `capacitor-assets` |
||||
|
- [ ] No manual asset generation scripts |
||||
|
- [ ] Configuration validation passes all checks |
||||
|
- [ ] CI workflow enforces asset policies |
||||
|
|
||||
|
### Quality Metrics |
||||
|
|
||||
|
- [ ] Zero duplicate asset sources |
||||
|
- [ ] 100% configuration schema compliance |
||||
|
- [ ] No platform assets committed to VCS |
||||
|
- [ ] Clean build tree after asset generation |
||||
|
- [ ] Deterministic asset outputs |
||||
|
|
||||
|
### User Experience |
||||
|
|
||||
|
- [ ] Clear asset management documentation |
||||
|
- [ ] Simple development commands |
||||
|
- [ ] Consistent asset generation across platforms |
||||
|
- [ ] Reduced confusion about asset sources |
||||
|
|
||||
|
## Next Steps |
||||
|
|
||||
|
1. **Execute Phase 2**: Run validation and testing steps |
||||
|
2. **Verify CI workflow**: Ensure all checks pass |
||||
|
3. **Execute Phase 3**: Remove duplicate assets and scripts |
||||
|
4. **Update documentation**: Finalize README and BUILDING.md |
||||
|
5. **Team training**: Ensure all developers understand new workflow |
||||
|
|
||||
|
## Rollback Plan |
||||
|
|
||||
|
If issues arise during migration: |
||||
|
|
||||
|
1. **Restore backup branch**: `git checkout backup-before-asset-migration` |
||||
|
2. **Revert configuration changes**: Remove new config files |
||||
|
3. **Restore manual scripts**: Re-enable previous asset generation |
||||
|
4. **Investigate issues**: Identify and resolve root causes |
||||
|
5. **Plan revised migration**: Adjust approach based on lessons learned |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Status**: Ready for Phase 2 execution |
||||
|
**Priority**: High |
||||
|
**Estimated Effort**: 2-3 hours |
||||
|
**Dependencies**: CI workflow validation |
||||
|
**Stakeholders**: Development team |
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
@ -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 }; |
@ -0,0 +1,218 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
|
||||
|
/** |
||||
|
* TimeSafari Asset Configuration Validator |
||||
|
* Validates capacitor-assets configuration against schema and source files |
||||
|
* Author: Matthew Raymer |
||||
|
* |
||||
|
* Usage: node scripts/assets-validator.js [config-path] |
||||
|
*/ |
||||
|
|
||||
|
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); |
||||
|
|
||||
|
/** |
||||
|
* Load and parse JSON file |
||||
|
* @param {string} filePath - Path to JSON file |
||||
|
* @returns {Object} Parsed JSON object |
||||
|
*/ |
||||
|
function loadJsonFile(filePath) { |
||||
|
try { |
||||
|
const content = fs.readFileSync(filePath, 'utf8'); |
||||
|
return JSON.parse(content); |
||||
|
} catch (error) { |
||||
|
throw new Error(`Failed to load ${filePath}: ${error.message}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Validate configuration against schema |
||||
|
* @param {Object} config - Configuration object to validate |
||||
|
* @param {Object} schema - JSON schema for validation |
||||
|
* @returns {Array} Array of validation errors |
||||
|
*/ |
||||
|
function validateAgainstSchema(config, schema) { |
||||
|
const errors = []; |
||||
|
|
||||
|
// Basic structure validation
|
||||
|
if (!config.icon || !config.splash) { |
||||
|
errors.push('Configuration must contain both "icon" and "splash" sections'); |
||||
|
} |
||||
|
|
||||
|
// Icon validation
|
||||
|
if (config.icon) { |
||||
|
if (!config.icon.source) { |
||||
|
errors.push('Icon section must contain "source" field'); |
||||
|
} else if (!/^resources\/.*\.(png|svg)$/.test(config.icon.source)) { |
||||
|
errors.push('Icon source must be a PNG or SVG file in resources/ directory'); |
||||
|
} |
||||
|
|
||||
|
// Android adaptive icon validation
|
||||
|
if (config.icon.android?.adaptive) { |
||||
|
const adaptive = config.icon.android.adaptive; |
||||
|
if (!adaptive.foreground || !adaptive.background) { |
||||
|
errors.push('Android adaptive icon must have both foreground and background'); |
||||
|
} |
||||
|
if (adaptive.foreground && !/^resources\/.*\.(png|svg)$/.test(adaptive.foreground)) { |
||||
|
errors.push('Android adaptive foreground must be a PNG or SVG file in resources/ directory'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Splash validation
|
||||
|
if (config.splash) { |
||||
|
if (!config.splash.source) { |
||||
|
errors.push('Splash section must contain "source" field'); |
||||
|
} else if (!/^resources\/.*\.(png|svg)$/.test(config.splash.source)) { |
||||
|
errors.push('Splash source must be a PNG or SVG file in resources/ directory'); |
||||
|
} |
||||
|
|
||||
|
if (config.splash.darkSource && !/^resources\/.*\.(png|svg)$/.test(config.splash.darkSource)) { |
||||
|
errors.push('Dark splash source must be a PNG or SVG file in resources/ directory'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return errors; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Validate that source files exist |
||||
|
* @param {Object} config - Configuration object |
||||
|
* @returns {Array} Array of missing file errors |
||||
|
*/ |
||||
|
function validateSourceFiles(config) { |
||||
|
const errors = []; |
||||
|
const requiredFiles = new Set(); |
||||
|
|
||||
|
// Collect all required source files
|
||||
|
if (config.icon?.source) requiredFiles.add(config.icon.source); |
||||
|
if (config.icon?.android?.adaptive?.foreground) requiredFiles.add(config.icon.android.adaptive.foreground); |
||||
|
if (config.icon?.android?.adaptive?.monochrome) requiredFiles.add(config.icon.android.adaptive.monochrome); |
||||
|
if (config.splash?.source) requiredFiles.add(config.splash.source); |
||||
|
if (config.splash?.darkSource) requiredFiles.add(config.splash.darkSource); |
||||
|
|
||||
|
// Check each file exists
|
||||
|
requiredFiles.forEach(file => { |
||||
|
const filePath = path.join(PROJECT_ROOT, file); |
||||
|
if (!fs.existsSync(filePath)) { |
||||
|
errors.push(`Source file not found: ${file}`); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return errors; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Validate target directories are writable |
||||
|
* @param {Object} config - Configuration object |
||||
|
* @returns {Array} Array of directory validation errors |
||||
|
*/ |
||||
|
function validateTargetDirectories(config) { |
||||
|
const errors = []; |
||||
|
const targetDirs = new Set(); |
||||
|
|
||||
|
// Collect all target directories
|
||||
|
if (config.icon?.android?.target) targetDirs.add(config.icon.android.target); |
||||
|
if (config.icon?.ios?.target) targetDirs.add(config.icon.ios.target); |
||||
|
if (config.icon?.web?.target) targetDirs.add(config.icon.web.target); |
||||
|
if (config.splash?.android?.target) targetDirs.add(config.splash.android.target); |
||||
|
if (config.splash?.ios?.target) targetDirs.add(config.splash.ios.target); |
||||
|
|
||||
|
// Check each target directory
|
||||
|
targetDirs.forEach(dir => { |
||||
|
const dirPath = path.join(PROJECT_ROOT, dir); |
||||
|
const parentDir = path.dirname(dirPath); |
||||
|
|
||||
|
if (!fs.existsSync(parentDir)) { |
||||
|
errors.push(`Parent directory does not exist: ${parentDir}`); |
||||
|
} else if (!fs.statSync(parentDir).isDirectory()) { |
||||
|
errors.push(`Parent path is not a directory: ${parentDir}`); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return errors; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Main validation function |
||||
|
* @param {string} configPath - Path to configuration file |
||||
|
* @returns {boolean} True if validation passes |
||||
|
*/ |
||||
|
function validateConfiguration(configPath) { |
||||
|
console.log('🔍 Validating TimeSafari asset configuration...'); |
||||
|
console.log(`📁 Config file: ${configPath}`); |
||||
|
console.log(`📁 Project root: ${PROJECT_ROOT}`); |
||||
|
|
||||
|
try { |
||||
|
// Load configuration
|
||||
|
const config = loadJsonFile(configPath); |
||||
|
console.log('✅ Configuration file loaded successfully'); |
||||
|
|
||||
|
// Load schema
|
||||
|
const schemaPath = path.join(PROJECT_ROOT, 'config', 'assets', 'schema.json'); |
||||
|
const schema = loadJsonFile(schemaPath); |
||||
|
console.log('✅ Schema file loaded successfully'); |
||||
|
|
||||
|
// Perform validations
|
||||
|
const schemaErrors = validateAgainstSchema(config, schema); |
||||
|
const fileErrors = validateSourceFiles(config); |
||||
|
const dirErrors = validateTargetDirectories(config); |
||||
|
|
||||
|
// Report results
|
||||
|
const allErrors = [...schemaErrors, ...fileErrors, ...dirErrors]; |
||||
|
|
||||
|
if (allErrors.length === 0) { |
||||
|
console.log('🎉 All validations passed successfully!'); |
||||
|
console.log(''); |
||||
|
console.log('📋 Configuration summary:'); |
||||
|
console.log(` Icon source: ${config.icon?.source || 'NOT SET'}`); |
||||
|
console.log(` Splash source: ${config.splash?.source || 'NOT SET'}`); |
||||
|
console.log(` Dark splash: ${config.splash?.darkSource || 'NOT SET'}`); |
||||
|
console.log(` Android adaptive: ${config.icon?.android?.adaptive ? 'ENABLED' : 'DISABLED'}`); |
||||
|
console.log(` iOS LaunchScreen: ${config.splash?.ios?.useStoryBoard ? 'ENABLED' : 'DISABLED'}`); |
||||
|
return true; |
||||
|
} else { |
||||
|
console.error('❌ Validation failed with the following errors:'); |
||||
|
allErrors.forEach((error, index) => { |
||||
|
console.error(` ${index + 1}. ${error}`); |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('❌ Validation failed:', error.message); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Main execution function |
||||
|
*/ |
||||
|
function main() { |
||||
|
const configPath = process.argv[2] || path.join(PROJECT_ROOT, 'capacitor-assets.config.json'); |
||||
|
|
||||
|
if (!fs.existsSync(configPath)) { |
||||
|
console.error(`❌ Configuration file not found: ${configPath}`); |
||||
|
console.log(''); |
||||
|
console.log('💡 Available options:'); |
||||
|
console.log(' - Use default: capacitor-assets.config.json'); |
||||
|
console.log(' - Specify path: node scripts/assets-validator.js path/to/config.json'); |
||||
|
console.log(' - Generate config: npm run assets:config'); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
|
||||
|
const success = validateConfiguration(configPath); |
||||
|
process.exit(success ? 0 : 1); |
||||
|
} |
||||
|
|
||||
|
// Run if called directly
|
||||
|
if (import.meta.url === `file://${process.argv[1]}`) { |
||||
|
main(); |
||||
|
} |
||||
|
|
||||
|
export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories }; |
@ -1,159 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari Android Resource Check Script |
|
||||
# Checks for missing Android resources and automatically fixes common issues |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res" |
|
||||
ASSETS_DIR="$PROJECT_ROOT/assets" |
|
||||
|
|
||||
echo "=== TimeSafari Android Resource Check ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Checking Android resources" |
|
||||
|
|
||||
# Function to check if a file exists |
|
||||
check_file() { |
|
||||
local file="$1" |
|
||||
local description="$2" |
|
||||
if [ -f "$file" ]; then |
|
||||
echo "[✓] $description: $file" |
|
||||
return 0 |
|
||||
else |
|
||||
echo "[✗] $description: $file (MISSING)" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Function to check if a directory exists and has files |
|
||||
check_directory() { |
|
||||
local dir="$1" |
|
||||
local description="$2" |
|
||||
if [ -d "$dir" ] && [ "$(ls -A "$dir" 2>/dev/null)" ]; then |
|
||||
echo "[✓] $description: $dir" |
|
||||
return 0 |
|
||||
else |
|
||||
echo "[✗] $description: $dir (MISSING OR EMPTY)" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Track issues |
|
||||
issues_found=0 |
|
||||
fixes_applied=0 |
|
||||
|
|
||||
echo "[INFO] Checking splash screen resources..." |
|
||||
# Ensure drawable directory exists |
|
||||
if [ ! -d "$ANDROID_RES_DIR/drawable" ]; then |
|
||||
echo "[FIX] Creating drawable directory..." |
|
||||
mkdir -p "$ANDROID_RES_DIR/drawable" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check splash screen resources |
|
||||
if ! check_file "$ANDROID_RES_DIR/drawable/splash.png" "Splash screen (light)"; then |
|
||||
if [ -f "$ASSETS_DIR/splash.png" ]; then |
|
||||
echo "[FIX] Copying splash.png to Android resources..." |
|
||||
cp "$ASSETS_DIR/splash.png" "$ANDROID_RES_DIR/drawable/splash.png" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
else |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
if ! check_file "$ANDROID_RES_DIR/drawable/splash_dark.png" "Splash screen (dark)"; then |
|
||||
if [ -f "$ASSETS_DIR/splash_dark.png" ]; then |
|
||||
echo "[FIX] Copying splash_dark.png to Android resources..." |
|
||||
cp "$ASSETS_DIR/splash_dark.png" "$ANDROID_RES_DIR/drawable/splash_dark.png" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
else |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking launcher icon resources..." |
|
||||
# Ensure mipmap directories exist |
|
||||
mipmap_dirs=("mdpi" "hdpi" "xhdpi" "xxhdpi" "xxxhdpi" "anydpi-v26") |
|
||||
for dir in "${mipmap_dirs[@]}"; do |
|
||||
if [ ! -d "$ANDROID_RES_DIR/mipmap-$dir" ]; then |
|
||||
echo "[FIX] Creating mipmap-$dir directory..." |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-$dir" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
# Check launcher icon resources |
|
||||
required_icons=( |
|
||||
"mipmap-mdpi/ic_launcher.png" |
|
||||
"mipmap-hdpi/ic_launcher.png" |
|
||||
"mipmap-xhdpi/ic_launcher.png" |
|
||||
"mipmap-xxhdpi/ic_launcher.png" |
|
||||
"mipmap-xxxhdpi/ic_launcher.png" |
|
||||
"mipmap-anydpi-v26/ic_launcher.xml" |
|
||||
"mipmap-anydpi-v26/ic_launcher_round.xml" |
|
||||
) |
|
||||
|
|
||||
missing_icons=0 |
|
||||
for icon in "${required_icons[@]}"; do |
|
||||
if ! check_file "$ANDROID_RES_DIR/$icon" "Launcher icon: $icon"; then |
|
||||
missing_icons=$((missing_icons + 1)) |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
if [ $missing_icons -gt 0 ]; then |
|
||||
echo "[FIX] Missing launcher icons detected. Running icon generation script..." |
|
||||
if [ -f "$SCRIPT_DIR/generate-android-icons.sh" ]; then |
|
||||
"$SCRIPT_DIR/generate-android-icons.sh" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
else |
|
||||
echo "[ERROR] Icon generation script not found: $SCRIPT_DIR/generate-android-icons.sh" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking Capacitor platform status..." |
|
||||
# Check if Android platform is properly initialized |
|
||||
if [ ! -d "$PROJECT_ROOT/android" ]; then |
|
||||
echo "[ERROR] Android platform directory not found" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
elif [ ! -f "$PROJECT_ROOT/android/app/src/main/AndroidManifest.xml" ]; then |
|
||||
echo "[ERROR] AndroidManifest.xml not found - platform may be corrupted" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
else |
|
||||
echo "[✓] Android platform appears to be properly initialized" |
|
||||
fi |
|
||||
|
|
||||
# Check for common build issues |
|
||||
echo "[INFO] Checking for common build issues..." |
|
||||
|
|
||||
# Check for invalid resource names (dashes in filenames) |
|
||||
invalid_resources=$(find "$ANDROID_RES_DIR" -name "*-*" -type f 2>/dev/null | grep -E '\.(png|jpg|jpeg|gif|xml)$' || true) |
|
||||
if [ -n "$invalid_resources" ]; then |
|
||||
echo "[WARNING] Found resources with invalid names (containing dashes):" |
|
||||
echo "$invalid_resources" | while read -r file; do |
|
||||
echo " - $file" |
|
||||
done |
|
||||
echo "[INFO] Android resource names must contain only lowercase a-z, 0-9, or underscore" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Summary |
|
||||
echo "" |
|
||||
echo "=== Resource Check Summary ===" |
|
||||
if [ $issues_found -eq 0 ] && [ $fixes_applied -eq 0 ]; then |
|
||||
echo "[SUCCESS] All Android resources are present and valid" |
|
||||
exit 0 |
|
||||
elif [ $fixes_applied -gt 0 ]; then |
|
||||
echo "[SUCCESS] Fixed $fixes_applied resource issues automatically" |
|
||||
if [ $issues_found -gt 0 ]; then |
|
||||
echo "[WARNING] $issues_found issues remain that require manual attention" |
|
||||
exit 1 |
|
||||
else |
|
||||
exit 0 |
|
||||
fi |
|
||||
else |
|
||||
echo "[ERROR] Found $issues_found resource issues that require manual attention" |
|
||||
exit 1 |
|
||||
fi |
|
@ -1,294 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari iOS Resource Check Script |
|
||||
# Checks for missing iOS resources and automatically fixes common issues |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
IOS_ASSETS_DIR="$PROJECT_ROOT/ios/App/App/Assets.xcassets" |
|
||||
RESOURCES_DIR="$PROJECT_ROOT/resources/ios" |
|
||||
|
|
||||
echo "=== TimeSafari iOS Resource Check ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Checking iOS resources" |
|
||||
|
|
||||
# Function to check if a file exists |
|
||||
check_file() { |
|
||||
local file="$1" |
|
||||
local description="$2" |
|
||||
if [ -f "$file" ]; then |
|
||||
echo "[✓] $description: $file" |
|
||||
return 0 |
|
||||
else |
|
||||
echo "[✗] $description: $file (MISSING)" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Function to check if a directory exists and has files |
|
||||
check_directory() { |
|
||||
local dir="$1" |
|
||||
local description="$2" |
|
||||
if [ -d "$dir" ] && [ "$(ls -A "$dir" 2>/dev/null)" ]; then |
|
||||
echo "[✓] $description: $dir" |
|
||||
return 0 |
|
||||
else |
|
||||
echo "[✗] $description: $dir (MISSING OR EMPTY)" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Track issues |
|
||||
issues_found=0 |
|
||||
fixes_applied=0 |
|
||||
|
|
||||
echo "[INFO] Checking iOS asset catalog structure..." |
|
||||
# Check if Assets.xcassets directory exists |
|
||||
if ! check_directory "$IOS_ASSETS_DIR" "iOS Assets.xcassets directory"; then |
|
||||
echo "[FIX] Creating iOS Assets.xcassets directory..." |
|
||||
mkdir -p "$IOS_ASSETS_DIR" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check main Contents.json |
|
||||
if ! check_file "$IOS_ASSETS_DIR/Contents.json" "Main Assets.xcassets Contents.json"; then |
|
||||
echo "[FIX] Creating main Assets.xcassets Contents.json..." |
|
||||
cat > "$IOS_ASSETS_DIR/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"info" : { |
|
||||
"version" : 1, |
|
||||
"author" : "xcode" |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking App Icon resources..." |
|
||||
# Check App Icon directory |
|
||||
if ! check_directory "$IOS_ASSETS_DIR/AppIcon.appiconset" "App Icon directory"; then |
|
||||
echo "[FIX] Creating App Icon directory..." |
|
||||
mkdir -p "$IOS_ASSETS_DIR/AppIcon.appiconset" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check App Icon Contents.json |
|
||||
if ! check_file "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" "App Icon Contents.json"; then |
|
||||
echo "[FIX] Creating App Icon Contents.json..." |
|
||||
cat > "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "60x60" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "60x60" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "76x76" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "83.5x83.5" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "ios-marketing", |
|
||||
"scale" : "1x", |
|
||||
"size" : "1024x1024" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking Splash Screen resources..." |
|
||||
# Check Splash directory |
|
||||
if ! check_directory "$IOS_ASSETS_DIR/Splash.imageset" "Splash screen directory"; then |
|
||||
echo "[FIX] Creating Splash screen directory..." |
|
||||
mkdir -p "$IOS_ASSETS_DIR/Splash.imageset" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check Splash Contents.json |
|
||||
if ! check_file "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" "Splash screen Contents.json"; then |
|
||||
echo "[FIX] Creating Splash screen Contents.json..." |
|
||||
cat > "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "1x" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "2x" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "3x" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check SplashDark directory |
|
||||
if ! check_directory "$IOS_ASSETS_DIR/SplashDark.imageset" "Dark splash screen directory"; then |
|
||||
echo "[FIX] Creating Dark splash screen directory..." |
|
||||
mkdir -p "$IOS_ASSETS_DIR/SplashDark.imageset" |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
# Check SplashDark Contents.json |
|
||||
if ! check_file "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" "Dark splash screen Contents.json"; then |
|
||||
echo "[FIX] Creating Dark splash screen Contents.json..." |
|
||||
cat > "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "1x" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "2x" |
|
||||
}, |
|
||||
{ |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "3x" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
fixes_applied=$((fixes_applied + 1)) |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking source resource files..." |
|
||||
# Check if source resources exist |
|
||||
if ! check_file "$RESOURCES_DIR/icon/icon.png" "iOS icon source"; then |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
|
|
||||
if ! check_file "$RESOURCES_DIR/splash/splash.png" "iOS splash source"; then |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
|
|
||||
if ! check_file "$RESOURCES_DIR/splash/splash_dark.png" "iOS dark splash source"; then |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Checking iOS platform status..." |
|
||||
# Check if iOS platform is properly initialized |
|
||||
if [ ! -d "$PROJECT_ROOT/ios" ]; then |
|
||||
echo "[ERROR] iOS platform directory not found" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
elif [ ! -f "$PROJECT_ROOT/ios/App/App/Info.plist" ]; then |
|
||||
echo "[ERROR] Info.plist not found - platform may be corrupted" |
|
||||
issues_found=$((issues_found + 1)) |
|
||||
else |
|
||||
echo "[✓] iOS platform appears to be properly initialized" |
|
||||
fi |
|
||||
|
|
||||
# Summary |
|
||||
echo "" |
|
||||
echo "=== iOS Resource Check Summary ===" |
|
||||
if [ $issues_found -eq 0 ] && [ $fixes_applied -eq 0 ]; then |
|
||||
echo "[SUCCESS] All iOS resources are present and valid" |
|
||||
exit 0 |
|
||||
elif [ $fixes_applied -gt 0 ]; then |
|
||||
echo "[SUCCESS] Fixed $fixes_applied resource issues automatically" |
|
||||
if [ $issues_found -gt 0 ]; then |
|
||||
echo "[WARNING] $issues_found issues remain that require manual attention" |
|
||||
echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux" |
|
||||
exit 1 |
|
||||
else |
|
||||
exit 0 |
|
||||
fi |
|
||||
else |
|
||||
echo "[ERROR] Found $issues_found resource issues that require manual attention" |
|
||||
echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux" |
|
||||
exit 1 |
|
||||
fi |
|
@ -1,107 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari Android Icon Generation Script |
|
||||
# Generates all required Android launcher icon sizes from assets/icon.png |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
ASSETS_DIR="$PROJECT_ROOT/assets" |
|
||||
ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res" |
|
||||
|
|
||||
echo "=== TimeSafari Android Icon Generation ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting Android icon generation" |
|
||||
|
|
||||
# Check if source icon exists |
|
||||
if [ ! -f "$ASSETS_DIR/icon.png" ]; then |
|
||||
echo "[ERROR] Source icon not found: $ASSETS_DIR/icon.png" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Check if ImageMagick is available and determine the correct command |
|
||||
IMAGEMAGICK_CMD="" |
|
||||
if command -v magick &> /dev/null; then |
|
||||
IMAGEMAGICK_CMD="magick" |
|
||||
echo "[INFO] Using ImageMagick v7+ (magick command)" |
|
||||
elif command -v convert &> /dev/null; then |
|
||||
IMAGEMAGICK_CMD="convert" |
|
||||
echo "[INFO] Using ImageMagick v6 (convert command)" |
|
||||
else |
|
||||
echo "[ERROR] ImageMagick not found. Please install ImageMagick." |
|
||||
echo " Arch: sudo pacman -S imagemagick" |
|
||||
echo " Ubuntu: sudo apt-get install imagemagick" |
|
||||
echo " macOS: brew install imagemagick" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Create mipmap directories if they don't exist |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-hdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-mdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xhdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxhdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxxhdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-anydpi-v26" |
|
||||
|
|
||||
echo "[INFO] Generating launcher icons..." |
|
||||
|
|
||||
# Function to resize image using the appropriate ImageMagick command |
|
||||
resize_image() { |
|
||||
local input="$1" |
|
||||
local output="$2" |
|
||||
local size="$3" |
|
||||
|
|
||||
if [ "$IMAGEMAGICK_CMD" = "magick" ]; then |
|
||||
# ImageMagick v7+ syntax |
|
||||
magick "$input" -resize "${size}x${size}" "$output" |
|
||||
else |
|
||||
# ImageMagick v6 syntax |
|
||||
convert "$input" -resize "${size}x${size}" "$output" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Generate launcher icons for different densities |
|
||||
# Android launcher icon sizes: mdpi=48, hdpi=72, xhdpi=96, xxhdpi=144, xxxhdpi=192 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" 48 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" 72 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" 96 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" 144 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" 192 |
|
||||
|
|
||||
# Generate round launcher icons |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher_round.png" 48 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher_round.png" 72 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher_round.png" 96 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png" 144 |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png" 192 |
|
||||
|
|
||||
# Create simple launcher XML files (no adaptive icons for now) |
|
||||
cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher.xml" << 'EOF' |
|
||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
<background android:drawable="@android:color/white"/> |
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> |
|
||||
</adaptive-icon> |
|
||||
EOF |
|
||||
|
|
||||
cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_round.xml" << 'EOF' |
|
||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
<background android:drawable="@android:color/white"/> |
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> |
|
||||
</adaptive-icon> |
|
||||
EOF |
|
||||
|
|
||||
# Create foreground mipmap files for adaptive icons |
|
||||
resize_image "$ASSETS_DIR/icon.png" "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_foreground.png" 108 |
|
||||
|
|
||||
echo "[SUCCESS] Generated Android launcher icons:" |
|
||||
echo " - mipmap-mdpi/ic_launcher.png (48x48)" |
|
||||
echo " - mipmap-hdpi/ic_launcher.png (72x72)" |
|
||||
echo " - mipmap-xhdpi/ic_launcher.png (96x96)" |
|
||||
echo " - mipmap-xxhdpi/ic_launcher.png (144x144)" |
|
||||
echo " - mipmap-xxxhdpi/ic_launcher.png (192x192)" |
|
||||
echo " - mipmap-anydpi-v26/ic_launcher_foreground.png (108x108)" |
|
||||
echo " - Updated mipmap-anydpi-v26 XML files" |
|
||||
echo "[SUCCESS] Android icon generation completed successfully!" |
|
@ -1,79 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari Android Icon Generation Script (Placeholder Icons) |
|
||||
# Generates placeholder Android launcher icons with "TS" text |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
ANDROID_RES_DIR="$PROJECT_ROOT/android/app/src/main/res" |
|
||||
|
|
||||
echo "=== TimeSafari Android Placeholder Icon Generation ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting Android placeholder icon generation" |
|
||||
|
|
||||
# Check if ImageMagick is available and determine the correct command |
|
||||
IMAGEMAGICK_CMD="" |
|
||||
if command -v magick &> /dev/null; then |
|
||||
IMAGEMAGICK_CMD="magick" |
|
||||
echo "[INFO] Using ImageMagick v7+ (magick command)" |
|
||||
elif command -v convert &> /dev/null; then |
|
||||
IMAGEMAGICK_CMD="convert" |
|
||||
echo "[INFO] Using ImageMagick v6 (convert command)" |
|
||||
else |
|
||||
echo "[ERROR] ImageMagick not found. Please install ImageMagick." |
|
||||
echo " Arch: sudo pacman -S imagemagick" |
|
||||
echo " Ubuntu: sudo apt-get install imagemagick" |
|
||||
echo " macOS: brew install imagemagick" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Create directories if they don't exist |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-mdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-hdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xhdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxhdpi" |
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxxhdpi" |
|
||||
|
|
||||
# Function to generate placeholder icon using the appropriate ImageMagick command |
|
||||
generate_placeholder_icon() { |
|
||||
local size="$1" |
|
||||
local output="$2" |
|
||||
local pointsize="$3" |
|
||||
|
|
||||
if [ "$IMAGEMAGICK_CMD" = "magick" ]; then |
|
||||
# ImageMagick v7+ syntax |
|
||||
magick -size "${size}x${size}" xc:blue -gravity center -pointsize "$pointsize" -fill white -annotate 0 "TS" "$output" |
|
||||
else |
|
||||
# ImageMagick v6 syntax |
|
||||
convert -size "${size}x${size}" xc:blue -gravity center -pointsize "$pointsize" -fill white -annotate 0 "TS" "$output" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
echo "[INFO] Generating placeholder launcher icons..." |
|
||||
|
|
||||
# Generate placeholder icons using ImageMagick |
|
||||
generate_placeholder_icon 48 "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" 20 |
|
||||
generate_placeholder_icon 72 "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" 30 |
|
||||
generate_placeholder_icon 96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" 40 |
|
||||
generate_placeholder_icon 144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" 60 |
|
||||
generate_placeholder_icon 192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" 80 |
|
||||
|
|
||||
echo "[INFO] Copying to round versions..." |
|
||||
|
|
||||
# Copy to round versions |
|
||||
cp "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher_round.png" |
|
||||
cp "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher_round.png" |
|
||||
cp "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher_round.png" |
|
||||
cp "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png" |
|
||||
cp "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png" |
|
||||
|
|
||||
echo "[SUCCESS] Generated Android placeholder launcher icons:" |
|
||||
echo " - mipmap-mdpi/ic_launcher.png (48x48)" |
|
||||
echo " - mipmap-hdpi/ic_launcher.png (72x72)" |
|
||||
echo " - mipmap-xhdpi/ic_launcher.png (96x96)" |
|
||||
echo " - mipmap-xxhdpi/ic_launcher.png (144x144)" |
|
||||
echo " - mipmap-xxxhdpi/ic_launcher.png (192x192)" |
|
||||
echo " - All round versions created" |
|
||||
echo "[SUCCESS] Android placeholder icon generation completed successfully!" |
|
@ -1,253 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari iOS Asset Generation Script |
|
||||
# Manually generates iOS assets using ImageMagick when capacitor-assets fails |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
IOS_ASSETS_DIR="$PROJECT_ROOT/ios/App/App/Assets.xcassets" |
|
||||
RESOURCES_DIR="$PROJECT_ROOT/resources/ios" |
|
||||
|
|
||||
echo "=== TimeSafari iOS Asset Generation ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Generating iOS assets manually" |
|
||||
|
|
||||
# Check if ImageMagick is available |
|
||||
if ! command -v convert &> /dev/null; then |
|
||||
echo "[ERROR] ImageMagick 'convert' command not found. Please install ImageMagick." |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Check if source files exist |
|
||||
if [ ! -f "$RESOURCES_DIR/icon/icon.png" ]; then |
|
||||
echo "[ERROR] Source icon not found: $RESOURCES_DIR/icon/icon.png" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
if [ ! -f "$RESOURCES_DIR/splash/splash.png" ]; then |
|
||||
echo "[ERROR] Source splash not found: $RESOURCES_DIR/splash/splash.png" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
if [ ! -f "$RESOURCES_DIR/splash/splash_dark.png" ]; then |
|
||||
echo "[ERROR] Source dark splash not found: $RESOURCES_DIR/splash/splash_dark.png" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
echo "[INFO] Generating iOS app icons..." |
|
||||
|
|
||||
# Generate app icons for different sizes |
|
||||
# iPhone icons |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 60x60 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@3x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 58x58 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 87x87 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@3x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 80x80 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 120x120 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@3x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 120x120 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-60@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 180x180 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-60@3x.png" |
|
||||
|
|
||||
# iPad icons |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 20x20 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@1x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-20@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 29x29 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@1x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 58x58 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-29@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 40x40 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@1x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 80x80 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-40@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 152x152 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-76@2x.png" |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 167x167 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-83.5@2x.png" |
|
||||
|
|
||||
# App Store icon |
|
||||
convert "$RESOURCES_DIR/icon/icon.png" -resize 1024x1024 "$IOS_ASSETS_DIR/AppIcon.appiconset/AppIcon-1024@1x.png" |
|
||||
|
|
||||
echo "[INFO] Generating iOS splash screens..." |
|
||||
|
|
||||
# Generate splash screens for different scales |
|
||||
convert "$RESOURCES_DIR/splash/splash.png" -resize 320x480 "$IOS_ASSETS_DIR/Splash.imageset/splash@1x.png" |
|
||||
convert "$RESOURCES_DIR/splash/splash.png" -resize 640x960 "$IOS_ASSETS_DIR/Splash.imageset/splash@2x.png" |
|
||||
convert "$RESOURCES_DIR/splash/splash.png" -resize 960x1440 "$IOS_ASSETS_DIR/Splash.imageset/splash@3x.png" |
|
||||
|
|
||||
# Generate dark splash screens |
|
||||
convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 320x480 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@1x.png" |
|
||||
convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 640x960 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@2x.png" |
|
||||
convert "$RESOURCES_DIR/splash/splash_dark.png" -resize 960x1440 "$IOS_ASSETS_DIR/SplashDark.imageset/splash@3x.png" |
|
||||
|
|
||||
echo "[INFO] Updating Contents.json files to reference generated images..." |
|
||||
|
|
||||
# Update AppIcon Contents.json to reference the generated files |
|
||||
cat > "$IOS_ASSETS_DIR/AppIcon.appiconset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"filename" : "AppIcon-20@2x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-20@3x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-29@2x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-29@3x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-40@2x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-40@3x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-60@2x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "2x", |
|
||||
"size" : "60x60" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-60@3x.png", |
|
||||
"idiom" : "iphone", |
|
||||
"scale" : "3x", |
|
||||
"size" : "60x60" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-20@1x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-20@2x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "20x20" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-29@1x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-29@2x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "29x29" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-40@1x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "1x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-40@2x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "40x40" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-76@2x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "76x76" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-83.5@2x.png", |
|
||||
"idiom" : "ipad", |
|
||||
"scale" : "2x", |
|
||||
"size" : "83.5x83.5" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "AppIcon-1024@1x.png", |
|
||||
"idiom" : "ios-marketing", |
|
||||
"scale" : "1x", |
|
||||
"size" : "1024x1024" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
|
|
||||
# Update Splash Contents.json to reference the generated files |
|
||||
cat > "$IOS_ASSETS_DIR/Splash.imageset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"filename" : "splash@1x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "1x" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "splash@2x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "2x" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "splash@3x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "3x" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
|
|
||||
# Update SplashDark Contents.json to reference the generated files |
|
||||
cat > "$IOS_ASSETS_DIR/SplashDark.imageset/Contents.json" << 'EOF' |
|
||||
{ |
|
||||
"images" : [ |
|
||||
{ |
|
||||
"filename" : "splash@1x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "1x" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "splash@2x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "2x" |
|
||||
}, |
|
||||
{ |
|
||||
"filename" : "splash@3x.png", |
|
||||
"idiom" : "universal", |
|
||||
"scale" : "3x" |
|
||||
} |
|
||||
], |
|
||||
"info" : { |
|
||||
"author" : "xcode", |
|
||||
"version" : 1 |
|
||||
} |
|
||||
} |
|
||||
EOF |
|
||||
|
|
||||
echo "[SUCCESS] iOS assets generated successfully!" |
|
||||
echo "[INFO] Generated files:" |
|
||||
find "$IOS_ASSETS_DIR" -name "*.png" | sort |
|
||||
|
|
||||
echo "" |
|
||||
echo "[NOTE] iOS builds require macOS with Xcode - cannot build on Linux" |
|
||||
echo "[INFO] Assets are now ready for when you build on macOS" |
|
@ -1,67 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# TimeSafari Generated Assets Purge Script |
|
||||
# Removes generated Android assets and resources from git history |
|
||||
# Author: Matthew Raymer |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" |
|
||||
|
|
||||
echo "=== TimeSafari Generated Assets Purge ===" |
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] Starting git history cleanup" |
|
||||
|
|
||||
# Check if we're in a git repository |
|
||||
if [ ! -d ".git" ]; then |
|
||||
echo "[ERROR] Not in a git repository. Please run this script from the project root." |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Check if we have uncommitted changes |
|
||||
if [ -n "$(git status --porcelain)" ]; then |
|
||||
echo "[ERROR] You have uncommitted changes. Please commit or stash them first." |
|
||||
echo "Current status:" |
|
||||
git status --short |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# Create backup branch |
|
||||
BACKUP_BRANCH="backup-before-asset-purge-$(date +%Y%m%d-%H%M%S)" |
|
||||
echo "[INFO] Creating backup branch: $BACKUP_BRANCH" |
|
||||
git branch "$BACKUP_BRANCH" |
|
||||
|
|
||||
echo "[INFO] Starting git filter-branch to remove generated assets..." |
|
||||
|
|
||||
# Use git filter-branch to remove the directories from history |
|
||||
git filter-branch --force --index-filter ' |
|
||||
# Remove generated Android assets directory |
|
||||
git rm -rf --cached --ignore-unmatch android/app/src/main/assets/public/ 2>/dev/null || true |
|
||||
|
|
||||
# Remove generated Android resources (but keep config files) |
|
||||
git rm -rf --cached --ignore-unmatch android/app/src/main/res/drawable*/ 2>/dev/null || true |
|
||||
git rm -rf --cached --ignore-unmatch android/app/src/main/res/mipmap*/ 2>/dev/null || true |
|
||||
git rm -rf --cached --ignore-unmatch android/app/src/main/res/values/ic_launcher_background.xml 2>/dev/null || true |
|
||||
|
|
||||
# Keep configuration files |
|
||||
git add android/app/src/main/res/values/strings.xml 2>/dev/null || true |
|
||||
git add android/app/src/main/res/values/styles.xml 2>/dev/null || true |
|
||||
git add android/app/src/main/res/layout/activity_main.xml 2>/dev/null || true |
|
||||
git add android/app/src/main/res/xml/config.xml 2>/dev/null || true |
|
||||
git add android/app/src/main/res/xml/file_paths.xml 2>/dev/null || true |
|
||||
' --prune-empty --tag-name-filter cat -- --all |
|
||||
|
|
||||
echo "[INFO] Cleaning up git filter-branch temporary files..." |
|
||||
rm -rf .git/refs/original/ |
|
||||
git reflog expire --expire=now --all |
|
||||
git gc --prune=now --aggressive |
|
||||
|
|
||||
echo "[SUCCESS] Generated assets purged from git history!" |
|
||||
echo "[INFO] Backup branch created: $BACKUP_BRANCH" |
|
||||
echo "[INFO] Repository size should be significantly reduced" |
|
||||
echo "" |
|
||||
echo "Next steps:" |
|
||||
echo "1. Test that the repository works correctly" |
|
||||
echo "2. Force push to remote: git push --force-with-lease origin <branch>" |
|
||||
echo "3. Inform team members to re-clone or reset their local repositories" |
|
||||
echo "4. Delete backup branch when confident: git branch -D $BACKUP_BRANCH" |
|
Loading…
Reference in new issue