diff --git a/scripts/blacksmith.md b/scripts/blacksmith.md index 691e930edb..d32dbf8e23 100644 --- a/scripts/blacksmith.md +++ b/scripts/blacksmith.md @@ -24,6 +24,12 @@ Run the PR checks against your current working tree: blacksmith testbox run --id "scripts/test-pr-check.sh" ``` +The script prints a ✅/❌ line after each section and a final summary with durations. It fails fast by default. To keep going and collect all failures before exiting non-zero, run: + +```bash +blacksmith testbox run --id "TEST_PR_CHECK_CONTINUE_ON_ERROR=1 scripts/test-pr-check.sh" +``` + ## Windows PR checks Testbox The Windows PR Testbox covers the Windows CLI v3 e2e matrix row. diff --git a/scripts/test-pr-check-windows.ps1 b/scripts/test-pr-check-windows.ps1 index 7994817781..3d3f40c655 100644 --- a/scripts/test-pr-check-windows.ps1 +++ b/scripts/test-pr-check-windows.ps1 @@ -1,7 +1,79 @@ $ErrorActionPreference = "Stop" +# Used inside Blacksmith Testbox runners to run full CI test suite + Set-Location (git rev-parse --show-toplevel) +function Find-NodeBin { + param([Parameter(Mandatory = $true)][string]$VersionPrefix) + + $roots = @() + if ($env:RUNNER_TOOL_CACHE) { + $roots += $env:RUNNER_TOOL_CACHE + } + $roots += @( + "C:\hostedtoolcache\windows", + "C:\hostedtoolcache", + "C:\actions-runner\_work\_tool" + ) + + foreach ($root in $roots) { + $nodeRoot = Join-Path $root "node" + if (-not (Test-Path $nodeRoot)) { + continue + } + + $match = Get-ChildItem -Path $nodeRoot -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name.StartsWith($VersionPrefix) } | + Sort-Object Name | + Select-Object -Last 1 + + if ($match) { + $bin = Join-Path $match.FullName "x64" + if (Test-Path (Join-Path $bin "node.exe")) { + return $bin + } + } + } + + return $null +} + +function Ensure-Pnpm { + $pnpmVersion = if ($env:PNPM_VERSION) { $env:PNPM_VERSION } else { "10.33.2" } + $npmPrefix = Join-Path $env:USERPROFILE ".npm-global" + $env:PATH = "$npmPrefix;$env:PATH" + + if (Get-Command pnpm -ErrorAction SilentlyContinue) { + return + } + + if (Get-Command corepack -ErrorAction SilentlyContinue) { + try { + Invoke-Native corepack prepare "pnpm@$pnpmVersion" --activate + } catch { + Write-Warning "corepack could not activate pnpm: $_" + } + } + + if (Get-Command pnpm -ErrorAction SilentlyContinue) { + return + } + + if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + throw "Unable to find pnpm or npm on PATH." + } + + New-Item -ItemType Directory -Force -Path $npmPrefix | Out-Null + Invoke-Native npm config set prefix $npmPrefix + Invoke-Native npm install -g "pnpm@$pnpmVersion" + $env:PATH = "$npmPrefix;$env:PATH" + + if (-not (Get-Command pnpm -ErrorAction SilentlyContinue)) { + throw "Unable to install pnpm." + } +} + function Start-Section { param([Parameter(Mandatory = $true)][string]$Title) @@ -39,6 +111,18 @@ function Invoke-Section { } } +if (-not $env:CI) { + $env:CI = "true" +} + +$node20Bin = Find-NodeBin "20.20" +if ($node20Bin) { + $env:PATH = "$node20Bin;$env:PATH" +} + +Ensure-Pnpm +Invoke-Native pnpm --version + Invoke-Section "Install CLI dependencies" { Invoke-Native pnpm install --frozen-lockfile --filter trigger.dev... } diff --git a/scripts/test-pr-check.sh b/scripts/test-pr-check.sh index 7ea105e2f9..c45fbee00b 100755 --- a/scripts/test-pr-check.sh +++ b/scripts/test-pr-check.sh @@ -1,21 +1,102 @@ #!/usr/bin/env bash set -euo pipefail +# Used inside Blacksmith Testbox runners to run full CI test suite + cd "$(git rev-parse --show-toplevel)" +declare -a SECTION_NAMES=() +declare -a SECTION_STATUSES=() +declare -a SECTION_DURATIONS=() +SUMMARY_PRINTED=0 + +format_duration() { + local seconds="$1" + printf "%dm%02ds" "$((seconds / 60))" "$((seconds % 60))" +} + section() { local title="$1" echo "" - echo "::group::${title}" + if [[ "${TEST_PR_CHECK_USE_GITHUB_GROUPS:-}" == "1" ]]; then + echo "::group::${title}" + else + echo "▶ ${title}" + fi } end_section() { - echo "::endgroup::" + if [[ "${TEST_PR_CHECK_USE_GITHUB_GROUPS:-}" == "1" ]]; then + echo "::endgroup::" + fi +} + +record_section() { + local title="$1" + local status="$2" + local duration="$3" + + SECTION_NAMES+=("${title}") + SECTION_STATUSES+=("${status}") + SECTION_DURATIONS+=("${duration}") } +print_summary() { + if [[ "${SUMMARY_PRINTED}" == "1" || "${#SECTION_NAMES[@]}" -eq 0 ]]; then + return 0 + fi + + SUMMARY_PRINTED=1 + echo "" + echo "PR check summary" + echo "================" + + local failures=0 + local index + for ((index = 0; index < ${#SECTION_NAMES[@]}; index++)); do + local icon="✅" + if [[ "${SECTION_STATUSES[$index]}" != "0" ]]; then + icon="❌" + failures=$((failures + 1)) + fi + + printf "%s %-42s %8s\n" \ + "${icon}" \ + "${SECTION_NAMES[$index]}" \ + "$(format_duration "${SECTION_DURATIONS[$index]}")" + done + + if [[ "${failures}" -gt 0 ]]; then + echo "" + echo "${failures} section(s) failed." + fi +} + +has_failures() { + local index + for ((index = 0; index < ${#SECTION_STATUSES[@]}; index++)); do + if [[ "${SECTION_STATUSES[$index]}" != "0" ]]; then + return 0 + fi + done + + return 1 +} + +on_exit() { + local status="$?" + print_summary + exit "${status}" +} + +trap on_exit EXIT + run_section() { local title="$1" shift + local start + start="$(date +%s)" + section "$title" local status=0 @@ -25,7 +106,23 @@ run_section() { status=$? fi + local duration + duration="$(($(date +%s) - start))" + record_section "${title}" "${status}" "${duration}" + end_section + + if [[ "${status}" == "0" ]]; then + echo "✅ ${title} passed in $(format_duration "${duration}")" + return 0 + fi + + echo "❌ ${title} failed in $(format_duration "${duration}")" + + if [[ "${TEST_PR_CHECK_CONTINUE_ON_ERROR:-}" == "1" ]]; then + return 0 + fi + return "${status}" } @@ -39,6 +136,94 @@ find_node_bin() { find /opt/hostedtoolcache/node -maxdepth 3 -type d -path "*/${version_prefix}*/x64/bin" 2>/dev/null | sort -V | tail -n 1 } +ensure_pnpm() { + local pnpm_version="${PNPM_VERSION:-10.33.2}" + local npm_prefix="${HOME}/.npm-global" + export PATH="${npm_prefix}/bin:${PATH}" + + if command -v pnpm >/dev/null 2>&1; then + return 0 + fi + + if command -v corepack >/dev/null 2>&1; then + corepack prepare "pnpm@${pnpm_version}" --activate || true + fi + + if command -v pnpm >/dev/null 2>&1; then + return 0 + fi + + if ! command -v npm >/dev/null 2>&1; then + echo "Unable to find pnpm or npm on PATH." >&2 + return 1 + fi + + mkdir -p "${npm_prefix}" + npm config set prefix "${npm_prefix}" + npm install -g "pnpm@${pnpm_version}" + export PATH="${npm_prefix}/bin:${PATH}" + + command -v pnpm >/dev/null 2>&1 +} + +find_tool_bin() { + local name="$1" + shift + + local candidate + for candidate in "$@"; do + if [[ -x "${candidate}/${name}" ]]; then + printf '%s\n' "${candidate}" + return 0 + fi + done + + if [[ -d /opt/hostedtoolcache ]]; then + local found + found="$(find /opt/hostedtoolcache -maxdepth 6 -type f -name "${name}" -perm -u+x 2>/dev/null | head -n 1)" + if [[ -n "${found}" ]]; then + dirname "${found}" + return 0 + fi + fi + + return 0 +} + +ensure_bun() { + export PATH="${HOME}/.bun/bin:${PATH}" + + if command -v bun >/dev/null 2>&1; then + return 0 + fi + + local bin_dir + if bin_dir="$(find_tool_bin bun "${HOME}/.bun/bin")" && [[ -n "${bin_dir}" ]]; then + export PATH="${bin_dir}:${PATH}" + return 0 + fi + + echo "Unable to find bun on PATH. The pr-testbox.yml warmup should install Bun; check the setup-bun step or the Testbox PATH." >&2 + return 1 +} + +ensure_deno() { + export PATH="${HOME}/.deno/bin:${PATH}" + + if command -v deno >/dev/null 2>&1; then + return 0 + fi + + local bin_dir + if bin_dir="$(find_tool_bin deno "${HOME}/.deno/bin")" && [[ -n "${bin_dir}" ]]; then + export PATH="${bin_dir}:${PATH}" + return 0 + fi + + echo "Unable to find deno on PATH. The pr-testbox.yml warmup should install Deno; check the setup-deno step or the Testbox PATH." >&2 + return 1 +} + with_node() { local node_bin="$1" shift @@ -125,6 +310,10 @@ run_sdk_runtime_compat_tests() { } export -f find_node_bin +export -f ensure_pnpm +export -f find_tool_bin +export -f ensure_bun +export -f ensure_deno export -f with_node export -f run_webapp_unit_tests export -f run_package_unit_tests @@ -134,6 +323,7 @@ export -f run_cli_e2e_tests export -f run_sdk_node_compat_tests export -f run_sdk_runtime_compat_tests +export CI="${CI:-true}" export DATABASE_URL="${DATABASE_URL:-postgresql://postgres:postgres@localhost:5432/postgres}" export DIRECT_URL="${DIRECT_URL:-postgresql://postgres:postgres@localhost:5432/postgres}" export SESSION_SECRET="${SESSION_SECRET:-secret}" @@ -146,11 +336,23 @@ export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=8192}" NODE20_BIN="${NODE20_BIN:-$(find_node_bin 20.20)}" NODE22_BIN="${NODE22_BIN:-$(find_node_bin 22.12)}" +if [[ -n "${NODE20_BIN}" ]]; then + export PATH="${NODE20_BIN}:${PATH}" +fi + +ensure_pnpm +ensure_bun +ensure_deno +pnpm --version +bun --version +deno --version + run_section "Install dependencies" pnpm install --frozen-lockfile -run_section "Generate Prisma client" pnpm run generate run_section "Format check" pnpm exec oxfmt --check . run_section "Lint" pnpm exec oxlint . + +run_section "Generate Prisma client" pnpm run generate run_section "Typecheck" pnpm run typecheck run_section "Check exports" pnpm run check-exports @@ -168,4 +370,8 @@ else fi run_section "SDK Bun/Deno/Cloudflare compatibility tests" run_sdk_runtime_compat_tests "${NODE20_BIN}" +if has_failures; then + exit 1 +fi + echo "All Linux PR checks completed."