Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,50 @@ registries:
password: ${{secrets.DOCKER_PASSWORD}}

updates:
# No Dockerfile lives at the repo root — each app has its own under
# apps/<app>/docker/Dockerfile, and dependabot's docker ecosystem has no
# glob support, so each needs its own directory entry or its base image
# never gets tracked.
- package-ecosystem: "docker"
directory: "/"
directory: "/apps/builder/docker"
schedule:
interval: "daily"
registries:
- dockerhub
- package-ecosystem: "docker"
directory: "/apps/worker/docker"
schedule:
interval: "daily"
registries:
- dockerhub
- package-ecosystem: "docker"
directory: "/apps/realtime/docker"
schedule:
interval: "daily"
registries:
- dockerhub
- package-ecosystem: "docker"
directory: "/apps/mcp-server/docker"
schedule:
interval: "daily"
registries:
- dockerhub
- package-ecosystem: "docker"
directory: "/apps/javascript-executor/docker"
schedule:
interval: "daily"
registries:
- dockerhub

# Tracks the actions pinned in .github/workflows/*.yml (actions/checkout,
# docker/build-push-action, etc.) — without this entry nothing refreshes
# those versions, floating-tag or SHA-pinned alike.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "chore(ci)"

# pnpm workspace: a single root entry covers every apps/* packages/* integrations/*
# member because they share one root pnpm-lock.yaml. Dependabot reads
Expand Down
108 changes: 96 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,45 @@ name: CI

# Quality gate for type-checking, linting, and tests.
#
# This workflow is the ONLY type gate for apps/builder: `next build` runs with
# `typescript.ignoreBuildErrors: true` (see apps/builder/next.config.ts), so
# `turbo run check-types` here must stay green — never remove it without
# The `check-types` job is the ONLY type gate for apps/builder: `next build`
# runs with `typescript.ignoreBuildErrors: true` (see apps/builder/next.config.ts),
# so `turbo run check-types` here must stay green — never remove it without
# re-enabling type-checking inside the build.
#
# The three phases run as separate jobs rather than one `turbo run` invocation.
# They are independent, so splitting gives each its own 4-vCPU runner (~3x the
# total CPU) and makes wall clock the slowest phase instead of their sum. It
# also lets each pick its own concurrency: tsc is single-threaded and CPU-bound,
# while vitest parallelizes internally and must stay within the vCPU budget.

on:
pull_request:
push:
branches: [main]

# Read-only: these jobs only check out, install, and run local scripts —
# none of them need to write contents, packages, or PR/issue state.
permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
name: Types, lint, tests
check-types:
name: Types
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v5

# Must stay BEFORE actions/setup-node so its `cache: pnpm` can resolve
# the pnpm binary to locate the store. v6 drops the deprecated Node 20
# runtime; the version itself comes from `packageManager` in package.json.
- name: Set up pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6.0.10

- name: Set up Node.js
uses: actions/setup-node@v5
Expand All @@ -42,8 +56,78 @@ jobs:
- name: Set up Turbo cache
uses: rharkor/caching-for-turbo@v2.5.1

# --concurrency=4 matches the runner's vCPU count: tsc and vitest tasks
# are all CPU-bound, so turbo's default of 10 concurrent tasks only adds
# contention (and test timeouts) on a 4-core box.
- name: Type-check, lint, and test
run: pnpm turbo run check-types lint test --concurrency=4
# 56 independent `tsc --noEmit` runs (no `composite`/`references` in this
# repo, so turbo.json declares no `dependsOn` — see the comment there).
# Each is single-threaded, so concurrency can fill all 4 vCPUs.
- name: Type-check
run: pnpm turbo run check-types --concurrency=4

lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up pnpm
uses: pnpm/action-setup@v6.0.10

- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Set up Turbo cache
uses: rharkor/caching-for-turbo@v2.5.1

# Root `pnpm lint` runs cheapest-first: check:agent-instructions, then
# `turbo run lint` (apps/builder's i18n key-parity check plus 57
# <NONEXISTENT> placeholders), then the slowest step — repo-wide Biome
# (biome.json globs apps/**, packages/**, integrations/**). One script
# covers everything; no separate turbo invocation needed.
- name: Lint
run: pnpm lint

test:
name: Tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up pnpm
uses: pnpm/action-setup@v6.0.10

- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Set up Turbo cache
uses: rharkor/caching-for-turbo@v2.5.1

# Turbo does not parallelize *within* a suite, so wall clock is bounded
# by the slowest single suite — builder (248 files), which is ~40% of
# total test CPU and scales to 4 vitest workers (see the measurements in
# packages/vitest-config/src/node.ts). The preset gives each suite 4
# workers on the `threads` pool.
#
# concurrency=2 slightly oversubscribes the 4 vCPUs by design: the 55
# other suites are small and mostly I/O- and import-bound, so letting one
# overlap builder fills the box while builder still gets the cores it
# needs. Measured end-to-end on the full 56-suite run: concurrency=1
# 170s, =2 123s, =4 101s (on a 12-core box, so CI compresses these) —
# =1 serializes 55 small suites to help only builder, while =4 would
# contend with builder's own workers on a 4-vCPU runner.
- name: Test
run: pnpm turbo run test --concurrency=2
44 changes: 8 additions & 36 deletions .github/workflows/pr-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ name: PR Labeler

on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
types: [opened, edited, reopened]

permissions:
pull-requests: write
issues: write

concurrency:
group: pr-labeler-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
label:
Expand All @@ -16,18 +19,6 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const LABEL_DEFS = {
'feature': { color: '0075ca', description: 'New feature or request' },
'bug': { color: 'd73a4a', description: "Something isn't working" },
'improvement': { color: 'a2eeef', description: 'Refactor or performance improvement' },
'chore': { color: 'e4e669', description: 'Maintenance / housekeeping' },
'ci': { color: 'e4e669', description: 'CI/CD pipeline changes' },
'docs': { color: '0075ca', description: 'Documentation changes' },
'security': { color: 'ee0701', description: 'Security fix or hardening' },
'breaking-change': { color: 'b60205', description: 'Contains a breaking change' },
'dependencies': { color: '0366d6', description: 'Dependency updates' },
};

const TYPE_MAP = [
{ re: /^feat(\([^)]+\))?!/, labels: ['feature', 'breaking-change'] },
{ re: /^feat(\([^)]+\))?:/, labels: ['feature'] },
Expand All @@ -53,28 +44,9 @@ jobs:
return;
}

// Ensure all required labels exist in the repo before applying them.
const { data: existing } = await github.rest.issues.listLabelsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
});
const existingNames = new Set(existing.map(l => l.name));

for (const name of match.labels) {
if (!existingNames.has(name)) {
const meta = LABEL_DEFS[name];
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name,
color: meta.color,
description: meta.description,
});
console.log(`Created label: ${name}`);
}
}

// Labels are bootstrapped once in the repo (see git history for
// pr-labeler.yml) rather than created here on every run — that
// dropped the `issues: write` scope this job used to need.
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
Expand Down
Loading
Loading