Skip to content

Add ultromics

Add ultromics #8249

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main, murmur-demo]
workflow_dispatch:
inputs:
pr:
description: "Optional PR number for path-aware bot retry"
required: false
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
dependency-review:
name: Dependency Review
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: moderate
deny-licenses: AGPL-1.0, AGPL-3.0, GPL-2.0, GPL-3.0, LGPL-2.0, LGPL-2.1, LGPL-3.0
workflow-security:
name: Workflow Security
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- name: Test repository scripts
run: >
node --test
scripts/ci-workflow.test.mjs
scripts/docs-index.test.mjs
scripts/dealroom-company-requests.test.mjs
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: "1.25"
cache: false
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- name: actionlint
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -ignore 'SC(2002|2086|2129)'
- name: zizmor
run: uvx --from zizmor==1.26.1 zizmor --persona regular --min-severity medium --min-confidence high .github/workflows
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
code: ${{ steps.manual-default.outputs.code || steps.manual-pr.outputs.code || steps.filter.outputs.code }}
crawler_code: ${{ steps.manual-default.outputs.crawler_code || steps.manual-pr.outputs.crawler_code || steps.filter.outputs.crawler_code }}
boards_csv: ${{ steps.manual-default.outputs.boards_csv || steps.manual-pr.outputs.boards_csv || steps.filter.outputs.boards_csv }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
fetch-depth: 0
- name: Manual dispatch defaults
id: manual-default
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr == ''
run: |
{
echo "code=true"
echo "crawler_code=false"
echo "boards_csv=false"
} >> "$GITHUB_OUTPUT"
- name: Classify manually dispatched PR paths
id: manual-pr
if: github.event_name == 'workflow_dispatch' && github.event.inputs.pr != ''
run: .github/scripts/classify-pr-paths.sh
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.inputs.pr }}
- name: Detect changed paths
id: filter
if: github.event_name != 'workflow_dispatch'
uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
with:
predicate-quantifier: every
filters: |
code:
- '**'
- '!**/*.md'
- '!docs/**'
- '!.github/dependabot.yml'
- '!.github/dependabot.yaml'
- '!.github/ISSUE_TEMPLATE/**'
- '!.github/DISCUSSION_TEMPLATE/**'
- '!apps/crawler/data/**'
- '!apps/crawler/traces/**'
- '!apps/crawler/VERSION'
crawler_code:
- 'apps/crawler/**'
- '!**/*.md'
- '!apps/crawler/data/**'
- '!apps/crawler/traces/**'
- '!apps/crawler/VERSION'
boards_csv:
- 'apps/crawler/data/boards.csv'
lint-web:
name: Lint Web
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: node scripts/verify-pnpm-config.mjs
- run: pnpm lint
i18n-coverage:
name: i18n Coverage Web
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: bash scripts/check-i18n-coverage.sh
lint-crawler:
name: Lint Crawler
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync --group dev
working-directory: apps/crawler
- run: uv run ruff check src/ tests/
working-directory: apps/crawler
- run: uv run ruff format --check src/ tests/
working-directory: apps/crawler
type-python:
name: Type Python
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync --group dev
working-directory: apps/crawler
- run: uv run pyright --level error
working-directory: apps/crawler
type-all:
name: Type All
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
# turbo run typecheck has dependsOn: ["^build"], so @jseek/mcp-server
# is built once and all consumers (apps/web) typecheck against the
# built declarations. Covers every TS workspace in the monorepo.
- run: pnpm --filter @jseek/mcp-server test
- run: pnpm typecheck
test-web:
name: Test Web
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
# `packages/mcp-server/package.json` has `"prepare": "tsc"`, so
# `pnpm install --frozen-lockfile` below builds `dist/` automatically.
# No explicit `pnpm --filter @jseek/mcp-server build` step is needed.
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: >
pnpm --filter @jobseek/web exec vitest run
--exclude src/lib/search/__tests__/typesense.e2e.test.ts
test-web-typesense-e2e:
name: Test Web Typesense E2E
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
services:
typesense:
image: typesense/typesense:27.1
ports:
- 8108:8108
options: --tmpfs /data:rw
env:
TYPESENSE_API_KEY: local_dev_typesense_key
TYPESENSE_DATA_DIR: /data
env:
REQUIRE_TYPESENSE_E2E: "true"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- name: Wait for Typesense
run: |
for _ in {1..60}; do
if curl -fsS http://localhost:8108/health >/dev/null; then
exit 0
fi
sleep 1
done
curl -fsS http://localhost:8108/health
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/web exec vitest run src/lib/search/__tests__/typesense.e2e.test.ts
test-web-isr:
# Slow lane: runs `pnpm build` once via the vitest globalSetup
# (`apps/web/test-setup/run-prod-build.ts`) and asserts each
# must-stay-cacheable route is classified with its expected glyph
# (`◐ Partial Prerender` or `○ Static`) in Next 16's route summary.
# Catches three classes the build itself cannot: parser drift if
# Next's stdout shape changes, list rot if a route is removed, and
# ◐↔○ classification drift (the build only fails on `→ ƒ`). See
# #2885 (and incident #2243).
name: Test Web ISR (slow)
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
# `Production` environment exposes DATABASE_URL + TYPESENSE_* secrets for
# trusted same-repo runs. Fork PRs deliberately receive no secrets; blog
# `MdxMentions` company lookups must degrade to their missing-mention
# fallback instead of reaching Postgres when DATABASE_URL is absent.
environment: Production
# Job-level env: declared once, applied to every step in the job.
# Real secrets come from the `Production` environment above; the
# `ci-stub-*` literals are build-time-only placeholders that satisfy
# module-top-level env reads (e.g. better-auth's OAuth provider
# config) without authenticating to any real service — auth/SSO and
# search/write keys are only consumed at runtime by request handlers,
# which the build does not exercise.
env:
# Trusted runs use the real DSN so blog `<Mention>` MDX components can
# render company tooltips at build time. Fork runs leave this empty and
# exercise the secretless fallback path.
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_URL_UNPOOLED: ${{ secrets.DATABASE_URL_UNPOOLED }}
# This job classifies route cacheability. It must not depend on
# external Typesense availability or latency; trusted runs still have
# DATABASE_URL for blog company mentions, and fork/secretless runs
# exercise the missing-mention fallback path.
TYPESENSE_HOST: ""
TYPESENSE_PORT: ""
TYPESENSE_PROTOCOL: ""
# Build-time-only stubs (see job-level comment above).
BETTER_AUTH_SECRET: ci-stub-better-auth-secret-not-used-at-build-time
BETTER_AUTH_URL: http://localhost:3000
TYPESENSE_SEARCH_KEY: ci-stub-not-used-at-build-time
TYPESENSE_WRITE_KEY: ci-stub-not-used-at-build-time
GITHUB_CLIENT_ID: ci-stub
GITHUB_CLIENT_SECRET: ci-stub
GOOGLE_CLIENT_ID: ci-stub
GOOGLE_CLIENT_SECRET: ci-stub
LINKEDIN_CLIENT_ID: ci-stub
LINKEDIN_CLIENT_SECRET: ci-stub
RESEND_API_KEY: ci-stub
UPSTASH_REDIS_REST_URL: https://stub.invalid
UPSTASH_REDIS_REST_TOKEN: ci-stub
# It should also not spend build time rendering hundreds of company
# OG PNGs; unit tests cover the OG prebake query and renderer.
COMPANY_OG_PRERENDER_TOP_N: "0"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
# `pnpm install` auto-builds `packages/mcp-server/dist/` via its
# `prepare` script (`"prepare": "tsc"`), so no explicit build step
# is needed. `pnpm test:isr` then runs `pnpm build` itself via
# vitest globalSetup (`apps/web/test-setup/run-prod-build.ts`).
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/web test:isr
test-shim:
name: Test Murmur Shim
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/murmur-shim test
- run: pnpm --filter @jobseek/murmur-shim build
- name: Boundary gate (validateUrl/safeFetch)
run: bash apps/murmur-shim/scripts/grep-validateurl-boundary.sh
test-crawler:
name: Test Crawler
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync --group dev
working-directory: apps/crawler
- run: uv run pytest tests/ -v --ignore=tests/e2e/test_typesense_indexing.py
working-directory: apps/crawler
test-crawler-typesense-e2e:
name: Test Crawler Typesense E2E
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
services:
typesense:
image: typesense/typesense:27.1
ports:
- 8108:8108
options: --tmpfs /data:rw
env:
TYPESENSE_API_KEY: local_dev_typesense_key
TYPESENSE_DATA_DIR: /data
env:
TYPESENSE_HOST: localhost
TYPESENSE_PORT: "8108"
TYPESENSE_PROTOCOL: http
TYPESENSE_ADMIN_KEY: local_dev_typesense_key
REQUIRE_TYPESENSE_E2E: "true"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- name: Wait for Typesense
run: |
for _ in {1..60}; do
if curl -fsS http://localhost:8108/health >/dev/null; then
exit 0
fi
sleep 1
done
curl -fsS http://localhost:8108/health
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync --group dev
working-directory: apps/crawler
- run: uv run python ../../scripts/typesense-setup.py --force
working-directory: apps/crawler
- run: uv run pytest tests/e2e/test_typesense_indexing.py -v
working-directory: apps/crawler
coverage-crawler:
name: Coverage Crawler
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync --group dev
working-directory: apps/crawler
- name: Monitor parser coverage
working-directory: apps/crawler
run: >
uv run pytest
tests/test_recruitee.py
tests/test_smartrecruiters.py
tests/test_talentbrew.py
--cov=src.core.monitors.recruitee
--cov=src.core.monitors.smartrecruiters
--cov=src.core.monitors.talentbrew
--cov-report=term-missing
--cov-fail-under=85
coverage-web:
name: Coverage Web
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: >
pnpm --filter @jobseek/web exec vitest run
--config vitest.coverage.config.ts
--exclude src/lib/search/__tests__/typesense.e2e.test.ts
coverage-shim:
name: Coverage Murmur Shim
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/murmur-shim test:coverage
data-validation:
name: Data Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: python scripts/validate_data_csv.py
working-directory: apps/crawler
migration-drift:
name: Migration Drift
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/web db:check
web-smoke:
name: Web Build Smoke
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
environment: Production
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DATABASE_URL_UNPOOLED: ${{ secrets.DATABASE_URL_UNPOOLED }}
TYPESENSE_HOST: ""
TYPESENSE_PORT: ""
TYPESENSE_PROTOCOL: ""
BETTER_AUTH_SECRET: ci-stub-better-auth-secret-not-used-at-build-time
BETTER_AUTH_URL: http://127.0.0.1:3100
TYPESENSE_SEARCH_KEY: ci-stub-not-used-at-build-time
TYPESENSE_WRITE_KEY: ci-stub-not-used-at-build-time
GITHUB_CLIENT_ID: ci-stub
GITHUB_CLIENT_SECRET: ci-stub
GOOGLE_CLIENT_ID: ci-stub
GOOGLE_CLIENT_SECRET: ci-stub
LINKEDIN_CLIENT_ID: ci-stub
LINKEDIN_CLIENT_SECRET: ci-stub
RESEND_API_KEY: ci-stub
UPSTASH_REDIS_REST_URL: https://stub.invalid
UPSTASH_REDIS_REST_TOKEN: ci-stub
COMPANY_OG_PRERENDER_TOP_N: "0"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @jobseek/web build
- run: pnpm --filter @jobseek/web check:bundle
- run: pnpm --filter @jobseek/web exec playwright install chromium
- run: pnpm --filter @jobseek/web smoke
ws-package-smoke:
name: ws-package wheel smoke test
needs: changes
if: needs.changes.outputs.crawler_code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- name: Build slim ws-package wheel
working-directory: apps/crawler/ws-package
run: |
python -m pip install --upgrade pip build hatchling
python -m build --wheel
- name: Install wheel into clean venv
run: |
python -m venv /tmp/ws-smoke
/tmp/ws-smoke/bin/pip install apps/crawler/ws-package/dist/*.whl
- name: Import smoke test
run: |
/tmp/ws-smoke/bin/python apps/crawler/ws-package/smoketest.py
- name: ws --help / --version work end-to-end
run: |
/tmp/ws-smoke/bin/ws --help
/tmp/ws-smoke/bin/ws --version
version-check:
name: Version bump check
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.crawler_code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
fetch-depth: 0
- name: Check VERSION increased
run: |
PR_VERSION=$(cat apps/crawler/VERSION | tr -d '[:space:]')
MAIN_VERSION=$(git show origin/main:apps/crawler/VERSION | tr -d '[:space:]')
IFS='.' read -r pr_major pr_minor pr_patch <<< "$PR_VERSION"
IFS='.' read -r main_major main_minor main_patch <<< "$MAIN_VERSION"
pr_num=$((pr_major * 1000000 + pr_minor * 1000 + pr_patch))
main_num=$((main_major * 1000000 + main_minor * 1000 + main_patch))
if [ "$pr_num" -gt "$main_num" ]; then
echo "VERSION bumped: ${MAIN_VERSION} → ${PR_VERSION}"
else
echo "::error::apps/crawler/VERSION must be bumped for crawler code changes (main: ${MAIN_VERSION}, PR: ${PR_VERSION})"
exit 1
fi
probe-new-boards:
name: Probe new/changed boards
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.boards_csv == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
enable-cache: true
cache-dependency-glob: "apps/crawler/uv.lock"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.13"
- run: uv sync
working-directory: apps/crawler
- name: Fetch base ref
env:
BASE_REF: ${{ github.base_ref }}
run: git fetch --depth=1 origin "$BASE_REF"
- name: Probe added/changed boards
working-directory: apps/crawler
env:
BASE_REF: ${{ github.base_ref }}
run: uv run python ../../scripts/probe-new-boards.py --base-ref "origin/$BASE_REF"
required-ci:
name: Required CI
if: always()
needs:
- dependency-review
- workflow-security
- changes
- lint-web
- i18n-coverage
- lint-crawler
- type-python
- type-all
- test-web
- test-web-typesense-e2e
- test-web-isr
- test-shim
- test-crawler
- test-crawler-typesense-e2e
- coverage-crawler
- coverage-web
- coverage-shim
- data-validation
- migration-drift
- web-smoke
- ws-package-smoke
- version-check
- probe-new-boards
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- name: Check required job results
env:
NEEDS_JSON: ${{ toJson(needs) }}
EVENT_NAME: ${{ github.event_name }}
INPUT_PR: ${{ github.event.inputs.pr || '' }}
GH_TOKEN: ${{ github.token }}
run: |
set +e
node <<'NODE'
const needs = JSON.parse(process.env.NEEDS_JSON);
const eventName = process.env.EVENT_NAME;
const code = needs.changes?.outputs?.code === "true";
const crawlerCode = needs.changes?.outputs?.crawler_code === "true";
const boardsCsv = needs.changes?.outputs?.boards_csv === "true";
const failures = [];
function requireSuccess(job, condition = true) {
const result = needs[job]?.result;
if (result === "failure" || result === "cancelled" || result === "timed_out") {
failures.push(`${job} ended with ${result}`);
return;
}
if (condition && result !== "success") {
failures.push(`${job} expected success, got ${result ?? "missing"}`);
}
}
requireSuccess("workflow-security");
requireSuccess("changes");
requireSuccess("data-validation");
requireSuccess("dependency-review", eventName === "pull_request" && code);
for (const job of [
"lint-web",
"i18n-coverage",
"lint-crawler",
"type-python",
"type-all",
"test-web",
"test-web-typesense-e2e",
"test-web-isr",
"test-shim",
"test-crawler",
"test-crawler-typesense-e2e",
"coverage-crawler",
"coverage-web",
"coverage-shim",
"migration-drift",
"web-smoke",
]) {
requireSuccess(job, code);
}
requireSuccess("ws-package-smoke", crawlerCode);
requireSuccess("version-check", eventName === "pull_request" && crawlerCode);
requireSuccess("probe-new-boards", eventName === "pull_request" && boardsCsv);
if (failures.length > 0) {
console.error("Required CI failed:");
for (const failure of failures) console.error(`- ${failure}`);
process.exit(1);
}
console.log("Required CI passed");
NODE
status=$?
set -e
if [[ "$EVENT_NAME" == "workflow_dispatch" && -n "$INPUT_PR" ]]; then
if [[ "$status" -eq 0 ]]; then
state=success
description="Required CI passed"
else
state=failure
description="Required CI failed"
fi
gh api -X POST "repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA" \
-f state="$state" \
-f context="Required CI" \
-f description="$description" \
-f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
fi
exit "$status"