From 6007bc34e435b1f0db8fb13f074c8fbe5c812804 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 30 Jul 2025 12:47:55 +0000 Subject: [PATCH 01/17] refactor: centralize QR navigation logic and add export prompt after contact addition - Create QRNavigationService to handle platform-specific QR routing - Remove direct Capacitor imports from ContactsView, ProjectsView, HelpView - Replace duplicated QR routing logic with centralized service calls - Update HelpView template to use platform service methods (isCapacitor, capabilities) - Add export data prompt after successfully adding a contact - Add NOTIFY_EXPORT_DATA_PROMPT notification constant - Implement exportContactData() method with platform service integration - Fix TypeScript compatibility for Vue Router route parameters - Maintain consistent QR navigation behavior across all views Eliminates code duplication and improves platform abstraction by using PlatformService instead of direct Capacitor references. Enhances user experience with automatic export prompts for data backup. --- src/constants/notifications.ts | 6 ++ src/services/QRNavigationService.ts | 99 +++++++++++++++++++++++++++++ src/views/ContactsView.vue | 80 ++++++++++++++++++++--- src/views/HelpView.vue | 22 +++---- src/views/ProjectsView.vue | 12 ++-- 5 files changed, 194 insertions(+), 25 deletions(-) create mode 100644 src/services/QRNavigationService.ts diff --git a/src/constants/notifications.ts b/src/constants/notifications.ts index 9ffeb31f..8eb1c06f 100644 --- a/src/constants/notifications.ts +++ b/src/constants/notifications.ts @@ -846,6 +846,12 @@ export const NOTIFY_CONTACTS_ADDED = { message: "They were added.", }; +// Used in: ContactsView.vue (addContact method - export data prompt after contact addition) +export const NOTIFY_EXPORT_DATA_PROMPT = { + title: "Export Your Data", + message: "Would you like to export your contact data as a backup?", +}; + // Used in: ContactsView.vue (showCopySelectionsInfo method - info about copying contacts) export const NOTIFY_CONTACT_INFO_COPY = { title: "Info", diff --git a/src/services/QRNavigationService.ts b/src/services/QRNavigationService.ts new file mode 100644 index 00000000..33716c39 --- /dev/null +++ b/src/services/QRNavigationService.ts @@ -0,0 +1,99 @@ +import { PlatformServiceFactory } from "./PlatformServiceFactory"; +import { PlatformService } from "./PlatformService"; +import { logger } from "@/utils/logger"; + +/** + * QR Navigation Service + * + * Handles platform-specific routing logic for QR scanning operations. + * Removes coupling between views and routing logic by centralizing + * navigation decisions based on platform capabilities. + * + * @author Matthew Raymer + */ +export class QRNavigationService { + private static instance: QRNavigationService | null = null; + private platformService: PlatformService; + + private constructor() { + this.platformService = PlatformServiceFactory.getInstance(); + } + + /** + * Get singleton instance of QRNavigationService + */ + public static getInstance(): QRNavigationService { + if (!QRNavigationService.instance) { + QRNavigationService.instance = new QRNavigationService(); + } + return QRNavigationService.instance; + } + + /** + * Get the appropriate QR scanner route based on platform + * + * @returns Object with route name and parameters for QR scanning + */ + public getQRScannerRoute(): { + name: string; + params?: Record; + } { + const isCapacitor = this.platformService.isCapacitor(); + + logger.debug("QR Navigation - Platform detection:", { + isCapacitor, + platform: this.platformService.getCapabilities(), + }); + + if (isCapacitor) { + // Use native scanner on mobile platforms + return { name: "contact-qr-scan-full" }; + } else { + // Use web scanner on other platforms + return { name: "contact-qr" }; + } + } + + /** + * Get the appropriate QR display route based on platform + * + * @returns Object with route name and parameters for QR display + */ + public getQRDisplayRoute(): { + name: string; + params?: Record; + } { + const isCapacitor = this.platformService.isCapacitor(); + + logger.debug("QR Navigation - Display route detection:", { + isCapacitor, + platform: this.platformService.getCapabilities(), + }); + + if (isCapacitor) { + // Use dedicated display view on mobile + return { name: "contact-qr-scan-show" }; + } else { + // Use combined view on web + return { name: "contact-qr" }; + } + } + + /** + * Check if native QR scanning is available on current platform + * + * @returns true if native scanning is available, false otherwise + */ + public isNativeScanningAvailable(): boolean { + return this.platformService.isCapacitor(); + } + + /** + * Get platform capabilities for QR operations + * + * @returns Platform capabilities object + */ + public getPlatformCapabilities() { + return this.platformService.getCapabilities(); + } +} diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 3d5576e7..a6533201 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -127,7 +127,7 @@ import * as R from "ramda"; import { Component, Vue } from "vue-facing-decorator"; import { RouteLocationNormalizedLoaded, Router } from "vue-router"; import { useClipboard } from "@vueuse/core"; -import { Capacitor } from "@capacitor/core"; +// Capacitor import removed - using PlatformService instead import QuickNav from "../components/QuickNav.vue"; import EntityIcon from "../components/EntityIcon.vue"; @@ -161,13 +161,17 @@ import { GiveSummaryRecord } from "@/interfaces/records"; import { UserInfo } from "@/interfaces/common"; import { VerifiableCredential } from "@/interfaces/claims-result"; import * as libsUtil from "../libs/util"; -import { generateSaveAndActivateIdentity } from "../libs/util"; +import { + generateSaveAndActivateIdentity, + contactsToExportJson, +} from "../libs/util"; import { logger } from "../utils/logger"; // No longer needed - using PlatformServiceMixin methods // import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; import { APP_SERVER } from "@/constants/app"; +import { QRNavigationService } from "@/services/QRNavigationService"; import { NOTIFY_CONTACT_NO_INFO, NOTIFY_CONTACTS_ADD_ERROR, @@ -193,6 +197,7 @@ import { NOTIFY_REGISTRATION_ERROR_FALLBACK, NOTIFY_REGISTRATION_ERROR_GENERIC, NOTIFY_VISIBILITY_ERROR_FALLBACK, + NOTIFY_EXPORT_DATA_PROMPT, getRegisterPersonSuccessMessage, getVisibilitySuccessMessage, getGivesRetrievalErrorMessage, @@ -780,6 +785,9 @@ export default class ContactsView extends Vue { // Show success notification this.notify.success(addedMessage); + + // Show export data prompt after successful contact addition + await this.showExportDataPrompt(); } catch (err) { this.handleContactAddError(err); } @@ -1243,19 +1251,75 @@ export default class ContactsView extends Vue { /** * Handle QR code button click - route to appropriate scanner - * Uses native scanner on mobile platforms, web scanner otherwise + * Uses QRNavigationService to determine scanner type and route */ - public handleQRCodeClick() { this.$logAndConsole( "[ContactsView] handleQRCodeClick method called", false, ); - if (Capacitor.isNativePlatform()) { - this.$router.push({ name: "contact-qr-scan-full" }); - } else { - this.$router.push({ name: "contact-qr" }); + const qrNavigationService = QRNavigationService.getInstance(); + const route = qrNavigationService.getQRScannerRoute(); + + this.$router.push(route); + } + + /** + * Show export data prompt after adding a contact + * Prompts user to export their contact data as a backup + */ + private async showExportDataPrompt(): Promise { + setTimeout(() => { + this.$notify( + { + group: "modal", + type: "confirm", + title: NOTIFY_EXPORT_DATA_PROMPT.title, + text: NOTIFY_EXPORT_DATA_PROMPT.message, + onYes: async () => { + await this.exportContactData(); + }, + yesText: "Export Data", + onNo: async () => { + // User chose not to export - no action needed + }, + noText: "Not Now", + }, + -1, + ); + }, 1000); // Small delay to ensure success notification is shown first + } + + /** + * Export contact data to JSON file + * Uses platform service to handle platform-specific export logic + */ + private async exportContactData(): Promise { + try { + // Fetch all contacts from database + const allContacts = await this.$contacts(); + + // Convert contacts to export format + const exportData = contactsToExportJson(allContacts); + const jsonStr = JSON.stringify(exportData, null, 2); + + // Generate filename with current date + const today = new Date(); + const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format + const fileName = `timesafari-backup-contacts-${dateString}.json`; + + // Use platform service to handle export + await this.platformService.writeAndShareFile(fileName, jsonStr); + + this.notify.success( + "Contact export completed successfully. Check your downloads or share dialog.", + ); + } catch (error) { + logger.error("Export Error:", error); + this.notify.error( + `There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`, + ); } } } diff --git a/src/views/HelpView.vue b/src/views/HelpView.vue index 11505ba3..9b05b7aa 100644 --- a/src/views/HelpView.vue +++ b/src/views/HelpView.vue @@ -565,22 +565,22 @@

What app version is this?

{{ package.version }} ({{ commitHash }})

-
+

Do I have the latest version?

-

+

Check the App Store.

-

+

Check the Play Store.

- Sorry, your platform of '{{ Capacitor.getPlatform() }}' is not recognized. + Sorry, your platform is not recognized.

