mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
feat(ci): sunset legacy dashboard image publishing
This commit is contained in:
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
function parseStableVersion(value) {
|
||||
const match = String(value || '').match(/^v?([0-9]+)\.([0-9]+)\.([0-9]+)$/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
major: Number(match[1]),
|
||||
minor: Number(match[2]),
|
||||
patch: Number(match[3]),
|
||||
raw: `v${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`,
|
||||
};
|
||||
}
|
||||
|
||||
function compareVersions(left, right) {
|
||||
for (const key of ['major', 'minor', 'patch']) {
|
||||
if (left[key] !== right[key]) {
|
||||
return left[key] - right[key];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function stableVersionKey(version) {
|
||||
return `${version.major}.${version.minor}.${version.patch}`;
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const arg = argv[i];
|
||||
if (!arg.startsWith('--')) {
|
||||
throw new Error(`Unexpected argument: ${arg}`);
|
||||
}
|
||||
const key = arg.slice(2);
|
||||
if (key === 'tags-stdin') {
|
||||
args.tagsStdin = true;
|
||||
continue;
|
||||
}
|
||||
const value = argv[i + 1];
|
||||
if (!value || value.startsWith('--')) {
|
||||
throw new Error(`Missing value for --${key}`);
|
||||
}
|
||||
args[key] = value;
|
||||
i += 1;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function readStableTagsFromGit() {
|
||||
const output = execFileSync('git', ['tag', '--list', 'v*'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
return output.split(/\r?\n/).filter(Boolean);
|
||||
}
|
||||
|
||||
function parseStableTags(tags) {
|
||||
const byKey = new Map();
|
||||
for (const tag of tags) {
|
||||
const version = parseStableVersion(tag.trim());
|
||||
if (version) {
|
||||
byKey.set(stableVersionKey(version), version);
|
||||
}
|
||||
}
|
||||
return Array.from(byKey.values()).sort(compareVersions);
|
||||
}
|
||||
|
||||
function evaluateDashboardSunset({ targetTag, baselineVersion, releaseWindow, stableTags }) {
|
||||
const target = parseStableVersion(targetTag);
|
||||
if (!target) {
|
||||
throw new Error(`Target tag must be stable semver like v1.2.3, got: ${targetTag}`);
|
||||
}
|
||||
|
||||
const baseline = parseStableVersion(baselineVersion);
|
||||
if (!baseline) {
|
||||
throw new Error(`DEPRECATION_BASELINE_VERSION must be stable semver, got: ${baselineVersion}`);
|
||||
}
|
||||
|
||||
if (!Number.isInteger(releaseWindow) || releaseWindow < 1) {
|
||||
throw new Error(`STABLE_RELEASE_WINDOW must be a positive integer, got: ${releaseWindow}`);
|
||||
}
|
||||
|
||||
if (compareVersions(target, baseline) < 0) {
|
||||
return {
|
||||
publish: true,
|
||||
elapsed: 0,
|
||||
reason: `${target.raw} is before dashboard deprecation baseline ${baseline.raw}`,
|
||||
};
|
||||
}
|
||||
|
||||
const versions = parseStableTags(stableTags);
|
||||
const hasBaseline = versions.some((version) => compareVersions(version, baseline) === 0);
|
||||
if (compareVersions(target, baseline) > 0 && !hasBaseline) {
|
||||
throw new Error(
|
||||
`Cannot count dashboard sunset releases: baseline tag ${baseline.raw} is missing from git tags`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!versions.some((version) => compareVersions(version, target) === 0)) {
|
||||
versions.push(target);
|
||||
versions.sort(compareVersions);
|
||||
}
|
||||
|
||||
const elapsed = versions.filter(
|
||||
(version) => compareVersions(version, baseline) > 0 && compareVersions(version, target) <= 0,
|
||||
).length;
|
||||
const publish = elapsed < releaseWindow;
|
||||
const reason = publish
|
||||
? `legacy dashboard publish is still inside sunset window (${elapsed}/${releaseWindow} stable releases elapsed since ${baseline.raw})`
|
||||
: `legacy dashboard sunset reached (${elapsed}/${releaseWindow} stable releases elapsed since ${baseline.raw})`;
|
||||
|
||||
return { publish, elapsed, reason };
|
||||
}
|
||||
|
||||
function appendGithubOutput(values) {
|
||||
if (!process.env.GITHUB_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
const lines = Object.entries(values).map(([key, value]) => `${key}=${String(value)}`);
|
||||
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${lines.join('\n')}\n`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const targetTag = args.target || process.env.TARGET_TAG;
|
||||
const baselineVersion = args.baseline || process.env.DEPRECATION_BASELINE_VERSION;
|
||||
const windowValue = args.window || process.env.STABLE_RELEASE_WINDOW || '2';
|
||||
const releaseWindow = Number(windowValue);
|
||||
const stableTags = args.tagsStdin
|
||||
? fs.readFileSync(0, 'utf8').split(/\r?\n/).filter(Boolean)
|
||||
: readStableTagsFromGit();
|
||||
|
||||
const result = evaluateDashboardSunset({
|
||||
targetTag,
|
||||
baselineVersion,
|
||||
releaseWindow,
|
||||
stableTags,
|
||||
});
|
||||
|
||||
appendGithubOutput({
|
||||
publish: result.publish ? 'true' : 'false',
|
||||
elapsed: result.elapsed,
|
||||
reason: result.reason,
|
||||
});
|
||||
|
||||
const status = result.publish ? '[OK]' : '[i]';
|
||||
console.log(`${status} ${result.reason}`);
|
||||
if (!result.publish) {
|
||||
console.log('[i] Skipping ghcr.io/kaitranntt/ccs-dashboard publish; use ghcr.io/kaitranntt/ccs.');
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
console.error(`[X] ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
evaluateDashboardSunset,
|
||||
parseStableVersion,
|
||||
};
|
||||
Reference in New Issue
Block a user