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

@@ -1642,33 +1642,55 @@
},
"dependencies": {
"@agentclientprotocol/sdk": "0.21.0",
"@anthropic-ai/sdk": "0.92.0",
"@anthropic-ai/vertex-sdk": "^0.16.0",
"@aws-sdk/client-bedrock": "3.1041.0",
"@aws-sdk/client-bedrock-runtime": "3.1041.0",
"@aws-sdk/credential-provider-node": "3.972.39",
"@aws/bedrock-token-generator": "^1.1.0",
"@clack/prompts": "^1.3.0",
"@google/genai": "^1.51.0",
"@grammyjs/runner": "^2.0.3",
"@grammyjs/transformer-throttler": "^1.2.1",
"@homebridge/ciao": "^1.3.7",
"@lydell/node-pty": "1.2.0-beta.12",
"@mariozechner/pi-agent-core": "0.71.1",
"@mariozechner/pi-ai": "0.71.1",
"@mariozechner/pi-coding-agent": "0.71.1",
"@mariozechner/pi-tui": "0.71.1",
"@modelcontextprotocol/sdk": "1.29.0",
"@mozilla/readability": "^0.6.0",
"@slack/bolt": "^4.7.2",
"@slack/types": "^2.20.1",
"@slack/web-api": "^7.15.1",
"ajv": "^8.20.0",
"chalk": "^5.6.2",
"chokidar": "^5.0.0",
"commander": "^14.0.3",
"croner": "^10.0.1",
"dotenv": "^17.4.2",
"express": "5.2.1",
"file-type": "22.0.1",
"global-agent": "^4.1.3",
"grammy": "^1.42.0",
"https-proxy-agent": "^9.0.0",
"ipaddr.js": "^2.3.0",
"jiti": "^2.6.1",
"json5": "^2.2.3",
"jszip": "^3.10.1",
"linkedom": "^0.18.12",
"markdown-it": "14.1.1",
"minimatch": "10.2.5",
"node-edge-tts": "^1.2.10",
"openai": "^6.35.0",
"openshell": "0.1.0",
"pdfjs-dist": "^5.7.284",
"playwright-core": "1.59.1",
"proxy-agent": "^8.0.1",
"qrcode": "1.5.4",
"sqlite-vec": "0.1.9",
"tar": "7.5.13",
"tokenjuice": "0.7.0",
"tslog": "^4.10.2",
"typebox": "1.1.37",
"undici": "8.1.0",

66
pnpm-lock.yaml generated
View File

@@ -46,9 +46,39 @@ importers:
'@agentclientprotocol/sdk':
specifier: 0.21.0
version: 0.21.0(zod@4.4.1)
'@anthropic-ai/sdk':
specifier: 0.92.0
version: 0.92.0(zod@4.4.1)
'@anthropic-ai/vertex-sdk':
specifier: ^0.16.0
version: 0.16.0(zod@4.4.1)
'@aws-sdk/client-bedrock':
specifier: 3.1041.0
version: 3.1041.0
'@aws-sdk/client-bedrock-runtime':
specifier: 3.1024.0
version: 3.1024.0
'@aws-sdk/credential-provider-node':
specifier: 3.972.39
version: 3.972.39
'@aws/bedrock-token-generator':
specifier: ^1.1.0
version: 1.1.0
'@clack/prompts':
specifier: ^1.3.0
version: 1.3.0
'@google/genai':
specifier: ^1.51.0
version: 1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.1))
'@grammyjs/runner':
specifier: ^2.0.3
version: 2.0.3(grammy@1.42.0)
'@grammyjs/transformer-throttler':
specifier: ^1.2.1
version: 1.2.1(grammy@1.42.0)
'@homebridge/ciao':
specifier: ^1.3.7
version: 1.3.7
'@lydell/node-pty':
specifier: 1.2.0-beta.12
version: 1.2.0-beta.12
@@ -67,6 +97,18 @@ importers:
'@modelcontextprotocol/sdk':
specifier: 1.29.0
version: 1.29.0(zod@4.4.1)
'@mozilla/readability':
specifier: ^0.6.0
version: 0.6.0
'@slack/bolt':
specifier: ^4.7.2
version: 4.7.2(@types/express@5.0.6)
'@slack/types':
specifier: ^2.20.1
version: 2.20.1
'@slack/web-api':
specifier: ^7.15.1
version: 7.15.1
ajv:
specifier: ^8.20.0
version: 8.20.0
@@ -85,12 +127,18 @@ importers:
dotenv:
specifier: ^17.4.2
version: 17.4.2
express:
specifier: 5.2.1
version: 5.2.1
file-type:
specifier: 22.0.1
version: 22.0.1
global-agent:
specifier: ^4.1.3
version: 4.1.3
grammy:
specifier: ^1.42.0
version: 1.42.0
https-proxy-agent:
specifier: ^9.0.0
version: 9.0.0
@@ -106,12 +154,27 @@ importers:
jszip:
specifier: ^3.10.1
version: 3.10.1
linkedom:
specifier: ^0.18.12
version: 0.18.12
markdown-it:
specifier: 14.1.1
version: 14.1.1
minimatch:
specifier: 10.2.5
version: 10.2.5
node-edge-tts:
specifier: ^1.2.10
version: 1.2.10
openai:
specifier: ^6.35.0
version: 6.35.0(ws@8.20.0)(zod@4.4.1)
openshell:
specifier: 0.1.0
version: 0.1.0
pdfjs-dist:
specifier: ^5.7.284
version: 5.7.284
playwright-core:
specifier: 1.59.1
version: 1.59.1
@@ -127,6 +190,9 @@ importers:
tar:
specifier: 7.5.13
version: 7.5.13
tokenjuice:
specifier: 0.7.0
version: 0.7.0
tslog:
specifier: ^4.10.2
version: 4.10.2

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}`);
}

View File

@@ -251,4 +251,73 @@ describe("collectRootDependencyOwnershipCheckErrors", () => {
]);
expect(collectRootDependencyOwnershipCheckErrors(records)).toEqual([]);
});
it("allows runtime deps for bundled plugins that are still packaged in core", () => {
const repoRoot = makeTempRepo();
writeRepoFile(
repoRoot,
"package.json",
JSON.stringify({
dependencies: { "vendor-sdk": "^1.0.0" },
files: ["dist/", "!dist/extensions/externalized/**"],
}),
);
writeRepoFile(
repoRoot,
"extensions/internal/package.json",
JSON.stringify({ dependencies: { "vendor-sdk": "^1.0.0" } }),
);
writeRepoFile(repoRoot, "extensions/internal/openclaw.plugin.json", JSON.stringify({}));
writeRepoFile(
repoRoot,
"extensions/internal/src/setup.ts",
'const sdk = await import("vendor-sdk");\n',
);
const records = collectRootDependencyOwnershipAudit({ repoRoot, scanRoots: ["extensions"] });
expect(records).toMatchObject([
{
category: "root_owned_extension_runtime",
depName: "vendor-sdk",
internalizedBundledRuntimeOwners: ["internal:dependencies"],
sections: ["extensions"],
},
]);
expect(collectRootDependencyOwnershipCheckErrors(records)).toEqual([]);
});
it("keeps excluded bundled plugin deps localizable", () => {
const repoRoot = makeTempRepo();
writeRepoFile(
repoRoot,
"package.json",
JSON.stringify({
dependencies: { "vendor-sdk": "^1.0.0" },
files: ["dist/", "!dist/extensions/externalized/**"],
}),
);
writeRepoFile(
repoRoot,
"extensions/externalized/package.json",
JSON.stringify({ dependencies: { "vendor-sdk": "^1.0.0" } }),
);
writeRepoFile(repoRoot, "extensions/externalized/openclaw.plugin.json", JSON.stringify({}));
writeRepoFile(
repoRoot,
"extensions/externalized/src/setup.ts",
'const sdk = await import("vendor-sdk");\n',
);
const records = collectRootDependencyOwnershipAudit({ repoRoot, scanRoots: ["extensions"] });
expect(records).toMatchObject([
{
category: "extension_only_localizable",
depName: "vendor-sdk",
internalizedBundledRuntimeOwners: [],
sections: ["extensions"],
},
]);
});
});