@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/dist/rollup.d.ts CHANGED
@@ -1,14 +1,42 @@
1
1
  import { Plugin } from 'rollup';
2
2
  import { StreamDeckTargetOptions } from './bundler-shared';
3
- export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, } from './bundler-shared';
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
- * Path to the plugin `manifest.json`. When omitted, the plugin
7
- * auto-detects by scanning the project root for a `*.sdPlugin/manifest.json`.
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
- * Set to `false` to disable manifest type generation entirely.
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?: string | false;
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
- * - `buildStart` resolves root via `resolve(".")` (no Vite config)
23
- * - `writeBundle` reads output dir from `outputOptions.dir` or
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 codegen are shared via bundler-shared.ts,
28
- * font-inline.ts, and manifest-codegen.ts.
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 { generateManifestTypes } from "./manifest-codegen.js";
4
- import { dirname, resolve } from "node:path";
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
- * - `buildStart` resolves root via `resolve(".")` (no Vite config)
16
- * - `writeBundle` reads output dir from `outputOptions.dir` or
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 codegen are shared via bundler-shared.ts,
21
- * font-inline.ts, and manifest-codegen.ts.
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
- const root = resolve(".");
31
- const warn = (msg) => this.warn(msg);
32
- const result = generateManifestTypes(root, options.manifest, warn);
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
  }
@@ -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: 72,
9
- height: 72
8
+ width: 144,
9
+ height: 144
10
10
  },
11
11
  1: {
12
- width: 80,
13
- height: 80
12
+ width: 144,
13
+ height: 144
14
14
  },
15
15
  2: {
16
- width: 96,
17
- height: 96
16
+ width: 144,
17
+ height: 144
18
18
  },
19
19
  3: {
20
- width: 72,
21
- height: 72
20
+ width: 144,
21
+ height: 144
22
22
  },
23
23
  4: {
24
- width: 72,
25
- height: 72
24
+ width: 144,
25
+ height: 144
26
26
  },
27
27
  5: {
28
- width: 72,
29
- height: 72
28
+ width: 144,
29
+ height: 144
30
30
  },
31
31
  6: {
32
- width: 72,
33
- height: 72
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: 72,
41
- height: 72
40
+ width: 144,
41
+ height: 144
42
42
  },
43
43
  9: {
44
- width: 72,
45
- height: 72
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: 72,
76
- height: 72
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
- export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, } from './bundler-shared';
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
- * Path to the plugin `manifest.json`. When omitted, the plugin
15
- * auto-detects by scanning the project root for a `*.sdPlugin/manifest.json`.
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
- * Set to `false` to disable manifest type generation entirely.
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?: string | false;
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 → generate streamdeck-env.d.ts from manifest.json
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 to output directory
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 { generateManifestTypes } from "./manifest-codegen.js";
4
- import { resolve } from "node:path";
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 → generate streamdeck-env.d.ts from manifest.json
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 to output directory
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
- const warn = (msg) => resolvedConfig.logger.warn(msg);
42
- const result = generateManifestTypes(resolvedConfig.root, options.manifest, warn);
43
- if (result) {
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
- copyNativeBindings(resolve(resolvedConfig.root, resolvedConfig.build.outDir), isDevelopment, options, (msg) => {
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
- if (!options.uuid) return;
63
- exec(`streamdeck restart ${options.uuid}`, (err) => {
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.12",
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.71.7",
64
- "@takumi-rs/helpers": "^0.71.7",
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.3",
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-beta.18",
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;