Added check_version_consistency() function and integrated
it into main() verification flow.
Verification:
- Version check runs early in verification process ✅
605 lines
18 KiB
Bash
Executable File
605 lines
18 KiB
Bash
Executable File
#!/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 "$@"
|
||
|