name: CI on: pull_request: paths: - "**.go" - "go.mod" - "go.sum" - "frontend/**" - ".nvmrc" push: branches: - main paths: - "**.go" - "go.mod" - "go.sum" - "frontend/**" - ".nvmrc" permissions: contents: read jobs: go-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: Test run: | go list ./... | grep -v '/frontend/node_modules/' > /tmp/go-packages.txt go test -shuffle=on -count=1 $(cat /tmp/go-packages.txt) postgres-durable-first: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: xui_durable ports: - 5432:5432 options: >- --health-cmd "pg_isready -U postgres -d xui_durable" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: PostgreSQL durable-first tests run: | set -o pipefail XUI_DB_TYPE=postgres XUI_DB_DSN="host=127.0.0.1 port=5432 user=postgres password=postgres dbname=xui_durable sslmode=disable" \ go test ./internal/web/service -run 'PostgresCommitFailure' -count=1 -v | tee /tmp/postgres-durable-first.log if grep -q -- '--- SKIP' /tmp/postgres-durable-first.log; then exit 1 fi codegen: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - uses: actions/setup-node@v7 with: node-version-file: .nvmrc - name: Regenerate schemas, examples and OpenAPI run: npm run gen working-directory: frontend - name: Fail if generated files are stale (run 'npm run gen' and commit) run: git diff --exit-code -- frontend/src/generated frontend/public/openapi.json govulncheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: Install govulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest - name: Run govulncheck run: govulncheck ./... # Race + shuffle hygiene gate: data races and order-dependent tests fail the build. race: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: Race + shuffle run: | go list ./... | grep -v '/frontend/node_modules/' > /tmp/go-packages.txt go test -race -shuffle=on -count=1 $(cat /tmp/go-packages.txt) # Brief native-fuzz smoke on the security-/parser-critical decoders. Each runs the # generated corpus plus 30s of exploration; a crash here is a real input-handling bug. fuzz-smoke: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: Fuzz critical parsers (smoke) run: | go test -run '^$' -fuzz 'FuzzParseLink$' -fuzztime=30s ./internal/util/link/ go test -run '^$' -fuzz 'FuzzDecodeCertPin$' -fuzztime=30s ./internal/web/runtime/ golangci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v7 with: go-version-file: go.mod cache: true - name: Stub internal/web/dist for go:embed run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: version: latest frontend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: npm cache-dependency-path: frontend/package-lock.json - name: Install run: npm ci working-directory: frontend - name: Lint run: npm run lint working-directory: frontend - name: Typecheck run: npm run typecheck working-directory: frontend - name: Install Playwright Chromium (Storybook story tests) run: npx playwright install --with-deps chromium working-directory: frontend - name: Test run: npm test working-directory: frontend - name: Build run: npm run build working-directory: frontend - name: Build Storybook run: npm run build-storybook working-directory: frontend # Known-accepted as of 2026-07-26: 1 high advisory, # brace-expansion/minimatch via eslint-plugin-jsx-a11y's own pinned # minimatch@^3.1.2 (GHSA-mh99-v99m-4gvg). Not exploitable here -- this # chain only runs against this repo's own hardcoded lint globs, never # untrusted input. The eslint-core minimatch@10.x instance of the same # advisory is already fixed via the "minimatch@^10" override below # (brace-expansion 5.0.8); jsx-a11y's own minimatch is pinned to an old # major with no patched release in that line, and forcing it to 10.x # via override breaks npm's own dependency-tree validation (a genuine # incompatibility, not just an npm quirk) -- so this one can only be # fixed by an eslint-plugin-jsx-a11y release bumping its own minimatch. # `npm audit fix --force`'s suggestion is a downgrade to before jsx-a11y # adopted the vulnerable chain at all; left as-is rather than trading a # real regression for a vulnerability that doesn't apply. # The other advisory this comment used to cover (react-router RSC CSRF # bypass, GHSA-qwww-vcr4-c8h2) is actually fixed now: migrated off the # abandoned react-router-dom (frozen at 7.18.1, pinning the vulnerable # react-router 7.18.1) onto react-router 8.3.0 directly, which has the # real forward fix -- not a downgrade. Re-check on a future bump in # case upstream ships a real fix for the remaining advisory too. - name: Audit run: npm audit --audit-level=high working-directory: frontend