@owncast/plugin-sdk 0.5.0 → 0.6.0

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.
@@ -25,27 +25,6 @@ function fail(e) {
25
25
  process.exit(1);
26
26
  }
27
27
 
28
- // toolchainEnv extends the current environment with the variables
29
- // the dynamic linker needs to find `libbinaryen` next to `wasm-merge`
30
- // and `wasm-opt` (which extism-js shells out to during the wasm
31
- // pipeline). Linux uses LD_LIBRARY_PATH; macOS uses DYLD_LIBRARY_PATH
32
- // plus DYLD_FALLBACK_LIBRARY_PATH (Apple Silicon strips
33
- // DYLD_LIBRARY_PATH in some sandboxed contexts, the FALLBACK
34
- // variant survives). Setting all three is safe on both OSes; the
35
- // inactive ones are ignored. This is the difference between "build
36
- // succeeds" and `library not loaded: @rpath/libbinaryen.dylib` on
37
- // macOS.
38
- function toolchainEnv(cache) {
39
- const libDir = path.join(cache, "lib");
40
- return {
41
- ...process.env,
42
- PATH: `${cache}:${process.env.PATH}`,
43
- LD_LIBRARY_PATH: `${libDir}:${process.env.LD_LIBRARY_PATH || ""}`,
44
- DYLD_LIBRARY_PATH: `${libDir}:${process.env.DYLD_LIBRARY_PATH || ""}`,
45
- DYLD_FALLBACK_LIBRARY_PATH: `${libDir}:${process.env.DYLD_FALLBACK_LIBRARY_PATH || "/usr/local/lib:/usr/lib"}`,
46
- };
47
- }
48
-
49
28
  // slugPattern matches a valid plugin slug: a lowercase letter
50
29
  // followed by lowercase letters/digits/hyphens, up to 64 chars total.
51
30
  // Same shape the host + SDK + registry all validate against.
@@ -127,11 +106,9 @@ function runBinary(name, args) {
127
106
  );
128
107
  process.exit(1);
129
108
  }
