From b3e9ffad6c398486cc0d022f9cee6e59df2fad07 Mon Sep 17 00:00:00 2001 From: Richard Herman <1429781+GeekyEggo@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:09:19 +0100 Subject: [PATCH] feat: add support for Stream Deck 6.8 (#12) * feat: add 6.8, and migration documentation * docs: update summary of font.size to indicate measurement type * chore: fix linting * refactor: move 6.7 and 6.8 into 6.6, and improve migration documentation --------- Co-authored-by: Richard Herman --- src/streamdeck/plugins/README.md | 62 +++++++++++++ src/streamdeck/plugins/layout.ts | 5 +- .../plugins/manifest/__tests__/all.test.ts | 2 +- .../manifest/__tests__/files/v6.8.json | 91 +++++++++++++++++++ .../plugins/manifest/__tests__/v6.6.test.ts | 16 ++-- .../plugins/manifest/__tests__/v6.7.test.ts | 47 ---------- src/streamdeck/plugins/manifest/latest.ts | 3 +- src/streamdeck/plugins/manifest/v6.6.ts | 6 +- src/streamdeck/plugins/manifest/v6.7.ts | 16 ---- src/streamdeck/plugins/schemas.ts | 3 +- 10 files changed, 170 insertions(+), 81 deletions(-) create mode 100644 src/streamdeck/plugins/README.md create mode 100644 src/streamdeck/plugins/manifest/__tests__/files/v6.8.json delete mode 100644 src/streamdeck/plugins/manifest/__tests__/v6.7.test.ts delete mode 100644 src/streamdeck/plugins/manifest/v6.7.ts diff --git a/src/streamdeck/plugins/README.md b/src/streamdeck/plugins/README.md new file mode 100644 index 0000000..97a77bb --- /dev/null +++ b/src/streamdeck/plugins/README.md @@ -0,0 +1,62 @@ +# Stream Deck Plugin Schemas + +## Manifest + +Manifest versioning is achieved using reverse-migrations, by extending the versions successor and utilizing the `Omit` utility type to _remove_ new features, for example: + +```mermaid +classDiagram + Manifest <|-- Manifest_6_6 + Manifest_6_6 <|-- Manifest_6_5 + class Manifest { + + string: Software.MinimumVersion + ... + } + class Manifest_6_6 { + + "6.6" | "6.7": Software.MinimumVersion + // No changes introduced from 6.6 to 6.7. + } + class Manifest_6_5 { + + "6.5": Software.MinimumVersion + // Changes introduced from 6.5 to 6.6, omit new properties. + } +``` + +### Glossary + +- vLatest — The current version, with a flat `Software.MinimumVersion`. +- vCurrent — The current version, for example `v6.7.ts`. +- vNext — The new version being introduced, for example `v6.8.ts`. + +### File Structure Example + +Manifest versions, and the type responsible for generating the JSON schema, are located in the following file structure: + +``` +./src/streamdeck/plugins/ +├── manifest/ +│ ├── latest.ts # vLatest — vCurrent, with flat Software.MinimumVersion, e.g. "6.5" | "6.6" | "6.7" +| ├── v6.5.ts +| ├── v6.6.ts +| └── v6.7.ts # vCurrent, with specific Software.MinimumVersion, e.g. "6.7" +└── schemas.ts +``` + +### Adding Versions of Stream Deck + +When adding a new version of Stream Deck, its important to consider if there are manifest changes. If simply adding a new version of `Software.MinimumVersion`, vCurrent can be updated to include the new version. If there are new properties introduced, a reverse-migration is needed. + +The following flowchart depicts how to introduce a new version of Stream Deck to the manifest type and JSON schema. + +```mermaid +graph TD; + a("New version of
Stream Deck app")-->b + b{"Manifest
changes"} + b-->|Yes|y0("Add changes to ./manifest/latest.ts") + y0-->y1("Create vNext (e.g. v6.8.ts) in ./manifest/ — extend latest, and re-set Software.MinimumVersion") + y1-->y2("Update vCurrent in ./manifest/ — extend vNext, and omit changes") + y2-->y3("Update Manifest type within ./schemas/ to include vNext")-->z0 + b-->|No|n("Update vCurrent Software.MinimumVersion to include new version")-->z0 + z0("Add unit tests for new version") + z0-->z1("Fin") +``` diff --git a/src/streamdeck/plugins/layout.ts b/src/streamdeck/plugins/layout.ts index a6000b9..72e0714 100644 --- a/src/streamdeck/plugins/layout.ts +++ b/src/streamdeck/plugins/layout.ts @@ -217,8 +217,9 @@ export type Text = LayoutItemBase<"text"> & { */ font?: { /** - * Size of the font. **Note**, when the `key` of this layout item is set to `"title"` within the layout's JSON definition, this value will be ignored in favour of the user's - * preferred title settings, as set in property inspector. + * Size of the font, in pixels, represented as a whole number. + * + * **Note**, when the `key` of this layout item is set to `"title"` within the layout's JSON definition, this value will be ignored in favour of the user's preferred title settings, as set in property inspector. */ size?: number; diff --git a/src/streamdeck/plugins/manifest/__tests__/all.test.ts b/src/streamdeck/plugins/manifest/__tests__/all.test.ts index 767972d..32a3854 100644 --- a/src/streamdeck/plugins/manifest/__tests__/all.test.ts +++ b/src/streamdeck/plugins/manifest/__tests__/all.test.ts @@ -1,6 +1,6 @@ import { validateStreamDeckPluginManifest } from "@tests"; -describe.each(["v6.4", "v6.5", "v6.6", "v6.7"])("%s", (version) => { +describe.each(["v6.4", "v6.5", "v6.6", "v6.7", "v6.8"])("%s", (version) => { const filePath = `${version}.json`; /** diff --git a/src/streamdeck/plugins/manifest/__tests__/files/v6.8.json b/src/streamdeck/plugins/manifest/__tests__/files/v6.8.json new file mode 100644 index 0000000..c4999a9 --- /dev/null +++ b/src/streamdeck/plugins/manifest/__tests__/files/v6.8.json @@ -0,0 +1,91 @@ +{ + "$schema": "../../../../../../streamdeck/plugins/manifest.json", + "Actions": [ + { + "Controllers": ["Encoder", "Keypad"], + "DisableAutomaticStates": true, + "DisableCaching": false, + "Encoder": { + "background": "background", + "Icon": "icon", + "layout": "$A0", + "StackColor": "#000000", + "TriggerDescription": { + "LongTouch": "Long touch", + "Push": "Push", + "Rotate": "Rotate", + "Touch": "Touch" + } + }, + "Icon": "action-icon", + "Name": "Action One", + "OS": ["mac", "windows"], + "PropertyInspectorPath": "action.html", + "States": [ + { + "FontFamily": "Arial", + "FontSize": 12, + "FontStyle": "Bold", + "FontUnderline": true, + "Image": "action-state-image", + "MultiActionImage": "action-state-multi-action-image", + "Name": "Action State One", + "ShowTitle": true, + "Title": "State One", + "TitleAlignment": "bottom", + "TitleColor": "#000000" + } + ], + "SupportedInMultiActions": true, + "Tooltip": "This is the tooltip", + "UserTitleEnabled": true, + "UUID": "com.elgato.test.one", + "VisibleInActionsList": true + } + ], + "ApplicationsToMonitor": { + "mac": ["finder"], + "windows": ["explorer.exe"] + }, + "Author": "Elgato", + "Category": "Testing", + "CategoryIcon": "category-icon", + "CodePath": "main.js", + "CodePathMac": "main-darwin.js", + "CodePathWin": "main-windows.js", + "DefaultWindowSize": [500, 650], + "Description": "Manifest version 6.4", + "Icon": "icon", + "Name": "Test Manifest", + "Nodejs": { + "Debug": "break", + "GenerateProfilerOutput": false, + "Version": "20" + }, + "OS": [ + { + "MinimumVersion": "13", + "Platform": "mac" + }, + { + "MinimumVersion": "10", + "Platform": "windows" + } + ], + "Profiles": [ + { + "DeviceType": 7, + "DontAutoSwitchWhenInstalled": true, + "Name": "Test Profile", + "Readonly": true + } + ], + "PropertyInspectorPath": "pi.html", + "SDKVersion": 2, + "Software": { + "MinimumVersion": "6.8" + }, + "URL": "https://www.elgato.com", + "UUID": "com.elgato.test", + "Version": "1.0.0.0" +} diff --git a/src/streamdeck/plugins/manifest/__tests__/v6.6.test.ts b/src/streamdeck/plugins/manifest/__tests__/v6.6.test.ts index d518f92..b3f506c 100644 --- a/src/streamdeck/plugins/manifest/__tests__/v6.6.test.ts +++ b/src/streamdeck/plugins/manifest/__tests__/v6.6.test.ts @@ -1,14 +1,12 @@ import { validateStreamDeckPluginManifest } from "@tests"; -const VERSION = "6.6"; - -describe("v6.6", () => { +describe.each(["6.6" as const, "6.7" as const, "6.8" as const])("v%s", (version) => { /** - * Asserts a valid v6.6 manifest. + * Asserts the full manifest. */ test("full manifest", () => { // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("v6.6.json"); + const errors = validateStreamDeckPluginManifest(`v${version}.json`); expect(errors).toHaveLength(0); }); @@ -17,7 +15,7 @@ describe("v6.6", () => { */ test("Actions[].States[] allow more than 2 items", () => { // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("v6.6.json", (m) => { + const errors = validateStreamDeckPluginManifest(`v${version}.json`, (m) => { m.Actions[0].States.push({ Image: "imgs/two" }); m.Actions[0].States.push({ Image: "imgs/three" }); m.Actions[0].States.push({ Image: "imgs/four" }); @@ -25,13 +23,13 @@ describe("v6.6", () => { expect(errors).toHaveLength(0); }); - describe("v6.6 features", () => { + describe(`v${version} feature compatibility`, () => { /** * Asserts `Actions[].OS` is not valid for a v6.5 manifest. */ test("Actions[].OS is valid", () => { // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("Actions[].OS.json", (m) => (m.Software.MinimumVersion = VERSION)); + const errors = validateStreamDeckPluginManifest("Actions[].OS.json", (m) => (m.Software.MinimumVersion = version)); expect(errors).toHaveLength(0); }); @@ -40,7 +38,7 @@ describe("v6.6", () => { */ test("Profiles[].AutoInstall is valid", () => { // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("Profiles[].AutoInstall.json", (m) => (m.Software.MinimumVersion = VERSION)); + const errors = validateStreamDeckPluginManifest("Profiles[].AutoInstall.json", (m) => (m.Software.MinimumVersion = version)); expect(errors).toHaveLength(0); }); }); diff --git a/src/streamdeck/plugins/manifest/__tests__/v6.7.test.ts b/src/streamdeck/plugins/manifest/__tests__/v6.7.test.ts deleted file mode 100644 index fdc643f..0000000 --- a/src/streamdeck/plugins/manifest/__tests__/v6.7.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { validateStreamDeckPluginManifest } from "@tests"; - -const VERSION = "6.7"; - -describe("v6.7", () => { - /** - * Asserts a valid v6.7 manifest. - */ - test("full manifest", () => { - // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("v6.7.json"); - expect(errors).toHaveLength(0); - }); - - /** - * Asserts more than 2 states are allowed. - */ - test("Actions[].States[] allow more than 2 items", () => { - // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("v6.7.json", (m) => { - m.Actions[0].States.push({ Image: "imgs/two" }); - m.Actions[0].States.push({ Image: "imgs/three" }); - m.Actions[0].States.push({ Image: "imgs/four" }); - }); - expect(errors).toHaveLength(0); - }); - - describe("v6.7 features", () => { - /** - * Asserts `Actions[].OS` is not valid for a v6.5 manifest. - */ - test("Actions[].OS is valid", () => { - // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("Actions[].OS.json", (m) => (m.Software.MinimumVersion = VERSION)); - expect(errors).toHaveLength(0); - }); - - /** - * Asserts `Profiles[].AutoInstall` is not valid for a v6.5 manifest. - */ - test("Profiles[].AutoInstall is valid", () => { - // Arrange, act, assert. - const errors = validateStreamDeckPluginManifest("Profiles[].AutoInstall.json", (m) => (m.Software.MinimumVersion = VERSION)); - expect(errors).toHaveLength(0); - }); - }); -}); diff --git a/src/streamdeck/plugins/manifest/latest.ts b/src/streamdeck/plugins/manifest/latest.ts index 0a97a46..6a4aca1 100644 --- a/src/streamdeck/plugins/manifest/latest.ts +++ b/src/streamdeck/plugins/manifest/latest.ts @@ -1,4 +1,5 @@ import { DeviceType } from "../device-type"; +import type { Manifest as ManifestSchema } from "../schemas"; /** * Determines the Stream Deck software requirements for this plugin. @@ -7,7 +8,7 @@ export type Software = { /** * Minimum version of the Stream Deck application required for this plugin to run. */ - MinimumVersion: "6.4" | "6.5" | "6.6" | "6.7"; + MinimumVersion: ManifestSchema["Software"]["MinimumVersion"]; }; /** diff --git a/src/streamdeck/plugins/manifest/v6.6.ts b/src/streamdeck/plugins/manifest/v6.6.ts index f2804dc..b9a8be5 100644 --- a/src/streamdeck/plugins/manifest/v6.6.ts +++ b/src/streamdeck/plugins/manifest/v6.6.ts @@ -1,9 +1,9 @@ -import type { Manifest_6_7 } from "./v6.7"; +import type { Manifest } from "./latest"; /** * Defines the plugin and available actions, and all information associated with them, including the plugin's entry point, all iconography, action default behavior, etc. */ -export type Manifest_6_6 = Omit & { +export type Manifest_6_6 = Omit & { /** * Determines the Stream Deck software requirements for this plugin. */ @@ -11,6 +11,6 @@ export type Manifest_6_6 = Omit & { /** * Minimum version of the Stream Deck application required for this plugin to run. */ - MinimumVersion: "6.6"; + MinimumVersion: "6.6" | "6.7" | "6.8"; }; }; diff --git a/src/streamdeck/plugins/manifest/v6.7.ts b/src/streamdeck/plugins/manifest/v6.7.ts deleted file mode 100644 index 5f5d694..0000000 --- a/src/streamdeck/plugins/manifest/v6.7.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Manifest } from "./latest"; - -/** - * Defines the plugin and available actions, and all information associated with them, including the plugin's entry point, all iconography, action default behavior, etc. - */ -export type Manifest_6_7 = Omit & { - /** - * Determines the Stream Deck software requirements for this plugin. - */ - Software: { - /** - * Minimum version of the Stream Deck application required for this plugin to run. - */ - MinimumVersion: "6.7"; - }; -}; diff --git a/src/streamdeck/plugins/schemas.ts b/src/streamdeck/plugins/schemas.ts index ca8728f..3c50f00 100644 --- a/src/streamdeck/plugins/schemas.ts +++ b/src/streamdeck/plugins/schemas.ts @@ -1,12 +1,11 @@ import { type Manifest_6_4 } from "./manifest/v6.4"; import { type Manifest_6_5 } from "./manifest/v6.5"; import { type Manifest_6_6 } from "./manifest/v6.6"; -import { type Manifest_6_7 } from "./manifest/v6.7"; /** * Defines the plugin and available actions, and all information associated with them, including the plugin's entry point, all iconography, action default behavior, etc. */ -export type Manifest = JsonSchema | JsonSchema | JsonSchema | JsonSchema; +export type Manifest = JsonSchema | JsonSchema | JsonSchema; export type { Layout } from "./layout";