fix(runtime): harden plugin capture mocks

This commit is contained in:
Vincent Koc 2026-05-02 10:51:35 -07:00
parent b919df78d3
commit a6af8800e0
No known key found for this signature in database
8 changed files with 236 additions and 33 deletions

View File

@ -55,8 +55,11 @@ export function validatePlatformProbes(report, options = {}) {
errors.push("all TypeScript loader entrypoints must track a Jiti fallback candidate");
}
for (const entrypoint of report.entrypoints) {
if (entrypoint.loaderPrimary === "tsx" && (!entrypoint.captureUsesTsx || !entrypoint.syntheticUsesTsx)) {
errors.push(`${entrypoint.id}: tsx loader strategy is not reflected in capture and synthetic commands`);
if (
entrypoint.loaderPrimary === "tsx" &&
(!entrypoint.captureUsesTypeScriptLoader || !entrypoint.syntheticUsesTypeScriptLoader)
) {
errors.push(`${entrypoint.id}: TypeScript loader strategy is not reflected in capture and synthetic commands`);
}
}
return errors;
@ -94,9 +97,21 @@ export function renderPlatformProbesMarkdown(report, options = {}) {
entrypoint.loaderAlternatives.join(", ") || "-",
entrypoint.captureUsesTsx ? "yes" : "no",
entrypoint.syntheticUsesTsx ? "yes" : "no",
entrypoint.captureUsesMockSdk ? "yes" : "no",
entrypoint.syntheticUsesMockSdk ? "yes" : "no",
entrypoint.entrypoint,
]),
["Fixture", "Status", "Primary", "Alternatives", "Capture TSX", "Synthetic TSX", "Entrypoint"],
[
"Fixture",
"Status",
"Primary",
"Alternatives",
"Capture TSX",
"Synthetic TSX",
"Capture Mock SDK",
"Synthetic Mock SDK",
"Entrypoint",
],
),
"",
"## Portability Findings",
@ -137,6 +152,10 @@ export function renderPlatformProbesMarkdown(report, options = {}) {
function summarizeEntrypoint(fixtureId, entrypoint) {
const captureStep = entrypoint.steps.find((step) => step.kind === "capture");
const syntheticStep = entrypoint.steps.find((step) => step.kind === "synthetic-probe");
const captureUsesTsx = Boolean(captureStep?.command.includes("--import tsx"));
const syntheticUsesTsx = Boolean(syntheticStep?.command.includes("--import tsx"));
const captureUsesMockSdk = Boolean(captureStep?.command.includes("--mock-sdk"));
const syntheticUsesMockSdk = Boolean(syntheticStep?.command.includes("--mock-sdk"));
return {
fixture: fixtureId,
id: entrypoint.id,
@ -148,8 +167,12 @@ function summarizeEntrypoint(fixtureId, entrypoint) {
loaderAlternatives: entrypoint.loaderStrategy.alternatives,
capturePlanned: Boolean(captureStep),
syntheticProbePlanned: Boolean(syntheticStep),
captureUsesTsx: Boolean(captureStep?.command.includes("--import tsx")),
syntheticUsesTsx: Boolean(syntheticStep?.command.includes("--import tsx")),
captureUsesTsx,
syntheticUsesTsx,
captureUsesMockSdk,
syntheticUsesMockSdk,
captureUsesTypeScriptLoader: captureUsesTsx || captureUsesMockSdk,
syntheticUsesTypeScriptLoader: syntheticUsesTsx || syntheticUsesMockSdk,
};
}
@ -260,7 +283,7 @@ function buildRecommendations(portabilityFindings, entrypoints) {
if (entrypoints.some((entrypoint) => entrypoint.loaderPrimary === "tsx")) {
recommendations.push({
area: "loader",
action: "keep tsx as the source-entrypoint smoke path, add a Jiti execution lane before treating TS plugin source compatibility as covered",
action: "keep mock-SDK TypeScript capture green, add a real host-loader/Jiti lane before treating TS plugin source compatibility as covered",
});
}
if (portabilityFindings.some((finding) => finding.riskCodes.includes("rsync-required"))) {

View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const packageJsonPath = path.resolve(process.cwd(), "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
let changed = false;
for (const [name, specifier] of Object.entries(packageJson.devDependencies ?? {})) {
if (typeof specifier === "string" && specifier.startsWith("workspace:")) {
delete packageJson.devDependencies[name];
changed = true;
}
}
if (packageJson.devDependencies && Object.keys(packageJson.devDependencies).length === 0) {
delete packageJson.devDependencies;
changed = true;
}
if (changed) {
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
}

View File

@ -254,9 +254,20 @@ export async function createMockSdkPackage(rootDir, options = {}) {
)}\n`,
"utf8",
);
await writeFile(path.join(pluginSdkDir, "index.js"), mockSdkSource(), "utf8");
const rootExportNames = new Set([
...mockSdkExportNames,
...(imports.bySpecifier.get("openclaw/plugin-sdk") ?? []),
]);
await writeFile(path.join(pluginSdkDir, "index.js"), mockSdkSource(rootExportNames), "utf8");
for (const [subpath, exportNames] of Object.entries(mockSdkSubpathExports)) {
await writeFile(path.join(pluginSdkDir, `${subpath}.js`), mockSdkSubpathSource(exportNames), "utf8");
const specifier = `openclaw/plugin-sdk/${subpath}`;
await writeFile(
path.join(pluginSdkDir, `${subpath}.js`),
mockSdkSubpathSource(exportNames, imports.bySpecifier.get(specifier) ?? new Set(), {
zod: subpath === "zod",
}),
"utf8",
);
}
for (const specifier of imports.openclawSdkSpecifiers) {
if (specifier === "openclaw/plugin-sdk") {
@ -428,6 +439,9 @@ export async function resolve(specifier, context, nextResolve) {
const subpath = specifier.slice("openclaw/plugin-sdk/".length);
return moduleUrl(path.join(pluginSdkDir, \`\${subpath}.js\`));
}
if (externalMap.has(specifier)) {
return moduleUrl(externalMap.get(specifier));
}
try {
return await nextResolve(specifier, context);
} catch (error) {
@ -531,6 +545,12 @@ function genericExportStatement(name) {
if (["createChatChannelPlugin", "createPlugin", "defineChannelPluginEntry", "definePlugin", "definePluginEntry", "defineSetupPluginEntry"].includes(name)) {
return name === "definePluginEntry" ? "export { definePluginEntry };" : `export const ${name} = definePluginEntry;`;
}
if (name === "defineBundledChannelEntry") {
return "export { defineBundledChannelEntry };";
}
if (name === "defineBundledChannelSetupEntry") {
return "export { defineBundledChannelSetupEntry };";
}
if (/^[A-Z].*Schema$/u.test(name)) {
return `export const ${name} = createSchema();`;
}
@ -547,9 +567,41 @@ function genericMockRuntimeSource(options = {}) {
}
return typeof entry === "function" ? { register: entry } : entry;
}
function defineBundledChannelEntry(entry = {}) {
return {
...entry,
kind: "bundled-channel-entry",
register(api) {
if (api?.registrationMode === "cli-metadata") {
return entry.registerCliMetadata?.(api);
}
if (api?.registrationMode !== "tool-discovery") {
api?.registerChannel?.({
id: entry.id,
name: entry.name,
description: entry.description,
plugin: { id: entry.id, name: entry.name },
});
}
entry.registerCliMetadata?.(api);
return entry.registerFull?.(api);
},
};
}
function defineBundledChannelSetupEntry(entry = {}) {
return {
...entry,
kind: "bundled-channel-setup-entry",
};
}
` : ""}
function createMockValue(name) {
function fn(...args) {
if (name === "resolvePreferredOpenClawTmpDir") {
return process.env.TMPDIR || "/tmp";
}
if (name.startsWith("normalize")) {
return typeof args[0] === "string" ? args[0] : "";
}
@ -728,7 +780,8 @@ function createTypeNamespace() {
`;
}
function mockSdkSource() {
function mockSdkSource(exportNames = mockSdkExportNames) {
const dynamicExportNames = [...exportNames].filter((name) => !mockSdkExportNames.includes(name));
return `function normalizeEntry(entry) {
return typeof entry === "function" ? { register: entry } : entry;
}
@ -1538,15 +1591,20 @@ export const OPENAI_RESPONSES_STREAM_HOOKS = buildProviderStreamFamilyHooks("ope
export const OPENROUTER_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("openrouter-thinking");
export const TOOL_STREAM_DEFAULT_ON_HOOKS = buildProviderStreamFamilyHooks("tool-stream-default");
export const pluginSdkMock = true;
${dynamicExportNames.map(genericExportStatement).join("\n")}
export default {
${mockSdkExportNames.map((name) => ` ${name},`).join("\n")}
${[...exportNames].map((name) => ` ${name},`).join("\n")}
};
`;
}
function mockSdkSubpathSource(exportNames) {
return `${exportNames.map((name) => `export { ${name} } from "./index.js";`).join("\n")}
function mockSdkSubpathSource(staticExportNames, importedExportNames, options = {}) {
const staticNames = new Set(staticExportNames);
const dynamicNames = [...importedExportNames].filter((name) => !staticNames.has(name));
return `${[...staticNames].map((name) => `export { ${name} } from "./index.js";`).join("\n")}
${dynamicNames.length > 0 ? genericMockRuntimeSource({ includeSdkRuntime: true, zod: options.zod }) : ""}
${dynamicNames.map(genericExportStatement).join("\n")}
export { default } from "./index.js";
`;
}

View File

@ -71,6 +71,7 @@ export async function buildWorkspacePlan(options = {}) {
installStepCount: allSteps.filter((step) => step.kind === "install").length,
auditStepCount: allSteps.filter((step) => step.kind === "audit").length,
buildStepCount: allSteps.filter((step) => step.kind === "build").length,
pruneDevWorkspaceDependencyStepCount: allSteps.filter((step) => step.kind === "prune-dev-workspace-deps").length,
artifactStepCount: allSteps.filter((step) => step.kind === "prepare-artifacts").length,
captureStepCount: allSteps.filter((step) => step.kind === "capture").length,
syntheticProbeStepCount: allSteps.filter((step) => step.kind === "synthetic-probe").length,
@ -179,6 +180,7 @@ export function renderWorkspacePlanMarkdown(plan, options = {}) {
["Artifact dirs", plan.summary.artifactStepCount],
["Install steps", plan.summary.installStepCount],
["Audit steps", plan.summary.auditStepCount],
["Prune dev workspace dependency steps", plan.summary.pruneDevWorkspaceDependencyStepCount],
["Build steps", plan.summary.buildStepCount],
["Capture steps", plan.summary.captureStepCount],
["Synthetic probe steps", plan.summary.syntheticProbeStepCount],
@ -248,6 +250,14 @@ async function buildEntrypointPlan({ fixtureId, entrypoint, packageSummary, pack
}
if (requiredCapabilities.includes("dependency-install")) {
if (hasWorkspaceProtocolDevDependencies(packageJson)) {
steps.push({
kind: "prune-dev-workspace-deps",
command: `node ${helperScript(settings, workspacePath, settings.pruneWorkspaceDevDepsScript, "prune-workspace-dev-deps-cli.js")}`,
cwd: workspacePath,
reason: "remove workspace: devDependencies from the isolated runtime install; the mock SDK supplies OpenClaw host imports",
});
}
steps.push({
kind: "install",
command: installCommand(packageManager),
@ -319,6 +329,7 @@ function workspaceSettings(options) {
resultsRoot: repoRelative(options.resultsRoot ?? defaultWorkspacePlanOptions.resultsRoot),
rootDir: path.resolve(options.rootDir ?? process.cwd()),
syntheticProbeScript: options.syntheticProbeScript ?? defaultWorkspacePlanOptions.syntheticProbeScript,
pruneWorkspaceDevDepsScript: options.pruneWorkspaceDevDepsScript,
workspaceRoot: repoRelative(options.workspaceRoot ?? defaultWorkspacePlanOptions.workspaceRoot),
};
}
@ -377,6 +388,12 @@ function hasHostLinkedOpenClawDependency(packageSummary) {
].includes("openclaw");
}
function hasWorkspaceProtocolDevDependencies(packageJson) {
return Object.values(packageJson.devDependencies ?? {}).some(
(value) => typeof value === "string" && value.startsWith("workspace:"),
);
}
function detectPackageManager(rootDir, packageDir, packageJson) {
const declared = typeof packageJson.packageManager === "string" ? packageJson.packageManager.split("@")[0] : null;
if (declared) {
@ -458,15 +475,13 @@ function runCommand(packageManager, script) {
}
function captureCommand(settings, fixtureId, entrypoint, workspacePath) {
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
const script = helperScript(settings, workspacePath, settings.captureScript, "capture-cli.js");
return `${settings.optInEnv} node${loader} ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
return `${settings.optInEnv} node ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
}
function syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath) {
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
const script = helperScript(settings, workspacePath, settings.syntheticProbeScript, "synthetic-probes-cli.js");
return `${settings.optInEnv} node${loader} ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
return `${settings.optInEnv} node ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
}
function helperScript(settings, workspacePath, configuredScript, helperFileName) {

View File

@ -151,6 +151,7 @@ test("capture entrypoint can mock OpenClaw plugin SDK imports", async () => {
'import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";',
'import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";',
'import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";',
'import { GPT5_BEHAVIOR_CONTRACT } from "openclaw/plugin-sdk/provider-model-shared";',
'import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";',
'import { registerPluginHttpRoute, resolveWebhookPath } from "openclaw/plugin-sdk/webhook-ingress";',
"",
@ -171,6 +172,7 @@ test("capture entrypoint can mock OpenClaw plugin SDK imports", async () => {
"",
"export default definePluginEntry((api) => {",
" if (!pluginSdkMock) throw new Error('expected mock SDK');",
" if (!GPT5_BEHAVIOR_CONTRACT) throw new Error('expected dynamic subpath mock export');",
" provider.register(api);",
" api.registerHttpRoute({ path: resolveWebhookPath('hook'), handler() {} });",
" api.registerTool({ name: 'fixture_tool', inputSchema: { type: 'object' }, run() {} });",
@ -192,3 +194,71 @@ test("capture entrypoint can mock OpenClaw plugin SDK imports", async () => {
["registration:registerProvider", "registration:registerHttpRoute", "registration:registerTool"],
);
});
test("mock capture prefers discovered bare mocks over installed dependency exports", async () => {
const dir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-mock-bare-capture-"));
await mkdir(path.join(dir, "node_modules/typebox"), { recursive: true });
await writeFile(
path.join(dir, "node_modules/typebox/package.json"),
JSON.stringify({ name: "typebox", version: "0.0.0", type: "module", exports: "./index.js" }, null, 2),
"utf8",
);
await writeFile(path.join(dir, "node_modules/typebox/index.js"), "export const Type = {};\n", "utf8");
const entrypoint = path.join(dir, "index.mjs");
await writeFile(
entrypoint,
[
"import path from 'node:path';",
'import { Static, Type } from "typebox";',
'import { resolvePreferredOpenClawTmpDir } from "fixture-api";',
"export function register(api) {",
" if (!Static || !Type) throw new Error('expected mocked typebox exports');",
" path.join(resolvePreferredOpenClawTmpDir(), 'fixture');",
" api.registerTool({ name: 'fixture_tool', inputSchema: Type.Object({}), run() {} });",
"}",
].join("\n"),
"utf8",
);
const result = await captureEntrypoint("index.mjs", {
cwd: dir,
pluginRoot: dir,
mockSdk: true,
});
assert.equal(result.status, "captured");
assert.deepEqual(result.captured.map((item) => `${item.kind}:${item.name}`), ["registration:registerTool"]);
});
test("mock capture expands bundled channel entry registration shells", async () => {
const dir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-bundled-channel-capture-"));
const entrypoint = path.join(dir, "index.mjs");
await writeFile(
entrypoint,
[
'import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";',
"export default defineBundledChannelEntry({",
" id: 'fixture-channel',",
" name: 'Fixture Channel',",
" description: 'Fixture channel',",
" plugin: { specifier: './channel.js', exportName: 'fixtureChannel' },",
" registerFull(api) {",
" api.registerTool({ name: 'fixture_tool', inputSchema: { type: 'object' }, run() {} });",
" },",
"});",
].join("\n"),
"utf8",
);
const result = await captureEntrypoint("index.mjs", {
cwd: dir,
pluginRoot: dir,
mockSdk: true,
});
assert.equal(result.status, "captured");
assert.deepEqual(result.captured.map((item) => `${item.kind}:${item.name}`), [
"registration:registerChannel",
"registration:registerTool",
]);
});

View File

@ -40,11 +40,11 @@ test("platform probes classify loader and shell portability risks", () => {
},
{
kind: "capture",
command: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 node --import tsx capture.mjs ./src/index.ts",
command: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 node capture.mjs ./src/index.ts --mock-sdk",
},
{
kind: "synthetic-probe",
command: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 node --import tsx synthetic.mjs --entrypoint ./src/index.ts",
command: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 node synthetic.mjs --entrypoint ./src/index.ts --mock-sdk",
},
],
},
@ -124,7 +124,7 @@ test("platform probes separate executor-covered portability risks from residual
assert.match(renderPlatformProbesMarkdown(report), /Covered Portability Findings/);
});
test("platform probe validation requires jiti fallback and reflected tsx commands", () => {
test("platform probe validation requires jiti fallback and reflected TypeScript loader commands", () => {
const errors = validatePlatformProbes({
mode: "plan-only",
targets: ["linux", "macos", "windows", "container"],
@ -137,11 +137,14 @@ test("platform probe validation requires jiti fallback and reflected tsx command
id: "cold-import.extension:fixture:index",
loaderPrimary: "tsx",
captureUsesTsx: true,
captureUsesTypeScriptLoader: true,
syntheticUsesTsx: false,
syntheticUsesMockSdk: false,
syntheticUsesTypeScriptLoader: false,
},
],
});
assert.ok(errors.some((error) => error.includes("Jiti fallback")));
assert.ok(errors.some((error) => error.includes("tsx loader strategy")));
assert.ok(errors.some((error) => error.includes("TypeScript loader strategy")));
});

View File

@ -106,14 +106,14 @@ test("runtime capture records conversation binding resolved callbacks", async ()
);
});
test("runtime capture report classifies missing mocked SDK exports", async () => {
const rootDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-capture-missing-export-"));
test("runtime capture report synthesizes newly imported mocked SDK exports", async () => {
const rootDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-capture-dynamic-export-"));
await mkdir(path.join(rootDir, "src"), { recursive: true });
await writeFile(
path.join(rootDir, "package.json"),
`${JSON.stringify(
{
name: "openclaw-missing-sdk-export",
name: "openclaw-dynamic-sdk-export",
version: "1.0.0",
type: "module",
openclaw: {
@ -131,9 +131,10 @@ test("runtime capture report classifies missing mocked SDK exports", async () =>
[
'import { definitelyMissing } from "openclaw/plugin-sdk/plugin-entry";',
"",
"export default definitelyMissing({",
" register() {},",
"});",
"export function register(api) {",
" if (!definitelyMissing) throw new Error('expected dynamic mock export');",
" api.registerTool({ name: 'fixture_tool', inputSchema: { type: 'object' }, run() {} });",
"}",
].join("\n"),
"utf8",
);
@ -142,17 +143,18 @@ test("runtime capture report classifies missing mocked SDK exports", async () =>
const compatibilityReport = await inspectCompatibilityFixtureSet(config, { openclawPath: false });
const captureReport = await buildRuntimeCaptureReport({ report: compatibilityReport, rootDir });
assert.equal(captureReport.summary.failedCount, 1);
assert.equal(captureReport.results[0].status, "error");
assert.equal(captureReport.results[0].failureClass, "missing-sdk-export");
assert.equal(captureReport.results[0].missingExport, "definitelyMissing");
assert.equal(captureReport.summary.failedCount, 0);
assert.equal(captureReport.results[0].status, "captured");
assert.deepEqual(captureReport.results[0].captured.map((entry) => `${entry.kind}:${entry.name}`), [
"registration:registerTool",
]);
const outDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-capture-missing-export-out-"));
const outDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-capture-dynamic-export-out-"));
await writeRuntimeCaptureReport(captureReport, {
jsonPath: path.join(outDir, "capture.json"),
markdownPath: path.join(outDir, "capture.md"),
});
assert.match(await readFile(path.join(outDir, "capture.md"), "utf8"), /missing-sdk-export/);
assert.match(await readFile(path.join(outDir, "capture.md"), "utf8"), /registerTool/);
});
test("runtime capture report classifies registration execution failures", async () => {

View File

@ -23,6 +23,7 @@ test("workspace plan maps blocked entrypoints to opt-in install/build/capture st
packageManager: "npm@10.0.0",
scripts: { build: "tsup" },
dependencies: { "left-pad": "^1.3.0", openclaw: "^1.0.0" },
devDependencies: { "@openclaw/plugin-sdk": "workspace:*" },
},
null,
2,
@ -57,6 +58,7 @@ test("workspace plan maps blocked entrypoints to opt-in install/build/capture st
assert.equal(plan.summary.artifactStepCount, 2);
assert.equal(plan.summary.installStepCount, 1);
assert.equal(plan.summary.auditStepCount, 1);
assert.equal(plan.summary.pruneDevWorkspaceDependencyStepCount, 1);
assert.equal(plan.summary.buildStepCount, 1);
assert.equal(plan.summary.captureStepCount, 2);
assert.equal(plan.summary.syntheticProbeStepCount, 2);
@ -73,7 +75,14 @@ test("workspace plan maps blocked entrypoints to opt-in install/build/capture st
assert.ok(entrypoint.requiredCapabilities.includes("sdk-alias-compat"));
assert.ok(entrypoint.requiredCapabilities.includes("ts-loader"));
assert.ok(entrypoint.steps.some((step) => step.kind === "install" && step.command === "npm install --ignore-scripts"));
assert.ok(entrypoint.steps.some((step) => step.kind === "capture" && step.command.includes("node --import tsx capture.mjs")));
assert.ok(
entrypoint.steps.some(
(step) => step.kind === "prune-dev-workspace-deps" && step.command.includes("prune-workspace-dev-deps-cli.js"),
),
);
assert.ok(entrypoint.steps.some((step) => step.kind === "capture" && step.command.includes("node capture.mjs")));
assert.ok(entrypoint.steps.some((step) => step.kind === "capture" && step.command.includes("--mock-sdk")));
assert.ok(entrypoint.steps.every((step) => !step.command.includes("--import tsx")));
assert.ok(entrypoint.steps.some((step) => step.kind === "synthetic-probe" && step.command.includes("synthetic.mjs")));
const buildEntrypoint = plan.fixtures[0].entrypoints.find((item) => item.packageName === "build-fixture");
assert.ok(buildEntrypoint);