mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
release: mirror bundled channel deps at root (#63065)
Merged via squash.
Prepared head SHA: ac26799a54
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
lstatSync,
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
realpathSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { isAbsolute, join, relative } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "../src/infra/errors.ts";
|
||||
import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../src/plugins/runtime-sidecar-paths.ts";
|
||||
@@ -11,8 +19,27 @@ import { parseReleaseVersion, resolveNpmCommandInvocation } from "./openclaw-npm
|
||||
|
||||
type InstalledPackageJson = {
|
||||
version?: string;
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
type InstalledBundledExtensionPackageJson = {
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
releaseChecks?: {
|
||||
rootDependencyMirrorAllowlist?: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type InstalledBundledExtensionManifestRecord = {
|
||||
manifest: InstalledBundledExtensionPackageJson;
|
||||
path: string;
|
||||
};
|
||||
|
||||
const MAX_BUNDLED_EXTENSION_MANIFEST_BYTES = 1024 * 1024;
|
||||
|
||||
export type PublishedInstallScenario = {
|
||||
name: string;
|
||||
installSpecs: string[];
|
||||
@@ -64,6 +91,141 @@ export function collectInstalledPackageErrors(params: {
|
||||
}
|
||||
}
|
||||
|
||||
errors.push(...collectInstalledMirroredRootDependencyManifestErrors(params.packageRoot));
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function resolveInstalledBinaryPath(prefixDir: string, platform = process.platform): string {
|
||||
return platform === "win32"
|
||||
? join(prefixDir, "openclaw.cmd")
|
||||
: join(prefixDir, "bin", "openclaw");
|
||||
}
|
||||
|
||||
function collectRuntimeDependencySpecs(packageJson: InstalledPackageJson): Map<string, string> {
|
||||
return new Map([
|
||||
...Object.entries(packageJson.dependencies ?? {}),
|
||||
...Object.entries(packageJson.optionalDependencies ?? {}),
|
||||
]);
|
||||
}
|
||||
|
||||
function readBundledExtensionPackageJsons(packageRoot: string): {
|
||||
manifests: InstalledBundledExtensionManifestRecord[];
|
||||
errors: string[];
|
||||
} {
|
||||
const extensionsDir = join(packageRoot, "dist", "extensions");
|
||||
if (!existsSync(extensionsDir)) {
|
||||
return { manifests: [], errors: [] };
|
||||
}
|
||||
|
||||
const manifests: InstalledBundledExtensionManifestRecord[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const entry of readdirSync(extensionsDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionDirPath = join(extensionsDir, entry.name);
|
||||
const packageJsonPath = join(extensionsDir, entry.name, "package.json");
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
errors.push(`installed bundled extension manifest missing: ${packageJsonPath}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const packageJsonStats = lstatSync(packageJsonPath);
|
||||
if (!packageJsonStats.isFile()) {
|
||||
throw new Error("manifest must be a regular file");
|
||||
}
|
||||
if (packageJsonStats.size > MAX_BUNDLED_EXTENSION_MANIFEST_BYTES) {
|
||||
throw new Error(`manifest exceeds ${MAX_BUNDLED_EXTENSION_MANIFEST_BYTES} bytes`);
|
||||
}
|
||||
|
||||
const realExtensionDirPath = realpathSync(extensionDirPath);
|
||||
const realPackageJsonPath = realpathSync(packageJsonPath);
|
||||
const relativeManifestPath = relative(realExtensionDirPath, realPackageJsonPath);
|
||||
if (
|
||||
relativeManifestPath.length === 0 ||
|
||||
relativeManifestPath.startsWith("..") ||
|
||||
isAbsolute(relativeManifestPath)
|
||||
) {
|
||||
throw new Error("manifest resolves outside the bundled extension directory");
|
||||
}
|
||||
|
||||
manifests.push({
|
||||
manifest: JSON.parse(
|
||||
readFileSync(realPackageJsonPath, "utf8"),
|
||||
) as InstalledBundledExtensionPackageJson,
|
||||
path: realPackageJsonPath,
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
`installed bundled extension manifest invalid: failed to parse ${packageJsonPath}: ${formatErrorMessage(error)}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { manifests, errors };
|
||||
}
|
||||
|
||||
export function collectInstalledMirroredRootDependencyManifestErrors(
|
||||
packageRoot: string,
|
||||
): string[] {
|
||||
const packageJsonPath = join(packageRoot, "package.json");
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
return ["installed package is missing package.json."];
|
||||
}
|
||||
|
||||
const rootPackageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as InstalledPackageJson;
|
||||
const rootRuntimeDeps = collectRuntimeDependencySpecs(rootPackageJson);
|
||||
const { manifests, errors } = readBundledExtensionPackageJsons(packageRoot);
|
||||
|
||||
for (const { manifest: extensionPackageJson } of manifests) {
|
||||
const allowlist = extensionPackageJson.openclaw?.releaseChecks?.rootDependencyMirrorAllowlist;
|
||||
if (allowlist === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (!Array.isArray(allowlist)) {
|
||||
errors.push(
|
||||
"installed bundled extension manifest invalid: openclaw.releaseChecks.rootDependencyMirrorAllowlist must be an array.",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionRuntimeDeps = collectRuntimeDependencySpecs(extensionPackageJson);
|
||||
for (const entry of allowlist) {
|
||||
if (typeof entry !== "string" || entry.trim().length === 0) {
|
||||
errors.push(
|
||||
"installed bundled extension manifest invalid: openclaw.releaseChecks.rootDependencyMirrorAllowlist entries must be non-empty strings.",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const extensionSpec = extensionRuntimeDeps.get(entry);
|
||||
if (!extensionSpec) {
|
||||
errors.push(
|
||||
`installed bundled extension manifest invalid: mirrored dependency '${entry}' must be declared in the extension runtime dependencies.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const rootSpec = rootRuntimeDeps.get(entry);
|
||||
if (!rootSpec) {
|
||||
errors.push(
|
||||
`installed package is missing mirrored root runtime dependency '${entry}' required by a bundled extension.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rootSpec !== extensionSpec) {
|
||||
errors.push(
|
||||
`installed package mirrored dependency '${entry}' version mismatch: root '${rootSpec}', extension '${extensionSpec}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
@@ -89,6 +251,15 @@ function installSpec(prefixDir: string, spec: string, cwd: string): void {
|
||||
npmExec(["install", "-g", "--prefix", prefixDir, spec, "--no-fund", "--no-audit"], cwd);
|
||||
}
|
||||
|
||||
function readInstalledBinaryVersion(prefixDir: string, cwd: string): string {
|
||||
return execFileSync(resolveInstalledBinaryPath(prefixDir), ["--version"], {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function verifyScenario(version: string, scenario: PublishedInstallScenario): void {
|
||||
const workingDir = mkdtempSync(join(tmpdir(), `openclaw-postpublish-${scenario.name}.`));
|
||||
const prefixDir = join(workingDir, "prefix");
|
||||
@@ -108,6 +279,13 @@ function verifyScenario(version: string, scenario: PublishedInstallScenario): vo
|
||||
installedVersion: pkg.version?.trim() ?? "",
|
||||
packageRoot,
|
||||
});
|
||||
const installedBinaryVersion = readInstalledBinaryVersion(prefixDir, workingDir);
|
||||
|
||||
if (installedBinaryVersion !== scenario.expectedVersion) {
|
||||
errors.push(
|
||||
`installed openclaw binary version mismatch: expected ${scenario.expectedVersion}, found ${installedBinaryVersion || "<missing>"}.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`${scenario.name} failed:\n- ${errors.join("\n- ")}`);
|
||||
|
||||
Reference in New Issue
Block a user