





















@@ -1,6 +1,9 @@
11import assert from "node:assert/strict";
2+import { spawnSync } from "node:child_process";
23import fs from "node:fs";
4+import os from "node:os";
35import path from "node:path";
6+import { pathToFileURL } from "node:url";
47import {
58collectBuiltBundledPluginStagedRuntimeDependencyErrors,
69collectBundledPluginRootRuntimeMirrorErrors,
@@ -39,6 +42,209 @@ const errors = [
3942];
40434144assert.deepEqual(errors, [], errors.join("\n"));
45+46+function packageNodeModulesPath(nodeModulesDir, packageName) {
47+return path.join(nodeModulesDir, ...packageName.split("/"));
48+}
49+50+function stageBrowserRuntimeDependencyStub(stageNodeModulesDir, packageName) {
51+const packageDir = packageNodeModulesPath(stageNodeModulesDir, packageName);
52+fs.mkdirSync(packageDir, { recursive: true });
53+fs.writeFileSync(
54+path.join(packageDir, "package.json"),
55+`${JSON.stringify(
56+ {
57+ name: packageName,
58+ version: "0.0.0",
59+ main: "./index.cjs",
60+ },
61+ null,
62+ 2,
63+ )}\n`,
64+"utf8",
65+);
66+67+if (packageName === "playwright-core") {
68+fs.writeFileSync(
69+path.join(packageDir, "index.cjs"),
70+[
71+"module.exports = {",
72+" chromium: { marker: 'stub-chromium' },",
73+" devices: { 'Stub Device': { marker: 'stub-device' } },",
74+"};",
75+"",
76+].join("\n"),
77+"utf8",
78+);
79+return;
80+}
81+82+if (packageName === "typebox") {
83+fs.writeFileSync(
84+path.join(packageDir, "index.cjs"),
85+[
86+"const createSchema = (kind, value = {}) => ({ kind, ...value });",
87+"const Type = new Proxy(function Type() {}, {",
88+" get(_target, prop) {",
89+" if (prop === Symbol.toStringTag) {",
90+" return 'Type';",
91+" }",
92+" return (...args) => createSchema(String(prop), { args });",
93+" },",
94+"});",
95+"module.exports = { Type };",
96+"",
97+].join("\n"),
98+"utf8",
99+);
100+return;
101+}
102+103+fs.writeFileSync(path.join(packageDir, "index.cjs"), "module.exports = {};\n", "utf8");
104+}
105+106+function findBuiltBrowserEntryPath(distDir) {
107+const candidates = fs
108+.readdirSync(distDir, { withFileTypes: true })
109+.filter((entry) => entry.isFile() && /^pw-ai-(?!state-).*\.js$/u.test(entry.name))
110+.map((entry) => path.join(distDir, entry.name))
111+.toSorted((left, right) => left.localeCompare(right));
112+if (candidates.length === 0) {
113+throw new assert.AssertionError({
114+message: `missing built pw-ai entry under ${distDir}`,
115+});
116+}
117+return candidates[0];
118+}
119+120+function createBuiltBrowserImportSmokeFixture(packageRoot) {
121+const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-built-browser-smoke-"));
122+const tempDistDir = path.join(tempRoot, "dist");
123+const tempNodeModulesDir = path.join(tempRoot, "node_modules");
124+const stageNodeModulesDir = path.join(
125+tempRoot,
126+".openclaw",
127+"plugin-runtime-deps",
128+"browser",
129+"node_modules",
130+);
131+132+fs.cpSync(path.join(packageRoot, "dist"), tempDistDir, {
133+recursive: true,
134+dereference: true,
135+});
136+fs.copyFileSync(path.join(packageRoot, "package.json"), path.join(tempRoot, "package.json"));
137+fs.cpSync(path.join(packageRoot, "node_modules"), tempNodeModulesDir, {
138+recursive: true,
139+dereference: true,
140+});
141+fs.rmSync(path.join(tempNodeModulesDir, "playwright-core"), {
142+force: true,
143+recursive: true,
144+});
145+146+assert.ok(!fs.existsSync(path.join(tempNodeModulesDir, "playwright-core")));
147+fs.mkdirSync(stageNodeModulesDir, { recursive: true });
148+assert.deepEqual(fs.readdirSync(stageNodeModulesDir), []);
149+150+const browserPackageJson = JSON.parse(
151+fs.readFileSync(path.join(tempDistDir, "extensions", "browser", "package.json"), "utf8"),
152+);
153+const browserRuntimeDeps = new Map(
154+[
155+ ...Object.entries(browserPackageJson.dependencies ?? {}),
156+ ...Object.entries(browserPackageJson.optionalDependencies ?? {}),
157+].filter((entry) => typeof entry[1] === "string" && entry[1].length > 0),
158+);
159+const missingBrowserRuntimeDeps = [...browserRuntimeDeps.keys()]
160+.filter((packageName) => {
161+const rootSentinel = path.join(tempNodeModulesDir, ...packageName.split("/"), "package.json");
162+const stagedSentinel = path.join(
163+stageNodeModulesDir,
164+ ...packageName.split("/"),
165+"package.json",
166+);
167+return !fs.existsSync(rootSentinel) && !fs.existsSync(stagedSentinel);
168+})
169+.toSorted((left, right) => left.localeCompare(right));
170+171+for (const packageName of missingBrowserRuntimeDeps) {
172+stageBrowserRuntimeDependencyStub(stageNodeModulesDir, packageName);
173+}
174+175+return {
176+entryPath: findBuiltBrowserEntryPath(tempDistDir),
177+ stageNodeModulesDir,
178+ tempRoot,
179+};
180+}
181+182+function runNodeEval(params) {
183+return spawnSync(process.execPath, ["--input-type=module", "--eval", params.source], {
184+cwd: params.cwd,
185+encoding: "utf8",
186+env: params.env,
187+});
188+}
189+190+function runBuiltBrowserImportSmoke(packageRoot) {
191+const fixture = createBuiltBrowserImportSmokeFixture(packageRoot);
192+try {
193+assert.ok(fs.existsSync(fixture.entryPath), `missing built pw-ai entry: ${fixture.entryPath}`);
194+assert.ok(
195+!fs.existsSync(path.join(fixture.tempRoot, "node_modules", "playwright-core")),
196+"package-root playwright-core should be absent in the smoke fixture",
197+);
198+assert.ok(
199+fs.existsSync(path.join(fixture.stageNodeModulesDir, "playwright-core", "package.json")),
200+"staged playwright-core should be present in the smoke fixture",
201+);
202+203+const rootEsmResult = runNodeEval({
204+cwd: fixture.tempRoot,
205+env: { ...process.env, NODE_PATH: fixture.stageNodeModulesDir },
206+source:
207+"await import('playwright-core')" +
208+".then(() => { process.exitCode = 1; })" +
209+".catch((error) => { if (error?.code !== 'ERR_MODULE_NOT_FOUND') throw error; });",
210+});
211+assert.equal(
212+rootEsmResult.status,
213+0,
214+[
215+"[build-smoke] native ESM unexpectedly resolved staged playwright-core",
216+rootEsmResult.stdout.trim(),
217+rootEsmResult.stderr.trim(),
218+]
219+.filter(Boolean)
220+.join("\n"),
221+);
222+223+const builtImportResult = runNodeEval({
224+cwd: fixture.tempRoot,
225+env: { ...process.env, NODE_PATH: fixture.stageNodeModulesDir },
226+source: `await import(${JSON.stringify(pathToFileURL(fixture.entryPath).href)});`,
227+});
228+assert.equal(
229+builtImportResult.status,
230+0,
231+[
232+"[build-smoke] built browser pw-ai import failed",
233+`status=${String(builtImportResult.status)}`,
234+`signal=${String(builtImportResult.signal)}`,
235+builtImportResult.stdout.trim(),
236+builtImportResult.stderr.trim(),
237+]
238+.filter(Boolean)
239+.join("\n"),
240+);
241+} finally {
242+fs.rmSync(fixture.tempRoot, { recursive: true, force: true });
243+}
244+}
245+246+runBuiltBrowserImportSmoke(packageRoot);
247+42248process.stdout.write(
43249`[build-smoke] bundled runtime dependency smoke passed packageRoot=${packageRoot}\n`,
44250);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。