@openclaw/plugin-inspector 0.2.0 → 0.3.1
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 +38 -0
- package/README.md +66 -5
- package/docs/plugin-inspector-banner.jpg +0 -0
- package/examples/circleci-plugin-inspector.yml +19 -0
- package/examples/github-actions-code-scanning.yml +31 -0
- package/examples/github-actions-plugin-inspector.yml +1 -1
- package/examples/gitlab-ci-plugin-inspector.yml +11 -0
- package/examples/package-json-plugin-inspector.json +1 -1
- package/package.json +10 -2
- package/src/advanced.js +10 -0
- package/src/api.js +169 -3
- package/src/capture-api.js +41 -3
- package/src/cli.js +48 -15
- package/src/index.js +227 -0
- package/src/init.js +90 -13
- package/src/mock-sdk-capture-runner.js +12 -8
- package/src/report.js +60 -8
- package/src/sdk-mock.js +202 -9
- package/src/synthetic-probe-suite.js +19 -0
- package/src/synthetic-probes-cli.js +16 -12
- package/src/synthetic-probes.js +55 -0
package/src/cli.js
CHANGED
|
@@ -72,8 +72,17 @@ async function runCheck(commandArgs) {
|
|
|
72
72
|
const json = commandArgs.includes("--json");
|
|
73
73
|
const capture = readRuntimeFlag(commandArgs);
|
|
74
74
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
75
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
75
76
|
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
76
|
-
const { report, paths } = await runPluginCheck({
|
|
77
|
+
const { report, paths } = await runPluginCheck({
|
|
78
|
+
allowExecution,
|
|
79
|
+
capture,
|
|
80
|
+
configPath,
|
|
81
|
+
mockSdk,
|
|
82
|
+
openclawPath,
|
|
83
|
+
outDir,
|
|
84
|
+
pluginRoot,
|
|
85
|
+
});
|
|
77
86
|
await writeCiOutputArtifacts(report, {
|
|
78
87
|
...ciOutputs,
|
|
79
88
|
cwd: path.dirname(paths.jsonPath),
|
|
@@ -83,7 +92,7 @@ async function runCheck(commandArgs) {
|
|
|
83
92
|
if (json) {
|
|
84
93
|
console.log(JSON.stringify(report, null, 2));
|
|
85
94
|
} else {
|
|
86
|
-
console.log(renderTextSummary(report));
|
|
95
|
+
console.log(renderTextSummary(report, { artifacts: paths }));
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
if (report.status !== "pass") {
|
|
@@ -95,19 +104,27 @@ async function runInit(commandArgs) {
|
|
|
95
104
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
96
105
|
const configPath = readFlag(commandArgs, "--config") ?? undefined;
|
|
97
106
|
const workflowPath = readFlag(commandArgs, "--workflow") ?? undefined;
|
|
98
|
-
const packageManager = readFlag(commandArgs, "--package-manager") ??
|
|
107
|
+
const packageManager = readFlag(commandArgs, "--package-manager") ?? undefined;
|
|
99
108
|
const result = await writePluginInspectorInit({
|
|
100
109
|
pluginRoot,
|
|
101
110
|
configPath,
|
|
102
111
|
workflowPath,
|
|
103
112
|
packageManager,
|
|
104
113
|
ci: commandArgs.includes("--ci"),
|
|
114
|
+
dryRun: commandArgs.includes("--dry-run"),
|
|
115
|
+
scripts: commandArgs.includes("--scripts"),
|
|
105
116
|
force: commandArgs.includes("--force"),
|
|
106
117
|
});
|
|
107
118
|
|
|
119
|
+
if (commandArgs.includes("--json")) {
|
|
120
|
+
console.log(JSON.stringify(initCommandSummary(result), null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
for (const filePath of result.written) {
|
|
109
|
-
console.log(
|
|
125
|
+
console.log(`${result.dryRun ? "would write" : "wrote"} ${path.relative(result.pluginRoot, filePath)}`);
|
|
110
126
|
}
|
|
127
|
+
console.log(`package manager: ${result.packageManager}`);
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
async function runReport(command, commandArgs) {
|
|
@@ -128,7 +145,7 @@ async function runReport(command, commandArgs) {
|
|
|
128
145
|
if (json) {
|
|
129
146
|
console.log(JSON.stringify(report, null, 2));
|
|
130
147
|
} else {
|
|
131
|
-
console.log(renderTextSummary(report));
|
|
148
|
+
console.log(renderTextSummary(report, { artifacts: paths }));
|
|
132
149
|
}
|
|
133
150
|
|
|
134
151
|
if (check && report.status !== "pass") {
|
|
@@ -144,8 +161,10 @@ async function runCi(commandArgs) {
|
|
|
144
161
|
const json = commandArgs.includes("--json");
|
|
145
162
|
const capture = readRuntimeFlag(commandArgs);
|
|
146
163
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
164
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
147
165
|
const ciOutputs = readCiOutputFlags(commandArgs, { defaultEnabled: true });
|
|
148
166
|
const { report, reportDir } = await runCiCompatibilityReport({
|
|
167
|
+
allowExecution,
|
|
149
168
|
capture,
|
|
150
169
|
configPath,
|
|
151
170
|
mockSdk,
|
|
@@ -184,7 +203,7 @@ async function runCi(commandArgs) {
|
|
|
184
203
|
}
|
|
185
204
|
}
|
|
186
205
|
|
|
187
|
-
async function runCiCompatibilityReport({ capture, configPath, mockSdk, openclawPath, outDir, pluginRoot }) {
|
|
206
|
+
async function runCiCompatibilityReport({ allowExecution, capture, configPath, mockSdk, openclawPath, outDir, pluginRoot }) {
|
|
188
207
|
if (configPath) {
|
|
189
208
|
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
190
209
|
const report = await inspectCompatibilityFixtureSet(config, { openclawPath });
|
|
@@ -195,7 +214,7 @@ async function runCiCompatibilityReport({ capture, configPath, mockSdk, openclaw
|
|
|
195
214
|
};
|
|
196
215
|
}
|
|
197
216
|
|
|
198
|
-
const { report } = await runPluginCheck({
|
|
217
|
+
const { report } = await runPluginCheck({ allowExecution, capture, mockSdk, openclawPath, outDir, pluginRoot });
|
|
199
218
|
return {
|
|
200
219
|
report,
|
|
201
220
|
reportDir: path.resolve(pluginRoot ?? process.cwd(), outDir),
|
|
@@ -207,11 +226,12 @@ async function runCapture(commandArgs) {
|
|
|
207
226
|
const outputPath = readFlag(commandArgs, "--output");
|
|
208
227
|
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
209
228
|
const mockSdk = readMockSdkFlag(commandArgs) ?? commandArgs.includes("--mock-sdk");
|
|
229
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
210
230
|
if (!entrypoint) {
|
|
211
231
|
throw new Error("capture requires an entrypoint path");
|
|
212
232
|
}
|
|
213
|
-
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
214
|
-
throw new Error("capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
233
|
+
if (!allowExecution && process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
234
|
+
throw new Error("capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 or --allow-execute in an isolated workspace");
|
|
215
235
|
}
|
|
216
236
|
|
|
217
237
|
const result = await captureEntrypoint(entrypoint, { mockSdk, pluginRoot });
|
|
@@ -281,6 +301,10 @@ function readMockSdkFlag(commandArgs) {
|
|
|
281
301
|
return undefined;
|
|
282
302
|
}
|
|
283
303
|
|
|
304
|
+
function readAllowExecutionFlag(commandArgs) {
|
|
305
|
+
return commandArgs.includes("--allow-execute");
|
|
306
|
+
}
|
|
307
|
+
|
|
284
308
|
function renderCiTextSummary(summary) {
|
|
285
309
|
return [
|
|
286
310
|
`Status: ${summary.status.toUpperCase()}`,
|
|
@@ -290,6 +314,15 @@ function renderCiTextSummary(summary) {
|
|
|
290
314
|
].join("\n");
|
|
291
315
|
}
|
|
292
316
|
|
|
317
|
+
function initCommandSummary(result) {
|
|
318
|
+
return {
|
|
319
|
+
dryRun: result.dryRun,
|
|
320
|
+
packageManager: result.packageManager,
|
|
321
|
+
pluginRoot: result.pluginRoot,
|
|
322
|
+
files: result.written.map((filePath) => path.relative(result.pluginRoot, filePath)),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
293
326
|
function renderConfigTextSummary(config) {
|
|
294
327
|
const fixture = config.fixtures[0];
|
|
295
328
|
return [
|
|
@@ -308,16 +341,16 @@ function printHelp() {
|
|
|
308
341
|
|
|
309
342
|
Usage:
|
|
310
343
|
plugin-inspector
|
|
311
|
-
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json]
|
|
344
|
+
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--json]
|
|
312
345
|
plugin-inspector config [--plugin-root <path>] [--config <path>] [--json]
|
|
313
|
-
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--package-manager npm|pnpm|yarn|bun] [--force]
|
|
346
|
+
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--scripts] [--package-manager npm|pnpm|yarn|bun] [--dry-run] [--json] [--force]
|
|
314
347
|
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
315
|
-
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--check] [--json] [--sarif [path]] [--junit [path]]
|
|
316
|
-
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json] [--no-sarif] [--no-junit]
|
|
317
|
-
|
|
348
|
+
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--check] [--json] [--sarif [path]] [--junit [path]] [--allow-execute]
|
|
349
|
+
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--json] [--no-sarif] [--no-junit]
|
|
350
|
+
plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--allow-execute] [--plugin-root <path>] [--output <path>]
|
|
318
351
|
|
|
319
352
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
320
353
|
CI writes SARIF and JUnit artifacts by default; check/inspect can write them with --sarif and --junit.
|
|
321
|
-
Runtime capture is opt-in because it imports plugin code; use --runtime with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
354
|
+
Runtime capture is opt-in because it imports plugin code; use --runtime with --allow-execute or PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
322
355
|
`);
|
|
323
356
|
}
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,239 @@
|
|
|
1
|
+
import * as pluginApi from "./api.js";
|
|
2
|
+
import * as ciPolicyApi from "./ci-policy.js";
|
|
3
|
+
import * as ciSummaryApi from "./ci-summary.js";
|
|
4
|
+
import * as configApi from "./config.js";
|
|
5
|
+
import * as contractCaptureApi from "./contract-capture.js";
|
|
6
|
+
import * as contractCoverageApi from "./contract-coverage.js";
|
|
7
|
+
import * as executionResultsApi from "./execution-results.js";
|
|
8
|
+
import * as importLoopProfileApi from "./import-loop-profile.js";
|
|
9
|
+
import * as inspectorApi from "./inspector.js";
|
|
10
|
+
import * as issuesApi from "./issues.js";
|
|
11
|
+
import * as openClawTargetApi from "./openclaw-target.js";
|
|
12
|
+
import * as profileDiffApi from "./profile-diff.js";
|
|
13
|
+
import * as refDiffApi from "./ref-diff.js";
|
|
14
|
+
import * as reportApi from "./report.js";
|
|
15
|
+
import * as runtimeProfileApi from "./runtime-profile.js";
|
|
16
|
+
import * as syntheticProbeSuiteApi from "./synthetic-probe-suite.js";
|
|
17
|
+
import * as syntheticProbesApi from "./synthetic-probes.js";
|
|
18
|
+
|
|
19
|
+
export const pluginRoot = Object.freeze({
|
|
20
|
+
loadConfig: pluginApi.loadPluginConfig,
|
|
21
|
+
inspect: pluginApi.inspectPluginRoot,
|
|
22
|
+
runCheck: pluginApi.runPluginCheck,
|
|
23
|
+
captureEntrypoint: pluginApi.capturePluginEntrypoint,
|
|
24
|
+
setup: pluginApi.setupPluginInspector,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const fixtureSuites = Object.freeze({
|
|
28
|
+
loadConfig: configApi.loadInspectorConfig,
|
|
29
|
+
inspect: pluginApi.inspectCompatibilityFixtureSetConfig,
|
|
30
|
+
inspectStatic: pluginApi.inspectFixtureSetConfig,
|
|
31
|
+
runReport: pluginApi.runFixtureSetReport,
|
|
32
|
+
writeReports: pluginApi.writeFixtureSetReports,
|
|
33
|
+
renderReport: pluginApi.renderFixtureSetMarkdownReport,
|
|
34
|
+
renderIssues: pluginApi.renderFixtureSetIssuesReport,
|
|
35
|
+
buildColdImportReadiness: pluginApi.buildFixtureSetColdImportReadiness,
|
|
36
|
+
runColdImportReadiness: pluginApi.runFixtureSetColdImportReadiness,
|
|
37
|
+
writeColdImportReadiness: pluginApi.writeFixtureSetColdImportReadiness,
|
|
38
|
+
buildWorkspacePlan: pluginApi.buildFixtureSetWorkspacePlan,
|
|
39
|
+
runWorkspacePlan: pluginApi.runFixtureSetWorkspacePlan,
|
|
40
|
+
writeWorkspacePlan: pluginApi.writeFixtureSetWorkspacePlan,
|
|
41
|
+
buildPlatformProbes: pluginApi.buildFixtureSetPlatformProbes,
|
|
42
|
+
runPlatformProbes: pluginApi.runFixtureSetPlatformProbes,
|
|
43
|
+
writePlatformProbes: pluginApi.writeFixtureSetPlatformProbes,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const staticInspection = Object.freeze({
|
|
47
|
+
loadConfig: configApi.loadInspectorConfig,
|
|
48
|
+
inspectSourceText: inspectorApi.inspectSourceText,
|
|
49
|
+
inspectPlugin: inspectorApi.inspectPlugin,
|
|
50
|
+
inspectFixtureSet: inspectorApi.inspectFixtureSet,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const reports = Object.freeze({
|
|
54
|
+
renderMarkdown: reportApi.renderMarkdownReport,
|
|
55
|
+
renderTextSummary: pluginApi.renderTextSummary,
|
|
56
|
+
write: reportApi.writeReport,
|
|
57
|
+
issueId: issuesApi.issueId,
|
|
58
|
+
classifyIssueFinding: issuesApi.classifyIssueFinding,
|
|
59
|
+
knownIssueCodes: issuesApi.knownIssueCodes,
|
|
60
|
+
openClawTargetPathCandidates: openClawTargetApi.openClawTargetPathCandidates,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const contracts = Object.freeze({
|
|
64
|
+
buildCapture: contractCaptureApi.buildContractCapture,
|
|
65
|
+
writeCapture: contractCaptureApi.writeContractCapture,
|
|
66
|
+
renderCapture: contractCaptureApi.renderContractCaptureMarkdown,
|
|
67
|
+
validateCapture: contractCaptureApi.validateContractCapture,
|
|
68
|
+
validateCoverage: contractCoverageApi.validateContractCoverage,
|
|
69
|
+
defaults: Object.freeze({
|
|
70
|
+
registrationAssertions: contractCaptureApi.defaultRegistrationAssertions,
|
|
71
|
+
registrationArguments: contractCaptureApi.defaultRegistrationArguments,
|
|
72
|
+
hookAssertions: contractCaptureApi.defaultHookAssertions,
|
|
73
|
+
hookEvents: contractCaptureApi.defaultHookEvents,
|
|
74
|
+
hookContexts: contractCaptureApi.defaultHookContexts,
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const ci = Object.freeze({
|
|
79
|
+
buildSummary: ciSummaryApi.buildCiSummary,
|
|
80
|
+
writeSummary: ciSummaryApi.writeCiSummary,
|
|
81
|
+
renderSummary: ciSummaryApi.renderCiSummaryMarkdown,
|
|
82
|
+
readReports: ciSummaryApi.readCiReports,
|
|
83
|
+
deriveStatus: ciSummaryApi.deriveCiStatus,
|
|
84
|
+
buildPolicyReport: ciPolicyApi.buildCiPolicyReport,
|
|
85
|
+
writePolicyReport: ciPolicyApi.writeCiPolicyReport,
|
|
86
|
+
renderPolicyReport: ciPolicyApi.renderCiPolicyMarkdown,
|
|
87
|
+
validatePolicy: ciPolicyApi.validateCiPolicy,
|
|
88
|
+
validatePolicyReport: ciPolicyApi.validateCiPolicyReport,
|
|
89
|
+
buildExecutionResults: executionResultsApi.buildExecutionResultsReport,
|
|
90
|
+
writeExecutionResults: executionResultsApi.writeExecutionResultsReport,
|
|
91
|
+
renderExecutionResults: executionResultsApi.renderExecutionResultsMarkdown,
|
|
92
|
+
writeOutputs: pluginApi.writeCiOutputArtifacts,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export const runtime = Object.freeze({
|
|
96
|
+
buildProfile: runtimeProfileApi.buildRuntimeProfile,
|
|
97
|
+
writeProfile: runtimeProfileApi.writeRuntimeProfile,
|
|
98
|
+
renderProfile: runtimeProfileApi.renderRuntimeProfileMarkdown,
|
|
99
|
+
validateProfile: runtimeProfileApi.validateRuntimeProfile,
|
|
100
|
+
buildProfileDiff: profileDiffApi.buildProfileDiff,
|
|
101
|
+
writeProfileDiff: profileDiffApi.writeProfileDiff,
|
|
102
|
+
renderProfileDiff: profileDiffApi.renderProfileDiffMarkdown,
|
|
103
|
+
validateProfileDiff: profileDiffApi.validateProfileDiff,
|
|
104
|
+
buildRefDiff: refDiffApi.buildRefDiff,
|
|
105
|
+
writeRefDiff: refDiffApi.writeRefDiff,
|
|
106
|
+
renderRefDiff: refDiffApi.renderRefDiffMarkdown,
|
|
107
|
+
validateRefDiff: refDiffApi.validateRefDiff,
|
|
108
|
+
buildImportLoopProfile: importLoopProfileApi.buildImportLoopProfile,
|
|
109
|
+
writeImportLoopProfile: importLoopProfileApi.writeImportLoopProfile,
|
|
110
|
+
renderImportLoopProfile: importLoopProfileApi.renderImportLoopProfileMarkdown,
|
|
111
|
+
validateImportLoopProfile: importLoopProfileApi.validateImportLoopProfile,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const synthetic = Object.freeze({
|
|
115
|
+
buildPlan: syntheticProbesApi.buildSyntheticProbePlan,
|
|
116
|
+
buildPlanFromReport: syntheticProbeSuiteApi.buildSyntheticProbePlanFromReport,
|
|
117
|
+
writePlan: syntheticProbesApi.writeSyntheticProbePlan,
|
|
118
|
+
renderPlan: syntheticProbesApi.renderSyntheticProbeMarkdown,
|
|
119
|
+
validatePlan: syntheticProbesApi.validateSyntheticProbePlan,
|
|
120
|
+
runCaptured: syntheticProbesApi.runCapturedSyntheticProbes,
|
|
121
|
+
registrationExecutionProfiles: syntheticProbesApi.syntheticRegistrationExecutionProfiles,
|
|
122
|
+
defaultHookEvents: syntheticProbesApi.defaultSyntheticHookEvents,
|
|
123
|
+
defaultHookContexts: syntheticProbesApi.defaultSyntheticHookContexts,
|
|
124
|
+
defaultRegistrationArguments: syntheticProbesApi.defaultSyntheticRegistrationArguments,
|
|
125
|
+
});
|
|
126
|
+
|
|
1
127
|
export {
|
|
2
128
|
capturePluginEntrypoint,
|
|
129
|
+
buildFixtureSetColdImportReadiness,
|
|
130
|
+
buildFixtureSetPlatformProbes,
|
|
131
|
+
buildFixtureSetWorkspacePlan,
|
|
3
132
|
createCaptureApi,
|
|
133
|
+
inspectCompatibilityFixtureSetConfig,
|
|
4
134
|
inspectFixtureSetConfig,
|
|
5
135
|
inspectPluginRoot,
|
|
6
136
|
loadPluginConfig,
|
|
137
|
+
renderFixtureSetColdImportReadinessMarkdown,
|
|
138
|
+
renderFixtureSetIssuesReport,
|
|
139
|
+
renderFixtureSetMarkdownReport,
|
|
140
|
+
renderFixtureSetPlatformProbesMarkdown,
|
|
141
|
+
renderFixtureSetWorkspacePlanMarkdown,
|
|
7
142
|
renderTextSummary,
|
|
143
|
+
runFixtureSetColdImportReadiness,
|
|
144
|
+
runFixtureSetPlatformProbes,
|
|
145
|
+
runFixtureSetReport,
|
|
146
|
+
runFixtureSetWorkspacePlan,
|
|
8
147
|
runPluginCheck,
|
|
9
148
|
setupPluginInspector,
|
|
149
|
+
validateColdImportReadiness,
|
|
150
|
+
validateFixtureSetPlatformProbes,
|
|
151
|
+
validateFixtureSetWorkspacePlan,
|
|
10
152
|
writeCiOutputArtifacts,
|
|
153
|
+
writeFixtureSetColdImportReadiness,
|
|
154
|
+
writeFixtureSetPlatformProbes,
|
|
155
|
+
writeFixtureSetReports,
|
|
156
|
+
writeFixtureSetWorkspacePlan,
|
|
11
157
|
writePluginReports,
|
|
12
158
|
} from "./api.js";
|
|
159
|
+
export {
|
|
160
|
+
buildContractCapture,
|
|
161
|
+
defaultHookAssertions,
|
|
162
|
+
defaultHookContexts,
|
|
163
|
+
defaultHookEvents,
|
|
164
|
+
defaultRegistrationArguments,
|
|
165
|
+
defaultRegistrationAssertions,
|
|
166
|
+
renderContractCaptureMarkdown,
|
|
167
|
+
validateContractCapture,
|
|
168
|
+
writeContractCapture,
|
|
169
|
+
} from "./contract-capture.js";
|
|
170
|
+
export { validateContractCoverage } from "./contract-coverage.js";
|
|
171
|
+
export {
|
|
172
|
+
buildCiPolicyReport,
|
|
173
|
+
defaultCiPolicyReportOptions,
|
|
174
|
+
renderCiPolicyMarkdown,
|
|
175
|
+
validateCiPolicy,
|
|
176
|
+
validateCiPolicyReport,
|
|
177
|
+
writeCiPolicyReport,
|
|
178
|
+
} from "./ci-policy.js";
|
|
179
|
+
export {
|
|
180
|
+
buildCiSummary,
|
|
181
|
+
defaultCiReportPaths,
|
|
182
|
+
deriveCiStatus,
|
|
183
|
+
readCiReports,
|
|
184
|
+
renderCiSummaryMarkdown,
|
|
185
|
+
writeCiSummary,
|
|
186
|
+
} from "./ci-summary.js";
|
|
187
|
+
export { loadInspectorConfig } from "./config.js";
|
|
188
|
+
export {
|
|
189
|
+
buildExecutionResultsReport,
|
|
190
|
+
defaultExecutionResultsOptions,
|
|
191
|
+
renderExecutionResultsMarkdown,
|
|
192
|
+
writeExecutionResultsReport,
|
|
193
|
+
} from "./execution-results.js";
|
|
194
|
+
export {
|
|
195
|
+
buildImportLoopProfile,
|
|
196
|
+
defaultImportLoopProfileOptions,
|
|
197
|
+
renderImportLoopProfileMarkdown,
|
|
198
|
+
validateImportLoopProfile,
|
|
199
|
+
writeImportLoopProfile,
|
|
200
|
+
} from "./import-loop-profile.js";
|
|
201
|
+
export { classifyIssueFinding, issueId, knownIssueCodes } from "./issues.js";
|
|
202
|
+
export { inspectFixtureSet, inspectPlugin, inspectSourceText } from "./inspector.js";
|
|
203
|
+
export { openClawTargetPathCandidates } from "./openclaw-target.js";
|
|
204
|
+
export {
|
|
205
|
+
buildProfileDiff,
|
|
206
|
+
defaultProfileDiffOptions,
|
|
207
|
+
renderProfileDiffMarkdown,
|
|
208
|
+
validateProfileDiff,
|
|
209
|
+
writeProfileDiff,
|
|
210
|
+
} from "./profile-diff.js";
|
|
211
|
+
export {
|
|
212
|
+
buildRefDiff,
|
|
213
|
+
defaultRefDiffDimensions,
|
|
214
|
+
defaultRefDiffOptions,
|
|
215
|
+
renderRefDiffMarkdown,
|
|
216
|
+
validateRefDiff,
|
|
217
|
+
writeRefDiff,
|
|
218
|
+
} from "./ref-diff.js";
|
|
219
|
+
export { renderMarkdownReport, writeReport } from "./report.js";
|
|
220
|
+
export {
|
|
221
|
+
buildRuntimeProfile,
|
|
222
|
+
defaultRuntimeProfileCommands,
|
|
223
|
+
defaultRuntimeProfileOptions,
|
|
224
|
+
renderRuntimeProfileMarkdown,
|
|
225
|
+
validateRuntimeProfile,
|
|
226
|
+
writeRuntimeProfile,
|
|
227
|
+
} from "./runtime-profile.js";
|
|
228
|
+
export { buildSyntheticProbePlanFromReport } from "./synthetic-probe-suite.js";
|
|
229
|
+
export {
|
|
230
|
+
buildSyntheticProbePlan,
|
|
231
|
+
defaultSyntheticHookContexts,
|
|
232
|
+
defaultSyntheticHookEvents,
|
|
233
|
+
defaultSyntheticRegistrationArguments,
|
|
234
|
+
renderSyntheticProbeMarkdown,
|
|
235
|
+
runCapturedSyntheticProbes,
|
|
236
|
+
syntheticRegistrationExecutionProfiles,
|
|
237
|
+
validateSyntheticProbePlan,
|
|
238
|
+
writeSyntheticProbePlan,
|
|
239
|
+
} from "./synthetic-probes.js";
|
package/src/init.js
CHANGED
|
@@ -5,32 +5,66 @@ import { inferPluginSeams, packageId } from "./config.js";
|
|
|
5
5
|
|
|
6
6
|
export const defaultInitConfigPath = "plugin-inspector.config.json";
|
|
7
7
|
export const defaultInitWorkflowPath = ".github/workflows/plugin-inspector.yml";
|
|
8
|
+
export const defaultInitPackageScripts = {
|
|
9
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
10
|
+
"plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute",
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
export async function writePluginInspectorInit(options = {}) {
|
|
10
14
|
const pluginRoot = path.resolve(options.pluginRoot ?? options.cwd ?? process.cwd());
|
|
11
15
|
const configPath = path.resolve(pluginRoot, options.configPath ?? defaultInitConfigPath);
|
|
16
|
+
const workflowPath = options.ci === true ? path.resolve(pluginRoot, options.workflowPath ?? defaultInitWorkflowPath) : null;
|
|
17
|
+
const packageManager = options.packageManager ?? (await detectPackageManager(pluginRoot));
|
|
18
|
+
const dryRun = options.dryRun === true;
|
|
12
19
|
const written = [];
|
|
13
20
|
|
|
14
|
-
if (existsSync(configPath) && options.force !== true) {
|
|
21
|
+
if (!dryRun && existsSync(configPath) && options.force !== true) {
|
|
15
22
|
throw new Error(`${path.relative(pluginRoot, configPath)} already exists; pass --force to overwrite it`);
|
|
16
23
|
}
|
|
24
|
+
if (!dryRun && workflowPath && existsSync(workflowPath) && options.force !== true) {
|
|
25
|
+
throw new Error(`${path.relative(pluginRoot, workflowPath)} already exists; pass --force to overwrite it`);
|
|
26
|
+
}
|
|
27
|
+
const packageJsonPath = path.join(pluginRoot, "package.json");
|
|
28
|
+
const packageJson = options.scripts === true ? await readJsonIfExists(packageJsonPath) : null;
|
|
29
|
+
if (options.scripts === true) {
|
|
30
|
+
if (!packageJson) {
|
|
31
|
+
throw new Error("package.json is required to write plugin-inspector package scripts");
|
|
32
|
+
}
|
|
33
|
+
for (const name of Object.keys(defaultInitPackageScripts)) {
|
|
34
|
+
if (!dryRun && packageJson.scripts?.[name] && options.force !== true) {
|
|
35
|
+
throw new Error(`package.json scripts.${name} already exists; pass --force to overwrite it`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
17
39
|
|
|
18
40
|
const config = await buildPluginInspectorConfig({ pluginRoot });
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
if (!dryRun) {
|
|
42
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
43
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
44
|
+
}
|
|
21
45
|
written.push(configPath);
|
|
22
46
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
if (workflowPath) {
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
50
|
+
await writeFile(workflowPath, renderGithubActionsWorkflow({ packageManager }), "utf8");
|
|
27
51
|
}
|
|
28
|
-
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
29
|
-
await writeFile(workflowPath, renderGithubActionsWorkflow({ packageManager: options.packageManager }), "utf8");
|
|
30
52
|
written.push(workflowPath);
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
|
|
55
|
+
if (options.scripts === true) {
|
|
56
|
+
const existingScripts = packageJson.scripts ?? {};
|
|
57
|
+
packageJson.scripts = {
|
|
58
|
+
...existingScripts,
|
|
59
|
+
...defaultInitPackageScripts,
|
|
60
|
+
};
|
|
61
|
+
if (!dryRun) {
|
|
62
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
63
|
+
}
|
|
64
|
+
written.push(packageJsonPath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { pluginRoot, configPath, dryRun, packageManager, written };
|
|
34
68
|
}
|
|
35
69
|
|
|
36
70
|
export async function buildPluginInspectorConfig(options = {}) {
|
|
@@ -79,7 +113,7 @@ jobs:
|
|
|
79
113
|
node-version: 24
|
|
80
114
|
cache: ${setup.cache}
|
|
81
115
|
${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.install}
|
|
82
|
-
- run:
|
|
116
|
+
- run: ${setup.exec} @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
|
|
83
117
|
- uses: actions/upload-artifact@v5
|
|
84
118
|
if: always()
|
|
85
119
|
with:
|
|
@@ -88,19 +122,62 @@ ${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.in
|
|
|
88
122
|
`;
|
|
89
123
|
}
|
|
90
124
|
|
|
125
|
+
export async function detectPackageManager(pluginRoot) {
|
|
126
|
+
const root = path.resolve(pluginRoot ?? process.cwd());
|
|
127
|
+
const packageJson = await readJsonIfExists(path.join(root, "package.json"));
|
|
128
|
+
const packageManager = packageJson?.packageManager;
|
|
129
|
+
if (typeof packageManager === "string") {
|
|
130
|
+
const [name] = packageManager.split("@");
|
|
131
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(name)) {
|
|
132
|
+
return name;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
137
|
+
return "pnpm";
|
|
138
|
+
}
|
|
139
|
+
if (existsSync(path.join(root, "yarn.lock"))) {
|
|
140
|
+
return "yarn";
|
|
141
|
+
}
|
|
142
|
+
if (existsSync(path.join(root, "bun.lockb")) || existsSync(path.join(root, "bun.lock"))) {
|
|
143
|
+
return "bun";
|
|
144
|
+
}
|
|
145
|
+
return "npm";
|
|
146
|
+
}
|
|
147
|
+
|
|
91
148
|
function inferSourceRoot(packageJson) {
|
|
92
149
|
const entrypoints = [
|
|
93
150
|
packageJson?.openclaw?.entrypoint,
|
|
94
151
|
...(packageJson?.openclaw?.extensions ?? []),
|
|
95
152
|
...(packageJson?.openclaw?.runtimeExtensions ?? []),
|
|
153
|
+
...entrypointStrings(packageJson?.exports?.["."]),
|
|
154
|
+
...entrypointStrings(packageJson?.exports),
|
|
155
|
+
packageJson?.module,
|
|
156
|
+
packageJson?.main,
|
|
96
157
|
].filter((value) => typeof value === "string");
|
|
97
|
-
const entrypoint = entrypoints[0] ??
|
|
98
|
-
if (
|
|
158
|
+
const entrypoint = entrypoints[0] ?? "src/index.js";
|
|
159
|
+
if (stripRelativePrefix(entrypoint).startsWith("src/")) {
|
|
99
160
|
return "src";
|
|
100
161
|
}
|
|
101
162
|
return ".";
|
|
102
163
|
}
|
|
103
164
|
|
|
165
|
+
function entrypointStrings(value) {
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
return [value];
|
|
168
|
+
}
|
|
169
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
return ["import", "default", "require", "node", "module"]
|
|
173
|
+
.map((key) => value[key])
|
|
174
|
+
.filter((item) => typeof item === "string");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function stripRelativePrefix(filePath) {
|
|
178
|
+
return filePath.replace(/^\.\//, "");
|
|
179
|
+
}
|
|
180
|
+
|
|
104
181
|
async function readJsonIfExists(filePath) {
|
|
105
182
|
if (!existsSync(filePath)) {
|
|
106
183
|
return null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
4
|
import { register } from "node:module";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
@@ -26,13 +27,16 @@ async function run(options) {
|
|
|
26
27
|
const pluginRoot = path.resolve(options.cwd ?? process.cwd(), options.pluginRoot ?? path.dirname(entrypoint));
|
|
27
28
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-mock-sdk-"));
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
cleanupTempDirOnExit(workspace);
|
|
31
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
32
|
+
register(pathToFileURL(loaderPath));
|
|
33
|
+
return await captureLinkedEntrypoint(entrypoint, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cleanupTempDirOnExit(dir) {
|
|
37
|
+
process.once("exit", () => {
|
|
38
|
+
rmSync(dir, { force: true, recursive: true });
|
|
39
|
+
});
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
async function captureLinkedEntrypoint(entrypoint, options) {
|
package/src/report.js
CHANGED
|
@@ -246,27 +246,79 @@ export async function writeReport(report, options = {}) {
|
|
|
246
246
|
export async function writeCompatibilityReport(report, options = {}) {
|
|
247
247
|
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
248
248
|
const basename = options.basename ?? "plugin-inspector-report";
|
|
249
|
-
const jsonPath = path.join(outDir, `${basename}.json`);
|
|
250
|
-
const markdownPath = path.join(outDir, `${basename}.md`);
|
|
251
|
-
const issuesPath = path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
|
|
249
|
+
const jsonPath = options.jsonPath ?? path.join(outDir, `${basename}.json`);
|
|
250
|
+
const markdownPath = options.markdownPath ?? path.join(outDir, `${basename}.md`);
|
|
251
|
+
const issuesPath = options.issuesPath ?? path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
|
|
252
|
+
const markdownOptions = compatibilityRenderOptions(options, {
|
|
253
|
+
title: options.markdownTitle ?? options.title,
|
|
254
|
+
...options.markdownOptions,
|
|
255
|
+
});
|
|
256
|
+
const issuesOptions = compatibilityRenderOptions(options, {
|
|
257
|
+
title: options.issuesTitle ?? options.title,
|
|
258
|
+
...options.issuesOptions,
|
|
259
|
+
});
|
|
252
260
|
|
|
253
261
|
return writeArtifacts(
|
|
254
262
|
[
|
|
255
263
|
{ name: "jsonPath", path: jsonPath, json: report },
|
|
256
|
-
{ name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report) },
|
|
257
|
-
{ name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report) },
|
|
264
|
+
{ name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report, markdownOptions) },
|
|
265
|
+
{ name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report, issuesOptions) },
|
|
258
266
|
],
|
|
259
267
|
{ check: options.check },
|
|
260
268
|
);
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
function compatibilityRenderOptions(options, overrides) {
|
|
272
|
+
const renderOptions = {
|
|
273
|
+
formatEvidence: options.formatEvidence,
|
|
274
|
+
severityLabels: options.severityLabels,
|
|
275
|
+
...overrides,
|
|
276
|
+
};
|
|
277
|
+
return Object.fromEntries(Object.entries(renderOptions).filter(([, value]) => value !== undefined));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function renderTextSummary(report, options = {}) {
|
|
281
|
+
const lines = [
|
|
265
282
|
`Status: ${report.status.toUpperCase()}`,
|
|
266
283
|
`Fixtures: ${report.summary.fixtureCount}`,
|
|
267
284
|
`Breakages: ${report.summary.breakageCount}`,
|
|
285
|
+
...(typeof report.summary.issueCount === "number" ? [`Issues: ${report.summary.issueCount}`] : []),
|
|
268
286
|
`Logs: ${report.summary.logCount}`,
|
|
269
|
-
]
|
|
287
|
+
];
|
|
288
|
+
const artifacts = Object.entries(options.artifacts ?? {}).filter(([, filePath]) => Boolean(filePath));
|
|
289
|
+
if (artifacts.length > 0) {
|
|
290
|
+
lines.push("", "Reports:", ...artifacts.map(([name, filePath]) => `- ${artifactLabel(name)}: ${filePath}`));
|
|
291
|
+
}
|
|
292
|
+
const findings = topTextFindings(report, options.topFindings ?? 3);
|
|
293
|
+
if (findings.length > 0) {
|
|
294
|
+
lines.push("", "Top findings:", ...findings.map((finding) => `- ${finding}`));
|
|
295
|
+
}
|
|
296
|
+
return lines.join("\n");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function topTextFindings(report, limit) {
|
|
300
|
+
if (report.status === "pass" || limit <= 0) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
...(report.breakages ?? []).map((finding) => formatTextFinding(finding, "breakage")),
|
|
305
|
+
...(report.issues ?? [])
|
|
306
|
+
.filter((issue) => issue.status === "blocking" || issue.severity === "P0" || issue.severity === "P1")
|
|
307
|
+
.map((issue) => formatTextFinding(issue, issue.severity ?? "issue")),
|
|
308
|
+
...(report.warnings ?? []).map((finding) => formatTextFinding(finding, "warning")),
|
|
309
|
+
].slice(0, limit);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatTextFinding(finding, fallbackLevel) {
|
|
313
|
+
const level = finding.level ?? finding.severity ?? fallbackLevel;
|
|
314
|
+
const code = finding.code ? ` ${finding.code}` : "";
|
|
315
|
+
const message = finding.message ?? finding.title ?? "see report";
|
|
316
|
+
const evidence = Array.isArray(finding.evidence) && finding.evidence.length > 0 ? ` (${finding.evidence[0]})` : "";
|
|
317
|
+
return `${String(level).toUpperCase()} ${finding.fixture ?? "unknown"}${code}: ${message}${evidence}`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function artifactLabel(name) {
|
|
321
|
+
return String(name).replace(/Path$/u, "");
|
|
270
322
|
}
|
|
271
323
|
|
|
272
324
|
export function renderMarkdownReport(report) {
|