Files
daily-notification-plugin/scripts/verify.sh
Matthew Raymer b72d2e27e3 feat(ci): Add version consistency check function to verify.sh
Added check_version_consistency() function and integrated
it into main() verification flow.

Verification:
- Version check runs early in verification process 
2025-12-23 07:56:05 +00:00

605 lines
18 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"