@@ -592,12 +592,13 @@ import { Component, Vue } from "vue-facing-decorator"; import { Router } from "vue-router"; import { useClipboard } from "@vueuse/core"; -import { Capacitor } from "@capacitor/core"; +// Capacitor import removed - using QRNavigationService instead import * as Package from "../../package.json"; import QuickNav from "../components/QuickNav.vue"; import { APP_SERVER } from "../constants/app"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; +import { QRNavigationService } from "@/services/QRNavigationService"; /** * HelpView.vue - Comprehensive Help System Component @@ -643,7 +644,7 @@ export default class HelpView extends Vue { showVerifiable = false; APP_SERVER = APP_SERVER; - Capacitor = Capacitor; + // Capacitor reference removed - using QRNavigationService instead // Ideally, we put no functionality in here, especially in the setup, // because we never want this page to have a chance of throwing an error. @@ -711,11 +712,10 @@ export default class HelpView extends Vue { * @private */ private handleQRCodeClick(): void { - if (Capacitor.isNativePlatform()) { - this.$router.push({ name: "contact-qr-scan-full" }); - } else { - this.$router.push({ name: "contact-qr" }); - } + const qrNavigationService = QRNavigationService.getInstance(); + const route = qrNavigationService.getQRScannerRoute(); + + this.$router.push(route); } /** diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 84db4f02..b2f2b7f4 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -264,7 +264,7 @@ import { AxiosRequestConfig } from "axios"; import { Component, Vue } from "vue-facing-decorator"; import { Router } from "vue-router"; -import { Capacitor } from "@capacitor/core"; +// Capacitor import removed - using QRNavigationService instead import { NotificationIface } from "../constants/app"; import EntityIcon from "../components/EntityIcon.vue"; @@ -281,6 +281,7 @@ import { OnboardPage, iconForUnitCode } from "../libs/util"; import { logger } from "../utils/logger"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; +import { QRNavigationService } from "@/services/QRNavigationService"; import { NOTIFY_NO_ACCOUNT_ERROR, NOTIFY_PROJECT_LOAD_ERROR, @@ -755,11 +756,10 @@ export default class ProjectsView extends Vue { * - Web-based QR interface for browser environments */ private handleQRCodeClick() { - if (Capacitor.isNativePlatform()) { - this.$router.push({ name: "contact-qr-scan-full" }); - } else { - this.$router.push({ name: "contact-qr" }); - } + const qrNavigationService = QRNavigationService.getInstance(); + const route = qrNavigationService.getQRScannerRoute(); + + this.$router.push(route); } /** From ea6757c69635560d99f603536978389b8b9dc887 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 13 Aug 2025 02:29:49 +0000 Subject: [PATCH 02/17] fix(android): resolve icon generation and build script errors Fixed Android build issues that were preventing successful builds: - Updated icon generation script to create proper adaptive icons without referencing missing drawable files - Changed log_warning function call to log_warn in build script - Icon generation now creates foreground mipmap files and proper XML configs - Build process successfully generates all required Android assets Resolves "export: production).=: not a valid identifier" error and enables successful Android builds with automatic resource generation. https://app.clickup.com/t/86b5uau17 --- scripts/build-android.sh | 2 +- scripts/generate-android-icons.sh | 31 ++++++++++--------------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 53ada97e..0f139be1 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -256,7 +256,7 @@ fi # Step 1: Check and fix Android resources safe_execute "Checking Android resources" "$(dirname "$0")/check-android-resources.sh" || { - log_warning "Resource check found issues, but continuing with build..." + log_warn "Resource check found issues, but continuing with build..." } # Step 2: Clean Android app diff --git a/scripts/generate-android-icons.sh b/scripts/generate-android-icons.sh index 432f2428..b2279a98 100755 --- a/scripts/generate-android-icons.sh +++ b/scripts/generate-android-icons.sh @@ -35,6 +35,7 @@ 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..." @@ -53,44 +54,32 @@ convert "$ASSETS_DIR/icon.png" -resize 96x96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_l convert "$ASSETS_DIR/icon.png" -resize 144x144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png" convert "$ASSETS_DIR/icon.png" -resize 192x192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png" -# Create background and foreground mipmap files -# These reference the existing drawable files -cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_background.xml" << 'EOF' - - - - -EOF - -cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_foreground.xml" << 'EOF' - - - - -EOF - -# Update the existing launcher XML files to reference the correct resources +# Create simple launcher XML files (no adaptive icons for now) cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher.xml" << 'EOF' - - + + EOF cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_round.xml" << 'EOF' - - + + EOF +# Create foreground mipmap files for adaptive icons +convert "$ASSETS_DIR/icon.png" -resize 108x108 "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_foreground.png" + 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!" \ No newline at end of file From 3926f9289d063dbe424ba580e32f8a5bf384ad42 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 13 Aug 2025 06:42:32 +0000 Subject: [PATCH 03/17] fix(build): update ImageMagick commands to use modern v7 syntax - Replace deprecated 'convert' commands with 'magick' for ImageMagick v7+ - Add automatic version detection with fallback to 'convert' for v6 compatibility - Update generate-android-icons.sh and generate-icons.sh scripts - Eliminate deprecation warnings during Android builds - Maintain backward compatibility for older ImageMagick installations The scripts now automatically detect ImageMagick version and use the appropriate command syntax, eliminating the "convert command is deprecated" warnings while preserving functionality across different ImageMagick versions. --- scripts/generate-android-icons.sh | 50 +++++++++++++----- scripts/generate-icons.sh | 87 +++++++++++++++++++++++++------ 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/scripts/generate-android-icons.sh b/scripts/generate-android-icons.sh index b2279a98..aa2775ca 100755 --- a/scripts/generate-android-icons.sh +++ b/scripts/generate-android-icons.sh @@ -20,9 +20,16 @@ if [ ! -f "$ASSETS_DIR/icon.png" ]; then exit 1 fi -# Check if ImageMagick is available -if ! command -v convert &> /dev/null; then - echo "[ERROR] ImageMagick (convert) not found. Please install ImageMagick." +# 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" @@ -39,20 +46,35 @@ 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 -convert "$ASSETS_DIR/icon.png" -resize 48x48 "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png" -convert "$ASSETS_DIR/icon.png" -resize 72x72 "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png" -convert "$ASSETS_DIR/icon.png" -resize 96x96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png" -convert "$ASSETS_DIR/icon.png" -resize 144x144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png" -convert "$ASSETS_DIR/icon.png" -resize 192x192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png" +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 -convert "$ASSETS_DIR/icon.png" -resize 48x48 "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher_round.png" -convert "$ASSETS_DIR/icon.png" -resize 72x72 "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher_round.png" -convert "$ASSETS_DIR/icon.png" -resize 96x96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher_round.png" -convert "$ASSETS_DIR/icon.png" -resize 144x144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher_round.png" -convert "$ASSETS_DIR/icon.png" -resize 192x192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher_round.png" +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' @@ -72,7 +94,7 @@ cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_round.xml" << 'EOF' EOF # Create foreground mipmap files for adaptive icons -convert "$ASSETS_DIR/icon.png" -resize 108x108 "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_foreground.png" +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)" diff --git a/scripts/generate-icons.sh b/scripts/generate-icons.sh index bed6fd14..3f0062ed 100755 --- a/scripts/generate-icons.sh +++ b/scripts/generate-icons.sh @@ -1,22 +1,79 @@ #!/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/app/src/main/res/mipmap-mdpi -mkdir -p android/app/src/main/res/mipmap-hdpi -mkdir -p android/app/src/main/res/mipmap-xhdpi -mkdir -p android/app/src/main/res/mipmap-xxhdpi -mkdir -p android/app/src/main/res/mipmap-xxxhdpi +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 -convert -size 48x48 xc:blue -gravity center -pointsize 20 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-mdpi/ic_launcher.png -convert -size 72x72 xc:blue -gravity center -pointsize 30 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-hdpi/ic_launcher.png -convert -size 96x96 xc:blue -gravity center -pointsize 40 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -convert -size 144x144 xc:blue -gravity center -pointsize 60 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -convert -size 192x192 xc:blue -gravity center -pointsize 80 -fill white -annotate 0 "TS" android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +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/app/src/main/res/mipmap-mdpi/ic_launcher.png android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -cp android/app/src/main/res/mipmap-hdpi/ic_launcher.png android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -cp android/app/src/main/res/mipmap-xhdpi/ic_launcher.png android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -cp android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -cp android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png \ No newline at end of file +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!" \ No newline at end of file From 45a8859a19960ba5820fbe248dbb0867685dfa73 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 13 Aug 2025 07:07:48 +0000 Subject: [PATCH 04/17] fix(assets): resolve Android and iOS resource generation issues Android build was failing due to missing drawable and mipmap directories for splash screens and launcher icons. iOS was missing complete asset catalog structure for app icons and splash screens. - Create missing Android resource directories (drawable, mipmap-*) - Add splash screen files to Android drawable directory - Generate complete set of Android launcher icons - Create iOS asset catalog structure with proper Contents.json files - Generate 21 iOS assets (app icons + splash screens) using ImageMagick - Add resource validation scripts for both platforms - Enhance Android resource check to auto-create missing directories NOTE: you need to test this from a fresh clone and after an npm install! Android build now completes successfully. iOS assets ready for macOS/Xcode builds. Both platforms have complete resource sets for development. --- android/app/src/main/res/values/colors.xml | 7 + .../SplashDark.imageset/Contents.json | 23 ++ .../SplashDark.imageset/splash@1x.png | Bin 0 -> 17366 bytes .../SplashDark.imageset/splash@2x.png | Bin 0 -> 63882 bytes .../SplashDark.imageset/splash@3x.png | Bin 0 -> 160149 bytes scripts/check-android-resources.sh | 17 + scripts/check-ios-resources.sh | 294 ++++++++++++++++++ scripts/generate-ios-assets.sh | 253 +++++++++++++++ 8 files changed, 594 insertions(+) create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 ios/App/App/Assets.xcassets/SplashDark.imageset/Contents.json create mode 100644 ios/App/App/Assets.xcassets/SplashDark.imageset/splash@1x.png create mode 100644 ios/App/App/Assets.xcassets/SplashDark.imageset/splash@2x.png create mode 100644 ios/App/App/Assets.xcassets/SplashDark.imageset/splash@3x.png create mode 100755 scripts/check-ios-resources.sh create mode 100755 scripts/generate-ios-assets.sh diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..79f802e1 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #3F51B5 + #303F9F + #FF4081 + #FFFFFF + diff --git a/ios/App/App/Assets.xcassets/SplashDark.imageset/Contents.json b/ios/App/App/Assets.xcassets/SplashDark.imageset/Contents.json new file mode 100644 index 00000000..0b268f6f --- /dev/null +++ b/ios/App/App/Assets.xcassets/SplashDark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "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 + } +} diff --git a/ios/App/App/Assets.xcassets/SplashDark.imageset/splash@1x.png b/ios/App/App/Assets.xcassets/SplashDark.imageset/splash@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..79fc756ebd0d5445de7cfc43ab5014cc9fdea813 GIT binary patch literal 17366 zcmd3tWl$Z>*WfRN0D<5T+$}(GcXxMpmy5e=kN^p;!QJKJZXvh@2p4y~xL;&>p8vb` z?(T=J+HYG^Q`OT`GkvO0pVNK1&o5dlBLCcoux~YDmu$~( z4`#OF;wrZ0<^aIw1dktG@&hW^L&}wE(|l2=#6)b-;(un5JY8%Uuq9bE-dCzWK2meA zQqifoU}@9clj`*8I@d*MIJ-EbOCVsZX7kat+W4%cX@Du#hIgNNvfbJYdI>+XO&q(o6}0``{M(9Udh%EyAd5{^Z7}@H zg@8MYf*1`dxePI;m*eVLzK3vIHKYr+(PQ#KQeVI9T`9YJgPuO&cYyf2Ud0I&#rXu^ zvaU9w^FY{-CBd8gqYgT4&c9dJNZ&mg@x0=_^N$p1?0;Cq*q@+}$PHn5Mj%y0l& zD1Ym8GIJRp12fs-S}M=#HTyDJ;d~R-uM<7w^!tkYdLJycY3Qb30S?7 zsz1(K7S65Hf578?Wwq6uwSp56%v0>B&nF|K45ezLq73%9abB4k3zed#d>0Q3YoNSw zeCOrqH-9eS7!F}MzH!V@D@_isZm)H=_1AagrRnSmjmXvT*U{2qRRbGvnO`n+GK%Yu zO02-Zuxlg6uQv;$&X?#NUxjX?*-eS;VNJg>C0_%~NPEq&kC}qQ5@>M!wNGhPZU){n zKf(Oo<>VFYIr*4ZVS|go7khgMI5|7&dKe8xsh|IOgbN|~El!I2Jy1KCM}WRn{Vb$g z1VvlYBeGpe{9ZL`don~(%*vhwZ)BAep+u-zr2F23#j%%0vpoH5Kt5^nXv1`V$U0H398Gu++b(O|)9JzI5rBw!K zX+hULFDEf2he5xMsjrti{(<&~y0t)@{u=+^vlg#2!O~LbvGLtC_v-V_qTu~BLf{9y zk5b&^o>_pfJ{wdm6ZpabllN9Ed>{Y+D#j+lVnJ+P__Gc5IsV*tXomA%txAI98gpnN z_Q}mMY;`+!qx|tIHlubX8D6?H1*(|@ii9;?6|Qw@m0u<#I>r%Lhx74)K+B4@v@uKN zT^%DvEWdIF-T8vm4KCfB^y;}9i(SmV$%%yKX3R@C|L3k)RhyEgvgEP6BfDY+O{MHQ zvf+XOe4roa(!f}Xic*;ks8OHy=%k~*I-f|I(EjxslCU-fdq}Kn!kPMTQy3hh}zjU|Vr@a4LaYzG23KMlQHxPi7gr&s8&S z1J_l|&GgTd4O4#e`9Ug6ya{~}qb0!JFiBhi2(&RT*!L+17&QBT9&b|8eNYvE9MZ{FnZ1jIHnXn(5QE_`@y~Pp zQpGqM+?~I=O$}`(PO~+d%eVd`Az^E(JU{)l%@Q-v_(Q#3Gu=$ZIztml%+w@CFio?6Pp@1hx27fPkj5t% zp5ul-pDN5d=lH{a%F$#AInf^R3+HXPYJUD|I<-p^{1ZjqgxFVkhJ@BcBL7BzgDd z)0<^Osc{a~CS*GjQ_SJKz_&L1A{}hsnr#*vNYCJbIq%r@2tJ%WIcM57zKQ%3o`EH7 z#+0g5&8~F!)89BzmCO+PYI;vM%@EBcShaYSXn4?=74A2T3CGK6u;WRpC^Do0Vl@LY zOwWqd^#s3fj5nu*3G$zXl3tBWbrrfF$*lgSjs-uPe=Ix5^W)5ycwDzQ zm!h=GGl5JU!6_+LwLr+zjHQ8X`}-77xeyRp9Ctfda8dx&bitTp4888G@#!nFj|aJJ z#zRB0GDzrAP-rr4FSCy7VSqpk1Dir7Q%3NouX-aXTx9Y$nKt^rL1N32k*jn~^7@2p z3Xu>2;t3-ZTV{8}RaJ5E#z2R~3xbjfmeBp)k5Z9+NaKI64s2*wnSfy&q-AyliG85M zTFhObugQ&6y>-P`eyt=+EK<$lS-HAtCVr-ryHnoTW{XXkWy}L5{kS@I5aU@G_Y6(r zUmqK78n2>iOUv&YT)4MDwJ(RO)j8nsjnP`L!Io051zu)jXFPmWdx{prx6#U!rd6{3 zEt?MRL^&gnjmK$`-(o(Qi5*UWbvkf&H)N7B0An;SAjPYA%zHcZqyUxw6m4cyw+vMq zv+@{E7!C3(+wgYk&+eI$EezjE0y(HK?8P47*Q`=)bC$E!X>GeGDvh1qXL687@4Vd? zF4&pk(nc+&5AwpBusP#))tLHIar#NVNVRIEo1)OozZe2IS84rsPT5gx_Q2SDll&uG1%$Q6w)ZdU&`(>nR zA_XDFu;i)G4ZGD)YT(w$>_h5Am|np0rR6XC!o09!_@3eXw9i7ijM6+1lEKYSv9M_f z$8gS+U(_|>JJp}T?2*Lr7LRBfeA3!p1u&dDif|S;e{RAK{d(SfNngQwd>$W$<87UA z=8Y?8y^8<9UcFo6J^a>4m@~w?gA19-V57zxVUu>=unpcuw?!qlqrzpDYL3;4D94ad zIxEuiT^NvD^BveW3TTSX_-kjjtsvd*o1d+QkgRJpQWmNbrPi#qceK>koY%O07aWQVa5q{b-DK3z^`Z* zqPG-j%O1@z-<VP?cmti^BY*EQiboh+YZQ<(sowOWznwH_8fAbzjg|Ctm<6%kH^ zf`URqL;^rXXZ=06Ra$u~%dPCU8@vRz7+}x`_U$D=hAyNm`R8O}@e#|pzxU?3p=0aw z5AU??2m7BE>!;rk|J+IHp!ORR0m;OQTgIem03!58IARi}tzEnn6jOJCQ%P!4rZN)G zeI_h;GIWY#Du^O{)zO^s*UwG~5}?o|oJzU(j-fJ+tOhE(l{ zBL65K;eEnr;pSdMt?D_q=H`R>;F55_-y={FysG_3- z>OG$0xK{#R1!z z1`_lgTPD3vd3_vMBKJ$OLGS<^Gih*u!{T6&Jy^lbcMGo`YDp~f- z8kmPKye;S*v2lHD)(?EneR(cU*@aYBA6l^eR2n#R=lcntWb)SW*mpe-FAxZ%wEHox z&pD4h+SKC|DI8s&P+$0UF>q5n=vjMsd&FBHN2x@%q>lqnnI_2u81OuSCU`zUo8y1D zA|JS}Y?+*rhT~n@N9+3Kw zxL17%ws*5H5Bxt>JkLl-3}!hB3L^bVh!vQ^lFRA85||M8L)n(q~d!$ z*4IFk7LG*D>MyinpFOAQV%=;%@eXU?Q_qdxS6A9q2+J;h%J{s2zgZXGwYm|}LyZiF<;$5Y#`-_Cd|%1L86u|Q!)50FsPai< zM8HtfsG%4253GQb^{^iq8KQTyzk-s%hGQT#J;%ln@2Q5s@B_B)x zhG`$8c}cPjG4e4flSaU~@e!O{x@BZ)ejtOPlm1_*x(Mz;Om9sxaNv&ztJjK~cF^=N z$O}izyPF#iRr|L3>4CfZhQH?_!Inkxi*OKfSAFUy92vwx(xBJK(#aM7H@ioinxgtZ z?%TCCd+8d)a$gt(9SM3pd+}ybF_cd1tV?GWJ9WxI;r9R``Cs`32Db2aeido!WO>w+rY3Ek(ktb0F}G(8`8pUKm;v@}n>>^#ttKu2+fo|kOVuOFW^(m-P^ zyLpKN(wUSLzhKvI=gz#h!<=|MpQCqEEXgJCzG^cWzRcVOo)1Y2z5dxYK11^@|NCV) z@v&r6@2;v}^4F6JPHUZSYJ^2DV@Mt%T_pBarHf2GiwRw*AW*ma)}EJ{!(cXs(V0Qw z_Z*XcT1apmhc^v1{g5C0wljHnGPY9bvcA6B=zIWFYw`Bx@%b}S3wH|_lQGO-(~Sp! zEYU}+UhQx(6gfhT^wY=-H8!Bm<$(< zr|ad##MuL{2?%!L(rE-3<{Ff&Nd^lbAG}?-HQ^EPU&UCzgCUEdz?V$?yjFhfvo@4w z+~$8cJ9{bDDa-RcgJ#L+Qx1E*tu07UZ@njQW13SHdqf8Fhe)g;9SqsW0p$Hjw)gvS5g=VxYaKy1i!Z-63#G|`dl{*NUDqjN zX~Hiv>weekK~}AuL|*-{NeF%t5&+og0kb_9W_(Op3b=E0xKOGea4vWJYiohfsZJ=a z@Y9$*ba5=>S2lB}_lxoCSC7uYS0AJ!0+T>9g5rIvUAX6iZ_&nfPpcad+#0_ zU=Llpj7P|Q*~`s+9&vjPBn_bmtwyBUXh0ATNwi@PEe|J|q$yq&mFiM{>oL7pbheRrK#gvJj96pmj(Qv+TkN2@)j#*4UuFqE>w z1S8A_A9Fh~eWS=MWt?9xZoE1jAtg9L(H1NjIjeX18(A)nb># zTlB*yYTn&2IL&*Ggd@8QH z8RG$V`{yGV*VIZEc?y~4pJtxV0?*dv=u%)nW4$~ZaMrd@20jR+72h|K~uimaKDek<<7x`WnJuWg)=X)Nn5>7k<9$SAD zrJ1_h@s-37sbyoRB=ciSE>(=5RtY0tcOti?YtbuY4=g$Ia)^jWwruX@$g)JB-er&5b?93)+-nSl-imk%!sRdKPr?)#deLqfG_*6Y5s6~G z2mQs>)`0?nr%%^gDbDuT!0)>@L~-vBDJf#CnxX(-=_e5Zav{-g09twURMkC0T#}gP>^fMdtb1WnO!6eF`YcuVFXva)95lu zS&F3^ydVACyleL@X0v=pVnumTjCupO%5=d1r;9r8FY-|)Wu#O4^Fp-gQ?_{fS3dRC z9mIXz;T&AC4L=cVM+eb%v>&cCRFc)spNe)vqLMClN|qhpRsfrCMmO|}EB1R6RrnwC zh}o{VC8k$@&GDW2cCWa1P5pgUIJZ(}iB=>1$=gqSCSiTD`;5ViCY%bY2u%X!>TcY!q@7zeL1B5~>U(Y>C*xs;4|8d}L;M)Em4a^p-aw$*rPI`JTfa6l*7rSnMKbgv0`&7HI8|Y?-{{i%lZ3YGNL>p z)bipWOs$I^1Ixn3m{N{5k6>(wDiN(>%axSl*>(JB)` zzsVP##qGpIs>`ymV@){7R9r}WfFaAHn~8}OCK@Saj(6m7c{;eEEjj!sz|A_pR3A#l z<9dA%*RqlwUyveJUh9(etU9KW{-F|OYPUx9xw53*k0GuI2kbdg1kDsVU{d*+3orXQ z*LL-{3XYEUWi}|}k9;)AZlw?@Pa_eflrOW-L!P*qmAG4^&%Dn%wC<`U^9S8Ikv=q7 zmS294wWwVWdJedgFij`UV!uL}VAGy03MpPlRCh}2fWN5?B`->mnneP3SWE{f2L6qj zXF>vE$Te;+J(I!WOO7#&we)PFT%7t32xdd1$Qg8FDz3R%Kv{&#K$|FI ztO{KcUBM}6fbeapVTqlRqh%L8CWxln!*jB?I!&&|o0XE$< z=lVRzTz<{> zDx)pYAo74*ou|+V`a52B?3W04g5r{9!kmK67hf-gd3>M#+W1k0lgR*0WbEjQCev{w zXO6l9n1x>Zy>k5R>Oc}~tY*}{nJ*#N_#99`)c~-+tarzO%z=pA5x2?F(9(!sA&cIO zh6IZ}B5$QNGfL<_-&n|fwQ0^)$8$4^@&mRlZEO^J&kG!e0C`EDI}iw24LU4!YQ5N5 zvd$H}ds;d}xF7r^L#NE_r^^M~D}A4o7PijE|NJ=QQdjRXHo^S@UVk|gdRl+F*>vV1 z`5v)fe1;I`!F}wZN)SIx1v_;I*ci)w_V3&lK6b!h73AVGGM4kiH>)T~CbciCFzlvF zl`7~81K&sVE)YhiHbZ2RQ*E$`^$V=m$0ErSKV<)fC_E+7wY??cybH{PB3E_!o_ znSyBK0J2o*g4m7Z93|U!v+6R98O^|Zq{#3Hec%yr8~Z`-2JH0#kNi6CZ~LFEw~FHE`R zPVhwAbDc{p>?4j2mwx>7a+NQXDvAmt1pT0t8DrdINDMIT%^_45^a@>vPWHXLGlp)N zujlr*aFu@V=6jwkE=Md4x(d2KdtC^EazAa(?(Jy_#4%Ns4SxR`^*PD{ePl3%eoBIP z9z(TuJxeKcnDkk!p-H7lyBxuSI#6$v&g(1rb$|UpoW%vm1MNjn2Y{Ia=?o2?$<9_fqduGMbx^VO8vuLKK$Q};UCaiqypRmQ2NCd!z(oW+Zl{niBov46wdnnHn zq6;RYFZ;t_I5HK5UrCGWL9cxSk<$A5Vu)$;)86TX60VrCAsdAUAFdrJLcJtG+>tM)LsVqO+(p!E`Qn#Dgxh>no za&qADpUYrZ_TT)*@Z8TTQcp@P+Nn>MI-(C9 z>$su}ra~;rmUv*lqLO5L3z%M0-1fVnf4CzDOsS{=A0r3Ogl;CZCv$uD+K(rV+IK!M zuYJpvRWB`V;vR{dDtXN9O?D+OMfq| z|80joNQ-&&GXWEGz`6G8w|ng0zjVoi0oX3k?*)C%bq(#0xXOYz%dbyCyg|1P%z|D@ z_b&^-QqBUm%gt*$UIs^!WY85$DY#50R>?7^ooyc&a-jy;Qb%fa4P@E+$XWt?mvbg2 z{90Ibu*`7D&x|)Vur6#QtM1FdvuEWXp>();{`*R{7z?WKmWJlr*>B83PYLUf&#zaM zY0O4MsFBItVlXndzFpc(2Hq!g-wk-YULMs;Yj|BD9O+W3WquuMj3(Gp$I!dihs9j~ z!xQA^z+5DxDjm=d9B&wt_cdq(_q;DfPdO2vx2?rOwjne8fw#{s>waez0?l8@cv)cR zc1ODd`l#-cz6U+MzTm!IcC35bwSrFWe6b}fgwPY#k3Vb*UR65@zqCz7%$@HrwtT6r zp;?aJQz4?;ogE!zU^cwJ#WE)R!iXVj$vARk@ZpEs#AqZ7(dL^$5|CB%G}-=2Jq~?t zm!$MF904QhfmLSfn{B30^Hipal_@uzeRze!3J)|CMW|p*Scu@0M!375td#{{?%%%R&K45< z2|q%hEKU$CcHjM)G8MoiZ1n4Cns;uwz?^zq?23r!ER&$RA)!pyg4~XOk;^Zw$}6Hd zkT~5`S`*DcD3?-SDn{8#(DU-^-Rpc0fxQ!ZS2XE|=e~0^q05Bt&wH=UU2RPb?MP8n zmXq2PQxt*zh#_Ic#qzbCkHyj?8L?8eL33FJpTmC<#C|Sx2EM+Oc3-B*Gxuyk1<7U= z!ctze4}E;F1(_2s2t$0LYZ6x4%YQ@Y+aj1VxiW-ouz8Pzup&on(N}yI4dodsO|e59 z8*|3K$L`Et9?=Rt&L{_*m{;0E9|uISB{Sda5W7)C6_g3%RnDbHbLq#j;=&R;7v@8(a6?iWUyM-@FM! zC{CuL-Z`Z9!c{F&U+HJhl#!!bL?e0HjwF3qz*7F0mR2akwf!zw6?-*ss>^69ZOL&2 zw9!sl;nxF-!y;Ja@A!`pRhD-8{7whCgv~f6YQp6G9EhlfeyhhyxjC`J7k&x^WI_mM z3Ce+?MR1TY6#(3p#%zkLw-2TgC1!iQufGPh?89$b;e?+iOMoaI{O`tIkJtJ3wOPNI zbRYab=8C$#pcc~TgxD99)qr^-oJQ{Mc`DVdCr|X(g@1mXXy>OJIleLWy%-`Dc)m8_ zNmkYPZ9pay%pmG*(RPR2Li{kqU(?~};cO|8a(HTnqb3!$d5)kg{5<>3`q@ZWD5$w_ z%AMd;e?91EJmb(Jkx^0i*X16Pg=7uF?Vuf6@@pb#9ZkLjcTO9MDqi!FmN=$Q;938- z{bp{i$ANr$8c*A`kGpzP&|-bG=aWNmggcb_{nviN_}2@s$$+C}>slEDjHnByq2!YD zPDotb+k%VVeYKtpQG${k6N5a4a4P$uQJO@yw5^_zdkHNrJzFK?Pc$8tog-=w-{!I6 z+ErDD&dIi0%=>AoT8?3EbjL!)rCMFY2_{Sa+oVFW6dS6(vzvG#VUiv|c?Qxjv-aP$ zm1NbTQ&ddAa)Ly7WR3m<1Gll;cHb`RMv80{#x+70TG!2=cZ)RwPxs$nPUNUQqz*a+ z#v`)DG`DcUB&oVLLb1MXCT!C_8GXle)2XN`m*$`%NiFvuo%}JG>P*J}((75Ox8kH? zPi&KeGpc2SyU-5k{rhezDI+sA)-9)Pkk4vS$J-p-c;OMRS+%7$`eIV&6cB_hrSaft zz&__Ntd{+q)|uY+%VBdp2LH6BB1d7(~zO6!sCe;MxV7R9^m19nuFDq{4t>C^Ss;DRXwmSx-V!T|Izl{2OwTg+1=dsU!KE zE#;N85gbGziXtv@+U;$x4~f)`T*7GT?l~tp4%#+2`?FXod_si;-8!c%H>v3b$7P(zRq%Fl^ zm-JUsfo|6cH75xWv0Tsox0SxS=zT1T4Gtn%kx(yEf|ZUn=%0s+Qf9b!%(q08vJvF& zrhaN6`J>rUp943&_?9M)$b3Sam&6OJIy*_rVLny%f@vM3Njq%Q65NARDaXhR3&jm% z?~AiE1kU@{F6fkDQG*^HQyBXvR2~JDqfYQ*mtZtysC>Iu2qKt`Z~ea` za)}WjJ~1nQsr9~(b`#zEn0Xd5GMKNiFzBY?qnOlu>q?5Gt!Inl5pl9qVnbVEbb||Q zrrq!DLZg3XN-JZ@EmWhT__2$NiTy+}Gks;sH%g62Wgv`w#?Q8b7!CIrL&-Loj0=E) z3i9SS1ikXch+)G<`3ECz$jJm9o^LRu6l+s3rR%&xcsIM zB$(m*#y_)zQcCjACEB5Q?^WRHzMOl=Z}}bC2t!tZleldnqZgwnH865vTiL|&?BQ#b z&znYHdm?j18DWQiL#a43{=SJ( zGO125TSf+$5*QdE$!v%pXcD7};OJyAkp^=Bo)TS?`)#6*bDs0 zr4XHqWDR=HFAx~7Pj}MW1Z*jzjVkV{e*1+1DIfE_2rNE|-2l6G?;|2U|7Pgz4xUPZQ9ixPU&Z6RfU58#Q>mtv+r~9^jTsyXH8WyWFQl(E6bX=F zM=fqMX4`->Ry(S~2@#ve&7cBT5(KHL_Fd-(U#MsaCef!F1mfn;sd)??=r1h+1Ry}H z7l+2Ie6Uze7BR>_$05m= z>QXYIO3YpHHbktzZ_^A~l}fH*CliTO5mNKHL9 zhQr*@2$t$+*9c|V?U?lI*lO|QI*U2)Dp%l<;uL{|U}$AmN(m?>t_Zy?dr(ZAF`i3$ zZeNZjke3ihFt#Wr1I{ZC8E+t()ARyI++nv#a1D+-RE@TuorL|G>wKce2pzPoU-LOV z+|ZFjDi&z{6I8x`haSWh^K$TXFHW1v9ElBcA9H(jdp33I2N#YA4Y)X8f{+3u1qV&1 zgj-j7TqR*CGPweE?U+4_$2-jE6jA0v1T4`K-H8QS@Tsdo*>c!IS$gKHd-;mTvt|&; zX#IqJQkIZ@I*-GF(+tZ78$qRk_4`Wd5!+tv#?A_uQIciyxf2P1hq3C=cQ`t_n4(B; z3y;>Ia$F3I-o(gU9_~l(DJLlGf?(3d@`6{G@N zs$$TD`m88FSwZqeUB603#1Pe7#RmdM_f@|?zB_sro?4MhQS-3lW8M!8^-DHfD@FI` z^y$PqJocMWWGddUeqz@FGk4*%=r9y?+cVZ&83s2)Xf6G(H_}H9dJl1^pbMU#)@;u+ONfm^)EAag?j?<|cHz(b}Dd2d_nCJ_mv?-p~ zpo{gF-753R+g&LtN?BV3891_HY%vKnq1)2j3jyXaNm!Xsk}V?hhMJQ>(6(BZ3^zIw-Smp{MdaZ`z6?r2Kr_% zTe_cyMs8EjB~EoqzG&ib;=5sCu63p&o&;#pt^X7!O@*$~iv1&zS?X zQ!8(0|7@|dcX#E1aU;v&4bPn;Dw?wH0^rf;U`5%zI$-?eOwao;m0F!0Bad>M1B!&qz0}bXu;Zn2EU9 zh_dm+X{`F@Pl<~Vg08^VW#vG~1L^Di*DPgH0e{8<+^?!sg)+bPf0h2MqsjQxtiPh6y~dxz`BF^5kI@U5s~)5@>U^}`5OjiOtnN)SQZb0Mp^rYRZc%j; z9`1V2evse*ukpEG-xCHN{j@&yncFm{{VFe?gDw(|ZvVws%v;Pm2T*Xau!lVLa&B+z zdwn4-+~Kl&d3n@*ulZ3fH=-?PJP)>wz1r4lSIMZBZ18sPS9jA5x8U_8l*IesenfpB zMN=e}0$o(%?*0w6~xT{sibI2Cj^GTd|V*V;JnBBFRV<>dFsY=?vkmzqj0->$VR zLD(ju)<*khhPn#;2PXI15G2#jowh6=e*F`84Gh-^`wo;33@Z_}?oH2*+V!j>hOg~mk?~)uPp^CfV6)--Z0xVL7?_QDpikd~UpC)ljceiZ8z?pk5@Az&>eFD0 zDaEUUZTZ-_FRHbnr@|@vLE~{<3?aLx>d}Q9DOr^Rg=U6>iVQpFJX{20xzYkVAvx;p zD8%ns2q{4)aNZEPaW_31+8E)V58#!9tAC$e^wuT^ViaNodJb*42tZa%D$yc5*l6nP z2BH5dF)S|$_-jf21G{4zhI>Db!nXC>=EHjdd%k=d7NqsG?Zm# z&_@p|%}~{pe14AP2fWWsqzzC(PhwPE}qV)Kqq$jRCg}SyznyxGb)0K8~RI! z%o}kPy*O>P%y8I2hmWvqlJD(6OJ($CDB8ZD0bhAP#Gt&3R}Mdha-XeO$9rSJUwtEi!YGI#}+n zz~7M(B)s#czp*X@E6m)#EGau8Va82md z`1gT4iX*dXuB4N+OJWeQ0Uc_vl~=CZ2W)nCzpCp1Ca)gGw-iW>x?>h>sG##Kc3nl? z(q0y_hGEU6p-bSPgN3A$!zkEZ2giK;LoN!UTNwJNegBW82si7ao!0^`5Te8c6^A^> z+{BS62afBPx>2tIDYJ6UPKTpkOU3xbv#{=Z6)S~i@sB+feS*&NznheYg6a| zBmeYK*n#!HPZ7col@GHCFQ8@^uJBGQm#czdnESm%AEkd}(H? zBucWlNmYFbLq=%`^q@=Yl1PCaA)}SouRBJkF!AST|JqzE=O#mySKL0jN_+%M$0|D9 z;hR8YH>EMV6H?#-<-nfSSOyb~5S6k5hiSi36X3&rQcECLVVExXlKI77ye1{0yo%B< z0;~?fAoj%2@$q8M1H-Xef2O#R7ZuB{+VPW&(ecaw@>wZro)&q+;P-eH!1d?-m(UT4 zRRv800$E1ln&dr_30343+m0j{z@-~w$RGZs@3kJRd9w&A;UaAj$r2e3^eN4s#<3V) znXae|(cW+tbhU5IcJuUqo0639;3e zFn*seV^$|BG?x0rT~hpttEzgqASG#E38rh(@I`T=#T?ImttffFV0H4B zpDDiCJe+U-caBa+ykJK|gLO2k)rL3(?+rI`axx6-GdbaCMRt)*;))R=9D7iA7z2NJypzsw&DQI7M2S&1B6ZkX=R(u;lZw|(bEA#iU9?JB z;OUNN?4r-ql$Y#IMR~41uH!df*lbTA^ebzftC1{pG55IcU*hNc*ikef3*K_1>LAo5 zFQF4#+W%PPYVd@MER>*v6s}eh50{6X&jjq4vh2I6Jz225n`->cIt0_Goh$1tS~6s> z5=3T&17zv=5USMB^}_HRpg7Tl59Ftow`ay;nPI|R*^(;MDm3mcDb^oro8IEqsuZt1 zksu=G%crkn3*`sma`X5_ZoLS2mDl*lX;)%-T~l~mJA#m%Sa`A*ADZZy?>y&6@t8a8 z-X_lDm4f)3ph1uq+qd|ITaw@%y$44-kBgPL3}aP%#ieycVDu_WBC6&V4j0QB$JaZQv1H`emO+F3}0;}eLt?QQk9 zG?19J^U7zDbURkmwZRtSlWQutq8((scWrV)txakaWS8+oJuv^UcQgI<4g8&O(=#}c z@w03A&bu5^$Zi|NHIMOYhyPUpPgwttj<Y$L zj_Lw{Jz$$dqfWtDY@Nsq&u^G|wb@#>SLm7llTI+lT~qpP!UBf9`bzP}jY*17x*C5&X6|;u)7G55$sc?Rp96z+= zBm4B0zfEt-fl=K5<|GuNF4A|rA9Q;gpkx1m-2(Z^->9Cbg7RN%)3uz+D%*b*MN4$$ zb_JSeop&A0?T7p&_c|4Bv-G|SdVxRX#CwU3&6yEZSaR5kEDPeY%yHq&k(`=wGp5xi zX88g2kq_*dKs5s@9=18>?RzoppnYbgVxPi#RP9voE&~T9=XX@@Lc_|MFD<=>$97BY z^u=x1eZ{*w=9fhO4Ri?p)9;iS?}iCKVpx;@>Ai3C)qExpf7ZLtj;0UN+^)pO5h`OH zOIvw|yfR8%Ezd8WANU8Iu~N)c9f*6>*+7DHSIi3J55fXgD_{ZV44v~0C%Y5~$7Tb}$X+<%cJt&(+|ru#E9d`@v(g!SO}+Iy3Q zg&u3Dz|;RGPoCm)K98EP07MS5n-sgB-oLKG*A(0`BD(R_fr)#YKn&aVR~7Qhr|PE- z`F&y?Y;F9&3fbjJtSYZzv1O7FcQeSK!D&OeZsOHMMG>$9C?60?K6w)6)M_$=&fEd12F#cqgM+Fm=-5biEAf z(YHP^MP!GTsF@jGo0R7DdA+|-6XG=(cMu73TNH$ZBhSx-!KR+WH@wY?kfy{cu*AcT(@hxC(*o#__=3S;LvCN&w&@gHV&+d!da6efAsFJrU8%WyS5`#9{!( zYs9FeRxzrYgK2D2Kw}EML^Tt?jm%So{C4?Z+)PKRH*U9sL(Bh%72>}Bm(D3W)HWWQ(B~(pL*qZ$p3(DZ8yG4!=#ul0a$=k}J<+aXh?I~ADJ#d>v&qZx4$KwLxB%ePX%_NQP~;bn5#vrQwC zMgN~B%$eSwj#kPd3+(3uWtV+{pwD&nWvg97{2BZ(9>s!{>37e)hIH)5EXU_0p^o*s@uKY$EahC2;>yEnzQE*5F;)BGzH}moOToTfCvGXdpAaYQV#m2g%{Yf5j2R zODl!))OKe=$r_sWkJqBC?X5n^E<15jkp05{WAE&270L^_KtQqK|N5r@_}$=J^G^%* zN+m_*T}da-nA$o>n>^DtsS)%`b5X}?6_@2y6kIYj@q66ZBZUf? zyyUB4ltXl(lPgm^QQo)hQ+HK)CC1v=(G|juRxPAAWO%#6{!61{_1jl&zWE^e

