* feat(ops): auto-promote stage→main e merge em stage sem exigir head Substitui o promote manual por workflow disparado no ci-stage green, relaxa strict na proteção de stage (script configure:branch-protection) e documenta o novo paradigma em AGENT-OPS e work-issue. * style: format AGENT-OPS and configure-branch-protection for ci-pr * feat(ops): pnpm push com ensure-repo-deps determinístico Bootstrap install + Husky antes de todo push (pnpm push e pre-push). Documenta fluxo canônico em work-issue, agent-pr-workflow e AGENT-OPS. * fix(ops): pnpm push roda gate no script, sem depender do Husky Cloud environment.json chama prepare explicitamente após install. * test(int): isolate advisor portfolio search from shared cairu slug Parallel int specs were stomping municipality.advisors on the fixed cairu row; allocate per-test municipalities instead.
105 lines
3.2 KiB
JavaScript
105 lines
3.2 KiB
JavaScript
/**
|
|
* `pnpm agent:promote -- --i-am-human` — manual override: promote `stage` → `main`.
|
|
*
|
|
* Normal path: `promote-stage-to-main.yml` auto-merges when ci-stage is green.
|
|
* Use this only when the workflow failed or you need to promote out of band.
|
|
*
|
|
* Checks (in order):
|
|
* 1. `main` has not diverged from `stage` (hotfix direto em main exige antes
|
|
* merge/rebase de main em stage + CI stage green de novo).
|
|
* 2. Latest ci-stage run on `stage` is green.
|
|
* 3. Creates the PR stage→main if none is open, then merges (ci-stage is the
|
|
* pre-promote gate; ci.yml on main is the post-merge safety net).
|
|
*/
|
|
|
|
import { execFileSync } from 'node:child_process'
|
|
|
|
import { dieAgent, gh, ghJson, parseArgs } from './lib/agent-github.mjs'
|
|
|
|
const die = dieAgent('promote')
|
|
const { flags } = parseArgs(process.argv.slice(2), new Set())
|
|
|
|
if (flags['i-am-human'] !== true) {
|
|
die(
|
|
'Refused: promote override is human-only. Re-run as a human with:\n' +
|
|
' pnpm agent:promote -- --i-am-human\n' +
|
|
'Auto-promote runs via promote-stage-to-main.yml when ci-stage is green.',
|
|
)
|
|
}
|
|
|
|
const git = (args) => execFileSync('git', args, { encoding: 'utf8' }).trim()
|
|
git(['fetch', 'origin', 'main', 'stage'])
|
|
|
|
// 1. Divergence: main must be an ancestor of stage.
|
|
try {
|
|
git(['merge-base', '--is-ancestor', 'origin/main', 'origin/stage'])
|
|
} catch {
|
|
die(
|
|
'`main` divergiu de `stage` (hotfix direto em main?). Antes do promote:\n' +
|
|
' git checkout stage && git merge origin/main && git push origin stage\n' +
|
|
'…e espere o CI stage ficar verde de novo.',
|
|
)
|
|
}
|
|
|
|
// 2. CI stage green on the stage head.
|
|
const stageSha = git(['rev-parse', 'origin/stage'])
|
|
const stageRuns = ghJson([
|
|
'run',
|
|
'list',
|
|
'--branch',
|
|
'stage',
|
|
'--workflow',
|
|
'ci-stage.yml',
|
|
'--limit',
|
|
'5',
|
|
'--json',
|
|
'headSha,status,conclusion,url',
|
|
])
|
|
const latestStageRun = stageRuns.find((run) => run.headSha === stageSha)
|
|
if (
|
|
!latestStageRun ||
|
|
latestStageRun.status !== 'completed' ||
|
|
latestStageRun.conclusion !== 'success'
|
|
) {
|
|
die(
|
|
`CI stage não está verde no head de stage (${stageSha.slice(0, 8)}). ` +
|
|
`Último run: ${latestStageRun ? `${latestStageRun.status}/${latestStageRun.conclusion} — ${latestStageRun.url}` : 'nenhum'}`,
|
|
)
|
|
}
|
|
console.log('[agent:promote] CI stage green ✓')
|
|
|
|
// 3. PR stage → main (create if needed).
|
|
let pr = ghJson([
|
|
'pr',
|
|
'list',
|
|
'--state',
|
|
'open',
|
|
'--base',
|
|
'main',
|
|
'--head',
|
|
'stage',
|
|
'--json',
|
|
'number,url',
|
|
])[0]
|
|
if (!pr) {
|
|
const url = gh([
|
|
'pr',
|
|
'create',
|
|
'--base',
|
|
'main',
|
|
'--head',
|
|
'stage',
|
|
'--title',
|
|
`Promote stage → main (${new Date().toISOString().slice(0, 10)})`,
|
|
'--body',
|
|
'Override manual via `pnpm agent:promote --i-am-human`. CI stage green verificado antes do merge.',
|
|
])
|
|
pr = { number: url.split('/').pop(), url }
|
|
console.log(`[agent:promote] created PR ${pr.url}`)
|
|
} else {
|
|
console.log(`[agent:promote] reusing open PR ${pr.url}`)
|
|
}
|
|
|
|
// 4. Merge (ci-stage was the pre-promote gate; ci.yml on main is the post-merge safety net).
|
|
gh(['pr', 'merge', String(pr.number), '--admin', '--merge'])
|
|
console.log(`[agent:promote] merged stage → main via PR #${pr.number}. Deploy Vercel a caminho.`)
|