130
- const env = toolchainEnv(cache);
131
109
  try {
132
110
  execFileSync(bin, args.length > 0 ? args : [process.cwd()], {
133
111
  stdio: "inherit",
134
- env,
135
112
  });
136
113
  } catch (e) {
137
114
  process.exit(typeof e.status === "number" ? e.status : 1);
@@ -166,94 +143,27 @@ async function buildMain() {
166
143
  "no plugin source found (expected src/plugin.ts or plugin.js)",
167
144
  );
168
145
 
169
- // Synthesize an entry that injects the manifest, requires user code,
170
- // then re-exports the SDK runtime exports as wasm-visible exports.
146
+ // Shared-engine model: bundle the author's plugin into a tiny CommonJS
147
+ // script with @owncast/plugin-sdk marked EXTERNAL. It ships in the .ocpkg as
148
+ // plugin.js; the host infers the JavaScript runtime from that filename and
149
+ // runs it on the embedded JS engine, which provides
150
+ // require("@owncast/plugin-sdk"). No per-plugin wasm, no extism-js.
171
151
  const buildDir = path.join(cwd, ".owncast-build");
172
152
  fs.mkdirSync(buildDir, { recursive: true });
173
- const synthEntry = path.join(buildDir, "entry.js");
174
- const manifestJSON = JSON.stringify(manifest);
175
- // Always emit register/on_event/on_filter as wasm exports. The SDK derives
176
- // subscriptions at runtime from the plugin's handler methods and merges
177
- // them into the manifest returned by register(). The host then only calls
178
- // on_event/on_filter for plugins actually subscribed to that event, so
179
- // unused exports are harmless.
180
- const entrySrc = `const sdk = require("@owncast/plugin-sdk");
181
- const MANIFEST_BASE = ${manifestJSON};
182
- require(${JSON.stringify(entry)});
183
- function register() {
184
- const manifest = Object.assign({}, MANIFEST_BASE, { subscriptions: sdk.describeSubscriptions() });
185
- Host.outputString(JSON.stringify(manifest));
186
- return 0;
187
- }
188
- function on_event() {
189
- const envelope = JSON.parse(Host.inputString());
190
- sdk.dispatchEvent(envelope);
191
- return 0;
192
- }
193
- function on_filter() {
194
- const envelope = JSON.parse(Host.inputString());
195
- const result = sdk.dispatchFilter(envelope);
196
- Host.outputString(JSON.stringify(result));
197
- return 0;
198
- }
199
- function on_http_request() {
200
- const request = JSON.parse(Host.inputString());
201
- const response = sdk.dispatchHttp(request);
202
- Host.outputString(JSON.stringify(response));
203
- return 0;
204
- }
205
- function on_tab_content() {
206
- const req = JSON.parse(Host.inputString());
207
- Host.outputString(sdk.dispatchTabContent(req));
208
- return 0;
209
- }
210
- function on_page_content() {
211
- const req = JSON.parse(Host.inputString());
212
- Host.outputString(sdk.dispatchPageContent(req));
213
- return 0;
214
- }
215
- module.exports = { register, on_event, on_filter, on_http_request, on_tab_content, on_page_content };
216
- `;
217
- fs.writeFileSync(synthEntry, entrySrc);
218
-
219
- // Bundle to a single CJS file targeting the QuickJS runtime extism-js uses.
220
- const bundledJS = path.join(buildDir, "bundle.js");
153
+ const scriptOut = path.join(cwd, `${slug}.js`);
221
154
  await esbuild.build({
222
- entryPoints: [synthEntry],
155
+ entryPoints: [entry],
223
156
  bundle: true,
224
157
  format: "cjs",
225
158
  platform: "neutral",
226
159
  target: "es2020",
227
- outfile: bundledJS,
160
+ external: ["@owncast/plugin-sdk"],
161
+ outfile: scriptOut,
228
162
  logLevel: "warning",
229
163
  });
230
164
 
231
- // Generate index.d.ts declaring exports + host imports based on permissions.
232
- const dts = path.join(buildDir, "index.d.ts");
233
- fs.writeFileSync(dts, generateInterface(manifest));
234
-
235
- // Find toolchain.
236
- const cache = findCacheDir();
237
- const extismJs = path.join(cache, "extism-js");
238
- if (!fs.existsSync(extismJs)) {
239
- throw new Error(
240
- `extism-js not found at ${extismJs}, run \`npm install\` to fetch the toolchain`,
241
- );
242
- }
243
- const env = toolchainEnv(cache);
244
-
245
- const wasmOut = path.join(cwd, `${slug}.wasm`);
246
- execFileSync(extismJs, [bundledJS, "-i", dts, "-o", wasmOut], {
247
- stdio: "inherit",
248
- env,
249
- });
250
-
251
- // public/ and assets/ live at the source root; the host's
252
- // loose-files loader picks them up as siblings of the built
253
- // <slug>.wasm without any rename, so the build CLI doesn't need to
254
- // create or mirror anything for them.
255
-
256
- console.log(`built ${path.relative(cwd, wasmOut)}`);
165
+ // public/ and assets/ live at the source root; the packager picks them up.
166
+ console.log(`built ${path.relative(cwd, scriptOut)}`);
257
167
  }
258
168
 
259
169
  // `owncast-plugin package`, bundle the project into a single .ocpkg file
