From 8009be6e33c4a503eda7282e4b33d8a437852f06 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:07:06 -0700 Subject: [PATCH] fix(desktop): ship the SDK packages plugins import in production builds (#15068) * fix(desktop): ship the SDK packages plugins import in production builds Plugin installs strip @cline/* dependencies because the host provides them, and the plugin worker resolves those from node_modules beside its bootstrap. The packaged desktop app ships only the bootstrap, so a marketplace plugin like goal failed with "Cannot find module '@cline/sdk'" (dev builds worked because the repo's node_modules sit above src-tauri). Bundle self-contained @cline/sdk, core, agents, llms and shared next to the bootstrap (one shared copy via code splitting, ~17 MB), and widen the Tauri resource glob so the nested files are actually bundled. * fix(desktop): ship the SDK packages' subpath exports for plugins Plugins can import supported subpaths such as @cline/shared/storage or @cline/core/telemetry. Bundle every runtime export of each package and write its exports map, skipping type-only exports that have no JS. --- .../desktop-app/scripts/build-sidecar-bin.ts | 49 +++++++++++++++++++ .../desktop-app/src-tauri/tauri.conf.json | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/apps/examples/desktop-app/scripts/build-sidecar-bin.ts b/apps/examples/desktop-app/scripts/build-sidecar-bin.ts index 22b42db826..3eb75fc0d6 100644 --- a/apps/examples/desktop-app/scripts/build-sidecar-bin.ts +++ b/apps/examples/desktop-app/scripts/build-sidecar-bin.ts @@ -1,3 +1,4 @@ +import { existsSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { $ } from "bun"; import { telemetryDefineArgs } from "./telemetry-define-args"; @@ -126,6 +127,53 @@ const buildUniversalMacSidecar = async (): Promise => { await $`lipo -info ${outfile}`; }; +// Plugin installs drop their @cline/* dependencies because the host provides +// them, and the plugin worker resolves those from node_modules beside its +// bootstrap. Nothing like that exists next to the compiled sidecar, so ship +// self-contained builds there. Splitting keeps one shared copy of the SDK, +// placed in core so every chunk sits inside a `"type": "module"` package. +const bundlePluginSdkPackages = async (): Promise => { + const outdir = "src-tauri/bin/extensions/node_modules/@cline"; + const packagesRoot = "../../../sdk/packages"; + const packages = await Promise.all( + ["sdk", "core", "agents", "llms", "shared"].map(async (name) => { + const root = `${packagesRoot}/${name}`; + const manifest = (await Bun.file(`${root}/package.json`).json()) as { + exports: Record; + }; + // Type-only exports (e.g. @cline/shared/types) have no JS to ship. + const exports = Object.fromEntries( + Object.entries(manifest.exports) + .map(([path, entry]) => [path, entry.import] as const) + .filter(([, file]) => existsSync(`${root}/${file}`)), + ); + return { root, name, exports }; + }), + ); + await $`rm -rf ${outdir}`; + const result = await Bun.build({ + entrypoints: packages.flatMap(({ root, exports }) => + Object.values(exports).map((file) => `${root}/${file}`), + ), + root: packagesRoot, + outdir, + naming: { chunk: "core/dist/[name]-[hash].[ext]" }, + target: "node", + format: "esm", + splitting: true, + minify: true, + }); + if (!result.success) { + throw new AggregateError(result.logs, "plugin SDK bundle failed"); + } + for (const { name, exports } of packages) { + await Bun.write( + `${outdir}/${name}/package.json`, + JSON.stringify({ name: `@cline/${name}`, type: "module", exports }), + ); + } +}; + const main = async () => { // All compiled helpers and the sidecar depend on fresh SDK package exports. await $`bun run build:sdk`.cwd( @@ -136,6 +184,7 @@ const main = async () => { // Plugins run in worker threads that load this self-contained bootstrap // from disk; the app ships it as a resource (see plugin-bootstrap.ts). await $`cp ../../../sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js src-tauri/bin/extensions/plugin-sandbox-bootstrap.js`; + await bundlePluginSdkPackages(); if (targetTriple === "universal-apple-darwin") { await buildUniversalMacSidecar(); } else { diff --git a/apps/examples/desktop-app/src-tauri/tauri.conf.json b/apps/examples/desktop-app/src-tauri/tauri.conf.json index dc24e05b8d..9718ed1a53 100644 --- a/apps/examples/desktop-app/src-tauri/tauri.conf.json +++ b/apps/examples/desktop-app/src-tauri/tauri.conf.json @@ -46,7 +46,7 @@ "icons/app/*.png", "icons/app/macos/*.png", "bin/remote-helpers/*", - "bin/extensions/*" + "bin/extensions/**/*" ], "icon": [ "icons/32x32.png",