@openclaw/plugin-inspector 0.0.0 → 0.1.0
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 +15 -0
- package/LICENSE +21 -0
- package/README.md +249 -2
- package/examples/github-actions-plugin-inspector.yml +24 -0
- package/examples/plugin-inspector.config.json +15 -0
- package/package.json +55 -3
- package/src/artifacts.js +113 -0
- package/src/capture-api.js +115 -0
- package/src/ci-policy.js +259 -0
- package/src/ci-summary.js +223 -0
- package/src/cli.js +130 -0
- package/src/cold-import-readiness.js +271 -0
- package/src/compatibility-report.js +356 -0
- package/src/config.js +182 -0
- package/src/contract-capture.js +288 -0
- package/src/contract-coverage.js +167 -0
- package/src/contract-probes.js +156 -0
- package/src/execution-results.js +297 -0
- package/src/fixture-summary.js +780 -0
- package/src/import-loop-profile.js +169 -0
- package/src/index.js +186 -0
- package/src/inspector.js +405 -0
- package/src/issues.js +366 -0
- package/src/json-file.js +10 -0
- package/src/mock-sdk-capture-runner.js +69 -0
- package/src/openclaw-target.js +179 -0
- package/src/path-utils.js +28 -0
- package/src/platform-probes.js +238 -0
- package/src/process-profile.js +117 -0
- package/src/profile-diff.js +222 -0
- package/src/ref-diff.js +335 -0
- package/src/report.js +435 -0
- package/src/runtime-capture-report.js +134 -0
- package/src/runtime-profile.js +289 -0
- package/src/sdk-mock.js +61 -0
- package/src/stats.js +13 -0
- package/src/synthetic-probes.js +544 -0
- package/src/workspace-plan.js +496 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
5
|
+
import { buildColdImportReadiness } from "./cold-import-readiness.js";
|
|
6
|
+
import { normalizeRepoPath, posixJoin, slugForArtifact } from "./path-utils.js";
|
|
7
|
+
|
|
8
|
+
export const defaultWorkspacePlanOptions = {
|
|
9
|
+
captureScript: "plugin-inspector-capture",
|
|
10
|
+
optInEnv: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1",
|
|
11
|
+
resultsRoot: ".plugin-inspector/results",
|
|
12
|
+
syntheticProbeScript: "plugin-inspector-synthetic-probes",
|
|
13
|
+
workspaceRoot: ".plugin-inspector/workspaces",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function buildWorkspacePlan(options = {}) {
|
|
17
|
+
const report = options.report;
|
|
18
|
+
if (!report) {
|
|
19
|
+
throw new TypeError("buildWorkspacePlan requires a compatibility report");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const settings = workspaceSettings(options);
|
|
23
|
+
const readiness = options.readiness ?? buildColdImportReadiness({ report, rootDir: settings.rootDir });
|
|
24
|
+
const packageByPath = new Map(
|
|
25
|
+
report.fixtures.flatMap((fixture) => (fixture.packages ?? []).map((packageSummary) => [packageSummary.path, packageSummary])),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const fixtures = [];
|
|
29
|
+
for (const fixtureReadiness of readiness.fixtures) {
|
|
30
|
+
const entries = [];
|
|
31
|
+
for (const entrypoint of fixtureReadiness.entrypoints) {
|
|
32
|
+
const packageSummary = packageByPath.get(entrypoint.packagePath);
|
|
33
|
+
if (!packageSummary) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const packageJson = await readPackageJson(settings.rootDir, packageSummary.path);
|
|
37
|
+
entries.push(
|
|
38
|
+
await buildEntrypointPlan({
|
|
39
|
+
entrypoint,
|
|
40
|
+
fixtureId: fixtureReadiness.id,
|
|
41
|
+
packageJson,
|
|
42
|
+
packageSummary,
|
|
43
|
+
settings,
|
|
44
|
+
targetOpenClawPath: report.targetOpenClaw.configuredPath,
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
fixtures.push({
|
|
49
|
+
id: fixtureReadiness.id,
|
|
50
|
+
entrypoints: entries,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const allEntries = fixtures.flatMap((fixture) => fixture.entrypoints);
|
|
55
|
+
const allSteps = allEntries.flatMap((entrypoint) => entrypoint.steps);
|
|
56
|
+
return {
|
|
57
|
+
generatedAt: report.generatedAt,
|
|
58
|
+
mode: "plan-only",
|
|
59
|
+
optIn: {
|
|
60
|
+
env: settings.optInEnv,
|
|
61
|
+
reason: "Dependency install, build scripts, and plugin import execution are intentionally outside default CI.",
|
|
62
|
+
},
|
|
63
|
+
targetOpenClaw: {
|
|
64
|
+
status: report.targetOpenClaw.status,
|
|
65
|
+
configuredPath: report.targetOpenClaw.configuredPath,
|
|
66
|
+
},
|
|
67
|
+
summary: {
|
|
68
|
+
fixtureCount: fixtures.length,
|
|
69
|
+
entrypointCount: allEntries.length,
|
|
70
|
+
installStepCount: allSteps.filter((step) => step.kind === "install").length,
|
|
71
|
+
auditStepCount: allSteps.filter((step) => step.kind === "audit").length,
|
|
72
|
+
buildStepCount: allSteps.filter((step) => step.kind === "build").length,
|
|
73
|
+
artifactStepCount: allSteps.filter((step) => step.kind === "prepare-artifacts").length,
|
|
74
|
+
captureStepCount: allSteps.filter((step) => step.kind === "capture").length,
|
|
75
|
+
syntheticProbeStepCount: allSteps.filter((step) => step.kind === "synthetic-probe").length,
|
|
76
|
+
targetOpenClawLinkStepCount: allSteps.filter((step) => step.kind === "link-openclaw").length,
|
|
77
|
+
tsLoaderEntrypointCount: allEntries.filter((entrypoint) =>
|
|
78
|
+
entrypoint.requiredCapabilities.includes("ts-loader"),
|
|
79
|
+
).length,
|
|
80
|
+
jitiAlternativeCount: allEntries.filter((entrypoint) =>
|
|
81
|
+
entrypoint.loaderStrategy.alternatives.includes("jiti"),
|
|
82
|
+
).length,
|
|
83
|
+
missingBuildScriptCount: allEntries.filter((entrypoint) =>
|
|
84
|
+
entrypoint.blockers.some((blocker) => blocker.code === "missing-build-script"),
|
|
85
|
+
).length,
|
|
86
|
+
sdkAliasRequiredCount: allEntries.filter((entrypoint) =>
|
|
87
|
+
entrypoint.requiredCapabilities.includes("sdk-alias-compat"),
|
|
88
|
+
).length,
|
|
89
|
+
},
|
|
90
|
+
fixtures,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function validateWorkspacePlan(plan, options = {}) {
|
|
95
|
+
const settings = workspaceSettings(options);
|
|
96
|
+
const errors = [];
|
|
97
|
+
if (plan.mode !== "plan-only") {
|
|
98
|
+
errors.push("workspace plan must stay plan-only for default checks");
|
|
99
|
+
}
|
|
100
|
+
if (plan.optIn.env !== settings.optInEnv) {
|
|
101
|
+
errors.push(`workspace execution must require ${settings.optInEnv}`);
|
|
102
|
+
}
|
|
103
|
+
for (const fixture of plan.fixtures) {
|
|
104
|
+
for (const entrypoint of fixture.entrypoints) {
|
|
105
|
+
if (!entrypoint.packagePath || !entrypoint.entrypoint) {
|
|
106
|
+
errors.push(`${entrypoint.id}: missing package path or entrypoint`);
|
|
107
|
+
}
|
|
108
|
+
if (entrypoint.steps.length === 0) {
|
|
109
|
+
errors.push(`${entrypoint.id}: missing workspace steps`);
|
|
110
|
+
}
|
|
111
|
+
if (!entrypoint.loaderStrategy?.primary || !entrypoint.loaderStrategy.reason) {
|
|
112
|
+
errors.push(`${entrypoint.id}: missing loader strategy`);
|
|
113
|
+
}
|
|
114
|
+
if (
|
|
115
|
+
entrypoint.requiredCapabilities.includes("ts-loader") &&
|
|
116
|
+
!entrypoint.loaderStrategy?.alternatives?.includes("jiti")
|
|
117
|
+
) {
|
|
118
|
+
errors.push(`${entrypoint.id}: ts-loader capability must track a jiti fallback`);
|
|
119
|
+
}
|
|
120
|
+
if (!entrypoint.steps.some((step) => step.kind === "prepare")) {
|
|
121
|
+
errors.push(`${entrypoint.id}: missing prepare step`);
|
|
122
|
+
}
|
|
123
|
+
if (!entrypoint.steps.some((step) => step.kind === "prepare-artifacts")) {
|
|
124
|
+
errors.push(`${entrypoint.id}: missing prepare-artifacts step`);
|
|
125
|
+
}
|
|
126
|
+
if (!entrypoint.steps.some((step) => step.kind === "capture")) {
|
|
127
|
+
errors.push(`${entrypoint.id}: missing capture step`);
|
|
128
|
+
}
|
|
129
|
+
if (!entrypoint.steps.some((step) => step.kind === "synthetic-probe")) {
|
|
130
|
+
errors.push(`${entrypoint.id}: missing synthetic-probe step`);
|
|
131
|
+
}
|
|
132
|
+
if (entrypoint.requiredCapabilities.includes("dependency-install") && !entrypoint.steps.some((step) => step.kind === "install")) {
|
|
133
|
+
errors.push(`${entrypoint.id}: dependency install capability has no install step`);
|
|
134
|
+
}
|
|
135
|
+
if (entrypoint.requiredCapabilities.includes("dependency-install") && !entrypoint.steps.some((step) => step.kind === "audit")) {
|
|
136
|
+
errors.push(`${entrypoint.id}: dependency install capability has no audit step`);
|
|
137
|
+
}
|
|
138
|
+
if (entrypoint.requiredCapabilities.includes("target-openclaw-link") && !entrypoint.steps.some((step) => step.kind === "link-openclaw")) {
|
|
139
|
+
errors.push(`${entrypoint.id}: target-openclaw-link capability has no link-openclaw step`);
|
|
140
|
+
}
|
|
141
|
+
if (entrypoint.requiredCapabilities.includes("build") && !entrypoint.steps.some((step) => step.kind === "build") && !entrypoint.blockers.some((blocker) => blocker.code === "missing-build-script")) {
|
|
142
|
+
errors.push(`${entrypoint.id}: build capability has no build step or missing-build-script blocker`);
|
|
143
|
+
}
|
|
144
|
+
for (const step of entrypoint.steps) {
|
|
145
|
+
if (!step.command || !step.cwd || !step.reason) {
|
|
146
|
+
errors.push(`${entrypoint.id}: ${step.kind} step missing command, cwd, or reason`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return errors;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function writeWorkspacePlan(plan, options = {}) {
|
|
155
|
+
return writeJsonMarkdownArtifacts({
|
|
156
|
+
jsonPath: options.jsonPath,
|
|
157
|
+
markdownPath: options.markdownPath,
|
|
158
|
+
json: plan,
|
|
159
|
+
markdown: renderWorkspacePlanMarkdown(plan, options),
|
|
160
|
+
check: options.check,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function renderWorkspacePlanMarkdown(plan, options = {}) {
|
|
165
|
+
return [
|
|
166
|
+
`# ${options.title ?? "Plugin Inspector Isolated Workspace Plan"}`,
|
|
167
|
+
"",
|
|
168
|
+
`Generated: ${plan.generatedAt}`,
|
|
169
|
+
`Mode: ${plan.mode}`,
|
|
170
|
+
`Opt-in: ${plan.optIn.env}`,
|
|
171
|
+
"",
|
|
172
|
+
"## Summary",
|
|
173
|
+
"",
|
|
174
|
+
markdownTable(
|
|
175
|
+
[
|
|
176
|
+
["Fixtures", plan.summary.fixtureCount],
|
|
177
|
+
["Entrypoints", plan.summary.entrypointCount],
|
|
178
|
+
["Artifact dirs", plan.summary.artifactStepCount],
|
|
179
|
+
["Install steps", plan.summary.installStepCount],
|
|
180
|
+
["Audit steps", plan.summary.auditStepCount],
|
|
181
|
+
["Build steps", plan.summary.buildStepCount],
|
|
182
|
+
["Capture steps", plan.summary.captureStepCount],
|
|
183
|
+
["Synthetic probe steps", plan.summary.syntheticProbeStepCount],
|
|
184
|
+
["Target OpenClaw link steps", plan.summary.targetOpenClawLinkStepCount],
|
|
185
|
+
["TypeScript loader entrypoints", plan.summary.tsLoaderEntrypointCount],
|
|
186
|
+
["Jiti fallback candidates", plan.summary.jitiAlternativeCount],
|
|
187
|
+
["Missing build scripts", plan.summary.missingBuildScriptCount],
|
|
188
|
+
["SDK alias required", plan.summary.sdkAliasRequiredCount],
|
|
189
|
+
],
|
|
190
|
+
["Metric", "Value"],
|
|
191
|
+
),
|
|
192
|
+
"",
|
|
193
|
+
"## Entrypoint Workspaces",
|
|
194
|
+
"",
|
|
195
|
+
markdownTable(
|
|
196
|
+
plan.fixtures.flatMap((fixture) =>
|
|
197
|
+
fixture.entrypoints.map((entrypoint) => [
|
|
198
|
+
fixture.id,
|
|
199
|
+
entrypoint.packageManager,
|
|
200
|
+
entrypoint.status,
|
|
201
|
+
`${entrypoint.loaderStrategy.primary}${entrypoint.loaderStrategy.alternatives.length > 0 ? ` (+${entrypoint.loaderStrategy.alternatives.join(", ")})` : ""}`,
|
|
202
|
+
entrypoint.entrypoint,
|
|
203
|
+
entrypoint.requiredCapabilities.join(", "),
|
|
204
|
+
entrypoint.steps
|
|
205
|
+
.map((step) => `${step.kind}: ${step.command}${step.artifactPath ? ` -> ${step.artifactPath}` : ""}`)
|
|
206
|
+
.join("; "),
|
|
207
|
+
]),
|
|
208
|
+
),
|
|
209
|
+
["Fixture", "PM", "Status", "Loader", "Entrypoint", "Capabilities", "Steps"],
|
|
210
|
+
),
|
|
211
|
+
].join("\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function buildEntrypointPlan({ fixtureId, entrypoint, packageSummary, packageJson, settings, targetOpenClawPath }) {
|
|
215
|
+
const packagePath = normalizeRepoPath(packageSummary.path);
|
|
216
|
+
const packageDir = path.posix.dirname(packagePath);
|
|
217
|
+
const packageManager = detectPackageManager(settings.rootDir, packageDir, packageJson);
|
|
218
|
+
const lockfile = findNearestLockfile(settings.rootDir, packageDir);
|
|
219
|
+
const buildScript = packageJson.scripts?.build;
|
|
220
|
+
const requiredCapabilities = requiredCapabilitiesFor(entrypoint);
|
|
221
|
+
const loaderStrategy = loaderStrategyFor(entrypoint);
|
|
222
|
+
const blockers = [...entrypoint.blockers];
|
|
223
|
+
const workspacePath = posixJoin(settings.workspaceRoot, fixtureId);
|
|
224
|
+
const resultPath = posixJoin(settings.resultsRoot, fixtureId);
|
|
225
|
+
const steps = [];
|
|
226
|
+
|
|
227
|
+
steps.push({
|
|
228
|
+
kind: "prepare",
|
|
229
|
+
command: `mkdir -p ${workspacePath} && rsync -a --delete ${packageDir}/ ${workspacePath}/`,
|
|
230
|
+
cwd: repoRelative("."),
|
|
231
|
+
reason: "copy fixture package into an isolated mutable workspace",
|
|
232
|
+
});
|
|
233
|
+
steps.push({
|
|
234
|
+
kind: "prepare-artifacts",
|
|
235
|
+
command: `mkdir -p ${resultPath}`,
|
|
236
|
+
cwd: repoRelative("."),
|
|
237
|
+
reason: "create a stable result directory for capture and synthetic probe artifacts",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (requiredCapabilities.includes("target-openclaw-link")) {
|
|
241
|
+
steps.push({
|
|
242
|
+
kind: "link-openclaw",
|
|
243
|
+
command: `${packageManager} pkg set dependencies.openclaw="file:${targetOpenClawWorkspacePath(settings, fixtureId, targetOpenClawPath)}"`,
|
|
244
|
+
cwd: workspacePath,
|
|
245
|
+
reason: "link the plugin's openclaw peer dependency to the target checkout under test",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (requiredCapabilities.includes("dependency-install")) {
|
|
250
|
+
steps.push({
|
|
251
|
+
kind: "install",
|
|
252
|
+
command: installCommand(packageManager),
|
|
253
|
+
cwd: workspacePath,
|
|
254
|
+
reason: "install runtime dependencies without mutating the pinned submodule",
|
|
255
|
+
});
|
|
256
|
+
steps.push({
|
|
257
|
+
kind: "audit",
|
|
258
|
+
command: auditCommand(settings, packageManager, fixtureId, workspacePath),
|
|
259
|
+
cwd: workspacePath,
|
|
260
|
+
artifactPath: auditArtifactPath(settings, fixtureId),
|
|
261
|
+
reason: "capture package-manager dependency audit metadata as warning-only plugin upstream risk",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (requiredCapabilities.includes("build")) {
|
|
266
|
+
if (buildScript) {
|
|
267
|
+
steps.push({
|
|
268
|
+
kind: "build",
|
|
269
|
+
command: runCommand(packageManager, "build"),
|
|
270
|
+
cwd: workspacePath,
|
|
271
|
+
reason: "produce missing OpenClaw build entrypoint",
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
blockers.push({
|
|
275
|
+
code: "missing-build-script",
|
|
276
|
+
message: "entrypoint points at build output but package.json has no build script",
|
|
277
|
+
evidence: packagePath,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
steps.push({
|
|
283
|
+
kind: "capture",
|
|
284
|
+
command: captureCommand(settings, fixtureId, entrypoint, workspacePath),
|
|
285
|
+
cwd: workspacePath,
|
|
286
|
+
artifactPath: artifactPath(settings, fixtureId, entrypoint, "capture"),
|
|
287
|
+
reason: "cold import the entrypoint against the capture shim",
|
|
288
|
+
});
|
|
289
|
+
steps.push({
|
|
290
|
+
kind: "synthetic-probe",
|
|
291
|
+
command: syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath),
|
|
292
|
+
cwd: workspacePath,
|
|
293
|
+
artifactPath: artifactPath(settings, fixtureId, entrypoint, "synthetic"),
|
|
294
|
+
reason: "invoke retained hook and registration handlers with synthetic payloads",
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
id: entrypoint.id,
|
|
299
|
+
fixture: fixtureId,
|
|
300
|
+
packagePath,
|
|
301
|
+
packageName: packageSummary.name,
|
|
302
|
+
entrypoint: entrypoint.path,
|
|
303
|
+
status: entrypoint.status,
|
|
304
|
+
packageManager,
|
|
305
|
+
lockfile,
|
|
306
|
+
loaderStrategy,
|
|
307
|
+
requiredCapabilities,
|
|
308
|
+
blockers,
|
|
309
|
+
steps,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function workspaceSettings(options) {
|
|
314
|
+
return {
|
|
315
|
+
captureScript: options.captureScript ?? defaultWorkspacePlanOptions.captureScript,
|
|
316
|
+
defaultTargetOpenClawWorkspacePath: options.defaultTargetOpenClawWorkspacePath ?? "../../../openclaw",
|
|
317
|
+
optInEnv: options.optInEnv ?? defaultWorkspacePlanOptions.optInEnv,
|
|
318
|
+
resultsRoot: repoRelative(options.resultsRoot ?? defaultWorkspacePlanOptions.resultsRoot),
|
|
319
|
+
rootDir: path.resolve(options.rootDir ?? process.cwd()),
|
|
320
|
+
syntheticProbeScript: options.syntheticProbeScript ?? defaultWorkspacePlanOptions.syntheticProbeScript,
|
|
321
|
+
workspaceRoot: repoRelative(options.workspaceRoot ?? defaultWorkspacePlanOptions.workspaceRoot),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function loaderStrategyFor(entrypoint) {
|
|
326
|
+
const needsTypeScriptLoader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required");
|
|
327
|
+
if (!needsTypeScriptLoader) {
|
|
328
|
+
return {
|
|
329
|
+
source: "native-node",
|
|
330
|
+
primary: "node",
|
|
331
|
+
alternatives: [],
|
|
332
|
+
reason: "entrypoint extension can be loaded by Node without a TypeScript source loader",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
source: "typescript-source",
|
|
338
|
+
primary: "tsx",
|
|
339
|
+
alternatives: ["jiti"],
|
|
340
|
+
reason: "TypeScript entrypoints are currently planned with tsx and tracked with a Jiti-compatible fallback for OpenClaw loader parity.",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function requiredCapabilitiesFor(entrypoint) {
|
|
345
|
+
const capabilities = new Set();
|
|
346
|
+
for (const blocker of entrypoint.blockers) {
|
|
347
|
+
if (blocker.code === "dependency-install-required") {
|
|
348
|
+
capabilities.add("dependency-install");
|
|
349
|
+
}
|
|
350
|
+
if (blocker.code === "build-required") {
|
|
351
|
+
capabilities.add("build");
|
|
352
|
+
}
|
|
353
|
+
if (blocker.code === "ts-loader-required") {
|
|
354
|
+
capabilities.add("ts-loader");
|
|
355
|
+
}
|
|
356
|
+
if (blocker.code === "sdk-alias-required") {
|
|
357
|
+
capabilities.add("sdk-alias-compat");
|
|
358
|
+
}
|
|
359
|
+
if (blocker.code === "top-level-side-effect-review") {
|
|
360
|
+
capabilities.add("side-effect-sandbox");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (entrypoint.blockers.some((blocker) => /\bopenclaw\b/.test(blocker.evidence ?? ""))) {
|
|
364
|
+
capabilities.add("target-openclaw-link");
|
|
365
|
+
}
|
|
366
|
+
capabilities.add("capture-shim");
|
|
367
|
+
capabilities.add("synthetic-probes");
|
|
368
|
+
return [...capabilities].sort();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function detectPackageManager(rootDir, packageDir, packageJson) {
|
|
372
|
+
const declared = typeof packageJson.packageManager === "string" ? packageJson.packageManager.split("@")[0] : null;
|
|
373
|
+
if (declared) {
|
|
374
|
+
return declared;
|
|
375
|
+
}
|
|
376
|
+
const lockfile = findNearestLockfile(rootDir, packageDir);
|
|
377
|
+
if (lockfile?.endsWith("pnpm-lock.yaml")) {
|
|
378
|
+
return "pnpm";
|
|
379
|
+
}
|
|
380
|
+
if (lockfile?.endsWith("yarn.lock")) {
|
|
381
|
+
return "yarn";
|
|
382
|
+
}
|
|
383
|
+
if (lockfile?.endsWith("bun.lock") || lockfile?.endsWith("bun.lockb")) {
|
|
384
|
+
return "bun";
|
|
385
|
+
}
|
|
386
|
+
return "npm";
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function findNearestLockfile(rootDir, packageDir) {
|
|
390
|
+
const candidates = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
391
|
+
let current = path.resolve(rootDir, packageDir);
|
|
392
|
+
while (isWithinPath(rootDir, current)) {
|
|
393
|
+
for (const candidate of candidates) {
|
|
394
|
+
const lockfile = path.join(current, candidate);
|
|
395
|
+
if (existsSync(lockfile)) {
|
|
396
|
+
return repoRelative(path.relative(rootDir, lockfile));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const parent = path.dirname(current);
|
|
400
|
+
if (parent === current) {
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
current = parent;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isWithinPath(rootDir, candidatePath) {
|
|
409
|
+
const relative = path.relative(rootDir, candidatePath);
|
|
410
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function readPackageJson(rootDir, packagePath) {
|
|
414
|
+
return JSON.parse(await readFile(path.join(rootDir, ...normalizeRepoPath(packagePath).split("/")), "utf8"));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function installCommand(packageManager) {
|
|
418
|
+
const commands = {
|
|
419
|
+
bun: "bun install --ignore-scripts",
|
|
420
|
+
npm: "npm install --ignore-scripts",
|
|
421
|
+
pnpm: "pnpm install --ignore-scripts",
|
|
422
|
+
yarn: "yarn install --ignore-scripts",
|
|
423
|
+
};
|
|
424
|
+
return commands[packageManager] ?? `${packageManager} install --ignore-scripts`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function auditCommand(settings, packageManager, fixtureId, workspacePath) {
|
|
428
|
+
const output = workspaceRelativeArtifactPath(settings, fixtureId, workspacePath, "package-audit.json");
|
|
429
|
+
if (packageManager === "npm") {
|
|
430
|
+
return `npm audit --json > ${output} || true`;
|
|
431
|
+
}
|
|
432
|
+
if (packageManager === "pnpm") {
|
|
433
|
+
return `pnpm audit --json > ${output} || true`;
|
|
434
|
+
}
|
|
435
|
+
if (packageManager === "yarn") {
|
|
436
|
+
return `yarn npm audit --json > ${output} || true`;
|
|
437
|
+
}
|
|
438
|
+
if (packageManager === "bun") {
|
|
439
|
+
return `bun audit --json > ${output} || true`;
|
|
440
|
+
}
|
|
441
|
+
return `${packageManager} audit --json > ${output} || true`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function runCommand(packageManager, script) {
|
|
445
|
+
if (packageManager === "npm") {
|
|
446
|
+
return `npm run ${script}`;
|
|
447
|
+
}
|
|
448
|
+
return `${packageManager} run ${script}`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function captureCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
452
|
+
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
453
|
+
return `${settings.optInEnv} node${loader} ${settings.captureScript} ${entrypoint.specifier} --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
457
|
+
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
458
|
+
return `${settings.optInEnv} node${loader} ${settings.syntheticProbeScript} --entrypoint ${entrypoint.specifier} --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function targetOpenClawWorkspacePath(settings, fixtureId, targetOpenClawPath) {
|
|
462
|
+
if (!targetOpenClawPath) {
|
|
463
|
+
return settings.defaultTargetOpenClawWorkspacePath;
|
|
464
|
+
}
|
|
465
|
+
const workspacePath = path.join(settings.rootDir, settings.workspaceRoot, fixtureId);
|
|
466
|
+
return repoRelative(path.relative(workspacePath, path.resolve(settings.rootDir, targetOpenClawPath)));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function repoRelative(value) {
|
|
470
|
+
return String(value).replaceAll(path.sep, "/");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function artifactPath(settings, fixtureId, entrypoint, kind) {
|
|
474
|
+
return posixJoin(settings.resultsRoot, fixtureId, `${slugForArtifact(entrypoint.id)}.${kind}.json`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, kind) {
|
|
478
|
+
return workspaceRelativeArtifactPath(
|
|
479
|
+
settings,
|
|
480
|
+
fixtureId,
|
|
481
|
+
workspacePath,
|
|
482
|
+
`${slugForArtifact(entrypoint.id)}.${kind}.json`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function workspaceRelativeArtifactPath(settings, fixtureId, workspacePath, fileName) {
|
|
487
|
+
return repoRelative(path.posix.relative(workspacePath, posixJoin(settings.resultsRoot, fixtureId, fileName)));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function auditArtifactPath(settings, fixtureId) {
|
|
491
|
+
return posixJoin(settings.resultsRoot, fixtureId, "package-audit.json");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function markdownTable(rows, headers) {
|
|
495
|
+
return renderPaddedMarkdownTable(rows, headers);
|
|
496
|
+
}
|