@@ -269,16 +179,19 @@ async function packageMain() {
269
179
  const manifest = readAndResolveManifest(manifestPath);
270
180
  const slug = manifest.slug;
271
181
 
272
- const wasmPath = path.join(cwd, `${slug}.wasm`);
273
- if (!fs.existsSync(wasmPath)) {
182
+ const scriptPath = path.join(cwd, `${slug}.js`);
183
+ if (!fs.existsSync(scriptPath)) {
274
184
  await buildMain();
275
185
  }
276
186
 
187
+ // The code entry's name (plugin.js) is what tells the host this is a
188
+ // JavaScript plugin — no "type" field in the manifest. The manifest ships
189
+ // verbatim.
277
190
  const publicDir = path.join(cwd, "public");
278
191
  const assetsDir = path.join(cwd, "assets");
279
192
  const zip = new JSZip();
280
193
  zip.file("plugin.manifest.json", fs.readFileSync(manifestPath));
281
- zip.file("plugin.wasm", fs.readFileSync(wasmPath));
194
+ zip.file("plugin.js", fs.readFileSync(scriptPath));
282
195
  let fileCount = 2;
283
196
  // Bundle a top-level icon.png if the plugin source root has one.
284
197
  // The host reads it from /api/plugins/<slug>/icon to render in the
@@ -329,19 +242,19 @@ async function packageMain() {
329
242
  `packaged ${path.relative(cwd, outPath)} (${sizeKb} KB, ${fileCount} files)`,
330
243
  );
331
244
 
332
- // Drop the intermediate <slug>.wasm now that it's bundled inside the
245
+ // Drop the intermediate <slug>.js now that it's bundled inside the
333
246
  // .ocpkg. The .ocpkg is the only artifact authors care about: leaving
334
- // the loose .wasm next to it just confuses "what do I ship". Only
247
+ // the loose script next to it just confuses "what do I ship". Only
335
248
  // runs on a successful package so a mid-pipeline failure leaves the
336
249
  // last good build in place for debugging.
337
250
  try {
338
- fs.unlinkSync(wasmPath);
251
+ fs.unlinkSync(scriptPath);
339
252
  } catch (e) {
340
253
  // Don't fail the package step over a cleanup miss. The .ocpkg is
341
254
  // already written; surface the warning so the author notices the
342
255
  // straggler but treat the run as successful.
343
256
  if (e.code !== "ENOENT") {
344
- console.warn(`warning: could not clean up ${path.relative(cwd, wasmPath)}: ${e.message}`);
257
+ console.warn(`warning: could not clean up ${path.relative(cwd, scriptPath)}: ${e.message}`);
345
258
  }
346
259
  }
347
260
  }
@@ -366,112 +279,6 @@ function* walkFiles(dir) {
366
279
  }
367
280
  }
368
281
 
