@openclaw/plugin-inspector 0.0.0 → 0.1.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 +21 -0
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/examples/github-actions-plugin-inspector.yml +24 -0
- package/examples/plugin-inspector.config.json +15 -0
- package/package.json +56 -3
- package/src/advanced.js +186 -0
- package/src/api.js +85 -0
- 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 +113 -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 +10 -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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Refresh npm package docs with the simplified CLI-first README and public API surface cleanup.
|
|
8
|
+
|
|
9
|
+
## 0.1.0 - 2026-04-27
|
|
10
|
+
|
|
11
|
+
Initial public package release for `@openclaw/plugin-inspector`.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Plugin-root `plugin-inspector check` command with optional `plugin-inspector.config.json`.
|
|
16
|
+
- Static OpenClaw plugin compatibility reports, issue reports, and CI policy summaries.
|
|
17
|
+
- Crabpot-compatible fixture-set inspection and report assembly APIs.
|
|
18
|
+
- Target OpenClaw surface parsing for compat registry records, hook names, registrar names, SDK exports, and manifest type fields.
|
|
19
|
+
- Package metadata, manifest, SDK import, hook, registration, runtime-capture, cold-import, synthetic-probe, runtime-profile, ref-diff, and profile-diff report helpers.
|
|
20
|
+
- Optional `PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --capture` runtime registration capture using a temporary mocked `openclaw/plugin-sdk`.
|
|
21
|
+
- Copy-ready config and GitHub Actions examples under `examples/`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenClaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
<img src="docs/plugin-inspector-banner.jpg" alt="openclaw plugin inspector banner"/>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 💊 OpenClaw Plugin Inspector
|
|
4
|
+
|
|
5
|
+
`plugin-inspector` is the reusable OpenClaw plugin compatibility inspector. It
|
|
6
|
+
wraps the static inspection, registration capture, and report model prototyped
|
|
7
|
+
in crabpot into an npm-publishable package.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Install it as a dev dependency in a plugin repo:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --save-dev @openclaw/plugin-inspector
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then run it from the plugin root:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @openclaw/plugin-inspector check
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## CLI
|
|
24
|
+
|
|
25
|
+
Run the default plugin-root check from a plugin package directory:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
plugin-inspector check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That command reads the current directory as one plugin, inspects package
|
|
32
|
+
metadata, `openclaw.plugin.json`, source imports, `api.on(...)`,
|
|
33
|
+
`api.register*`, and writes:
|
|
34
|
+
|
|
35
|
+
- `reports/plugin-inspector-report.json`
|
|
36
|
+
- `reports/plugin-inspector-report.md`
|
|
37
|
+
- `reports/plugin-inspector-issues.md`
|
|
38
|
+
|
|
39
|
+
Use `--no-openclaw` when CI should not compare against a local OpenClaw
|
|
40
|
+
checkout:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
plugin-inspector check --no-openclaw
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use a simple plugin-root config when you want stable fixture metadata or
|
|
47
|
+
expected seams:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"version": 1,
|
|
52
|
+
"plugin": {
|
|
53
|
+
"id": "weather",
|
|
54
|
+
"priority": "high",
|
|
55
|
+
"seams": ["dynamic-tool"],
|
|
56
|
+
"sourceRoot": "src",
|
|
57
|
+
"expect": {
|
|
58
|
+
"registrations": ["registerTool"]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"openclaw": {
|
|
62
|
+
"defaultCheckoutPath": "../openclaw"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
plugin-inspector check --config plugin-inspector.config.json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Copy-ready examples live in `examples/plugin-inspector.config.json` and
|
|
74
|
+
`examples/github-actions-plugin-inspector.yml`.
|
|
75
|
+
|
|
76
|
+
Fixture-set configs are still supported for crabpot-style compatibility suites:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
plugin-inspector report --config crabpot.config.json --out reports
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Capture a plugin entrypoint in an explicitly isolated execution lane:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture ./dist/index.js --mock-sdk
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Run the optional runtime capture smoke during `check`:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --capture
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Runtime capture creates a temporary mock `openclaw/plugin-sdk` package, imports
|
|
95
|
+
declared OpenClaw package entrypoints, calls their `register(api)` function with
|
|
96
|
+
the capture API, and writes:
|
|
97
|
+
|
|
98
|
+
- `reports/plugin-inspector-runtime-capture.json`
|
|
99
|
+
- `reports/plugin-inspector-runtime-capture.md`
|
|
100
|
+
|
|
101
|
+
### CI
|
|
102
|
+
|
|
103
|
+
With a dev dependency:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"scripts": {
|
|
108
|
+
"plugin:check": "plugin-inspector check --no-openclaw",
|
|
109
|
+
"plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --capture"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
GitHub Actions:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
name: plugin-inspector
|
|
118
|
+
|
|
119
|
+
on:
|
|
120
|
+
pull_request:
|
|
121
|
+
push:
|
|
122
|
+
branches: [main]
|
|
123
|
+
|
|
124
|
+
jobs:
|
|
125
|
+
check:
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
steps:
|
|
128
|
+
- uses: actions/checkout@v5
|
|
129
|
+
- uses: actions/setup-node@v5
|
|
130
|
+
with:
|
|
131
|
+
node-version: 24
|
|
132
|
+
cache: npm
|
|
133
|
+
- run: npm ci
|
|
134
|
+
- run: npm run plugin:check
|
|
135
|
+
- run: npm run plugin:check:runtime
|
|
136
|
+
- uses: actions/upload-artifact@v5
|
|
137
|
+
if: always()
|
|
138
|
+
with:
|
|
139
|
+
name: plugin-inspector-reports
|
|
140
|
+
path: reports/plugin-inspector-*
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Scope
|
|
144
|
+
|
|
145
|
+
Default inspection is offline and credential-free. It reads manifests, package
|
|
146
|
+
metadata, and source files, then reports observed `api.on(...)`,
|
|
147
|
+
`api.register*`, `define*`, SDK imports, and manifest contracts.
|
|
148
|
+
OpenClaw target checkout parsing is limited to public compatibility registries,
|
|
149
|
+
SDK package exports, manifest types, hooks, and captured registrar metadata.
|
|
150
|
+
|
|
151
|
+
Cold import capture and synthetic contract probes are explicit opt-in modes.
|
|
152
|
+
Live lanes will stay credential-gated and must never run in default CI.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: plugin-inspector
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v5
|
|
13
|
+
- uses: actions/setup-node@v5
|
|
14
|
+
with:
|
|
15
|
+
node-version: 24
|
|
16
|
+
cache: npm
|
|
17
|
+
- run: npm ci
|
|
18
|
+
- run: npm run plugin:check
|
|
19
|
+
- run: npm run plugin:check:runtime
|
|
20
|
+
- uses: actions/upload-artifact@v5
|
|
21
|
+
if: always()
|
|
22
|
+
with:
|
|
23
|
+
name: plugin-inspector-reports
|
|
24
|
+
path: reports/plugin-inspector-*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"plugin": {
|
|
4
|
+
"id": "weather",
|
|
5
|
+
"priority": "high",
|
|
6
|
+
"seams": ["dynamic-tool"],
|
|
7
|
+
"sourceRoot": "src",
|
|
8
|
+
"expect": {
|
|
9
|
+
"registrations": ["registerTool"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"openclaw": {
|
|
13
|
+
"defaultCheckoutPath": "../openclaw"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/plugin-inspector",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Offline compatibility inspector for OpenClaw plugins.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "OpenClaw",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/openclaw/plugin-inspector.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/openclaw/plugin-inspector/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/openclaw/plugin-inspector#readme",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"plugin-inspector": "src/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": "./src/index.js",
|
|
25
|
+
"./advanced": "./src/advanced.js",
|
|
26
|
+
"./capture-api": "./src/capture-api.js",
|
|
27
|
+
"./ci-policy": "./src/ci-policy.js",
|
|
28
|
+
"./cold-import-readiness": "./src/cold-import-readiness.js",
|
|
29
|
+
"./contract-capture": "./src/contract-capture.js",
|
|
30
|
+
"./contract-coverage": "./src/contract-coverage.js",
|
|
31
|
+
"./execution-results": "./src/execution-results.js",
|
|
32
|
+
"./import-loop-profile": "./src/import-loop-profile.js",
|
|
33
|
+
"./openclaw-target": "./src/openclaw-target.js",
|
|
34
|
+
"./platform-probes": "./src/platform-probes.js",
|
|
35
|
+
"./profile-diff": "./src/profile-diff.js",
|
|
36
|
+
"./ref-diff": "./src/ref-diff.js",
|
|
37
|
+
"./runtime-capture-report": "./src/runtime-capture-report.js",
|
|
38
|
+
"./runtime-profile": "./src/runtime-profile.js",
|
|
39
|
+
"./workspace-plan": "./src/workspace-plan.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"src",
|
|
43
|
+
"examples",
|
|
44
|
+
"README.md",
|
|
45
|
+
"CHANGELOG.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"check": "npm test && npm pack --dry-run",
|
|
50
|
+
"release:local": "npm run check",
|
|
51
|
+
"test": "node --test test/*.test.js"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"openclaw",
|
|
55
|
+
"plugin",
|
|
56
|
+
"compatibility",
|
|
57
|
+
"ci"
|
|
58
|
+
]
|
|
6
59
|
}
|
package/src/advanced.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
export {
|
|
2
|
+
escapeMarkdownTableCell,
|
|
3
|
+
renderArtifactContent,
|
|
4
|
+
renderMarkdownTable,
|
|
5
|
+
renderPaddedMarkdownTable,
|
|
6
|
+
writeArtifacts,
|
|
7
|
+
writeJsonMarkdownArtifacts,
|
|
8
|
+
} from "./artifacts.js";
|
|
9
|
+
export {
|
|
10
|
+
normalizeRepoPath,
|
|
11
|
+
posixJoin,
|
|
12
|
+
resolveFromRoot,
|
|
13
|
+
resolveRequiredFromRoot,
|
|
14
|
+
slugForArtifact,
|
|
15
|
+
toRepoPath,
|
|
16
|
+
} from "./path-utils.js";
|
|
17
|
+
export { readJsonFile, readOptionalJsonFile } from "./json-file.js";
|
|
18
|
+
export { assertRunCount, percentile } from "./stats.js";
|
|
19
|
+
export { createCaptureApi } from "./capture-api.js";
|
|
20
|
+
export {
|
|
21
|
+
buildCiPolicyReport,
|
|
22
|
+
defaultCiPolicyReportOptions,
|
|
23
|
+
renderCiPolicyMarkdown,
|
|
24
|
+
validateCiPolicy,
|
|
25
|
+
validateCiPolicyReport,
|
|
26
|
+
writeCiPolicyReport,
|
|
27
|
+
} from "./ci-policy.js";
|
|
28
|
+
export {
|
|
29
|
+
buildCiSummary,
|
|
30
|
+
defaultCiReportPaths,
|
|
31
|
+
deriveCiStatus,
|
|
32
|
+
readCiReports,
|
|
33
|
+
renderCiSummaryMarkdown,
|
|
34
|
+
writeCiSummary,
|
|
35
|
+
} from "./ci-summary.js";
|
|
36
|
+
export {
|
|
37
|
+
buildContractProbes,
|
|
38
|
+
contractProbeRules,
|
|
39
|
+
probePriority,
|
|
40
|
+
} from "./contract-probes.js";
|
|
41
|
+
export {
|
|
42
|
+
buildContractCapture,
|
|
43
|
+
defaultHookAssertions,
|
|
44
|
+
defaultHookContexts,
|
|
45
|
+
defaultHookEvents,
|
|
46
|
+
defaultRegistrationArguments,
|
|
47
|
+
defaultRegistrationAssertions,
|
|
48
|
+
renderContractCaptureMarkdown,
|
|
49
|
+
validateContractCapture,
|
|
50
|
+
writeContractCapture,
|
|
51
|
+
} from "./contract-capture.js";
|
|
52
|
+
export {
|
|
53
|
+
renderCompatibilityIssuesReport,
|
|
54
|
+
renderCompatibilityMarkdownReport,
|
|
55
|
+
} from "./compatibility-report.js";
|
|
56
|
+
export {
|
|
57
|
+
knownIssueClasses,
|
|
58
|
+
validateContractCoverage,
|
|
59
|
+
} from "./contract-coverage.js";
|
|
60
|
+
export {
|
|
61
|
+
buildColdImportReadiness,
|
|
62
|
+
renderColdImportReadinessMarkdown,
|
|
63
|
+
validateColdImportReadiness,
|
|
64
|
+
writeColdImportReadiness,
|
|
65
|
+
} from "./cold-import-readiness.js";
|
|
66
|
+
export {
|
|
67
|
+
buildIssues,
|
|
68
|
+
classifyIssueFinding,
|
|
69
|
+
deprecatedCompatRecords,
|
|
70
|
+
issueId,
|
|
71
|
+
issueMetadata,
|
|
72
|
+
issueMetadataByCode,
|
|
73
|
+
knownIssueCodes,
|
|
74
|
+
summarizeIssueClasses,
|
|
75
|
+
} from "./issues.js";
|
|
76
|
+
export {
|
|
77
|
+
buildExecutionResultsReport,
|
|
78
|
+
defaultExecutionResultsOptions,
|
|
79
|
+
renderExecutionResultsMarkdown,
|
|
80
|
+
writeExecutionResultsReport,
|
|
81
|
+
} from "./execution-results.js";
|
|
82
|
+
export {
|
|
83
|
+
buildCompatibilityFixtureReport,
|
|
84
|
+
classifyCompatibilityFixture,
|
|
85
|
+
classifyPackageContracts,
|
|
86
|
+
classifyTargetOpenClawCoverage,
|
|
87
|
+
readPackageSummaries,
|
|
88
|
+
readPluginManifests,
|
|
89
|
+
summarizePackage,
|
|
90
|
+
} from "./fixture-summary.js";
|
|
91
|
+
export {
|
|
92
|
+
buildImportLoopProfile,
|
|
93
|
+
defaultImportLoopProfileOptions,
|
|
94
|
+
renderImportLoopProfileMarkdown,
|
|
95
|
+
validateImportLoopProfile,
|
|
96
|
+
writeImportLoopProfile,
|
|
97
|
+
} from "./import-loop-profile.js";
|
|
98
|
+
export {
|
|
99
|
+
defaultOpenClawCheckoutPaths,
|
|
100
|
+
openClawTargetPathCandidates,
|
|
101
|
+
parseCompatRecordEntries,
|
|
102
|
+
parseExportedStringArray,
|
|
103
|
+
parsePluginSdkExports,
|
|
104
|
+
parseTypeFields,
|
|
105
|
+
readOpenClawTargetSurface,
|
|
106
|
+
} from "./openclaw-target.js";
|
|
107
|
+
export {
|
|
108
|
+
captureEntrypoint,
|
|
109
|
+
captureEntrypointWithMockSdk,
|
|
110
|
+
inspectCompatibilityFixtureSet,
|
|
111
|
+
inspectFixtureSet,
|
|
112
|
+
inspectPlugin,
|
|
113
|
+
inspectSourceText,
|
|
114
|
+
} from "./inspector.js";
|
|
115
|
+
export {
|
|
116
|
+
defaultPluginRootConfigFiles,
|
|
117
|
+
fixtureCheckoutPath,
|
|
118
|
+
fixtureSourceRoot,
|
|
119
|
+
loadInspectorConfig,
|
|
120
|
+
loadPluginRootConfig,
|
|
121
|
+
normalizeInspectorConfig,
|
|
122
|
+
normalizePluginRootConfig,
|
|
123
|
+
validateInspectorConfig,
|
|
124
|
+
} from "./config.js";
|
|
125
|
+
export {
|
|
126
|
+
buildPlatformProbes,
|
|
127
|
+
defaultPlatformTargets,
|
|
128
|
+
renderPlatformProbesMarkdown,
|
|
129
|
+
validatePlatformProbes,
|
|
130
|
+
writePlatformProbes,
|
|
131
|
+
} from "./platform-probes.js";
|
|
132
|
+
export {
|
|
133
|
+
buildProfileDiff,
|
|
134
|
+
defaultProfileDiffOptions,
|
|
135
|
+
renderProfileDiffMarkdown,
|
|
136
|
+
validateProfileDiff,
|
|
137
|
+
writeProfileDiff,
|
|
138
|
+
} from "./profile-diff.js";
|
|
139
|
+
export {
|
|
140
|
+
buildRefDiff,
|
|
141
|
+
defaultRefDiffDimensions,
|
|
142
|
+
defaultRefDiffOptions,
|
|
143
|
+
renderRefDiffMarkdown,
|
|
144
|
+
validateRefDiff,
|
|
145
|
+
writeRefDiff,
|
|
146
|
+
} from "./ref-diff.js";
|
|
147
|
+
export {
|
|
148
|
+
buildCompatibilityReport,
|
|
149
|
+
classifyCompatRecordCoverage,
|
|
150
|
+
renderMarkdownReport,
|
|
151
|
+
renderTextSummary,
|
|
152
|
+
writeCompatibilityReport,
|
|
153
|
+
writeReport,
|
|
154
|
+
} from "./report.js";
|
|
155
|
+
export {
|
|
156
|
+
buildRuntimeProfile,
|
|
157
|
+
defaultRuntimeProfileCommands,
|
|
158
|
+
defaultRuntimeProfileOptions,
|
|
159
|
+
renderRuntimeProfileMarkdown,
|
|
160
|
+
validateRuntimeProfile,
|
|
161
|
+
writeRuntimeProfile,
|
|
162
|
+
} from "./runtime-profile.js";
|
|
163
|
+
export {
|
|
164
|
+
buildRuntimeCaptureReport,
|
|
165
|
+
renderRuntimeCaptureMarkdown,
|
|
166
|
+
writeRuntimeCaptureReport,
|
|
167
|
+
} from "./runtime-capture-report.js";
|
|
168
|
+
export { createMockSdkPackage } from "./sdk-mock.js";
|
|
169
|
+
export {
|
|
170
|
+
buildSyntheticProbePlan,
|
|
171
|
+
defaultSyntheticHookContexts,
|
|
172
|
+
defaultSyntheticHookEvents,
|
|
173
|
+
defaultSyntheticRegistrationArguments,
|
|
174
|
+
renderSyntheticProbeMarkdown,
|
|
175
|
+
runCapturedSyntheticProbes,
|
|
176
|
+
syntheticRegistrationExecutionProfiles,
|
|
177
|
+
validateSyntheticProbePlan,
|
|
178
|
+
writeSyntheticProbePlan,
|
|
179
|
+
} from "./synthetic-probes.js";
|
|
180
|
+
export {
|
|
181
|
+
buildWorkspacePlan,
|
|
182
|
+
defaultWorkspacePlanOptions,
|
|
183
|
+
renderWorkspacePlanMarkdown,
|
|
184
|
+
validateWorkspacePlan,
|
|
185
|
+
writeWorkspacePlan,
|
|
186
|
+
} from "./workspace-plan.js";
|
package/src/api.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createCaptureApi } from "./capture-api.js";
|
|
3
|
+
import { loadInspectorConfig, loadPluginRootConfig } from "./config.js";
|
|
4
|
+
import { captureEntrypoint } from "./inspector.js";
|
|
5
|
+
import { renderTextSummary, writeCompatibilityReport } from "./report.js";
|
|
6
|
+
import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
|
|
7
|
+
import { inspectCompatibilityFixtureSet, inspectFixtureSet } from "./inspector.js";
|
|
8
|
+
|
|
9
|
+
export async function loadPluginConfig(options = {}) {
|
|
10
|
+
if (options.config) {
|
|
11
|
+
return options.config;
|
|
12
|
+
}
|
|
13
|
+
const cwd = options.pluginRoot ?? options.cwd;
|
|
14
|
+
if (options.configPath) {
|
|
15
|
+
return options.fixtureSet === true
|
|
16
|
+
? loadInspectorConfig(options.configPath, { cwd })
|
|
17
|
+
: loadPluginRootConfig(options.configPath, { cwd });
|
|
18
|
+
}
|
|
19
|
+
return loadPluginRootConfig(null, { cwd });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function inspectPluginRoot(options = {}) {
|
|
23
|
+
const config = await loadPluginConfig(options);
|
|
24
|
+
return inspectCompatibilityFixtureSet(config, {
|
|
25
|
+
generatedAt: options.generatedAt,
|
|
26
|
+
openclawPath: options.openclawPath,
|
|
27
|
+
targetOpenClaw: options.targetOpenClaw,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function inspectFixtureSetConfig(options = {}) {
|
|
32
|
+
const config = options.config ?? (await loadInspectorConfig(options.configPath, { cwd: options.cwd }));
|
|
33
|
+
return inspectFixtureSet(config, { generatedAt: options.generatedAt });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function writePluginReports(report, options = {}) {
|
|
37
|
+
return writeCompatibilityReport(report, {
|
|
38
|
+
basename: options.basename,
|
|
39
|
+
check: options.check,
|
|
40
|
+
cwd: options.cwd ?? options.pluginRoot,
|
|
41
|
+
issuesBasename: options.issuesBasename,
|
|
42
|
+
outDir: options.outDir,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function runPluginCheck(options = {}) {
|
|
47
|
+
const outDir = options.outDir ?? "reports";
|
|
48
|
+
const report = await inspectPluginRoot(options);
|
|
49
|
+
const paths = await writePluginReports(report, { ...options, outDir });
|
|
50
|
+
const result = { report, paths };
|
|
51
|
+
|
|
52
|
+
if (options.capture === true) {
|
|
53
|
+
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
54
|
+
throw new Error("runtime capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
55
|
+
}
|
|
56
|
+
const config = await loadPluginConfig(options);
|
|
57
|
+
const runtimeCapture = await buildRuntimeCaptureReport({
|
|
58
|
+
mockSdk: options.mockSdk ?? true,
|
|
59
|
+
report,
|
|
60
|
+
rootDir: config.rootDir,
|
|
61
|
+
});
|
|
62
|
+
const outputRoot = options.cwd ?? options.pluginRoot ?? process.cwd();
|
|
63
|
+
const runtimeCapturePaths = await writeRuntimeCaptureReport(runtimeCapture, {
|
|
64
|
+
jsonPath: path.resolve(outputRoot, outDir, "plugin-inspector-runtime-capture.json"),
|
|
65
|
+
markdownPath: path.resolve(outputRoot, outDir, "plugin-inspector-runtime-capture.md"),
|
|
66
|
+
});
|
|
67
|
+
result.runtimeCapture = runtimeCapture;
|
|
68
|
+
result.runtimeCapturePaths = runtimeCapturePaths;
|
|
69
|
+
if (runtimeCapture.summary.failedCount > 0) {
|
|
70
|
+
throw new Error(`plugin-inspector runtime capture failed for ${runtimeCapture.summary.failedCount} entrypoints`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.failOnBreakages === true && report.status !== "pass") {
|
|
75
|
+
throw new Error(`plugin-inspector found ${report.summary.breakageCount} breakages`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function capturePluginEntrypoint(entrypoint, options = {}) {
|
|
82
|
+
return captureEntrypoint(entrypoint, options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { createCaptureApi, renderTextSummary };
|
package/src/artifacts.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function writeArtifacts(artifacts, options = {}) {
|
|
5
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0) {
|
|
6
|
+
throw new TypeError("writeArtifacts requires at least one artifact");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const written = {};
|
|
10
|
+
for (const artifact of artifacts) {
|
|
11
|
+
const artifactPath = artifact.path;
|
|
12
|
+
if (!artifactPath) {
|
|
13
|
+
throw new TypeError("artifact.path is required");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const content = renderArtifactContent(artifact);
|
|
17
|
+
await mkdir(path.dirname(artifactPath), { recursive: true });
|
|
18
|
+
await writeFile(artifactPath, content, "utf8");
|
|
19
|
+
|
|
20
|
+
if (options.check || artifact.check) {
|
|
21
|
+
await assertFileMatches(artifactPath, content);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (artifact.name) {
|
|
25
|
+
written[artifact.name] = artifactPath;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return written;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function writeJsonMarkdownArtifacts({ jsonPath, markdownPath, json, markdown, check = false }) {
|
|
33
|
+
await writeArtifacts(
|
|
34
|
+
[
|
|
35
|
+
{ name: "jsonPath", path: jsonPath, json },
|
|
36
|
+
{ name: "markdownPath", path: markdownPath, markdown },
|
|
37
|
+
],
|
|
38
|
+
{ check },
|
|
39
|
+
);
|
|
40
|
+
return { jsonPath, markdownPath };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function renderArtifactContent(artifact) {
|
|
44
|
+
if ("content" in artifact) {
|
|
45
|
+
return String(artifact.content);
|
|
46
|
+
}
|
|
47
|
+
if ("json" in artifact) {
|
|
48
|
+
return `${JSON.stringify(artifact.json, null, 2)}\n`;
|
|
49
|
+
}
|
|
50
|
+
if ("markdown" in artifact) {
|
|
51
|
+
return `${artifact.markdown}\n`;
|
|
52
|
+
}
|
|
53
|
+
throw new TypeError("artifact must provide content, json, or markdown");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderMarkdownTable(rows, headers, options = {}) {
|
|
57
|
+
if (rows.length === 0 && options.empty != null) {
|
|
58
|
+
return options.empty;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const nullValue = options.nullValue ?? "";
|
|
62
|
+
const escape = options.escape !== false;
|
|
63
|
+
const normalizedRows = [headers, ...rows].map((row) =>
|
|
64
|
+
row.map((cell) => {
|
|
65
|
+
const value = String(cell ?? nullValue);
|
|
66
|
+
return escape ? escapeMarkdownTableCell(value) : value;
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (options.padding) {
|
|
71
|
+
const widths = headers.map((_, columnIndex) =>
|
|
72
|
+
Math.max(...normalizedRows.map((row) => row[columnIndex].length)),
|
|
73
|
+
);
|
|
74
|
+
const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
|
|
75
|
+
return [
|
|
76
|
+
renderRow(normalizedRows[0]),
|
|
77
|
+
renderRow(widths.map((width) => "-".repeat(width))),
|
|
78
|
+
...normalizedRows.slice(1).map(renderRow),
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const separator = headers.map(() => options.separator ?? "---");
|
|
83
|
+
return [normalizedRows[0], separator, ...normalizedRows.slice(1)]
|
|
84
|
+
.map((row) => `| ${row.join(" | ")} |`)
|
|
85
|
+
.join("\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function renderPaddedMarkdownTable(rows, headers, options = {}) {
|
|
89
|
+
return renderMarkdownTable(rows, headers, {
|
|
90
|
+
empty: "_none_",
|
|
91
|
+
escape: false,
|
|
92
|
+
padding: true,
|
|
93
|
+
...options,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function escapeMarkdownTableCell(value) {
|
|
98
|
+
return value.replace(/\|/g, "\\|").replace(/\n/g, "<br>");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function assertFileMatches(filePath, expected) {
|
|
102
|
+
try {
|
|
103
|
+
const actual = await readFile(filePath, "utf8");
|
|
104
|
+
if (actual !== expected) {
|
|
105
|
+
throw new Error(`${filePath} is not up to date`);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error?.code === "ENOENT") {
|
|
109
|
+
throw new Error(`${filePath} is missing`);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|