Files
pbakaus_impeccable/scripts/ci-test-plan.mjs
T
Paul BakausandAbdul Wahab 3df4c4b10d Simplify CI test plan routing
Collapse the nightly alternate plan into the shared event routing while preserving every GitHub output and schedule behavior. Strengthen the nightly characterization for all deterministic suites.\n\nAI assistance: prepared by OpenAI Codex under maintainer pbakaus's standing scheduled-refactor authorization.
2026-08-28 15:00:56 +05:00

98 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import { execFileSync } from 'node:child_process';
import { DEFAULT_SUITES, matchesSuiteTriggers } from './test-suites.mjs';
const eventName = process.env.GITHUB_EVENT_NAME || '';
const localNoChanges = !eventName && !process.env.CI_CHANGED_FILES;
// The nightly schedule exists for exactly one thing: the full live-e2e
// matrix. A schedule event has no diff base, so the change-detection path
// degenerates to "everything changed"; without this guard that would flip on
// every file-triggered opt-in suite, including the ones that bill LLM APIs
// (skill-behavior, accept-cleanup, deepseek), every single night.
const isSchedule = eventName === 'schedule';
const changedFiles = localNoChanges || isSchedule ? [] : getChangedFiles();
const forceDeterministic = localNoChanges || isSchedule || eventName === 'push' || eventName === 'workflow_dispatch';
const forceOptIn = eventName === 'workflow_dispatch';
const plan = {
core: true,
detector: forceDeterministic || matchesSuiteTriggers('detector', changedFiles),
live: forceDeterministic || matchesSuiteTriggers('live', changedFiles),
framework: forceDeterministic || matchesSuiteTriggers('framework', changedFiles),
cli_remote_e2e: forceOptIn,
live_e2e: isSchedule || forceOptIn || matchesSuiteTriggers('live-e2e', changedFiles),
live_e2e_accept_cleanup: forceOptIn || matchesSuiteTriggers('live-e2e-accept-cleanup', changedFiles),
skill_behavior: forceOptIn || matchesSuiteTriggers('skill-behavior', changedFiles),
live_svelte_adapter_deepseek: forceOptIn || matchesSuiteTriggers('live-svelte-adapter-deepseek', changedFiles),
};
writeGithubOutputs(plan);
printSummary(plan, changedFiles);
function getChangedFiles() {
if (process.env.CI_CHANGED_FILES) {
return process.env.CI_CHANGED_FILES
.split(/\r?\n/)
.map((file) => file.trim())
.filter(Boolean);
}
const event = process.env.GITHUB_EVENT_NAME || '';
const sha = process.env.GITHUB_SHA || 'HEAD';
if (event === 'pull_request' && process.env.GITHUB_BASE_REF) {
const base = `origin/${process.env.GITHUB_BASE_REF}`;
return gitDiffNames(`${base}...${sha}`) || gitDiffNames(`${base}...HEAD`) || allChanged();
}
const before = process.env.GITHUB_EVENT_BEFORE;
if (before && !/^0+$/.test(before)) {
return gitDiffNames(`${before}..${sha}`) || allChanged();
}
return allChanged();
}
function allChanged() {
return git(['ls-files']).split(/\r?\n/).filter(Boolean);
}
function gitDiffNames(range) {
try {
return git(['diff', '--name-only', range]).split(/\r?\n/).filter(Boolean);
} catch {
return null;
}
}
function git(args) {
return execFileSync('git', args, { encoding: 'utf-8' });
}
function writeGithubOutputs(outputs) {
const outputPath = process.env.GITHUB_OUTPUT;
if (!outputPath) return;
const lines = [];
for (const [key, value] of Object.entries(outputs)) {
lines.push(`${key}=${value ? 'true' : 'false'}`);
}
fs.appendFileSync(outputPath, lines.join('\n') + '\n');
}
function printSummary(outputs, files) {
const deterministic = DEFAULT_SUITES.map((name) => `${name}=${outputs[name]}`).join(' ');
console.log(`Event: ${eventName || 'local'}`);
console.log(`Changed files: ${files.length}`);
console.log(`Deterministic suites: ${deterministic}`);
console.log(
[
`cli_remote_e2e=${outputs.cli_remote_e2e}`,
`live_e2e=${outputs.live_e2e}`,
`live_e2e_accept_cleanup=${outputs.live_e2e_accept_cleanup}`,
`skill_behavior=${outputs.skill_behavior}`,
`deepseek=${outputs.live_svelte_adapter_deepseek}`,
].join(' '),
);
}