Skip to content

Commit f42e5fa

Browse files
committed
Bundle Stream Deck plugin in DMG install flow
1 parent 186a501 commit f42e5fa

5 files changed

Lines changed: 116 additions & 0 deletions

File tree

electron-builder.release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ extraResources:
1717
to: trayTemplate.png
1818
- from: build/trayTemplate@2x.png
1919
to: trayTemplate@2x.png
20+
- from: stream-deck/com.roie.deck-threads.sdPlugin
21+
to: stream-deck-plugin/com.roie.deck-threads.sdPlugin
2022

2123
directories:
2224
buildResources: build

electron/main.cjs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { app, BrowserWindow, ipcMain, Menu, nativeImage, nativeTheme, shell, Tray } = require("electron");
2+
const fs = require("node:fs");
23
const path = require("node:path");
34
const http = require("node:http");
45
const { execFile } = require("node:child_process");
@@ -14,9 +15,11 @@ const {
1415
writeSourceAllocation,
1516
} = require("./source-allocation.cjs");
1617
const { assignStableTaskSlots, readTaskSlotIds, writeTaskSlotIds } = require("./task-slots.cjs");
18+
const { installBundledStreamDeckPlugin } = require("./stream-deck-plugin-install.cjs");
1719

1820
const execFileAsync = promisify(execFile);
1921
const BRIDGE_HOST = "127.0.0.1";
22+
const STREAM_DECK_PLUGIN_BUNDLE = "com.roie.deck-threads.sdPlugin";
2023
const requestedBridgePort = Number(process.env.DECK_THREADS_BRIDGE_PORT);
2124
const BRIDGE_PORT = Number.isInteger(requestedBridgePort) && requestedBridgePort > 0 && requestedBridgePort <= 65535
2225
? requestedBridgePort
@@ -59,6 +62,27 @@ function getResourcePath(filename) {
5962
: path.join(__dirname, "..", "build", filename);
6063
}
6164

