SEO - Engineering Page #204
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: Docs | |
| on: | |
| pull_request: | |
| paths: | |
| - "docs/**" | |
| - ".github/workflows/docs.yml" | |
| push: | |
| branches: [main] | |
| paths: | |
| - "docs/**" | |
| - ".github/workflows/docs.yml" | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: docs-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| packages: write | |
| pull-requests: read | |
| env: | |
| DOCS_SITE_URL: https://docs.gobii.ai | |
| DOCS_IMAGE: ghcr.io/gobii-ai/gobii-docs | |
| jobs: | |
| preview_preflight: | |
| name: Resolve Docs Preview Inputs | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| outputs: | |
| docs_changed: ${{ steps.resolve.outputs.docs_changed }} | |
| head_sha: ${{ steps.resolve.outputs.head_sha }} | |
| short_sha: ${{ steps.resolve.outputs.short_sha }} | |
| preview_url: ${{ steps.resolve.outputs.preview_url }} | |
| steps: | |
| - name: Resolve PR docs changes | |
| id: resolve | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| if (!pr) { | |
| throw new Error('pull_request payload missing.'); | |
| } | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const docsChanged = files.some((file) => file.filename.startsWith('docs/')); | |
| const headSha = pr.head.sha; | |
| core.setOutput('docs_changed', String(docsChanged)); | |
| core.setOutput('head_sha', headSha); | |
| core.setOutput('short_sha', headSha.substring(0, 7)); | |
| core.setOutput('preview_url', `https://docs-pr-${pr.number}.ship.gobii.ai`); | |
| build: | |
| name: Build Docs | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: docs/package-lock.json | |
| - name: Install dependencies | |
| working-directory: docs | |
| run: npm ci | |
| - name: Verify analytics secret | |
| if: github.event_name != 'pull_request' | |
| env: | |
| DOCS_GTAG_TRACKING_ID: ${{ secrets.DOCS_GTAG_TRACKING_ID }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${DOCS_GTAG_TRACKING_ID:-}" ]; then | |
| echo "DOCS_GTAG_TRACKING_ID must be configured on gobii-ai/gobii-platform for production docs builds." >&2 | |
| exit 1 | |
| fi | |
| - name: Build static docs | |
| working-directory: docs | |
| env: | |
| DOCS_GTAG_TRACKING_ID: ${{ secrets.DOCS_GTAG_TRACKING_ID }} | |
| run: npm run build | |
| - name: Verify analytics tag when configured | |
| working-directory: docs | |
| env: | |
| DOCS_GTAG_TRACKING_ID: ${{ secrets.DOCS_GTAG_TRACKING_ID }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${DOCS_GTAG_TRACKING_ID:-}" ]; then | |
| grep -q 'googletagmanager.com/gtag/js?id=' build/index.html | |
| fi | |
| - name: Verify required routes | |
| working-directory: docs | |
| run: | | |
| set -euo pipefail | |
| required=( | |
| build/index.html | |
| build/getting-started/introduction.html | |
| build/developers/developer-basics.html | |
| build/api-reference/gobii-api.html | |
| build/api-reference/list-persistent-agents.html | |
| build/api-reference/agents-api/get-agents.html | |
| build/api-reference/agents-api/get-agents.md | |
| build/GobiiAPI.yaml | |
| build/openapi/GobiiAPI.yaml | |
| build/legacy-redirects.conf | |
| build/api-reference/openapi.json | |
| build/llms.txt | |
| build/llms-full.txt | |
| build/pagefind/pagefind-ui.js | |
| ) | |
| for file in "${required[@]}"; do | |
| test -s "$file" | |
| done | |
| - name: Verify nginx routing | |
| working-directory: docs | |
| env: | |
| DOCS_GTAG_TRACKING_ID: ${{ secrets.DOCS_GTAG_TRACKING_ID }} | |
| run: | | |
| set -euo pipefail | |
| image="gobii-docs-ci:${GITHUB_SHA}" | |
| container="gobii-docs-ci-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" | |
| docker build \ | |
| --build-arg DOCS_SITE_URL="${DOCS_SITE_URL}" \ | |
| --build-arg DOCS_GTAG_TRACKING_ID="${DOCS_GTAG_TRACKING_ID}" \ | |
| -t "$image" . | |
| docker run -d --rm --name "$container" -p 8080:8080 "$image" | |
| trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT | |
| ready=0 | |
| for _ in {1..20}; do | |
| if curl -fsS http://127.0.0.1:8080/healthz >/dev/null 2>&1; then | |
| ready=1 | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| test "$ready" = "1" | |
| code_for() { | |
| curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:8080$1" | |
| } | |
| redirect_for() { | |
| curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' "http://127.0.0.1:8080$1" | |
| } | |
| test "$(code_for /)" = "200" | |
| test "$(code_for /healthz)" = "200" | |
| if [ -n "${DOCS_GTAG_TRACKING_ID:-}" ]; then | |
| curl -fsS http://127.0.0.1:8080/ | grep -q 'googletagmanager.com/gtag/js?id=' | |
| fi | |
| test "$(code_for /api-reference/gobii-api)" = "200" | |
| test "$(code_for /api-reference/list-persistent-agents)" = "200" | |
| test "$(redirect_for /index.html)" = "301 http://127.0.0.1:8080/" | |
| test "$(redirect_for /api-reference)" = "301 http://127.0.0.1:8080/api-reference/gobii-api" | |
| test "$(redirect_for /api-reference/agents-api/get-agents)" = "301 http://127.0.0.1:8080/api-reference/list-persistent-agents" | |
| test "$(redirect_for '/api-reference/agents-api/get-agents?utm_source=x')" = "301 http://127.0.0.1:8080/api-reference/list-persistent-agents?utm_source=x" | |
| test "$(redirect_for /api-reference/agents-api/get-agents.html)" = "301 http://127.0.0.1:8080/api-reference/list-persistent-agents" | |
| test "$(redirect_for /getting-started/introduction)" = "301 http://127.0.0.1:8080/" | |
| test "$(redirect_for /getting-started/introduction.html)" = "301 http://127.0.0.1:8080/" | |
| test "$(redirect_for /getting-started/introduction/)" = "301 http://127.0.0.1:8080/" | |
| test "$(redirect_for /core-concepts)" = "301 http://127.0.0.1:8080/start-here/core-concepts" | |
| test "$(redirect_for /core-concepts.html)" = "301 http://127.0.0.1:8080/start-here/core-concepts" | |
| test "$(redirect_for /core-concepts/)" = "301 http://127.0.0.1:8080/start-here/core-concepts" | |
| test "$(redirect_for /console-guides)" = "301 http://127.0.0.1:8080/using-gobii" | |
| test "$(redirect_for /console-guides.html)" = "301 http://127.0.0.1:8080/using-gobii" | |
| test "$(redirect_for /console-guides/)" = "301 http://127.0.0.1:8080/using-gobii" | |
| test "$(redirect_for /console-guides/live-chat-guide)" = "301 http://127.0.0.1:8080/using-gobii/chat-and-timeline" | |
| test "$(redirect_for /console-guides/live-chat-guide.html)" = "301 http://127.0.0.1:8080/using-gobii/chat-and-timeline" | |
| test "$(redirect_for /console-guides/live-chat-guide/)" = "301 http://127.0.0.1:8080/using-gobii/chat-and-timeline" | |
| test "$(redirect_for /console-guides/meta-gobii)" = "301 http://127.0.0.1:8080/using-gobii/meta-gobii" | |
| test "$(redirect_for /console-guides/meta-gobii.html)" = "301 http://127.0.0.1:8080/using-gobii/meta-gobii" | |
| test "$(redirect_for /console-guides/meta-gobii/)" = "301 http://127.0.0.1:8080/using-gobii/meta-gobii" | |
| test "$(code_for /using-gobii/meta-gobii)" = "200" | |
| test "$(code_for /definitely-not-a-real-docs-url)" = "404" | |
| test "$(redirect_for /definitely-not-a-real-docs-url/)" = "301 http://127.0.0.1:8080/definitely-not-a-real-docs-url" | |
| test "$(code_for /404)" = "404" | |
| test "$(code_for /404.html)" = "404" | |
| header_for() { | |
| local path="$1" | |
| local header="$2" | |
| curl -sSI "http://127.0.0.1:8080${path}" \ | |
| | awk -v header="${header}" 'BEGIN { IGNORECASE = 1 } index($0, header ":") == 1 { sub("\r$", ""); sub("^[^:]+: *", ""); print; exit }' | |
| } | |
| asset_js="$(node - <<'NODE' | |
| (async () => { | |
| const html = await (await fetch('http://127.0.0.1:8080/')).text(); | |
| const match = html.match(/\/assets\/js\/[^"]+\.js/); | |
| if (!match) process.exit(1); | |
| console.log(match[0]); | |
| })(); | |
| NODE | |
| )" | |
| pagefind_fragment="/$(find build/pagefind/fragment -name '*.pf_fragment' -print -quit | sed 's#^build/##')" | |
| test "$(header_for /healthz Cache-Control)" = "no-store" | |
| test "$(header_for / Cache-Control)" = "public, max-age=0, must-revalidate" | |
| test "$(header_for / CDN-Cache-Control)" = "public, max-age=300, stale-while-revalidate=86400" | |
| test "$(header_for / Cloudflare-CDN-Cache-Control)" = "public, max-age=300, stale-while-revalidate=86400" | |
| test "$(header_for "$asset_js" Cache-Control)" = "public, max-age=31536000, immutable" | |
| test "$(header_for /assets/js/not-real.js Cache-Control)" = "" | |
| test "$(header_for /pagefind/pagefind-ui.js Cache-Control)" = "public, max-age=300, must-revalidate" | |
| test "$(header_for "$pagefind_fragment" Cache-Control)" = "public, max-age=31536000, immutable" | |
| test "$(header_for /api-reference/openapi.json Cloudflare-CDN-Cache-Control)" = "public, max-age=300, stale-while-revalidate=86400" | |
| test "$(header_for /api-reference/openapi.json X-Robots-Tag)" = "noindex" | |
| test "$(header_for /developers.md Cache-Control)" = "public, max-age=300, must-revalidate" | |
| test "$(header_for /developers.md X-Robots-Tag)" = "noindex" | |
| test "$(header_for /llms.txt X-Robots-Tag)" = "noindex" | |
| test "$(header_for /llms-full.txt X-Robots-Tag)" = "noindex" | |
| test "$(header_for /GobiiAPI.yaml X-Robots-Tag)" = "noindex" | |
| test "$(header_for /openapi/GobiiAPI.yaml X-Robots-Tag)" = "noindex" | |
| test "$(header_for / X-Robots-Tag)" = "" | |
| test "$(header_for /api-reference/list-persistent-agents X-Robots-Tag)" = "" | |
| test "$(header_for /api-reference/agents-api/get-agents Cache-Control)" = "public, max-age=300" | |
| publish: | |
| name: Publish Docs Image | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| outputs: | |
| short_sha: ${{ steps.meta.outputs.short_sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Resolve image metadata | |
| id: meta | |
| run: | | |
| short_sha="${GITHUB_SHA::7}" | |
| echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push docs image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: docs | |
| file: docs/Dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.DOCS_IMAGE }}:sha-${{ github.sha }} | |
| ${{ env.DOCS_IMAGE }}:sha-${{ steps.meta.outputs.short_sha }} | |
| ${{ env.DOCS_IMAGE }}:staging | |
| labels: | | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| build-args: | | |
| DOCS_SITE_URL=${{ env.DOCS_SITE_URL }} | |
| DOCS_GTAG_TRACKING_ID=${{ secrets.DOCS_GTAG_TRACKING_ID }} | |
| cache-from: type=gha,scope=gobii-docs | |
| cache-to: type=gha,mode=max,scope=gobii-docs | |
| platforms: linux/amd64 | |
| provenance: false | |
| sbom: false | |
| publish_preview: | |
| name: Publish Docs Preview Image | |
| runs-on: ubuntu-latest | |
| needs: [build, preview_preflight] | |
| if: > | |
| github.event_name == 'pull_request' && | |
| needs.preview_preflight.outputs.docs_changed == 'true' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| outputs: | |
| image: ${{ steps.meta.outputs.image }} | |
| steps: | |
| - name: Checkout PR head | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preview_preflight.outputs.head_sha }} | |
| - name: Resolve image metadata | |
| id: meta | |
| env: | |
| HEAD_SHA: ${{ needs.preview_preflight.outputs.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| echo "image=${DOCS_IMAGE}:sha-${HEAD_SHA}" >> "$GITHUB_OUTPUT" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push docs preview image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: docs | |
| file: docs/Dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.DOCS_IMAGE }}:sha-${{ needs.preview_preflight.outputs.head_sha }} | |
| ${{ env.DOCS_IMAGE }}:pr-${{ github.event.pull_request.number }} | |
| labels: | | |
| org.opencontainers.image.revision=${{ needs.preview_preflight.outputs.head_sha }} | |
| build-args: | | |
| DOCS_SITE_URL=${{ needs.preview_preflight.outputs.preview_url }} | |
| cache-from: type=gha,scope=gobii-docs | |
| cache-to: type=gha,mode=max,scope=gobii-docs | |
| platforms: linux/amd64 | |
| provenance: false | |
| sbom: false | |
| deploy_preview: | |
| name: Dispatch Docs Preview Deployment | |
| runs-on: ubuntu-latest | |
| needs: [preview_preflight, publish_preview] | |
| if: > | |
| github.event_name == 'pull_request' && | |
| needs.preview_preflight.outputs.docs_changed == 'true' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| owner: gobii-ai | |
| repositories: gobii | |
| - name: Notify infra repo | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: gobii-ai/gobii | |
| event-type: docs-preview-ready | |
| client-payload: | | |
| { | |
| "owner": "${{ github.repository_owner }}", | |
| "repo": "${{ github.event.repository.name }}", | |
| "pr_number": ${{ github.event.pull_request.number }}, | |
| "head_sha": "${{ needs.preview_preflight.outputs.head_sha }}", | |
| "image": "${{ needs.publish_preview.outputs.image }}", | |
| "html_url": "${{ github.event.pull_request.html_url }}", | |
| "trigger_run_id": ${{ github.run_id }}, | |
| "trigger_run_attempt": ${{ github.run_attempt }}, | |
| "actor": "${{ github.actor }}" | |
| } | |
| deploy: | |
| name: Dispatch Docs Staging Deployment | |
| runs-on: ubuntu-latest | |
| needs: [publish] | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@v3 | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| owner: gobii-ai | |
| repositories: gobii | |
| - name: Notify infra repo | |
| uses: peter-evans/repository-dispatch@v4 | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| repository: gobii-ai/gobii | |
| event-type: docs-staging-ready | |
| client-payload: | | |
| { | |
| "owner": "${{ github.repository_owner }}", | |
| "repo": "${{ github.event.repository.name }}", | |
| "ref": "${{ github.ref }}", | |
| "head_sha": "${{ github.sha }}", | |
| "short_sha": "${{ needs.publish.outputs.short_sha }}", | |
| "image": "${{ env.DOCS_IMAGE }}:sha-${{ github.sha }}", | |
| "trigger_run_id": ${{ github.run_id }}, | |
| "trigger_run_attempt": ${{ github.run_attempt }}, | |
| "actor": "${{ github.actor }}" | |
| } |