Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
214 lines
7.4 KiB
JavaScript
214 lines
7.4 KiB
JavaScript
// Resolves Docker upgrade-survivor baseline specs from requested tokens and
|
|
// live release history JSON captured by release workflows.
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { normalizeUpgradeSurvivorBaselineSpec } from "./lib/docker-e2e-plan.mjs";
|
|
import { compareReleaseVersions, parseReleaseVersion } from "./lib/npm-publish-plan.mjs";
|
|
|
|
export function parseArgs(argv) {
|
|
const args = new Map();
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (!arg.startsWith("--")) {
|
|
throw new Error(`unexpected argument: ${arg}`);
|
|
}
|
|
const key = arg.slice(2);
|
|
const value = argv[index + 1];
|
|
if (value === undefined || value.startsWith("-")) {
|
|
throw new Error(`missing value for --${key}`);
|
|
}
|
|
args.set(key, value);
|
|
index += 1;
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function splitSpecs(raw) {
|
|
return String(raw ?? "")
|
|
.split(/[,\s]+/u)
|
|
.map((token) => token.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function dedupeSpecs(specs) {
|
|
return [...new Set(specs.map(normalizeUpgradeSurvivorBaselineSpec).filter(Boolean))];
|
|
}
|
|
|
|
function parsePositiveInteger(value, label) {
|
|
const text = String(value ?? "").trim();
|
|
if (!/^[1-9]\d*$/u.test(text)) {
|
|
throw new Error(`${label} must be a positive integer`);
|
|
}
|
|
const parsed = Number(text);
|
|
if (!Number.isSafeInteger(parsed)) {
|
|
throw new Error(`${label} must be a positive integer`);
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function readPublishedVersions(file) {
|
|
if (!file) {
|
|
return undefined;
|
|
}
|
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
if (!Array.isArray(parsed)) {
|
|
throw new Error(`npm versions list must be a JSON array: ${file}`);
|
|
}
|
|
return new Set(parsed.filter((version) => typeof version === "string"));
|
|
}
|
|
|
|
function stableVersionFromTag(tagName) {
|
|
const version = String(tagName ?? "").replace(/^v/u, "");
|
|
return parseStableVersion(version) ? version : undefined;
|
|
}
|
|
|
|
function parseStableVersion(version) {
|
|
const parsed = parseReleaseVersion(String(version ?? ""));
|
|
return parsed?.channel === "stable" ? parsed : undefined;
|
|
}
|
|
|
|
function compareStableVersions(left, right) {
|
|
if (!parseStableVersion(left) || !parseStableVersion(right)) {
|
|
throw new Error(`cannot compare release versions: ${left} ${right}`);
|
|
}
|
|
const comparison = compareReleaseVersions(left, right);
|
|
if (comparison === null) {
|
|
throw new Error(`cannot compare release versions: ${left} ${right}`);
|
|
}
|
|
return comparison;
|
|
}
|
|
|
|
function npmPublishedVersion(version, publishedVersions) {
|
|
if (!version || !publishedVersions) {
|
|
return version;
|
|
}
|
|
if (publishedVersions.has(version)) {
|
|
return version;
|
|
}
|
|
const baseVersion = version.replace(/-[0-9]+$/u, "");
|
|
return publishedVersions.has(baseVersion) ? baseVersion : undefined;
|
|
}
|
|
|
|
function readStableReleases(file, publishedVersions) {
|
|
const ansiEscape = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
const raw = readFileSync(file, "utf8").replace(ansiEscape, "");
|
|
const parsed = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) {
|
|
throw new Error(`release list must be a JSON array: ${file}`);
|
|
}
|
|
return parsed
|
|
.filter((release) => !release.isPrerelease)
|
|
.map((release) => ({
|
|
publishedAt: release.publishedAt,
|
|
version: npmPublishedVersion(stableVersionFromTag(release.tagName), publishedVersions),
|
|
}))
|
|
.filter((release) => release.version && release.publishedAt)
|
|
.toSorted((a, b) => String(b.publishedAt).localeCompare(String(a.publishedAt)));
|
|
}
|
|
|
|
/**
|
|
* Expands the release-history token into recent stable plus pinned historical baselines.
|
|
*/
|
|
export function resolveReleaseHistory(args) {
|
|
const releasesJson = args.get("releases-json");
|
|
if (!releasesJson) {
|
|
throw new Error("--releases-json is required when requested baselines include release-history");
|
|
}
|
|
const historyCount = parsePositiveInteger(args.get("history-count") ?? "6", "--history-count");
|
|
const includeVersion = args.get("include-version") ?? "2026.4.23";
|
|
const preDate = args.get("pre-date") ?? "2026-03-15T00:00:00Z";
|
|
const publishedVersions = readPublishedVersions(args.get("npm-versions-json"));
|
|
const releases = readStableReleases(releasesJson, publishedVersions);
|
|
const versions = releases.slice(0, historyCount).map((release) => release.version);
|
|
const exact = releases.find((release) => release.version === includeVersion);
|
|
if (exact) {
|
|
versions.push(exact.version);
|
|
}
|
|
const preDateRelease = releases.find(
|
|
(release) => new Date(release.publishedAt).getTime() < new Date(preDate).getTime(),
|
|
);
|
|
if (preDateRelease) {
|
|
versions.push(preDateRelease.version);
|
|
}
|
|
return dedupeSpecs(versions);
|
|
}
|
|
|
|
/**
|
|
* Resolves the last N stable release versions from release metadata.
|
|
*/
|
|
export function resolveLastStable(args, count) {
|
|
const releasesJson = args.get("releases-json");
|
|
if (!releasesJson) {
|
|
throw new Error("--releases-json is required when requested baselines include last-stable-*");
|
|
}
|
|
if (!Number.isInteger(count) || count < 1) {
|
|
throw new Error(`invalid last-stable baseline count: ${count}`);
|
|
}
|
|
const publishedVersions = readPublishedVersions(args.get("npm-versions-json"));
|
|
const releases = readStableReleases(releasesJson, publishedVersions);
|
|
return dedupeSpecs(releases.slice(0, count).map((release) => release.version));
|
|
}
|
|
|
|
/**
|
|
* Resolves all stable release versions at or after the requested minimum.
|
|
*/
|
|
export function resolveAllSince(args, minimumVersion) {
|
|
const releasesJson = args.get("releases-json");
|
|
if (!releasesJson) {
|
|
throw new Error("--releases-json is required when requested baselines include all-since-*");
|
|
}
|
|
const publishedVersions = readPublishedVersions(args.get("npm-versions-json"));
|
|
const releases = readStableReleases(releasesJson, publishedVersions);
|
|
return dedupeSpecs(
|
|
releases
|
|
.map((release) => release.version)
|
|
.filter((version) => compareStableVersions(version, minimumVersion) >= 0),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Expands requested baseline tokens into normalized package/version specs.
|
|
*/
|
|
export function resolveBaselines(args) {
|
|
const requested = args.get("requested") ?? "";
|
|
const fallback = args.get("fallback") ?? "openclaw@latest";
|
|
const requestedTokens = splitSpecs(requested);
|
|
if (requestedTokens.length === 0) {
|
|
return dedupeSpecs([fallback]);
|
|
}
|
|
const resolved = [];
|
|
for (const token of requestedTokens) {
|
|
if (token === "release-history") {
|
|
resolved.push(...resolveReleaseHistory(args));
|
|
} else if (token.startsWith("last-stable-")) {
|
|
const count = parsePositiveInteger(
|
|
token.slice("last-stable-".length),
|
|
"last-stable baseline count",
|
|
);
|
|
resolved.push(...resolveLastStable(args, count));
|
|
} else if (token.startsWith("all-since-")) {
|
|
const minimumVersion = token.slice("all-since-".length);
|
|
if (!parseStableVersion(minimumVersion)) {
|
|
throw new Error(`invalid all-since baseline token: ${token}`);
|
|
}
|
|
resolved.push(...resolveAllSince(args, minimumVersion));
|
|
} else {
|
|
resolved.push(token);
|
|
}
|
|
}
|
|
return dedupeSpecs(resolved);
|
|
}
|
|
|
|
const isMain = process.argv[1] ? fileURLToPath(import.meta.url) === process.argv[1] : false;
|
|
|
|
if (isMain) {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const baselines = resolveBaselines(args).join(" ");
|
|
process.stdout.write(`${baselines}\n`);
|
|
|
|
const githubOutput = args.get("github-output");
|
|
if (githubOutput) {
|
|
writeFileSync(githubOutput, `baselines=${baselines}\n`, { flag: "a" });
|
|
}
|
|
}
|