@openclaw/plugin-inspector 0.1.2 → 0.2.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 +28 -0
- package/README.md +56 -8
- package/examples/github-actions-plugin-inspector.yml +1 -2
- package/examples/package-json-plugin-inspector.json +21 -0
- package/package.json +1 -1
- package/src/advanced.js +10 -0
- package/src/api.js +2 -1
- package/src/capture-api.js +20 -3
- package/src/capture-cli.js +61 -0
- package/src/ci-outputs.js +235 -0
- package/src/ci-summary.js +1 -1
- package/src/cli.js +157 -6
- package/src/config.js +30 -3
- package/src/contract-probes.js +5 -0
- package/src/fixture-summary.js +40 -16
- package/src/index.js +1 -0
- package/src/init.js +1 -2
- package/src/issues.js +8 -0
- package/src/openclaw-target.js +27 -0
- package/src/report.js +1 -0
- package/src/sdk-mock.js +50 -5
- package/src/synthetic-probes-cli.js +101 -0
- package/src/synthetic-probes.js +6 -5
- package/src/workspace-plan.js +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add package.json `pluginInspector` config discovery for plugin-root checks.
|
|
8
|
+
- Add `plugin-inspector config` for resolved plugin-root config summaries.
|
|
9
|
+
- Add author-facing `plugin-inspector inspect` plugin-root flow.
|
|
10
|
+
- Add CI-native SARIF and JUnit outputs; `plugin-inspector ci` writes them by default.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Make generated CI workflows use one `plugin-inspector ci --no-openclaw --runtime --mock-sdk` command.
|
|
15
|
+
- Harden runtime capture for string handler registrations, parse-capable config schema helpers, and provider auth/catalog SDK mocks.
|
|
16
|
+
|
|
17
|
+
## 0.1.3 - 2026-04-27
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Add reserved bundled-plugin SDK import detection so external plugins get explicit compatibility findings for private OpenClaw SDK shims.
|
|
22
|
+
- Add packaged workspace capture and synthetic-probe helper CLIs for generated isolated workspace plans.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Make `plugin-inspector ci` write compatibility-backed CI summary artifacts instead of the legacy inventory report.
|
|
27
|
+
- Default packaged helper captures to the mocked OpenClaw SDK while preserving `--real-sdk` opt-in behavior.
|
|
28
|
+
- Detect the default runtime capture artifact at `reports/plugin-inspector-runtime-capture.json`.
|
|
29
|
+
- Report the actual log count in CLI text summaries.
|
|
30
|
+
|
|
3
31
|
## 0.1.2 - 2026-04-27
|
|
4
32
|
|
|
5
33
|
### Added
|
package/README.md
CHANGED
|
@@ -35,17 +35,39 @@ npx plugin-inspector check
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npx @openclaw/plugin-inspector check
|
|
38
|
+
npx @openclaw/plugin-inspector inspect
|
|
39
|
+
npx @openclaw/plugin-inspector ci --no-openclaw
|
|
40
|
+
npx @openclaw/plugin-inspector config
|
|
38
41
|
npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
|
|
39
42
|
npx @openclaw/plugin-inspector init --ci --package-manager pnpm
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
`check`
|
|
43
|
-
|
|
45
|
+
`check` and `inspect` read the current directory as one plugin unless
|
|
46
|
+
`--plugin-root` is set. `inspect` is the friendly author-facing alias; `check`
|
|
47
|
+
is kept for scripts. Both write:
|
|
44
48
|
|
|
45
49
|
- `reports/plugin-inspector-report.json`
|
|
46
50
|
- `reports/plugin-inspector-report.md`
|
|
47
51
|
- `reports/plugin-inspector-issues.md`
|
|
48
52
|
|
|
53
|
+
Use CI-native outputs when you want annotations or test-summary ingestion:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
plugin-inspector inspect --no-openclaw --sarif --junit
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That also writes:
|
|
60
|
+
|
|
61
|
+
- `reports/plugin-inspector.sarif`
|
|
62
|
+
- `reports/plugin-inspector.junit.xml`
|
|
63
|
+
|
|
64
|
+
`config` prints the resolved plugin root, fixture id, seams, and capture
|
|
65
|
+
settings before CI runs:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
plugin-inspector config --json
|
|
69
|
+
```
|
|
70
|
+
|
|
49
71
|
Use `--no-openclaw` when CI should not compare against a local OpenClaw
|
|
50
72
|
checkout:
|
|
51
73
|
|
|
@@ -53,8 +75,9 @@ checkout:
|
|
|
53
75
|
plugin-inspector check --no-openclaw
|
|
54
76
|
```
|
|
55
77
|
|
|
56
|
-
Use `plugin-inspector.config.json`
|
|
57
|
-
|
|
78
|
+
Use either `plugin-inspector.config.json` or a `package.json`
|
|
79
|
+
`pluginInspector` block when CI needs stable fixture metadata, expected seams,
|
|
80
|
+
or runtime capture defaults:
|
|
58
81
|
|
|
59
82
|
```json
|
|
60
83
|
{
|
|
@@ -83,9 +106,32 @@ Then run:
|
|
|
83
106
|
plugin-inspector check --config plugin-inspector.config.json
|
|
84
107
|
```
|
|
85
108
|
|
|
109
|
+
For a single plugin package, the same config can live in `package.json`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"scripts": {
|
|
114
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw"
|
|
115
|
+
},
|
|
116
|
+
"pluginInspector": {
|
|
117
|
+
"version": 1,
|
|
118
|
+
"plugin": {
|
|
119
|
+
"id": "weather",
|
|
120
|
+
"priority": "high",
|
|
121
|
+
"seams": ["dynamic-tool"],
|
|
122
|
+
"sourceRoot": "src"
|
|
123
|
+
},
|
|
124
|
+
"capture": {
|
|
125
|
+
"mockSdk": true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
86
131
|
`init --ci` writes this shape for you, plus
|
|
87
132
|
`.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
|
|
88
133
|
`examples/plugin-inspector.config.json` and
|
|
134
|
+
`examples/package-json-plugin-inspector.json` and
|
|
89
135
|
`examples/github-actions-plugin-inspector.yml`.
|
|
90
136
|
|
|
91
137
|
## Runtime Capture
|
|
@@ -121,8 +167,8 @@ Minimal package scripts:
|
|
|
121
167
|
```json
|
|
122
168
|
{
|
|
123
169
|
"scripts": {
|
|
124
|
-
"plugin:check": "plugin-inspector
|
|
125
|
-
"plugin:
|
|
170
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
171
|
+
"plugin:ci": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector ci --no-openclaw --runtime --mock-sdk"
|
|
126
172
|
}
|
|
127
173
|
}
|
|
128
174
|
```
|
|
@@ -147,8 +193,7 @@ jobs:
|
|
|
147
193
|
node-version: 24
|
|
148
194
|
cache: npm
|
|
149
195
|
- run: npm ci
|
|
150
|
-
- run: npx @openclaw/plugin-inspector
|
|
151
|
-
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
196
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk
|
|
152
197
|
- uses: actions/upload-artifact@v5
|
|
153
198
|
if: always()
|
|
154
199
|
with:
|
|
@@ -156,6 +201,9 @@ jobs:
|
|
|
156
201
|
path: reports/plugin-inspector-*
|
|
157
202
|
```
|
|
158
203
|
|
|
204
|
+
`ci` writes the normal report, CI summary, SARIF, and JUnit files by default.
|
|
205
|
+
Pass `--no-sarif` or `--no-junit` only if your CI surface cannot consume them.
|
|
206
|
+
|
|
159
207
|
## Fixture Suites
|
|
160
208
|
|
|
161
209
|
Fixture-set configs are still supported for crabpot-style compatibility suites:
|
|
@@ -15,8 +15,7 @@ jobs:
|
|
|
15
15
|
node-version: 24
|
|
16
16
|
cache: npm
|
|
17
17
|
- run: npm ci
|
|
18
|
-
- run: npx @openclaw/plugin-inspector
|
|
19
|
-
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
18
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk
|
|
20
19
|
- uses: actions/upload-artifact@v5
|
|
21
20
|
if: always()
|
|
22
21
|
with:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
4
|
+
"plugin:ci": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector ci --no-openclaw --runtime --mock-sdk"
|
|
5
|
+
},
|
|
6
|
+
"pluginInspector": {
|
|
7
|
+
"version": 1,
|
|
8
|
+
"plugin": {
|
|
9
|
+
"id": "weather",
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"seams": ["dynamic-tool"],
|
|
12
|
+
"sourceRoot": "src",
|
|
13
|
+
"expect": {
|
|
14
|
+
"registrations": ["registerTool"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"capture": {
|
|
18
|
+
"mockSdk": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -33,6 +33,14 @@ export {
|
|
|
33
33
|
renderCiSummaryMarkdown,
|
|
34
34
|
writeCiSummary,
|
|
35
35
|
} from "./ci-summary.js";
|
|
36
|
+
export {
|
|
37
|
+
buildSarifReport,
|
|
38
|
+
defaultJunitPath,
|
|
39
|
+
defaultSarifPath,
|
|
40
|
+
renderJunitXml,
|
|
41
|
+
reportFindings,
|
|
42
|
+
writeCiOutputArtifacts,
|
|
43
|
+
} from "./ci-outputs.js";
|
|
36
44
|
export {
|
|
37
45
|
buildContractProbes,
|
|
38
46
|
contractProbeRules,
|
|
@@ -100,6 +108,7 @@ export {
|
|
|
100
108
|
openClawTargetPathCandidates,
|
|
101
109
|
parseCompatRecordEntries,
|
|
102
110
|
parseExportedStringArray,
|
|
111
|
+
parsePluginSdkEntrypointSpecifiers,
|
|
103
112
|
parsePluginSdkExports,
|
|
104
113
|
parseTypeFields,
|
|
105
114
|
readOpenClawTargetSurface,
|
|
@@ -121,6 +130,7 @@ export {
|
|
|
121
130
|
loadPluginRootConfig,
|
|
122
131
|
normalizeInspectorConfig,
|
|
123
132
|
normalizePluginRootConfig,
|
|
133
|
+
packageJsonConfigKeys,
|
|
124
134
|
packageId,
|
|
125
135
|
validateInspectorConfig,
|
|
126
136
|
} from "./config.js";
|
package/src/api.js
CHANGED
|
@@ -4,6 +4,7 @@ import { loadInspectorConfig, loadPluginRootConfig } from "./config.js";
|
|
|
4
4
|
import { writePluginInspectorInit } from "./init.js";
|
|
5
5
|
import { captureEntrypoint } from "./inspector.js";
|
|
6
6
|
import { renderTextSummary, writeCompatibilityReport } from "./report.js";
|
|
7
|
+
import { writeCiOutputArtifacts } from "./ci-outputs.js";
|
|
7
8
|
import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
|
|
8
9
|
import { inspectCompatibilityFixtureSet, inspectFixtureSet } from "./inspector.js";
|
|
9
10
|
|
|
@@ -88,4 +89,4 @@ export async function setupPluginInspector(options = {}) {
|
|
|
88
89
|
return writePluginInspectorInit(options);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export { createCaptureApi, renderTextSummary };
|
|
92
|
+
export { createCaptureApi, renderTextSummary, writeCiOutputArtifacts };
|
package/src/capture-api.js
CHANGED
|
@@ -223,6 +223,7 @@ function createStoreContext() {
|
|
|
223
223
|
|
|
224
224
|
function registrationObject(args, defaults) {
|
|
225
225
|
const first = args[0];
|
|
226
|
+
const callable = firstCallable(args);
|
|
226
227
|
if (first && typeof first === "object") {
|
|
227
228
|
return {
|
|
228
229
|
...defaults,
|
|
@@ -232,13 +233,29 @@ function registrationObject(args, defaults) {
|
|
|
232
233
|
};
|
|
233
234
|
}
|
|
234
235
|
if (typeof first === "string") {
|
|
235
|
-
return {
|
|
236
|
+
return withCallableDefaults({
|
|
236
237
|
...defaults,
|
|
237
238
|
name: first,
|
|
238
239
|
id: defaults.id,
|
|
239
|
-
};
|
|
240
|
+
}, callable);
|
|
240
241
|
}
|
|
241
|
-
return { ...defaults };
|
|
242
|
+
return withCallableDefaults({ ...defaults }, callable);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function firstCallable(args) {
|
|
246
|
+
return args.find((arg) => typeof arg === "function");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function withCallableDefaults(value, callable) {
|
|
250
|
+
if (!callable) {
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
...value,
|
|
255
|
+
handler: typeof value.handler === "function" ? value.handler : callable,
|
|
256
|
+
run: typeof value.run === "function" ? value.run : callable,
|
|
257
|
+
execute: typeof value.execute === "function" ? value.execute : callable,
|
|
258
|
+
};
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
function summarizeArguments(args) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { captureEntrypoint, writeArtifacts } from "./advanced.js";
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
await run(args);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error(error.message);
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function run(commandArgs) {
|
|
14
|
+
const entrypoint = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
15
|
+
const outputPath = readFlag(commandArgs, "--output");
|
|
16
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
17
|
+
const mockSdk = readMockSdkFlag(commandArgs) ?? true;
|
|
18
|
+
|
|
19
|
+
if (!entrypoint) {
|
|
20
|
+
throw new Error("capture requires an entrypoint path");
|
|
21
|
+
}
|
|
22
|
+
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
23
|
+
throw new Error("capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await captureEntrypoint(entrypoint, { mockSdk, pluginRoot });
|
|
27
|
+
const json = `${JSON.stringify(result, null, 2)}\n`;
|
|
28
|
+
if (outputPath) {
|
|
29
|
+
await writeArtifacts([{ path: outputPath, content: json }]);
|
|
30
|
+
} else {
|
|
31
|
+
process.stdout.write(json);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFlag(commandArgs, name) {
|
|
36
|
+
const index = commandArgs.indexOf(name);
|
|
37
|
+
if (index === -1) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return commandArgs[index + 1] ?? null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readMockSdkFlag(commandArgs) {
|
|
44
|
+
const sdk = readFlag(commandArgs, "--sdk");
|
|
45
|
+
if (sdk === "mock") {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (sdk === "real") {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (sdk && !["mock", "real"].includes(sdk)) {
|
|
52
|
+
throw new Error("--sdk must be mock or real");
|
|
53
|
+
}
|
|
54
|
+
if (commandArgs.includes("--mock-sdk")) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (commandArgs.includes("--real-sdk")) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
@@ -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/ci-summary.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readOptionalJsonFile } from "./json-file.js";
|
|
|
5
5
|
|
|
6
6
|
export const defaultCiReportPaths = {
|
|
7
7
|
compatibility: "reports/plugin-inspector-report.json",
|
|
8
|
-
capture: "reports/plugin-inspector-capture.json",
|
|
8
|
+
capture: "reports/plugin-inspector-runtime-capture.json",
|
|
9
9
|
synthetic: "reports/plugin-inspector-synthetic-probes.json",
|
|
10
10
|
coldImport: "reports/plugin-inspector-cold-import.json",
|
|
11
11
|
workspace: "reports/plugin-inspector-workspace-plan.json",
|