Skip to content

ci(app): add missing tsx devDependency for the test script #5

ci(app): add missing tsx devDependency for the test script

ci(app): add missing tsx devDependency for the test script #5

Workflow file for this run

name: CI
# Gates every PR + push to main on:
# - TypeScript: lint, typecheck, tests, production bundle build
# - Rust: cargo check + clippy + unit tests against the program
# Both job families run in parallel; failures fail the workflow.
#
# Deliberately NOT here:
# - cargo build-sbf (requires Solana toolchain, slow ~5m, deferred to
# a future job that runs on a schedule or workflow_dispatch only).
# - pnpm --filter @pushflip/dealer test (LiteSVM-based; runs locally,
# adds CI minutes for marginal value — re-enable once dealer
# productionization stabilizes).
# - Container build smoke (covered end-to-end by deploy-tucker.sh).
on:
pull_request:
paths-ignore:
- 'wiki/**'
- 'docs/wiki/**'
- 'scripts/wiki-health-check.sh'
- '.github/workflows/wiki-build.yml'
- '.github/workflows/telegram-notify.yml'
- 'notes.md'
push:
branches: [main]
paths-ignore:
- 'wiki/**'
- 'docs/wiki/**'
- 'scripts/wiki-health-check.sh'
- '.github/workflows/wiki-build.yml'
- '.github/workflows/telegram-notify.yml'
- 'notes.md'
workflow_dispatch:
# Least-privilege token. The workflow only needs to read repo contents
# (checkout) — no writes, no PR comments, no package operations. Setting
# this explicitly closes the org/repo default which is typically
# `contents: write` and would let a compromised dep push to the repo
# via the workflow's GITHUB_TOKEN. Defense-in-depth.
permissions:
contents: read
# Cancel an in-flight CI run when a new push lands on the same ref.
# Saves CI minutes and gives faster feedback on the head commit.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
typescript:
name: TypeScript (lint + typecheck + test + build)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: pnpm
- name: Install workspace deps
run: pnpm install --frozen-lockfile
# The repo-level `.npmrc` already sets `ignore-scripts=true`,
# which skips biome/ultracite postinstalls AND better-sqlite3's
# `prebuild-install || node-gyp rebuild` install script. The
# better-sqlite3 binding is restored by the dedicated step
# below. The `--ignore-scripts` CLI flag was previously passed
# here too — it was redundant with the .npmrc setting, so
# removed for clarity.
- name: Build better-sqlite3 native binding
# The repo's `.npmrc ignore-scripts=true` blocks every form of
# `pnpm install/rebuild` from running install scripts, even
# with explicit allow-lists like `pnpm.onlyBuiltDependencies`
# — verified empirically (commit 7f8e646's `pnpm rebuild
# better-sqlite3` step ran in 700ms with zero output and
# produced no binding). The reliable bypass is to invoke the
# package's own install script directly via `npm run install`
# from its directory: that runs `prebuild-install`, which
# downloads the precompiled binding from upstream's GitHub
# release for the current (node-20, linux-x64) tuple. The
# `bash -c` form lets the glob expand the pnpm-store path
# without committing to a specific better-sqlite3 version.
run: |
cd node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3
npm run install
- name: Verify better-sqlite3 native binding loads
# Load-bearing guard. `require('better-sqlite3')` alone does
# NOT trigger the binding load — better-sqlite3's lib/database.js
# only calls `require('bindings')('better_sqlite3.node')` lazily
# inside the Database constructor (line 48). So this step
# instantiates an in-memory Database AND executes a query,
# which forces the .node file to load. Without this, a missing
# binding would slip past the smoke and only surface inside
# `pnpm test`, several steps later, with a less actionable
# failure context.
run: pnpm --filter @pushflip/faucet exec node -e "const D=require('better-sqlite3'); const db=new D(':memory:'); db.exec('SELECT 1'); db.close()"
- name: Lint (app)
run: pnpm --filter @pushflip/app lint
- name: Lint (faucet)
run: pnpm --filter @pushflip/faucet lint
- name: Typecheck (client)
run: pnpm --filter @pushflip/client exec tsc --noEmit
- name: Typecheck (app)
run: pnpm --filter @pushflip/app typecheck
- name: Typecheck (faucet)
run: pnpm --filter @pushflip/faucet typecheck
- name: Typecheck (scripts)
run: pnpm --filter @pushflip/scripts exec tsc --noEmit
- name: Test (client)
run: pnpm --filter @pushflip/client test
- name: Test (faucet)
run: pnpm --filter @pushflip/faucet test
- name: Test (app)
run: pnpm --filter @pushflip/app test
- name: Build (app — proves Vite production bundle compiles)
env:
# Match the deploy-script defaults so the build doesn't fail
# on the production-only env-var-required guards in
# use-faucet.ts / use-display-name.ts.
VITE_FAUCET_URL: /api/faucet
VITE_NICKNAME_URL: /api/nickname
VITE_RPC_ENDPOINT: https://api.devnet.solana.com
VITE_RPC_WS_ENDPOINT: wss://api.devnet.solana.com
run: pnpm --filter @pushflip/app build
- name: Summary
if: always()
run: |
{
echo "## TypeScript CI summary"
echo
echo "- pnpm: $(pnpm --version)"
echo "- node: $(node --version)"
echo "- workspaces: $(jq -r '.workspaces[]' package.json | wc -l)"
} >> "$GITHUB_STEP_SUMMARY"
rust:
name: Rust (check + clippy + test)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
with:
# Scope cache to the workspace; the test harness shares the
# same target dir as the program so cache reuse is high.
shared-key: pushflip-rust
- name: Cargo check (all targets)
# Exclude the integration-test crate: its build.rs invokes
# `cargo build-sbf`, which requires the Solana platform-tools
# that this runner intentionally doesn't install (see the
# workflow header — sbf builds are deferred to a slower job).
# `cargo check --all-targets` would still run that build.rs
# and panic. The test crate's source IS still type-checked,
# via the dedicated `Cargo check (integration test crate)`
# step below using `PUSHFLIP_SKIP_SBF=1`.
run: cargo check --workspace --exclude pushflip-tests --all-targets --locked
- name: Cargo clippy (all targets, report-only)
# `-D warnings` would block every PR on the ~24 deferred
# dead-code warnings tracked under Task 3.B.End. Running
# without `-D warnings` so the report still appears in the
# job log + summary, but doesn't block. Future tightening
# (once the deferred warnings are cleaned up) should add
# `-- -D warnings` here.
# `--exclude pushflip-tests`: same reason as Cargo check above.
run: cargo clippy --workspace --exclude pushflip-tests --all-targets --locked 2>&1 | tee clippy.log
continue-on-error: false
- name: Cargo clippy summary
# Note: NOT `if: always()`. If the prior clippy step failed
# (which would be a `cargo clippy` invocation error, not a
# warning count), `clippy.log` may be missing or partial and
# this summary would lie. Letting the summary skip in that
# case keeps the GitHub run page honest.
run: |
if [ ! -s clippy.log ]; then
echo "clippy.log missing or empty — clippy step failed before producing output" >&2
exit 1
fi
# `^warning:` (with the colon) matches individual diagnostic
# headers and skips the per-crate roll-up like
# `warning: pushflip (lib) generated 8 warnings`.
# `^error:` is similarly precise.
warning_count=$(grep -c '^warning:' clippy.log || true)
error_count=$(grep -c '^error:' clippy.log || true)
{
echo "### Clippy"
echo
echo "- Warnings: $warning_count"
echo "- Errors: $error_count"
} >> "$GITHUB_STEP_SUMMARY"
# Fail the step if clippy produced ANY error (not warning).
# Errors here would mean a real lint regression, not a
# deferred dead-code warning.
[ "$error_count" -eq 0 ]
- name: Cargo test (program unit tests)
# `-p pushflip` skips the integration-test crate which requires
# `cargo build-sbf` to have run first. That's a separate slower
# job (deferred — see workflow comment header).
run: cargo test -p pushflip --locked
- name: Cargo check (integration test crate)
# Type-checks tests/src/*.rs (~1.5k LOC of LiteSVM-based
# integration tests) WITHOUT invoking `cargo build-sbf`.
# `PUSHFLIP_SKIP_SBF=1` makes tests/build.rs short-circuit and
# write a stub `pushflip-skip-sbf-stub.so` so the
# `include_bytes!(env!("PUSHFLIP_TEST_SBF_PATH"))` in the test
# source still resolves at compile time. This catches accidental
# compile breakage in the integration test crate that the
# `--exclude pushflip-tests` step above would otherwise let
# through. ACTUALLY RUNNING the tests is the deferred
# SBF-toolchain job's responsibility; this step is a typecheck
# gate only.
env:
PUSHFLIP_SKIP_SBF: "1"
run: cargo check -p pushflip-tests --tests --locked
- name: Summary
if: always()
run: |
{
echo "## Rust CI summary"
echo
echo "- rustc: $(rustc --version)"
echo "- cargo: $(cargo --version)"
} >> "$GITHUB_STEP_SUMMARY"