sin-github-issues / src /workflows.ts
delqhi's picture
Deploy A2A-SIN-GitHub-Issues with a2a HTTP server fix
5a8fc31 verified
Raw
History Blame Contribute Delete
4.81 kB
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { runCommand, runJsonCommand } from './command.js';
const execFileAsync = promisify(execFile);
type WorkflowRunApi = {
id: number;
name?: string;
display_title?: string;
event?: string;
head_branch?: string;
head_sha?: string;
status?: string;
conclusion?: string | null;
html_url?: string;
created_at?: string;
updated_at?: string;
};
type WorkflowRunsResponse = {
workflow_runs?: WorkflowRunApi[];
};
type RunnerApi = {
id: number;
name: string;
os: string;
status: string;
busy: boolean;
labels?: Array<{ name: string }>;
};
type RunnersResponse = {
total_count?: number;
runners?: RunnerApi[];
};
const SUITE_COMMANDS: Record<string, string> = {
'docs-and-architecture': 'npm ci && npm run test:docs:root && npm run test:contracts:active && npm run test:dashboard:boundary && npm run test:guardrails && npm run test:cycles',
'python-unit-311': "python3.11 -m venv .venv311 && . .venv311/bin/activate && python -m pip install --upgrade pip && python -m pip install -r requirements.txt -r requirements.runtime.txt && python -m pip install -e '.[dev]' && pytest tests/unit -q",
'dashboard-quality': 'npm --prefix dashboard-enterprise ci && npm --prefix dashboard-enterprise run lint && npm --prefix dashboard-enterprise run type-check && npm run ops:dashboard:build:ci && npm run ops:dashboard:interaction-smoke:ci',
};
export async function listWorkflowRuns(input: {
repo: string;
branch?: string;
event?: string;
status?: string;
limit?: number;
}) {
const params = new URLSearchParams();
params.set('per_page', String(Math.max(1, Math.min(input.limit || 20, 100))));
if (input.branch) params.set('branch', input.branch);
if (input.event) params.set('event', input.event);
if (input.status) params.set('status', input.status);
const suffix = params.toString() ? `?${params.toString()}` : '';
const payload = await runJsonCommand<WorkflowRunsResponse>('gh', ['api', `repos/${input.repo}/actions/runs${suffix}`]);
return {
ok: true,
repo: input.repo,
runs: (payload.workflow_runs || []).map((row) => ({
id: row.id,
workflowName: row.name || null,
title: row.display_title || null,
event: row.event || null,
branch: row.head_branch || null,
sha: row.head_sha || null,
status: row.status || null,
conclusion: row.conclusion || null,
url: row.html_url || null,
createdAt: row.created_at || null,
updatedAt: row.updated_at || null,
})),
};
}
export async function rerunWorkflowRun(repo: string, runId: number) {
await runCommand('gh', ['run', 'rerun', String(runId), '--repo', repo]);
return { ok: true, repo, runId };
}
export async function cancelWorkflowRun(repo: string, runId: number) {
await runCommand('gh', ['run', 'cancel', String(runId), '--repo', repo]);
return { ok: true, repo, runId };
}
export async function listRunners(repo: string) {
const payload = await runJsonCommand<RunnersResponse>('gh', ['api', `repos/${repo}/actions/runners`]);
return {
ok: true,
repo,
totalCount: payload.total_count || 0,
runners: (payload.runners || []).map((row) => ({
id: row.id,
name: row.name,
os: row.os,
status: row.status,
busy: row.busy,
labels: (row.labels || []).map((label) => label.name),
})),
};
}
export async function runLocalSuite(input: {
repoPath: string;
suite: 'docs-and-architecture' | 'python-unit-311' | 'dashboard-quality';
timeoutMs?: number;
}) {
const command = SUITE_COMMANDS[input.suite];
if (!command) {
throw new Error(`unknown_ci_suite:${input.suite}`);
}
const startedAt = new Date().toISOString();
const timeout = Math.max(60_000, Math.min(input.timeoutMs || 900_000, 3_600_000));
try {
const result = await execFileAsync('/bin/bash', ['-lc', command], {
cwd: input.repoPath,
env: process.env,
maxBuffer: 1024 * 1024 * 16,
timeout,
});
return {
ok: true,
suite: input.suite,
startedAt,
finishedAt: new Date().toISOString(),
stdout: String(result.stdout || '').trim(),
stderr: String(result.stderr || '').trim(),
};
} catch (error) {
const failure = error as { stdout?: string; stderr?: string; message?: string; signal?: string; code?: number };
return {
ok: false,
suite: input.suite,
startedAt,
finishedAt: new Date().toISOString(),
stdout: String(failure.stdout || '').trim(),
stderr: String(failure.stderr || '').trim(),
signal: failure.signal || null,
code: typeof failure.code === 'number' ? failure.code : null,
error: String(failure.message || 'local_suite_failed'),
};
}
}