mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-28 19:47:25 +00:00
112 lines
4.4 KiB
JavaScript
112 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import path from 'node:path';
|
|
import { versionFromTag } from './release-artifact-checks.mjs';
|
|
|
|
export const REQUIRED_RELEASE_CHECKS = Object.freeze(['verify', 'node20', 'e2e']);
|
|
|
|
export function parseCheckRuns(text) {
|
|
return String(text).split(/\r?\n/u).filter(Boolean).map(line => {
|
|
const [name, conclusion, url = ''] = line.split('\t');
|
|
if (!name || !conclusion) throw new Error(`Invalid check-run record: ${line}`);
|
|
return { name, conclusion, url };
|
|
});
|
|
}
|
|
|
|
export function validateRequiredChecks(checkRuns, required = REQUIRED_RELEASE_CHECKS) {
|
|
for (const name of required) {
|
|
const matches = checkRuns.filter(check => check.name === name);
|
|
if (matches.length === 0) throw new Error(`Required check is missing for the release commit: ${name}`);
|
|
if (matches.some(check => check.conclusion !== 'success')) {
|
|
const conclusions = matches.map(check => check.conclusion).join(', ');
|
|
throw new Error(`Required check ${name} did not succeed: ${conclusions}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function run(command, args) {
|
|
return execFileSync(command, args, {
|
|
cwd: process.cwd(),
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
}).trim();
|
|
}
|
|
|
|
export function validateRepositoryName(repo) {
|
|
if (!/^[^/\s]+\/[^/\s]+$/u.test(repo || '')) {
|
|
throw new Error(`Invalid GitHub repository: ${repo || '<empty>'}`);
|
|
}
|
|
return repo;
|
|
}
|
|
|
|
export function validateVersionSnapshot(expectedVersion, snapshot) {
|
|
for (const [label, actualVersion] of Object.entries(snapshot)) {
|
|
if (actualVersion !== expectedVersion) {
|
|
throw new Error(`${label} version ${actualVersion || '<missing>'} does not match tag version ${expectedVersion}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function validateReleaseSourceVersion(expectedVersion, repoRoot = process.cwd()) {
|
|
const readJson = relativePath => JSON.parse(fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'));
|
|
const packageJson = readJson('package.json');
|
|
const packageLock = readJson('package-lock.json');
|
|
const manifest = readJson('extension/manifest.base.json');
|
|
const websiteVersion = readJson('website/version.json');
|
|
const constants = fs.readFileSync(path.join(repoRoot, 'shared/constants.js'), 'utf8');
|
|
const appVersion = /export const APP_VERSION = ["']([^"']+)["']/u.exec(constants)?.[1] || '';
|
|
validateVersionSnapshot(expectedVersion, {
|
|
'package.json': packageJson.version,
|
|
'package-lock.json': packageLock.version,
|
|
'package-lock root package': packageLock.packages?.['']?.version,
|
|
'extension manifest': manifest.version,
|
|
'shared constants': appVersion,
|
|
'website/version.json': websiteVersion.version
|
|
});
|
|
}
|
|
|
|
export function verifyReleaseRef({ tag, repo }) {
|
|
const version = versionFromTag(tag);
|
|
validateRepositoryName(repo);
|
|
validateReleaseSourceVersion(version);
|
|
const tagRef = `refs/tags/${tag}`;
|
|
if (run('git', ['cat-file', '-t', tagRef]) !== 'tag') {
|
|
throw new Error(`${tag} must be an annotated tag`);
|
|
}
|
|
const tagCommit = run('git', ['rev-list', '-n', '1', tagRef]);
|
|
const mainCommit = run('git', ['rev-parse', 'origin/main']);
|
|
if (tagCommit !== mainCommit) {
|
|
throw new Error(`Release tag ${tag} points to ${tagCommit}, but origin/main is ${mainCommit}`);
|
|
}
|
|
|
|
const checks = parseCheckRuns(run('gh', [
|
|
'api', `repos/${repo}/commits/${tagCommit}/check-runs`,
|
|
'--jq', '.check_runs[] | [.name, .conclusion, .html_url] | @tsv'
|
|
]));
|
|
validateRequiredChecks(checks);
|
|
return { version, tagCommit };
|
|
}
|
|
|
|
function main() {
|
|
const tag = process.env.GITHUB_REF_NAME || '';
|
|
const repo = process.env.GITHUB_REPOSITORY || '';
|
|
const outputPath = process.env.GITHUB_OUTPUT || '';
|
|
const result = verifyReleaseRef({ tag, repo });
|
|
if (!outputPath) throw new Error('GITHUB_OUTPUT is required');
|
|
fs.appendFileSync(outputPath, `version=${result.version}\ntag_commit=${result.tagCommit}\n`, 'utf8');
|
|
console.log(`Release preflight accepted ${tag} at ${result.tagCommit}`);
|
|
}
|
|
|
|
const isMainModule = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
if (isMainModule) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(`Release preflight failed: ${error.message}`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|