|
| 1 | +#!/bin/bash |
| 2 | +# |
| 3 | +# Pre-push hook to catch CI issues before pushing |
| 4 | +# Runs comprehensive CRAN-style R CMD check in temporary directory |
| 5 | +# |
| 6 | + |
| 7 | +set -e # Exit on error |
| 8 | + |
| 9 | +# First check version consistency across metadata files |
| 10 | +if [ -f ".dev/check-version-consistency.R" ]; then |
| 11 | + Rscript .dev/check-version-consistency.R |
| 12 | + VERSION_STATUS=$? |
| 13 | + echo "" |
| 14 | + |
| 15 | + if [ $VERSION_STATUS -ne 0 ]; then |
| 16 | + echo "Push cancelled." |
| 17 | + exit 1 |
| 18 | + fi |
| 19 | +fi |
| 20 | + |
| 21 | +echo "==========================================" |
| 22 | +echo " Pre-push: Spelling Validation" |
| 23 | +echo "==========================================" |
| 24 | +echo "" |
| 25 | + |
| 26 | +Rscript -e " |
| 27 | +if (requireNamespace('spelling', quietly = TRUE)) { |
| 28 | + cat('📝 Checking spelling...\n\n') |
| 29 | + results <- spelling::spell_check_package() |
| 30 | +
|
| 31 | + if (nrow(results) > 0) { |
| 32 | + cat('❌ Spelling errors found:\n\n') |
| 33 | + print(results) |
| 34 | + cat('\n') |
| 35 | + cat('Please fix spelling errors or add valid technical terms to inst/WORDLIST\n') |
| 36 | + cat('Push cancelled.\n') |
| 37 | + quit(status = 1) |
| 38 | + } |
| 39 | +
|
| 40 | + cat('✅ No spelling errors found\n') |
| 41 | + quit(status = 0) |
| 42 | +} else { |
| 43 | + cat('⚠️ spelling package not installed - skipping spell check\n') |
| 44 | + cat(' Install with: install.packages(\"spelling\")\n') |
| 45 | + quit(status = 0) |
| 46 | +} |
| 47 | +" |
| 48 | + |
| 49 | +SPELL_STATUS=$? |
| 50 | + |
| 51 | +echo "" |
| 52 | +if [ $SPELL_STATUS -ne 0 ]; then |
| 53 | + echo "Push cancelled." |
| 54 | + exit 1 |
| 55 | +else |
| 56 | + echo "==========================================" |
| 57 | + echo " ✅ Spelling check passed!" |
| 58 | + echo "==========================================" |
| 59 | +fi |
| 60 | + |
| 61 | +echo "" |
| 62 | + |
| 63 | +echo "==========================================" |
| 64 | +echo " Pre-push: CRAN-style R CMD check" |
| 65 | +echo "==========================================" |
| 66 | +echo "" |
| 67 | + |
| 68 | +# Check if we have LaTeX/TinyTeX for vignettes (non-interactive) |
| 69 | +if ! command -v pdflatex &> /dev/null; then |
| 70 | + echo "⚠️ WARNING: pdflatex not found - skipping vignette build" |
| 71 | + echo " Install TinyTeX with: Rscript .dev/install-tinytex.R" |
| 72 | + echo "" |
| 73 | + SKIP_VIGNETTES="TRUE" |
| 74 | +else |
| 75 | + SKIP_VIGNETTES="FALSE" |
| 76 | +fi |
| 77 | + |
| 78 | +# Create temporary directory for CRAN check |
| 79 | +CHECK_DIR=$(mktemp -d -t emburden-check-XXXXXX) |
| 80 | +echo "📦 Building package in temporary directory: $CHECK_DIR" |
| 81 | +echo "" |
| 82 | + |
| 83 | +# Ensure cleanup on exit |
| 84 | +cleanup() { |
| 85 | + if [ -d "$CHECK_DIR" ]; then |
| 86 | + echo "" |
| 87 | + echo "🧹 Cleaning up temporary directory..." |
| 88 | + rm -rf "$CHECK_DIR" |
| 89 | + fi |
| 90 | +} |
| 91 | +trap cleanup EXIT INT TERM |
| 92 | + |
| 93 | +# Run comprehensive CRAN check |
| 94 | +echo "🔍 Running R CMD check --as-cran..." |
| 95 | +echo " (This may take 2-3 minutes for full vignette build)" |
| 96 | +echo "" |
| 97 | + |
| 98 | +Rscript -e " |
| 99 | +# Set environment to simulate CRAN |
| 100 | +Sys.setenv( |
| 101 | + '_R_CHECK_CRAN_INCOMING_' = 'TRUE', |
| 102 | + '_R_CHECK_CRAN_INCOMING_REMOTE_' = 'FALSE', # Skip URL checks (slow) |
| 103 | + '_R_CHECK_DONTTEST_EXAMPLES_' = 'false', # Skip \donttest{} examples (matches CRAN) |
| 104 | + 'NOT_CRAN' = 'false' |
| 105 | +) |
| 106 | +
|
| 107 | +# Run comprehensive check |
| 108 | +skip_vignettes <- as.logical('$SKIP_VIGNETTES') |
| 109 | +
|
| 110 | +check_args <- c('--as-cran') |
| 111 | +build_args <- c('--no-manual') # PDF manual requires pandoc |
| 112 | +
|
| 113 | +if (skip_vignettes) { |
| 114 | + cat('⚠️ Skipping vignette build (no pdflatex)\n\n') |
| 115 | + check_args <- c(check_args, '--no-build-vignettes', '--ignore-vignettes') |
| 116 | + build_args <- c(build_args, '--no-build-vignettes') |
| 117 | +} else { |
| 118 | + # Use 'both' to match GitHub Actions workflow (tries gs+qpdf, falls back to qpdf) |
| 119 | + build_args <- c(build_args, '--compact-vignettes=both') |
| 120 | + cat('ℹ️ Using --compact-vignettes=both (matches GitHub Actions workflow)\n\n') |
| 121 | +} |
| 122 | +
|
| 123 | +cat('Check arguments:', paste(check_args, collapse=' '), '\n') |
| 124 | +cat('Build arguments:', paste(build_args, collapse=' '), '\n\n') |
| 125 | +
|
| 126 | +# Run check with rcmdcheck (cleaner output) |
| 127 | +if (requireNamespace('rcmdcheck', quietly = TRUE)) { |
| 128 | + results <- rcmdcheck::rcmdcheck( |
| 129 | + path = '.', |
| 130 | + args = check_args, |
| 131 | + build_args = build_args, |
| 132 | + error_on = 'never', # Don't stop, show results |
| 133 | + check_dir = '$CHECK_DIR' |
| 134 | + ) |
| 135 | +
|
| 136 | + # Print summary |
| 137 | + cat('\n') |
| 138 | + cat(strrep('=', 60), '\n') |
| 139 | + cat('R CMD check results:\n') |
| 140 | + cat(strrep('=', 60), '\n') |
| 141 | + print(results) |
| 142 | +
|
| 143 | + # Check for errors or warnings (fail on errors only, allow warnings) |
| 144 | + if (length(results\$errors) > 0) { |
| 145 | + cat('\n❌ ERRORS found:\n') |
| 146 | + cat(paste(results\$errors, collapse='\n'), '\n') |
| 147 | + quit(status = 1) |
| 148 | + } |
| 149 | +
|
| 150 | + if (length(results\$warnings) > 0) { |
| 151 | + cat('\n⚠️ WARNINGS found (allowed, push will continue):\n') |
| 152 | + cat(paste(results\$warnings, collapse='\n'), '\n') |
| 153 | + # Don't fail on warnings - CI will catch critical ones |
| 154 | + } |
| 155 | +
|
| 156 | + if (length(results\$notes) > 0) { |
| 157 | + cat('\nℹ️ NOTES:\n') |
| 158 | + cat(paste(results\$notes, collapse='\n'), '\n') |
| 159 | + # Notes are OK, don't fail |
| 160 | + } |
| 161 | +
|
| 162 | + cat('\n✅ R CMD check passed!\n') |
| 163 | + quit(status = 0) |
| 164 | +
|
| 165 | +} else { |
| 166 | + cat('Installing rcmdcheck package...\n') |
| 167 | + install.packages('rcmdcheck', repos = 'https://cran.rstudio.com') |
| 168 | + cat('❌ Please re-run push after rcmdcheck is installed.\n') |
| 169 | + quit(status = 1) |
| 170 | +} |
| 171 | +" |
| 172 | + |
| 173 | +CHECK_STATUS=$? |
| 174 | + |
| 175 | +echo "" |
| 176 | +if [ $CHECK_STATUS -ne 0 ]; then |
| 177 | + echo "❌ R CMD check --as-cran failed with errors!" |
| 178 | + echo " Fix errors before pushing. Push cancelled." |
| 179 | + exit 1 |
| 180 | +else |
| 181 | + echo "==========================================" |
| 182 | + echo " ✅ CRAN check passed (or warnings only)!" |
| 183 | + echo "==========================================" |
| 184 | +fi |
| 185 | + |
| 186 | +echo "" |
| 187 | + |
| 188 | +# Optional: CRAN submission dry-run test |
| 189 | +if [ "$SKIP_CRAN_DRYRUN" != "true" ]; then |
| 190 | + echo "==========================================" |
| 191 | + echo " Pre-push: CRAN Submission Dry-Run" |
| 192 | + echo "==========================================" |
| 193 | + echo "" |
| 194 | + echo "Testing CRAN submission process (no actual submission)..." |
| 195 | + echo "" |
| 196 | + |
| 197 | + Rscript -e " |
| 198 | + # Find the tarball from R CMD build |
| 199 | + tarball <- list.files(pattern = 'emburden_.*\\.tar\\.gz$', full.names = TRUE) |
| 200 | + if (length(tarball) == 0) { |
| 201 | + cat('⚠️ No tarball found - skipping CRAN submission test\n') |
| 202 | + quit(status = 0) |
| 203 | + } |
| 204 | +
|
| 205 | + tarball <- tarball[1] |
| 206 | + cat('Found tarball:', tarball, '\n\n') |
| 207 | +
|
| 208 | + # Test CRAN submission preparation (doesn't actually submit) |
| 209 | + if (requireNamespace('devtools', quietly = TRUE)) { |
| 210 | + cat('📋 Checking CRAN submission readiness...\n\n') |
| 211 | +
|
| 212 | + # Check if package can be submitted |
| 213 | + tryCatch({ |
| 214 | + # This checks the package is ready but doesn't submit |
| 215 | + devtools::check_built(tarball, cran = TRUE, remote = FALSE, manual = FALSE) |
| 216 | + cat('\n✅ CRAN submission dry-run passed!\n') |
| 217 | + cat(' Package is ready for CRAN submission.\n') |
| 218 | + }, error = function(e) { |
| 219 | + cat('\n❌ CRAN submission test failed:\n') |
| 220 | + cat(' ', conditionMessage(e), '\n') |
| 221 | + cat('\nNote: This is just a dry-run test. Fix issues before actual submission.\n') |
| 222 | + # Don't fail the push on dry-run errors |
| 223 | + }) |
| 224 | + } else { |
| 225 | + cat('ℹ️ devtools not installed - skipping CRAN submission test\n') |
| 226 | + } |
| 227 | + " || true # Don't fail push if dry-run has issues |
| 228 | + |
| 229 | + echo "" |
| 230 | + echo "==========================================" |
| 231 | +else |
| 232 | + echo "ℹ️ Skipping CRAN submission dry-run (SKIP_CRAN_DRYRUN=true)" |
| 233 | + echo "" |
| 234 | +fi |
| 235 | + |
| 236 | +echo "🚀 Proceeding with push..." |
| 237 | +exit 0 |
0 commit comments