mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 20:46:57 +02:00
251 lines
8.0 KiB
JavaScript
251 lines
8.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import {
|
|
collectBuiltBundledPluginStagedRuntimeDependencyErrors,
|
|
collectBundledPluginRootRuntimeMirrorErrors,
|
|
collectBundledPluginRuntimeDependencySpecs,
|
|
collectRootDistBundledRuntimeMirrors,
|
|
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
|
|
import { parsePackageRootArg } from "./lib/package-root-args.mjs";
|
|
|
|
const { packageRoot } = parsePackageRootArg(
|
|
process.argv.slice(2),
|
|
"OPENCLAW_BUNDLED_RUNTIME_DEPS_ROOT",
|
|
);
|
|
const rootPackageJsonPath = path.join(packageRoot, "package.json");
|
|
const builtPluginsDir = path.join(packageRoot, "dist", "extensions");
|
|
|
|
assert.ok(fs.existsSync(rootPackageJsonPath), `package.json missing from ${packageRoot}`);
|
|
assert.ok(fs.existsSync(builtPluginsDir), `built bundled plugins missing from ${builtPluginsDir}`);
|
|
|
|
const rootPackageJson = JSON.parse(fs.readFileSync(rootPackageJsonPath, "utf8"));
|
|
const bundledRuntimeDependencySpecs = collectBundledPluginRuntimeDependencySpecs(
|
|
path.join(packageRoot, "extensions"),
|
|
);
|
|
const requiredRootMirrors = collectRootDistBundledRuntimeMirrors({
|
|
bundledRuntimeDependencySpecs,
|
|
distDir: path.join(packageRoot, "dist"),
|
|
});
|
|
const errors = [
|
|
...collectBundledPluginRootRuntimeMirrorErrors({
|
|
bundledRuntimeDependencySpecs,
|
|
requiredRootMirrors,
|
|
rootPackageJson,
|
|
}),
|
|
...collectBuiltBundledPluginStagedRuntimeDependencyErrors({
|
|
bundledPluginsDir: builtPluginsDir,
|
|
}),
|
|
];
|
|
|
|
assert.deepEqual(errors, [], errors.join("\n"));
|
|
|
|
function packageNodeModulesPath(nodeModulesDir, packageName) {
|
|
return path.join(nodeModulesDir, ...packageName.split("/"));
|
|
}
|
|
|
|
function stageBrowserRuntimeDependencyStub(stageNodeModulesDir, packageName) {
|
|
const packageDir = packageNodeModulesPath(stageNodeModulesDir, packageName);
|
|
fs.mkdirSync(packageDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(packageDir, "package.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
name: packageName,
|
|
version: "0.0.0",
|
|
main: "./index.cjs",
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf8",
|
|
);
|
|
|
|
if (packageName === "playwright-core") {
|
|
fs.writeFileSync(
|
|
path.join(packageDir, "index.cjs"),
|
|
[
|
|
"module.exports = {",
|
|
" chromium: { marker: 'stub-chromium' },",
|
|
" devices: { 'Stub Device': { marker: 'stub-device' } },",
|
|
"};",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (packageName === "typebox") {
|
|
fs.writeFileSync(
|
|
path.join(packageDir, "index.cjs"),
|
|
[
|
|
"const createSchema = (kind, value = {}) => ({ kind, ...value });",
|
|
"const Type = new Proxy(function Type() {}, {",
|
|
" get(_target, prop) {",
|
|
" if (prop === Symbol.toStringTag) {",
|
|
" return 'Type';",
|
|
" }",
|
|
" return (...args) => createSchema(String(prop), { args });",
|
|
" },",
|
|
"});",
|
|
"module.exports = { Type };",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
return;
|
|
}
|
|
|
|
fs.writeFileSync(path.join(packageDir, "index.cjs"), "module.exports = {};\n", "utf8");
|
|
}
|
|
|
|
function findBuiltBrowserEntryPath(distDir) {
|
|
const candidates = fs
|
|
.readdirSync(distDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && /^pw-ai-(?!state-).*\.js$/u.test(entry.name))
|
|
.map((entry) => path.join(distDir, entry.name))
|
|
.toSorted((left, right) => left.localeCompare(right));
|
|
if (candidates.length === 0) {
|
|
throw new assert.AssertionError({
|
|
message: `missing built pw-ai entry under ${distDir}`,
|
|
});
|
|
}
|
|
return candidates[0];
|
|
}
|
|
|
|
function createBuiltBrowserImportSmokeFixture(packageRoot) {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-built-browser-smoke-"));
|
|
const tempDistDir = path.join(tempRoot, "dist");
|
|
const tempNodeModulesDir = path.join(tempRoot, "node_modules");
|
|
const stageNodeModulesDir = path.join(
|
|
tempRoot,
|
|
".openclaw",
|
|
"plugin-runtime-deps",
|
|
"browser",
|
|
"node_modules",
|
|
);
|
|
|
|
fs.cpSync(path.join(packageRoot, "dist"), tempDistDir, {
|
|
recursive: true,
|
|
dereference: true,
|
|
});
|
|
fs.copyFileSync(path.join(packageRoot, "package.json"), path.join(tempRoot, "package.json"));
|
|
fs.cpSync(path.join(packageRoot, "node_modules"), tempNodeModulesDir, {
|
|
recursive: true,
|
|
dereference: true,
|
|
});
|
|
fs.rmSync(path.join(tempNodeModulesDir, "playwright-core"), {
|
|
force: true,
|
|
recursive: true,
|
|
});
|
|
|
|
assert.ok(!fs.existsSync(path.join(tempNodeModulesDir, "playwright-core")));
|
|
fs.mkdirSync(stageNodeModulesDir, { recursive: true });
|
|
assert.deepEqual(fs.readdirSync(stageNodeModulesDir), []);
|
|
|
|
const browserPackageJson = JSON.parse(
|
|
fs.readFileSync(path.join(tempDistDir, "extensions", "browser", "package.json"), "utf8"),
|
|
);
|
|
const browserRuntimeDeps = new Map(
|
|
[
|
|
...Object.entries(browserPackageJson.dependencies ?? {}),
|
|
...Object.entries(browserPackageJson.optionalDependencies ?? {}),
|
|
].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0),
|
|
);
|
|
const missingBrowserRuntimeDeps = [...browserRuntimeDeps.keys()]
|
|
.filter((packageName) => {
|
|
const rootSentinel = path.join(tempNodeModulesDir, ...packageName.split("/"), "package.json");
|
|
const stagedSentinel = path.join(
|
|
stageNodeModulesDir,
|
|
...packageName.split("/"),
|
|
"package.json",
|
|
);
|
|
return !fs.existsSync(rootSentinel) && !fs.existsSync(stagedSentinel);
|
|
})
|
|
.toSorted((left, right) => left.localeCompare(right));
|
|
|
|
for (const packageName of missingBrowserRuntimeDeps) {
|
|
stageBrowserRuntimeDependencyStub(stageNodeModulesDir, packageName);
|
|
}
|
|
|
|
return {
|
|
entryPath: findBuiltBrowserEntryPath(tempDistDir),
|
|
stageNodeModulesDir,
|
|
tempRoot,
|
|
};
|
|
}
|
|
|
|
function runNodeEval(params) {
|
|
return spawnSync(process.execPath, ["--input-type=module", "--eval", params.source], {
|
|
cwd: params.cwd,
|
|
encoding: "utf8",
|
|
env: params.env,
|
|
});
|
|
}
|
|
|
|
function runBuiltBrowserImportSmoke(packageRoot) {
|
|
const fixture = createBuiltBrowserImportSmokeFixture(packageRoot);
|
|
try {
|
|
assert.ok(fs.existsSync(fixture.entryPath), `missing built pw-ai entry: ${fixture.entryPath}`);
|
|
assert.ok(
|
|
!fs.existsSync(path.join(fixture.tempRoot, "node_modules", "playwright-core")),
|
|
"package-root playwright-core should be absent in the smoke fixture",
|
|
);
|
|
assert.ok(
|
|
fs.existsSync(path.join(fixture.stageNodeModulesDir, "playwright-core", "package.json")),
|
|
"staged playwright-core should be present in the smoke fixture",
|
|
);
|
|
|
|
const rootEsmResult = runNodeEval({
|
|
cwd: fixture.tempRoot,
|
|
env: { ...process.env, NODE_PATH: fixture.stageNodeModulesDir },
|
|
source:
|
|
"await import('playwright-core')" +
|
|
".then(() => { process.exitCode = 1; })" +
|
|
".catch((error) => { if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error; });",
|
|
});
|
|
assert.equal(
|
|
rootEsmResult.status,
|
|
0,
|
|
[
|
|
"[build-smoke] native ESM unexpectedly resolved staged playwright-core",
|
|
rootEsmResult.stdout.trim(),
|
|
rootEsmResult.stderr.trim(),
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n"),
|
|
);
|
|
|
|
const builtImportResult = runNodeEval({
|
|
cwd: fixture.tempRoot,
|
|
env: { ...process.env, NODE_PATH: fixture.stageNodeModulesDir },
|
|
source: `await import(${JSON.stringify(pathToFileURL(fixture.entryPath).href)});`,
|
|
});
|
|
assert.equal(
|
|
builtImportResult.status,
|
|
0,
|
|
[
|
|
"[build-smoke] built browser pw-ai import failed",
|
|
`status=${String(builtImportResult.status)}`,
|
|
`signal=${String(builtImportResult.signal)}`,
|
|
builtImportResult.stdout.trim(),
|
|
builtImportResult.stderr.trim(),
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n"),
|
|
);
|
|
} finally {
|
|
fs.rmSync(fixture.tempRoot, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
runBuiltBrowserImportSmoke(packageRoot);
|
|
|
|
process.stdout.write(
|
|
`[build-smoke] bundled runtime dependency smoke passed packageRoot=${packageRoot}\n`,
|
|
);
|