@openclaw/plugin-inspector 0.1.1 → 0.1.3
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 +25 -0
- package/README.md +82 -41
- package/examples/github-actions-plugin-inspector.yml +2 -2
- package/examples/plugin-inspector.config.json +3 -0
- package/package.json +1 -1
- package/src/advanced.js +10 -0
- package/src/api.js +14 -8
- package/src/capture-api.js +180 -12
- package/src/capture-cli.js +61 -0
- package/src/ci-summary.js +1 -1
- package/src/cli.js +138 -7
- package/src/config.js +16 -2
- package/src/contract-probes.js +5 -0
- package/src/fixture-summary.js +40 -16
- package/src/index.js +1 -0
- package/src/init.js +150 -0
- package/src/inspector.js +89 -14
- package/src/issues.js +8 -0
- package/src/mock-sdk-capture-runner.js +110 -17
- package/src/openclaw-target.js +27 -0
- package/src/report.js +1 -0
- package/src/runtime-capture-report.js +11 -1
- package/src/sdk-mock.js +1266 -13
- package/src/synthetic-probes-cli.js +101 -0
- package/src/synthetic-probes.js +153 -9
- package/src/workspace-plan.js +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add reserved bundled-plugin SDK import detection so external plugins get explicit compatibility findings for private OpenClaw SDK shims.
|
|
8
|
+
- Add packaged workspace capture and synthetic-probe helper CLIs for generated isolated workspace plans.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Make `plugin-inspector ci` write compatibility-backed CI summary artifacts instead of the legacy inventory report.
|
|
13
|
+
- Default packaged helper captures to the mocked OpenClaw SDK while preserving `--real-sdk` opt-in behavior.
|
|
14
|
+
- Detect the default runtime capture artifact at `reports/plugin-inspector-runtime-capture.json`.
|
|
15
|
+
- Report the actual log count in CLI text summaries.
|
|
16
|
+
|
|
17
|
+
## 0.1.2 - 2026-04-27
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Add public SDK subpath mocks, registrar return profiles, richer lifecycle capture probes, and plugin-root `init`/`--plugin-root` CLI flows.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Harden runtime capture for TypeScript entrypoints, extensionless local imports, mocked external packages, namespace imports, async output capture, and explicit mock/real SDK lanes.
|
|
26
|
+
- Refresh README and CI examples around `npx @openclaw/plugin-inspector check --no-openclaw` and opt-in runtime capture.
|
|
27
|
+
|
|
3
28
|
## 0.1.1 - 2026-04-27
|
|
4
29
|
|
|
5
30
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
<img src="docs/plugin-inspector-banner.jpg" alt="openclaw plugin inspector banner"/>
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# OpenClaw Plugin Inspector
|
|
4
4
|
|
|
5
|
-
`plugin-inspector` is the
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`plugin-inspector` is the offline compatibility check for OpenClaw plugins. Run
|
|
6
|
+
it from a plugin root to inspect package metadata, `openclaw.plugin.json`, SDK
|
|
7
|
+
imports, `api.on(...)`, `api.register*`, and optional runtime registration
|
|
8
|
+
capture.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Quick Start
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
From a plugin package directory:
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
|
-
|
|
15
|
+
npx @openclaw/plugin-inspector
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
That runs `check`, writes report artifacts to `reports/`, and exits non-zero
|
|
19
|
+
when compatibility breakages are found.
|
|
20
|
+
|
|
21
|
+
Add a local config and GitHub Actions workflow:
|
|
18
22
|
|
|
19
23
|
```bash
|
|
20
|
-
npx @openclaw/plugin-inspector
|
|
24
|
+
npx @openclaw/plugin-inspector init --ci
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
Or install it as a dev dependency:
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
```bash
|
|
30
|
+
npm install --save-dev @openclaw/plugin-inspector
|
|
31
|
+
npx plugin-inspector check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
26
35
|
|
|
27
36
|
```bash
|
|
28
|
-
plugin-inspector check
|
|
37
|
+
npx @openclaw/plugin-inspector check
|
|
38
|
+
npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
|
|
39
|
+
npx @openclaw/plugin-inspector init --ci --package-manager pnpm
|
|
29
40
|
```
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`api.register*`, and writes:
|
|
42
|
+
`check` reads the current directory as one plugin unless `--plugin-root` is set.
|
|
43
|
+
It writes:
|
|
34
44
|
|
|
35
45
|
- `reports/plugin-inspector-report.json`
|
|
36
46
|
- `reports/plugin-inspector-report.md`
|
|
@@ -43,8 +53,8 @@ checkout:
|
|
|
43
53
|
plugin-inspector check --no-openclaw
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
Use
|
|
47
|
-
expected seams:
|
|
56
|
+
Use `plugin-inspector.config.json` when CI needs stable fixture metadata,
|
|
57
|
+
expected seams, or runtime capture defaults:
|
|
48
58
|
|
|
49
59
|
```json
|
|
50
60
|
{
|
|
@@ -58,6 +68,9 @@ expected seams:
|
|
|
58
68
|
"registrations": ["registerTool"]
|
|
59
69
|
}
|
|
60
70
|
},
|
|
71
|
+
"capture": {
|
|
72
|
+
"mockSdk": true
|
|
73
|
+
},
|
|
61
74
|
"openclaw": {
|
|
62
75
|
"defaultCheckoutPath": "../openclaw"
|
|
63
76
|
}
|
|
@@ -70,48 +83,51 @@ Then run:
|
|
|
70
83
|
plugin-inspector check --config plugin-inspector.config.json
|
|
71
84
|
```
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
`init --ci` writes this shape for you, plus
|
|
87
|
+
`.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
|
|
88
|
+
`examples/plugin-inspector.config.json` and
|
|
74
89
|
`examples/github-actions-plugin-inspector.yml`.
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
plugin-inspector report --config crabpot.config.json --out reports
|
|
80
|
-
```
|
|
91
|
+
## Runtime Capture
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
Runtime capture imports plugin entrypoints in an isolated subprocess and records
|
|
94
|
+
the registrations made during `register(api)`. It is opt-in because it executes
|
|
95
|
+
plugin code:
|
|
83
96
|
|
|
84
97
|
```bash
|
|
85
|
-
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector
|
|
98
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --runtime --mock-sdk
|
|
86
99
|
```
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```
|
|
101
|
+
By default, runtime capture uses a generated mock for `openclaw/plugin-sdk` and
|
|
102
|
+
common external packages so plugin code can load in clean CI without OpenClaw
|
|
103
|
+
installed. Use `--real-sdk` only when the plugin workspace already has real SDK
|
|
104
|
+
dependencies installed and you intentionally want to test that path.
|
|
93
105
|
|
|
94
|
-
Runtime capture
|
|
95
|
-
declared OpenClaw package entrypoints, calls their `register(api)` function with
|
|
96
|
-
the capture API, and writes:
|
|
106
|
+
Runtime capture writes:
|
|
97
107
|
|
|
98
108
|
- `reports/plugin-inspector-runtime-capture.json`
|
|
99
109
|
- `reports/plugin-inspector-runtime-capture.md`
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
You can also capture one entrypoint directly:
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
```bash
|
|
114
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture ./dist/index.js --mock-sdk
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## CI
|
|
118
|
+
|
|
119
|
+
Minimal package scripts:
|
|
104
120
|
|
|
105
121
|
```json
|
|
106
122
|
{
|
|
107
123
|
"scripts": {
|
|
108
124
|
"plugin:check": "plugin-inspector check --no-openclaw",
|
|
109
|
-
"plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --
|
|
125
|
+
"plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --runtime --mock-sdk"
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
128
|
```
|
|
113
129
|
|
|
114
|
-
GitHub Actions:
|
|
130
|
+
GitHub Actions without a local dev dependency:
|
|
115
131
|
|
|
116
132
|
```yaml
|
|
117
133
|
name: plugin-inspector
|
|
@@ -131,8 +147,8 @@ jobs:
|
|
|
131
147
|
node-version: 24
|
|
132
148
|
cache: npm
|
|
133
149
|
- run: npm ci
|
|
134
|
-
- run:
|
|
135
|
-
- run:
|
|
150
|
+
- run: npx @openclaw/plugin-inspector check --no-openclaw
|
|
151
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
136
152
|
- uses: actions/upload-artifact@v5
|
|
137
153
|
if: always()
|
|
138
154
|
with:
|
|
@@ -140,6 +156,31 @@ jobs:
|
|
|
140
156
|
path: reports/plugin-inspector-*
|
|
141
157
|
```
|
|
142
158
|
|
|
159
|
+
## Fixture Suites
|
|
160
|
+
|
|
161
|
+
Fixture-set configs are still supported for crabpot-style compatibility suites:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
plugin-inspector report --config crabpot.config.json --out reports
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use fixture suites when one repo wants to inspect many plugins. Use plugin-root
|
|
168
|
+
`check` for normal plugin CI.
|
|
169
|
+
|
|
170
|
+
## Mocking Model
|
|
171
|
+
|
|
172
|
+
Default inspection is static, offline, and credential-free. Runtime capture is
|
|
173
|
+
the only mode that imports plugin code.
|
|
174
|
+
|
|
175
|
+
When `--mock-sdk` is enabled, the inspector generates temporary modules for
|
|
176
|
+
`openclaw/plugin-sdk` subpaths and unresolved external packages discovered in
|
|
177
|
+
the plugin import graph. The mock SDK captures registrations; it does not call
|
|
178
|
+
network services, launch OpenClaw, run provider SDKs, or emulate service
|
|
179
|
+
lifecycle side effects.
|
|
180
|
+
|
|
181
|
+
Use the mock lane for plugin compatibility CI. Keep live provider/service tests
|
|
182
|
+
in the plugin repo behind their own credentials and explicit opt-in flags.
|
|
183
|
+
|
|
143
184
|
## Scope
|
|
144
185
|
|
|
145
186
|
Default inspection is offline and credential-free. It reads manifests, package
|
|
@@ -148,5 +189,5 @@ metadata, and source files, then reports observed `api.on(...)`,
|
|
|
148
189
|
OpenClaw target checkout parsing is limited to public compatibility registries,
|
|
149
190
|
SDK package exports, manifest types, hooks, and captured registrar metadata.
|
|
150
191
|
|
|
151
|
-
Cold import capture
|
|
152
|
-
Live lanes
|
|
192
|
+
Cold import capture, synthetic contract probes, and runtime capture are explicit
|
|
193
|
+
opt-in modes. Live lanes stay credential-gated and must never run in default CI.
|
|
@@ -15,8 +15,8 @@ jobs:
|
|
|
15
15
|
node-version: 24
|
|
16
16
|
cache: npm
|
|
17
17
|
- run: npm ci
|
|
18
|
-
- run:
|
|
19
|
-
- run:
|
|
18
|
+
- run: npx @openclaw/plugin-inspector check --no-openclaw
|
|
19
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
20
20
|
- uses: actions/upload-artifact@v5
|
|
21
21
|
if: always()
|
|
22
22
|
with:
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -100,6 +100,7 @@ export {
|
|
|
100
100
|
openClawTargetPathCandidates,
|
|
101
101
|
parseCompatRecordEntries,
|
|
102
102
|
parseExportedStringArray,
|
|
103
|
+
parsePluginSdkEntrypointSpecifiers,
|
|
103
104
|
parsePluginSdkExports,
|
|
104
105
|
parseTypeFields,
|
|
105
106
|
readOpenClawTargetSurface,
|
|
@@ -116,12 +117,21 @@ export {
|
|
|
116
117
|
defaultPluginRootConfigFiles,
|
|
117
118
|
fixtureCheckoutPath,
|
|
118
119
|
fixtureSourceRoot,
|
|
120
|
+
inferPluginSeams,
|
|
119
121
|
loadInspectorConfig,
|
|
120
122
|
loadPluginRootConfig,
|
|
121
123
|
normalizeInspectorConfig,
|
|
122
124
|
normalizePluginRootConfig,
|
|
125
|
+
packageId,
|
|
123
126
|
validateInspectorConfig,
|
|
124
127
|
} from "./config.js";
|
|
128
|
+
export {
|
|
129
|
+
buildPluginInspectorConfig,
|
|
130
|
+
defaultInitConfigPath,
|
|
131
|
+
defaultInitWorkflowPath,
|
|
132
|
+
renderGithubActionsWorkflow,
|
|
133
|
+
writePluginInspectorInit,
|
|
134
|
+
} from "./init.js";
|
|
125
135
|
export {
|
|
126
136
|
buildPlatformProbes,
|
|
127
137
|
defaultPlatformTargets,
|
package/src/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createCaptureApi } from "./capture-api.js";
|
|
3
3
|
import { loadInspectorConfig, loadPluginRootConfig } from "./config.js";
|
|
4
|
+
import { writePluginInspectorInit } from "./init.js";
|
|
4
5
|
import { captureEntrypoint } from "./inspector.js";
|
|
5
6
|
import { renderTextSummary, writeCompatibilityReport } from "./report.js";
|
|
6
7
|
import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
|
|
@@ -45,24 +46,25 @@ export async function writePluginReports(report, options = {}) {
|
|
|
45
46
|
|
|
46
47
|
export async function runPluginCheck(options = {}) {
|
|
47
48
|
const outDir = options.outDir ?? "reports";
|
|
48
|
-
const
|
|
49
|
-
const
|
|
49
|
+
const config = await loadPluginConfig(options);
|
|
50
|
+
const report = await inspectPluginRoot({ ...options, config });
|
|
51
|
+
const paths = await writePluginReports(report, { ...options, pluginRoot: config.rootDir, outDir });
|
|
50
52
|
const result = { report, paths };
|
|
53
|
+
const capture = options.capture ?? config.capture?.runtime ?? false;
|
|
54
|
+
const mockSdk = options.mockSdk ?? config.capture?.mockSdk ?? true;
|
|
51
55
|
|
|
52
|
-
if (
|
|
56
|
+
if (capture === true) {
|
|
53
57
|
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
54
58
|
throw new Error("runtime capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
55
59
|
}
|
|
56
|
-
const config = await loadPluginConfig(options);
|
|
57
60
|
const runtimeCapture = await buildRuntimeCaptureReport({
|
|
58
|
-
mockSdk
|
|
61
|
+
mockSdk,
|
|
59
62
|
report,
|
|
60
63
|
rootDir: config.rootDir,
|
|
61
64
|
});
|
|
62
|
-
const outputRoot = options.cwd ?? options.pluginRoot ?? process.cwd();
|
|
63
65
|
const runtimeCapturePaths = await writeRuntimeCaptureReport(runtimeCapture, {
|
|
64
|
-
jsonPath: path.resolve(
|
|
65
|
-
markdownPath: path.resolve(
|
|
66
|
+
jsonPath: path.resolve(config.rootDir, outDir, "plugin-inspector-runtime-capture.json"),
|
|
67
|
+
markdownPath: path.resolve(config.rootDir, outDir, "plugin-inspector-runtime-capture.md"),
|
|
66
68
|
});
|
|
67
69
|
result.runtimeCapture = runtimeCapture;
|
|
68
70
|
result.runtimeCapturePaths = runtimeCapturePaths;
|
|
@@ -82,4 +84,8 @@ export async function capturePluginEntrypoint(entrypoint, options = {}) {
|
|
|
82
84
|
return captureEntrypoint(entrypoint, options);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
export async function setupPluginInspector(options = {}) {
|
|
88
|
+
return writePluginInspectorInit(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
export { createCaptureApi, renderTextSummary };
|
package/src/capture-api.js
CHANGED
|
@@ -1,15 +1,68 @@
|
|
|
1
|
+
export const defaultCaptureApiRegistrarProfiles = {
|
|
2
|
+
registerChannel: {
|
|
3
|
+
returnValue: ({ args }) => registrationObject(args, { id: "channel" }),
|
|
4
|
+
},
|
|
5
|
+
registerCli: {
|
|
6
|
+
returnValue: ({ args }) => registrationObject(args, { name: "cli" }),
|
|
7
|
+
},
|
|
8
|
+
registerCommand: {
|
|
9
|
+
returnValue: ({ args }) => registrationObject(args, { name: "command" }),
|
|
10
|
+
},
|
|
11
|
+
registerContextEngine: {
|
|
12
|
+
returnValue: ({ args }) => registrationObject(args, { id: "context-engine" }),
|
|
13
|
+
},
|
|
14
|
+
registerGatewayMethod: {
|
|
15
|
+
returnValue: ({ args }) => registrationObject(args, { name: "gateway.method" }),
|
|
16
|
+
},
|
|
17
|
+
registerHook: {
|
|
18
|
+
returnValue: ({ api }) => api,
|
|
19
|
+
},
|
|
20
|
+
registerHttpRoute: {
|
|
21
|
+
returnValue: ({ args }) => ({
|
|
22
|
+
...registrationObject(args, { method: "GET", path: "/" }),
|
|
23
|
+
unregister() {},
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
registerInteractiveHandler: {
|
|
27
|
+
returnValue: ({ args }) => registrationObject(args, { id: "interactive-handler" }),
|
|
28
|
+
},
|
|
29
|
+
registerMemoryPromptSection: {
|
|
30
|
+
returnValue: ({ args }) => registrationObject(args, { id: "memory-prompt-section" }),
|
|
31
|
+
},
|
|
32
|
+
registerMemoryRuntime: {
|
|
33
|
+
returnValue: ({ args }) => registrationObject(args, { id: "memory-runtime" }),
|
|
34
|
+
},
|
|
35
|
+
registerProvider: {
|
|
36
|
+
returnValue: ({ args }) => registrationObject(args, { id: "provider" }),
|
|
37
|
+
},
|
|
38
|
+
registerService: {
|
|
39
|
+
returnValue: ({ args }) => ({
|
|
40
|
+
...registrationObject(args, { name: "service" }),
|
|
41
|
+
start: async () => undefined,
|
|
42
|
+
stop: async () => undefined,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
registerSpeechProvider: {
|
|
46
|
+
returnValue: ({ args }) => registrationObject(args, { id: "speech-provider" }),
|
|
47
|
+
},
|
|
48
|
+
registerTool: {
|
|
49
|
+
returnValue: ({ args }) => registrationObject(args, { name: "tool" }),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
1
53
|
export function createCaptureApi(options = {}) {
|
|
2
54
|
const captured = [];
|
|
3
55
|
const retained = [];
|
|
4
|
-
const
|
|
56
|
+
const registrarProfiles = {
|
|
57
|
+
...defaultCaptureApiRegistrarProfiles,
|
|
58
|
+
...(options.registrarProfiles ?? {}),
|
|
59
|
+
};
|
|
60
|
+
const knownRegistrars = new Set(options.knownRegistrars ?? Object.keys(registrarProfiles));
|
|
5
61
|
const retainHandlers = options.retainHandlers === true;
|
|
6
62
|
|
|
7
63
|
const api = new Proxy(
|
|
8
64
|
{
|
|
9
|
-
|
|
10
|
-
logger: options.logger ?? console,
|
|
11
|
-
pluginConfig: options.pluginConfig ?? {},
|
|
12
|
-
runtime: options.runtime ?? {},
|
|
65
|
+
...createCaptureContext(options),
|
|
13
66
|
on(name, handler) {
|
|
14
67
|
const captureIndex =
|
|
15
68
|
captured.push({
|
|
@@ -42,6 +95,7 @@ export function createCaptureApi(options = {}) {
|
|
|
42
95
|
}
|
|
43
96
|
if (typeof property === "string" && isRegistrarProperty(property)) {
|
|
44
97
|
return (...args) => {
|
|
98
|
+
const returnValue = registrationReturnValue(property, args, { api, registrarProfiles });
|
|
45
99
|
const captureIndex =
|
|
46
100
|
captured.push({
|
|
47
101
|
kind: "registration",
|
|
@@ -54,10 +108,11 @@ export function createCaptureApi(options = {}) {
|
|
|
54
108
|
kind: "registration",
|
|
55
109
|
name: property,
|
|
56
110
|
arguments: args,
|
|
111
|
+
returnValue,
|
|
57
112
|
captureIndex,
|
|
58
113
|
});
|
|
59
114
|
}
|
|
60
|
-
return
|
|
115
|
+
return returnValue;
|
|
61
116
|
};
|
|
62
117
|
}
|
|
63
118
|
return undefined;
|
|
@@ -68,19 +123,122 @@ export function createCaptureApi(options = {}) {
|
|
|
68
123
|
return api;
|
|
69
124
|
}
|
|
70
125
|
|
|
126
|
+
export function createCaptureContext(options = {}) {
|
|
127
|
+
return {
|
|
128
|
+
config: options.config ?? {},
|
|
129
|
+
logger: options.logger ?? console,
|
|
130
|
+
pluginConfig: options.pluginConfig ?? {},
|
|
131
|
+
runtime: options.runtime ?? createRuntimeContext(options),
|
|
132
|
+
secrets: options.secrets ?? createSecretContext(options),
|
|
133
|
+
store: options.store ?? createStoreContext(options),
|
|
134
|
+
paths: options.paths ?? {
|
|
135
|
+
cacheDir: ".plugin-inspector/cache",
|
|
136
|
+
configDir: ".plugin-inspector/config",
|
|
137
|
+
dataDir: ".plugin-inspector/data",
|
|
138
|
+
},
|
|
139
|
+
agent: options.agent ?? {
|
|
140
|
+
id: "plugin-inspector-agent",
|
|
141
|
+
accountId: "default",
|
|
142
|
+
},
|
|
143
|
+
gateway: options.gateway ?? {
|
|
144
|
+
baseUrl: "http://127.0.0.1:0",
|
|
145
|
+
registerRoute(route) {
|
|
146
|
+
return {
|
|
147
|
+
...route,
|
|
148
|
+
unregister() {},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
fetch: options.fetch ?? (async () => ({ ok: true, status: 200, json: async () => ({}), text: async () => "" })),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
71
156
|
function isRegistrarProperty(property) {
|
|
72
157
|
return property.startsWith("register") || property.startsWith("define");
|
|
73
158
|
}
|
|
74
159
|
|
|
75
|
-
function registrationReturnValue(name, args) {
|
|
76
|
-
|
|
160
|
+
function registrationReturnValue(name, args, context) {
|
|
161
|
+
const profile = context.registrarProfiles[name];
|
|
162
|
+
if (profile?.returnValue) {
|
|
163
|
+
return profile.returnValue({ name, args, api: context.api });
|
|
164
|
+
}
|
|
165
|
+
return registrationObject(args, {});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createRuntimeContext(options) {
|
|
169
|
+
const runtime = options.runtime ?? {};
|
|
170
|
+
return {
|
|
171
|
+
...runtime,
|
|
172
|
+
agent: runtime.agent ?? {},
|
|
173
|
+
env: options.env ?? {},
|
|
174
|
+
logger: options.logger ?? console,
|
|
175
|
+
now: () => new Date(0),
|
|
176
|
+
tts: runtime.tts ?? {},
|
|
177
|
+
state: {
|
|
178
|
+
resolveStateDir: () => options.stateDir ?? process.cwd(),
|
|
179
|
+
...(runtime.state ?? {}),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function createSecretContext(options) {
|
|
185
|
+
const secrets = new Map(Object.entries(options.secretValues ?? {}));
|
|
186
|
+
return {
|
|
187
|
+
async get(name) {
|
|
188
|
+
return secrets.get(name) ?? null;
|
|
189
|
+
},
|
|
190
|
+
async has(name) {
|
|
191
|
+
return secrets.has(name);
|
|
192
|
+
},
|
|
193
|
+
async require(name) {
|
|
194
|
+
if (!secrets.has(name)) {
|
|
195
|
+
throw new Error(`Missing mocked secret: ${name}`);
|
|
196
|
+
}
|
|
197
|
+
return secrets.get(name);
|
|
198
|
+
},
|
|
199
|
+
async resolve(value) {
|
|
200
|
+
return typeof value === "string" && value.startsWith("secret:") ? (secrets.get(value.slice(7)) ?? null) : value;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createStoreContext() {
|
|
206
|
+
const values = new Map();
|
|
207
|
+
return {
|
|
208
|
+
async delete(key) {
|
|
209
|
+
return values.delete(key);
|
|
210
|
+
},
|
|
211
|
+
async get(key) {
|
|
212
|
+
return values.get(key);
|
|
213
|
+
},
|
|
214
|
+
async list() {
|
|
215
|
+
return [...values.keys()].sort();
|
|
216
|
+
},
|
|
217
|
+
async set(key, value) {
|
|
218
|
+
values.set(key, value);
|
|
219
|
+
return value;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function registrationObject(args, defaults) {
|
|
225
|
+
const first = args[0];
|
|
226
|
+
if (first && typeof first === "object") {
|
|
77
227
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
228
|
+
...defaults,
|
|
229
|
+
...first,
|
|
230
|
+
name: objectName(first) ?? defaults.name,
|
|
231
|
+
id: objectId(first) ?? defaults.id,
|
|
81
232
|
};
|
|
82
233
|
}
|
|
83
|
-
|
|
234
|
+
if (typeof first === "string") {
|
|
235
|
+
return {
|
|
236
|
+
...defaults,
|
|
237
|
+
name: first,
|
|
238
|
+
id: defaults.id,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return { ...defaults };
|
|
84
242
|
}
|
|
85
243
|
|
|
86
244
|
function summarizeArguments(args) {
|
|
@@ -113,3 +271,13 @@ function objectName(value) {
|
|
|
113
271
|
}
|
|
114
272
|
return typeof value.id === "string" ? value.id : null;
|
|
115
273
|
}
|
|
274
|
+
|
|
275
|
+
function objectId(value) {
|
|
276
|
+
if (!value || typeof value !== "object") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
if (typeof value.id === "string") {
|
|
280
|
+
return value.id;
|
|
281
|
+
}
|
|
282
|
+
return typeof value.name === "string" ? value.name : null;
|
|
283
|
+
}
|
|
@@ -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
|
+
}
|
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",
|