diff --git a/src/fixture-summary.js b/src/fixture-summary.js index ddae968..9022aeb 100644 --- a/src/fixture-summary.js +++ b/src/fixture-summary.js @@ -1102,8 +1102,13 @@ function packageNpmPackIssues(packageSummary, fixtureReport) { }); } - const missingEntrypoints = packageSummary.openclaw?.entrypoints - .filter((entrypoint) => !repoPathIncludedInNpmPack(packageSummary, entrypoint.relativePath)) + const entrypoints = packageSummary.openclaw?.entrypoints ?? []; + const missingEntrypoints = entrypoints + .filter( + (entrypoint) => + !repoPathIncludedInNpmPack(packageSummary, entrypoint.relativePath) && + !hasPackagedRuntimeEntrypoint(entrypoint, packageSummary, entrypoints), + ) .map((entrypoint) => `${entrypoint.kind}:${entrypoint.specifier} -> ${entrypoint.relativePath}`) ?? []; if (missingEntrypoints.length > 0) { findings.push({ @@ -1131,6 +1136,35 @@ function packageNpmPackMissingMetadata(packageSummary, fixtureReport) { return missing; } +function hasPackagedRuntimeEntrypoint(entrypoint, packageSummary, entrypoints) { + if (!isSourceEntrypoint(entrypoint.specifier)) { + return false; + } + + const runtimeBuildSpecifier = runtimeBuildSpecifierFor(entrypoint.specifier); + const matchingRuntimeEntrypoint = entrypoints.find( + (candidate) => + candidate.requiresBuild && + normalizeEntrypointSpecifier(candidate.specifier) === normalizeEntrypointSpecifier(runtimeBuildSpecifier), + ); + if (matchingRuntimeEntrypoint && repoPathIncludedInNpmPack(packageSummary, matchingRuntimeEntrypoint.relativePath)) { + return true; + } + + if ( + entrypoint.kind === "extension" && + entrypoints.some( + (candidate) => candidate.kind === "runtimeExtension" && repoPathIncludedInNpmPack(packageSummary, candidate.relativePath), + ) + ) { + return true; + } + + const packageDir = path.posix.dirname(normalizeRepoPath(packageSummary.path)); + const runtimeBuildPath = path.posix.join(packageDir === "." ? "" : packageDir, normalizeEntrypointSpecifier(runtimeBuildSpecifier)); + return repoPathIncludedInNpmPack(packageSummary, runtimeBuildPath); +} + function packageMinHostVersionDrift(packageSummary) { const openclaw = packageSummary.openclaw; if (!nonEmptyString(openclaw?.install?.minHostVersion) || !nonEmptyString(openclaw?.buildOpenClawVersion)) { diff --git a/test/report.test.js b/test/report.test.js index e47ee8a..a42dde4 100644 --- a/test/report.test.js +++ b/test/report.test.js @@ -880,6 +880,13 @@ test("package contract classifier accepts built runtime entries for source packa path: "plugins/fixture/package.json", name: "@openclaw/fixture-plugin", version: "1.0.0", + npmPack: { + advertised: true, + private: false, + filesMode: "allowlist", + files: ["dist/**", "openclaw.plugin.json"], + invalidFileSpecs: [], + }, dependencies: [], peerDependencies: ["openclaw"], optionalDependencies: [], @@ -906,6 +913,20 @@ test("package contract classifier accepts built runtime entries for source packa exists: true, requiresBuild: true, }, + { + kind: "setupEntry", + specifier: "./setup-entry.ts", + relativePath: "plugins/fixture/setup-entry.ts", + exists: false, + requiresBuild: false, + }, + { + kind: "runtimeExtension", + specifier: "./dist/setup-entry.js", + relativePath: "plugins/fixture/dist/setup-entry.js", + exists: true, + requiresBuild: true, + }, ], }, }, @@ -913,6 +934,7 @@ test("package contract classifier accepts built runtime entries for source packa }); assert.equal(result.warnings.some((finding) => finding.code === "package-entrypoint-missing"), false); + assert.equal(result.warnings.some((finding) => finding.code === "package-npm-pack-entrypoint-missing"), false); assert.equal(result.decisions.some((decision) => decision.seam === "package-entrypoint"), false); });