369
- function generateInterface(manifest) {
370
- const exports = [
371
- "register(): I32",
372
- "on_event(): I32",
373
- "on_filter(): I32",
374
- "on_http_request(): I32",
375
- "on_tab_content(): I32",
376
- "on_page_content(): I32",
377
- ];
378
-
379
- const perms = new Set(manifest.permissions || []);
380
- const imports = [];
381
- // Timers are ambient (no permission): the host always provides them, since
382
- // a plugin can't setTimeout in the sandbox.
383
- imports.push("owncast_timer_set(id: I64, delayMs: I64, repeat: I32): I32");
384
- imports.push("owncast_timer_clear(id: I64): void");
385
- // Config is ambient too: a plugin reading its own manifest-declared config
386
- // (admin override falling back to the declared default) needs no permission.
387
- imports.push("owncast_config_get(keyPtr: PTR): PTR");
388
- // Asset reading is ambient: a plugin reads only files it shipped itself.
389
- imports.push("owncast_asset_read(pathPtr: PTR): PTR");
390
- if (perms.has("chat.send")) {
391
- imports.push("owncast_send_chat(textPtr: PTR): void");
392
- imports.push("owncast_send_chat_action(textPtr: PTR): void");
393
- imports.push("owncast_send_chat_system(bodyPtr: PTR): void");
394
- imports.push("owncast_send_chat_to(clientId: I64, textPtr: PTR): void");
395
- }
396
- if (perms.has("chat.history")) {
397
- imports.push("owncast_chat_history(limit: I32): PTR");
398
- imports.push("owncast_chat_clients(): PTR");
399
- }
400
- if (perms.has("chat.moderate")) {
401
- imports.push("owncast_delete_message(idPtr: PTR): void");
402
- imports.push("owncast_kick_client(clientId: I64): void");
403
- }
404
- if (perms.has("notifications.send")) {
405
- imports.push("owncast_notify_discord(textPtr: PTR): void");
406
- imports.push("owncast_notify_browser_push(payloadPtr: PTR): void");
407
- imports.push("owncast_notify_fediverse(payloadPtr: PTR): void");
408
- }
409
- if (perms.has("users.read")) {
410
- imports.push("owncast_users_list(): PTR");
411
- imports.push("owncast_user_get(idPtr: PTR): PTR");
412
- }
413
- if (perms.has("users.moderate")) {
414
- imports.push(
415
- "owncast_user_set_enabled(idPtr: PTR, enabled: I32, reasonPtr: PTR): void",
416
- );
417
- imports.push("owncast_ban_ip(ipPtr: PTR): void");
418
- }
419
- if (perms.has("storage.upload")) {
420
- imports.push("owncast_storage_upload(namePtr: PTR, dataPtr: PTR): PTR");
421
- }
422
- if (perms.has("storage.fs")) {
423
- imports.push("owncast_fs_read(pathPtr: PTR): PTR");
424
- imports.push("owncast_fs_write(pathPtr: PTR, dataPtr: PTR): PTR");
425
- imports.push("owncast_fs_list(dirPtr: PTR): PTR");
426
- imports.push("owncast_fs_delete(pathPtr: PTR): PTR");
427
- imports.push("owncast_fs_exists(pathPtr: PTR): I32");
428
- }
429
- if (perms.has("fediverse.post")) {
430
- imports.push("owncast_fediverse_post(textPtr: PTR): PTR");
431
- }
432
- if (perms.has("storage.kv")) {
433
- imports.push("owncast_kv_get(keyPtr: PTR): PTR");
434
- imports.push("owncast_kv_set(keyPtr: PTR, valPtr: PTR): void");
435
- }
436
- if (perms.has("events.emit"))
437
- imports.push(
438
- "owncast_emit_event(eventTypePtr: PTR, payloadPtr: PTR): void",
439
- );
440
- if (perms.has("http.sse"))
441
- imports.push(
442
- "owncast_sse_send(channelPtr: PTR, eventPtr: PTR, dataPtr: PTR): void",
443
- );
444
- if (perms.has("server.read")) {
445
- imports.push("owncast_stream_current(): PTR");
446
- imports.push("owncast_server_info(): PTR");
447
- imports.push("owncast_server_socials(): PTR");
448
- imports.push("owncast_server_emotes(): PTR");
449
- imports.push("owncast_server_federation(): PTR");
450
- imports.push("owncast_stream_broadcaster(): PTR");
451
- imports.push("owncast_server_tags(): PTR");
452
- }
453
- if (perms.has("videoconfig.read")) {
454
- imports.push("owncast_video_config_read(): PTR");
455
- }
456
- if (perms.has("videoconfig.write")) {
457
- imports.push("owncast_video_config_write(configPtr: PTR): PTR");
458
- }
459
- if (perms.has("ui.modify")) {
460
- imports.push("owncast_add_actions(actionsPtr: PTR): void");
461
- imports.push("owncast_clear_actions(): void");
462
- }
463
-
464
- let out = `declare module 'main' {\n`;
465
- for (const e of exports) out += ` export function ${e};\n`;
466
- out += `}\n`;
467
- if (imports.length > 0) {
468
- out += `\ndeclare module 'extism:host' {\n interface user {\n`;
469
- for (const i of imports) out += ` ${i};\n`;
470
- out += ` }\n}\n`;
471
- }
472
- return out;
473
- }
474
-
475
282
  function findCacheDir() {
476
283
  // Look in node_modules/@owncast/plugin-sdk/bin/.cache (when used as a dep)
477
284
  // and in the repo's tools/ dir (when developing). The dev candidate
@@ -482,12 +289,10 @@ function findCacheDir() {
482
289
  path.join(__dirname, "..", "bin", ".cache"),
483
290
  path.join(__dirname, "..", "..", "..", "tools"),
484
291
  ];
485
- // Pick the first candidate that has any of the expected tools, different
486
- // commands need different binaries (build needs extism-js, test needs
487
- // owncast-plugin-test) but they share a cache.
292
+ // Pick the first candidate that has the prebuilt host binaries (the only
293
+ // tooling the SDK ships now — `build` is pure esbuild and needs nothing here).
488
294
  for (const c of candidates) {
489
295
  if (
490
- fs.existsSync(path.join(c, "extism-js")) ||
491
296
  fs.existsSync(path.join(c, "owncast-plugin-test")) ||
492
297
  fs.existsSync(path.join(c, "owncast-plugin-serve"))
493
298
  ) {
package/index.d.ts CHANGED
@@ -312,6 +312,22 @@ export interface TickEvent {
312
312
  }
313
313
 
314
314
  export interface PluginDef {
315
+ /** Declarative chat-command table. When set, the SDK wires the chat
316
+ * subscription and prefix parsing for you — no onChatMessage needed. Maps
317
+ * canonical command name → definition (run/description/usage/aliases/
318
+ * modOnly/cooldownMs/...); see {@link CommandDefinition}. For advanced
319
+ * composition (e.g. dropping command messages via a filter) use the
320
+ * lower-level {@link defineCommands} router instead. If you also provide
321
+ * onChatMessage, the router runs first and then onChatMessage runs for every
322
+ * message. */
323
+ commands?: Record<string, CommandDefinition>;
324
+ /** Command prefix for the `commands` table. Default "!". */
325
+ commandPrefix?: string;
326
+ /** Match command names case-sensitively. Default false. */
327
+ commandsCaseSensitive?: boolean;
328
+ /** Called when a prefixed message matched no command in `commands`. */
329
+ onUnknownCommand?(ctx: CommandContext): void;
330
+
315
331
  /** Notification handler for chat messages. Fire-and-forget. */
316
332
  onChatMessage?(msg: ChatMessage): void | Promise<void>;
317
333
 
@@ -407,6 +423,11 @@ export interface CommandContext {
407
423
 
408
424
  /** One command in a {@link defineCommands} table. */
409
425
  export interface CommandDefinition {
426
+ /** Short, human-readable summary of what the command does. Surfaced in
427
+ * command listings (e.g. a future `!help`); ignored by the router itself. */
428
+ description?: string;
429
+ /** Optional usage/example string, e.g. "!latency <0-4>". */
430
+ usage?: string;
410
431
  /** Alternate names that invoke this command. */
411
432
  aliases?: string[];
412
433
  /** Only allow senders whose scopes include "MODERATOR". */
package/index.js CHANGED
@@ -7,6 +7,11 @@
7
7
 
8
8
  let registered = null;
9
9
 
10
+ // Command metadata recorded by defineCommands, reported to the host via
11
+ // register() so it can build a unified `!help` across all plugins. One entry
12
+ // per command: { name, prefix, description, usage, aliases, modOnly }.
13
+ const commandManifest = [];
14
+
10
15
  // Host-driven timers. The sandbox has no setTimeout, so owncast.timer.* asks
11
16
  // the host to schedule a callback and call back via the internal "timer.fire"
12
17
  // event. The author's callback stays here in the long-lived instance, keyed by
@@ -149,6 +154,28 @@ const isFn = (x) => typeof x === JsType.Function;
149
154
  const isObj = (x) => x !== null && typeof x === JsType.Object;
150
155
 
151
156
  function definePlugin(def) {
157
+ // `commands` is declarative sugar: give definePlugin a command table (and an
158
+ // optional commandPrefix) and the SDK wires the chat subscription for you —
159
+ // no onChatMessage needed. It expands into an onChatMessage handler here, so
160
+ // subscription derivation and dispatch treat it like any chat handler. If you
161
+ // also pass onChatMessage, the command router runs first, then your handler.
162
+ // For advanced composition (e.g. dropping command messages from chat via a
163
+ // filter), use the lower-level defineCommands() router directly instead.
164
+ if (def && isObj(def.commands)) {
165
+ const router = defineCommands({
166
+ prefix: def.commandPrefix,
167
+ caseSensitive: def.commandsCaseSensitive,
168
+ commands: def.commands,
169
+ onUnknown: def.onUnknownCommand,
170
+ });
171
+ const userHandler = def.onChatMessage;
172
+ def.onChatMessage = isFn(userHandler)
173
+ ? (msg) => {
174
+ router(msg);
175
+ userHandler(msg);
176
+ }
177
+ : router;
178
+ }
152
179
  registered = def;
153
180
  return def;
154
181
  }
@@ -183,13 +210,24 @@ function defineCommands(config) {
183
210
  const caseSensitive = !!config.caseSensitive;
184
211
  const norm = (s) => (caseSensitive ? s : s.toLowerCase());
185
212
 
186
- // Resolve every name and alias to its canonical command definition.
213
+ // Resolve every name and alias to its canonical command definition, and
214
+ // record metadata so the host can build a unified `!help` (see
215
+ // describeCommands). Metadata is reported via register(); it never affects
216
+ // routing.
187
217
  const table = new Map();
188
218
  const defs = config.commands || {};
189
219
  for (const name of Object.keys(defs)) {
190
220
  const def = defs[name];
191
221
  table.set(norm(name), { name, def });
192
222
  for (const alias of def.aliases || []) table.set(norm(alias), { name, def });
223
+ commandManifest.push({
224
+ name,
225
+ prefix,
226
+ description: def.description || "",
227
+ usage: def.usage || "",
228
+ aliases: def.aliases || [],
229
+ modOnly: !!def.modOnly,
230
+ });
193
231
  }
194
232
 
195
233
  // Per-(command,user) cooldown clock, in memory for the plugin's lifetime.
@@ -286,6 +324,13 @@ function describeSubscriptions() {
286
324
  return { notify, filter: filterSubs };
287
325
  }
288
326
 
327
+ // Used by the build-generated entry to report the plugin's chat commands in
328
+ // register(), so the host can answer a unified `!help`. Empty when the plugin
329
+ // declares no commands.
330
+ function describeCommands() {
331
+ return commandManifest;
332
+ }
333
+
289
334
  function dispatchEvent(envelope) {
290
335
  const { eventType, payload } = envelope;
291
336
  // Internal: a host-scheduled timer elapsed. Run the author's callback,
@@ -909,6 +954,7 @@ module.exports = {
909
954
  Events,
910
955
  Permissions,
911
956
  describeSubscriptions,
957
+ describeCommands,
912
958
  dispatchEvent,
913
959
  dispatchFilter,
914
960
  dispatchHttp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owncast/plugin-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "SDK for authoring Owncast plugins in JavaScript",
5
5
  "license": "MIT",
6
6
  "author": "Owncast",
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env node
2
- // Downloads per-platform tooling into <sdk>/bin/.cache so the build CLI
3
- // finds it without polluting the user's system:
2
+ // Downloads the prebuilt host binaries into <sdk>/bin/.cache so `owncast-plugin
3
+ // test` / `serve` work without polluting the user's system:
4
4
  //
5
- // - extism-js , JS → wasm compiler (extism/js-pdk releases)
6
- // - wasm-merge, wasm-opt, lib , binaryen post-processing (WebAssembly/binaryen releases)
7
5
  // - owncast-plugin-test/serve , scenario runner + dev server (this repo's releases)
8
6
  //
7
+ // That's all an author needs: plugins ship source and run on the interpreter
8
+ // engine the host already embeds, so the wasm compiler toolchain (extism-js,
9
+ // binaryen) is NOT downloaded here — it's a maintainer-only dependency of the
10
+ // engine build (see engines/install-toolchain.mjs).
11
+ //
9
12
  // PoC scope: linux-x86_64 + darwin-arm64 + darwin-x86_64 covered.
10
13
  // owncast-plugin-test/serve downloads gracefully skip if the matching
11
14
  // release asset isn't published yet, dev builds can substitute their own
@@ -15,10 +18,7 @@ const fs = require("fs");
15
18
  const path = require("path");
16
19
  const https = require("https");
17
20
  const zlib = require("zlib");
18
- const { execFileSync } = require("child_process");
19
21
 
20
- const EXTISM_JS_VERSION = "v1.6.0";
21
- const BINARYEN_VERSION = "version_119";
22
22
  const HOST_BINARIES_REPO = "owncast/plugin-sdk";
23
23
 
24
24
  // The host binaries (owncast-plugin-test/serve) implement the host-function
@@ -98,29 +98,6 @@ function platformKey() {
98
98
  throw new Error(`unsupported platform: ${platform}/${arch}`);
99
99
  }
100
100
 
101
- function extismJsURL() {
102
- // extism-js release naming uses different conventions per OS.
103
- const map = {
104
- "linux-x86_64": `extism-js-x86_64-linux-${EXTISM_JS_VERSION}.gz`,
105
- "linux-aarch64": `extism-js-aarch64-linux-${EXTISM_JS_VERSION}.gz`,
106
- "darwin-x86_64": `extism-js-x86_64-macos-${EXTISM_JS_VERSION}.gz`,
107
- "darwin-arm64": `extism-js-aarch64-macos-${EXTISM_JS_VERSION}.gz`,
108
- };
109
- const file = map[platformKey()];
110
- return `https://github.com/extism/js-pdk/releases/download/${EXTISM_JS_VERSION}/${file}`;
111
- }
112
-
113
- function binaryenURL() {
114
- const map = {
115
- "linux-x86_64": `binaryen-${BINARYEN_VERSION}-x86_64-linux.tar.gz`,
116
- "linux-aarch64": `binaryen-${BINARYEN_VERSION}-aarch64-linux.tar.gz`,
117
- "darwin-x86_64": `binaryen-${BINARYEN_VERSION}-x86_64-macos.tar.gz`,
118
- "darwin-arm64": `binaryen-${BINARYEN_VERSION}-arm64-macos.tar.gz`,
119
- };
120
- const file = map[platformKey()];
121
- return `https://github.com/WebAssembly/binaryen/releases/download/${BINARYEN_VERSION}/${file}`;
122
- }
123
-
124
101
  function hostBinaryURL(name, version) {
125
102
  // Per-platform asset naming matches Go's GOOS-GOARCH convention so the
126
103
  // release CI can `go build` once per matrix entry without renaming.
@@ -155,38 +132,6 @@ async function main() {
155
132
  const cacheDir = path.join(__dirname, "..", "bin", ".cache");
156
133
  fs.mkdirSync(cacheDir, { recursive: true });
157
134
 
158
- const extismDest = path.join(cacheDir, "extism-js");
159
- if (!fs.existsSync(extismDest)) {
160
- const gz = path.join(cacheDir, "extism-js.gz");
161
- console.log(`[plugin-sdk] downloading extism-js ${EXTISM_JS_VERSION}...`);
162
- await download(extismJsURL(), gz);
163
- const buf = zlib.gunzipSync(fs.readFileSync(gz));
164
- fs.writeFileSync(extismDest, buf);
165
- fs.chmodSync(extismDest, 0o755);
166
- fs.unlinkSync(gz);
167
- }
168
-
169
- const wasmMergeDest = path.join(cacheDir, "wasm-merge");
170
- const wasmOptDest = path.join(cacheDir, "wasm-opt");
171
- if (!fs.existsSync(wasmMergeDest) || !fs.existsSync(wasmOptDest)) {
172
- const tar = path.join(cacheDir, "binaryen.tar.gz");
173
- console.log(`[plugin-sdk] downloading binaryen ${BINARYEN_VERSION}...`);
174
- await download(binaryenURL(), tar);
175
- execFileSync("tar", ["xzf", tar, "-C", cacheDir]);
176
- const extracted = path.join(cacheDir, `binaryen-${BINARYEN_VERSION}`);
177
- fs.copyFileSync(path.join(extracted, "bin", "wasm-merge"), wasmMergeDest);
178
- fs.copyFileSync(path.join(extracted, "bin", "wasm-opt"), wasmOptDest);
179
- fs.chmodSync(wasmMergeDest, 0o755);
180
- fs.chmodSync(wasmOptDest, 0o755);
181
- // copy lib too, wasm-opt links against libbinaryen.so on linux
182
- const libSrc = path.join(extracted, "lib");
183
- if (fs.existsSync(libSrc)) {
184
- fs.cpSync(libSrc, path.join(cacheDir, "lib"), { recursive: true });
185
- }
186
- fs.rmSync(extracted, { recursive: true });
187
- fs.unlinkSync(tar);
188
- }
189
-
190
135
  // owncast-plugin-test + owncast-plugin-serve, built from this repo's
191
136
  // host-runtime/ Go sources, published as gzipped release assets on
192
137
  // github.com/owncast/plugin-sdk (roughly halves the download). Skip silently
package/testing.js CHANGED
@@ -10,9 +10,9 @@
10
10
  //
11
11
  // const { runScenarios } = require("@owncast/plugin-sdk/testing");
12
12
  //
13
- // const chat = (user, body) => ({
13
+ // const chat = (name, body) => ({
14
14
  // event: "chat.message.received",
15
- // payload: { id: "1", user, body, timestamp: "2024-01-01T00:00:00Z" },
15
+ // payload: { id: "1", user: { id: name, displayName: name }, body, timestamp: "2024-01-01T00:00:00Z" },
16
16
  // });
17
17
  //
18
18
  // runScenarios([