Skip to content

feat(frontend): make the role grant's environment field a direct-execution switch #429

feat(frontend): make the role grant's environment field a direct-execution switch

feat(frontend): make the role grant's environment field a direct-execution switch #429

Workflow file for this run

name: CI
on:
pull_request:
branches:
- 'main'
- 'release/*.*.*'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
detect-changed-files:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
go: ${{ steps.detect.outputs.go }}
frontend: ${{ steps.detect.outputs.frontend }}
proto: ${{ steps.detect.outputs.proto }}
steps:
# HEAD is the pull request's merge commit; its first parent is the base branch.
- uses: actions/checkout@v7
with:
fetch-depth: 2
sparse-checkout: .github
- name: Detect which areas changed
id: detect
run: |
files=$(git diff --name-only --no-renames HEAD^1 HEAD)
changed() { grep -qE "$1" <<<"$files" && echo true || echo false; }
go=$(changed '^(backend/|action/|proto/.*\.go$|go\.(mod|sum)$|\.golangci\.yaml$|\.github/workflows/ci\.yml$)')
# The frontend gate's `prepare` script also reads these backend files.
frontend=$(changed '^(frontend/|backend/enterprise/plan\.yaml$|backend/common/permission/permission\.yaml$|backend/api/mcp/gen/openapi\.yaml$|scripts/check_link_test\.go$|\.github/workflows/ci\.yml$)')
proto=$(changed '^(proto/|\.github/workflows/ci\.yml$)')
printf 'go=%s\nfrontend=%s\nproto=%s\n' "$go" "$frontend" "$proto" >> "$GITHUB_OUTPUT"
echo "Changed areas: go=$go, frontend=$frontend, proto=$proto. Checks for an unchanged area are skipped, and all-checks-passed counts a skipped check as passing." | tee -a "$GITHUB_STEP_SUMMARY"
go-tests:
needs: detect-changed-files
if: needs.detect-changed-files.outputs.go == 'true'
runs-on: self-hosted
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.27.1'
cache: false # Caching is slow.
- name: Verify go.mod is tidy
run: |
go mod tidy
git diff --exit-code
- name: Run all tests
# go test runs packages in command-line order. backend/tests is the
# longest package by far, so it goes first instead of after the ~130
# packages that sort before it alphabetically.
run: go test -p=8 -timeout 30m -ldflags "-w -s" -v ./backend/tests ./backend/api/v1 ./backend/... ./action/... | tee test.log; exit ${PIPESTATUS[0]}
- name: Pretty print tests running time
# grep: filter out lines like "--- PASS: Test (15.04s)"
# sed: remove unnecessary characters
# awk: re-format lines to "PASS: Test (15.04s)"
# sort: cut into columns by delimiter ' ' (single space) and sort by column 3 (test time in seconds) as numeric type in reverse order (largest comes first)
# awk: accumulate sum by test time in seconds
run: grep --color=never -e '--- PASS:' -e '--- FAIL:' test.log | sed 's/[:()]//g' | awk '{print $2,$3,$4}' | sort -t' ' -nk3 -r | awk '{sum += $3; print $1,$2,$3,sum"s"}'
golangci-lint:
needs: detect-changed-files
if: needs.detect-changed-files.outputs.go == 'true'
runs-on: self-hosted
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.27.1'
cache: false # Caching is slow.
- name: Install golangci-lint
uses: golangci/golangci-lint-action@v9
with:
# renovate: datasource=github-releases depName=golangci/golangci-lint
version: v2.13.2
install-only: true
skip-cache: true
- name: Verify Go version compatibility
run: |
GO_MAJOR_MINOR=$(go env GOVERSION | sed 's/go//' | cut -d. -f1-2)
LINT_GO=$(golangci-lint version --json 2>/dev/null | jq -r '.go // empty' || true)
if [ -z "$LINT_GO" ]; then
echo "Skipping compatibility check: unable to read golangci-lint Go version"
exit 0
fi
LINT_MAJOR_MINOR=$(echo "$LINT_GO" | sed 's/go//' | cut -d. -f1-2)
if [ "$GO_MAJOR_MINOR" != "$LINT_MAJOR_MINOR" ]; then
echo "::error::Go version mismatch: setup-go provides go${GO_MAJOR_MINOR} but golangci-lint was built with go${LINT_MAJOR_MINOR}. Update golangci-lint to a version built with go${GO_MAJOR_MINOR}."
exit 1
fi
- name: golangci-lint
env:
# Go's default GOGC=100 collects every time a ~5 GB live heap doubles, which
# is the wrong policy for a batch run. Collecting near a target instead is
# cheaper on CPU, wall and peak RSS alike. GOMEMLIMIT is a soft limit over
# Go-managed memory, not a guaranteed RSS ceiling, but it holds in practice
# here: 10.17 GB peak measured, against 11.05 GB with the default.
GOGC: "off"
GOMEMLIMIT: 10GiB
run: golangci-lint run --verbose -j 8 --timeout 30m --max-same-issues=30 --allow-parallel-runners
frontend-tests:
needs: detect-changed-files
if: needs.detect-changed-files.outputs.frontend == 'true'
runs-on: self-hosted
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '24.18.1'
# The runner's pnpm store persists between runs, so the Actions
# cache adds nothing and downloading it is slow.
cache: ''
- uses: pnpm/setup@v2
with:
version: '11.17.0'
- name: Install dependencies
run: pnpm install --frozen-lockfile
working-directory: frontend
# Single gate, identical to what contributors run locally. The stages
# it runs are listed in frontend/scripts/run-gate.mjs.
- name: Test
run: pnpm test
working-directory: frontend
- uses: actions/setup-go@v7
with:
go-version: '1.27.1'
cache: false # Caching is slow.
- name: Validate links
run: go test -timeout 600s -v ./scripts/...
lint-protos:
needs: detect-changed-files
if: needs.detect-changed-files.outputs.proto == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: buf
uses: bufbuild/buf-action@v1
with:
input: "proto"
breaking: false
token: ${{ secrets.BUF_TOKEN }}
# The only check the main ruleset requires, so every job that must pass
# belongs in `needs`.
all-checks-passed:
needs: [detect-changed-files, go-tests, golangci-lint, frontend-tests, lint-protos]
# always() on the job and its step: a cancelled run must fail this check, not skip it.
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Fail if any job failed or was cancelled
if: always()
env:
NEEDS: ${{ toJSON(needs) }}
run: |
jq -r 'to_entries[] | "- \(.key): \(.value.result)"' <<<"$NEEDS" | tee -a "$GITHUB_STEP_SUMMARY"
failed=$(jq -r 'to_entries[] | select(.value.result == "failure" or .value.result == "cancelled") | .key' <<<"$NEEDS")
for job in $failed; do echo "::error::$job did not pass"; done
[ -z "$failed" ] || exit 1