@openclaw/plugin-inspector 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +81 -138
- package/examples/github-actions-plugin-inspector.yml +2 -2
- package/examples/plugin-inspector.config.json +3 -0
- package/package.json +3 -2
- package/src/advanced.js +195 -0
- package/src/api.js +91 -0
- package/src/capture-api.js +180 -12
- package/src/cli.js +69 -27
- package/src/config.js +16 -2
- package/src/index.js +9 -184
- package/src/init.js +150 -0
- package/src/inspector.js +89 -14
- package/src/mock-sdk-capture-runner.js +110 -17
- package/src/runtime-capture-report.js +11 -1
- package/src/sdk-mock.js +1266 -13
- package/src/synthetic-probes.js +153 -9
package/src/init.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { inferPluginSeams, packageId } from "./config.js";
|
|
5
|
+
|
|
6
|
+
export const defaultInitConfigPath = "plugin-inspector.config.json";
|
|
7
|
+
export const defaultInitWorkflowPath = ".github/workflows/plugin-inspector.yml";
|
|
8
|
+
|
|
9
|
+
export async function writePluginInspectorInit(options = {}) {
|
|
10
|
+
const pluginRoot = path.resolve(options.pluginRoot ?? options.cwd ?? process.cwd());
|
|
11
|
+
const configPath = path.resolve(pluginRoot, options.configPath ?? defaultInitConfigPath);
|
|
12
|
+
const written = [];
|
|
13
|
+
|
|
14
|
+
if (existsSync(configPath) && options.force !== true) {
|
|
15
|
+
throw new Error(`${path.relative(pluginRoot, configPath)} already exists; pass --force to overwrite it`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const config = await buildPluginInspectorConfig({ pluginRoot });
|
|
19
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
20
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
21
|
+
written.push(configPath);
|
|
22
|
+
|
|
23
|
+
if (options.ci === true) {
|
|
24
|
+
const workflowPath = path.resolve(pluginRoot, options.workflowPath ?? defaultInitWorkflowPath);
|
|
25
|
+
if (existsSync(workflowPath) && options.force !== true) {
|
|
26
|
+
throw new Error(`${path.relative(pluginRoot, workflowPath)} already exists; pass --force to overwrite it`);
|
|
27
|
+
}
|
|
28
|
+
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
29
|
+
await writeFile(workflowPath, renderGithubActionsWorkflow({ packageManager: options.packageManager }), "utf8");
|
|
30
|
+
written.push(workflowPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { pluginRoot, configPath, written };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function buildPluginInspectorConfig(options = {}) {
|
|
37
|
+
const pluginRoot = path.resolve(options.pluginRoot ?? options.cwd ?? process.cwd());
|
|
38
|
+
const packageJson = await readJsonIfExists(path.join(pluginRoot, "package.json"));
|
|
39
|
+
const pluginManifest = await readJsonIfExists(path.join(pluginRoot, "openclaw.plugin.json"));
|
|
40
|
+
const sourceRoot = inferSourceRoot(packageJson);
|
|
41
|
+
|
|
42
|
+
const plugin = {
|
|
43
|
+
id: pluginManifest?.id ?? packageId(packageJson?.name) ?? "plugin",
|
|
44
|
+
priority: "high",
|
|
45
|
+
seams: inferPluginSeams(pluginManifest, packageJson),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (sourceRoot !== ".") {
|
|
49
|
+
plugin.sourceRoot = sourceRoot;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
version: 1,
|
|
54
|
+
plugin,
|
|
55
|
+
capture: {
|
|
56
|
+
mockSdk: true,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function renderGithubActionsWorkflow(options = {}) {
|
|
62
|
+
const packageManager = normalizePackageManager(options.packageManager);
|
|
63
|
+
const setup = packageManagerSetup(packageManager);
|
|
64
|
+
|
|
65
|
+
return `name: plugin-inspector
|
|
66
|
+
|
|
67
|
+
on:
|
|
68
|
+
pull_request:
|
|
69
|
+
push:
|
|
70
|
+
branches: [main]
|
|
71
|
+
|
|
72
|
+
jobs:
|
|
73
|
+
check:
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v5
|
|
77
|
+
- uses: actions/setup-node@v5
|
|
78
|
+
with:
|
|
79
|
+
node-version: 24
|
|
80
|
+
cache: ${setup.cache}
|
|
81
|
+
${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.install}
|
|
82
|
+
- run: ${setup.exec} @openclaw/plugin-inspector check --no-openclaw
|
|
83
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 ${setup.exec} @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
84
|
+
- uses: actions/upload-artifact@v5
|
|
85
|
+
if: always()
|
|
86
|
+
with:
|
|
87
|
+
name: plugin-inspector-reports
|
|
88
|
+
path: reports/plugin-inspector-*
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function inferSourceRoot(packageJson) {
|
|
93
|
+
const entrypoints = [
|
|
94
|
+
packageJson?.openclaw?.entrypoint,
|
|
95
|
+
...(packageJson?.openclaw?.extensions ?? []),
|
|
96
|
+
...(packageJson?.openclaw?.runtimeExtensions ?? []),
|
|
97
|
+
].filter((value) => typeof value === "string");
|
|
98
|
+
const entrypoint = entrypoints[0] ?? packageJson?.exports?.["."] ?? packageJson?.main ?? "src/index.js";
|
|
99
|
+
if (typeof entrypoint === "string" && entrypoint.startsWith("src/")) {
|
|
100
|
+
return "src";
|
|
101
|
+
}
|
|
102
|
+
return ".";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function readJsonIfExists(filePath) {
|
|
106
|
+
if (!existsSync(filePath)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizePackageManager(packageManager = "npm") {
|
|
113
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(packageManager)) {
|
|
114
|
+
return packageManager;
|
|
115
|
+
}
|
|
116
|
+
throw new Error("--package-manager must be npm, pnpm, yarn, or bun");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function packageManagerSetup(packageManager) {
|
|
120
|
+
if (packageManager === "pnpm") {
|
|
121
|
+
return {
|
|
122
|
+
cache: "pnpm",
|
|
123
|
+
corepack: true,
|
|
124
|
+
install: "pnpm install --frozen-lockfile",
|
|
125
|
+
exec: "pnpm dlx",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (packageManager === "yarn") {
|
|
129
|
+
return {
|
|
130
|
+
cache: "yarn",
|
|
131
|
+
corepack: true,
|
|
132
|
+
install: "yarn install --immutable",
|
|
133
|
+
exec: "yarn dlx",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (packageManager === "bun") {
|
|
137
|
+
return {
|
|
138
|
+
cache: "npm",
|
|
139
|
+
corepack: false,
|
|
140
|
+
install: "bun install --frozen-lockfile",
|
|
141
|
+
exec: "bunx",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
cache: "npm",
|
|
146
|
+
corepack: false,
|
|
147
|
+
install: "npm ci",
|
|
148
|
+
exec: "npx",
|
|
149
|
+
};
|
|
150
|
+
}
|
package/src/inspector.js
CHANGED
|
@@ -156,7 +156,12 @@ export async function captureEntrypoint(entrypoint, options = {}) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
const resolvedEntrypoint = path.resolve(options.cwd ?? process.cwd(), entrypoint);
|
|
159
|
-
|
|
159
|
+
let module;
|
|
160
|
+
try {
|
|
161
|
+
module = await import(pathToFileURL(resolvedEntrypoint).href);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw classifyCapturePhaseError(error, "entrypoint-import-error");
|
|
164
|
+
}
|
|
160
165
|
const register = findRegisterExport(module);
|
|
161
166
|
|
|
162
167
|
if (!register) {
|
|
@@ -168,7 +173,11 @@ export async function captureEntrypoint(entrypoint, options = {}) {
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
const api = createCaptureApi(options.apiOptions);
|
|
171
|
-
|
|
176
|
+
try {
|
|
177
|
+
await register(api);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw classifyCapturePhaseError(error, "registration-execution-error");
|
|
180
|
+
}
|
|
172
181
|
const result = {
|
|
173
182
|
status: "captured",
|
|
174
183
|
entrypoint: resolvedEntrypoint,
|
|
@@ -188,19 +197,85 @@ export async function captureEntrypointWithMockSdk(entrypoint, options = {}) {
|
|
|
188
197
|
pluginRoot: options.pluginRoot,
|
|
189
198
|
apiOptions: options.apiOptions,
|
|
190
199
|
};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
try {
|
|
201
|
+
const { stdout } = await execFileAsync(
|
|
202
|
+
process.execPath,
|
|
203
|
+
["--no-warnings", "--preserve-symlinks", runnerPath, JSON.stringify(payload)],
|
|
204
|
+
{
|
|
205
|
+
cwd: options.cwd ?? process.cwd(),
|
|
206
|
+
env: {
|
|
207
|
+
...process.env,
|
|
208
|
+
...(options.env ?? {}),
|
|
209
|
+
},
|
|
210
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
199
211
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
203
|
-
|
|
212
|
+
);
|
|
213
|
+
return JSON.parse(stdout);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw classifyMockSdkCaptureError(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function classifyMockSdkCaptureError(error) {
|
|
220
|
+
const rawMessage = [error?.stderr, error?.stdout, error?.message].filter(Boolean).join("\n");
|
|
221
|
+
const missingExport = rawMessage.match(/does not provide an export named ['"]([^'"]+)['"]/)?.[1];
|
|
222
|
+
if (missingExport) {
|
|
223
|
+
return enrichCaptureError(error, {
|
|
224
|
+
message: `Mock SDK import failed: openclaw/plugin-sdk is missing export ${missingExport}`,
|
|
225
|
+
failureClass: "missing-sdk-export",
|
|
226
|
+
missingExport,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const missingModule =
|
|
231
|
+
rawMessage.match(/Cannot find (?:package|module) ['"]([^'"]*openclaw\/plugin-sdk[^'"]*)['"]/)?.[1] ??
|
|
232
|
+
rawMessage.match(/Package subpath ['"](\.\/plugin-sdk\/[^'"]+)['"]/)?.[1];
|
|
233
|
+
if (missingModule || rawMessage.includes("openclaw/plugin-sdk")) {
|
|
234
|
+
return enrichCaptureError(error, {
|
|
235
|
+
message: `Mock SDK import failed: ${missingModule ?? "openclaw/plugin-sdk module could not be resolved"}`,
|
|
236
|
+
failureClass: "missing-sdk-module",
|
|
237
|
+
missingModule,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const failureClass = rawMessage.match(/\[plugin-inspector:([^\]]+)\]/)?.[1];
|
|
242
|
+
if (failureClass) {
|
|
243
|
+
return enrichCaptureError(error, {
|
|
244
|
+
message: firstMeaningfulErrorLine(rawMessage.replace(/\[plugin-inspector:[^\]]+\]/, "")) ?? "Mock SDK capture failed",
|
|
245
|
+
failureClass,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return enrichCaptureError(error, {
|
|
250
|
+
message: firstMeaningfulErrorLine(rawMessage) ?? "Mock SDK capture failed",
|
|
251
|
+
failureClass: "mock-sdk-capture-error",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function classifyCapturePhaseError(error, failureClass) {
|
|
256
|
+
return enrichCaptureError(error, {
|
|
257
|
+
message: error instanceof Error ? error.message : String(error),
|
|
258
|
+
failureClass,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function enrichCaptureError(error, details) {
|
|
263
|
+
const wrapped = new Error(details.message, { cause: error });
|
|
264
|
+
wrapped.failureClass = details.failureClass;
|
|
265
|
+
if (details.missingExport) {
|
|
266
|
+
wrapped.missingExport = details.missingExport;
|
|
267
|
+
}
|
|
268
|
+
if (details.missingModule) {
|
|
269
|
+
wrapped.missingModule = details.missingModule;
|
|
270
|
+
}
|
|
271
|
+
return wrapped;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function firstMeaningfulErrorLine(message) {
|
|
275
|
+
return String(message)
|
|
276
|
+
.split("\n")
|
|
277
|
+
.map((line) => line.trim())
|
|
278
|
+
.find((line) => line && !line.startsWith("Command failed:"));
|
|
204
279
|
}
|
|
205
280
|
|
|
206
281
|
function findRegisterExport(module) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdtemp, rm
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { register } from "node:module";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { pathToFileURL } from "node:url";
|
|
@@ -7,12 +8,16 @@ import { createCaptureApi } from "./capture-api.js";
|
|
|
7
8
|
import { createMockSdkPackage } from "./sdk-mock.js";
|
|
8
9
|
|
|
9
10
|
const options = JSON.parse(process.argv[2] ?? "{}");
|
|
11
|
+
let activeOutputCapture = null;
|
|
10
12
|
|
|
11
13
|
try {
|
|
12
14
|
const result = await run(options);
|
|
13
|
-
|
|
15
|
+
writeRunnerStdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
14
16
|
} catch (error) {
|
|
15
|
-
|
|
17
|
+
if (error.failureClass) {
|
|
18
|
+
writeRunnerStderr(`[plugin-inspector:${error.failureClass}]\n`);
|
|
19
|
+
}
|
|
20
|
+
writeRunnerStderr(`${error.stack ?? error.message}\n`);
|
|
16
21
|
process.exitCode = 1;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -22,37 +27,79 @@ async function run(options) {
|
|
|
22
27
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-mock-sdk-"));
|
|
23
28
|
|
|
24
29
|
try {
|
|
25
|
-
await createMockSdkPackage(workspace);
|
|
26
|
-
|
|
27
|
-
await
|
|
28
|
-
const linkedEntrypoint = path.join(linkedPluginRoot, path.relative(pluginRoot, entrypoint));
|
|
29
|
-
return await captureLinkedEntrypoint(linkedEntrypoint, options);
|
|
30
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
31
|
+
register(pathToFileURL(loaderPath));
|
|
32
|
+
return await captureLinkedEntrypoint(entrypoint, options);
|
|
30
33
|
} finally {
|
|
31
34
|
await rm(workspace, { force: true, recursive: true });
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
async function captureLinkedEntrypoint(entrypoint, options) {
|
|
36
|
-
const
|
|
39
|
+
const outputCapture = installProcessOutputCapture();
|
|
40
|
+
activeOutputCapture = outputCapture;
|
|
41
|
+
|
|
42
|
+
let module;
|
|
43
|
+
try {
|
|
44
|
+
module = await import(pathToFileURL(entrypoint).href);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
await drainAsyncOutput();
|
|
47
|
+
throw capturePhaseError(error, "entrypoint-import-error");
|
|
48
|
+
}
|
|
37
49
|
const register = findRegisterExport(module);
|
|
38
50
|
|
|
39
51
|
if (!register) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
await drainAsyncOutput();
|
|
53
|
+
return withProcessOutput(
|
|
54
|
+
{
|
|
55
|
+
status: "no-register-export",
|
|
56
|
+
entrypoint: options.entrypoint,
|
|
57
|
+
mockSdk: true,
|
|
58
|
+
captured: [],
|
|
59
|
+
},
|
|
60
|
+
outputCapture,
|
|
61
|
+
);
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
const api = createCaptureApi(options.apiOptions);
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
try {
|
|
66
|
+
await register(api);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await drainAsyncOutput();
|
|
69
|
+
throw capturePhaseError(error, "registration-execution-error");
|
|
70
|
+
}
|
|
71
|
+
await drainAsyncOutput();
|
|
72
|
+
|
|
73
|
+
const result = {
|
|
51
74
|
status: "captured",
|
|
52
75
|
entrypoint: options.entrypoint,
|
|
53
76
|
mockSdk: true,
|
|
54
77
|
captured: api.getCapturedContracts(),
|
|
55
78
|
};
|
|
79
|
+
if (options.apiOptions?.retainHandlers === true) {
|
|
80
|
+
result.retained = api.getRetainedContracts();
|
|
81
|
+
}
|
|
82
|
+
return withProcessOutput(result, outputCapture);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function withProcessOutput(result, outputCapture) {
|
|
86
|
+
const stdout = outputCapture.stdout();
|
|
87
|
+
const stderr = outputCapture.stderr();
|
|
88
|
+
if (stdout.length === 0 && stderr.length === 0) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...result,
|
|
93
|
+
processOutput: {
|
|
94
|
+
stdout,
|
|
95
|
+
stderr,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function capturePhaseError(error, failureClass) {
|
|
101
|
+
error.failureClass = failureClass;
|
|
102
|
+
return error;
|
|
56
103
|
}
|
|
57
104
|
|
|
58
105
|
function findRegisterExport(module) {
|
|
@@ -67,3 +114,49 @@ function findRegisterExport(module) {
|
|
|
67
114
|
}
|
|
68
115
|
return null;
|
|
69
116
|
}
|
|
117
|
+
|
|
118
|
+
function installProcessOutputCapture() {
|
|
119
|
+
const stdoutChunks = [];
|
|
120
|
+
const stderrChunks = [];
|
|
121
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
122
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
123
|
+
|
|
124
|
+
process.stdout.write = (chunk, encoding, callback) => {
|
|
125
|
+
stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
|
|
126
|
+
invokeWriteCallback(encoding, callback);
|
|
127
|
+
return true;
|
|
128
|
+
};
|
|
129
|
+
process.stderr.write = (chunk, encoding, callback) => {
|
|
130
|
+
stderrChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
|
|
131
|
+
invokeWriteCallback(encoding, callback);
|
|
132
|
+
return true;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
originalStdoutWrite,
|
|
137
|
+
originalStderrWrite,
|
|
138
|
+
stdout: () => stdoutChunks.join(""),
|
|
139
|
+
stderr: () => stderrChunks.join(""),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function invokeWriteCallback(encoding, callback) {
|
|
144
|
+
if (typeof encoding === "function") {
|
|
145
|
+
encoding();
|
|
146
|
+
} else if (typeof callback === "function") {
|
|
147
|
+
callback();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function drainAsyncOutput() {
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
153
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function writeRunnerStdout(text) {
|
|
157
|
+
(activeOutputCapture?.originalStdoutWrite ?? process.stdout.write.bind(process.stdout))(text);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writeRunnerStderr(text) {
|
|
161
|
+
(activeOutputCapture?.originalStderrWrite ?? process.stderr.write.bind(process.stderr))(text);
|
|
162
|
+
}
|
|
@@ -71,13 +71,20 @@ export function renderRuntimeCaptureMarkdown(captureReport, options = {}) {
|
|
|
71
71
|
result.fixture,
|
|
72
72
|
result.status,
|
|
73
73
|
result.entrypoint,
|
|
74
|
-
(result.captured ?? []).map((item) => `${item.kind}:${item.name}`).join(", ") || result
|
|
74
|
+
(result.captured ?? []).map((item) => `${item.kind}:${item.name}`).join(", ") || formatCaptureError(result),
|
|
75
75
|
]),
|
|
76
76
|
["Fixture", "Status", "Entrypoint", "Captured"],
|
|
77
77
|
),
|
|
78
78
|
].join("\n");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function formatCaptureError(result) {
|
|
82
|
+
if (!result.error) {
|
|
83
|
+
return "-";
|
|
84
|
+
}
|
|
85
|
+
return result.failureClass ? `${result.failureClass}: ${result.error}` : result.error;
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
function captureTargets(fixture, rootDir) {
|
|
82
89
|
return fixture.packages.flatMap((packageSummary) => {
|
|
83
90
|
const packageRoot = path.dirname(path.resolve(rootDir, packageSummary.path));
|
|
@@ -124,6 +131,9 @@ async function captureTarget(target, options) {
|
|
|
124
131
|
packagePath: target.packagePath,
|
|
125
132
|
entrypoint: target.entrypoint.relativePath,
|
|
126
133
|
error: error.message,
|
|
134
|
+
...(error.failureClass ? { failureClass: error.failureClass } : {}),
|
|
135
|
+
...(error.missingExport ? { missingExport: error.missingExport } : {}),
|
|
136
|
+
...(error.missingModule ? { missingModule: error.missingModule } : {}),
|
|
127
137
|
captured: [],
|
|
128
138
|
};
|
|
129
139
|
}
|