grr-cli 2.3.0

Google tools from the terminal, at maximum performance: 401 methods across 14 Google APIs in one generated command tree, zero-config, over HTTP/3
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: "0 4 * * 1"
  workflow_dispatch:

permissions:
  contents: read

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

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    if: github.event_name != 'schedule'
    name: test (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest]
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 5
        with:
          components: rust-src
      - uses: Swatinem/rust-cache@v2
        timeout-minutes: 5
        with:
          cache-bin: true
      - name: Install cargo-nextest
        uses: taiki-e/install-action@v2
        timeout-minutes: 5
        with:
          # The `nextest` tag this used to float on is an unversioned per-tool
          # ref from the pre-v2 era; v2 is the maintained line and names the
          # tool explicitly, so Dependabot can see what it is bumping.
          tool: nextest
      # No `cargo build` step before this: `cargo nextest run` compiles the
      # same lib + bin units, so a separate build only paid for that compile
      # twice on a cold cache. A build error surfaces here instead.
      - name: Test
        timeout-minutes: 30
        run: cargo nextest run --all-features
      - name: Test doctests
        timeout-minutes: 15
        run: cargo test --doc

  clippy:
    if: github.event_name != 'schedule'
    name: clippy
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 5
        with:
          components: clippy, rust-src
      - uses: Swatinem/rust-cache@v2
        timeout-minutes: 5
      - name: Lint
        run: cargo clippy --all-targets -- -D warnings
      - name: Package the crate (publish / docs.rs gate)
        timeout-minutes: 10
        # The documented no-env gate (AGENTS.md): `cargo package --allow-dirty`
        # must succeed with no GRR_* env and no .env present. It parses and
        # packages the manifest exactly as the published crate sees it, which is
        # where the docs.rs break lived — ordinary build/clippy never validate
        # that package-level view. Runs here (the single-OS ubuntu Rust job, not
        # the ubuntu+windows test matrix) because the check is platform-
        # independent and duplicating it per matrix leg is pure waste.
        run: cargo package --allow-dirty

  fmt:
    if: github.event_name != 'schedule'
    name: fmt
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 5
        with:
          # This job only runs `cargo fmt`; rust-src was carried over from the
          # other jobs but nothing here needs it.
          components: rustfmt
      - name: Check formatting
        run: cargo fmt --all -- --check

  scripts:
    if: github.event_name != 'schedule'
    name: scripts typecheck
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: actions/setup-node@v7
        timeout-minutes: 5
        with:
          node-version: "26"
          cache: npm
          cache-dependency-path: package-lock.json
      - name: Install script toolchain
        run: npm install
      - name: Typecheck scripts
        run: npm run typecheck
      - name: Generated artifacts are current
        timeout-minutes: 5
        # These gates used to run inside the `test` matrix, which executed
        # them twice per PR (once on Linux, once on Windows). They compare
        # committed bytes against generator output, which is platform-
        # independent, so one Linux run covers both matrix legs.
        run: |
          node scripts/generate-commands.ts --check
          node scripts/generate-skills.ts --check
          node scripts/generate-coverage.ts --check

  site:
    # The site deploys straight from main (Cloudflare Pages builds it on
    # push), so a broken .astro file or a bad import would ship before anyone
    # noticed. Nothing else in this workflow compiles the site, which is why
    # this job exists.
    if: github.event_name != 'schedule'
    name: site (build + check)
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: actions/setup-node@v7
        timeout-minutes: 5
        with:
          node-version: "26"
          cache: npm
          cache-dependency-path: site/package-lock.json
      - name: Install site dependencies
        run: npm install
        working-directory: site
      - name: Type-check the Astro sources
        run: npm run check
        working-directory: site
      # Generated mascot assets plus the CLI-banner parity (AGENTS invariant
      # 13: src/logo.rs's map must be a crop of this generator's idle map,
      # with one palette). A separate step on purpose — `npm run check` is
      # astro check, this is the asset generator's own gate.
      - name: Check generated mascot assets + banner parity
        run: node scripts/generate-mascot.mjs --check
        working-directory: site
      - name: Build the site
        run: npm run build
        working-directory: site

  feature-subset:
    # The per-service cargo features are gone; the one meaningful subset left
    # is a CLI-only build (`--no-default-features --features cli`). Lib+bins
    # only, deliberately not --all-targets: the integration tests exercise
    # the curated clients, which are not part of this subset.
    if: github.event_name != 'schedule'
    name: feature-subset (cli)
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 5
        with:
          components: clippy, rust-src
      - uses: Swatinem/rust-cache@v2
        timeout-minutes: 5
        with:
          # Distinct key from the clippy job: a different feature set means a
          # different target dir; sharing a key would evict each other.
          key: cli-only
      - name: Clippy (no default features, cli only)
        run: cargo clippy --no-default-features --features cli -- -D warnings

  security:
    name: security audit
    runs-on: ubuntu-24.04-arm
    timeout-minutes: 15
    # An advisory must not block unrelated PRs: the audit still reports in the
    # job log and summary, but a new RUSTSEC advisory against a transitive
    # dependency is not a reason to red a docs or feature PR. On the weekly
    # schedule there is no unrelated PR, so there the audit is a real gate —
    # advisory-only everywhere would mean it can never red anything at all.
    continue-on-error: ${{ github.event_name == 'pull_request' }}
    permissions:
      contents: read
      issues: write
      checks: write
    steps:
      - uses: actions/checkout@v7
        timeout-minutes: 2
      - uses: dtolnay/rust-toolchain@nightly
        timeout-minutes: 5
      # Cargo.lock is deliberately untracked (.gitignore): every build resolves
      # the latest semver-compatible versions instead of pinning. cargo-audit,
      # however, reads a lockfile, and rustsec/audit-check passes
      # `--file ./Cargo.lock` unconditionally — with no lock in the tree the
      # job failed on EVERY run ("not found: Couldn't load ./Cargo.lock" →
      # "Unexpected end of JSON input"), invisibly, because continue-on-error
      # below keeps the run green. Resolve the graph the way a build does and
      # audit that: the versions a fresh build will use are the ones worth
      # checking against advisories.
      - name: Resolve the lockfile the audit reads
        timeout-minutes: 5
        run: cargo generate-lockfile
      - name: Audit Rust dependencies
        uses: rustsec/audit-check@v2
        timeout-minutes: 10
        with:
          token: ${{ secrets.GITHUB_TOKEN }}