@fcannizzaro/streamdeck-react 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/action.d.ts +2 -2
- package/dist/action.js +2 -1
- package/dist/bundler-shared.d.ts +31 -0
- package/dist/bundler-shared.js +64 -2
- package/dist/font-inline.js +1 -1
- package/dist/google-font.d.ts +61 -0
- package/dist/google-font.js +124 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -1
- package/dist/manifest-extract.d.ts +32 -0
- package/dist/manifest-extract.js +141 -0
- package/dist/manifest-gen.d.ts +52 -0
- package/dist/manifest-gen.js +229 -0
- package/dist/manifest-types.d.ts +238 -0
- package/dist/plugin.js +44 -35
- package/dist/render/render-pool.js +1 -1
- package/dist/rollup.d.ts +37 -10
- package/dist/rollup.js +43 -14
- package/dist/roots/registry.js +20 -20
- package/dist/types.d.ts +35 -35
- package/dist/vite.d.ts +24 -8
- package/dist/vite.js +48 -13
- package/package.json +10 -5
- package/dist/manifest-codegen.d.ts +0 -38
- package/dist/manifest-codegen.js +0 -110
package/dist/rollup.d.ts
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
import { Plugin } from 'rollup';
|
|
2
2
|
import { StreamDeckTargetOptions } from './bundler-shared';
|
|
3
|
-
|
|
3
|
+
import { PluginManifestInfo } from './manifest-types';
|
|
4
|
+
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, TakumiBackend, } from './bundler-shared';
|
|
5
|
+
export type { ManifestActionSource, PluginManifestInfo, ActionManifestInfo, ManifestController, ManifestEncoderInfo, ManifestTriggerDescription, ManifestStateInfo, ManifestOSInfo, ManifestNodejsInfo, ManifestProfileInfo, } from './manifest-types';
|
|
4
6
|
export interface StreamDeckRollupOptions extends StreamDeckTargetOptions {
|
|
5
7
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* Code-first manifest generation. When a `PluginManifestInfo` object
|
|
9
|
+
* is provided, the plugin generates `manifest.json` in the `.sdPlugin`
|
|
10
|
+
* directory during `writeBundle`.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
12
|
+
* Actions are **auto-extracted** from `defineAction()` calls in the
|
|
13
|
+
* source code — no need to list them here. The plugin scans the
|
|
14
|
+
* module graph during the build and extracts `uuid`, `info`, and
|
|
15
|
+
* component presence from each `defineAction()` call.
|
|
16
|
+
*
|
|
17
|
+
* Actions with `info.disabled: true` are skipped.
|
|
18
|
+
*
|
|
19
|
+
* Auto-derived defaults:
|
|
20
|
+
* - `CodePath` from the bundler output path
|
|
21
|
+
* - `Controllers` from key/dial/touchStrip presence
|
|
22
|
+
* - `OS`, `Nodejs`, `SDKVersion`, `Software` have defaults
|
|
23
|
+
* - `States` default to `[{ Image: icon }]`
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* streamDeckReact({
|
|
28
|
+
* manifest: {
|
|
29
|
+
* uuid: "com.example.my-plugin",
|
|
30
|
+
* name: "My Plugin",
|
|
31
|
+
* author: "Me",
|
|
32
|
+
* description: "A plugin",
|
|
33
|
+
* icon: "imgs/plugin-icon",
|
|
34
|
+
* version: "1.0.0.0",
|
|
35
|
+
* },
|
|
36
|
+
* })
|
|
37
|
+
* ```
|
|
10
38
|
*/
|
|
11
|
-
manifest?:
|
|
39
|
+
manifest?: PluginManifestInfo;
|
|
12
40
|
}
|
|
13
41
|
/**
|
|
14
42
|
* Rollup plugin for Stream Deck React projects.
|
|
@@ -19,12 +47,11 @@ export interface StreamDeckRollupOptions extends StreamDeckTargetOptions {
|
|
|
19
47
|
* - No `configResolved` — dev mode detected via `this.meta.watchMode`
|
|
20
48
|
* - `onLog` suppresses `MODULE_LEVEL_DIRECTIVE` warnings (caused by
|
|
21
49
|
* "use client" directives in React dependencies)
|
|
22
|
-
* - `
|
|
23
|
-
* - `writeBundle`
|
|
24
|
-
* `dirname(outputOptions.file)` (Rollup supports both)
|
|
50
|
+
* - `moduleParsed` extracts defineAction() metadata from each module
|
|
51
|
+
* - `writeBundle` generates manifest.json + copies native bindings
|
|
25
52
|
* - No `closeBundle` restart hook (handled externally)
|
|
26
53
|
*
|
|
27
|
-
* Font inlining and manifest
|
|
28
|
-
* font-inline.ts, and manifest-
|
|
54
|
+
* Font inlining and manifest generation are shared via bundler-shared.ts,
|
|
55
|
+
* font-inline.ts, manifest-gen.ts, and manifest-extract.ts.
|
|
29
56
|
*/
|
|
30
57
|
export declare function streamDeckReact(options?: StreamDeckRollupOptions): Plugin;
|
package/dist/rollup.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
1
|
+
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, deriveCodePath, isDevelopmentMode, isLibraryDevtoolsImport, resolvePluginDir, shouldStripDevtools } from "./bundler-shared.js";
|
|
2
2
|
import { loadFont, resolveFontId } from "./font-inline.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { generateManifestJsonString, validateManifestConfig, validatePluginUUID, writeManifestIfChanged } from "./manifest-gen.js";
|
|
4
|
+
import { extractActionsFromAST, extractedToActionSource } from "./manifest-extract.js";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
5
6
|
//#region src/rollup.ts
|
|
6
7
|
/**
|
|
7
8
|
* Rollup plugin for Stream Deck React projects.
|
|
@@ -12,33 +13,43 @@ import { dirname, resolve } from "node:path";
|
|
|
12
13
|
* - No `configResolved` — dev mode detected via `this.meta.watchMode`
|
|
13
14
|
* - `onLog` suppresses `MODULE_LEVEL_DIRECTIVE` warnings (caused by
|
|
14
15
|
* "use client" directives in React dependencies)
|
|
15
|
-
* - `
|
|
16
|
-
* - `writeBundle`
|
|
17
|
-
* `dirname(outputOptions.file)` (Rollup supports both)
|
|
16
|
+
* - `moduleParsed` extracts defineAction() metadata from each module
|
|
17
|
+
* - `writeBundle` generates manifest.json + copies native bindings
|
|
18
18
|
* - No `closeBundle` restart hook (handled externally)
|
|
19
19
|
*
|
|
20
|
-
* Font inlining and manifest
|
|
21
|
-
* font-inline.ts, and manifest-
|
|
20
|
+
* Font inlining and manifest generation are shared via bundler-shared.ts,
|
|
21
|
+
* font-inline.ts, manifest-gen.ts, and manifest-extract.ts.
|
|
22
22
|
*/
|
|
23
23
|
function streamDeckReact(options = {}) {
|
|
24
|
+
const extractedActions = [];
|
|
24
25
|
return {
|
|
25
26
|
name: "fcannizzaro-streamdeck-react",
|
|
26
27
|
onLog(_level, log) {
|
|
27
28
|
if (log.code === "MODULE_LEVEL_DIRECTIVE") return false;
|
|
28
29
|
},
|
|
29
30
|
buildStart() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (result) {
|
|
34
|
-
this.addWatchFile(result.manifestPath);
|
|
35
|
-
if (result.written) console.log("[@fcannizzaro/streamdeck-react] Generated src/streamdeck-env.d.ts");
|
|
31
|
+
if (options.manifest) {
|
|
32
|
+
const uuidError = validatePluginUUID(options.manifest.uuid);
|
|
33
|
+
if (uuidError) this.warn(`[@fcannizzaro/streamdeck-react] ${uuidError.message}`);
|
|
36
34
|
}
|
|
35
|
+
extractedActions.length = 0;
|
|
36
|
+
},
|
|
37
|
+
moduleParsed(moduleInfo) {
|
|
38
|
+
if (!options.manifest) return;
|
|
39
|
+
if (!moduleInfo.code?.includes("defineAction")) return;
|
|
40
|
+
try {
|
|
41
|
+
const actions = extractActionsFromAST(this.parse(moduleInfo.code));
|
|
42
|
+
for (const action of actions) {
|
|
43
|
+
if (action.info?.disabled) continue;
|
|
44
|
+
extractedActions.push(action);
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
37
47
|
},
|
|
38
48
|
resolveId: {
|
|
39
49
|
order: "pre",
|
|
40
50
|
handler(source, importer) {
|
|
41
51
|
if (shouldStripDevtools(this.meta.watchMode) && isLibraryDevtoolsImport(source, importer)) return NOOP_DEVTOOLS_ID;
|
|
52
|
+
if (options.takumi !== "wasm" && source === "@takumi-rs/core") return TAKUMI_NATIVE_LOADER_ID;
|
|
42
53
|
return resolveFontId(source, importer);
|
|
43
54
|
}
|
|
44
55
|
},
|
|
@@ -46,6 +57,7 @@ function streamDeckReact(options = {}) {
|
|
|
46
57
|
order: "pre",
|
|
47
58
|
handler(id) {
|
|
48
59
|
if (id === "\0streamdeck-react:noop-devtools") return NOOP_DEVTOOLS_CODE;
|
|
60
|
+
if (id === "\0streamdeck-react:takumi-native") return TAKUMI_NATIVE_LOADER_CODE;
|
|
49
61
|
return loadFont(id);
|
|
50
62
|
}
|
|
51
63
|
},
|
|
@@ -54,6 +66,23 @@ function streamDeckReact(options = {}) {
|
|
|
54
66
|
const outDir = outputOptions.file ? dirname(outputOptions.file) : outputOptions.dir;
|
|
55
67
|
if (!outDir) return;
|
|
56
68
|
copyNativeBindings(outDir, isDevelopment, options, (msg) => this.warn(msg));
|
|
69
|
+
if (options.manifest) {
|
|
70
|
+
const pluginDir = resolvePluginDir(outDir);
|
|
71
|
+
if (!pluginDir) {
|
|
72
|
+
this.warn("[@fcannizzaro/streamdeck-react] Could not resolve .sdPlugin directory from output path. Manifest generation skipped.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const codePath = deriveCodePath(outputOptions.file ?? join(outDir, "plugin.mjs"), pluginDir);
|
|
76
|
+
const fullConfig = {
|
|
77
|
+
...options.manifest,
|
|
78
|
+
actions: extractedActions.map(extractedToActionSource)
|
|
79
|
+
};
|
|
80
|
+
const errors = validateManifestConfig(fullConfig, (msg) => this.warn(msg));
|
|
81
|
+
if (errors.length > 0) this.warn(`[@fcannizzaro/streamdeck-react] Manifest validation found ${errors.length} issue(s)`);
|
|
82
|
+
const content = generateManifestJsonString(fullConfig, codePath);
|
|
83
|
+
const manifestPath = join(pluginDir, "manifest.json");
|
|
84
|
+
if (writeManifestIfChanged(manifestPath, content)) console.log(`[@fcannizzaro/streamdeck-react] Generated ${manifestPath}`);
|
|
85
|
+
}
|
|
57
86
|
}
|
|
58
87
|
};
|
|
59
88
|
}
|
package/dist/roots/registry.js
CHANGED
|
@@ -5,44 +5,44 @@ import { TouchStripRoot } from "./touchstrip-root.js";
|
|
|
5
5
|
var SEGMENT_WIDTH = 200;
|
|
6
6
|
var KEY_SIZES = {
|
|
7
7
|
0: {
|
|
8
|
-
width:
|
|
9
|
-
height:
|
|
8
|
+
width: 144,
|
|
9
|
+
height: 144
|
|
10
10
|
},
|
|
11
11
|
1: {
|
|
12
|
-
width:
|
|
13
|
-
height:
|
|
12
|
+
width: 144,
|
|
13
|
+
height: 144
|
|
14
14
|
},
|
|
15
15
|
2: {
|
|
16
|
-
width:
|
|
17
|
-
height:
|
|
16
|
+
width: 144,
|
|
17
|
+
height: 144
|
|
18
18
|
},
|
|
19
19
|
3: {
|
|
20
|
-
width:
|
|
21
|
-
height:
|
|
20
|
+
width: 144,
|
|
21
|
+
height: 144
|
|
22
22
|
},
|
|
23
23
|
4: {
|
|
24
|
-
width:
|
|
25
|
-
height:
|
|
24
|
+
width: 144,
|
|
25
|
+
height: 144
|
|
26
26
|
},
|
|
27
27
|
5: {
|
|
28
|
-
width:
|
|
29
|
-
height:
|
|
28
|
+
width: 144,
|
|
29
|
+
height: 144
|
|
30
30
|
},
|
|
31
31
|
6: {
|
|
32
|
-
width:
|
|
33
|
-
height:
|
|
32
|
+
width: 144,
|
|
33
|
+
height: 144
|
|
34
34
|
},
|
|
35
35
|
7: {
|
|
36
36
|
width: 144,
|
|
37
37
|
height: 144
|
|
38
38
|
},
|
|
39
39
|
8: {
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
40
|
+
width: 144,
|
|
41
|
+
height: 144
|
|
42
42
|
},
|
|
43
43
|
9: {
|
|
44
|
-
width:
|
|
45
|
-
height:
|
|
44
|
+
width: 144,
|
|
45
|
+
height: 144
|
|
46
46
|
},
|
|
47
47
|
10: {
|
|
48
48
|
width: 144,
|
|
@@ -72,8 +72,8 @@ function getCanvasInfo(deviceType, surfaceType) {
|
|
|
72
72
|
};
|
|
73
73
|
return {
|
|
74
74
|
...KEY_SIZES[deviceType] ?? {
|
|
75
|
-
width:
|
|
76
|
-
height:
|
|
75
|
+
width: 144,
|
|
76
|
+
height: 144
|
|
77
77
|
},
|
|
78
78
|
type: "key"
|
|
79
79
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ComponentType, ReactNode } from 'react';
|
|
2
2
|
import { JsonObject, JsonValue } from '@elgato/utils';
|
|
3
3
|
import { AdapterActionHandle, StreamDeckAdapter } from './adapter/types';
|
|
4
|
+
import { ActionManifestInfo, PluginManifestInfo } from './manifest-types';
|
|
4
5
|
/** Controller surface type. */
|
|
5
6
|
export type Controller = "Keypad" | "Encoder";
|
|
6
7
|
/** Grid coordinates for a key or encoder on a device. */
|
|
@@ -73,12 +74,32 @@ export interface TouchStripLayout {
|
|
|
73
74
|
items: TouchStripLayoutItem[];
|
|
74
75
|
}
|
|
75
76
|
export type EncoderLayout = string | TouchStripLayout;
|
|
77
|
+
/** Takumi renderer backend selection. `"native-binding"` uses the native Rust NAPI addon (`@takumi-rs/core`). `"wasm"` uses the WebAssembly build (`@takumi-rs/wasm`), suitable for WebContainer and browser environments. */
|
|
78
|
+
export type TakumiBackend = "native-binding" | "wasm";
|
|
76
79
|
export interface PluginConfig {
|
|
77
80
|
/** Stream Deck adapter. Defaults to physicalDevice() (Elgato SDK). */
|
|
78
81
|
adapter?: StreamDeckAdapter;
|
|
79
82
|
fonts: FontConfig[];
|
|
80
83
|
actions: ActionDefinition[];
|
|
81
84
|
wrapper?: WrapperComponent;
|
|
85
|
+
/**
|
|
86
|
+
* Plugin manifest metadata. Optional — used for runtime
|
|
87
|
+
* documentation. For manifest generation, provide plugin-level
|
|
88
|
+
* info via the bundler plugin's `manifest` option.
|
|
89
|
+
*/
|
|
90
|
+
info?: PluginManifestInfo;
|
|
91
|
+
/**
|
|
92
|
+
* Takumi renderer backend.
|
|
93
|
+
*
|
|
94
|
+
* - `"native-binding"` — uses `@takumi-rs/core` (native Rust NAPI addon).
|
|
95
|
+
* Requires a platform-specific binary (e.g. `@takumi-rs/core-darwin-arm64`).
|
|
96
|
+
* - `"wasm"` — uses `@takumi-rs/wasm` (WebAssembly build).
|
|
97
|
+
* Requires `@takumi-rs/wasm` to be installed. WOFF fonts are not supported
|
|
98
|
+
* in this mode — use TTF/OTF only. Worker threads are force-disabled.
|
|
99
|
+
*
|
|
100
|
+
* @default "native-binding"
|
|
101
|
+
*/
|
|
102
|
+
takumi?: TakumiBackend;
|
|
82
103
|
imageFormat?: "png" | "webp";
|
|
83
104
|
caching?: boolean;
|
|
84
105
|
devicePixelRatio?: number;
|
|
@@ -91,34 +112,12 @@ export interface PluginConfig {
|
|
|
91
112
|
imageCacheMaxBytes?: number;
|
|
92
113
|
/** Maximum TouchStrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
|
|
93
114
|
touchStripCacheMaxBytes?: number;
|
|
94
|
-
/** Offload Takumi rendering to a worker thread. Set to false to disable. @default true */
|
|
115
|
+
/** Offload Takumi rendering to a worker thread. Set to false to disable. Automatically disabled when `takumi` is `"wasm"`. @default true */
|
|
95
116
|
useWorker?: boolean;
|
|
96
117
|
}
|
|
97
118
|
export interface Plugin {
|
|
98
119
|
connect(): Promise<void>;
|
|
99
120
|
}
|
|
100
|
-
export interface ManifestActions {
|
|
101
|
-
}
|
|
102
|
-
/** Action UUID — a union of manifest UUIDs when available, plain `string` otherwise. */
|
|
103
|
-
export type ActionUUID = [keyof ManifestActions] extends [never] ? string : Extract<keyof ManifestActions, string>;
|
|
104
|
-
type HasController<UUID extends string, C extends string> = UUID extends keyof ManifestActions ? ManifestActions[UUID] extends {
|
|
105
|
-
controllers: readonly (infer Item)[];
|
|
106
|
-
} ? C extends Item ? true : false : false : false;
|
|
107
|
-
type KeySurface<UUID extends string> = HasController<UUID, "Keypad"> extends true ? {
|
|
108
|
-
key: ComponentType;
|
|
109
|
-
} : {
|
|
110
|
-
key?: ComponentType;
|
|
111
|
-
};
|
|
112
|
-
type EncoderSurface<UUID extends string> = HasController<UUID, "Encoder"> extends true ? {
|
|
113
|
-
dial: ComponentType;
|
|
114
|
-
touchStrip?: ComponentType;
|
|
115
|
-
} | {
|
|
116
|
-
dial?: ComponentType;
|
|
117
|
-
touchStrip: ComponentType;
|
|
118
|
-
} : {
|
|
119
|
-
dial?: ComponentType;
|
|
120
|
-
touchStrip?: ComponentType;
|
|
121
|
-
};
|
|
122
121
|
export interface ActionConfig<S extends JsonObject = JsonObject> {
|
|
123
122
|
uuid: string;
|
|
124
123
|
key?: ComponentType;
|
|
@@ -129,19 +128,15 @@ export interface ActionConfig<S extends JsonObject = JsonObject> {
|
|
|
129
128
|
dialLayout?: EncoderLayout;
|
|
130
129
|
wrapper?: WrapperComponent;
|
|
131
130
|
defaultSettings?: Partial<S>;
|
|
131
|
+
/**
|
|
132
|
+
* Action manifest metadata. This is the **primary source** for
|
|
133
|
+
* manifest.json generation — the bundler plugin auto-extracts
|
|
134
|
+
* `info` from each `defineAction()` call at build time.
|
|
135
|
+
*
|
|
136
|
+
* Set `disabled: true` to exclude this action from the manifest.
|
|
137
|
+
*/
|
|
138
|
+
info?: ActionManifestInfo;
|
|
132
139
|
}
|
|
133
|
-
/** Resolved action config shape. When `ManifestActions` is populated (via `streamdeck-env.d.ts`), this becomes a mapped type that iterates over every UUID in the manifest and produces a discriminated union. Each member intersects `KeySurface<UUID>` and `EncoderSurface<UUID>` to enforce controller-specific requirements. When `ManifestActions` is empty, it falls back to the permissive `ActionConfig<S>`. */
|
|
134
|
-
export type ActionConfigInput<S extends JsonObject = JsonObject> = [keyof ManifestActions] extends [
|
|
135
|
-
never
|
|
136
|
-
] ? ActionConfig<S> : {
|
|
137
|
-
[UUID in ActionUUID]: {
|
|
138
|
-
uuid: UUID;
|
|
139
|
-
/** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
|
|
140
|
-
dialLayout?: EncoderLayout;
|
|
141
|
-
wrapper?: WrapperComponent;
|
|
142
|
-
defaultSettings?: Partial<S>;
|
|
143
|
-
} & KeySurface<UUID> & EncoderSurface<UUID>;
|
|
144
|
-
}[ActionUUID];
|
|
145
140
|
export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
146
141
|
uuid: string;
|
|
147
142
|
key?: ComponentType;
|
|
@@ -152,6 +147,11 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
|
|
|
152
147
|
dialLayout?: EncoderLayout;
|
|
153
148
|
wrapper?: WrapperComponent;
|
|
154
149
|
defaultSettings: Partial<S>;
|
|
150
|
+
/**
|
|
151
|
+
* Action manifest metadata. Carried through from defineAction()
|
|
152
|
+
* for runtime access and manifest generation.
|
|
153
|
+
*/
|
|
154
|
+
info?: ActionManifestInfo;
|
|
155
155
|
}
|
|
156
156
|
export interface DeviceInfo {
|
|
157
157
|
id: string;
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { StreamDeckTargetOptions } from './bundler-shared';
|
|
3
|
-
|
|
3
|
+
import { PluginManifestInfo } from './manifest-types';
|
|
4
|
+
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, TakumiBackend, } from './bundler-shared';
|
|
5
|
+
export type { ManifestActionSource, PluginManifestInfo, ActionManifestInfo, ManifestController, ManifestEncoderInfo, ManifestTriggerDescription, ManifestStateInfo, ManifestOSInfo, ManifestNodejsInfo, ManifestProfileInfo, } from './manifest-types';
|
|
4
6
|
export interface StreamDeckReactOptions extends StreamDeckTargetOptions {
|
|
5
7
|
/**
|
|
6
8
|
* The plugin UUID used to restart the plugin after each build
|
|
7
9
|
* (e.g. `"com.example.react-pokemon"`).
|
|
8
10
|
*
|
|
9
11
|
* When set, the plugin will run `streamdeck restart <uuid>` after
|
|
10
|
-
* each successful build.
|
|
12
|
+
* each successful build. If `manifest` is set, the UUID is
|
|
13
|
+
* auto-derived from `manifest.uuid`.
|
|
11
14
|
*/
|
|
12
15
|
uuid?: string;
|
|
13
16
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* Code-first manifest generation. When a `PluginManifestInfo` object
|
|
18
|
+
* is provided, the plugin generates `manifest.json` in the `.sdPlugin`
|
|
19
|
+
* directory during `writeBundle`.
|
|
16
20
|
*
|
|
17
|
-
*
|
|
21
|
+
* Actions are **auto-extracted** from `defineAction()` calls in the
|
|
22
|
+
* source code — no need to list them here. The plugin scans the
|
|
23
|
+
* module graph during the build and extracts `uuid`, `info`, and
|
|
24
|
+
* component presence from each `defineAction()` call.
|
|
25
|
+
*
|
|
26
|
+
* Actions with `info.disabled: true` are skipped.
|
|
27
|
+
*
|
|
28
|
+
* Auto-derived defaults:
|
|
29
|
+
* - `CodePath` from the bundler output path
|
|
30
|
+
* - `Controllers` from key/dial/touchStrip presence
|
|
31
|
+
* - `OS`, `Nodejs`, `SDKVersion`, `Software` have defaults
|
|
32
|
+
* - `States` default to `[{ Image: icon }]`
|
|
18
33
|
*/
|
|
19
|
-
manifest?:
|
|
34
|
+
manifest?: PluginManifestInfo;
|
|
20
35
|
}
|
|
21
36
|
/**
|
|
22
37
|
* Vite plugin for Stream Deck React projects.
|
|
@@ -24,10 +39,11 @@ export interface StreamDeckReactOptions extends StreamDeckTargetOptions {
|
|
|
24
39
|
* Responsibilities mapped to Vite lifecycle hooks:
|
|
25
40
|
*
|
|
26
41
|
* configResolved → detect dev/production mode, set strip flags
|
|
27
|
-
* buildStart →
|
|
42
|
+
* buildStart → validate plugin UUID format (early check)
|
|
43
|
+
* moduleParsed → extract defineAction() metadata from each module
|
|
28
44
|
* resolveId → redirect devtools imports (production) + font imports
|
|
29
45
|
* load → return noop devtools stub + inline font as base64 Buffer
|
|
30
|
-
* writeBundle → copy native .node bindings
|
|
46
|
+
* writeBundle → copy native .node bindings + generate manifest.json
|
|
31
47
|
* closeBundle → restart Stream Deck plugin (optional, via CLI)
|
|
32
48
|
*
|
|
33
49
|
* Font inlining:
|
package/dist/vite.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
1
|
+
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, deriveCodePath, isLibraryDevtoolsImport, resolvePluginDir, shouldStripDevtools } from "./bundler-shared.js";
|
|
2
2
|
import { loadFont, resolveFontId } from "./font-inline.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { generateManifestJsonString, validateManifestConfig, validatePluginUUID, writeManifestIfChanged } from "./manifest-gen.js";
|
|
4
|
+
import { extractActionsFromAST, extractedToActionSource } from "./manifest-extract.js";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
5
6
|
import { exec } from "node:child_process";
|
|
6
7
|
//#region src/vite.ts
|
|
7
8
|
/**
|
|
@@ -10,10 +11,11 @@ import { exec } from "node:child_process";
|
|
|
10
11
|
* Responsibilities mapped to Vite lifecycle hooks:
|
|
11
12
|
*
|
|
12
13
|
* configResolved → detect dev/production mode, set strip flags
|
|
13
|
-
* buildStart →
|
|
14
|
+
* buildStart → validate plugin UUID format (early check)
|
|
15
|
+
* moduleParsed → extract defineAction() metadata from each module
|
|
14
16
|
* resolveId → redirect devtools imports (production) + font imports
|
|
15
17
|
* load → return noop devtools stub + inline font as base64 Buffer
|
|
16
|
-
* writeBundle → copy native .node bindings
|
|
18
|
+
* writeBundle → copy native .node bindings + generate manifest.json
|
|
17
19
|
* closeBundle → restart Stream Deck plugin (optional, via CLI)
|
|
18
20
|
*
|
|
19
21
|
* Font inlining:
|
|
@@ -27,6 +29,7 @@ function streamDeckReact(options = {}) {
|
|
|
27
29
|
let resolvedConfig;
|
|
28
30
|
let isDevelopment = false;
|
|
29
31
|
let stripDevtools = false;
|
|
32
|
+
const extractedActions = [];
|
|
30
33
|
return {
|
|
31
34
|
name: "fcannizzaro-streamdeck-react",
|
|
32
35
|
apply: "build",
|
|
@@ -38,29 +41,61 @@ function streamDeckReact(options = {}) {
|
|
|
38
41
|
stripDevtools = shouldStripDevtools(isWatch);
|
|
39
42
|
},
|
|
40
43
|
buildStart() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.addWatchFile(result.manifestPath);
|
|
45
|
-
if (result.written) resolvedConfig.logger.info("[@fcannizzaro/streamdeck-react] Generated src/streamdeck-env.d.ts");
|
|
44
|
+
if (options.manifest) {
|
|
45
|
+
const uuidError = validatePluginUUID(options.manifest.uuid);
|
|
46
|
+
if (uuidError) resolvedConfig.logger.warn(`[@fcannizzaro/streamdeck-react] ${uuidError.message}`);
|
|
46
47
|
}
|
|
48
|
+
extractedActions.length = 0;
|
|
49
|
+
},
|
|
50
|
+
moduleParsed(moduleInfo) {
|
|
51
|
+
if (!options.manifest) return;
|
|
52
|
+
if (!moduleInfo.code?.includes("defineAction")) return;
|
|
53
|
+
try {
|
|
54
|
+
const actions = extractActionsFromAST(this.parse(moduleInfo.code));
|
|
55
|
+
for (const action of actions) {
|
|
56
|
+
if (action.info?.disabled) continue;
|
|
57
|
+
extractedActions.push(action);
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
47
60
|
},
|
|
48
61
|
resolveId(source, importer) {
|
|
49
62
|
if (stripDevtools && isLibraryDevtoolsImport(source, importer)) return NOOP_DEVTOOLS_ID;
|
|
63
|
+
if (options.takumi !== "wasm" && source === "@takumi-rs/core") return TAKUMI_NATIVE_LOADER_ID;
|
|
50
64
|
return resolveFontId(source, importer);
|
|
51
65
|
},
|
|
52
66
|
load(id) {
|
|
53
67
|
if (id === "\0streamdeck-react:noop-devtools") return NOOP_DEVTOOLS_CODE;
|
|
68
|
+
if (id === "\0streamdeck-react:takumi-native") return TAKUMI_NATIVE_LOADER_CODE;
|
|
54
69
|
return loadFont(id);
|
|
55
70
|
},
|
|
56
71
|
writeBundle() {
|
|
57
|
-
|
|
72
|
+
const outDir = resolve(resolvedConfig.root, resolvedConfig.build.outDir);
|
|
73
|
+
copyNativeBindings(outDir, isDevelopment, options, (msg) => {
|
|
58
74
|
resolvedConfig.logger.warn(msg);
|
|
59
75
|
});
|
|
76
|
+
if (options.manifest) {
|
|
77
|
+
const pluginDir = resolvePluginDir(outDir);
|
|
78
|
+
if (!pluginDir) {
|
|
79
|
+
resolvedConfig.logger.warn("[@fcannizzaro/streamdeck-react] Could not resolve .sdPlugin directory from output path. Manifest generation skipped.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const codePath = deriveCodePath(join(outDir, "plugin.mjs"), pluginDir);
|
|
83
|
+
const fullConfig = {
|
|
84
|
+
...options.manifest,
|
|
85
|
+
actions: extractedActions.map(extractedToActionSource)
|
|
86
|
+
};
|
|
87
|
+
const warn = (msg) => resolvedConfig.logger.warn(msg);
|
|
88
|
+
const errors = validateManifestConfig(fullConfig, warn);
|
|
89
|
+
if (errors.length > 0) resolvedConfig.logger.warn(`[@fcannizzaro/streamdeck-react] Manifest validation found ${errors.length} issue(s)`);
|
|
90
|
+
const content = generateManifestJsonString(fullConfig, codePath);
|
|
91
|
+
const manifestPath = join(pluginDir, "manifest.json");
|
|
92
|
+
if (writeManifestIfChanged(manifestPath, content)) resolvedConfig.logger.info(`[@fcannizzaro/streamdeck-react] Generated ${manifestPath}`);
|
|
93
|
+
}
|
|
60
94
|
},
|
|
61
95
|
closeBundle() {
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
const uuid = options.uuid ?? options.manifest?.uuid;
|
|
97
|
+
if (!uuid) return;
|
|
98
|
+
exec(`streamdeck restart ${uuid}`, (err) => {
|
|
64
99
|
if (err) console.warn(`[streamdeck-restart] ${err.message}`);
|
|
65
100
|
});
|
|
66
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fcannizzaro/streamdeck-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Build Stream Deck plugins with React — render components directly to keys, dials, and touch screens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elgato",
|
|
@@ -60,22 +60,24 @@
|
|
|
60
60
|
"test": "bun test ./src"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@takumi-rs/core": "^0.
|
|
64
|
-
"@takumi-rs/helpers": "^0.
|
|
63
|
+
"@takumi-rs/core": "^0.73.1",
|
|
64
|
+
"@takumi-rs/helpers": "^0.73.1",
|
|
65
65
|
"react-reconciler": "^0.33.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@happy-dom/global-registrator": "^20.8.
|
|
68
|
+
"@happy-dom/global-registrator": "^20.8.4",
|
|
69
69
|
"@types/react": "^19.2.14",
|
|
70
70
|
"@types/react-dom": "^19.2.3",
|
|
71
71
|
"@types/react-reconciler": "^0.33.0",
|
|
72
|
+
"acorn": "^8.14.1",
|
|
72
73
|
"react-dom": "^19.2.4",
|
|
73
74
|
"rollup": "^4.59.0",
|
|
74
|
-
"vite": "8.0.0
|
|
75
|
+
"vite": "8.0.0",
|
|
75
76
|
"vite-plugin-dts": "^4.5.4"
|
|
76
77
|
},
|
|
77
78
|
"peerDependencies": {
|
|
78
79
|
"@elgato/streamdeck": "^2.0.2",
|
|
80
|
+
"@takumi-rs/wasm": "^0.71.7",
|
|
79
81
|
"react": "^18.0.0 || ^19.0.0",
|
|
80
82
|
"rollup": "^4.0.0",
|
|
81
83
|
"typescript": "^5",
|
|
@@ -85,6 +87,9 @@
|
|
|
85
87
|
"@elgato/streamdeck": {
|
|
86
88
|
"optional": true
|
|
87
89
|
},
|
|
90
|
+
"@takumi-rs/wasm": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
88
93
|
"rollup": {
|
|
89
94
|
"optional": true
|
|
90
95
|
},
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface ParsedAction {
|
|
2
|
-
uuid: string;
|
|
3
|
-
controllers: readonly ("Keypad" | "Encoder")[];
|
|
4
|
-
}
|
|
5
|
-
/**
|
|
6
|
-
* Find the manifest.json inside a `*.sdPlugin` directory.
|
|
7
|
-
*
|
|
8
|
-
* - If `explicit` is a string, it is resolved against `root`.
|
|
9
|
-
* - If `explicit` is `false`, codegen is disabled (returns `null`).
|
|
10
|
-
* - Otherwise, auto-detects by scanning `root` for `*.sdPlugin/manifest.json`.
|
|
11
|
-
*/
|
|
12
|
-
export declare function findManifestPath(root: string, explicit?: string | false, warn?: (msg: string) => void): string | null;
|
|
13
|
-
/**
|
|
14
|
-
* Parse a Stream Deck plugin manifest and extract action metadata.
|
|
15
|
-
* Returns an empty array on failure (with a warning).
|
|
16
|
-
*/
|
|
17
|
-
export declare function parseManifest(path: string, warn?: (msg: string) => void): ParsedAction[];
|
|
18
|
-
/**
|
|
19
|
-
* Generate the `declare module` augmentation string from parsed actions.
|
|
20
|
-
* Returns an empty string when there are no actions.
|
|
21
|
-
*/
|
|
22
|
-
export declare function generateManifestDts(actions: ParsedAction[]): string;
|
|
23
|
-
/**
|
|
24
|
-
* Write the generated `.d.ts` file only when the content has changed.
|
|
25
|
-
* Creates the parent directory if it does not exist.
|
|
26
|
-
*
|
|
27
|
-
* @returns `true` if the file was written, `false` if content was unchanged.
|
|
28
|
-
*/
|
|
29
|
-
export declare function writeManifestDtsIfChanged(outPath: string, content: string): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* High-level helper: find, parse, generate, and write manifest types.
|
|
32
|
-
*
|
|
33
|
-
* @returns The resolved manifest path (for watch registration), or `null`.
|
|
34
|
-
*/
|
|
35
|
-
export declare function generateManifestTypes(root: string, manifestOption: string | false | undefined, warn?: (msg: string) => void): {
|
|
36
|
-
manifestPath: string;
|
|
37
|
-
written: boolean;
|
|
38
|
-
} | null;
|