@openclaw/plugin-inspector 0.1.2 → 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 +14 -0
- package/package.json +1 -1
- package/src/advanced.js +1 -0
- package/src/capture-cli.js +61 -0
- package/src/ci-summary.js +1 -1
- package/src/cli.js +74 -2
- package/src/contract-probes.js +5 -0
- package/src/fixture-summary.js +40 -16
- package/src/issues.js +8 -0
- package/src/openclaw-target.js +27 -0
- package/src/report.js +1 -0
- package/src/synthetic-probes-cli.js +101 -0
- package/src/workspace-plan.js +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## 0.1.2 - 2026-04-27
|
|
4
18
|
|
|
5
19
|
### Added
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -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",
|
package/src/cli.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
renderTextSummary,
|
|
4
5
|
runPluginCheck,
|
|
5
6
|
} from "./index.js";
|
|
6
7
|
import {
|
|
8
|
+
buildCiSummary,
|
|
7
9
|
captureEntrypoint,
|
|
10
|
+
inspectCompatibilityFixtureSet,
|
|
8
11
|
inspectFixtureSet,
|
|
9
12
|
loadInspectorConfig,
|
|
13
|
+
writeCiSummary,
|
|
14
|
+
writeCompatibilityReport,
|
|
10
15
|
writePluginInspectorInit,
|
|
11
16
|
writeArtifacts,
|
|
12
17
|
writeReport,
|
|
@@ -23,8 +28,10 @@ try {
|
|
|
23
28
|
await runCheck(commandArgs);
|
|
24
29
|
} else if (command === "init") {
|
|
25
30
|
await runInit(commandArgs);
|
|
26
|
-
} else if (command === "inspect" || command === "report"
|
|
31
|
+
} else if (command === "inspect" || command === "report") {
|
|
27
32
|
await runReport(command, commandArgs);
|
|
33
|
+
} else if (command === "ci") {
|
|
34
|
+
await runCi(commandArgs);
|
|
28
35
|
} else if (command === "capture") {
|
|
29
36
|
await runCapture(commandArgs);
|
|
30
37
|
} else {
|
|
@@ -95,6 +102,62 @@ async function runReport(command, commandArgs) {
|
|
|
95
102
|
}
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
async function runCi(commandArgs) {
|
|
106
|
+
const configPath = readFlag(commandArgs, "--config");
|
|
107
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
108
|
+
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
109
|
+
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
110
|
+
const json = commandArgs.includes("--json");
|
|
111
|
+
const { report, reportDir } = await runCiCompatibilityReport({
|
|
112
|
+
configPath,
|
|
113
|
+
openclawPath,
|
|
114
|
+
outDir,
|
|
115
|
+
pluginRoot,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const summary = await buildCiSummary({
|
|
119
|
+
artifactBaseDir: reportDir,
|
|
120
|
+
reportPaths: {
|
|
121
|
+
compatibility: "plugin-inspector-report.json",
|
|
122
|
+
},
|
|
123
|
+
reports: {
|
|
124
|
+
compatibility: report,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
await writeCiSummary(summary, {
|
|
128
|
+
jsonPath: path.join(reportDir, "plugin-inspector-ci-summary.json"),
|
|
129
|
+
markdownPath: path.join(reportDir, "plugin-inspector-ci-summary.md"),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (json) {
|
|
133
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
134
|
+
} else {
|
|
135
|
+
console.log(renderCiTextSummary(summary));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (summary.status !== "pass") {
|
|
139
|
+
throw new Error("plugin-inspector ci summary failed");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function runCiCompatibilityReport({ configPath, openclawPath, outDir, pluginRoot }) {
|
|
144
|
+
if (configPath) {
|
|
145
|
+
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
146
|
+
const report = await inspectCompatibilityFixtureSet(config, { openclawPath });
|
|
147
|
+
await writeCompatibilityReport(report, { cwd: config.rootDir, outDir });
|
|
148
|
+
return {
|
|
149
|
+
report,
|
|
150
|
+
reportDir: path.resolve(config.rootDir, outDir),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const { report } = await runPluginCheck({ pluginRoot, outDir, openclawPath });
|
|
155
|
+
return {
|
|
156
|
+
report,
|
|
157
|
+
reportDir: path.resolve(pluginRoot ?? process.cwd(), outDir),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
98
161
|
async function runCapture(commandArgs) {
|
|
99
162
|
const entrypoint = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
100
163
|
const outputPath = readFlag(commandArgs, "--output");
|
|
@@ -154,6 +217,15 @@ function readMockSdkFlag(commandArgs) {
|
|
|
154
217
|
return undefined;
|
|
155
218
|
}
|
|
156
219
|
|
|
220
|
+
function renderCiTextSummary(summary) {
|
|
221
|
+
return [
|
|
222
|
+
`Status: ${summary.status.toUpperCase()}`,
|
|
223
|
+
`Breakages: ${summary.summary.breakages}`,
|
|
224
|
+
`Issues: ${summary.summary.issues}`,
|
|
225
|
+
`Artifacts: ${Object.values(summary.artifacts).filter(Boolean).length}`,
|
|
226
|
+
].join("\n");
|
|
227
|
+
}
|
|
228
|
+
|
|
157
229
|
function printHelp() {
|
|
158
230
|
console.log(`plugin-inspector
|
|
159
231
|
|
|
@@ -163,7 +235,7 @@ Usage:
|
|
|
163
235
|
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--package-manager npm|pnpm|yarn|bun] [--force]
|
|
164
236
|
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
165
237
|
plugin-inspector inspect --config <path> [--out <dir>] [--check] [--json]
|
|
166
|
-
plugin-inspector ci --config <path> [--out <dir>]
|
|
238
|
+
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--json]
|
|
167
239
|
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--plugin-root <path>] [--output <path>]
|
|
168
240
|
|
|
169
241
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
package/src/contract-probes.js
CHANGED
|
@@ -29,6 +29,11 @@ export const contractProbeRules = {
|
|
|
29
29
|
contract: "Every observed OpenClaw plugin SDK import remains exported by the target OpenClaw package.",
|
|
30
30
|
target: "sdk-alias",
|
|
31
31
|
},
|
|
32
|
+
"reserved-sdk-import": {
|
|
33
|
+
id: "sdk.import.reserved-bundled-plugin-boundary",
|
|
34
|
+
contract: "External plugins use documented public SDK subpaths instead of reserved bundled-plugin compatibility shims.",
|
|
35
|
+
target: "sdk-import",
|
|
36
|
+
},
|
|
32
37
|
"provider-auth-env-vars": {
|
|
33
38
|
id: "manifest.compat.provider-auth-env-vars",
|
|
34
39
|
contract: "Legacy provider auth env metadata continues to map into config/help surfaces.",
|
package/src/fixture-summary.js
CHANGED
|
@@ -626,7 +626,12 @@ function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, war
|
|
|
626
626
|
|
|
627
627
|
const sdkExports = new Set(targetOpenClaw.sdkExports);
|
|
628
628
|
const unknownImports = fixtureReport.sdkImportDetails.filter((sdkImport) => !sdkExports.has(sdkImport.specifier));
|
|
629
|
-
|
|
629
|
+
const reservedSdkExports = new Set(targetOpenClaw.reservedSdkExports ?? []);
|
|
630
|
+
const reservedImports = fixtureReport.sdkImportDetails.filter((sdkImport) =>
|
|
631
|
+
reservedSdkExports.has(sdkImport.specifier),
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
if (reservedImports.length === 0 && unknownImports.length === 0) {
|
|
630
635
|
logs.push({
|
|
631
636
|
fixture: fixture.id,
|
|
632
637
|
code: "sdk-exports-present",
|
|
@@ -637,21 +642,40 @@ function classifySdkImportCoverage({ fixture, fixtureReport, targetOpenClaw, war
|
|
|
637
642
|
return;
|
|
638
643
|
}
|
|
639
644
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
645
|
+
if (unknownImports.length > 0) {
|
|
646
|
+
warnings.push({
|
|
647
|
+
fixture: fixture.id,
|
|
648
|
+
code: "sdk-export-missing",
|
|
649
|
+
level: "warning",
|
|
650
|
+
message: "fixture imports plugin SDK aliases that are not exported by the target OpenClaw package",
|
|
651
|
+
evidence: detailEvidence(unknownImports, "specifier"),
|
|
652
|
+
compatRecord: "plugin-sdk-export-aliases",
|
|
653
|
+
});
|
|
654
|
+
decisions.push({
|
|
655
|
+
fixture: fixture.id,
|
|
656
|
+
decision: "core-compat-adapter",
|
|
657
|
+
seam: "sdk-alias",
|
|
658
|
+
action: "Restore the package export alias or publish a versioned migration map before cold-importing old plugins.",
|
|
659
|
+
evidence: unique(unknownImports.map((sdkImport) => sdkImport.specifier)).join(", "),
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (reservedImports.length > 0) {
|
|
664
|
+
warnings.push({
|
|
665
|
+
fixture: fixture.id,
|
|
666
|
+
code: "reserved-sdk-import",
|
|
667
|
+
level: "warning",
|
|
668
|
+
message: "fixture imports reserved bundled-plugin SDK compatibility subpaths",
|
|
669
|
+
evidence: detailEvidence(reservedImports, "specifier"),
|
|
670
|
+
});
|
|
671
|
+
decisions.push({
|
|
672
|
+
fixture: fixture.id,
|
|
673
|
+
decision: "plugin-upstream-fix",
|
|
674
|
+
seam: "sdk-import",
|
|
675
|
+
action: "Move the plugin to documented public SDK subpaths or plugin-local helpers before relying on this compatibility shim.",
|
|
676
|
+
evidence: unique(reservedImports.map((sdkImport) => sdkImport.specifier)).join(", "),
|
|
677
|
+
});
|
|
678
|
+
}
|
|
655
679
|
}
|
|
656
680
|
|
|
657
681
|
function classifyManifestFieldCoverage({ fixture, fixtureReport, targetOpenClaw, warnings, logs, decisions }) {
|
package/src/issues.js
CHANGED
|
@@ -32,6 +32,7 @@ export const knownIssueCodes = new Set([
|
|
|
32
32
|
"provider-auth-env-vars",
|
|
33
33
|
"registration-capture-gap",
|
|
34
34
|
"runtime-tool-capture",
|
|
35
|
+
"reserved-sdk-import",
|
|
35
36
|
"sdk-export-missing",
|
|
36
37
|
]);
|
|
37
38
|
|
|
@@ -78,6 +79,12 @@ export const issueMetadataByCode = {
|
|
|
78
79
|
decision: "core-compat-adapter",
|
|
79
80
|
title: "plugin SDK import aliases are missing from target package exports",
|
|
80
81
|
},
|
|
82
|
+
"reserved-sdk-import": {
|
|
83
|
+
severity: "P1",
|
|
84
|
+
owner: "plugin",
|
|
85
|
+
decision: "plugin-upstream-fix",
|
|
86
|
+
title: "plugin imports reserved bundled-plugin SDK compatibility subpaths",
|
|
87
|
+
},
|
|
81
88
|
"missing-compat-record": {
|
|
82
89
|
severity: "P1",
|
|
83
90
|
owner: "core",
|
|
@@ -310,6 +317,7 @@ function issueClassFor(code, options) {
|
|
|
310
317
|
"package-openclaw-entry-missing",
|
|
311
318
|
"package-openclaw-metadata-missing",
|
|
312
319
|
"package-plugin-api-compat-missing",
|
|
320
|
+
"reserved-sdk-import",
|
|
313
321
|
].includes(code)
|
|
314
322
|
) {
|
|
315
323
|
return "upstream-metadata";
|
package/src/openclaw-target.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
31
31
|
const apiBuilderPath = path.join(resolvedPath, "src/plugins/api-builder.ts");
|
|
32
32
|
const capturedRegistrationPath = path.join(resolvedPath, "src/plugins/captured-registration.ts");
|
|
33
33
|
const manifestTypesPath = path.join(resolvedPath, "src/plugins/manifest.ts");
|
|
34
|
+
const pluginSdkEntrypointsPath = path.join(resolvedPath, "src/plugin-sdk/entrypoints.ts");
|
|
34
35
|
const packagePath = path.join(resolvedPath, "package.json");
|
|
35
36
|
|
|
36
37
|
const registrySource = await readFile(registryPath, "utf8");
|
|
@@ -48,6 +49,18 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
48
49
|
const sdkExports = existsSync(packagePath)
|
|
49
50
|
? parsePluginSdkExports(JSON.parse(await readFile(packagePath, "utf8")))
|
|
50
51
|
: [];
|
|
52
|
+
const pluginSdkEntrypointsSource = existsSync(pluginSdkEntrypointsPath)
|
|
53
|
+
? await readFile(pluginSdkEntrypointsPath, "utf8")
|
|
54
|
+
: "";
|
|
55
|
+
const reservedSdkExports = pluginSdkEntrypointsSource
|
|
56
|
+
? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "reservedBundledPluginSdkEntrypoints")
|
|
57
|
+
: [];
|
|
58
|
+
const supportedFacadeSdkExports = pluginSdkEntrypointsSource
|
|
59
|
+
? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "supportedBundledFacadeSdkEntrypoints")
|
|
60
|
+
: [];
|
|
61
|
+
const publicPluginOwnedSdkExports = pluginSdkEntrypointsSource
|
|
62
|
+
? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "publicPluginOwnedSdkEntrypoints")
|
|
63
|
+
: [];
|
|
51
64
|
|
|
52
65
|
return {
|
|
53
66
|
configuredPath: requestedPath,
|
|
@@ -69,6 +82,13 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
69
82
|
packagePath: existsSync(packagePath) ? relativePath(rootDir, packagePath) : null,
|
|
70
83
|
sdkExportCount: sdkExports.length,
|
|
71
84
|
sdkExports,
|
|
85
|
+
pluginSdkEntrypointsPath: existsSync(pluginSdkEntrypointsPath)
|
|
86
|
+
? relativePath(rootDir, pluginSdkEntrypointsPath)
|
|
87
|
+
: null,
|
|
88
|
+
reservedSdkExportCount: reservedSdkExports.length,
|
|
89
|
+
reservedSdkExports,
|
|
90
|
+
supportedFacadeSdkExports,
|
|
91
|
+
publicPluginOwnedSdkExports,
|
|
72
92
|
manifestTypesPath: existsSync(manifestTypesPath) ? relativePath(rootDir, manifestTypesPath) : null,
|
|
73
93
|
manifestFieldCount: manifestFields.length,
|
|
74
94
|
manifestFields,
|
|
@@ -149,11 +169,18 @@ function emptyTargetSurface({ configuredPath, searchedPaths = undefined, status
|
|
|
149
169
|
apiRegistrars: [],
|
|
150
170
|
capturedRegistrars: [],
|
|
151
171
|
sdkExports: [],
|
|
172
|
+
reservedSdkExports: [],
|
|
173
|
+
supportedFacadeSdkExports: [],
|
|
174
|
+
publicPluginOwnedSdkExports: [],
|
|
152
175
|
manifestFields: [],
|
|
153
176
|
manifestContractFields: [],
|
|
154
177
|
};
|
|
155
178
|
}
|
|
156
179
|
|
|
180
|
+
export function parsePluginSdkEntrypointSpecifiers(source, exportName) {
|
|
181
|
+
return parseExportedStringArray(source, exportName).map((entrypoint) => `openclaw/plugin-sdk/${entrypoint}`).sort();
|
|
182
|
+
}
|
|
183
|
+
|
|
157
184
|
function parseCapturedRegistrars(source) {
|
|
158
185
|
return unique([...source.matchAll(/^\s*(register[A-Za-z0-9]+)\s*\(/gm)].map((match) => match[1])).sort();
|
|
159
186
|
}
|
package/src/report.js
CHANGED
|
@@ -160,6 +160,7 @@ export async function buildCompatibilityReport(options = {}) {
|
|
|
160
160
|
warningCount: warnings.length,
|
|
161
161
|
suggestionCount: suggestions.length,
|
|
162
162
|
decisionCount: decisions.length,
|
|
163
|
+
logCount: logs.length,
|
|
163
164
|
issueCount: issues.length,
|
|
164
165
|
p0IssueCount: issues.filter((issue) => issue.severity === "P0").length,
|
|
165
166
|
p1IssueCount: issues.filter((issue) => issue.severity === "P1").length,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { register } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { captureEntrypoint, runCapturedSyntheticProbes, writeArtifacts } from "./advanced.js";
|
|
8
|
+
import { createMockSdkPackage } from "./sdk-mock.js";
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await run(args);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(error.message);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function run(commandArgs) {
|
|
20
|
+
const entrypoint = readFlag(commandArgs, "--entrypoint") ?? commandArgs.find((arg) => !arg.startsWith("-"));
|
|
21
|
+
const outputPath = readFlag(commandArgs, "--output");
|
|
22
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
23
|
+
const includeLifecycle = commandArgs.includes("--include-lifecycle");
|
|
24
|
+
const includeChannelRuntime = commandArgs.includes("--include-channel-runtime");
|
|
25
|
+
const includeProviderCapabilities = commandArgs.includes("--include-provider-capabilities");
|
|
26
|
+
const mockSdk = readMockSdkFlag(commandArgs) ?? true;
|
|
27
|
+
|
|
28
|
+
if (!entrypoint) {
|
|
29
|
+
throw new Error("synthetic probes require --entrypoint <path>");
|
|
30
|
+
}
|
|
31
|
+
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
32
|
+
throw new Error("synthetic probes import plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const capture = await captureForSyntheticProbes(entrypoint, {
|
|
36
|
+
mockSdk,
|
|
37
|
+
pluginRoot,
|
|
38
|
+
apiOptions: { retainHandlers: true },
|
|
39
|
+
});
|
|
40
|
+
const results = await runCapturedSyntheticProbes(capture, {
|
|
41
|
+
includeLifecycle,
|
|
42
|
+
includeChannelRuntime,
|
|
43
|
+
includeProviderCapabilities,
|
|
44
|
+
});
|
|
45
|
+
const json = `${JSON.stringify(results, null, 2)}\n`;
|
|
46
|
+
|
|
47
|
+
if (outputPath) {
|
|
48
|
+
await writeArtifacts([{ path: outputPath, content: json }]);
|
|
49
|
+
} else {
|
|
50
|
+
process.stdout.write(json);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function captureForSyntheticProbes(entrypoint, options) {
|
|
55
|
+
if (options.mockSdk !== true) {
|
|
56
|
+
return captureEntrypoint(entrypoint, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const resolvedEntrypoint = path.resolve(process.cwd(), entrypoint);
|
|
60
|
+
const pluginRoot = path.resolve(process.cwd(), options.pluginRoot ?? path.dirname(resolvedEntrypoint));
|
|
61
|
+
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-synthetic-mock-sdk-"));
|
|
62
|
+
try {
|
|
63
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
64
|
+
register(pathToFileURL(loaderPath));
|
|
65
|
+
return captureEntrypoint(entrypoint, {
|
|
66
|
+
...options,
|
|
67
|
+
mockSdk: false,
|
|
68
|
+
pluginRoot,
|
|
69
|
+
});
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(workspace, { force: true, recursive: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readFlag(commandArgs, name) {
|
|
76
|
+
const index = commandArgs.indexOf(name);
|
|
77
|
+
if (index === -1) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return commandArgs[index + 1] ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readMockSdkFlag(commandArgs) {
|
|
84
|
+
const sdk = readFlag(commandArgs, "--sdk");
|
|
85
|
+
if (sdk === "mock") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (sdk === "real") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (sdk && !["mock", "real"].includes(sdk)) {
|
|
92
|
+
throw new Error("--sdk must be mock or real");
|
|
93
|
+
}
|
|
94
|
+
if (commandArgs.includes("--mock-sdk")) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (commandArgs.includes("--real-sdk")) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
package/src/workspace-plan.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
5
6
|
import { buildColdImportReadiness } from "./cold-import-readiness.js";
|
|
6
7
|
import { normalizeRepoPath, posixJoin, slugForArtifact } from "./path-utils.js";
|
|
7
8
|
|
|
8
9
|
export const defaultWorkspacePlanOptions = {
|
|
9
|
-
captureScript:
|
|
10
|
+
captureScript: null,
|
|
10
11
|
optInEnv: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1",
|
|
11
12
|
resultsRoot: ".plugin-inspector/results",
|
|
12
|
-
syntheticProbeScript:
|
|
13
|
+
syntheticProbeScript: null,
|
|
13
14
|
workspaceRoot: ".plugin-inspector/workspaces",
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -450,12 +451,23 @@ function runCommand(packageManager, script) {
|
|
|
450
451
|
|
|
451
452
|
function captureCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
452
453
|
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
453
|
-
|
|
454
|
+
const script = helperScript(settings, workspacePath, settings.captureScript, "capture-cli.js");
|
|
455
|
+
return `${settings.optInEnv} node${loader} ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
function syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
457
459
|
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
458
|
-
|
|
460
|
+
const script = helperScript(settings, workspacePath, settings.syntheticProbeScript, "synthetic-probes-cli.js");
|
|
461
|
+
return `${settings.optInEnv} node${loader} ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function helperScript(settings, workspacePath, configuredScript, helperFileName) {
|
|
465
|
+
if (configuredScript) {
|
|
466
|
+
return configuredScript;
|
|
467
|
+
}
|
|
468
|
+
const helperPath = fileURLToPath(new URL(`./${helperFileName}`, import.meta.url));
|
|
469
|
+
const workspaceFsPath = path.join(settings.rootDir, workspacePath);
|
|
470
|
+
return repoRelative(path.relative(workspaceFsPath, helperPath));
|
|
459
471
|
}
|
|
460
472
|
|
|
461
473
|
function targetOpenClawWorkspacePath(settings, fixtureId, targetOpenClawPath) {
|