65+
async function ensureBundledStreamDeckPlugin() {
66+
if (process.platform !== "darwin" || !app.isPackaged) {
67+
return { status: "development" };
68+
}
69+
70+
const source = path.join(process.resourcesPath, "stream-deck-plugin", STREAM_DECK_PLUGIN_BUNDLE);
71+
const pluginsRoot = process.env.DECK_THREADS_STREAM_DECK_PLUGINS_DIR
72+
|| path.join(app.getPath("home"), "Library", "Application Support", "com.elgato.StreamDeck", "Plugins");
73+
const destination = path.join(pluginsRoot, STREAM_DECK_PLUGIN_BUNDLE);
74+
const result = await installBundledStreamDeckPlugin(source, destination);
75+
76+
if (result.status === "installed"
77+
&& process.env.DECK_THREADS_SKIP_STREAM_DECK_RESTART !== "1"
78+
&& fs.existsSync("/Applications/Elgato Stream Deck.app")) {
79+
await execFileAsync("/usr/bin/pkill", ["-x", "Stream Deck"]).catch(() => undefined);
80+
await execFileAsync("/usr/bin/open", ["-a", "/Applications/Elgato Stream Deck.app"]).catch(() => undefined);
81+
}
82+
83+
return result;
84+
}
85+
6286
function getLaunchAtLoginStatus() {
6387
if (process.platform !== "darwin" || !app.isPackaged) {
6488
return { openAtLogin: false, status: "development" };
@@ -481,6 +505,15 @@ app.whenReady().then(() => {
481505
const openedAtLogin = app.isPackaged && app.getLoginItemSettings().wasOpenedAtLogin;
482506
if (openedAtLogin) app.dock?.hide();
483507
else showMainWindow();
508+
ensureBundledStreamDeckPlugin()
509+
.then((result) => {
510+
if (result.status === "installed") {
511+
emitEvent("system", "success", "Stream Deck plugin installed", `Version ${result.version || "current"} is ready.`);
512+
}
513+
})
514+
.catch((error) => {
515+
emitEvent("system", "warning", "Could not install the Stream Deck plugin", error.message);
516+
});
484517
if (process.env.CODEX_BRIDGE_API_DISABLED !== "1") startBridgeServer();
485518
app.on("activate", showMainWindow);
486519
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const fs = require("node:fs");
2+
const path = require("node:path");
3+
4+
function readPluginVersion(pluginRoot) {
5+
try {
6+
const manifest = JSON.parse(fs.readFileSync(path.join(pluginRoot, "manifest.json"), "utf8"));
7+
return typeof manifest.Version === "string" ? manifest.Version : null;
8+
} catch {
9+
return null;
10+
}
11+
}
12+
13+
async function installBundledStreamDeckPlugin(source, destination) {
14+
if (!fs.existsSync(source)) return { status: "not-bundled", version: null };
15+
16+
const sourceVersion = readPluginVersion(source);
17+
const destinationVersion = readPluginVersion(destination);
18+
if (sourceVersion && sourceVersion === destinationVersion) {
19+
return { status: "up-to-date", version: sourceVersion };
20+
}
21+
22+
const pluginsRoot = path.dirname(destination);
23+
const staging = path.join(pluginsRoot, `.${path.basename(destination)}.installing-${process.pid}`);
24+
await fs.promises.mkdir(pluginsRoot, { recursive: true });
25+
await fs.promises.rm(staging, { recursive: true, force: true });
26+
await fs.promises.cp(source, staging, { recursive: true });
27+
await fs.promises.rm(destination, { recursive: true, force: true });
28+
await fs.promises.rename(staging, destination);
29+
return { status: "installed", version: sourceVersion };
30+
}
31+
32+
module.exports = {
33+
installBundledStreamDeckPlugin,
34+
readPluginVersion,
35+
};

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"marketplace:assets": "electron scripts/generate-marketplace-assets.cjs",
2626
"start": "electron .",
2727
"test:state": "node scripts/test-codex-state.cjs",
28+
"test:bundled-plugin": "node scripts/test-bundled-plugin.cjs",
2829
"qa:electron": "node scripts/qa-electron.cjs",
2930
"streamdeck:build": "npm --prefix stream-deck run build",
3031
"streamdeck:validate": "npm --prefix stream-deck run validate",
@@ -72,6 +73,10 @@
7273
{
7374
"from": "build/trayTemplate@2x.png",
7475
"to": "trayTemplate@2x.png"
76+
},
77+
{
78+
"from": "stream-deck/com.roie.deck-threads.sdPlugin",
79+
"to": "stream-deck-plugin/com.roie.deck-threads.sdPlugin"
7580
}
7681
],
7782
"directories": {

scripts/test-bundled-plugin.cjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const assert = require("node:assert/strict");
2+
const fs = require("node:fs");
3+
const os = require("node:os");
4+
const path = require("node:path");
5+
const { installBundledStreamDeckPlugin } = require("../electron/stream-deck-plugin-install.cjs");
6+
7+
async function main() {
8+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "deck-threads-plugin-test-"));
9+
const source = path.join(root, "source", "com.roie.deck-threads.sdPlugin");
10+
const destination = path.join(root, "plugins", "com.roie.deck-threads.sdPlugin");
11+
try {
12+
fs.mkdirSync(source, { recursive: true });
13+
fs.writeFileSync(path.join(source, "manifest.json"), JSON.stringify({ Version: "1.0.0" }));
14+
fs.writeFileSync(path.join(source, "plugin.js"), "current");
15+
16+
const installed = await installBundledStreamDeckPlugin(source, destination);
17+
assert.equal(installed.status, "installed");
18+
assert.equal(fs.readFileSync(path.join(destination, "plugin.js"), "utf8"), "current");
19+
20+
const unchanged = await installBundledStreamDeckPlugin(source, destination);
21+
assert.equal(unchanged.status, "up-to-date");
22+
23+
fs.writeFileSync(path.join(source, "manifest.json"), JSON.stringify({ Version: "1.1.0" }));
24+
fs.writeFileSync(path.join(source, "plugin.js"), "updated");
25+
const updated = await installBundledStreamDeckPlugin(source, destination);
26+
assert.equal(updated.status, "installed");
27+
assert.equal(updated.version, "1.1.0");
28+
assert.equal(fs.readFileSync(path.join(destination, "plugin.js"), "utf8"), "updated");
29+
30+
const missing = await installBundledStreamDeckPlugin(path.join(root, "missing"), destination);
31+
assert.equal(missing.status, "not-bundled");
32+
} finally {
33+
fs.rmSync(root, { recursive: true, force: true });
34+
}
35+
console.log("Bundled Stream Deck plugin installation verified.");
36+
}
37+
38+
main().catch((error) => {
39+
console.error(error);
40+
process.exitCode = 1;
41+
});

0 commit comments

Comments
 (0)