truss-image 0.16.0

Image toolkit with a shared Rust core across the CLI, HTTP server, and WASM demo.
Documentation
name: Build/Test/Lint

on:
  push:
    branches:
      - main
    paths:
      - "src/**"
      - "tests/**"
      - "examples/**"
      - "packages/**"
      - "scripts/**"
      - "justfile"
      - "Cargo.toml"
      - "Cargo.lock"
      - ".github/release-targets.json"
      - ".github/workflows/release.yml"
      - ".github/workflows/rust.yml"
  pull_request:
    paths:
      - "src/**"
      - "tests/**"
      - "examples/**"
      - "packages/**"
      - "scripts/**"
      - "justfile"
      - "Cargo.toml"
      - "Cargo.lock"
      - ".github/release-targets.json"
      - ".github/workflows/release.yml"
      - ".github/workflows/rust.yml"

concurrency:
  group: rust-${{ github.ref }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always

jobs:
  # ── Always: Ubuntu test + lint (PR and main) ────────────────────────
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Install nextest
        uses: taiki-e/install-action@nextest

      - name: Run tests
        run: cargo nextest run --all-features --all-targets

      - name: Run doc tests
        run: cargo test --all-features --doc

  clippy:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Run clippy
        run: cargo clippy --all-features --all-targets -- -D warnings

  fmt:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt

      - name: Check formatting
        run: cargo fmt --all -- --check

  doc:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Build documentation
        run: cargo doc --no-deps
        env:
          RUSTDOCFLAGS: -D warnings

  wasm-package:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-unknown-unknown

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 22

      - name: Cache cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Read Wasm package config
        id: wasm-package-config
        shell: bash
        run: |
          version="$(node -p "require('./packages/truss-wasm/package.json').trussWasmBuild.wasmBindgenVersion")"
          echo "wasm_bindgen_version=${version}" >> "$GITHUB_OUTPUT"

      - name: Install wasm-bindgen CLI
        shell: bash
        run: |
          version="${{ steps.wasm-package-config.outputs.wasm_bindgen_version }}"
          if [ "$(wasm-bindgen --version 2>/dev/null || true)" != "wasm-bindgen ${version}" ]; then
            cargo install wasm-bindgen-cli --version "${version}"
          fi

      - name: Verify TypeScript URL signer package version metadata
        run: cd packages/truss-url-signer && npm run check:version

      - name: Compile-check TypeScript URL signer package definitions
        run: cd packages/truss-url-signer && npm run typecheck

      - name: Test TypeScript URL signer package
        run: cd packages/truss-url-signer && npm test

      - name: Pack TypeScript URL signer package
        run: cd packages/truss-url-signer && npm pack --dry-run

      - name: Pack npm Wasm package
        run: cd packages/truss-wasm && npm pack --dry-run

      - name: Run Wasm consumer smoke test
        run: node ./scripts/run-wasm-consumer-smoke.mjs

      - name: Run Vite Wasm example smoke test
        run: node ./scripts/run-wasm-vite-example-smoke.mjs

      - name: Run Vite example runtime smoke test
        if: runner.os == 'Linux'
        run: node ./scripts/run-wasm-vite-example-runtime-smoke.mjs

  # ── Release distribution tooling ───────────────────────────────────
  # Covers the release manifest generator and verifier without needing a tag:
  # the end-to-end test packs stand-in executables with the same script the
  # release workflow uses, then generates and verifies a manifest over them.
  release-scripts:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 22

      # Run on all three runner families: the packing script takes a different
      # branch for GNU tar, bsdtar and ZIP, and each release job exercises only
      # the branch its own runner has.
      - name: Test release manifest tooling
        run: node --test scripts/lib/release-manifest.test.mjs scripts/release-artifacts.test.mjs

      - name: Check the pinned release toolchain against rust-version
        if: runner.os == 'Linux'
        run: |
          toolchain="$(grep -oP '^\s*RUST_TOOLCHAIN:\s*"\K[^"]+' .github/workflows/release.yml)"
          node ./scripts/check-rust-toolchain-pin.mjs --toolchain "${toolchain}"

  # ── Next.js example smoke test ─────────────────────────────────────
  nextjs-example:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: 22

      - name: Run Next.js example smoke test
        run: node ./scripts/run-nextjs-example-smoke.mjs

  # ── Cross-platform tests (PR and main) ──────────────────────────────
  test-cross-platform:
    strategy:
      matrix:
        os: [macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Install nextest
        uses: taiki-e/install-action@nextest

      - name: Run tests
        run: cargo nextest run --all-targets

      - name: Run doc tests
        run: cargo test --doc

  # ── OpenAPI spec validation ─────────────────────────────────────────
  openapi-validate:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Install openapi-spec-validator
        run: pip install openapi-spec-validator

      - name: Validate OpenAPI spec
        run: python -m openapi_spec_validator docs/openapi.yaml

  # ── security audit ──────────────────────────────────────────────────
  security-audit:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Run security audit
        uses: rustsec/audit-check@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}