name: Docs site on: # Stable desktop releases are the production publication boundary. The # release gate below excludes mobile and prerelease tags from this trigger. release: types: [published] pull_request: paths: - 'docs/site/**' - '.github/workflows/docs.yml' workflow_dispatch: inputs: tag: description: 'Stable desktop release tag to redeploy (vX.Y.Z)' required: true type: string permissions: contents: read concurrency: group: docs-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('release-{0}', github.event.release.tag_name || inputs.tag) }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} defaults: run: # The Vercel project is rooted at `.`; this package directory is the # complete upload and build context. working-directory: docs/site jobs: # This job deliberately has no deployment credentials and runs for fork PRs # as well as same-repository PRs. check: name: Build and test if: github.event_name == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout pull request uses: actions/checkout@v6 with: persist-credentials: false - name: Setup pnpm uses: pnpm/action-setup@v6 with: version: 10.24.0 run_install: false - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm cache-dependency-path: docs/site/pnpm-lock.yaml - name: Install site dependencies run: pnpm --ignore-workspace install --frozen-lockfile - name: Run package tests run: pnpm --ignore-workspace test - name: Lint site run: pnpm --ignore-workspace lint - name: Typecheck site run: pnpm --ignore-workspace exec tsc --noEmit --incremental false - name: Build site run: pnpm --ignore-workspace build release_gate: name: Authorize release if: >- github.repository == 'stablyai/orca' && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') runs-on: ubuntu-latest timeout-minutes: 5 outputs: deploy: ${{ steps.validate.outputs.deploy }} steps: - name: Validate stable desktop tag id: validate env: EVENT_NAME: ${{ github.event_name }} RELEASE_TAG: ${{ github.event.release.tag_name }} INPUT_TAG: ${{ inputs.tag }} RELEASE_PRERELEASE: ${{ github.event.release.prerelease }} RELEASE_DRAFT: ${{ github.event.release.draft }} RELEASE_AUTHOR: ${{ github.event.release.author.login }} DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} WORKFLOW_REF: ${{ github.ref }} GH_TOKEN: ${{ github.token }} shell: bash working-directory: . run: | set -euo pipefail tag="$INPUT_TAG" [[ "$EVENT_NAME" == "release" ]] && tag="$RELEASE_TAG" # Match the stable desktop format used by release-policy.yml. This # intentionally rejects mobile-* and all -rc.* tags. stable_tag=false [[ "$tag" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] && stable_tag=true if [[ "$stable_tag" != "true" ]]; then echo "Release $tag is not a stable desktop release; skipping docs deployment." echo "deploy=false" >> "$GITHUB_OUTPUT" exit 0 fi if [[ "$EVENT_NAME" == "release" ]]; then authorized_ref=true authorized_author=false [[ "$RELEASE_AUTHOR" == "github-actions[bot]" ]] && authorized_author=true release_state_ok=false [[ "$RELEASE_PRERELEASE" == "false" && "$RELEASE_DRAFT" == "false" ]] && release_state_ok=true else authorized_ref=false [[ "$WORKFLOW_REF" == "refs/heads/$DEFAULT_BRANCH" ]] && authorized_ref=true release_json="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$tag")" authorized_author=false [[ "$(jq -r '.author.login' <<<"$release_json")" == "github-actions[bot]" ]] && authorized_author=true release_state_ok=false if [[ "$(jq -r '.tag_name' <<<"$release_json")" == "$tag" && "$(jq -r '.prerelease' <<<"$release_json")" == "false" && "$(jq -r '.draft' <<<"$release_json")" == "false" ]]; then release_state_ok=true fi fi deploy=false if [[ "$authorized_ref" == "true" && "$authorized_author" == "true" && "$release_state_ok" == "true" ]]; then deploy=true else echo "Release $tag is not an authorized stable desktop release; skipping docs deployment." fi echo "deploy=$deploy" >> "$GITHUB_OUTPUT" preview: name: Preview # Only trusted branches can access the Vercel preview environment. Forks # still receive the credential-free check job above. needs: check if: >- github.event_name == 'pull_request' && needs.check.result == 'success' && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read issues: write pull-requests: write environment: name: docs-preview env: VERCEL_TELEMETRY_DISABLED: '1' steps: - name: Checkout pull request uses: actions/checkout@v6 with: persist-credentials: false - name: Setup pnpm uses: pnpm/action-setup@v6 with: version: 10.24.0 run_install: false - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm cache-dependency-path: docs/site/pnpm-lock.yaml - name: Install site dependencies run: pnpm --ignore-workspace install --frozen-lockfile - name: Verify Vercel credentials env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: | set -euo pipefail test -n "${VERCEL_TOKEN:-}" || { echo '::error::VERCEL_TOKEN is not configured for docs-preview.'; exit 1; } test -n "${VERCEL_ORG_ID:-}" || { echo '::error::VERCEL_ORG_ID is not configured for docs-preview.'; exit 1; } test -n "${VERCEL_PROJECT_ID:-}" || { echo '::error::VERCEL_PROJECT_ID is not configured for docs-preview.'; exit 1; } - name: Pull Vercel preview settings env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: pnpm --ignore-workspace exec vercel pull --yes --non-interactive --environment=preview - name: Build preview run: pnpm --ignore-workspace exec vercel build --non-interactive - name: Deploy preview id: deploy shell: bash env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: | set -euo pipefail # `--non-interactive` returns a JSON status envelope, not a bare URL. deployment_json="$(pnpm --ignore-workspace exec vercel deploy --prebuilt --yes --non-interactive --format json)" printf '%s\n' "$deployment_json" url="$(jq -er '.deployment.url // .url // empty' <<<"$deployment_json")" [[ "$url" =~ ^https://[^[:space:]]+$ ]] printf '%s\n' "$url" echo "url=$url" >> "$GITHUB_OUTPUT" echo "Preview deployed to $url" >> "$GITHUB_STEP_SUMMARY" - name: Link preview in pull request uses: actions/github-script@v8 env: PREVIEW_URL: ${{ steps.deploy.outputs.url }} with: script: | const marker = '' const url = process.env.PREVIEW_URL if (!url) throw new Error('Preview URL was not produced') const body = `${marker}\nDocs preview: [${url}](${url})` const comments = await github.paginate(github.rest.issues.listComments, { owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, per_page: 100 }) const existing = comments.find((comment) => comment.body?.includes(marker)) if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body }) } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body }) } production: name: Production if: >- (github.event_name == 'release' || github.event_name == 'workflow_dispatch') && needs.release_gate.result == 'success' && needs.release_gate.outputs.deploy == 'true' needs: release_gate runs-on: ubuntu-latest timeout-minutes: 15 environment: name: docs-production url: https://www.onorca.dev/docs env: VERCEL_TELEMETRY_DISABLED: '1' steps: - name: Checkout released tag uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name || inputs.tag }} persist-credentials: false - name: Setup pnpm uses: pnpm/action-setup@v6 with: version: 10.24.0 run_install: false - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 22 cache: pnpm cache-dependency-path: docs/site/pnpm-lock.yaml - name: Install site dependencies run: pnpm --ignore-workspace install --frozen-lockfile - name: Verify Vercel credentials env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: | set -euo pipefail test -n "${VERCEL_TOKEN:-}" || { echo '::error::VERCEL_TOKEN is not configured for docs-production.'; exit 1; } test -n "${VERCEL_ORG_ID:-}" || { echo '::error::VERCEL_ORG_ID is not configured for docs-production.'; exit 1; } test -n "${VERCEL_PROJECT_ID:-}" || { echo '::error::VERCEL_PROJECT_ID is not configured for docs-production.'; exit 1; } - name: Pull Vercel production settings env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: pnpm --ignore-workspace exec vercel pull --yes --non-interactive --environment=production - name: Run package tests run: pnpm --ignore-workspace test - name: Lint site run: pnpm --ignore-workspace lint - name: Typecheck site run: pnpm --ignore-workspace exec tsc --noEmit --incremental false - name: Build production site run: pnpm --ignore-workspace exec vercel build --prod --non-interactive - name: Deploy production site env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} run: pnpm --ignore-workspace exec vercel deploy --prebuilt --prod --yes --non-interactive