Add ultromics #8249
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] | |
| 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" |