Skip to content

fix(api): link digest release rows upstream, not to /release/ stubs #4027

fix(api): link digest release rows upstream, not to /release/ stubs

fix(api): link digest release rows upstream, not to /release/ stubs #4027

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
# Cancel in-progress runs on the same PR ref when a new commit lands. Pushes
# to main never cancel — every merge should produce a green run.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
BUN_VERSION: "1.3.14"
jobs:
# Cheap path-filter job so downstream jobs can skip on docs-only PRs and
# so the expensive codegen-drift steps only run when GraphQL inputs
# actually moved. Jobs that depend on this and evaluate `if:` to false
# are reported as "Skipped" — required status checks treat that as
# passing, so the workflow stays compatible with branch protection.
#
# Two paths-filter steps: exclusion-based `code` needs
# predicate-quantifier: every (with the default `some`, `**` matches
# every file and `!**/*.md` never excludes — so CHANGELOG-only PRs
# still ran full lint/test). OR-style filters keep the default `some`.
changes:
name: Detect changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
code: ${{ steps.code.outputs.code }}
schema_inputs: ${{ steps.rest.outputs.schema_inputs }}
codegen_inputs: ${{ steps.rest.outputs.codegen_inputs }}
publishable: ${{ steps.rest.outputs.publishable }}
web_build: ${{ steps.rest.outputs.web_build }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
persist-credentials: false
# Exclusions only work with predicate-quantifier: every (dorny/paths-filter).
# Workflow files intentionally stay in scope — changing CI on a
# docs-only commit should still run CI.
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: code
with:
predicate-quantifier: every
filters: |
code:
- '**'
- '!**/*.md'
- '!docs/**'
- '!.context/**'
- '!LICENSE'
# OR-style filters — default predicate-quantifier: some.
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: rest
with:
filters: |
# GraphQL schema snapshot is generated from workers/api source.
# Skip the verify step when nothing here moved.
schema_inputs:
- 'workers/api/**'
- 'packages/core/**'
- 'packages/api-types/graphql/schema.graphql'
# web/codegen consumes the committed schema + web operations.
codegen_inputs:
- 'web/**'
- 'packages/api-types/graphql/schema.graphql'
# The two packages published to npm. A PR touching them should
# carry a changeset — the changeset-check job below only warns.
publishable:
- 'packages/core/**'
- 'packages/api-types/**'
# Mirror web/vercel.json ignoreCommand: Next production build when
# anything Vercel would rebuild has moved. Root oxlint ignores
# web/**, so missing named exports only surface on `next build`.
web_build:
- 'web/**'
- 'packages/**'
- 'scripts/**'
- 'bun.lock'
- 'package.json'
- '.github/workflows/ci.yml'
lint:
name: Lint & Format
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- run: bun install --frozen-lockfile
# oxlint lints + type-checks the workspace from the root .oxlintrc.json
# (typescript-go via oxlint-tsgolint; curated semantic rules + typeCheck).
- name: Oxlint
run: bun run lint
- name: Oxfmt
run: bun run format:check
test:
name: Test
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- run: bun install --frozen-lockfile
# workers/discovery, workers/mcp, and workers/webhooks are excluded
# from the root workspace and carry their own bun.lock — the root
# install above doesn't populate their node_modules, so per-worker
# tsc + deploy both need a local install. These steps also catch
# stale per-worker lockfiles at PR time (previously a separate set
# of steps at the end of this job; folded in so we don't install
# twice).
- name: Install workers/discovery deps
working-directory: workers/discovery
run: bun install --frozen-lockfile
- name: Install workers/mcp deps
working-directory: workers/mcp
run: bun install --frozen-lockfile
- name: Install workers/webhooks deps
working-directory: workers/webhooks
run: bun install --frozen-lockfile
# workers/mcp is excluded from the root oxlint type-check (carved-out
# workspace with its own bun.lock + Agents SDK tool Zod shapes that
# typescript-go doesn't model yet). Keep a targeted tsc gate here; UI
# sub-build has mcp:ui:typecheck separately.
- name: Type-check (workers/mcp)
working-directory: workers/mcp
run: npx tsc --noEmit
- name: Check migration filenames
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: ./scripts/check-migration-filenames.sh "origin/$BASE_REF"
- name: Pair schema changes with a wrangler migration (PR only)
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
# If a schema file changed on this PR, require at least one added
# file under workers/api/migrations/. Catches schema changes that
# ship without a paired wrangler migration.
schema_changed=$(git diff --name-only "origin/$BASE_REF"...HEAD -- \
'packages/core/src/schema.ts' \
'packages/core-internal/src/schema-coverage.ts' \
'workers/api/src/db/schema-cron.ts' \
'workers/api/src/db/schema-auth.ts' \
'workers/api/src/db/schema-follows.ts' || true)
if [ -z "$schema_changed" ]; then
echo "No schema files changed — pairing check skipped."
exit 0
fi
added=$(git diff --name-only --diff-filter=A "origin/$BASE_REF"...HEAD -- 'workers/api/migrations/*.sql' || true)
if [ -z "$added" ]; then
echo "ERROR: schema files changed, but this PR adds no file under workers/api/migrations/." >&2
echo "Changed schema files:" >&2
echo "$schema_changed" | sed 's/^/ /' >&2
echo "Hand-author the wrangler migration and commit it alongside the schema change." >&2
echo "Preview the SQL with: bun run db:generate (output goes to .drizzle-out/)." >&2
exit 1
fi
echo "Schema files changed:"
echo "$schema_changed" | sed 's/^/ /'
echo "Found new wrangler migration file(s):"
echo "$added" | sed 's/^/ /'
- name: Verify GraphQL schema snapshot is up to date
if: needs.changes.outputs.schema_inputs == 'true'
run: |
bun workers/api/scripts/print-graphql-schema.ts
if ! git diff --exit-code packages/api-types/graphql/schema.graphql; then
echo "ERROR: packages/api-types/graphql/schema.graphql is stale." >&2
echo "Regenerate with: bun workers/api/scripts/print-graphql-schema.ts" >&2
exit 1
fi
- name: Verify GraphQL operation codegen is up to date
if: needs.changes.outputs.codegen_inputs == 'true'
# graphql-codegen emits single-quoted, unformatted output; the
# committed copy is oxfmt-normalised by lint-staged on commit.
# Mirror that here before diffing or every PR would fail.
working-directory: web
run: |
bun run codegen
bunx oxfmt --write src/lib/graphql/__generated__/
if ! git diff --exit-code src/lib/graphql/__generated__/; then
echo "ERROR: web/src/lib/graphql/__generated__/ is stale." >&2
echo "Regenerate with: cd web && bun run codegen && bunx oxfmt --write src/lib/graphql/__generated__/" >&2
exit 1
fi
- name: Verify OpenAPI spec covers public-read routes
# Diffs every method registered under a publicReadRoutes prefix
# against the generated /v1/openapi.json. Fails if any public-read
# route is missing describeRoute(...) and isn't on the script's
# ALLOWLIST. Sub-second pure-JS check; no network, D1, or Wrangler.
run: bun scripts/check-openapi-coverage.ts
- name: Verify managed-agent YAML is up to date
# Re-renders the committed managed-agents/*.agent.yaml from the prompt
# builders, AGENT_TOOLS, and skill IDs, and fails if any file drifted.
# Pure codegen — no network or API calls. Regenerate locally with:
# bun scripts/render-managed-agents.ts
run: bun scripts/render-managed-agents.ts --check
- name: Run tests
run: bun run test
# Same production Next build Vercel runs for web/ (Next 16 + Turbopack). Root
# oxlint ignorePatterns include web/**, so type/export errors that fail the
# Vercel deploy never fail lint/test alone. Path filter mirrors
# web/vercel.json ignoreCommand so docs-only PRs stay skipped.
web-build:
name: Web build (Next)
needs: changes
if: needs.changes.outputs.web_build == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
env:
# Public prod API is fine for compile + static generation; no secrets
# required. Matches web/.env.example defaults for a green build.
RELEASES_API_URL: https://api.releases.sh
NEXT_PUBLIC_BETTER_AUTH_URL: https://api.releases.sh
NEXT_PUBLIC_USER_API_KEYS: "false"
NEXT_PUBLIC_DEVICE_AUTH_ENABLED: "false"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
# Root install: web is a workspace package and consumes packages/* via
# workspace:*. Matches Vercel installCommand ("bun install") at monorepo root.
- run: bun install --frozen-lockfile
# web/package.json "build": copy install script, build .well-known assets,
# then `next build` (Turbopack in Next 16). Catches missing exports,
# bad imports, and Next compile errors before Vercel.
- name: Next production build
working-directory: web
run: bun run build
# Non-blocking reminder: a PR that touches a published package (packages/core,
# packages/api-types) should usually add a changeset so the release notes +
# version bump are captured. `continue-on-error` makes this advisory — a
# missing changeset surfaces as a warning but never fails the PR. Do NOT add
# this to branch protection.
changeset-check:
name: Changeset present?
needs: changes
if: github.event_name == 'pull_request' && needs.changes.outputs.publishable == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
continue-on-error: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ env.BUN_VERSION }}
- run: bun install --frozen-lockfile
- name: Check for a changeset
env:
BASE_REF: ${{ github.base_ref }}
run: |
if bunx changeset status --since="origin/$BASE_REF"; then
echo "Changeset present (or no publishable change detected)."
else
echo "::warning::This PR changes a published package but adds no changeset. Run 'bun run changeset' to capture the version bump + changelog. (Advisory — not required to merge.)"
fi