@openclaw/plugin-inspector 0.1.3 → 0.3.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 +34 -0
- package/README.md +80 -11
- 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 -2
- package/examples/gitlab-ci-plugin-inspector.yml +11 -0
- package/examples/package-json-plugin-inspector.json +21 -0
- package/package.json +4 -1
- package/src/advanced.js +19 -0
- package/src/api.js +60 -3
- package/src/capture-api.js +60 -5
- package/src/ci-outputs.js +235 -0
- package/src/cli.js +129 -17
- package/src/config.js +30 -3
- package/src/index.js +6 -0
- package/src/init.js +90 -14
- package/src/mock-sdk-capture-runner.js +12 -8
- package/src/report.js +60 -8
- package/src/sdk-mock.js +252 -14
- package/src/synthetic-probe-suite.js +19 -0
- package/src/synthetic-probes-cli.js +16 -12
- package/src/synthetic-probes.js +61 -5
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { writeArtifacts } from "./artifacts.js";
|
|
3
|
+
|
|
4
|
+
export const defaultSarifPath = "plugin-inspector.sarif";
|
|
5
|
+
export const defaultJunitPath = "plugin-inspector.junit.xml";
|
|
6
|
+
|
|
7
|
+
export async function writeCiOutputArtifacts(report, options = {}) {
|
|
8
|
+
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
9
|
+
const artifacts = [];
|
|
10
|
+
|
|
11
|
+
if (options.sarifPath) {
|
|
12
|
+
artifacts.push({
|
|
13
|
+
name: "sarifPath",
|
|
14
|
+
path: path.resolve(outDir, options.sarifPath),
|
|
15
|
+
json: buildSarifReport(report),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.junitPath) {
|
|
20
|
+
artifacts.push({
|
|
21
|
+
name: "junitPath",
|
|
22
|
+
path: path.resolve(outDir, options.junitPath),
|
|
23
|
+
content: renderJunitXml(report),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (artifacts.length === 0) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return writeArtifacts(artifacts, { check: options.check });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildSarifReport(report) {
|
|
35
|
+
const findings = reportFindings(report);
|
|
36
|
+
const rules = [...new Map(findings.map((finding) => [finding.code, sarifRule(finding)])).values()];
|
|
37
|
+
const fixtureById = new Map((report.fixtures ?? []).map((fixture) => [fixture.id, fixture]));
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
41
|
+
version: "2.1.0",
|
|
42
|
+
runs: [
|
|
43
|
+
{
|
|
44
|
+
tool: {
|
|
45
|
+
driver: {
|
|
46
|
+
name: "plugin-inspector",
|
|
47
|
+
informationUri: "https://github.com/openclaw/plugin-inspector",
|
|
48
|
+
rules,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
results: findings.map((finding) => sarifResult(finding, fixtureById)),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderJunitXml(report) {
|
|
58
|
+
const findings = reportFindings(report);
|
|
59
|
+
const testcases = findings.length > 0 ? findings.map(junitFindingTestcase) : [junitPassingTestcase(report)];
|
|
60
|
+
const failures = findings.filter(isBlockingFinding).length;
|
|
61
|
+
const tests = testcases.length;
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
65
|
+
`<testsuite name="plugin-inspector" tests="${tests}" failures="${failures}" errors="0" skipped="0">`,
|
|
66
|
+
...testcases,
|
|
67
|
+
"</testsuite>",
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function reportFindings(report) {
|
|
73
|
+
const findings = new Map();
|
|
74
|
+
for (const finding of [...(report.breakages ?? []), ...(report.warnings ?? []), ...(report.suggestions ?? [])]) {
|
|
75
|
+
findings.set(findingKey(finding), finding);
|
|
76
|
+
}
|
|
77
|
+
for (const issue of report.issues ?? []) {
|
|
78
|
+
const finding = issueToFinding(issue);
|
|
79
|
+
findings.set(findingKey(finding), {
|
|
80
|
+
...findings.get(findingKey(finding)),
|
|
81
|
+
...finding,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return [...findings.values()];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function issueToFinding(issue) {
|
|
88
|
+
return {
|
|
89
|
+
fixture: issue.fixture,
|
|
90
|
+
code: issue.code,
|
|
91
|
+
level: issue.status === "blocking" ? "breakage" : "warning",
|
|
92
|
+
message: issue.title,
|
|
93
|
+
evidence: issue.evidence ?? [],
|
|
94
|
+
severity: issue.severity,
|
|
95
|
+
issueClass: issue.issueClass,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findingKey(finding) {
|
|
100
|
+
return [
|
|
101
|
+
finding.fixture ?? "",
|
|
102
|
+
finding.code ?? "",
|
|
103
|
+
...normalizeEvidence(finding.evidence),
|
|
104
|
+
].join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sarifRule(finding) {
|
|
108
|
+
return {
|
|
109
|
+
id: finding.code,
|
|
110
|
+
shortDescription: {
|
|
111
|
+
text: finding.code,
|
|
112
|
+
},
|
|
113
|
+
fullDescription: {
|
|
114
|
+
text: finding.message ?? finding.code,
|
|
115
|
+
},
|
|
116
|
+
defaultConfiguration: {
|
|
117
|
+
level: sarifLevel(finding),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sarifResult(finding, fixtureById) {
|
|
123
|
+
return {
|
|
124
|
+
ruleId: finding.code,
|
|
125
|
+
level: sarifLevel(finding),
|
|
126
|
+
message: {
|
|
127
|
+
text: finding.message ?? finding.code,
|
|
128
|
+
},
|
|
129
|
+
locations: [sarifLocation(finding, fixtureById)],
|
|
130
|
+
properties: {
|
|
131
|
+
fixture: finding.fixture,
|
|
132
|
+
severity: finding.severity ?? finding.level,
|
|
133
|
+
issueClass: finding.issueClass,
|
|
134
|
+
evidence: normalizeEvidence(finding.evidence),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sarifLocation(finding, fixtureById) {
|
|
140
|
+
const parsed = parseEvidenceLocation(normalizeEvidence(finding.evidence)[0]);
|
|
141
|
+
const fixture = fixtureById.get(finding.fixture);
|
|
142
|
+
const uri = parsed?.uri ?? fixture?.path ?? ".";
|
|
143
|
+
return {
|
|
144
|
+
physicalLocation: {
|
|
145
|
+
artifactLocation: {
|
|
146
|
+
uri: normalizeUri(uri),
|
|
147
|
+
},
|
|
148
|
+
region: {
|
|
149
|
+
startLine: parsed?.line ?? 1,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseEvidenceLocation(evidence) {
|
|
156
|
+
if (!evidence) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ref = evidence.includes(" @ ") ? evidence.split(" @ ").pop() : evidence;
|
|
161
|
+
const match = /^(?<uri>.+?):(?<line>\d+)(?::\d+)?$/.exec(ref);
|
|
162
|
+
if (!match?.groups?.uri) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
uri: match.groups.uri,
|
|
167
|
+
line: Number(match.groups.line),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function junitFindingTestcase(finding) {
|
|
172
|
+
const classname = `plugin-inspector.${xmlName(finding.fixture ?? "unknown")}`;
|
|
173
|
+
const name = `${finding.level ?? "finding"}:${finding.code}`;
|
|
174
|
+
const output = normalizeEvidence(finding.evidence).join("\n");
|
|
175
|
+
if (!isBlockingFinding(finding)) {
|
|
176
|
+
return [
|
|
177
|
+
` <testcase classname="${escapeXml(classname)}" name="${escapeXml(name)}">`,
|
|
178
|
+
output ? ` <system-out>${escapeXml(output)}</system-out>` : "",
|
|
179
|
+
" </testcase>",
|
|
180
|
+
]
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [
|
|
186
|
+
` <testcase classname="${escapeXml(classname)}" name="${escapeXml(name)}">`,
|
|
187
|
+
` <failure message="${escapeXml(finding.message ?? finding.code)}">${escapeXml(output || finding.message || finding.code)}</failure>`,
|
|
188
|
+
" </testcase>",
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function junitPassingTestcase(report) {
|
|
193
|
+
return ` <testcase classname="plugin-inspector" name="status:${escapeXml(report.status ?? "pass")}"/>`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isBlockingFinding(finding) {
|
|
197
|
+
return finding.level === "breakage" || finding.status === "blocking" || finding.severity === "P0";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function sarifLevel(finding) {
|
|
201
|
+
if (isBlockingFinding(finding) || finding.severity === "P1") {
|
|
202
|
+
return "error";
|
|
203
|
+
}
|
|
204
|
+
if (finding.level === "warning" || finding.severity === "P2") {
|
|
205
|
+
return "warning";
|
|
206
|
+
}
|
|
207
|
+
return "note";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeEvidence(evidence) {
|
|
211
|
+
if (Array.isArray(evidence)) {
|
|
212
|
+
return evidence.map(String);
|
|
213
|
+
}
|
|
214
|
+
if (evidence == null) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
return [String(evidence)];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function normalizeUri(uri) {
|
|
221
|
+
return String(uri).replaceAll(path.sep, "/");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function xmlName(value) {
|
|
225
|
+
return String(value).replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function escapeXml(value) {
|
|
229
|
+
return String(value)
|
|
230
|
+
.replace(/&/g, "&")
|
|
231
|
+
.replace(/</g, "<")
|
|
232
|
+
.replace(/>/g, ">")
|
|
233
|
+
.replace(/"/g, """)
|
|
234
|
+
.replace(/'/g, "'");
|
|
235
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
|
+
loadPluginConfig,
|
|
4
5
|
renderTextSummary,
|
|
5
6
|
runPluginCheck,
|
|
6
7
|
} from "./index.js";
|
|
7
8
|
import {
|
|
8
9
|
buildCiSummary,
|
|
9
10
|
captureEntrypoint,
|
|
11
|
+
defaultJunitPath,
|
|
12
|
+
defaultSarifPath,
|
|
10
13
|
inspectCompatibilityFixtureSet,
|
|
11
14
|
inspectFixtureSet,
|
|
12
15
|
loadInspectorConfig,
|
|
16
|
+
writeCiOutputArtifacts,
|
|
13
17
|
writeCiSummary,
|
|
14
18
|
writeCompatibilityReport,
|
|
15
19
|
writePluginInspectorInit,
|
|
@@ -28,8 +32,14 @@ try {
|
|
|
28
32
|
await runCheck(commandArgs);
|
|
29
33
|
} else if (command === "init") {
|
|
30
34
|
await runInit(commandArgs);
|
|
35
|
+
} else if (command === "config") {
|
|
36
|
+
await runConfig(commandArgs);
|
|
31
37
|
} else if (command === "inspect" || command === "report") {
|
|
32
|
-
|
|
38
|
+
if (command === "inspect" && !commandArgs.includes("--config")) {
|
|
39
|
+
await runCheck(commandArgs);
|
|
40
|
+
} else {
|
|
41
|
+
await runReport(command, commandArgs);
|
|
42
|
+
}
|
|
33
43
|
} else if (command === "ci") {
|
|
34
44
|
await runCi(commandArgs);
|
|
35
45
|
} else if (command === "capture") {
|
|
@@ -42,6 +52,18 @@ try {
|
|
|
42
52
|
process.exitCode = 1;
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
async function runConfig(commandArgs) {
|
|
56
|
+
const configPath = readFlag(commandArgs, "--config");
|
|
57
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
58
|
+
const config = await loadPluginConfig({ configPath, pluginRoot });
|
|
59
|
+
|
|
60
|
+
if (commandArgs.includes("--json")) {
|
|
61
|
+
console.log(JSON.stringify(config, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
console.log(renderConfigTextSummary(config));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
async function runCheck(commandArgs) {
|
|
46
68
|
const configPath = readFlag(commandArgs, "--config");
|
|
47
69
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
@@ -50,12 +72,27 @@ async function runCheck(commandArgs) {
|
|
|
50
72
|
const json = commandArgs.includes("--json");
|
|
51
73
|
const capture = readRuntimeFlag(commandArgs);
|
|
52
74
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
53
|
-
const
|
|
75
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
76
|
+
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
77
|
+
const { report, paths } = await runPluginCheck({
|
|
78
|
+
allowExecution,
|
|
79
|
+
capture,
|
|
80
|
+
configPath,
|
|
81
|
+
mockSdk,
|
|
82
|
+
openclawPath,
|
|
83
|
+
outDir,
|
|
84
|
+
pluginRoot,
|
|
85
|
+
});
|
|
86
|
+
await writeCiOutputArtifacts(report, {
|
|
87
|
+
...ciOutputs,
|
|
88
|
+
cwd: path.dirname(paths.jsonPath),
|
|
89
|
+
outDir: ".",
|
|
90
|
+
});
|
|
54
91
|
|
|
55
92
|
if (json) {
|
|
56
93
|
console.log(JSON.stringify(report, null, 2));
|
|
57
94
|
} else {
|
|
58
|
-
console.log(renderTextSummary(report));
|
|
95
|
+
console.log(renderTextSummary(report, { artifacts: paths }));
|
|
59
96
|
}
|
|
60
97
|
|
|
61
98
|
if (report.status !== "pass") {
|
|
@@ -67,19 +104,27 @@ async function runInit(commandArgs) {
|
|
|
67
104
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
68
105
|
const configPath = readFlag(commandArgs, "--config") ?? undefined;
|
|
69
106
|
const workflowPath = readFlag(commandArgs, "--workflow") ?? undefined;
|
|
70
|
-
const packageManager = readFlag(commandArgs, "--package-manager") ??
|
|
107
|
+
const packageManager = readFlag(commandArgs, "--package-manager") ?? undefined;
|
|
71
108
|
const result = await writePluginInspectorInit({
|
|
72
109
|
pluginRoot,
|
|
73
110
|
configPath,
|
|
74
111
|
workflowPath,
|
|
75
112
|
packageManager,
|
|
76
113
|
ci: commandArgs.includes("--ci"),
|
|
114
|
+
dryRun: commandArgs.includes("--dry-run"),
|
|
115
|
+
scripts: commandArgs.includes("--scripts"),
|
|
77
116
|
force: commandArgs.includes("--force"),
|
|
78
117
|
});
|
|
79
118
|
|
|
119
|
+
if (commandArgs.includes("--json")) {
|
|
120
|
+
console.log(JSON.stringify(initCommandSummary(result), null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
80
124
|
for (const filePath of result.written) {
|
|
81
|
-
console.log(
|
|
125
|
+
console.log(`${result.dryRun ? "would write" : "wrote"} ${path.relative(result.pluginRoot, filePath)}`);
|
|
82
126
|
}
|
|
127
|
+
console.log(`package manager: ${result.packageManager}`);
|
|
83
128
|
}
|
|
84
129
|
|
|
85
130
|
async function runReport(command, commandArgs) {
|
|
@@ -87,14 +132,20 @@ async function runReport(command, commandArgs) {
|
|
|
87
132
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
88
133
|
const check = commandArgs.includes("--check") || command === "ci";
|
|
89
134
|
const json = commandArgs.includes("--json");
|
|
135
|
+
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
90
136
|
const config = await loadInspectorConfig(configPath);
|
|
91
137
|
const report = await inspectFixtureSet(config);
|
|
92
|
-
await writeReport(report, { outDir });
|
|
138
|
+
const paths = await writeReport(report, { outDir });
|
|
139
|
+
await writeCiOutputArtifacts(report, {
|
|
140
|
+
...ciOutputs,
|
|
141
|
+
cwd: path.dirname(paths.jsonPath),
|
|
142
|
+
outDir: ".",
|
|
143
|
+
});
|
|
93
144
|
|
|
94
145
|
if (json) {
|
|
95
146
|
console.log(JSON.stringify(report, null, 2));
|
|
96
147
|
} else {
|
|
97
|
-
console.log(renderTextSummary(report));
|
|
148
|
+
console.log(renderTextSummary(report, { artifacts: paths }));
|
|
98
149
|
}
|
|
99
150
|
|
|
100
151
|
if (check && report.status !== "pass") {
|
|
@@ -108,8 +159,15 @@ async function runCi(commandArgs) {
|
|
|
108
159
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
109
160
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
110
161
|
const json = commandArgs.includes("--json");
|
|
162
|
+
const capture = readRuntimeFlag(commandArgs);
|
|
163
|
+
const mockSdk = readMockSdkFlag(commandArgs);
|
|
164
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
165
|
+
const ciOutputs = readCiOutputFlags(commandArgs, { defaultEnabled: true });
|
|
111
166
|
const { report, reportDir } = await runCiCompatibilityReport({
|
|
167
|
+
allowExecution,
|
|
168
|
+
capture,
|
|
112
169
|
configPath,
|
|
170
|
+
mockSdk,
|
|
113
171
|
openclawPath,
|
|
114
172
|
outDir,
|
|
115
173
|
pluginRoot,
|
|
@@ -128,6 +186,11 @@ async function runCi(commandArgs) {
|
|
|
128
186
|
jsonPath: path.join(reportDir, "plugin-inspector-ci-summary.json"),
|
|
129
187
|
markdownPath: path.join(reportDir, "plugin-inspector-ci-summary.md"),
|
|
130
188
|
});
|
|
189
|
+
await writeCiOutputArtifacts(report, {
|
|
190
|
+
...ciOutputs,
|
|
191
|
+
cwd: reportDir,
|
|
192
|
+
outDir: ".",
|
|
193
|
+
});
|
|
131
194
|
|
|
132
195
|
if (json) {
|
|
133
196
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -140,7 +203,7 @@ async function runCi(commandArgs) {
|
|
|
140
203
|
}
|
|
141
204
|
}
|
|
142
205
|
|
|
143
|
-
async function runCiCompatibilityReport({ configPath, openclawPath, outDir, pluginRoot }) {
|
|
206
|
+
async function runCiCompatibilityReport({ allowExecution, capture, configPath, mockSdk, openclawPath, outDir, pluginRoot }) {
|
|
144
207
|
if (configPath) {
|
|
145
208
|
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
146
209
|
const report = await inspectCompatibilityFixtureSet(config, { openclawPath });
|
|
@@ -151,7 +214,7 @@ async function runCiCompatibilityReport({ configPath, openclawPath, outDir, plug
|
|
|
151
214
|
};
|
|
152
215
|
}
|
|
153
216
|
|
|
154
|
-
const { report } = await runPluginCheck({
|
|
217
|
+
const { report } = await runPluginCheck({ allowExecution, capture, mockSdk, openclawPath, outDir, pluginRoot });
|
|
155
218
|
return {
|
|
156
219
|
report,
|
|
157
220
|
reportDir: path.resolve(pluginRoot ?? process.cwd(), outDir),
|
|
@@ -163,11 +226,12 @@ async function runCapture(commandArgs) {
|
|
|
163
226
|
const outputPath = readFlag(commandArgs, "--output");
|
|
164
227
|
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
165
228
|
const mockSdk = readMockSdkFlag(commandArgs) ?? commandArgs.includes("--mock-sdk");
|
|
229
|
+
const allowExecution = readAllowExecutionFlag(commandArgs);
|
|
166
230
|
if (!entrypoint) {
|
|
167
231
|
throw new Error("capture requires an entrypoint path");
|
|
168
232
|
}
|
|
169
|
-
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
170
|
-
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");
|
|
171
235
|
}
|
|
172
236
|
|
|
173
237
|
const result = await captureEntrypoint(entrypoint, { mockSdk, pluginRoot });
|
|
@@ -187,6 +251,26 @@ function readFlag(commandArgs, name) {
|
|
|
187
251
|
return commandArgs[index + 1] ?? null;
|
|
188
252
|
}
|
|
189
253
|
|
|
254
|
+
function readOptionalPathFlag(commandArgs, name, defaultPath) {
|
|
255
|
+
const index = commandArgs.indexOf(name);
|
|
256
|
+
if (index === -1) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const value = commandArgs[index + 1];
|
|
260
|
+
return value && !value.startsWith("-") ? value : defaultPath;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function readCiOutputFlags(commandArgs, options = {}) {
|
|
264
|
+
return {
|
|
265
|
+
sarifPath: commandArgs.includes("--no-sarif")
|
|
266
|
+
? null
|
|
267
|
+
: (readOptionalPathFlag(commandArgs, "--sarif", defaultSarifPath) ?? (options.defaultEnabled ? defaultSarifPath : null)),
|
|
268
|
+
junitPath: commandArgs.includes("--no-junit")
|
|
269
|
+
? null
|
|
270
|
+
: (readOptionalPathFlag(commandArgs, "--junit", defaultJunitPath) ?? (options.defaultEnabled ? defaultJunitPath : null)),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
190
274
|
function readRuntimeFlag(commandArgs) {
|
|
191
275
|
if (commandArgs.includes("--runtime") || commandArgs.includes("--capture")) {
|
|
192
276
|
return true;
|
|
@@ -217,6 +301,10 @@ function readMockSdkFlag(commandArgs) {
|
|
|
217
301
|
return undefined;
|
|
218
302
|
}
|
|
219
303
|
|
|
304
|
+
function readAllowExecutionFlag(commandArgs) {
|
|
305
|
+
return commandArgs.includes("--allow-execute");
|
|
306
|
+
}
|
|
307
|
+
|
|
220
308
|
function renderCiTextSummary(summary) {
|
|
221
309
|
return [
|
|
222
310
|
`Status: ${summary.status.toUpperCase()}`,
|
|
@@ -226,19 +314,43 @@ function renderCiTextSummary(summary) {
|
|
|
226
314
|
].join("\n");
|
|
227
315
|
}
|
|
228
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
|
+
|
|
326
|
+
function renderConfigTextSummary(config) {
|
|
327
|
+
const fixture = config.fixtures[0];
|
|
328
|
+
return [
|
|
329
|
+
`Plugin: ${fixture.id}`,
|
|
330
|
+
`Root: ${config.rootDir}`,
|
|
331
|
+
`Config: ${config.configPath ?? "auto"}`,
|
|
332
|
+
`Priority: ${fixture.priority}`,
|
|
333
|
+
`Seams: ${fixture.seams.join(", ")}`,
|
|
334
|
+
`Runtime capture: ${config.capture?.runtime === true ? "on" : "off"}`,
|
|
335
|
+
`Mock SDK: ${config.capture?.mockSdk === false ? "off" : "on"}`,
|
|
336
|
+
].join("\n");
|
|
337
|
+
}
|
|
338
|
+
|
|
229
339
|
function printHelp() {
|
|
230
340
|
console.log(`plugin-inspector
|
|
231
341
|
|
|
232
342
|
Usage:
|
|
233
343
|
plugin-inspector
|
|
234
|
-
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json]
|
|
235
|
-
plugin-inspector
|
|
344
|
+
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--allow-execute] [--json]
|
|
345
|
+
plugin-inspector config [--plugin-root <path>] [--config <path>] [--json]
|
|
346
|
+
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--scripts] [--package-manager npm|pnpm|yarn|bun] [--dry-run] [--json] [--force]
|
|
236
347
|
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
237
|
-
plugin-inspector inspect --config <path> [--out <dir>] [--check] [--json]
|
|
238
|
-
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--json]
|
|
239
|
-
|
|
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>]
|
|
240
351
|
|
|
241
352
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
242
|
-
|
|
353
|
+
CI writes SARIF and JUnit artifacts by default; check/inspect can write them with --sarif and --junit.
|
|
354
|
+
Runtime capture is opt-in because it imports plugin code; use --runtime with --allow-execute or PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
243
355
|
`);
|
|
244
356
|
}
|
package/src/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
export const npmPackagePayloadDir = ".crabpot-package";
|
|
6
6
|
export const defaultPluginRootConfigFiles = ["plugin-inspector.config.json", ".plugin-inspector.json"];
|
|
7
|
+
export const packageJsonConfigKeys = ["pluginInspector", "plugin-inspector"];
|
|
7
8
|
|
|
8
9
|
export async function loadInspectorConfig(configPath, options = {}) {
|
|
9
10
|
if (!configPath) {
|
|
@@ -24,16 +25,23 @@ export async function loadInspectorConfig(configPath, options = {}) {
|
|
|
24
25
|
export async function loadPluginRootConfig(configPath = null, options = {}) {
|
|
25
26
|
const rootDir = path.resolve(options.cwd ?? process.cwd());
|
|
26
27
|
const resolvedPath = configPath ? path.resolve(rootDir, configPath) : findPluginRootConfigPath(rootDir);
|
|
27
|
-
|
|
28
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
29
|
+
const packageJson = await readJsonIfExists(packageJsonPath);
|
|
30
|
+
const packageConfig = packageJsonConfig(packageJson);
|
|
31
|
+
|
|
32
|
+
if (!resolvedPath && !packageJson && !existsSync(path.join(rootDir, "openclaw.plugin.json"))) {
|
|
28
33
|
throw new Error("run from a plugin root with package.json/openclaw.plugin.json, or pass --config");
|
|
29
34
|
}
|
|
30
|
-
|
|
35
|
+
|
|
36
|
+
const config = resolvedPath
|
|
37
|
+
? JSON.parse(await readFile(resolvedPath, "utf8"))
|
|
38
|
+
: (packageConfig.config ?? { version: 1 });
|
|
31
39
|
const normalizedConfig = await normalizePluginRootConfig(config, { rootDir });
|
|
32
40
|
validateInspectorConfig(normalizedConfig);
|
|
33
41
|
return {
|
|
34
42
|
...normalizedConfig,
|
|
35
43
|
rootDir,
|
|
36
|
-
configPath: resolvedPath,
|
|
44
|
+
configPath: resolvedPath ?? packageConfig.configPath,
|
|
37
45
|
};
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -164,6 +172,25 @@ function findPluginRootConfigPath(rootDir) {
|
|
|
164
172
|
return defaultPluginRootConfigFiles.map((file) => path.join(rootDir, file)).find(existsSync) ?? null;
|
|
165
173
|
}
|
|
166
174
|
|
|
175
|
+
function packageJsonConfig(packageJson) {
|
|
176
|
+
if (!packageJson) {
|
|
177
|
+
return { config: null, configPath: null };
|
|
178
|
+
}
|
|
179
|
+
for (const key of packageJsonConfigKeys) {
|
|
180
|
+
if (packageJson[key] === undefined) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!packageJson[key] || typeof packageJson[key] !== "object" || Array.isArray(packageJson[key])) {
|
|
184
|
+
throw new Error(`package.json ${key} must be an object`);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
config: packageJson[key],
|
|
188
|
+
configPath: `package.json#${key}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { config: null, configPath: null };
|
|
192
|
+
}
|
|
193
|
+
|
|
167
194
|
async function readJsonIfExists(filePath) {
|
|
168
195
|
if (!existsSync(filePath)) {
|
|
169
196
|
return null;
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export {
|
|
2
2
|
capturePluginEntrypoint,
|
|
3
3
|
createCaptureApi,
|
|
4
|
+
inspectCompatibilityFixtureSetConfig,
|
|
4
5
|
inspectFixtureSetConfig,
|
|
5
6
|
inspectPluginRoot,
|
|
6
7
|
loadPluginConfig,
|
|
8
|
+
renderFixtureSetIssuesReport,
|
|
9
|
+
renderFixtureSetMarkdownReport,
|
|
7
10
|
renderTextSummary,
|
|
11
|
+
runFixtureSetReport,
|
|
8
12
|
runPluginCheck,
|
|
9
13
|
setupPluginInspector,
|
|
14
|
+
writeCiOutputArtifacts,
|
|
15
|
+
writeFixtureSetReports,
|
|
10
16
|
writePluginReports,
|
|
11
17
|
} from "./api.js";
|