mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Merge pull request #62 from fsolla/agent/ops-gate-ci
chore(ops): gate:ci no pre-push + fluxo PR de agentes
This commit is contained in:
commit
f2d4227935
10 changed files with 246 additions and 17 deletions
61
.cursor/rules/agent-pr-workflow.mdc
Normal file
61
.cursor/rules/agent-pr-workflow.mdc
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
description: PR workflow for Teqo agents — overrides user-level gh-github draft defaults
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Agent PR workflow (Teqo)
|
||||
|
||||
**This repo overrides any user-level rule that says "always create PRs as draft".** Agent deliveries merge to `stage`, not `main`.
|
||||
|
||||
## Create (Ready, base stage)
|
||||
|
||||
```bash
|
||||
git push -u origin HEAD
|
||||
gh pr create --base stage --title "<id> — <title>" --body "$(cat <<'EOF'
|
||||
## O que é
|
||||
<2–4 linhas>
|
||||
|
||||
Closes #<N>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
- **Never** `--draft`. PRs must be **Ready for review** on creation.
|
||||
- Base is always **`stage`** (never `main` — promote is human-only via `pnpm agent:promote --i-am-human`).
|
||||
|
||||
## Auto-merge immediately
|
||||
|
||||
Right after `gh pr create`, enable auto-merge:
|
||||
|
||||
```bash
|
||||
gh pr merge --auto --merge
|
||||
```
|
||||
|
||||
Do not wait for human approval. Auto-merge fires when **required** checks pass.
|
||||
|
||||
## Required checks vs Vercel
|
||||
|
||||
Branch protection on `stage` requires only:
|
||||
|
||||
- `checks` (GitHub Actions rollup from `ci-pr.yml`)
|
||||
- `migration-lock`
|
||||
|
||||
**Vercel preview is NOT a merge gate.** Do not block on it.
|
||||
|
||||
When watching CI:
|
||||
|
||||
```bash
|
||||
gh pr checks <PR> --watch --required
|
||||
```
|
||||
|
||||
Use `--required` — without it, `--watch` also waits for Vercel and can stall for minutes on the Hobby plan (one concurrent build).
|
||||
|
||||
Once `checks` and `migration-lock` are green, auto-merge proceeds even if Vercel is still `PENDING`.
|
||||
|
||||
## Agent branches skip Vercel builds
|
||||
|
||||
Branches `agent/*` and `cursor/*` use `scripts/vercel-ignore-build.sh` so parallel agents do not queue Vercel builds. Previews still deploy for `stage` and `main`.
|
||||
|
||||
## Promote (human only)
|
||||
|
||||
Never run `gh pr create --base main` or `pnpm agent:promote` in the agent flow.
|
||||
|
|
@ -11,7 +11,7 @@ Established 2026-07-23 by the engineering-standards hardening (tracker: `docs/IM
|
|||
|
||||
1. **Dead code dies immediately.** After your edits, check what your change orphaned and delete it in the same delivery: run `pnpm exec knip` (files/dependencies are CI-blocking errors; unused exports are warnings being driven to zero). Never leave commented-out code, unused exports, superseded components, or one-shot scripts behind — git history is the archive.
|
||||
2. **Zero warnings.** `pnpm lint` runs with `--max-warnings=0`. Deliberate placeholders use a `_` prefix. Never silence a rule without an inline justification comment.
|
||||
3. **Gate em duas velocidades (desde 2026-07-30, paradigma de agentes paralelos — `docs/AGENT-OPS.md`).** **Fast gate local do agente** (antes de cada push): `pnpm gate:fast` (lint + typecheck + test:unit); o pre-push hook roda `pnpm gate:push` (fast + format:check + check:cycles). Fonte única: scripts `gate:*` em `package.json`; escape para WIP: `git push --no-verify`. Se a entrega tocou schema: também `pnpm migrate && pnpm db:seed:minimal && pnpm test:int`. **Gate completo = CI do PR** (`ci-pr.yml`: jobs paralelos — estático + unit/int afetados pelo diff + e2e por manifesto + build, contra o DB mínimo). O handoff humano (promote `stage→main`) exige CI stage + ci-pr green — não o build local. **CI vermelho no teu PR é teu, mesmo pré-existente ou de infra** — "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`).
|
||||
3. **Gate em duas velocidades (desde 2026-07-30, paradigma de agentes paralelos — `docs/AGENT-OPS.md`).** **Fast gate local do agente** (iteração durante a sessão): `pnpm gate:fast` (lint + typecheck + test:unit). **Pre-push hook** roda `pnpm gate:push` → `pnpm gate:ci` — espelho serial do `ci-pr.yml`: estático sempre; unit/int/e2e no **mesmo blast radius** que o CI (`scripts/test-affected.mjs`, `scripts/e2e-affected.mjs` vs `origin/stage`); build sempre. Fonte única: scripts `gate:*` em `package.json`; escape para WIP: `git push --no-verify`. `migration-lock` só existe no GitHub (conta PRs abertos em schema). O handoff humano (promote `stage→main`) exige CI stage + ci-pr green — não o build local. **CI vermelho no teu PR é teu, mesmo pré-existente ou de infra** — "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`).
|
||||
4. **Run gate commands bare, never piped** (`pnpm test | tail` swallows the exit code). Never run `knip --fix` blind — verify each removal with `git grep -w <symbol>` first (knip still cannot load `payload.config.ts`, so its graph is incomplete; ledgered as P3).
|
||||
|
||||
## Type honesty
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ Esta skill conduz UMA Issue rastreável do claim ao merge em `stage`. Substitui
|
|||
- [ ] 3. Freshness audit (enxuto) do plano contra o repositório
|
||||
- [ ] 4. Executar as fases (schema/server → UI via /impeccable → gates de engenharia)
|
||||
- [ ] 5. /simplify no diff da sessão → capture-review-debts
|
||||
- [ ] 6. Fechar em stage: fast gate → branch → commit → gh pr create --base stage (Closes #N)
|
||||
→ gh pr merge --auto --merge → gh pr checks --watch até o merge → consolidar pontas soltas
|
||||
- [ ] 6. Fechar em stage: pnpm gate:push → branch → commit → gh pr create --base stage (Closes #N, sem --draft)
|
||||
→ gh pr merge --auto --merge → gh pr checks --watch --required até o merge → consolidar pontas soltas
|
||||
```
|
||||
|
||||
## Passo 0 — Prep (deps)
|
||||
|
|
@ -81,7 +81,7 @@ Ordem fixa, fases pequenas e verificáveis, respeitando o appetite do plano:
|
|||
|
||||
1. **Schema e server** — migrations (`pnpm migrate:create`, seguir `payload-migrations`), collections, utilities, server actions, testes de domínio. Guardrails do repo: Local API com `user` → `overrideAccess: false`; escrita multi-collection → transação com `req: { transactionID }`; pessoa → join com `Contact`; opt-in/PII → `Consent` por chave estável falhando fechado.
|
||||
2. **UI via /impeccable** (classes B/C/D — a classe está no plano; se A, declare "Impeccable: N/A" e siga só engenharia): shape conforme a classe → craft → critique → (harden/optimize **só sob gatilho**) → polish. Paleta = tokens `data-theme='campaign'`; reusar `src/components/ui` e shells existentes; shape obrigatório em C **para** para confirmação do brief antes do craft.
|
||||
3. **Gates de engenharia** — fast gate local: `pnpm gate:fast` (lint + typecheck + test:unit) e, antes do push, `pnpm gate:push` (fast + format:check + check:cycles — o pre-push hook roda exatamente isso) (+ `pnpm migrate && pnpm db:seed:minimal && pnpm test:int` se tocou schema). Scan Aikido dos arquivos editados. Comandos bare, nunca piped.
|
||||
3. **Gates de engenharia** — iteração: `pnpm gate:fast`. Antes do push o hook roda `pnpm gate:push` (= `gate:ci`: estático + build sempre; unit/int/e2e no blast radius do CI via `test-affected`/`e2e-affected`). Scan Aikido dos arquivos editados. Comandos bare, nunca piped. Escape WIP: `git push --no-verify`.
|
||||
|
||||
Tracer bullet: se a Issue for grande, a primeira fatia vertical real (schema mínimo → uma action → uma superfície UI) vem cedo.
|
||||
|
||||
|
|
@ -91,11 +91,11 @@ Rode `/simplify` sobre o diff da sessão. Follow-ups maiores que o cleanup → `
|
|||
|
||||
## Passo 6 — Fechar em stage
|
||||
|
||||
1. Fast gate verde (Passo 4.3).
|
||||
1. `pnpm gate:push` verde (ou `pnpm gate:ci` — espelho do ci-pr; o hook pre-push roda na sequência do `git push`).
|
||||
2. Branch `agent/<id>-<slug>` (worktrees do Cursor são donos da criação; commits lógicos).
|
||||
3. `gh pr create --base stage` com `Closes #<N>` no body.
|
||||
4. `gh pr merge --auto --merge <PR>`.
|
||||
5. **Acompanhe os checks até o merge** (`gh pr checks <PR> --watch`): falha no ci-pr → corrige na mesma branch e reempurra (o auto-merge dispara de novo quando verde). **Qualquer falha é tua** — infra do workflow, teste pré-existente ou regressão da feature; "fora do escopo da feature" não é critério de parada (política "Dono do PR, dono do CI" em `docs/AGENT-OPS.md`; exceção de blast radius: migration/access/Consent → para e escala).
|
||||
3. `gh pr create --base stage` com `Closes #<N>` no body — **nunca `--draft`** (`.cursor/rules/agent-pr-workflow.mdc`).
|
||||
4. `gh pr merge --auto --merge <PR>` imediatamente após criar o PR.
|
||||
5. **Acompanhe só os checks obrigatórios** (`gh pr checks <PR> --watch --required`): gate = `checks` + `migration-lock`; Vercel não bloqueia. Falha no ci-pr → corrige na mesma branch.
|
||||
6. O flip `in-progress → done` é **determinístico no CI** (workflow `issue-done-on-stage-merge.yml` lê o `Closes #N` no merge em stage). O agente espera o merge apenas para verificar e **consolidar pontas soltas**: débitos não registrados, doc da sessão pendente (notebook do projeto + `docs/plans/<slug>.md` com Status/Atualizado em), comentário de fechamento na Issue com o que entrou. Se o agente falhar depois do merge, o status já está correto.
|
||||
|
||||
## Resumo final ao usuário
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# Fast gate before push (source of truth: package.json `gate:push`).
|
||||
# Full gate = CI do PR (docs/AGENT-OPS.md). Escape hatch para WIP: `git push --no-verify`.
|
||||
# Full ci-pr mirror before push (source of truth: package.json `gate:push` → `gate:ci`).
|
||||
# Escape hatch para WIP: `git push --no-verify`.
|
||||
pnpm gate:push
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ stage → CI stage green → (humano) pnpm agent:promote --i-am-human → main
|
|||
|
||||
**Skills do fluxo (desde 2026-07-30):** `plan-issue` (ideia → plano completo + gate de confirmação + Issue via `agent:register --model`) → `work-issue` (claim → verificação de modelo → freshness audit → execução → /simplify → PR `--base stage` → acompanha CI até o merge) → `project-status` (overview/fila/mermaid/bloqueios/consolidação, read-only). `docs/roadmap.md` = **legado congelado** (stub); a fonte canônica são as GitHub Issues.
|
||||
|
||||
- **Agente faz sozinho:** `pnpm agent:claim` (pega a próxima Issue `ready` desbloqueada por `prio:P0..P3`, lock otimista ready→in-progress) → implementa → `pnpm gate:fast` → `gh pr create --base stage` com `Closes #N` → acompanha `gh pr checks --watch` até o merge (falha → corrige na mesma branch). O flip `in-progress→done` é determinístico no CI (workflow `issue-done-on-stage-merge.yml`).
|
||||
- **Agente faz sozinho:** `pnpm agent:claim` → implementa → `pnpm gate:push` (hook pre-push = espelho ci-pr) → `gh pr create --base stage` com `Closes #N` (**Ready**, sem `--draft`) → `gh pr merge --auto --merge` → `gh pr checks --watch --required` até o merge. Regra PR: `.cursor/rules/agent-pr-workflow.mdc`.
|
||||
|
||||
### Dono do PR, dono do CI (desde 2026-07-30 — incidente PR #50)
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ Labels: estado `ready|in-progress|blocked|done|in-prod`, `prio:P0..P3`, `kind:fe
|
|||
1. Se toca schema (migration, collection, Consent key, dado de boot) → **atualizar `db:seed:minimal` no mesmo PR** (label `needs:migration`). CI PR falha se `migrate + seed:minimal + test:int` não passam.
|
||||
2. ≤1 PR aberto tocando `src/migrations/` | `payload-types.ts` (job `migration-lock`).
|
||||
3. `docs/plans/<slug>.md` novo só para Issue nova — nunca recriar plans históricos no merge.
|
||||
4. **Ready + auto-merge:** ver `.cursor/rules/agent-pr-workflow.mdc` — `gh pr create --base stage` (sem `--draft`) → `gh pr merge --auto --merge`; acompanhar com `gh pr checks --watch --required` (Vercel não é gate).
|
||||
|
||||
## CI por alvo
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ Labels: estado `ready|in-progress|blocked|done|in-prod`, `prio:P0..P3`, `kind:fe
|
|||
| `ci-stage.yml` | push em `stage` | `STAGE_DATABASE_URL` (Environment `stage`) | **só** `migrate` + smoke int (subset curado — a suíte completa é lenta demais contra Neon remoto, estourou timeout 2× em 2026-07-30; a full fica no ci-pr/ci.yml) — **NUNCA `pnpm build`** (build migra o banco que vê), nunca seed:minimal |
|
||||
| `ci.yml` | push em `main` | service Postgres | **gate completo sempre** (rede de segurança pós-promote): estático + unit + int full (com seed:minimal) + build + e2e full |
|
||||
|
||||
Fast gate local do agente antes do push: `pnpm gate:fast` (lint + typecheck + test:unit); o hook `.husky/pre-push` roda `pnpm gate:push` (fast + format:check + check:cycles). Fonte única: scripts `gate:*` em `package.json`. knip fica fora do hook (grafo incompleto — ledger P3; CI o roda bloqueante). Escape para WIP: `git push --no-verify` (o CI repete tudo). **E2e afetado localmente (mesmo critério do PR):** `pnpm test:e2e:affected`. int/build/e2e = CI.
|
||||
Fast gate local do agente (iteração): `pnpm gate:fast` (lint + typecheck + test:unit). O hook `.husky/pre-push` roda `pnpm gate:push` → `scripts/gate-ci.mjs` — **espelho serial do `ci-pr.yml`**: estático + build sempre; unit/int/e2e no **mesmo blast radius** que o CI de feature (`test-affected.mjs` / `e2e-affected.mjs` vs `origin/stage`). Requer Postgres local com `teqo_test` (`pnpm db:start` ou `.cursor/ensure-postgres.sh` no Cloud). `migration-lock` só no GitHub. Escape WIP: `git push --no-verify`. Debug e2e isolado: `pnpm test:e2e:affected`.
|
||||
|
||||
## Stage DB — runbook de refresh (semanal, humano)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Gate local DRY — gate:fast / gate:push + pre-push completo
|
||||
|
||||
Status: entregue (2026-07-30 — executado em sessão única, fora do fluxo agent:register, a pedido do humano)
|
||||
Status: entregue (2026-07-30) — **ampliado 2026-07-30**: `gate:push` → `gate:ci` espelha `ci-pr.yml` com seleção affected (`test-affected` / `e2e-affected` vs `origin/stage`), não suíte full por default.
|
||||
Atualizado em: 2026-07-30
|
||||
Issue: —
|
||||
Priority: P2
|
||||
|
|
@ -28,7 +28,7 @@ Decisão de produto (2026-07-30, brief do lote CI): scripts DRY (`gate:fast`, `g
|
|||
|
||||
## Decisões travadas
|
||||
|
||||
- **knip fora do pre-push.** O grafo do knip é incompleto (não carrega `payload.config.ts`, ledger P3) e é lento para um hook; CI já o roda como erro bloqueante. **Rejeitado:** knip no hook (custo por push + falsos positivos já fichados); knip no `gate:fast` documentado como obrigatório (mesma razão).
|
||||
- **knip fora do pre-push.** _(Revertido 2026-07-30 — `gate:ci` inclui knip para espelhar ci-pr.)_ O grafo do knip é incompleto (não carrega `payload.config.ts`, ledger P3) e é lento para um hook; CI já o roda como erro bloqueante. **Rejeitado na v1:** knip no hook (custo por push + falsos positivos já fichados).
|
||||
- **Sem pre-commit hook.** A divisão fica: nada no commit (commits intermediários de WIP são legítimos), tudo no push. **Rejeitado:** lint-staged no commit (adiciona toolchain e atrito sem pegar classe de erro que o push não pegue segundos depois).
|
||||
- **Escape hatch = `--no-verify` nativo, documentado; sem variável mágica própria.** **Rejeitado:** `SKIP_GATE=1` (mais uma convenção para manter; `--no-verify` é o padrão git que todo agente/humano já conhece).
|
||||
- **Hook chama `pnpm gate:push`, nunca inline de comandos.** Uma fonte de verdade editável em `package.json`. **Rejeitado:** manter a linha de shell no hook (foi o que causou o drift atual).
|
||||
|
|
@ -41,7 +41,7 @@ Decisão de produto (2026-07-30, brief do lote CI): scripts DRY (`gate:fast`, `g
|
|||
|
||||
Componentes:
|
||||
|
||||
- **`package.json`** — scripts `gate:fast` e `gate:push` (comandos bare encadeados com `&&`, nunca piped — invariante do repo).
|
||||
- **`package.json`** — scripts `gate:fast`, `gate:ci` (`scripts/gate-ci.mjs`) e `gate:push` (= `gate:ci`).
|
||||
- **`.husky/pre-push`** — `pnpm gate:push`.
|
||||
- **`.cursor/rules/engineering-standards.mdc`** — item 3 do "After every change" passa a citar `pnpm gate:fast` / `pnpm gate:push`.
|
||||
- **`docs/AGENT-OPS.md`** — linha do fast gate local atualizada para os scripts.
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@
|
|||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"gate:fast": "pnpm lint && pnpm typecheck && pnpm test:unit",
|
||||
"gate:push": "pnpm gate:fast && pnpm format:check && pnpm check:cycles",
|
||||
"gate:ci": "node scripts/gate-ci.mjs",
|
||||
"gate:push": "pnpm gate:ci",
|
||||
"check:cycles": "madge --circular --extensions ts,tsx --ts-config tsconfig.json src",
|
||||
"generate:importmap": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload generate:importmap",
|
||||
"generate:types": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload generate:types",
|
||||
|
|
|
|||
147
scripts/gate-ci.mjs
Normal file
147
scripts/gate-ci.mjs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Local mirror of `.github/workflows/ci-pr.yml` (serial): static checks always;
|
||||
* unit/int/e2e follow the same blast-radius selection as CI (`test-affected.mjs`,
|
||||
* `e2e-affected.mjs` vs `origin/stage`). Build always runs. `migration-lock` is
|
||||
* GitHub-only.
|
||||
*
|
||||
* Preflight checks only `teqo_test` (ci-pr never touches the dev DB). Escape:
|
||||
* `git push --no-verify`.
|
||||
*/
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { parse as parseEnv } from 'dotenv'
|
||||
|
||||
import { diagnoseDatabaseTarget } from './db-doctor.mjs'
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const readTestEnv = () => {
|
||||
try {
|
||||
return parseEnv(readFileSync(path.join(repoRoot, '.env.test'), 'utf8'))
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const testEnv = readTestEnv()
|
||||
|
||||
/** Env for migrate / seed / build — matches ci-pr.yml service Postgres. */
|
||||
const dbEnv = {
|
||||
...process.env,
|
||||
DATABASE_URL: testEnv.DATABASE_URL ?? 'postgresql://teqo:teqo@localhost:5432/teqo_test',
|
||||
PAYLOAD_SECRET: testEnv.PAYLOAD_SECRET ?? 'test-only-secret-not-used-in-production',
|
||||
NEXT_PUBLIC_SITE_URL: testEnv.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000',
|
||||
}
|
||||
|
||||
const readJsonScript = (script) => {
|
||||
const raw = execFileSync('node', [script], {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
env: process.env,
|
||||
})
|
||||
return JSON.parse(raw.trim())
|
||||
}
|
||||
|
||||
const run = (label, command, args, env = process.env) => {
|
||||
console.log(`\n[gate:ci] ▶ ${label}`)
|
||||
const result = spawnSync(command, args, {
|
||||
cwd: repoRoot,
|
||||
stdio: 'inherit',
|
||||
env,
|
||||
})
|
||||
if (result.status !== 0) {
|
||||
console.error(`\n[gate:ci] ✗ ${label} failed (exit ${result.status ?? 1})`)
|
||||
process.exit(result.status ?? 1)
|
||||
}
|
||||
}
|
||||
|
||||
const main = async () => {
|
||||
const testScope = readJsonScript('scripts/test-affected.mjs')
|
||||
const e2eScope = readJsonScript('scripts/e2e-affected.mjs')
|
||||
|
||||
console.log(`[gate:ci] vitest: ${testScope.mode} — ${testScope.reason} (base ${testScope.base})`)
|
||||
console.log(`[gate:ci] e2e: ${e2eScope.mode} — ${e2eScope.reason} (base ${e2eScope.base})`)
|
||||
if (e2eScope.unmapped?.length > 0) {
|
||||
console.error(`[gate:ci] e2e unmapped src/ paths:\n ${e2eScope.unmapped.join('\n ')}`)
|
||||
}
|
||||
|
||||
console.log('\n[gate:ci] ▶ preflight (teqo_test)')
|
||||
const dbOk = await diagnoseDatabaseTarget({
|
||||
label: 'test',
|
||||
databaseUrl: dbEnv.DATABASE_URL,
|
||||
})
|
||||
if (!dbOk) {
|
||||
console.error('\n[gate:ci] ✗ preflight (teqo_test) failed — run `pnpm db:start`')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
run('lint', 'pnpm', ['lint'])
|
||||
run('format:check', 'pnpm', ['format:check'])
|
||||
run('typecheck', 'pnpm', ['typecheck'])
|
||||
run('knip', 'pnpm', ['exec', 'knip'])
|
||||
run('check:cycles', 'pnpm', ['check:cycles'])
|
||||
|
||||
if (testScope.mode === 'full') {
|
||||
run('test:unit (full)', 'pnpm', ['test:unit'])
|
||||
} else if (testScope.mode === 'changed') {
|
||||
run('test:unit (changed)', 'pnpm', [
|
||||
'test:unit',
|
||||
'--',
|
||||
'--changed',
|
||||
testScope.base,
|
||||
'--passWithNoTests',
|
||||
])
|
||||
} else {
|
||||
console.log('\n[gate:ci] ⊘ test:unit skipped (no src/tests blast radius)')
|
||||
}
|
||||
|
||||
if (testScope.mode !== 'none') {
|
||||
run('migrate', 'pnpm', ['migrate'], dbEnv)
|
||||
run('db:seed:minimal', 'pnpm', ['db:seed:minimal'], dbEnv)
|
||||
if (testScope.mode === 'full') {
|
||||
run('test:int (full)', 'pnpm', ['test:int'])
|
||||
} else {
|
||||
run('test:int (changed)', 'pnpm', [
|
||||
'test:int',
|
||||
'--',
|
||||
'--changed',
|
||||
testScope.base,
|
||||
'--passWithNoTests',
|
||||
])
|
||||
}
|
||||
} else {
|
||||
console.log('\n[gate:ci] ⊘ test:int skipped (no src/tests blast radius)')
|
||||
}
|
||||
|
||||
run('build', 'pnpm', ['build'], dbEnv)
|
||||
|
||||
if (e2eScope.mode === 'none') {
|
||||
console.log('\n[gate:ci] ⊘ e2e skipped (no e2e blast radius)')
|
||||
} else {
|
||||
run('playwright install chromium', 'pnpm', ['exec', 'playwright', 'install', 'chromium'])
|
||||
run('e2e migrate', 'pnpm', ['migrate'], dbEnv)
|
||||
run('e2e seed:minimal', 'pnpm', ['db:seed:minimal'], dbEnv)
|
||||
run('e2e build', 'pnpm', ['build'], { ...dbEnv, NEXT_DIST_DIR: '.next/e2e' })
|
||||
|
||||
const e2eEnv = { ...dbEnv, E2E_PROD: '1', CI: '1', NEXT_DIST_DIR: '.next/e2e' }
|
||||
if (e2eScope.mode === 'full') {
|
||||
run('test:e2e (full)', 'pnpm', ['test:e2e'], e2eEnv)
|
||||
} else {
|
||||
const specPaths = e2eScope.specs.map((name) => `tests/e2e/${name}.e2e.spec.ts`)
|
||||
run(
|
||||
`test:e2e (selected: ${e2eScope.specs.join(', ')})`,
|
||||
'pnpm',
|
||||
['test:e2e', '--', ...specPaths],
|
||||
e2eEnv,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n[gate:ci] ✓ all checks passed (ci-pr mirror, affected scope)')
|
||||
}
|
||||
|
||||
await main()
|
||||
18
scripts/vercel-ignore-build.sh
Executable file
18
scripts/vercel-ignore-build.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
# Vercel "Ignored Build Step": exit 0 = skip deployment, exit 1 = build.
|
||||
# Agent feature branches must not queue preview builds (Hobby = 1 concurrent build).
|
||||
# stage/main always build (stage preview + prod).
|
||||
set -euo pipefail
|
||||
|
||||
ref="${VERCEL_GIT_COMMIT_REF:-}"
|
||||
|
||||
if [ "$ref" = "main" ] || [ "$ref" = "stage" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$ref" == agent/* ]] || [[ "$ref" == cursor/* ]]; then
|
||||
echo "skip: agent/cursor branch ($ref) — merge gate is GitHub Actions, not Vercel"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"regions": ["gru1"]
|
||||
"regions": ["gru1"],
|
||||
"ignoreCommand": "bash scripts/vercel-ignore-build.sh"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue