openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
166 lines (165 loc) • 7.43 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as isOpenClawCorrectionSemver, t as compareOpenClawSemver } from "./semver-aYpwYdrQ.js";
import { parse, prerelease, valid } from "semver";
//#region src/infra/npm-registry-spec.ts
const OPENCLAW_RELEASE_PREFIX_RE = /^\d{4}\./;
const DIST_TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
/**
* npm ≤11 prints `npm view`/`npm pack` `--json` results as a bare entry object
* or an entry array; npm 12 wraps view results in a singleton array and keys
* pack results by package name. Normalize both shapes to the entry list, or
* metadata reads mistake the wrapper for an entry and fail closed with
* incomplete-metadata errors.
*/
function resolveNpmJsonEntries(value) {
if (Array.isArray(value)) return value;
if (value && typeof value === "object") {
const record = value;
if (!(typeof record.id === "string" || typeof record.name === "string" || typeof record.version === "string" || typeof record.filename === "string")) {
const entries = Object.values(record).filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry));
if (entries.length > 0) return entries;
}
}
return [value];
}
function parseRegistryNpmSpecInternal(rawSpec) {
const spec = rawSpec.trim();
if (!spec) return {
ok: false,
error: "missing npm spec"
};
if (/\s/.test(spec)) return {
ok: false,
error: "unsupported npm spec: whitespace is not allowed"
};
if (spec.includes("://")) return {
ok: false,
error: "unsupported npm spec: URLs are not allowed"
};
if (spec.includes("#")) return {
ok: false,
error: "unsupported npm spec: git refs are not allowed"
};
if (spec.includes(":")) return {
ok: false,
error: "unsupported npm spec: protocol specs are not allowed"
};
const at = spec.lastIndexOf("@");
const hasSelector = at > 0;
const name = hasSelector ? spec.slice(0, at) : spec;
const selector = hasSelector ? spec.slice(at + 1) : "";
if (!(name.startsWith("@") ? /^@[a-z0-9][a-z0-9-._~]*\/[a-z0-9][a-z0-9-._~]*$/.test(name) : /^[a-z0-9][a-z0-9-._~]*$/.test(name))) return {
ok: false,
error: "unsupported npm spec: expected <name> or <name>@<version> from the npm registry"
};
if (!hasSelector) return {
ok: true,
parsed: {
name,
raw: spec,
selectorKind: "none",
selectorIsPrerelease: false
}
};
if (!selector) return {
ok: false,
error: "unsupported npm spec: missing version/tag after @"
};
if (/[\\/]/.test(selector)) return {
ok: false,
error: "unsupported npm spec: invalid version/tag"
};
const exactVersion = valid(selector);
if (exactVersion) return {
ok: true,
parsed: {
name,
raw: spec,
selector,
selectorKind: "exact-version",
selectorIsPrerelease: prerelease(exactVersion) !== null && !isOpenClawStableCorrectionVersion(selector)
}
};
if (!DIST_TAG_RE.test(selector)) return {
ok: false,
error: "unsupported npm spec: use an exact version or dist-tag (ranges are not allowed)"
};
return {
ok: true,
parsed: {
name,
raw: spec,
selector,
selectorKind: "tag",
selectorIsPrerelease: false
}
};
}
/** Parses a registry-only npm package spec into package name and optional selector metadata. */
function parseRegistryNpmSpec(rawSpec) {
const parsed = parseRegistryNpmSpecInternal(rawSpec);
return parsed.ok ? parsed.parsed : null;
}
/** Validates a registry-only npm spec and returns a user-facing error when rejected. */
function validateRegistryNpmSpec(rawSpec) {
const parsed = parseRegistryNpmSpecInternal(rawSpec);
return parsed.ok ? null : parsed.error;
}
/** Returns whether a value is an exact semver selector, with optional leading `v`. */
function isExactSemverVersion(value) {
return valid(value.trim()) !== null;
}
/** Parses OpenClaw's monthly patch stable/alpha/beta/correction version format. */
function parseOpenClawReleaseVersion(value) {
const trimmed = value.trim();
const parsed = OPENCLAW_RELEASE_PREFIX_RE.test(trimmed) ? parse(trimmed) : null;
if (!parsed || parsed.build.length > 0) return null;
if (parsed.minor < 1 || parsed.minor > 12 || parsed.patch < 1) return null;
const [label, sequence] = parsed.prerelease;
const isStable = parsed.prerelease.length === 0;
const isCorrection = isOpenClawCorrectionSemver(parsed) && typeof label === "number" && label > 0;
const isAlpha = parsed.prerelease.length === 2 && label === "alpha" && typeof sequence === "number" && sequence > 0;
const isBeta = parsed.prerelease.length === 2 && label === "beta" && typeof sequence === "number" && sequence > 0;
if (!isStable && !isCorrection && !isAlpha && !isBeta) return null;
return parsed;
}
/** Returns whether a version is an OpenClaw monthly patch stable correction release. */
function isOpenClawStableCorrectionVersion(value) {
const parsed = parseOpenClawReleaseVersion(value);
return parsed !== null && isOpenClawCorrectionSemver(parsed);
}
/** Resolves stable correction releases to their shared base release cohort. */
function resolveOpenClawReleaseCohortVersion(value) {
const trimmed = value.trim();
const parsed = parseOpenClawReleaseVersion(trimmed);
return parsed && isOpenClawCorrectionSemver(parsed) ? `${parsed.major}.${parsed.minor}.${parsed.patch}` : trimmed;
}
/** Compares OpenClaw monthly patch release versions across alpha, beta, stable, and corrections. */
function compareOpenClawReleaseVersions(left, right) {
const parsedLeft = parseOpenClawReleaseVersion(left);
const parsedRight = parseOpenClawReleaseVersion(right);
return parsedLeft && parsedRight ? compareOpenClawSemver(parsedLeft, parsedRight) : null;
}
/** Returns whether an exact semver value is a prerelease, excluding stable correction releases. */
function isPrereleaseSemverVersion(value) {
const trimmed = value.trim();
return prerelease(trimmed) !== null && !isOpenClawStableCorrectionVersion(trimmed);
}
/**
* Enforces explicit opt-in before an npm spec may resolve to a prerelease.
* Bare specs and `latest` stay on stable releases unless the resolved version
* is an OpenClaw stable correction.
*/
function isPrereleaseResolutionAllowed(params) {
if (!params.resolvedVersion || !isPrereleaseSemverVersion(params.resolvedVersion)) return true;
if (params.spec.selectorKind === "none") return false;
if (params.spec.selectorKind === "exact-version") return params.spec.selectorIsPrerelease;
return normalizeLowercaseStringOrEmpty(params.spec.selector) !== "latest";
}
/** Formats the install error shown when a registry spec resolves to a disallowed prerelease. */
function formatPrereleaseResolutionError(params) {
const selectorHint = params.spec.selectorKind === "none" || normalizeLowercaseStringOrEmpty(params.spec.selector) === "latest" ? `Use "${params.spec.name}@beta" (or another prerelease tag) or an exact prerelease version to opt in explicitly.` : `Use an explicit prerelease tag or exact prerelease version if you want prerelease installs.`;
return `Resolved ${params.spec.raw} to prerelease version ${params.resolvedVersion}, but prereleases are only installed when explicitly requested. ${selectorHint}`;
}
//#endregion
export { isPrereleaseSemverVersion as a, resolveOpenClawReleaseCohortVersion as c, isPrereleaseResolutionAllowed as i, validateRegistryNpmSpec as l, formatPrereleaseResolutionError as n, parseRegistryNpmSpec as o, isExactSemverVersion as r, resolveNpmJsonEntries as s, compareOpenClawReleaseVersions as t };