Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "20.x"
node-version-file: ".nvmrc"

- name: Install dependencies
run: npm install
run: npm ci

- name: Check formatting with Prettier
run: npx prettier --check .

- name: Check types
run: npx tsc --noEmit

- name: Build and test runtime compatibility
run: npm test

- name: Audit dependencies
run: npm audit --audit-level=high
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.22.1
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ A powerful plugin for Elgato Stream Deck that allows you to manage Docker contai

Before using this plugin, ensure you have the following installed:

- Elgato Stream Deck Software (latest version)
- Elgato Stream Deck Software 7.1 or newer (latest version recommended)
- Docker (with the Docker Engine API enabled)
- Node.js (for development and building the plugin)
- Node.js 24.22.1 or newer within the 24.x line (for development and building the plugin)

### Node.js security updates

Development and CI use the patched Node.js version pinned in `.nvmrc`. Node.js 24.22.1 addresses the [October 2026 security advisory](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases). npm rejects unsupported Node.js versions via `engines` and `.npmrc`.

The installed plugin uses **Stream Deck's bundled Node.js 24 runtime**, not the Node.js installed on your system. Stream Deck 7.1 introduced Node.js 24 support, but this minimum version alone does not guarantee the security fixes: keep Stream Deck updated and verify that its bundled Node.js 24 runtime is at least 24.22.1. The plugin manifest selects only the Node.js major version; it cannot pin a security patch. Updating npm dependencies or your system Node.js does not update this bundled runtime.

The dependency override for `@elgato/utils@0.4.1` pins patched Zod 4.6.7 to address [GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5). The current Elgato dependency declares Zod 3.25 but imports its bundled Zod 4 Mini API (`zod/v4-mini`), which the patched version also exports. `npm test` checks RPC schema compatibility and built-plugin registration. Reassess the override when Elgato ships a dependency range that includes patched Zod.

## Installation

Expand All @@ -47,8 +55,12 @@ git clone https://github.com/Darkdragon14/streamdeck-docker.git

### Install the dependencies:

Select the project's Node.js version with [nvm](https://github.com/nvm-sh/nvm) (or install the version from `.nvmrc` using your preferred version manager):

```bash
npm install
nvm install
nvm use
npm ci
```

### Build the project:
Expand Down Expand Up @@ -80,7 +92,9 @@ Clone the repository and navigate to the project directory:
```bash
git clone https://github.com/Darkdragon14/streamdeck-docker.git
cd streamdeck-docker
npm i
nvm install
nvm use
npm ci
npm run watch
```

Expand Down
6 changes: 3 additions & 3 deletions com.darkdragon14.elgato-docker.sdPlugin/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Name": "elgato-docker",
"Version": "2.2.0.0",
"Version": "2.2.0.1",
"Author": "@Darkdragon14",
"Actions": [
{
Expand Down Expand Up @@ -171,7 +171,7 @@
"Icon": "imgs/plugin/marketplace",
"SDKVersion": 3,
"Software": {
"MinimumVersion": "6.9"
"MinimumVersion": "7.1"
},
"OS": [
{
Expand All @@ -184,7 +184,7 @@
}
],
"Nodejs": {
"Version": "20",
"Version": "24",
"Debug": "enabled"
},
"UUID": "com.darkdragon14.elgato-docker"
Expand Down
59 changes: 36 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{
"scripts": {
"build": "rollup -c",
"test": "npm run build && node --test tests/*.test.mjs",
"watch": "rollup -c -w --watch.onEnd=\"streamdeck restart com.darkdragon14.elgato-docker\"",
"prettier": "npx prettier --write ."
},
"type": "module",
"engines": {
"node": ">=24.22.1 <25"
},
"devDependencies": {
"@elgato/cli": "^1.7.0",
"@elgato/prettier-config": "^0.2.4",
Expand All @@ -20,7 +24,12 @@
},
"dependencies": {
"@elgato/streamdeck": "^2.0.1",
"@elgato/utils": "^0.4.1"
"@elgato/utils": "0.4.1"
},
"overrides": {
"@elgato/utils@0.4.1": {
"zod": "4.6.7"
}
},
"prettier": "@elgato/prettier-config"
}
69 changes: 69 additions & 0 deletions tests/runtime-compatibility.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { JsonRpcError, JsonRpcRequest, JsonRpcResponse } from "@elgato/utils/rpc";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import { readFile } from "node:fs/promises";
import { test } from "node:test";
import { WebSocketServer } from "ws";

test("Elgato RPC schemas work with the patched Zod Mini implementation", () => {
const request = { jsonrpc: "2.0", method: "test", params: [1, "value"], id: "1" };
assert.equal(JsonRpcRequest.safeParse(request).success, true);
assert.equal(JsonRpcRequest.safeParse({ ...request, params: { value: 1 } }).success, true);
assert.equal(JsonRpcRequest.safeParse({ ...request, method: 123 }).success, false);
assert.equal(JsonRpcRequest.safeParse({ ...request, jsonrpc: "1.0" }).success, false);

const error = { code: -32600, message: "Invalid request", data: null };
assert.equal(JsonRpcError.safeParse(error).success, true);
assert.equal(JsonRpcError.safeParse({ ...error, code: "invalid" }).success, false);
assert.equal(JsonRpcResponse.safeParse({ jsonrpc: "2.0", result: { ok: true }, id: "1" }).success, true);
assert.equal(JsonRpcResponse.safeParse({ jsonrpc: "2.0", error, id: "1" }).success, true);
assert.equal(JsonRpcResponse.safeParse({ jsonrpc: "2.0", id: "1" }).success, false);
});

test("built plugin registers with Stream Deck using the patched dependencies", { timeout: 10000 }, async (t) => {
const pluginDirectory = new URL("../com.darkdragon14.elgato-docker.sdPlugin/", import.meta.url);
const manifest = JSON.parse(await readFile(new URL("manifest.json", pluginDirectory), "utf8"));
const server = new WebSocketServer({ host: "127.0.0.1", port: 0 });
t.after(() => {
for (const socket of server.clients) socket.terminate();
server.close();
});
await once(server, "listening");

const info = {
application: { version: "7.1.0", platform: "windows", language: "en", platformVersion: "10" },
plugin: { uuid: manifest.UUID, version: manifest.Version },
devices: [],
colors: {},
};
const child = spawn(
process.execPath,
[
manifest.CodePath,
"-port",
String(server.address().port),
"-pluginUUID",
manifest.UUID,
"-registerEvent",
"registerPlugin",
"-info",
JSON.stringify(info),
],
{ cwd: pluginDirectory, stdio: ["ignore", "ignore", "pipe"] },
);
let stderr = "";
child.stderr.on("data", (data) => (stderr += data));
t.after(() => child.kill());
const connection = once(server, "connection");
const registration = connection.then(async ([socket]) => {
const [data] = await once(socket, "message");
assert.deepEqual(JSON.parse(data.toString()), { event: "registerPlugin", uuid: manifest.UUID });
});
await Promise.race([
registration,
once(child, "exit").then(([code]) => {
throw new Error(`Plugin exited before registration (${code}): ${stderr}`);
}),
]);
});
Loading