diff --git a/apps/examples/desktop-app/scripts/build-desktop-runtime.ts b/apps/examples/desktop-app/scripts/build-desktop-runtime.ts index 5320928dd6..80e707037d 100644 --- a/apps/examples/desktop-app/scripts/build-desktop-runtime.ts +++ b/apps/examples/desktop-app/scripts/build-desktop-runtime.ts @@ -140,6 +140,53 @@ const buildUniversalMacCli = 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 CLI, 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 = `${BIN_DIR}/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 hasOpenTuiNativeVariant = (os: string, arch: string): boolean => existsSync(`${REPO_ROOT}/node_modules/.bun`) && [ @@ -158,6 +205,7 @@ const main = async () => { await $`mkdir -p ${BIN_DIR}/desktop-backend ${REMOTE_CLI_DIR} ${BIN_DIR}/extensions`; // Plugin workers load this bootstrap from disk in the backend and Hub. await $`cp ../../../sdk/packages/core/dist/extensions/plugin-sandbox-bootstrap.js ${BIN_DIR}/extensions/plugin-sandbox-bootstrap.js`; + await bundlePluginSdkPackages(); // Every build cross-compiles the Linux SSH copies (and macOS builds both // architectures), which needs the target platforms' OpenTUI native // packages in addition to the host's. diff --git a/apps/examples/desktop-app/src-tauri/tauri.conf.json b/apps/examples/desktop-app/src-tauri/tauri.conf.json index 5026fb1c66..09b6f151b8 100644 --- a/apps/examples/desktop-app/src-tauri/tauri.conf.json +++ b/apps/examples/desktop-app/src-tauri/tauri.conf.json @@ -47,7 +47,7 @@ "icons/app/macos/*.png", "bin/remote-helpers/*", "bin/desktop-backend/*", - "bin/extensions/*" + "bin/extensions/**/*" ], "icon": [ "icons/32x32.png",