build(deps): internalize bundled plugin runtime deps

This commit is contained in:
Peter Steinberger
2026-05-02 09:39:11 +01:00
parent 56a2e42437
commit 5eabb6e697
4 changed files with 232 additions and 0 deletions

View File

@@ -135,6 +135,57 @@ function collectExtensionDependencyDeclarations(repoRoot) {
return declarations;
}
function collectExcludedPackagedExtensionDirs(rootPackageJson) {
const excluded = new Set();
for (const entry of rootPackageJson.files ?? []) {
if (typeof entry !== "string") {
continue;
}
const match = /^!dist\/extensions\/([^/]+)\/\*\*$/u.exec(entry);
if (match?.[1]) {
excluded.add(match[1]);
}
}
return excluded;
}
function collectInternalizedBundledExtensionRuntimeDependencies(repoRoot, rootPackageJson) {
const dependencies = new Map();
const extensionsRoot = path.join(repoRoot, "extensions");
if (!fs.existsSync(extensionsRoot)) {
return dependencies;
}
const excluded = collectExcludedPackagedExtensionDirs(rootPackageJson);
for (const entry of fs.readdirSync(extensionsRoot, { withFileTypes: true })) {
if (!entry.isDirectory() || excluded.has(entry.name)) {
continue;
}
const packageJsonPath = path.join(extensionsRoot, entry.name, "package.json");
const manifestPath = path.join(extensionsRoot, entry.name, "openclaw.plugin.json");
if (!fs.existsSync(packageJsonPath) || !fs.existsSync(manifestPath)) {
continue;
}
const packageJson = readJson(packageJsonPath);
if (packageJson?.openclaw?.bundle?.includeInCore === false) {
continue;
}
for (const section of ["dependencies", "optionalDependencies"]) {
for (const depName of Object.keys(packageJson[section] ?? {})) {
const existing = dependencies.get(depName) ?? [];
existing.push(`${entry.name}:${section}`);
dependencies.set(depName, existing);
}
}
}
for (const values of dependencies.values()) {
values.sort((left, right) => left.localeCompare(right));
}
return dependencies;
}
function sectionSetContainsCore(sectionSet) {
return sectionSet.has("src") || sectionSet.has("packages") || sectionSet.has("ui");
}
@@ -190,6 +241,16 @@ export function classifyRootDependencyOwnership(record) {
};
}
if (
record.internalizedBundledRuntimeOwners?.length > 0 &&
sectionSetIsSubsetOf(sections, new Set(["extensions", "test"]))
) {
return {
category: "root_owned_extension_runtime",
recommendation: `keep at root while bundled plugin runtime dependencies are internalized; owners: ${record.internalizedBundledRuntimeOwners.join(", ")}`,
};
}
if (sectionSetIsSubsetOf(sections, new Set(["extensions", "test"]))) {
return {
category: "extension_only_localizable",
@@ -219,6 +280,7 @@ export function collectRootDependencyOwnershipAudit(params = {}) {
sections: new Set(),
files: new Set(),
declaredInExtensions: [],
internalizedBundledRuntimeOwners: [],
spec: rootDependencies[depName],
},
]),
@@ -249,6 +311,15 @@ export function collectRootDependencyOwnershipAudit(params = {}) {
}
}
const internalizedBundledRuntimeDependencies =
collectInternalizedBundledExtensionRuntimeDependencies(repoRoot, rootPackageJson);
for (const [depName, owners] of internalizedBundledRuntimeDependencies) {
const record = records.get(depName);
if (record) {
record.internalizedBundledRuntimeOwners = owners;
}
}
return [...records.values()]
.map((record) => {
const classification = classifyRootDependencyOwnership({
@@ -262,6 +333,7 @@ export function collectRootDependencyOwnershipAudit(params = {}) {
fileCount: record.files.size,
sampleFiles: [...record.files].slice(0, 5),
declaredInExtensions: record.declaredInExtensions,
internalizedBundledRuntimeOwners: record.internalizedBundledRuntimeOwners,
category: classification.category,
recommendation: classification.recommendation,
};
@@ -301,6 +373,9 @@ function printTextReport(records) {
if (record.declaredInExtensions.length > 0) {
details.push(`extensions=${record.declaredInExtensions.join(",")}`);
}
if (record.internalizedBundledRuntimeOwners.length > 0) {
details.push(`internalized=${record.internalizedBundledRuntimeOwners.join(",")}`);
}
console.log(`- ${record.depName}@${record.spec} :: ${details.join(" | ")}`);
console.log(` ${record.recommendation}`);
}