tw-cli 0.1.0

One command for a Markdoc corpus
# CI for tw — https://github.com/markdoc-rs/tw
#
# The gate itself is in `.github/scripts/check.sh`, not here, so release.yml
# runs exactly the same set before it publishes anything. A second list of
# steps drifts, and the half that drifts is always the one nobody watches.
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  check:
    name: Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          path: tw

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: tw

      - name: check.sh
        run: bash tw/.github/scripts/check.sh tw

      # The one check that is about a corpus rather than about the code.
      #
      # Deliberately a corpus built here rather than a fixture in another
      # repository: markdoc-lint/examples holds a markdoc-lint.toml and no
      # documents, so a run over it indexes zero files and would pass with
      # every rule removed. Two files and a known fault cost four lines and
      # exercise the config load, the index, the registry, the document
      # pass, the JSON envelope and the exit code.
      - name: Find a fault in a corpus, and say so in JSON
        run: |
          set -eu
          mkdir -p /tmp/corpus
          : > /tmp/corpus/markdoc-lint.toml
          printf '# Fine\n\nA paragraph.\n' > /tmp/corpus/ok.md
          printf '# Not fine\n\n{%% callout %%}\nbody\n' > /tmp/corpus/bad.md

          set +e
          ./tw/target/release/tw check --no-prose --json --root /tmp/corpus > /tmp/report.json
          code=$?
          set -e

          cat /tmp/report.json
          # Exit 1 is "found things", and it is what a hook and a CI step
          # both read. A green run on a corpus with a broken tag in it is
          # the failure this step exists to catch.
          [ "$code" = 1 ] || { echo "::error::tw check exited ${code} on a corpus with an unclosed tag" >&2; exit 1; }
          grep -q '"rule": "unclosed-tag"' /tmp/report.json || {
              echo "::error::tw check did not report the unclosed tag" >&2; exit 1; }
          # The envelope is the contract [[check.external]] rests on: an
          # object with a diagnostics array. Anything else and it has moved.
          grep -q '"diagnostics"' /tmp/report.json || {
              echo "::error::tw check --json printed no diagnostics key" >&2; exit 1; }
          echo "tw check finds a fault and reports it as JSON"

  # ────────────────────────────────────────────────────────────────────
  # tw is a cross-platform CLI and most of the people who run it are on
  # Windows. The gate above only proves Linux; this proves the tests pass
  # where the path handling, the executable suffix and the self-replace
  # rename actually differ.
  # ────────────────────────────────────────────────────────────────────
  test:
    name: test ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [windows-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
        with:
          path: tw

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: tw
          key: test-${{ matrix.os }}

      - name: cargo test
        working-directory: tw
        run: cargo test --all-targets