From 4076eb66abf58e2702f3e8a84bf6444ce656b934 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 30 Sep 2026 11:21:16 -0500 Subject: [PATCH] fix: ship the plugin sdk and serialport in desktop builds --- package-lock.json | 1 + package.json | 1 + packaging/build/after-pack.cjs | 38 +++++++++++-- src/backend/tests/electron/after-pack.test.ts | 56 +++++++++++++++++++ 4 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/backend/tests/electron/after-pack.test.ts diff --git a/package-lock.json b/package-lock.json index 0ab056fe4..86cd546ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "node-pty": "^1.1.0", "pg": "^8.23.0", "semver": "^7.8.5", + "serialport": "^13.0.0", "sharp": "^0.35.4", "socks": "^2.8.10", "ssh2": "^1.17.0", diff --git a/package.json b/package.json index e6767dcf7..67acb27c0 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "node-pty": "^1.1.0", "pg": "^8.23.0", "semver": "^7.8.5", + "serialport": "^13.0.0", "sharp": "^0.35.4", "socks": "^2.8.10", "ssh2": "^1.17.0", diff --git a/packaging/build/after-pack.cjs b/packaging/build/after-pack.cjs index cbe54d614..170731252 100644 --- a/packaging/build/after-pack.cjs +++ b/packaging/build/after-pack.cjs @@ -2,6 +2,34 @@ const fs = require("fs"); const path = require("path"); const { chmodSpawnHelpers } = require("../../scripts/patch-node-pty.cjs"); +const repoRoot = path.resolve(__dirname, "..", ".."); + +/** + * The SDK is a workspace package, so electron-builder skips it, yet core and + * every plugin import it at runtime. Ship its package.json and dist the way + * the Docker image does. + */ +function copyPluginSdk(resourcesDir, sdkDir = path.join(repoRoot, "packages", "plugin-sdk")) { + const target = path.join( + resourcesDir, + "app.asar.unpacked", + "node_modules", + "@termix", + "plugin-sdk", + ); + const dist = path.join(sdkDir, "dist"); + if (!fs.existsSync(dist)) { + throw new Error(`Plugin SDK is not built (${dist}). Run: npm run build:sdk`); + } + fs.rmSync(target, { recursive: true, force: true }); + fs.mkdirSync(target, { recursive: true }); + fs.copyFileSync(path.join(sdkDir, "package.json"), path.join(target, "package.json")); + fs.cpSync(dist, path.join(target, "dist"), { recursive: true }); + return target; +} + +exports.copyPluginSdk = copyPluginSdk; + exports.default = async function afterPack(context) { const { targets, appOutDir } = context; @@ -11,10 +39,6 @@ exports.default = async function afterPack(context) { fs.writeFileSync(markerPath, ""); } - if (context.electronPlatformName === "win32") { - return; - } - const resourcesDir = context.electronPlatformName === "darwin" ? path.join( @@ -25,6 +49,12 @@ exports.default = async function afterPack(context) { ) : path.join(appOutDir, "resources"); + copyPluginSdk(resourcesDir); + + if (context.electronPlatformName === "win32") { + return; + } + const nodePtyDir = path.join( resourcesDir, "app.asar.unpacked", diff --git a/src/backend/tests/electron/after-pack.test.ts b/src/backend/tests/electron/after-pack.test.ts new file mode 100644 index 000000000..450bad0d7 --- /dev/null +++ b/src/backend/tests/electron/after-pack.test.ts @@ -0,0 +1,56 @@ +import { createRequire } from "node:module"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +const require = createRequire(import.meta.url); +const { copyPluginSdk } = + require("../../../../packaging/build/after-pack.cjs") as { + copyPluginSdk: (resourcesDir: string, sdkDir?: string) => string; + }; + +let dir: string; +afterEach(() => { + if (dir) fs.rmSync(dir, { recursive: true, force: true }); +}); + +describe("afterPack plugin SDK copy", () => { + it("ships the SDK package.json and dist where the packaged backend resolves it", () => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-after-pack-")); + const sdk = path.join(dir, "sdk"); + fs.mkdirSync(path.join(sdk, "dist"), { recursive: true }); + fs.mkdirSync(path.join(sdk, "src")); + fs.writeFileSync( + path.join(sdk, "package.json"), + '{"name":"@termix/plugin-sdk"}', + ); + fs.writeFileSync(path.join(sdk, "dist", "settings.js"), "export {};"); + fs.writeFileSync(path.join(sdk, "src", "settings.ts"), ""); + const resources = path.join(dir, "resources"); + + const target = copyPluginSdk(resources, sdk); + + expect(target).toBe( + path.join( + resources, + "app.asar.unpacked", + "node_modules", + "@termix", + "plugin-sdk", + ), + ); + expect(fs.readdirSync(target).sort()).toEqual(["dist", "package.json"]); + expect(fs.existsSync(path.join(target, "dist", "settings.js"))).toBe(true); + }); + + it("stops the build when the SDK was never built", () => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), "termix-after-pack-")); + const sdk = path.join(dir, "sdk"); + fs.mkdirSync(sdk); + fs.writeFileSync(path.join(sdk, "package.json"), "{}"); + expect(() => copyPluginSdk(path.join(dir, "resources"), sdk)).toThrow( + "npm run build:sdk", + ); + }); +});