feat(html): support canonical HTML document creation #433
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| # Root-level Markdown and docs are documentation-only. Markdown under | |
| # skills/ is intentionally NOT ignored: skills are embedded in the CLI | |
| # binary, so changing them must produce CI evidence for release gates. | |
| - '*.md' | |
| - 'docs/**' | |
| - '.github/ISSUE_TEMPLATE/**' | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] | |
| branches: [main] | |
| # paths-ignore is intentionally NOT set here — workflow-level path filtering | |
| # prevents GitHub from creating any check run, which deadlocks required-status | |
| # checks on docs-only PRs. Path-based skipping is handled by the `changes` | |
| # preflight job below instead (dorny/paths-filter produces "skipped" statuses | |
| # that GitHub counts as passing for required checks). | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| changes: | |
| name: Detect changed paths | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| # code=true when at least one changed file is runtime code or an embedded | |
| # Skill Markdown file. Push events always run once the workflow-level | |
| # path filter admits them. | |
| # predicate-quantifier:every + negation patterns (no bare '**'): | |
| # a file matches 'code' iff ALL negation patterns are satisfied | |
| # (file is not a doc/template). The separate embedded_skills filter | |
| # re-includes skills/**/*.md because those files ship in the binary. | |
| code: ${{ github.event_name != 'pull_request' || steps.filter.outputs.code == 'true' || steps.filter.outputs.embedded_skills == 'true' }} | |
| steps: | |
| # No checkout: on pull_request events dorny/paths-filter lists changed | |
| # files via the GitHub API (needs only pull-requests: read, granted | |
| # above), so a working tree is unnecessary here. | |
| - uses: dorny/paths-filter@6852f92c20ea7fd3b0c25de3b5112db3a98da050 # v3 | |
| id: filter | |
| if: github.event_name == 'pull_request' | |
| with: | |
| # predicate-quantifier: every — a file matches 'code' only when ALL | |
| # negation patterns are satisfied (file is NOT a doc/template file). | |
| # | |
| # Validated by picomatch v4 unit test (same library used internally): | |
| # README.md: !**/*.md=false → every=false → not match → code=false ✅ | |
| # main file: all negations=true → every=true → match → code=true ✅ | |
| # mixed PR: code file matches → code=true (some over changed files) ✅ | |
| # dotfiles: all negations=true → every=true → CI runs ✅ | |
| # Dockerfile: all negations=true → CI runs ✅ | |
| # | |
| # Do NOT add '**' as an inclusion pattern: with the 'every' quantifier | |
| # the bare '**' pattern adds no useful constraint and risks regressing | |
| # the negation logic on future picomatch upgrades. Stick to negation-only. | |
| # (dorny/paths-filter v3 already sets picomatch dot:true so dotfiles like | |
| # .golangci.yml ARE matched by '**' under v3 — earlier versions did not.) | |
| # Do NOT use 'some' + negation: every changed file matches at least one | |
| # negation pattern (e.g. main.go matches !**/*.md), so some=true always | |
| # and the skip logic becomes a no-op. | |
| predicate-quantifier: 'every' | |
| filters: | | |
| code: | |
| - '!**/*.md' | |
| - '!docs/**' | |
| - '!.github/ISSUE_TEMPLATE/**' | |
| embedded_skills: | |
| - 'skills/**/*.md' | |
| build: | |
| name: build & test | |
| needs: [changes] | |
| # Draft guard stays at the job level. The code-vs-docs skip is enforced | |
| # per-step (below) instead, so the matrix child check | |
| # `build & test (1.24.x)` is always emitted (success on docs-only PRs, | |
| # real run on code PRs). Job-level `if: code == 'true'` would skip the | |
| # whole job BEFORE matrix expansion, so the parenthesised child check | |
| # required by the repository ruleset (id 16222449) would never be | |
| # created — recreating the very deadlock this workflow is meant to fix, | |
| # just on docs-only PRs instead of all PRs. | |
| if: github.event_name != 'pull_request' || !github.event.pull_request.draft | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Single-value matrix intentionally retained. The repository ruleset | |
| # (id 16222449, "main branch protection") pins the exact check name | |
| # "build & test (1.24.x)" — the parenthesised form GitHub emits only | |
| # for matrix jobs. Dropping the matrix renames the check to | |
| # "build & test" and breaks the required-status gate on every PR. | |
| # Add new Go versions here instead of removing the matrix. | |
| go-version: ["1.24.x"] | |
| steps: | |
| - name: skip notice (docs-only PR) | |
| if: needs.changes.outputs.code != 'true' | |
| run: echo "docs-only PR — skipping build & test (matrix=${{ matrix.go-version }})" | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| if: needs.changes.outputs.code == 'true' | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 | |
| if: needs.changes.outputs.code == 'true' | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| check-latest: true | |
| - name: gofmt | |
| if: needs.changes.outputs.code == 'true' | |
| run: | | |
| out=$(gofmt -l . | grep -v '^vendor/' || true) | |
| if [ -n "$out" ]; then | |
| echo "gofmt needed on:" | |
| echo "$out" | |
| exit 1 | |
| fi | |
| - name: go vet | |
| if: needs.changes.outputs.code == 'true' | |
| run: go vet ./... | |
| - name: go mod tidy check | |
| if: needs.changes.outputs.code == 'true' | |
| run: | | |
| go mod tidy | |
| if ! git diff --exit-code go.mod go.sum; then | |
| echo "go.mod / go.sum drift — run 'go mod tidy' and commit" | |
| exit 1 | |
| fi | |
| - name: golangci-lint | |
| if: needs.changes.outputs.code == 'true' | |
| uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 | |
| with: | |
| version: v2.12.2 | |
| - name: test (race, coverage) | |
| if: needs.changes.outputs.code == 'true' | |
| run: go test -race -shuffle=on -count=1 -covermode=atomic -coverprofile=coverage.out ./... | |
| - name: coverage summary | |
| if: needs.changes.outputs.code == 'true' | |
| run: go tool cover -func=coverage.out | tail -n 20 | |
| - name: build | |
| if: needs.changes.outputs.code == 'true' | |
| run: go build -o octo-cli ./cmd/octo-cli | |
| npm-test: | |
| name: npm test | |
| needs: [changes] | |
| # Job-level skip is intentional here and asymmetric with `build` above. | |
| # `npm test` is NOT a required status check (ruleset 16222449 pins only | |
| # "build & test (1.24.x)" and "code-review"), so skipping the whole job on | |
| # docs-only PRs is safe — there is no required check to keep alive. `build` | |
| # must use per-step gating instead because skipping it job-level would drop | |
| # its required matrix child check and re-create the deadlock. Do not "align" | |
| # these two jobs onto the same strategy. | |
| if: | | |
| (github.event_name != 'pull_request' || !github.event.pull_request.draft) && | |
| needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| defaults: | |
| run: | |
| working-directory: npm | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: "22" | |
| - name: npm test | |
| run: npm test |