Files
adolf/scripts/lib/npm-publish-plan.mjs
alvis bedb527145
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
Vendor OpenClaw source as Adolf fork baseline
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
2026-07-05 09:36:54 +00:00

280 lines
7.6 KiB
JavaScript

// Parses OpenClaw monthly patch release versions and npm dist-tag publish plans.
const STABLE_VERSION_REGEX = /^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<patch>[1-9]\d*)$/;
const ALPHA_VERSION_REGEX =
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<patch>[1-9]\d*)-alpha\.(?<alpha>[1-9]\d*)$/;
const BETA_VERSION_REGEX =
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<patch>[1-9]\d*)-beta\.(?<beta>[1-9]\d*)$/;
const CORRECTION_VERSION_REGEX =
/^(?<year>\d{4})\.(?<month>[1-9]\d?)\.(?<patch>[1-9]\d*)-(?<correction>[1-9]\d*)$/;
export const JUNE_2026_PATCH_FLOOR = 5;
/**
* @typedef {object} ParsedReleaseVersion
* @property {string} version
* @property {string} baseVersion
* @property {"stable" | "alpha" | "beta"} channel
* @property {number} year
* @property {number} month
* @property {number} patch
* @property {number | undefined} [alphaNumber]
* @property {number | undefined} [betaNumber]
* @property {number | undefined} [correctionNumber]
*/
/**
* @typedef {object} NpmPublishPlan
* @property {"stable" | "alpha" | "beta"} channel
* @property {"latest" | "alpha" | "beta"} publishTag
* @property {("latest" | "alpha" | "beta")[]} mirrorDistTags
*/
/**
* @typedef {object} NpmDistTagMirrorAuth
* @property {boolean} hasAuth
* @property {"node-auth-token" | "npm-token" | "none"} source
*/
/**
* @typedef {"--dry-run" | "--publish"} NpmPublishMode
*/
/**
* @param {string} version
* @param {Record<string, string | undefined>} groups
* @param {"stable" | "alpha" | "beta"} channel
* @returns {ParsedReleaseVersion | null}
*/
function parseVersionParts(version, groups, channel) {
const year = parseSafeIntegerPart(groups.year);
const month = parseSafeIntegerPart(groups.month);
const patch = parseSafeIntegerPart(groups.patch);
const alphaNumber = channel === "alpha" ? parseSafeIntegerPart(groups.alpha) : undefined;
const betaNumber = channel === "beta" ? parseSafeIntegerPart(groups.beta) : undefined;
if (
!Number.isSafeInteger(year) ||
!Number.isSafeInteger(month) ||
!Number.isSafeInteger(patch) ||
month < 1 ||
month > 12 ||
patch < 1
) {
return null;
}
if (channel === "beta" && (!Number.isSafeInteger(betaNumber) || (betaNumber ?? 0) < 1)) {
return null;
}
if (channel === "alpha" && (!Number.isSafeInteger(alphaNumber) || (alphaNumber ?? 0) < 1)) {
return null;
}
return {
version,
baseVersion: `${year}.${month}.${patch}`,
channel,
year,
month,
patch,
alphaNumber,
betaNumber,
};
}
function parseSafeIntegerPart(value) {
const raw = value ?? "";
if (!/^[0-9]+$/.test(raw)) {
return null;
}
const parsed = Number(raw);
return Number.isSafeInteger(parsed) ? parsed : null;
}
/**
* @param {string} version
* @returns {ParsedReleaseVersion | null}
*/
export function parseReleaseVersion(version) {
const trimmed = version.trim();
if (!trimmed) {
return null;
}
const stableMatch = STABLE_VERSION_REGEX.exec(trimmed);
if (stableMatch?.groups) {
return parseVersionParts(trimmed, stableMatch.groups, "stable");
}
const alphaMatch = ALPHA_VERSION_REGEX.exec(trimmed);
if (alphaMatch?.groups) {
return parseVersionParts(trimmed, alphaMatch.groups, "alpha");
}
const betaMatch = BETA_VERSION_REGEX.exec(trimmed);
if (betaMatch?.groups) {
return parseVersionParts(trimmed, betaMatch.groups, "beta");
}
const correctionMatch = CORRECTION_VERSION_REGEX.exec(trimmed);
if (correctionMatch?.groups) {
const parsedCorrection = parseVersionParts(trimmed, correctionMatch.groups, "stable");
const correctionNumber = parseSafeIntegerPart(correctionMatch.groups.correction);
if (
parsedCorrection === null ||
!Number.isSafeInteger(correctionNumber) ||
correctionNumber < 1
) {
return null;
}
return {
...parsedCorrection,
correctionNumber,
};
}
return null;
}
/**
* @param {string | ParsedReleaseVersion | null} version
* @returns {string[]}
*/
export function collectReleaseVersionFloorErrors(version) {
const parsedVersion =
typeof version === "string" ? parseReleaseVersion(version) : (version ?? null);
if (parsedVersion === null) {
return [];
}
if (
parsedVersion.year === 2026 &&
parsedVersion.month === 6 &&
parsedVersion.patch < JUNE_2026_PATCH_FLOOR &&
parsedVersion.channel !== "alpha"
) {
return [
`June 2026 stable and beta release trains must use patch ${JUNE_2026_PATCH_FLOOR} or higher because 2026.6.5-beta.1 is already published; found "${parsedVersion.version}".`,
];
}
return [];
}
/**
* @param {string} left
* @param {string} right
* @returns {number | null}
*/
export function compareReleaseVersions(left, right) {
const parsedLeft = parseReleaseVersion(left);
const parsedRight = parseReleaseVersion(right);
if (parsedLeft === null || parsedRight === null) {
return null;
}
if (parsedLeft.year !== parsedRight.year) {
return Math.sign(parsedLeft.year - parsedRight.year);
}
if (parsedLeft.month !== parsedRight.month) {
return Math.sign(parsedLeft.month - parsedRight.month);
}
if (parsedLeft.patch !== parsedRight.patch) {
return Math.sign(parsedLeft.patch - parsedRight.patch);
}
if (parsedLeft.channel !== parsedRight.channel) {
const rank = { alpha: 0, beta: 1, stable: 2 };
return Math.sign(rank[parsedLeft.channel] - rank[parsedRight.channel]);
}
if (parsedLeft.channel === "alpha" && parsedRight.channel === "alpha") {
return Math.sign((parsedLeft.alphaNumber ?? 0) - (parsedRight.alphaNumber ?? 0));
}
if (parsedLeft.channel === "beta" && parsedRight.channel === "beta") {
return Math.sign((parsedLeft.betaNumber ?? 0) - (parsedRight.betaNumber ?? 0));
}
return Math.sign((parsedLeft.correctionNumber ?? 0) - (parsedRight.correctionNumber ?? 0));
}
/**
* @param {string} version
* @param {string | null} [currentBetaVersion]
* @returns {NpmPublishPlan}
*/
export function resolveNpmPublishPlan(version, currentBetaVersion) {
const parsedVersion = parseReleaseVersion(version);
if (parsedVersion === null) {
throw new Error(`Unsupported release version "${version}".`);
}
if (parsedVersion.channel === "beta") {
return {
channel: "beta",
publishTag: "beta",
mirrorDistTags: [],
};
}
if (parsedVersion.channel === "alpha") {
return {
channel: "alpha",
publishTag: "alpha",
mirrorDistTags: [],
};
}
const normalizedCurrentBeta = currentBetaVersion?.trim();
if (normalizedCurrentBeta) {
const betaVsStable = compareReleaseVersions(normalizedCurrentBeta, version);
if (betaVsStable !== null && betaVsStable > 0) {
return {
channel: "stable",
publishTag: "latest",
mirrorDistTags: [],
};
}
}
return {
channel: "stable",
publishTag: "latest",
mirrorDistTags: ["beta"],
};
}
/**
* @param {{
* nodeAuthToken?: string | null | undefined;
* npmToken?: string | null | undefined;
* }} [params]
* @returns {NpmDistTagMirrorAuth}
*/
export function resolveNpmDistTagMirrorAuth(params = {}) {
const nodeAuthToken = params.nodeAuthToken?.trim();
if (nodeAuthToken) {
return { hasAuth: true, source: "node-auth-token" };
}
const npmToken = params.npmToken?.trim();
if (npmToken) {
return { hasAuth: true, source: "npm-token" };
}
return { hasAuth: false, source: "none" };
}
/**
* @param {{
* mode: NpmPublishMode;
* mirrorDistTags: string[] | readonly string[];
* hasAuth: boolean;
* }} params
* @returns {boolean}
*/
export function shouldRequireNpmDistTagMirrorAuth(params) {
return (
params.mode === "--publish" &&
params.mirrorDistTags.some((distTag) => distTag.trim().length > 0) &&
!params.hasAuth
);
}