From 775da540e07e8cdc6ff26982ca5f5197b66814ff Mon Sep 17 00:00:00 2001 From: Jake Fineman Date: Thu, 3 Sep 2026 09:33:16 -0400 Subject: [PATCH] ci: add fresh-install smoke regression guard (node 20/22) Adds .github/workflows/smoke-install.yml: builds, npm packs, and installs the REAL published tarball into a throwaway project on node 20 and 22 - the class of check that catches a dependency drift like the 2026-09-01 P0 (cli 1.0.8 resolving @wave-av/sdk ^2.0.11 -> the broken 2.1.2 release, module-resolution crash on every invocation, fixed on main in PR #39 by pinning the sdk dependency to 2.0.14). Verified locally against the built 1.0.9 tarball on both node versions before pushing: npm ci, npm run build, npm pack, fresh npm i , npx wave --version, wave --help, and wave status/wave doctor under a live WAVE_GATEWAY_API_KEY via doppler - all pass. The workflow classifies any nonzero exit as a pass unless the output matches a module-resolution error pattern (ReferenceError, Cannot find module, ERR_MODULE_NOT_FOUND, ERR_REQUIRE_ESM, "is not defined in ES module scope"), since normal CLI business-logic exits (e.g. status exiting 1 when unauthenticated) are not the crash class this guard exists to catch. No secret is echoed; WAVE_GATEWAY_API_KEY only appears in an env: block, never inline in a run: string, and the job no-ops cleanly when the secret is absent (forked PRs). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01K9mRh8G2ugbUt2kaXvFvF6 --- .github/workflows/smoke-install.yml | 100 ++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/smoke-install.yml diff --git a/.github/workflows/smoke-install.yml b/.github/workflows/smoke-install.yml new file mode 100644 index 0000000..4ef21d2 --- /dev/null +++ b/.github/workflows/smoke-install.yml @@ -0,0 +1,100 @@ +name: smoke-install + +# Regression guard for the 2026-09-01 P0: `npm i @wave-av/cli` resolved a broken +# `@wave-av/sdk` release (2.1.2) through the `^2.0.11` semver range and every invocation +# died before argv parsing with a module-resolution error. That class of bug — a real, +# already-published dependency drifting a fresh install into a broken combination — only +# shows up when you install the ACTUAL published tarball against the ACTUAL registry, so +# this workflow builds the package, packs it, and installs the tarball into a throwaway +# project exactly the way a real user's `npm install` would. Unit tests and type-check +# cannot catch this: they run against the local, already-linked node_modules tree. + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node: [20, 22] + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Pack tarball + run: npm pack --pack-destination "$RUNNER_TEMP" + + - name: Fresh install from the packed tarball (real registry, real deps) + run: | + mkdir -p "$RUNNER_TEMP/smoke" + cd "$RUNNER_TEMP/smoke" + npm init -y >/dev/null + tarball=$(ls "$RUNNER_TEMP"/wave-av-cli-*.tgz | head -n1) + npm i "$tarball" + + - name: wave --version / --help (module-resolution smoke) + run: | + cd "$RUNNER_TEMP/smoke" + npx --yes wave --version + npx --yes wave --help >/dev/null + + - name: wave status / wave doctor (live gateway reachability) + working-directory: ${{ runner.temp }}/smoke + env: + WAVE_GATEWAY_API_KEY: ${{ secrets.WAVE_GATEWAY_API_KEY }} + run: | + if [ -z "$WAVE_GATEWAY_API_KEY" ]; then + echo "skipped: WAVE_GATEWAY_API_KEY absent (fork or unset)" + exit 0 + fi + export WAVE_API_KEY="$WAVE_GATEWAY_API_KEY" + + # Pass criteria: the process ran and produced a normal CLI response - a clean + # exit, or a real HTTP-level auth/scope response (401/402/403 SCOPE_INSUFFICIENT), + # or the command's own "not authenticated" business-logic exit. Fail criteria: the + # output looks like the install-time crash class this workflow exists to catch - + # a module-resolution / ESM-vs-CJS error that means the package never even started. + run_check() { + label="$1" + shift + set +e + out=$("$@" 2>&1) + code=$? + set -e + echo "$label exit=$code" + echo "$out" | head -n 20 + if echo "$out" | grep -qE 'is not defined in ES module scope|Cannot find module|ERR_MODULE_NOT_FOUND|ERR_REQUIRE_ESM|ReferenceError|SyntaxError: Unexpected token|Cannot find package'; then + echo "::error::$label failed with a module-resolution class error" + return 1 + fi + echo "$label: process started and produced a normal CLI response - pass" + return 0 + } + + run_check "wave doctor" npx --yes wave doctor + run_check "wave status" npx --yes wave status