diff --git a/docs/.vitepress/config/theme.mts b/docs/.vitepress/config/theme.mts index e88733c42..0a6400b5a 100644 --- a/docs/.vitepress/config/theme.mts +++ b/docs/.vitepress/config/theme.mts @@ -59,6 +59,17 @@ const defaultSidebar = [ { text: "Zed", link: "/integrations/editors/zed" }, ], }, + { + text: "CI Integrations", + collapsed: false, + items: [ + { text: "Overview", link: "/integrations/ci" }, + { text: "GitHub Actions", link: "/integrations/ci/github-actions" }, + { text: "GitLab CI", link: "/integrations/ci/gitlab" }, + { text: "Bitbucket Pipelines", link: "/integrations/ci/bitbucket" }, + { text: "Reviewdog", link: "/integrations/ci/reviewdog" }, + ], + }, { text: "Language Bindings", collapsed: false, diff --git a/docs/docs/integrations/ci.md b/docs/docs/integrations/ci.md new file mode 100644 index 000000000..b50f7dc5c --- /dev/null +++ b/docs/docs/integrations/ci.md @@ -0,0 +1,12 @@ +# Using Herb in CI + +Run the Herb [Linter](/projects/linter), [Formatter](/projects/formatter), and parser [analyzer](/bindings/ruby/reference) as part of your CI pipeline to keep HTML+ERB templates consistent and catch regressions on every push. + +## Available Integrations + +- **[GitHub Actions](/integrations/ci/github-actions)** - Inline PR annotations are enabled automatically +- **[GitLab CI](/integrations/ci/gitlab)** - Runs in any Node or Ruby image; optional Code Quality report +- **[Bitbucket Pipelines](/integrations/ci/bitbucket)** - Parallel lint, format, and analyze steps +- **[Reviewdog](/integrations/ci/reviewdog)** - Post linter findings as inline review comments + +See the [Linter](/projects/linter) and [Formatter](/projects/formatter) docs for the full list of CLI flags available in each snippet. diff --git a/docs/docs/integrations/ci/bitbucket.md b/docs/docs/integrations/ci/bitbucket.md new file mode 100644 index 000000000..5538cdd1d --- /dev/null +++ b/docs/docs/integrations/ci/bitbucket.md @@ -0,0 +1,33 @@ +--- +title: Using Herb with Bitbucket Pipelines +--- + +# Bitbucket Pipelines + +Run lint, format check, and analyzer as parallel steps. + +::: warning Formatter is in experimental preview +`@herb-tools/formatter` is in early development, and `--check` will fail on any codebase that hasn't already been run through `herb-format`. Run `npx --yes @herb-tools/formatter app/views` once and commit the result before enabling the format step. +::: + +```yaml [bitbucket-pipelines.yml] +image: node:20 + +pipelines: + default: + - parallel: + - step: + name: Herb Lint + script: + - npx --yes @herb-tools/linter --fail-level warning + - step: + name: Herb Format Check + script: + - npx --yes @herb-tools/formatter --check app/views + - step: + name: Herb Analyze + image: ruby:3.3 + script: + - gem install herb + - herb analyze . +``` diff --git a/docs/docs/integrations/ci/github-actions.md b/docs/docs/integrations/ci/github-actions.md new file mode 100644 index 000000000..75690d3dd --- /dev/null +++ b/docs/docs/integrations/ci/github-actions.md @@ -0,0 +1,82 @@ +--- +title: Using Herb with GitHub Actions +--- + +# GitHub Actions + +The linter auto-detects GitHub Actions via the `GITHUB_ACTIONS` environment variable and emits inline PR annotations by default — no extra flag required. + +## Lint + Format check + +```yaml [.github/workflows/herb.yml] +name: Herb + +on: + push: + branches: [main] + pull_request: + +jobs: + herb: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Lint HTML+ERB templates + run: npx --yes @herb-tools/linter + + - name: Check HTML+ERB formatting + run: npx --yes @herb-tools/formatter --check app/views +``` + +The formatter requires an explicit path in CI — without one it reads from stdin, which `--check` rejects. Adjust `app/views` to match where your templates live. + +::: warning Formatter is in experimental preview +`@herb-tools/formatter` prints an experimental-preview banner on every invocation, and `--check` will fail on any codebase that hasn't already been run through `herb-format`. Before wiring this step into CI, see [Adopting the formatter](#adopting-the-formatter) below. +::: + +## Adopting the formatter + +`--check` only passes on an already-formatted tree. Run the formatter once, commit the result in its own change, then enable the CI step: + +```bash +npx --yes @herb-tools/formatter app/views +``` + +Review the diff, commit it separately from unrelated changes, and only then add the `--check` step to your workflow. + +## Stricter lint gate + +Fail the build on warnings in addition to errors: + +```yaml +- name: Lint HTML+ERB templates + run: npx --yes @herb-tools/linter --fail-level warning +``` + +## Parser analysis (Ruby) + +`herb analyze` reports how many templates parse cleanly and exits non-zero when any issue is detected (see `lib/herb/cli.rb`), so it can gate the build on its own. + +Append these steps to the `herb` job above to also run the parser analyzer, or put them in a separate job if you prefer to parallelize: + +```yaml +- uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + +- name: Analyze HTML+ERB templates + run: bundle exec herb analyze . +``` + +::: tip +If your project doesn't have `herb` in its `Gemfile`, replace `bundle exec herb` with `gem install herb && herb analyze .`, and drop `bundler-cache: true` from `ruby/setup-ruby` unless another step in the job needs it. +::: + +## See also + +- [Reviewdog](/integrations/ci/reviewdog) — post linter findings as inline PR review comments diff --git a/docs/docs/integrations/ci/gitlab.md b/docs/docs/integrations/ci/gitlab.md new file mode 100644 index 000000000..27fca7901 --- /dev/null +++ b/docs/docs/integrations/ci/gitlab.md @@ -0,0 +1,60 @@ +--- +title: Using Herb with GitLab CI +--- + +# GitLab CI + +GitLab doesn't consume GitHub-style annotations, so pass `--no-github` to keep output readable in job logs. + +## Lint, format, and analyze + +```yaml [.gitlab-ci.yml] +herb:lint: + image: node:20 + script: + - npx --yes @herb-tools/linter --no-github --fail-level warning + +herb:format: + image: node:20 + script: + - npx --yes @herb-tools/formatter --check app/views + +herb:analyze: + image: ruby:3.3 + script: + - gem install herb + - herb analyze . +``` + +`herb analyze` exits non-zero when issues are detected, so no extra gating is needed. + +::: warning Formatter is in experimental preview +`@herb-tools/formatter` is in early development, and `--check` will fail on any codebase that hasn't already been run through `herb-format`. Run `npx --yes @herb-tools/formatter app/views` once and commit the result before enabling the `herb:format` job. +::: + +## Code Quality report + +GitLab's [Code Quality report](https://docs.gitlab.com/ee/ci/testing/code_quality.html) expects a specific JSON schema (`description`, `check_name`, `fingerprint`, `severity`, `location.path`, `location.lines.begin`). The linter's `--json` output isn't in that shape, so transform it with `jq`: + +```yaml +herb:lint: + image: node:20 + before_script: + - apt-get update && apt-get install -y jq + script: + - npx --yes @herb-tools/linter --no-github --json > herb-lint.raw.json || true + - | + jq '[ .offenses[] | { + description: .message, + check_name: .code, + fingerprint: (.filename + ":" + .code + ":" + (.location.start.line|tostring) + ":" + (.location.start.column|tostring)), + severity: (if .severity == "error" then "major" elif .severity == "warning" then "minor" else "info" end), + location: { path: .filename, lines: { begin: .location.start.line } } + }]' herb-lint.raw.json > gl-code-quality-report.json + artifacts: + when: always + reports: + codequality: gl-code-quality-report.json +``` + +The `|| true` keeps the job running so the `jq` step still emits the report when the linter exits non-zero. Fail the pipeline on findings in a separate job if you want gating. diff --git a/docs/docs/integrations/ci/reviewdog.md b/docs/docs/integrations/ci/reviewdog.md new file mode 100644 index 000000000..2aabaa72f --- /dev/null +++ b/docs/docs/integrations/ci/reviewdog.md @@ -0,0 +1,70 @@ +--- +title: Using Herb with Reviewdog +--- + +# Reviewdog + +[Reviewdog](https://github.com/reviewdog/reviewdog) posts linter findings as inline review comments on pull and merge requests across GitHub, GitLab, and other providers. + +The linter's `--format=simple` output is human-oriented (file on one line, then indented `line:col message`) and isn't well-suited to reviewdog's `errorformat` parser. Use `--json` and transform it to [`rdjson`](https://github.com/reviewdog/reviewdog#rdjson) with `jq` instead — the JSON shape is documented in the [Linter README](/projects/linter) and source of truth for the transform below. + +## GitHub Actions + +```yaml [.github/workflows/herb-reviewdog.yml] +name: Herb (reviewdog) + +on: [pull_request] + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + herb: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - uses: reviewdog/action-setup@v1 + + - name: Run Herb linter via reviewdog + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npx --yes @herb-tools/linter --no-github --json \ + | jq '{ + source: { name: "herb-lint" }, + diagnostics: [ .offenses[] | { + message: .message, + location: { + path: .filename, + range: { + start: { line: .location.start.line, column: .location.start.column }, + end: { line: .location.end.line, column: .location.end.column } + } + }, + severity: (.severity | ascii_upcase), + code: { value: .code } + }] + }' \ + | reviewdog -f=rdjson -name="herb-lint" -reporter=github-pr-review -fail-level=error +``` + +::: tip +The linter exits non-zero when offenses are found, but reviewdog's own `-fail-level` is what decides whether the CI step fails. Bash pipelines return the last command's exit code by default, so reviewdog's exit code wins. +::: + +## Severity mapping + +The linter emits `error`, `warning`, `info`, and `hint` severities. Reviewdog's `rdjson` accepts `ERROR`, `WARNING`, and `INFO`. The jq snippet above uppercases the severity string directly, which works for `error`/`warning`/`info`; `hint` is not a valid rdjson severity and will be rejected. If you use hint-level rules, map them explicitly: + +```jq +severity: ( + if .severity == "error" then "ERROR" + elif .severity == "warning" then "WARNING" + else "INFO" end +) +```