mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
v1.59.0-beta.8: audit follow-ups and inner-corner sun rays
This commit is contained in:
@@ -14,13 +14,13 @@ const count = (dir, match, re) =>
|
||||
|
||||
const files = (dir, match) => readdirSync(dir).filter((f) => match.test(f)).length;
|
||||
|
||||
// AUD-159B6-06: counting only `test(` under-reported by every suite written
|
||||
// with describe/it — the inventory claimed 339 while `npm test` ran 352. Both
|
||||
// spellings are the same layer, so both are counted (indented `it(` included).
|
||||
// Count both node:test spellings and indented pytest methods. Pure backend
|
||||
// includes validation plus trail helper/recorder tests; only `test_ha_*` needs
|
||||
// the Home Assistant harness (AUD-159B7-03).
|
||||
const rows = [
|
||||
['frontend unit (node:test)', count('test', /\.test\.mjs$/, /^\s*(test|it)\(/gm)],
|
||||
['pure backend (pytest, no HA)', count('tests_backend', /^test_validation\.py$/, /^def test_/gm)],
|
||||
['HA-harness backend (CI, py3.13)', count('tests_backend', /^test_ha_.*\.py$/, /^async def test_|^def test_/gm)],
|
||||
['Node unit (frontend + tooling)', count('test', /\.test\.mjs$/, /^\s*(test|it)\(/gm)],
|
||||
['pure backend (pytest, no HA)', count('tests_backend', /^test_(?!ha_).*\.py$/, /^\s*(?:async\s+)?def test_/gm)],
|
||||
['HA-harness backend (CI, py3.13)', count('tests_backend', /^test_ha_.*\.py$/, /^\s*(?:async\s+)?def test_/gm)],
|
||||
['browser smokes (headless chromium)', files('demo', /^smoke_.*\.mjs$/)],
|
||||
];
|
||||
const w = Math.max(...rows.map(([n]) => n.length));
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Exact-SHA Validate gate used by .github/workflows/release.yml.
|
||||
// The policy is exported so failure/cancel/skip scenarios stay unit-tested.
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export function classifyValidateRuns(runs) {
|
||||
if (!Array.isArray(runs) || runs.length === 0) return 'wait';
|
||||
if (runs.some((run) => run?.status === 'completed' && run?.conclusion !== 'success')) return 'fail';
|
||||
if (runs.some((run) => run?.status !== 'completed')) return 'wait';
|
||||
return 'success';
|
||||
}
|
||||
|
||||
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
||||
|
||||
export async function waitForGreenValidate({ repo, sha, token, timeoutMs = 60 * 60 * 1000 }) {
|
||||
if (!repo || !sha || !token) throw new Error('repo, sha and token are required');
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
const url = `https://api.github.com/repos/${repo}/actions/workflows/validate.yml/runs?head_sha=${encodeURIComponent(sha)}&per_page=100`;
|
||||
while (true) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
'User-Agent': 'houseplan-release-gate',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
});
|
||||
if (!response.ok) throw new Error(`GitHub Actions API ${response.status}: ${await response.text()}`);
|
||||
const body = await response.json();
|
||||
const runs = Array.isArray(body?.workflow_runs) ? body.workflow_runs : [];
|
||||
const state = classifyValidateRuns(runs);
|
||||
if (state === 'fail') {
|
||||
const failed = runs.filter((run) => run?.status === 'completed' && run?.conclusion !== 'success');
|
||||
throw new Error(`Validate is not green for ${sha}: ${JSON.stringify(failed.map((run) => ({
|
||||
conclusion: run.conclusion, url: run.html_url,
|
||||
})))}`);
|
||||
}
|
||||
if (state === 'success') {
|
||||
console.log(`Validate is green for ${sha} (${runs.length} run(s))`);
|
||||
return;
|
||||
}
|
||||
if (Date.now() >= deadline) throw new Error(`No completed green Validate for ${sha} within the deadline`);
|
||||
const running = runs.filter((run) => run?.status !== 'completed').length;
|
||||
console.log(runs.length ? `waiting: ${running} Validate run(s) still going` : `waiting: no Validate run for ${sha} yet`);
|
||||
await sleep(30_000);
|
||||
}
|
||||
}
|
||||
|
||||
const invokedDirectly = process.argv[1]
|
||||
&& resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
|
||||
if (invokedDirectly) {
|
||||
const sha = process.argv[2];
|
||||
waitForGreenValidate({
|
||||
repo: process.env.REPO || process.env.GITHUB_REPOSITORY,
|
||||
sha,
|
||||
token: process.env.GH_TOKEN || process.env.GITHUB_TOKEN,
|
||||
}).catch((err) => {
|
||||
console.error(`::error::${err instanceof Error ? err.message : String(err)}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user