LM 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 literal 0 HcmV?d00001 diff --git a/ios/App/App/Assets.xcassets/SplashDark.imageset/splash@3x.png b/ios/App/App/Assets.xcassets/SplashDark.imageset/splash@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a1dbc7759555cf5a0627499c9f5b3c1058227038 GIT binary patch literal 160149 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]a9+?v)gZBENuNz>3Ili(=&QC7{;%0pi-FbV9qZ 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 z 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 literal 0 HcmV?d00001 diff --git a/scripts/check-android-resources.sh b/scripts/check-android-resources.sh index d328c051..cd994088 100755 --- a/scripts/check-android-resources.sh +++ b/scripts/check-android-resources.sh @@ -45,6 +45,13 @@ 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 @@ -67,6 +74,16 @@ if ! check_file "$ANDROID_RES_DIR/drawable/splash_dark.png" "Splash screen (dark 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" diff --git a/scripts/check-ios-resources.sh b/scripts/check-ios-resources.sh new file mode 100755 index 00000000..70466fb2 --- /dev/null +++ b/scripts/check-ios-resources.sh @@ -0,0 +1,294 @@ +#!/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 diff --git a/scripts/generate-ios-assets.sh b/scripts/generate-ios-assets.sh new file mode 100755 index 00000000..17131d2d --- /dev/null +++ b/scripts/generate-ios-assets.sh @@ -0,0 +1,253 @@ +#!/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" From a2840675221c55344c3ccb459d8ea982910f1ada Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 14 Aug 2025 07:22:26 +0000 Subject: [PATCH 05/17] 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 --- .cursor/rules/asset_configuration.mdc | 32 ++ .github/workflows/asset-validation.yml | 142 +++++++++ .gitignore | 12 + .node-version | 1 + .nvmrc | 1 + README.md | 45 ++- .../app/src/main/assets/capacitor.config.json | 2 +- assets/README.md | 2 - capacitor-assets.config.json | 38 +-- capacitor.config.ts | 116 +++++++ config/assets/capacitor-assets.config.json | 32 ++ config/assets/schema.json | 119 +++++++ doc/asset-migration-plan.md | 214 +++++++++++++ package.json | 4 + {assets => resources}/icon.png | Bin {assets => resources}/splash.png | Bin {assets => resources}/splash_dark.png | Bin scripts/assets-config.js | 174 +++++++++++ scripts/assets-validator.js | 218 +++++++++++++ scripts/check-android-resources.sh | 159 ---------- scripts/check-ios-resources.sh | 294 ------------------ scripts/generate-android-icons.sh | 107 ------- scripts/generate-icons.sh | 79 ----- scripts/generate-ios-assets.sh | 253 --------------- scripts/purge-generated-assets.sh | 67 ---- 25 files changed, 1125 insertions(+), 986 deletions(-) create mode 100644 .cursor/rules/asset_configuration.mdc create mode 100644 .github/workflows/asset-validation.yml create mode 100644 .node-version create mode 100644 .nvmrc delete mode 100644 assets/README.md create mode 100644 capacitor.config.ts create mode 100644 config/assets/capacitor-assets.config.json create mode 100644 config/assets/schema.json create mode 100644 doc/asset-migration-plan.md rename {assets => resources}/icon.png (100%) rename {assets => resources}/splash.png (100%) rename {assets => resources}/splash_dark.png (100%) create mode 100644 scripts/assets-config.js create mode 100644 scripts/assets-validator.js delete mode 100755 scripts/check-android-resources.sh delete mode 100755 scripts/check-ios-resources.sh delete mode 100755 scripts/generate-android-icons.sh delete mode 100755 scripts/generate-icons.sh delete mode 100755 scripts/generate-ios-assets.sh delete mode 100755 scripts/purge-generated-assets.sh diff --git a/.cursor/rules/asset_configuration.mdc b/.cursor/rules/asset_configuration.mdc new file mode 100644 index 00000000..916ecdd6 --- /dev/null +++ b/.cursor/rules/asset_configuration.mdc @@ -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 diff --git a/.github/workflows/asset-validation.yml b/.github/workflows/asset-validation.yml new file mode 100644 index 00000000..72cd2be0 --- /dev/null +++ b/.github/workflows/asset-validation.yml @@ -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'); + " diff --git a/.gitignore b/.gitignore index a9f37e49..4202ef2a 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,10 @@ icons *.log +# Build outputs +dist/ +build/ + # Generated Android assets and resources (should be generated during build) android/app/src/main/assets/public/ @@ -64,6 +68,14 @@ android/app/src/main/res/drawable*/ android/app/src/main/res/mipmap*/ android/app/src/main/res/values/ic_launcher_background.xml +# Android generated assets (deny-listed in CI) +android/app/src/main/res/mipmap-*/ic_launcher*.png +android/app/src/main/res/drawable*/splash*.png + +# iOS generated assets (deny-listed in CI) +ios/App/App/Assets.xcassets/**/AppIcon*.png +ios/App/App/Assets.xcassets/**/Splash*.png + # Keep these Android configuration files in version control: # - android/app/src/main/assets/capacitor.plugins.json # - android/app/src/main/res/values/strings.xml diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..a9d08739 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18.19.0 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a9d08739 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.19.0 diff --git a/README.md b/README.md index 6992ba09..efc9b1ad 100644 --- a/README.md +++ b/README.md @@ -136,11 +136,50 @@ const apiUrl = `${APP_SERVER}/api/claim/123`; See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions. -## Icons +## Asset Management -Application icons are in the `assets` directory, processed by the `capacitor-assets` command. +TimeSafari uses a standardized asset configuration system for consistent +icon and splash screen generation across all platforms. -To add a Font Awesome icon, add to fontawesome.ts and reference with `font-awesome` element and `icon` attribute with the hyphenated name. +### Asset Sources + +- **Single source of truth**: `resources/` directory (Capacitor default) +- **Source files**: `icon.png`, `splash.png`, `splash_dark.png` +- **Format**: PNG or SVG files for optimal quality + +### Asset Generation + +- **Configuration**: `config/assets/capacitor-assets.config.json` +- **Schema validation**: `config/assets/schema.json` +- **Build-time generation**: Platform assets generated via `capacitor-assets` +- **No VCS commits**: Generated assets are never committed to version control + +### Development Commands + +```bash +# Generate/update asset configurations +npm run assets:config + +# Validate asset configurations +npm run assets:validate + +# Clean generated platform assets (local dev only) +npm run assets:clean + +# Build with asset generation +npm run build:native +``` + +### Platform Support + +- **Android**: Adaptive icons with foreground/background, monochrome support +- **iOS**: LaunchScreen storyboard preferred, splash assets when needed +- **Web**: PWA icons generated during build to `dist/` (not committed) + +### Font Awesome Icons + +To add a Font Awesome icon, add to `fontawesome.ts` and reference with +`font-awesome` element and `icon` attribute with the hyphenated name. ## Other diff --git a/android/app/src/main/assets/capacitor.config.json b/android/app/src/main/assets/capacitor.config.json index 594ebca3..04ab8d0c 100644 --- a/android/app/src/main/assets/capacitor.config.json +++ b/android/app/src/main/assets/capacitor.config.json @@ -29,7 +29,7 @@ "splashFullScreen": true, "splashImmersive": true }, - "CapacitorSQLite": { + "CapSQLite": { "iosDatabaseLocation": "Library/CapacitorDatabase", "iosIsEncryption": false, "iosBiometric": { diff --git a/assets/README.md b/assets/README.md deleted file mode 100644 index b9272ff0..00000000 --- a/assets/README.md +++ /dev/null @@ -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. diff --git a/capacitor-assets.config.json b/capacitor-assets.config.json index d56533f4..92bd0414 100644 --- a/capacitor-assets.config.json +++ b/capacitor-assets.config.json @@ -1,36 +1,32 @@ { "icon": { - "ios": { - "source": "resources/ios/icon/icon.png", - "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" - }, "android": { - "source": "resources/android/icon/icon.png", + "adaptive": { + "background": "#121212", + "foreground": "resources/icon.png", + "monochrome": "resources/icon.png" + }, "target": "android/app/src/main/res" }, + "ios": { + "padding": 0, + "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" + }, + "source": "resources/icon.png", "web": { - "source": "resources/web/icon/icon.png", "target": "public/img/icons" } }, "splash": { - "ios": { - "source": "resources/ios/splash/splash.png", - "target": "ios/App/App/Assets.xcassets/Splash.imageset" - }, "android": { - "source": "resources/android/splash/splash.png", + "scale": "cover", "target": "android/app/src/main/res" - } - }, - "splashDark": { + }, + "darkSource": "resources/splash_dark.png", "ios": { - "source": "resources/ios/splash/splash_dark.png", - "target": "ios/App/App/Assets.xcassets/SplashDark.imageset" + "target": "ios/App/App/Assets.xcassets", + "useStoryBoard": true }, - "android": { - "source": "resources/android/splash/splash_dark.png", - "target": "android/app/src/main/res" - } + "source": "resources/splash.png" } -} \ No newline at end of file +} diff --git a/capacitor.config.ts b/capacitor.config.ts new file mode 100644 index 00000000..24ef38c6 --- /dev/null +++ b/capacitor.config.ts @@ -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; diff --git a/config/assets/capacitor-assets.config.json b/config/assets/capacitor-assets.config.json new file mode 100644 index 00000000..eb12403e --- /dev/null +++ b/config/assets/capacitor-assets.config.json @@ -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" + } + } +} diff --git a/config/assets/schema.json b/config/assets/schema.json new file mode 100644 index 00000000..89e76c0c --- /dev/null +++ b/config/assets/schema.json @@ -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 +} diff --git a/doc/asset-migration-plan.md b/doc/asset-migration-plan.md new file mode 100644 index 00000000..3a05353c --- /dev/null +++ b/doc/asset-migration-plan.md @@ -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 diff --git a/package.json b/package.json index 7b27258d..930cc8cf 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,10 @@ "auto-run:electron": "./scripts/auto-run.sh --platform=electron", "build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts", "build:capacitor:sync": "npm run build:capacitor && npx cap sync", + "build:native": "vite build && npx cap sync && npx capacitor-assets generate", + "assets:config": "node scripts/assets-config.js", + "assets:validate": "node scripts/assets-validator.js", + "assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true", "build:ios": "./scripts/build-ios.sh", "build:ios:dev": "./scripts/build-ios.sh --dev", "build:ios:test": "./scripts/build-ios.sh --test", diff --git a/assets/icon.png b/resources/icon.png similarity index 100% rename from assets/icon.png rename to resources/icon.png diff --git a/assets/splash.png b/resources/splash.png similarity index 100% rename from assets/splash.png rename to resources/splash.png diff --git a/assets/splash_dark.png b/resources/splash_dark.png similarity index 100% rename from assets/splash_dark.png rename to resources/splash_dark.png diff --git a/scripts/assets-config.js b/scripts/assets-config.js new file mode 100644 index 00000000..926ff1fa --- /dev/null +++ b/scripts/assets-config.js @@ -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 }; diff --git a/scripts/assets-validator.js b/scripts/assets-validator.js new file mode 100644 index 00000000..fcdbcdff --- /dev/null +++ b/scripts/assets-validator.js @@ -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 }; diff --git a/scripts/check-android-resources.sh b/scripts/check-android-resources.sh deleted file mode 100755 index cd994088..00000000 --- a/scripts/check-android-resources.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/check-ios-resources.sh b/scripts/check-ios-resources.sh deleted file mode 100755 index 70466fb2..00000000 --- a/scripts/check-ios-resources.sh +++ /dev/null @@ -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 diff --git a/scripts/generate-android-icons.sh b/scripts/generate-android-icons.sh deleted file mode 100755 index aa2775ca..00000000 --- a/scripts/generate-android-icons.sh +++ /dev/null @@ -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' - - - - - -EOF - -cat > "$ANDROID_RES_DIR/mipmap-anydpi-v26/ic_launcher_round.xml" << 'EOF' - - - - - -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!" \ No newline at end of file diff --git a/scripts/generate-icons.sh b/scripts/generate-icons.sh deleted file mode 100755 index 3f0062ed..00000000 --- a/scripts/generate-icons.sh +++ /dev/null @@ -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!" \ No newline at end of file diff --git a/scripts/generate-ios-assets.sh b/scripts/generate-ios-assets.sh deleted file mode 100755 index 17131d2d..00000000 --- a/scripts/generate-ios-assets.sh +++ /dev/null @@ -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" diff --git a/scripts/purge-generated-assets.sh b/scripts/purge-generated-assets.sh deleted file mode 100755 index 3be21db6..00000000 --- a/scripts/purge-generated-assets.sh +++ /dev/null @@ -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 " -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" \ No newline at end of file From 76749a097d756c0abdad823f3e1fd26ccc3bc002 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 14 Aug 2025 08:43:40 +0000 Subject: [PATCH 06/17] fix(build): update Android build script to use new asset validation - Replace check-android-resources.sh call with npm run assets:validate - Fix 'No such file or directory' error in build-android.sh - Ensure builds work consistently across all development environments - Maintain build workflow while using new standardized asset system This fix resolves the build failure that occurred after removing legacy asset generation scripts, ensuring the new capacitor-assets workflow integrates seamlessly with existing build orchestration. Tested: Android build completes successfully with asset validation Assets: 87 platform assets generated automatically via capacitor-assets --- scripts/build-android.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/build-android.sh b/scripts/build-android.sh index 0f139be1..50a76f90 100755 --- a/scripts/build-android.sh +++ b/scripts/build-android.sh @@ -254,9 +254,9 @@ if [ "$DEPLOY_APP" = true ]; then exit 0 fi -# Step 1: Check and fix Android resources -safe_execute "Checking Android resources" "$(dirname "$0")/check-android-resources.sh" || { - log_warn "Resource check found issues, but continuing with build..." +# Step 1: Validate asset configuration +safe_execute "Validating asset configuration" "npm run assets:validate" || { + log_warn "Asset validation found issues, but continuing with build..." } # Step 2: Clean Android app From 495a94827a84ee151a9499d84b688be69add4471 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 14 Aug 2025 09:08:06 +0000 Subject: [PATCH 07/17] refactor(assets): convert asset management scripts to TypeScript with tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- config/assets/capacitor-assets.config.json | 14 +- package-lock.json | 528 ++++++++++++++++++ package.json | 5 +- .../{assets-config.js => assets-config.ts} | 82 ++- ...ssets-validator.js => assets-validator.ts} | 103 ++-- 5 files changed, 666 insertions(+), 66 deletions(-) rename scripts/{assets-config.js => assets-config.ts} (73%) rename scripts/{assets-validator.js => assets-validator.ts} (64%) diff --git a/config/assets/capacitor-assets.config.json b/config/assets/capacitor-assets.config.json index eb12403e..92bd0414 100644 --- a/config/assets/capacitor-assets.config.json +++ b/config/assets/capacitor-assets.config.json @@ -1,10 +1,9 @@ { "icon": { - "source": "resources/icon.png", "android": { "adaptive": { - "foreground": "resources/icon.png", "background": "#121212", + "foreground": "resources/icon.png", "monochrome": "resources/icon.png" }, "target": "android/app/src/main/res" @@ -13,20 +12,21 @@ "padding": 0, "target": "ios/App/App/Assets.xcassets/AppIcon.appiconset" }, + "source": "resources/icon.png", "web": { "target": "public/img/icons" } }, "splash": { - "source": "resources/splash.png", - "darkSource": "resources/splash_dark.png", "android": { "scale": "cover", "target": "android/app/src/main/res" }, + "darkSource": "resources/splash_dark.png", "ios": { - "useStoryBoard": true, - "target": "ios/App/App/Assets.xcassets" - } + "target": "ios/App/App/Assets.xcassets", + "useStoryBoard": true + }, + "source": "resources/splash.png" } } diff --git a/package-lock.json b/package-lock.json index 814a71ad..d6914554 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,6 +133,7 @@ "rimraf": "^6.0.1", "tailwindcss": "^3.4.1", "ts-jest": "^29.4.0", + "tsx": "^4.20.4", "typescript": "~5.2.2", "vite": "^5.2.0" } @@ -3900,6 +3901,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", @@ -3917,6 +3935,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -3934,6 +3969,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", @@ -17406,6 +17458,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/getenv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", @@ -26798,6 +26863,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-workspace-root": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz", @@ -29272,6 +29347,459 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.4.tgz", + "integrity": "sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 930cc8cf..d961148d 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts", "build:capacitor:sync": "npm run build:capacitor && npx cap sync", "build:native": "vite build && npx cap sync && npx capacitor-assets generate", - "assets:config": "node scripts/assets-config.js", - "assets:validate": "node scripts/assets-validator.js", + "assets:config": "tsx scripts/assets-config.ts", + "assets:validate": "tsx scripts/assets-validator.ts", "assets:clean": "rimraf android/app/src/main/res/mipmap-* ios/App/App/Assets.xcassets/**/AppIcon*.png ios/App/App/Assets.xcassets/**/Splash*.png || true", "build:ios": "./scripts/build-ios.sh", "build:ios:dev": "./scripts/build-ios.sh --dev", @@ -245,6 +245,7 @@ "rimraf": "^6.0.1", "tailwindcss": "^3.4.1", "ts-jest": "^29.4.0", + "tsx": "^4.20.4", "typescript": "~5.2.2", "vite": "^5.2.0" } diff --git a/scripts/assets-config.js b/scripts/assets-config.ts similarity index 73% rename from scripts/assets-config.js rename to scripts/assets-config.ts index 926ff1fa..baaadce5 100644 --- a/scripts/assets-config.js +++ b/scripts/assets-config.ts @@ -1,11 +1,11 @@ -#!/usr/bin/env node +#!/usr/bin/env tsx /** * TimeSafari Asset Configuration Generator * Generates capacitor-assets configuration files with deterministic outputs * Author: Matthew Raymer * - * Usage: node scripts/assets-config.js + * Usage: tsx scripts/assets-config.ts */ import fs from 'fs'; @@ -16,12 +16,62 @@ 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 {Object} Sorted, stable configuration object + * @returns Sorted, stable configuration object */ -function generateAssetConfig() { - const config = { +function generateAssetConfig(): AssetConfig { + const config: AssetConfig = { icon: { source: "resources/icon.png", android: { @@ -59,10 +109,10 @@ function generateAssetConfig() { /** * Sort object keys recursively for deterministic output - * @param {Object} obj - Object to sort - * @returns {Object} Object with sorted keys + * @param obj - Object to sort + * @returns Object with sorted keys */ -function sortObjectKeys(obj) { +function sortObjectKeys(obj: any): any { if (obj === null || typeof obj !== 'object') { return obj; } @@ -71,7 +121,7 @@ function sortObjectKeys(obj) { return obj.map(sortObjectKeys); } - const sorted = {}; + const sorted: any = {}; Object.keys(obj) .sort() .forEach(key => { @@ -84,7 +134,7 @@ function sortObjectKeys(obj) { /** * Validate that required source files exist */ -function validateSourceFiles() { +function validateSourceFiles(): void { const requiredFiles = [ 'resources/icon.png', 'resources/splash.png', @@ -107,10 +157,10 @@ function validateSourceFiles() { /** * Write configuration to file with consistent formatting - * @param {Object} config - Configuration object - * @param {string} outputPath - Output file path + * @param config - Configuration object + * @param outputPath - Output file path */ -function writeConfig(config, outputPath) { +function writeConfig(config: AssetConfig, outputPath: string): void { const jsonString = JSON.stringify(config, null, 2); // Ensure consistent line endings and no trailing whitespace @@ -126,7 +176,7 @@ function writeConfig(config, outputPath) { /** * Main execution function */ -function main() { +function main(): void { console.log('🔄 Generating TimeSafari asset configuration...'); console.log(`📁 Project root: ${PROJECT_ROOT}`); console.log(`📅 Generated: ${new Date().toISOString()}`); @@ -161,7 +211,7 @@ function main() { console.log(' 4. Use "npm run build:native" for builds'); } catch (error) { - console.error('❌ Configuration generation failed:', error.message); + console.error('❌ Configuration generation failed:', error instanceof Error ? error.message : String(error)); process.exit(1); } } @@ -171,4 +221,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { main(); } -export { generateAssetConfig, sortObjectKeys, validateSourceFiles }; +export { generateAssetConfig, sortObjectKeys, validateSourceFiles, AssetConfig }; diff --git a/scripts/assets-validator.js b/scripts/assets-validator.ts similarity index 64% rename from scripts/assets-validator.js rename to scripts/assets-validator.ts index fcdbcdff..587a25c7 100644 --- a/scripts/assets-validator.js +++ b/scripts/assets-validator.ts @@ -1,11 +1,11 @@ -#!/usr/bin/env node +#!/usr/bin/env tsx /** * TimeSafari Asset Configuration Validator * Validates capacitor-assets configuration against schema and source files * Author: Matthew Raymer * - * Usage: node scripts/assets-validator.js [config-path] + * Usage: tsx scripts/assets-validator.ts [config-path] */ import fs from 'fs'; @@ -16,50 +16,71 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROJECT_ROOT = path.dirname(__dirname); +// TypeScript interfaces for validation +interface ValidationError { + message: string; +} + +interface AssetConfig { + icon?: { + source?: string; + android?: { + adaptive?: { + foreground?: string; + background?: string; + monochrome?: string; + }; + }; + }; + splash?: { + source?: string; + darkSource?: string; + }; +} + /** * Load and parse JSON file - * @param {string} filePath - Path to JSON file - * @returns {Object} Parsed JSON object + * @param filePath - Path to JSON file + * @returns Parsed JSON object */ -function loadJsonFile(filePath) { +function loadJsonFile(filePath: string): AssetConfig { try { const content = fs.readFileSync(filePath, 'utf8'); return JSON.parse(content); } catch (error) { - throw new Error(`Failed to load ${filePath}: ${error.message}`); + throw new Error(`Failed to load ${filePath}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate configuration against schema - * @param {Object} config - Configuration object to validate - * @param {Object} schema - JSON schema for validation - * @returns {Array} Array of validation errors + * @param config - Configuration object to validate + * @returns Array of validation errors */ -function validateAgainstSchema(config, schema) { - const errors = []; +function validateAgainstSchema(config: AssetConfig): ValidationError[] { + const errors: ValidationError[] = []; // Basic structure validation if (!config.icon || !config.splash) { - errors.push('Configuration must contain both "icon" and "splash" sections'); + errors.push({ message: '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'); + errors.push({ message: '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'); + errors.push({ message: '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'); + errors.push({ message: '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'); + errors.push({ message: 'Android adaptive foreground must be a PNG or SVG file in resources/ directory' }); } } } @@ -67,13 +88,13 @@ function validateAgainstSchema(config, schema) { // Splash validation if (config.splash) { if (!config.splash.source) { - errors.push('Splash section must contain "source" field'); + errors.push({ message: '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'); + errors.push({ message: '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'); + errors.push({ message: 'Dark splash source must be a PNG or SVG file in resources/ directory' }); } } @@ -82,12 +103,12 @@ function validateAgainstSchema(config, schema) { /** * Validate that source files exist - * @param {Object} config - Configuration object - * @returns {Array} Array of missing file errors + * @param config - Configuration object + * @returns Array of missing file errors */ -function validateSourceFiles(config) { - const errors = []; - const requiredFiles = new Set(); +function validateSourceFiles(config: AssetConfig): ValidationError[] { + const errors: ValidationError[] = []; + const requiredFiles = new Set(); // Collect all required source files if (config.icon?.source) requiredFiles.add(config.icon.source); @@ -100,7 +121,7 @@ function validateSourceFiles(config) { requiredFiles.forEach(file => { const filePath = path.join(PROJECT_ROOT, file); if (!fs.existsSync(filePath)) { - errors.push(`Source file not found: ${file}`); + errors.push({ message: `Source file not found: ${file}` }); } }); @@ -109,12 +130,12 @@ function validateSourceFiles(config) { /** * Validate target directories are writable - * @param {Object} config - Configuration object - * @returns {Array} Array of directory validation errors + * @param config - Configuration object + * @returns Array of directory validation errors */ -function validateTargetDirectories(config) { - const errors = []; - const targetDirs = new Set(); +function validateTargetDirectories(config: AssetConfig): ValidationError[] { + const errors: ValidationError[] = []; + const targetDirs = new Set(); // Collect all target directories if (config.icon?.android?.target) targetDirs.add(config.icon.android.target); @@ -129,9 +150,9 @@ function validateTargetDirectories(config) { const parentDir = path.dirname(dirPath); if (!fs.existsSync(parentDir)) { - errors.push(`Parent directory does not exist: ${parentDir}`); + errors.push({ message: `Parent directory does not exist: ${parentDir}` }); } else if (!fs.statSync(parentDir).isDirectory()) { - errors.push(`Parent path is not a directory: ${parentDir}`); + errors.push({ message: `Parent path is not a directory: ${parentDir}` }); } }); @@ -140,10 +161,10 @@ function validateTargetDirectories(config) { /** * Main validation function - * @param {string} configPath - Path to configuration file - * @returns {boolean} True if validation passes + * @param configPath - Path to configuration file + * @returns True if validation passes */ -function validateConfiguration(configPath) { +function validateConfiguration(configPath: string): boolean { console.log('🔍 Validating TimeSafari asset configuration...'); console.log(`📁 Config file: ${configPath}`); console.log(`📁 Project root: ${PROJECT_ROOT}`); @@ -159,7 +180,7 @@ function validateConfiguration(configPath) { console.log('✅ Schema file loaded successfully'); // Perform validations - const schemaErrors = validateAgainstSchema(config, schema); + const schemaErrors = validateAgainstSchema(config); const fileErrors = validateSourceFiles(config); const dirErrors = validateTargetDirectories(config); @@ -179,13 +200,13 @@ function validateConfiguration(configPath) { } else { console.error('❌ Validation failed with the following errors:'); allErrors.forEach((error, index) => { - console.error(` ${index + 1}. ${error}`); + console.error(` ${index + 1}. ${error.message}`); }); return false; } } catch (error) { - console.error('❌ Validation failed:', error.message); + console.error('❌ Validation failed:', error instanceof Error ? error.message : String(error)); return false; } } @@ -193,7 +214,7 @@ function validateConfiguration(configPath) { /** * Main execution function */ -function main() { +function main(): void { const configPath = process.argv[2] || path.join(PROJECT_ROOT, 'capacitor-assets.config.json'); if (!fs.existsSync(configPath)) { @@ -201,7 +222,7 @@ function main() { 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(' - Specify path: tsx scripts/assets-validator.ts path/to/config.json'); console.log(' - Generate config: npm run assets:config'); process.exit(1); } @@ -215,4 +236,4 @@ if (import.meta.url === `file://${process.argv[1]}`) { main(); } -export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories }; +export { validateConfiguration, validateAgainstSchema, validateSourceFiles, validateTargetDirectories, AssetConfig }; From 303f1bc565b577ead71eca7bbe2a30959f1aca12 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sat, 16 Aug 2025 08:38:25 +0000 Subject: [PATCH 08/17] refactor(cursor-rules): reorganize rules into logical directory structure Restructure .cursor/rules from flat organization to hierarchical categories: - app/: application-specific rules (timesafari, architectural decisions) - database/: database-related rules (absurd-sql, legacy dexie) - development/: development workflow rules - docs/: documentation standards and markdown rules - features/: feature-specific implementation rules (camera) - workflow/: version control and workflow rules Add base_context.mdc for shared context across all rule categories. Improves maintainability and discoverability of cursor rules. --- .../app/architectural_decision_record.mdc | 172 +++++++++++ .cursor/rules/{ => app}/timesafari.mdc | 0 .../rules/architectural_decision_record.mdc | 287 ------------------ .cursor/rules/base_context.mdc | 92 ++++++ .cursor/rules/{ => database}/absurd-sql.mdc | 5 +- .cursor/rules/database/legacy_dexie.mdc | 5 + .../{ => development}/development_guide.mdc | 5 +- .cursor/rules/{ => docs}/documentation.mdc | 0 .cursor/rules/{ => docs}/markdown.mdc | 0 .../{ => features}/camera-implementation.mdc | 0 .cursor/rules/legacy_dexie.mdc | 6 - .../rules/{ => workflow}/version_control.mdc | 0 12 files changed, 273 insertions(+), 299 deletions(-) create mode 100644 .cursor/rules/app/architectural_decision_record.mdc rename .cursor/rules/{ => app}/timesafari.mdc (100%) delete mode 100644 .cursor/rules/architectural_decision_record.mdc create mode 100644 .cursor/rules/base_context.mdc rename .cursor/rules/{ => database}/absurd-sql.mdc (95%) create mode 100644 .cursor/rules/database/legacy_dexie.mdc rename .cursor/rules/{ => development}/development_guide.mdc (82%) rename .cursor/rules/{ => docs}/documentation.mdc (100%) rename .cursor/rules/{ => docs}/markdown.mdc (100%) rename .cursor/rules/{ => features}/camera-implementation.mdc (100%) delete mode 100644 .cursor/rules/legacy_dexie.mdc rename .cursor/rules/{ => workflow}/version_control.mdc (100%) diff --git a/.cursor/rules/app/architectural_decision_record.mdc b/.cursor/rules/app/architectural_decision_record.mdc new file mode 100644 index 00000000..9772de1f --- /dev/null +++ b/.cursor/rules/app/architectural_decision_record.mdc @@ -0,0 +1,172 @@ +--- +description: +globs: +alwaysApply: true +--- +# TimeSafari Cross-Platform Architecture Guide + +## 1. Platform Support Matrix + +| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) | +|---------|-----------|--------------------|-------------------| +| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented | +| Deep Linking | URL Parameters | App URL Open Events | Not Implemented | +| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs | +| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented | +| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks | + +--- + +## 2. Project Structure + +### Core Directories +``` +src/ +├── components/ # Vue components +├── services/ # Platform services and business logic +├── views/ # Page components +├── router/ # Vue router configuration +├── types/ # TypeScript type definitions +├── utils/ # Utility functions +├── lib/ # Core libraries +├── platforms/ # Platform-specific implementations +├── electron/ # Electron-specific code +├── constants/ # Application constants +├── db/ # Database related code +├── interfaces/ # TypeScript interfaces +└── assets/ # Static assets +``` + +### Entry Points +- `main.ts` → Base entry +- `main.common.ts` → Shared init +- `main.capacitor.ts` → Mobile entry +- `main.electron.ts` → Electron entry +- `main.web.ts` → Web entry + +--- + +## 3. Service Architecture + +### Service Organization + +```tree +services/ +├── QRScanner/ +│ ├── WebInlineQRScanner.ts +│ └── interfaces.ts +├── platforms/ +│ ├── WebPlatformService.ts +│ ├── CapacitorPlatformService.ts +│ └── ElectronPlatformService.ts +└── factory/ + └── PlatformServiceFactory.ts +``` + +### Factory Pattern +Use a **singleton factory** to select platform services via `process.env.VITE_PLATFORM`. + +--- + +## 4. Feature Guidelines + +### QR Code Scanning +- Define `QRScannerService` interface. +- Implement platform-specific classes (`WebInlineQRScanner`, Capacitor, etc). +- Provide `addListener` and `onStream` hooks for composability. + +### Deep Linking +- URL format: `timesafari://[/][?query=value]` +- Web: `router.beforeEach` → parse query +- Capacitor: `App.addListener("appUrlOpen", …)` + +--- + +## 5. Build Process + +- `vite.config.common.mts` → shared config +- Platform configs: `vite.config.web.mts`, `.capacitor.mts`, `.electron.mts` +- Use `process.env.VITE_PLATFORM` for conditional loading. + +```bash +npm run build:web +npm run build:capacitor +npm run build:electron +``` + +--- + +## 6. Testing Strategy + +- **Unit tests** for services. +- **Playwright** for Web + Capacitor: + - `playwright.config-local.ts` includes web + Pixel 5. +- **Electron tests**: add `spectron` or Playwright-Electron. +- Mark tests with platform tags: + + ```ts + test.skip(!process.env.MOBILE_TEST, "Mobile-only test"); + ``` + +> 🔗 **Human Hook:** Before merging new tests, hold a short sync (≤15 min) with QA to align on coverage and flaky test risks. + +--- + +## 7. Error Handling + +- Global Vue error handler → logs with component name. +- Platform-specific wrappers log API errors with platform prefix (`[Capacitor API Error]`, etc). +- Use structured logging (not `console.log`). + +--- + +## 8. Best Practices + +- Keep platform code **isolated** in `platforms/`. +- Always define a **shared interface** first. +- Use feature detection, not platform detection, when possible. +- Dependency injection for services → improves testability. +- Maintain **Competence Hooks** in PRs (2–3 prompts for dev discussion). + +--- + +## 9. Dependency Management + +- Key deps: `@capacitor/core`, `electron`, `vue`. +- Use conditional `import()` for platform-specific libs. + +--- + +## 10. Security Considerations + +- **Permissions**: Always check + request gracefully. +- **Storage**: Secure storage for sensitive data; encrypt when possible. +- **Audits**: Schedule quarterly security reviews. + +--- + +## 11. ADR Process + +- All major architecture choices → log in `doc/adr/`. +- Use ADR template with Context, Decision, Consequences, Status. +- Link related ADRs in PR descriptions. + +> 🔗 **Human Hook:** When proposing a new ADR, schedule a 30-min design sync for discussion, not just async review. + +--- + +## 12. Collaboration Hooks + +- **QR features**: Sync with Security before merging → permissions & privacy. +- **New platform builds**: Demo in team meeting → confirm UX differences. +- **Critical ADRs**: Present in guild or architecture review. + +--- + +# Self-Check + +- [ ] Does this feature implement a shared interface? +- [ ] Are fallbacks + errors handled gracefully? +- [ ] Have relevant ADRs been updated/linked? +- [ ] Did I add competence hooks or prompts for the team? +- [ ] Was human interaction (sync/review/demo) scheduled? diff --git a/.cursor/rules/timesafari.mdc b/.cursor/rules/app/timesafari.mdc similarity index 100% rename from .cursor/rules/timesafari.mdc rename to .cursor/rules/app/timesafari.mdc diff --git a/.cursor/rules/architectural_decision_record.mdc b/.cursor/rules/architectural_decision_record.mdc deleted file mode 100644 index fdc53c60..00000000 --- a/.cursor/rules/architectural_decision_record.mdc +++ /dev/null @@ -1,287 +0,0 @@ ---- -description: -globs: -alwaysApply: true ---- -# TimeSafari Cross-Platform Architecture Guide - -## 1. Platform Support Matrix - -| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) | -|---------|-----------|-------------------|-------------------| -| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented | -| Deep Linking | URL Parameters | App URL Open Events | Not Implemented | -| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs | -| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented | -| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks | - -## 2. Project Structure - -### 2.1 Core Directories -``` -src/ -├── components/ # Vue components -├── services/ # Platform services and business logic -├── views/ # Page components -├── router/ # Vue router configuration -├── types/ # TypeScript type definitions -├── utils/ # Utility functions -├── lib/ # Core libraries -├── platforms/ # Platform-specific implementations -├── electron/ # Electron-specific code -├── constants/ # Application constants -├── db/ # Database related code -├── interfaces/ # TypeScript interfaces and type definitions -└── assets/ # Static assets -``` - -### 2.2 Entry Points -``` -src/ -├── main.ts # Base entry -├── main.common.ts # Shared initialization -├── main.capacitor.ts # Mobile entry -├── main.electron.ts # Electron entry -└── main.web.ts # Web/PWA entry -``` - -### 2.3 Build Configurations -``` -root/ -├── vite.config.common.mts # Shared config -├── vite.config.capacitor.mts # Mobile build -├── vite.config.electron.mts # Electron build -└── vite.config.web.mts # Web/PWA build -``` - -## 3. Service Architecture - -### 3.1 Service Organization -``` -services/ -├── QRScanner/ # QR code scanning service -│ ├── WebInlineQRScanner.ts -│ └── interfaces.ts -├── platforms/ # Platform-specific services -│ ├── WebPlatformService.ts -│ ├── CapacitorPlatformService.ts -│ └── ElectronPlatformService.ts -└── factory/ # Service factories - └── PlatformServiceFactory.ts -``` - -### 3.2 Service Factory Pattern -```typescript -// PlatformServiceFactory.ts -export class PlatformServiceFactory { - private static instance: PlatformService | null = null; - - public static getInstance(): PlatformService { - if (!PlatformServiceFactory.instance) { - const platform = process.env.VITE_PLATFORM || "web"; - PlatformServiceFactory.instance = createPlatformService(platform); - } - return PlatformServiceFactory.instance; - } -} -``` - -## 4. Feature Implementation Guidelines - -### 4.1 QR Code Scanning - -1. **Service Interface** -```typescript -interface QRScannerService { - checkPermissions(): Promise; - requestPermissions(): Promise; - isSupported(): Promise; - startScan(): Promise; - stopScan(): Promise; - addListener(listener: ScanListener): void; - onStream(callback: (stream: MediaStream | null) => void): void; - cleanup(): Promise; -} -``` - -2. **Platform-Specific Implementation** -```typescript -// WebInlineQRScanner.ts -export class WebInlineQRScanner implements QRScannerService { - private scanListener: ScanListener | null = null; - private isScanning = false; - private stream: MediaStream | null = null; - private events = new EventEmitter(); - - // Implementation of interface methods -} -``` - -### 4.2 Deep Linking - -1. **URL Structure** -```typescript -// Format: timesafari://[/][?queryParam1=value1] -interface DeepLinkParams { - route: string; - params?: Record; - query?: Record; -} -``` - -2. **Platform Handlers** -```typescript -// Capacitor -App.addListener("appUrlOpen", handleDeepLink); - -// Web -router.beforeEach((to, from, next) => { - handleWebDeepLink(to.query); -}); -``` - -## 5. Build Process - -### 5.1 Environment Configuration -```typescript -// vite.config.common.mts -export function createBuildConfig(mode: string) { - return { - define: { - 'process.env.VITE_PLATFORM': JSON.stringify(mode), - // PWA is automatically enabled for web platforms via build configuration - __IS_MOBILE__: JSON.stringify(isCapacitor), - __USE_QR_READER__: JSON.stringify(!isCapacitor) - } - }; -} -``` - -### 5.2 Platform-Specific Builds - -```bash -# Build commands from package.json -"build:web": "vite build --config vite.config.web.mts", -"build:capacitor": "vite build --config vite.config.capacitor.mts", -"build:electron": "vite build --config vite.config.electron.mts" -``` - -## 6. Testing Strategy - -### 6.1 Test Configuration -```typescript -// playwright.config-local.ts -const config: PlaywrightTestConfig = { - projects: [ - { - name: 'web', - use: { browserName: 'chromium' } - }, - { - name: 'mobile', - use: { ...devices['Pixel 5'] } - } - ] -}; -``` - -### 6.2 Platform-Specific Tests -```typescript -test('QR scanning works on mobile', async ({ page }) => { - test.skip(!process.env.MOBILE_TEST, 'Mobile-only test'); - // Test implementation -}); -``` - -## 7. Error Handling - -### 7.1 Global Error Handler -```typescript -function setupGlobalErrorHandler(app: VueApp) { - app.config.errorHandler = (err, instance, info) => { - logger.error("[App Error]", { - error: err, - info, - component: instance?.$options.name - }); - }; -} -``` - -### 7.2 Platform-Specific Error Handling -```typescript -// API error handling for Capacitor -if (process.env.VITE_PLATFORM === 'capacitor') { - logger.error(`[Capacitor API Error] ${endpoint}:`, { - message: error.message, - status: error.response?.status - }); -} -``` - -## 8. Best Practices - -### 8.1 Code Organization -- Use platform-specific directories for unique implementations -- Share common code through service interfaces -- Implement feature detection before using platform capabilities -- Keep platform-specific code isolated in dedicated directories -- Use TypeScript interfaces for cross-platform compatibility - -### 8.2 Platform Detection -```typescript -const platformService = PlatformServiceFactory.getInstance(); -const capabilities = platformService.getCapabilities(); - -if (capabilities.hasCamera) { - // Implement camera features -} -``` - -### 8.3 Feature Implementation -1. Define platform-agnostic interface -2. Create platform-specific implementations -3. Use factory pattern for instantiation -4. Implement graceful fallbacks -5. Add comprehensive error handling -6. Use dependency injection for better testability - -## 9. Dependency Management - -### 9.1 Platform-Specific Dependencies -```json -{ - "dependencies": { - "@capacitor/core": "^6.2.0", - "electron": "^33.2.1", - "vue": "^3.4.0" - } -} -``` - -### 9.2 Conditional Loading -```typescript -if (process.env.VITE_PLATFORM === 'capacitor') { - await import('@capacitor/core'); -} -``` - -## 10. Security Considerations - -### 10.1 Permission Handling -```typescript -async checkPermissions(): Promise { - if (platformService.isCapacitor()) { - return await checkNativePermissions(); - } - return await checkWebPermissions(); -} -``` - -### 10.2 Data Storage -- Use secure storage mechanisms for sensitive data -- Implement proper encryption for stored data -- Follow platform-specific security guidelines -- Regular security audits and updates - -This document should be updated as new features are added or platform-specific implementations change. Regular reviews ensure it remains current with the codebase. diff --git a/.cursor/rules/base_context.mdc b/.cursor/rules/base_context.mdc new file mode 100644 index 00000000..048aaabc --- /dev/null +++ b/.cursor/rules/base_context.mdc @@ -0,0 +1,92 @@ +--- +alwaysApply: true +--- +```json +{ + "coaching_level": "standard", + "socratic_max_questions": 7, + "verbosity": "normal", + "timebox_minutes": null, + "format_enforcement": "strict" +} +``` + +# Base Context — Human Competence First + +## Purpose +All interactions must *increase the human’s competence over time* while completing the task efficiently. The model may handle menial work and memory extension, but must also promote learning, autonomy, and healthy work habits. +The model should also **encourage human interaction and collaboration** rather than replacing it — outputs should be designed to **facilitate human discussion, decision-making, and creativity**, not to atomize tasks into isolated, purely machine-driven steps. + +## Principles +1) Competence over convenience: finish the task *and* leave the human more capable next time. +2) Mentorship, not lectures: be concise, concrete, and immediately applicable. +3) Transparency: show assumptions, limits, and uncertainty; cite when non-obvious. +4) Optional scaffolding: include small, skimmable learning hooks that do not bloat output. +5) Time respect: default to **lean output**; offer opt-in depth via toggles. +6) Psychological safety: encourage, never condescend; no medical/clinical advice. No censorship! +7) Reusability: structure outputs so they can be saved, searched, reused, and repurposed. +8) **Collaborative Bias**: Favor solutions that invite human review, discussion, and iteration. When in doubt, ask “Who should this be shown to?” or “Which human input would improve this?” + +## Toggle Definitions + +### coaching_level +Determines the depth of learning support: `light` (short hooks), `standard` (balanced), `deep` (detailed). + +### socratic_max_questions +The number of clarifying questions the model may ask before proceeding. +If >0, questions should be targeted, minimal, and followed by reasonable assumptions if unanswered. + +### verbosity +'terse' (just a sentence), `concise` (minimum commentary), `normal` (balanced explanation), or other project-defined levels. + +### timebox_minutes +*integer or null* — When set to a positive integer (e.g., `5`), this acts as a **time budget** guiding the model to prioritize delivering the most essential parts of the task within that constraint. +Behavior when set: +1. **Prioritize Core Output** — Deliver the minimum viable solution or result first. +2. **Limit Commentary** — Competence Hooks and Collaboration Hooks must be shorter than normal. +3. **Signal Skipped Depth** — Omitted details should be listed under *Deferred for depth*. +4. **Order by Value** — Start with blocking or high-value items, then proceed to nice-to-haves if budget allows. +If `null`, there is no timebox — the model can produce full-depth responses. + +### format_enforcement +`strict` (reject outputs with format drift) or `relaxed` (minor deviations acceptable). + +## Modes (select or combine) +- **Doer**: produce the artifact fast, minimal commentary. +- **Mentor**: add short “why/how” notes + next-step pointers. +- **Socratic**: ask up to N targeted questions when requirements are ambiguous. +- **Pair-Programmer/Pair-Writer**: explain tradeoffs as you implement. +- **Facilitator**: structure output to be reviewable, commentable, and ready for group discussion. + +Default: Doer + short Mentor notes. + +## Competence & Collaboration Levers (keep lightweight) +- “Why this works” (≤3 bullets) +- “Common pitfalls” (≤3 bullets) +- “Next skill unlock” (1 tiny action or reading) +- “Teach-back” (1 sentence prompt the human can answer to self-check) +- “Discussion prompts” (≤2 short questions for peers/stakeholders) + +## Output Contract (apply to every deliverable) +- Clear **Objective** (1 line) +- **Result** (artifact/code/answer) +- **Use/Run** (how to apply/test) +- **Competence Hooks** (the 4 learning levers above, kept terse) +- **Collaboration Hooks** (discussion prompts or group review steps) +- **Assumptions & Limits** +- **References** (if used; links or titles) + +## Do-Not +- No filler, hedging, or moralizing. +- No medical/mental-health advice; keep “healthy habits” to general work practices. +- No invented facts; mark uncertainty plainly. +- No censorship. +- Avoid outputs that bypass human review when such review is valuable. + +## Self-Check (model, before responding) +- [ ] Task done *and* at least one competence lever included (≤120 words total). +- [ ] At least one collaboration/discussion hook present. +- [ ] Output follows the **Output Contract** sections. +- [ ] Toggles respected; verbosity remains concise. +- [ ] Uncertainties/assumptions surfaced. +- [ ] No disallowed content. diff --git a/.cursor/rules/absurd-sql.mdc b/.cursor/rules/database/absurd-sql.mdc similarity index 95% rename from .cursor/rules/absurd-sql.mdc rename to .cursor/rules/database/absurd-sql.mdc index 56729c2a..e8b66e79 100644 --- a/.cursor/rules/absurd-sql.mdc +++ b/.cursor/rules/database/absurd-sql.mdc @@ -1,7 +1,6 @@ --- -description: -globs: -alwaysApply: true +globs: **/db/databaseUtil.ts, **/interfaces/absurd-sql.d.ts, **/src/registerSQLWorker.js, **/services/AbsurdSqlDatabaseService.ts +alwaysApply: false --- # Absurd SQL - Cursor Development Guide diff --git a/.cursor/rules/database/legacy_dexie.mdc b/.cursor/rules/database/legacy_dexie.mdc new file mode 100644 index 00000000..5ef07221 --- /dev/null +++ b/.cursor/rules/database/legacy_dexie.mdc @@ -0,0 +1,5 @@ +--- +globs: **/databaseUtil.ts,**/AccountViewView.vue,**/ContactsView.vue,**/DatabaseMigration.vue,**/NewIdentifierView.vue +alwaysApply: false +--- +All references in the codebase to Dexie apply only to migration from IndexedDb to Sqlite and will be deprecated in future versions. \ No newline at end of file diff --git a/.cursor/rules/development_guide.mdc b/.cursor/rules/development/development_guide.mdc similarity index 82% rename from .cursor/rules/development_guide.mdc rename to .cursor/rules/development/development_guide.mdc index cc43b000..439c1f26 100644 --- a/.cursor/rules/development_guide.mdc +++ b/.cursor/rules/development/development_guide.mdc @@ -1,7 +1,6 @@ --- -description: rules used while developing -globs: -alwaysApply: true +globs: **/src/**/* +alwaysApply: false --- ✅ use system date command to timestamp all interactions with accurate date and time ✅ python script files must always have a blank line at their end diff --git a/.cursor/rules/documentation.mdc b/.cursor/rules/docs/documentation.mdc similarity index 100% rename from .cursor/rules/documentation.mdc rename to .cursor/rules/docs/documentation.mdc diff --git a/.cursor/rules/markdown.mdc b/.cursor/rules/docs/markdown.mdc similarity index 100% rename from .cursor/rules/markdown.mdc rename to .cursor/rules/docs/markdown.mdc diff --git a/.cursor/rules/camera-implementation.mdc b/.cursor/rules/features/camera-implementation.mdc similarity index 100% rename from .cursor/rules/camera-implementation.mdc rename to .cursor/rules/features/camera-implementation.mdc diff --git a/.cursor/rules/legacy_dexie.mdc b/.cursor/rules/legacy_dexie.mdc deleted file mode 100644 index 82de6cca..00000000 --- a/.cursor/rules/legacy_dexie.mdc +++ /dev/null @@ -1,6 +0,0 @@ ---- -description: -globs: -alwaysApply: true ---- -All references in the codebase to Dexie apply only to migration from IndexedDb to Sqlite and will be deprecated in future versions. \ No newline at end of file diff --git a/.cursor/rules/version_control.mdc b/.cursor/rules/workflow/version_control.mdc similarity index 100% rename from .cursor/rules/version_control.mdc rename to .cursor/rules/workflow/version_control.mdc From 404f23c11859e99e2a45aab95bf1bdee5d47f74d Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sat, 16 Aug 2025 12:40:19 +0000 Subject: [PATCH 09/17] feat(cursor): add research diagnostic workflow and format base context rules - Add comprehensive R&D workflow rules for pre-implementation research and defect investigation - Format base context rules for better readability and line length compliance - Include evidence-first investigation templates and collaboration hooks --- .cursor/rules/base_context.mdc | 78 +++++++++------ .cursor/rules/research_diagnostic.mdc | 135 ++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 .cursor/rules/research_diagnostic.mdc diff --git a/.cursor/rules/base_context.mdc b/.cursor/rules/base_context.mdc index 048aaabc..f4dd1fb6 100644 --- a/.cursor/rules/base_context.mdc +++ b/.cursor/rules/base_context.mdc @@ -3,59 +3,73 @@ alwaysApply: true --- ```json { - "coaching_level": "standard", - "socratic_max_questions": 7, - "verbosity": "normal", - "timebox_minutes": null, - "format_enforcement": "strict" + "coaching_level": "standard", + "socratic_max_questions": 7, + "verbosity": "normal", + "timebox_minutes": null, + "format_enforcement": "strict" } ``` # Base Context — Human Competence First ## Purpose -All interactions must *increase the human’s competence over time* while completing the task efficiently. The model may handle menial work and memory extension, but must also promote learning, autonomy, and healthy work habits. -The model should also **encourage human interaction and collaboration** rather than replacing it — outputs should be designed to **facilitate human discussion, decision-making, and creativity**, not to atomize tasks into isolated, purely machine-driven steps. +All interactions must *increase the human’s competence over time* while +completing the task efficiently. The model may handle menial work and memory +extension, but must also promote learning, autonomy, and healthy work habits. +The model should also **encourage human interaction and collaboration** rather +than replacing it — outputs should be designed to **facilitate human discussion, +decision-making, and creativity**, not to atomize tasks into isolated, purely +machine-driven steps. ## Principles -1) Competence over convenience: finish the task *and* leave the human more capable next time. -2) Mentorship, not lectures: be concise, concrete, and immediately applicable. -3) Transparency: show assumptions, limits, and uncertainty; cite when non-obvious. -4) Optional scaffolding: include small, skimmable learning hooks that do not bloat output. -5) Time respect: default to **lean output**; offer opt-in depth via toggles. -6) Psychological safety: encourage, never condescend; no medical/clinical advice. No censorship! -7) Reusability: structure outputs so they can be saved, searched, reused, and repurposed. -8) **Collaborative Bias**: Favor solutions that invite human review, discussion, and iteration. When in doubt, ask “Who should this be shown to?” or “Which human input would improve this?” + +1) Competence over convenience: finish the task *and* leave the human more + capable next time. +2) Mentorship, not lectures: be concise, concrete, and immediately applicable. +3) Transparency: show assumptions, limits, and uncertainty; cite when non-obvious. +4) Optional scaffolding: include small, skimmable learning hooks that do not + bloat output. +5) Time respect: default to **lean output**; offer opt-in depth via toggles. +6) Psychological safety: encourage, never condescend; no medical/clinical advice. + No censorship! +7) Reusability: structure outputs so they can be saved, searched, reused, and repurposed. +8) **Collaborative Bias**: Favor solutions that invite human review, discussion, + and iteration. When in doubt, ask “Who should this be shown to?” or “Which human + input would improve this?” ## Toggle Definitions ### coaching_level -Determines the depth of learning support: `light` (short hooks), `standard` (balanced), `deep` (detailed). + +Determines the depth of learning support: `light` (short hooks), `standard` +(balanced), `deep` (detailed). ### socratic_max_questions -The number of clarifying questions the model may ask before proceeding. + +The number of clarifying questions the model may ask before proceeding. If >0, questions should be targeted, minimal, and followed by reasonable assumptions if unanswered. ### verbosity 'terse' (just a sentence), `concise` (minimum commentary), `normal` (balanced explanation), or other project-defined levels. ### timebox_minutes -*integer or null* — When set to a positive integer (e.g., `5`), this acts as a **time budget** guiding the model to prioritize delivering the most essential parts of the task within that constraint. -Behavior when set: -1. **Prioritize Core Output** — Deliver the minimum viable solution or result first. -2. **Limit Commentary** — Competence Hooks and Collaboration Hooks must be shorter than normal. -3. **Signal Skipped Depth** — Omitted details should be listed under *Deferred for depth*. -4. **Order by Value** — Start with blocking or high-value items, then proceed to nice-to-haves if budget allows. +*integer or null* — When set to a positive integer (e.g., `5`), this acts as a **time budget** guiding the model to prioritize delivering the most essential parts of the task within that constraint. +Behavior when set: +1. **Prioritize Core Output** — Deliver the minimum viable solution or result first. +2. **Limit Commentary** — Competence Hooks and Collaboration Hooks must be shorter than normal. +3. **Signal Skipped Depth** — Omitted details should be listed under *Deferred for depth*. +4. **Order by Value** — Start with blocking or high-value items, then proceed to nice-to-haves if budget allows. If `null`, there is no timebox — the model can produce full-depth responses. ### format_enforcement `strict` (reject outputs with format drift) or `relaxed` (minor deviations acceptable). ## Modes (select or combine) -- **Doer**: produce the artifact fast, minimal commentary. -- **Mentor**: add short “why/how” notes + next-step pointers. -- **Socratic**: ask up to N targeted questions when requirements are ambiguous. -- **Pair-Programmer/Pair-Writer**: explain tradeoffs as you implement. +- **Doer**: produce the artifact fast, minimal commentary. +- **Mentor**: add short “why/how” notes + next-step pointers. +- **Socratic**: ask up to N targeted questions when requirements are ambiguous. +- **Pair-Programmer/Pair-Writer**: explain tradeoffs as you implement. - **Facilitator**: structure output to be reviewable, commentable, and ready for group discussion. Default: Doer + short Mentor notes. @@ -84,9 +98,9 @@ Default: Doer + short Mentor notes. - Avoid outputs that bypass human review when such review is valuable. ## Self-Check (model, before responding) -- [ ] Task done *and* at least one competence lever included (≤120 words total). -- [ ] At least one collaboration/discussion hook present. -- [ ] Output follows the **Output Contract** sections. -- [ ] Toggles respected; verbosity remains concise. -- [ ] Uncertainties/assumptions surfaced. +- [ ] Task done *and* at least one competence lever included (≤120 words total). +- [ ] At least one collaboration/discussion hook present. +- [ ] Output follows the **Output Contract** sections. +- [ ] Toggles respected; verbosity remains concise. +- [ ] Uncertainties/assumptions surfaced. - [ ] No disallowed content. diff --git a/.cursor/rules/research_diagnostic.mdc b/.cursor/rules/research_diagnostic.mdc new file mode 100644 index 00000000..73d1b1cf --- /dev/null +++ b/.cursor/rules/research_diagnostic.mdc @@ -0,0 +1,135 @@ +--- +description: Use this workflow when doing **pre-implementation research, defect investigations with uncertain repros, or clarifying system architecture and behaviors**. +alwaysApply: false +--- +```json +{ + "coaching_level": "light", + "socratic_max_questions": 2, + "verbosity": "concise", + "timebox_minutes": null, + "format_enforcement": "strict" +} +``` + +# Research & Diagnostic Workflow (R&D) + +## Purpose + +Provide a **repeatable, evidence-first** workflow to investigate features and +defects **before coding**. Outputs are concise reports, hypotheses, and next +steps—**not** code changes. + +## When to Use + +- Pre-implementation research for new features +- Defect investigations (repros uncertain, user-specific failures) +- Architecture/behavior clarifications (e.g., auth flows, merges, migrations) + +--- + +## Output Contract (strict) + +1) **Objective** — 1–2 lines +2) **System Map (if helpful)** — short diagram or bullet flow (≤8 bullets) +3) **Findings (Evidence-linked)** — bullets; each with file/function refs +4) **Hypotheses & Failure Modes** — short list, each testable +5) **Corrections** — explicit deltas from earlier assumptions (if any) +6) **Diagnostics** — what to check next (logs, DB, env, repro steps) +7) **Risks & Scope** — what could break; affected components +8) **Decision/Next Steps** — what we’ll do, who’s involved, by when +9) **References** — code paths, ADRs, docs +10) **Competence & Collaboration Hooks** — brief, skimmable + +> Keep total length lean. Prefer links and bullets over prose. + +--- + +## Quickstart Template + +Copy/paste and fill: + +```md +# Investigation — + +## Objective + + +## System Map +- +- + +## Findings (Evidence) +- — evidence: `src/path/file.ts:function` (lines X–Y); log snippet/trace id +- — evidence: `...` + +## Hypotheses & Failure Modes +- H1: ; would fail when +- H2: ; watch for + +## Corrections +- Updated: + +## Diagnostics (Next Checks) +- [ ] Repro on +- [ ] Inspect for +- [ ] Capture + +## Risks & Scope +- Impacted: ; Data: ; Users: + +## Decision / Next Steps +- Owner: ; By: (YYYY-MM-DD) +- Action: ; Exit criteria: + +## References +- `src/...` +- ADR: `docs/adr/xxxx-yy-zz-something.md` +- Design: `docs/...` + +## Competence Hooks +- Why this works: <≤3 bullets> +- Common pitfalls: <≤3 bullets> +- Next skill: <≤1 item> +- Teach-back: "" +``` + +--- + +## Evidence Quality Bar + +- **Cite the source** (file:func, line range if possible). +- **Prefer primary evidence** (code, logs) over inference. +- **Disambiguate platform** (Web/Capacitor/Electron) and **state** (migration, auth). +- **Note uncertainty** explicitly. + +--- + +## Collaboration Hooks + +- **Syncs:** 10–15m with QA/Security/Platform owners for high-risk areas. +- **ADR:** Record major decisions; link here. +- **Review:** Share repro + diagnostics checklist in PR/issue. + +--- + +## Self-Check (model, before responding) + +- [ ] Output matches the **Output Contract** sections. +- [ ] Each claim has **evidence** or **uncertainty** is flagged. +- [ ] Hypotheses are testable; diagnostics are actionable. +- [ ] Competence + collaboration hooks present (≤120 words total). +- [ ] Respect toggles; keep it concise. + +--- + +## Optional Globs (examples) + +> Uncomment `globs` in the header if you want auto-attach behavior. + +- `src/platforms/**`, `src/services/**` — attach during service/feature investigations +- `docs/adr/**` — attach when editing ADRs + +## Referenced Files + +- Consider including templates as context: `@adr_template.md`, `@investigation_report_example.md` From bc618bb13b786859778ce37893a3f0b920c0693c Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sat, 16 Aug 2025 13:51:01 +0000 Subject: [PATCH 10/17] feat(typescript): implement type-safe database error handling and eliminate any types - Add comprehensive database error interfaces (DatabaseConstraintError, DatabaseStorageError, DexieError) - Implement type guards for database error handling (isDatabaseError, isDatabaseConstraintError, etc.) - Replace any types with proper TypeScript types in ContactsView, ProjectsView, and IdentitySwitcherView - Implement type-safe error handling patterns using new type guards - Fix dynamic property access with keyof operator for type safety Resolves Priority 1 type safety issues in database operations, project management, and identity switching. --- src/interfaces/common.ts | 78 ++++++++++++++++++++++++++++++ src/views/ContactsView.vue | 35 +++++++++----- src/views/IdentitySwitcherView.vue | 4 +- src/views/ProjectsView.vue | 12 +++-- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts index 7aca4120..9267cf70 100644 --- a/src/interfaces/common.ts +++ b/src/interfaces/common.ts @@ -98,3 +98,81 @@ export interface VerifiableCredentialClaim { credentialSubject: ClaimObject; [key: string]: unknown; } + +/** + * Database constraint error types for consistent error handling + */ +export interface DatabaseConstraintError extends Error { + name: "ConstraintError"; + message: string; + constraint?: string; +} + +/** + * Database storage error types for IndexedDB/SQLite operations + */ +export interface DatabaseStorageError extends Error { + name: "StorageError"; + message: string; + code?: string; + constraint?: string; +} + +/** + * Legacy Dexie error types for migration compatibility + */ +export interface DexieError extends Error { + name: string; + message: string; + inner?: unknown; + stack?: string; +} + +/** + * Type guard for database constraint errors + */ +export function isDatabaseConstraintError( + error: unknown, +): error is DatabaseConstraintError { + return error instanceof Error && error.name === "ConstraintError"; +} + +/** + * Type guard for database storage errors + */ +export function isDatabaseStorageError( + error: unknown, +): error is DatabaseStorageError { + return error instanceof Error && error.name === "StorageError"; +} + +/** + * Type guard for legacy Dexie errors + */ +export function isDexieError(error: unknown): error is DexieError { + return ( + error instanceof Error && + (error.name === "DexieError" || + error.message.includes("Key already exists in the object store") || + error.message.includes("ConstraintError")) + ); +} + +/** + * Unified error type for database operations + */ +export type DatabaseError = + | DatabaseConstraintError + | DatabaseStorageError + | DexieError; + +/** + * Type guard for any database error + */ +export function isDatabaseError(error: unknown): error is DatabaseError { + return ( + isDatabaseConstraintError(error) || + isDatabaseStorageError(error) || + isDexieError(error) + ); +} diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index 23208aae..965cf711 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -170,6 +170,7 @@ import { logger } from "../utils/logger"; // No longer needed - using PlatformServiceMixin methods // import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; +import { isDatabaseError } from "@/interfaces/common"; import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; import { APP_SERVER } from "@/constants/app"; import { @@ -376,7 +377,11 @@ export default class ContactsView extends Vue { "", async (name) => { await this.addContact({ - did: (registration.vc.credentialSubject.agent as any).identifier, + did: ( + registration.vc.credentialSubject.agent as { + identifier: string; + } + ).identifier, name: name, registered: true, }); @@ -387,7 +392,11 @@ export default class ContactsView extends Vue { async () => { // on cancel, will still add the contact await this.addContact({ - did: (registration.vc.credentialSubject.agent as any).identifier, + did: ( + registration.vc.credentialSubject.agent as { + identifier: string; + } + ).identifier, name: "(person who invited you)", registered: true, }); @@ -396,8 +405,7 @@ export default class ContactsView extends Vue { this.showOnboardingInfo(); }, ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { + } catch (error: unknown) { const fullError = "Error redeeming invite: " + errorStringForLog(error); this.$logAndConsole(fullError, true); let message = "Got an error sending the invite."; @@ -881,20 +889,21 @@ export default class ContactsView extends Vue { /** * Handle errors during contact addition */ - private handleContactAddError(err: any): void { + private handleContactAddError(err: unknown): void { const fullError = "Error when adding contact to storage: " + errorStringForLog(err); this.$logAndConsole(fullError, true); let message = NOTIFY_CONTACT_IMPORT_ERROR.message; - if ( - (err as any).message?.indexOf("Key already exists in the object store.") > - -1 - ) { - message = NOTIFY_CONTACT_IMPORT_CONFLICT.message; - } - if ((err as any).name === "ConstraintError") { - message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message; + + // Use type-safe error checking with our new type guards + if (isDatabaseError(err)) { + if (err.message.includes("Key already exists in the object store")) { + message = NOTIFY_CONTACT_IMPORT_CONFLICT.message; + } + if (err.name === "ConstraintError") { + message += " " + NOTIFY_CONTACT_IMPORT_CONSTRAINT.message; + } } this.notify.error(message, TIMEOUTS.LONG); diff --git a/src/views/IdentitySwitcherView.vue b/src/views/IdentitySwitcherView.vue index 59eedbdf..3dcc6972 100644 --- a/src/views/IdentitySwitcherView.vue +++ b/src/views/IdentitySwitcherView.vue @@ -234,7 +234,9 @@ export default class IdentitySwitcherView extends Vue { { did, settingsKeys: Object.keys(newSettings).filter( - (k) => (newSettings as any)[k] !== undefined, + (k) => + k in newSettings && + newSettings[k as keyof typeof newSettings] !== undefined, ), }, ); diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 7c4f0560..a87ce52e 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -464,8 +464,10 @@ export default class ProjectsView extends Vue { ); this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG); } - } catch (error: any) { - logger.error("Got error loading plans:", error.message || error); + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logger.error("Got error loading plans:", errorMessage); this.notify.error(NOTIFY_PROJECT_LOAD_ERROR.message, TIMEOUTS.LONG); } finally { this.isLoading = false; @@ -578,8 +580,10 @@ export default class ProjectsView extends Vue { ); this.notify.error(NOTIFY_OFFERS_LOAD_ERROR.message, TIMEOUTS.LONG); } - } catch (error: any) { - logger.error("Got error loading offers:", error.message || error); + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logger.error("Got error loading offers:", errorMessage); this.notify.error(NOTIFY_OFFERS_FETCH_ERROR.message, TIMEOUTS.LONG); } finally { this.isLoading = false; From ef4f845f74ceebe4e26553d6247284e33d8dc7fc Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sat, 16 Aug 2025 13:52:44 +0000 Subject: [PATCH 11/17] feat(ci): enforce type safety with ESLint errors and pre-commit validation - Change @typescript-eslint/no-explicit-any from warn to error to block builds with any types - Add type-safety-check script for automated pre-commit validation - Implement comprehensive pre-commit checks including ESLint, TypeScript compilation, and any type scanning - Include database migration status verification in pre-commit process - Provide colored output and clear guidance for type safety issues This ensures type safety is enforced at the CI level and prevents regression of any type usage. --- .eslintrc.js | 2 +- package.json | 1 + scripts/type-safety-check.sh | 103 +++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 scripts/type-safety-check.sh diff --git a/.eslintrc.js b/.eslintrc.js index fcd19ebe..43e7fbd5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,7 +30,7 @@ module.exports = { }], "no-console": process.env.NODE_ENV === "production" ? "error" : "warn", "no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn", - "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-unnecessary-type-constraint": "off", "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] diff --git a/package.json b/package.json index 1fcc2a0a..6418982a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", + "type-safety-check": "./scripts/type-safety-check.sh", "type-check": "tsc --noEmit", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js && node scripts/copy-wasm.js", "test:prerequisites": "node scripts/check-prerequisites.js", diff --git a/scripts/type-safety-check.sh b/scripts/type-safety-check.sh new file mode 100755 index 00000000..399343df --- /dev/null +++ b/scripts/type-safety-check.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +# Type Safety Pre-commit Check Script +# This script ensures type safety before commits by running linting and type checking + +set -e + +echo "🔍 Running Type Safety Pre-commit Checks..." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to print colored output +print_status() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Check if we're in the right directory +if [ ! -f "package.json" ]; then + print_error "Must run from project root directory" + exit 1 +fi + +# Step 1: Run ESLint with TypeScript rules +print_status "Running ESLint TypeScript checks..." +if npm run lint > /dev/null 2>&1; then + print_status "ESLint passed - no type safety issues found" +else + print_error "ESLint failed - type safety issues detected" + echo "" + echo "Running lint with details..." + npm run lint + echo "" + print_error "Please fix the above type safety issues before committing" + exit 1 +fi + +# Step 2: Run TypeScript type checking +print_status "Running TypeScript type checking..." +if npm run type-check > /dev/null 2>&1; then + print_status "TypeScript compilation passed" +else + print_error "TypeScript compilation failed" + echo "" + echo "Running type check with details..." + npm run type-check + echo "" + print_error "Please fix the above TypeScript errors before committing" + exit 1 +fi + +# Step 3: Check for any remaining 'any' types +print_status "Scanning for any remaining 'any' types..." +ANY_COUNT=$(grep -r "any" src/ --include="*.ts" --include="*.vue" | grep -v "// eslint-disable" | grep -v "eslint-disable-next-line" | wc -l) + +if [ "$ANY_COUNT" -eq 0 ]; then + print_status "No 'any' types found in source code" +else + print_warning "Found $ANY_COUNT instances of 'any' type usage" + echo "" + echo "Instances found:" + grep -r "any" src/ --include="*.ts" --include="*.vue" | grep -v "// eslint-disable" | grep -v "eslint-disable-next-line" || true + echo "" + print_error "Please replace 'any' types with proper TypeScript types before committing" + exit 1 +fi + +# Step 4: Verify database migration status +print_status "Checking database migration status..." +if grep -r "databaseUtil" src/ --include="*.ts" --include="*.vue" > /dev/null 2>&1; then + print_warning "Found databaseUtil imports - ensure migration is complete" + echo "" + echo "Files with databaseUtil imports:" + grep -r "databaseUtil" src/ --include="*.ts" --include="*.vue" | head -5 || true + echo "" + print_warning "Consider completing database migration to PlatformServiceMixin" +else + print_status "No databaseUtil imports found - migration appears complete" +fi + +# All checks passed +echo "" +print_status "All type safety checks passed! 🎉" +print_status "Your code is ready for commit" +echo "" +echo "📚 Remember to follow the Type Safety Guidelines:" +echo " - docs/typescript-type-safety-guidelines.md" +echo " - Use proper error handling patterns" +echo " - Leverage existing type definitions" +echo " - Run 'npm run lint-fix' for automatic fixes" + +exit 0 From 379056aae1c61fc374fdcaf642941d277e710067 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Sat, 16 Aug 2025 14:13:36 +0000 Subject: [PATCH 12/17] feat(typescript): resolve Priority 2 type safety issues across components - Eliminate all remaining any types in Priority 2 components (activity, gifts, usage limits, QR scanning, discovery, meetings) - Implement proper TypeScript types using existing interfaces (GiveActionClaim, EndorserRateLimits, ImageRateLimits) - Replace any types with unknown + proper type guards for error handling - Fix type assertions for external library integrations (QR scanning, mapping) - Maintain backward compatibility while improving type safety Resolves 7 Priority 2 type safety warnings, achieving 100% type safety for critical user-facing functionality. --- src/components/ActivityListItem.vue | 6 ++---- src/components/GiftedDialog.vue | 5 ++++- src/components/UsageLimitsSection.vue | 5 +++-- src/constants/notifications.ts | 2 +- src/views/ContactQRScanShowView.vue | 10 +++++++++- src/views/DiscoverView.vue | 10 ++++++---- src/views/ImportAccountView.vue | 5 +++-- src/views/OnboardMeetingListView.vue | 2 +- src/views/OnboardMeetingSetupView.vue | 6 ++++-- 9 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/components/ActivityListItem.vue b/src/components/ActivityListItem.vue index c68281da..39dfcffa 100644 --- a/src/components/ActivityListItem.vue +++ b/src/components/ActivityListItem.vue @@ -288,8 +288,7 @@ export default class ActivityListItem extends Vue { } get fetchAmount(): string { - const claim = - (this.record.fullClaim as any)?.claim || this.record.fullClaim; + const claim = this.record.fullClaim; const amount = claim.object?.amountOfThisGood ? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood) @@ -299,8 +298,7 @@ export default class ActivityListItem extends Vue { } get description(): string { - const claim = - (this.record.fullClaim as any)?.claim || this.record.fullClaim; + const claim = this.record.fullClaim; return `${claim?.description || ""}`; } diff --git a/src/components/GiftedDialog.vue b/src/components/GiftedDialog.vue index 83a8b93d..0b9cd16a 100644 --- a/src/components/GiftedDialog.vue +++ b/src/components/GiftedDialog.vue @@ -622,7 +622,10 @@ export default class GiftedDialog extends Vue { * Handle edit entity request from GiftDetailsStep * @param data - Object containing entityType and currentEntity */ - handleEditEntity(data: { entityType: string; currentEntity: any }) { + handleEditEntity(data: { + entityType: string; + currentEntity: { did: string; name: string }; + }) { this.goBackToStep1(data.entityType); } diff --git a/src/components/UsageLimitsSection.vue b/src/components/UsageLimitsSection.vue index 4eb9d8ef..ed53393d 100644 --- a/src/components/UsageLimitsSection.vue +++ b/src/components/UsageLimitsSection.vue @@ -83,6 +83,7 @@