#!/usr/bin/env bash # # Daily Notification Plugin - Verification Script # # Single entrypoint to validate the project state. # Used by CI and local development. # # @author Matthew Raymer # @version 1.0.0 set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" # Counters PASSED=0 FAILED=0 SKIPPED=0 # Logging functions print_header() { echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" } print_info() { echo -e "${BLUE}ℹ${NC} $1" } print_success() { echo -e "${GREEN}✓${NC} $1" ((PASSED++)) } print_error() { echo -e "${RED}✗${NC} $1" ((FAILED++)) } print_warning() { echo -e "${YELLOW}⚠${NC} $1" ((SKIPPED++)) } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Run command and capture result run_check() { local name="$1" shift print_info "Checking: $name" # Capture output for debugging on failure local output output=$("$@" 2>&1) local exit_code=$? if [ $exit_code -eq 0 ]; then print_success "$name" return 0 else print_error "$name" # Print captured output on failure for debugging echo "" echo "Command output:" echo "$output" | head -20 if [ $(echo "$output" | wc -l) -gt 20 ]; then echo "... (truncated, showing first 20 lines)" fi echo "" return 1 fi } # Print environment diagnostics print_environment() { print_header "Environment Diagnostics" echo "Project Root: $PROJECT_ROOT" echo "Script Directory: $SCRIPT_DIR" echo "" # Node.js if command_exists node; then echo "Node.js: $(node --version)" else echo "Node.js: ❌ Not found" fi # npm if command_exists npm; then echo "npm: $(npm --version)" else echo "npm: ❌ Not found" fi # Java (for Android) if command_exists java; then echo "Java: $(java -version 2>&1 | head -n 1)" else echo "Java: ⚠ Not found (Android builds may fail)" fi # Gradle (for Android) if command_exists gradle; then echo "Gradle: $(gradle --version 2>&1 | grep 'Gradle' | head -n 1 || echo 'Unknown')" else echo "Gradle: ⚠ Not found (using wrapper)" fi # Swift (for iOS) if command_exists swift; then echo "Swift: $(swift --version 2>&1 | head -n 1)" else echo "Swift: ⚠ Not found (iOS builds may fail)" fi # xcodebuild (for iOS) if command_exists xcodebuild; then echo "xcodebuild: $(xcodebuild -version 2>&1 | head -n 1)" else echo "xcodebuild: ⚠ Not found (iOS builds may fail)" fi echo "" } # Install dependencies (best effort) install_dependencies() { print_header "Installing Dependencies" if [ ! -d "$PROJECT_ROOT/node_modules" ]; then print_info "Installing npm dependencies..." cd "$PROJECT_ROOT" npm install || print_warning "npm install failed (non-blocking)" else print_success "Dependencies already installed" fi echo "" } # TypeScript checks check_typescript() { print_header "TypeScript Checks" cd "$PROJECT_ROOT" # Type check if run_check "TypeScript compilation" npm run typecheck; then : else print_error "TypeScript type checking failed" return 1 fi # Lint if run_check "ESLint" npm run lint; then : else print_warning "ESLint found issues (non-blocking)" fi # Unit tests (if present) if [ -f "$PROJECT_ROOT/package.json" ] && grep -q '"test"' "$PROJECT_ROOT/package.json"; then if run_check "Unit tests" npm test; then : else print_warning "Unit tests failed (non-blocking)" fi else print_warning "No unit tests configured" fi echo "" } # Build checks check_build() { print_header "Build Checks" cd "$PROJECT_ROOT" # Run build if run_check "npm run build" npm run build; then : else print_error "Build failed - this will break publish" return 1 fi # Verify dist/ exists if [ ! -d "$PROJECT_ROOT/dist" ]; then print_error "dist/ directory not found after build" return 1 else print_success "dist/ directory exists" fi echo "" } # Package checks check_package() { print_header "Package Checks" cd "$PROJECT_ROOT" # Run npm pack --dry-run print_info "Running npm pack --dry-run..." PACK_OUTPUT=$(npm pack --dry-run 2>&1) PACK_EXIT=$? if [ $PACK_EXIT -ne 0 ]; then print_error "npm pack --dry-run failed" echo "$PACK_OUTPUT" return 1 fi # Extract file list from pack output (handle both "===" and plain "Tarball Contents" formats) # Only include actual file entries (lines starting with size like "556B", "1.1kB", etc.) # This excludes metadata lines like "filename: ... .tgz" from Tarball Details section PACK_FILES=$(echo "$PACK_OUTPUT" | grep -A 10000 -E "npm notice === Tarball Contents ===|npm notice Tarball Contents" | grep "npm notice" | sed 's/npm notice //' | grep -v "^===" | grep -v "^Tarball Contents$" | grep -E '^[0-9]') # If still empty, try alternative format (without "===" header) if [ -z "$PACK_FILES" ]; then # Extract only file entries (lines starting with size pattern) PACK_FILES=$(echo "$PACK_OUTPUT" | grep "npm notice" | sed 's/npm notice //' | grep -E '^[0-9]' | grep -v "^package size:" | grep -v "^$") fi # If still empty, fallback to all npm notice lines (but exclude known metadata) if [ -z "$PACK_FILES" ]; then PACK_FILES=$(echo "$PACK_OUTPUT" | grep "npm notice" | sed 's/npm notice //' | grep -v "^package size:" | grep -v "^name:" | grep -v "^version:" | grep -v "^filename:" | grep -v "^shasum:" | grep -v "^integrity:" | grep -v "^total files:" | grep -v "^$") fi # Check for required files if echo "$PACK_FILES" | grep -q "CapacitorDailyNotification.podspec"; then print_success "Podspec included in package" else print_error "Podspec missing from package (check package.json 'files' field)" return 1 fi if echo "$PACK_FILES" | grep -q "dist/"; then print_success "dist/ included in package" else print_error "dist/ missing from package" return 1 fi if echo "$PACK_FILES" | grep -q "android/"; then print_success "android/ included in package" else print_warning "android/ not in package (may be intentional)" fi if echo "$PACK_FILES" | grep -q "ios/"; then print_success "ios/ included in package" else print_warning "ios/ not in package (may be intentional)" fi # Check for forbidden files (hard fail) # Patterns: Xcode user state, build artifacts, test apps, editor temp files, macOS junk FORBIDDEN_PATTERNS="xcuserdata/|\.xcuserstate|DerivedData/|\.tgz|ios/App/|\.DS_Store|\.swp|\.swo|\.orig|\.rej" FORBIDDEN_FOUND=$(echo "$PACK_FILES" | grep -E "$FORBIDDEN_PATTERNS" || true) if [ -n "$FORBIDDEN_FOUND" ]; then print_error "Forbidden files found in package (update package.json 'files' field):" echo "$FORBIDDEN_FOUND" | while read -r line; do echo " - $line" done print_info "Fix: Tighten package.json 'files' field to exclude ios/App/ and Xcode user state files" print_info "Or add to .npmignore: **/xcuserdata/**, **/*.xcuserstate, **/DerivedData/**, ios/App/**, .DS_Store, *.swp, *.swo, *.orig, *.rej" return 1 else print_success "No forbidden files (xcuserdata, xcuserstate, DerivedData, ios/App/, .DS_Store, editor temp files) in package" fi # Check for unwanted files (warnings) if echo "$PACK_FILES" | grep -q "test-apps/"; then print_warning "test-apps/ found in package (should be excluded)" fi if echo "$PACK_FILES" | grep -q "docs/"; then print_warning "docs/ found in package (should be excluded)" fi if echo "$PACK_FILES" | grep -q "node_modules/"; then print_warning "node_modules/ found in package (should be excluded)" fi # Print package manifest summary (first 20 lines) print_info "Package manifest summary (showing first 20 of $(echo "$PACK_FILES" | wc -l) files):" echo "$PACK_FILES" | head -20 | while read -r line; do echo " $line" done TOTAL_FILES=$(echo "$PACK_FILES" | wc -l) if [ "$TOTAL_FILES" -gt 20 ]; then print_info "... and $((TOTAL_FILES - 20)) more files" fi echo "" } # Android checks (best effort) check_android() { print_header "Android Checks" cd "$PROJECT_ROOT" if [ ! -d "$PROJECT_ROOT/android" ]; then print_warning "Android directory not found, skipping Android checks" return 0 fi if ! command_exists java; then print_warning "Java not found, skipping Android build checks" return 0 fi cd "$PROJECT_ROOT/android" # Check if gradlew exists if [ ! -f "$PROJECT_ROOT/android/gradlew" ]; then print_warning "gradlew not found, skipping Android build" return 0 fi # Try to run a minimal gradle task # Note: This may fail if Capacitor Android is not available as a dependency # (it's only available in a Capacitor app context, not standalone) # So we catch the error and treat it as non-blocking set +e local gradle_output gradle_output=$(./gradlew compileDebugJavaWithJavac --no-daemon 2>&1) local gradle_exit=$? set -e if [ $gradle_exit -eq 0 ]; then print_success "Android build (compile)" else print_warning "Android build check failed (non-blocking - expected in standalone context)" print_info "Android plugin requires Capacitor app context to build" fi echo "" } # iOS checks (best effort) check_ios() { print_header "iOS Checks" cd "$PROJECT_ROOT" if [ ! -d "$PROJECT_ROOT/ios" ]; then print_warning "iOS directory not found, skipping iOS checks" return 0 fi if ! command_exists xcodebuild; then print_warning "xcodebuild not found, skipping iOS build checks" print_info "Manual iOS build command: cd ios && xcodebuild -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' build" return 0 fi # Check if Podfile exists if [ ! -f "$PROJECT_ROOT/ios/Podfile" ]; then print_warning "Podfile not found, skipping iOS build" return 0 fi # Try to build (best effort, may fail in CI) # Note: Don't use pipe in run_check - it won't work. Capture output separately. cd "$PROJECT_ROOT/ios" BUILD_OUTPUT=$(xcodebuild -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' build 2>&1) BUILD_EXIT=$? if [ $BUILD_EXIT -eq 0 ]; then print_success "iOS build (compile)" # Show first 10 lines of output for context echo "$BUILD_OUTPUT" | head -10 | while read -r line; do echo " $line" done else print_warning "iOS build check failed (non-blocking - may require manual setup)" print_info "Manual iOS build command: cd ios && xcodebuild -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' build" fi # Try to run tests (best effort) print_info "Running iOS tests..." TEST_OUTPUT=$(xcodebuild test -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' -only-testing:DailyNotificationPluginTests/DailyNotificationRecoveryTests 2>&1) TEST_EXIT=$? if [ $TEST_EXIT -eq 0 ]; then print_success "iOS recovery tests passed" # Show test summary if available echo "$TEST_OUTPUT" | grep -E "Test Suite|Test Case|passed|failed" | head -10 | while read -r line; do echo " $line" done else print_warning "iOS tests failed or not available (non-blocking)" print_info "Manual iOS test command: cd ios && xcodebuild test -workspace DailyNotificationPlugin.xcworkspace -scheme DailyNotificationPlugin -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15'" fi echo "" } # Check for native code in src/ # Check core module source (can run before build) check_core_source() { print_header "Core Module (Source) Checks" cd "$PROJECT_ROOT" # Require core source dir + expected files if [ ! -d "src/core" ]; then print_error "Missing src/core/" return 1 fi local required=( "src/core/index.ts" "src/core/errors.ts" "src/core/enums.ts" "src/core/events.ts" "src/core/contracts.ts" "src/core/guards.ts" ) local missing=0 for f in "${required[@]}"; do if [ ! -f "$f" ]; then print_error "Missing core file: $f" missing=1 fi done if [ $missing -ne 0 ]; then return 1 fi # No platform imports inside core # Block Node builtins, React, Capacitor, and other platform-specific modules local NODE_BUILTINS="(fs|path|os|child_process|crypto|http|https|net|tls|zlib|stream|util|url|worker_threads|perf_hooks|vm)" local bad bad=$(grep -RInE \ "(from\s+['\"]|require\s*\(\s*['\"]|import\s*\(\s*['\"])(${NODE_BUILTINS}|react|@capacitor/|capacitor)['\"]" \ src/core 2>/dev/null || true) if [ -n "$bad" ]; then print_error "Core module contains forbidden platform imports:" echo "$bad" | head -50 | while read -r line; do echo " $line" done echo "" echo "Policy: src/core must not import platform, Node, or framework-specific modules." echo "Move platform-dependent code to src/web/ or platform adapters." return 1 fi print_success "Core source checks passed" echo "" } # Check core module build artifacts (must run after build) check_core_artifacts() { print_header "Core Module (Build Artifacts) Checks" cd "$PROJECT_ROOT" # Require build outputs for core local required=( "dist/esm/core/index.js" "dist/esm/core/index.d.ts" ) local missing=0 for f in "${required[@]}"; do if [ ! -f "$f" ]; then print_error "Missing build artifact: $f (did build run?)" missing=1 fi done if [ $missing -ne 0 ]; then return 1 fi # Require package.json export for ./core if ! node -e "const p=require('./package.json'); if(!p.exports||!p.exports['./core']) process.exit(1);" 2>/dev/null; then print_error "package.json missing exports['./core']" return 1 fi print_success "Core artifact checks passed" echo "" } check_native_code_in_src() { print_header "Checking for Native Code in src/" cd "$PROJECT_ROOT" # Check for Java files if find src/android -name "*.java" -type f 2>/dev/null | grep -q .; then print_error "Found Java files in src/android/ (should be removed)" find src/android -name "*.java" -type f 2>/dev/null | while read -r file; do echo " - $file" done return 1 else print_success "No Java files in src/android/" fi # Check for Swift/Objective-C files if find src/ios -name "*.swift" -o -name "*.m" -o -name "*.mm" -o -name "*.h" 2>/dev/null | grep -q .; then print_error "Found native code files in src/ios/ (should be removed)" find src/ios -name "*.swift" -o -name "*.m" -o -name "*.mm" -o -name "*.h" 2>/dev/null | while read -r file; do echo " - $file" done return 1 else print_success "No native code files in src/ios/" fi echo "" } # Version consistency check check_version_consistency() { print_header "Version Consistency Check" cd "$PROJECT_ROOT" if [ -f "scripts/check-version-consistency.sh" ]; then if bash scripts/check-version-consistency.sh; then print_success "Version consistency check passed" echo "" return 0 else print_error "Version consistency check failed" echo "" return 1 fi else print_warning "Version check script not found, skipping" echo "" return 0 fi } # Main execution main() { print_header "Daily Notification Plugin - Verification" print_environment install_dependencies # Run checks - use || true to prevent set -e from exiting on failure # We track failures in FAILED counter and exit at the end if any critical checks failed run_check "Version consistency" check_version_consistency || true run_check "Native code not in src/" check_native_code_in_src || true # Core source checks must be before build run_check "Core module source checks" check_core_source || true run_check "TypeScript typecheck" check_typescript || true run_check "Build" check_build || true # Core artifacts checks must be after build run_check "Core module artifact checks" check_core_artifacts || true run_check "Package checks" check_package || true check_android check_ios # Summary print_header "Verification Summary" echo "Passed: $PASSED" echo "Failed: $FAILED" echo "Skipped: $SKIPPED" echo "" if [ $FAILED -eq 0 ]; then print_success "All critical checks passed!" exit 0 else print_error "Some checks failed. Review output above." exit 1 fi } # Run main main "$@"