From 1c94019fb7db892021d1ff32e858642e13b166ec Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 27 Apr 2026 15:07:36 -0700 Subject: [PATCH] fix(init): infer source root from export maps --- CHANGELOG.md | 1 + src/init.js | 24 ++++++++++++++++++++++-- test/api.test.js | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c4af89..4c25ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Make generated CI workflows use one `plugin-inspector ci --no-openclaw --runtime --mock-sdk` command. - Make `plugin-inspector init --ci` detect `packageManager` and common lockfiles before generating CI install/run commands. - Make `plugin-inspector init` output repo-relative file paths and preflight generated files before writing. +- Make `plugin-inspector init` infer `sourceRoot: "src"` from package export maps like `"./src/index.js"`. - Harden runtime capture for string handler registrations, parse-capable config schema helpers, and provider auth/catalog SDK mocks. ## 0.1.3 - 2026-04-27 diff --git a/src/init.js b/src/init.js index 1663fb5..80f2c9e 100644 --- a/src/init.js +++ b/src/init.js @@ -143,14 +143,34 @@ function inferSourceRoot(packageJson) { packageJson?.openclaw?.entrypoint, ...(packageJson?.openclaw?.extensions ?? []), ...(packageJson?.openclaw?.runtimeExtensions ?? []), + ...entrypointStrings(packageJson?.exports?.["."]), + ...entrypointStrings(packageJson?.exports), + packageJson?.module, + packageJson?.main, ].filter((value) => typeof value === "string"); - const entrypoint = entrypoints[0] ?? packageJson?.exports?.["."] ?? packageJson?.main ?? "src/index.js"; - if (typeof entrypoint === "string" && entrypoint.startsWith("src/")) { + const entrypoint = entrypoints[0] ?? "src/index.js"; + if (stripRelativePrefix(entrypoint).startsWith("src/")) { return "src"; } return "."; } +function entrypointStrings(value) { + if (typeof value === "string") { + return [value]; + } + if (!value || typeof value !== "object" || Array.isArray(value)) { + return []; + } + return ["import", "default", "require", "node", "module"] + .map((key) => value[key]) + .filter((item) => typeof item === "string"); +} + +function stripRelativePrefix(filePath) { + return filePath.replace(/^\.\//, ""); +} + async function readJsonIfExists(filePath) { if (!existsSync(filePath)) { return null; diff --git a/test/api.test.js b/test/api.test.js index 4e51a1e..ce7b1b5 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -144,6 +144,24 @@ test("public API can initialize plugin inspector files", async () => { assert.match(workflow, /npx @openclaw\/plugin-inspector ci --no-openclaw --runtime --mock-sdk/); }); +test("public API initializes source root from package export maps", async () => { + const pluginRoot = await createPluginRoot({ + packageJson: { + openclaw: null, + exports: { + ".": { + import: "./src/plugin-entry.js", + }, + }, + }, + }); + + await setupPluginInspector({ pluginRoot, force: true }); + const config = JSON.parse(await readFile(path.join(pluginRoot, "plugin-inspector.config.json"), "utf8")); + + assert.equal(config.plugin.sourceRoot, "src"); +}); + test("fixture-set issue renderer is available without advanced internals", () => { const markdown = renderFixtureSetIssuesReport( { @@ -204,7 +222,11 @@ async function createPluginRoot(options = {}) { extensions: ["src/index.js"], compat: { pluginApi: "^1.0.0" }, }, + ...(options.packageJson ?? {}), }; + if (packageJson.openclaw === null) { + delete packageJson.openclaw; + } if (options.packageConfig) { packageJson.pluginInspector = { version: 1,