* 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.
57 lines
1.9 KiB
JavaScript
57 lines
1.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Idempotent bootstrap for worktrees/clones before hooks or gate scripts run.
|
|
* Installs deps when `node_modules` is missing/stale and wires Husky hooks.
|
|
*
|
|
* Used by `.husky/pre-push` and `pnpm push` — no imports from `scripts/lib/`
|
|
* (those need `node_modules` first).
|
|
*/
|
|
import { execFileSync, spawnSync } from 'node:child_process'
|
|
import { existsSync, statSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
|
|
const runOrDie = (label, command, args) => {
|
|
console.log(`[ensure-deps] ${label}`)
|
|
const result = spawnSync(command, args, { cwd: repoRoot, stdio: 'inherit' })
|
|
if ((result.status ?? 1) !== 0) {
|
|
console.error(`[ensure-deps] failed: ${command} ${args.join(' ')}`)
|
|
process.exit(result.status ?? 1)
|
|
}
|
|
}
|
|
|
|
const needsInstall = () => {
|
|
const nodeModules = path.join(repoRoot, 'node_modules')
|
|
const lockfile = path.join(repoRoot, 'pnpm-lock.yaml')
|
|
if (!existsSync(nodeModules) || !existsSync(path.join(nodeModules, '.pnpm'))) return true
|
|
if (!existsSync(path.join(nodeModules, '.bin', 'prettier'))) return true
|
|
if (!existsSync(lockfile)) return false
|
|
return statSync(lockfile).mtimeMs > statSync(nodeModules).mtimeMs
|
|
}
|
|
|
|
const huskyHooksMissing = () => {
|
|
try {
|
|
const hooksPath = execFileSync('git', ['config', 'core.hooksPath'], {
|
|
cwd: repoRoot,
|
|
encoding: 'utf8',
|
|
}).trim()
|
|
return !hooksPath.includes('husky')
|
|
} catch {
|
|
return true
|
|
}
|
|
}
|
|
|
|
export const ensureRepoDeps = () => {
|
|
if (needsInstall()) {
|
|
runOrDie('pnpm install --frozen-lockfile', 'pnpm', ['install', '--frozen-lockfile'])
|
|
}
|
|
if (huskyHooksMissing()) {
|
|
runOrDie('pnpm run prepare (husky)', 'pnpm', ['run', 'prepare'])
|
|
}
|
|
}
|
|
|
|
if (process.argv[1]?.endsWith('ensure-repo-deps.mjs')) {
|
|
ensureRepoDeps()
|
|
}
|