scripts/release-ci.mjs
Rama main · réplica de lectura del repositorio original.
import { appendFileSync, readFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { setTimeout as delay } from 'node:timers/promises';
import { validateAgentRelease } from '../app/src/lib/agent-release.mjs';
export function selectCiRun(runs, sha) {
return runs.filter(run => run.head_sha === sha &&
['push', 'workflow_dispatch'].includes(run.event))
.sort((a, b) => b.id - a.id)[0];
}
export function planAgentRelease({ applicationVersion, agentVersion, sourceChanged }) {
if (applicationVersion === agentVersion) return { build: true };
if (sourceChanged) throw new Error('Agent source changed: bump the app version and run pnpm agent:release:prepare before tagging.');
return { build: false };
}
export async function waitForCi({ request, repository, sha, ref, pause = delay }) {
let dispatched = false;
for (let attempt = 0; attempt < 120; attempt++) {
const result = await request(`/repos/${repository}/actions/workflows/ci.yml/runs?head_sha=${sha}&per_page=100`);
const run = selectCiRun(result.workflow_runs, sha);
if (!run && !dispatched) {
await request(`/repos/${repository}/actions/workflows/ci.yml/dispatches`, { ref });
dispatched = true;
}
if (run?.status === 'completed') {
if (run.conclusion !== 'success') throw new Error(`CI did not pass for ${sha}: ${run.html_url}`);
return run.id;
}
await pause(15000);
}
throw new Error('Timed out waiting for CI for the release commit.');
}
export async function githubRequest(path, body) {
const response = await fetch(`https://api.github.com${path}`, {
method: body ? 'POST' : 'GET',
signal: AbortSignal.timeout(30000),
headers: { Authorization: `Bearer ${process.env.GH_TOKEN}`, Accept: 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28', ...(body ? { 'Content-Type': 'application/json' } : {}) },
...(body ? { body: JSON.stringify(body) } : {}),
});
if (!response.ok) throw new Error(`GitHub request failed (${response.status}): ${path}`);
return response.status === 204 ? null : response.json();
}
async function main() {
const root = resolve(import.meta.dirname, '..');
const pkg = JSON.parse(readFileSync(resolve(root, 'app/package.json'), 'utf8'));
const pin = validateAgentRelease(JSON.parse(readFileSync(resolve(root, 'app/agent-release.json'), 'utf8')));
const ref = process.env.RELEASE_TAG ?? process.env.GITHUB_REF_NAME;
if (ref !== `v${pkg.version}`) throw new Error('Release tag must match the application package version.');
const sha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim();
let sourceChanged = false;
if (pkg.version !== pin.version) {
const previous = execFileSync('git', ['rev-parse', `refs/tags/v${pin.version}^{commit}`], { cwd: root, encoding: 'utf8' }).trim();
sourceChanged = Boolean(execFileSync('git', ['diff', '--name-only', previous, sha, '--', 'agent', 'go.work', 'go.work.sum', 'scripts/build-agent.mjs', 'scripts/agent-release.mjs'], { cwd: root, encoding: 'utf8' }).trim());
}
const plan = planAgentRelease({ applicationVersion: pkg.version, agentVersion: pin.version, sourceChanged });
const run = await waitForCi({ request: githubRequest, repository: process.env.GITHUB_REPOSITORY, sha, ref });
appendFileSync(process.env.GITHUB_OUTPUT, `ci_run_id=${run}\nbuild_agent=${plan.build}\n`);
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();