@owncast/plugin-sdk 0.6.0 → 0.11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owncast/plugin-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.11.0",
4
4
  "description": "SDK for authoring Owncast plugins in JavaScript",
5
5
  "license": "MIT",
6
6
  "author": "Owncast",
@@ -28,6 +28,7 @@
28
28
  "files": [
29
29
  "index.js",
30
30
  "index.d.ts",
31
+ "slug.js",
31
32
  "testing.js",
32
33
  "bin/owncast-plugin.js",
33
34
  "scripts/postinstall.js"
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // That's all an author needs: plugins ship source and run on the interpreter
8
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
9
+ // binaryen) is NOT downloaded here. It's a maintainer-only dependency of the
10
10
  // engine build (see engines/install-toolchain.mjs).
11
11
  //
12
12
  // PoC scope: linux-x86_64 + darwin-arm64 + darwin-x86_64 covered.
@@ -23,15 +23,15 @@ const HOST_BINARIES_REPO = "owncast/plugin-sdk";
23
23
 
24
24
  // The host binaries (owncast-plugin-test/serve) implement the host-function
25
25
  // contract that the bundled JS runtime imports. That contract is additive
26
- // within a major version host functions are only ever added, never removed or
27
- // renamed (a removal is a breaking change that requires a major bump) so the
26
+ // within a major version. Host functions are only ever added, never removed or
27
+ // renamed (a removal is a breaking change that requires a major bump), so the
28
28
  // NEWEST published binary is compatible with every plugin runtime. We therefore
29
29
  // fetch the latest release tag rather than deriving one from the npm version.
30
30
  //
31
31
  // This keeps the binary in lockstep with `@owncast/plugin-sdk@^x` (which npm
32
32
  // already floats to the newest compatible runtime) and fixes the old "zero the
33
33
  // patch" guess: that fetched v<major>.<minor>.0, which 404'd on JS-only patches
34
- // and when a host change shipped in a patch (e.g. timer support in 0.4.2)
34
+ // and, when a host change shipped in a patch (e.g. timer support in 0.4.2),
35
35
  // fetched a binary too old to satisfy the runtime's imports, breaking
36
36
  // `npm test`.
37
37
  //
@@ -156,7 +156,7 @@ async function main() {
156
156
  fs.chmodSync(dest, 0o755);
157
157
  fs.unlinkSync(gz);
158
158
  } catch (e) {
159
- // 404 is expected before the first release; other errors get a soft
159
+ // 404 is expected before the first release. Other errors get a soft
160
160
  // warning so the user sees them but the install still succeeds.
161
161
  console.warn(
162
162
  `[plugin-sdk] could not fetch ${binary}: ${e.message}\n` +
package/slug.js ADDED
@@ -0,0 +1,26 @@
1
+ // slugify mirrors the host's Go slugify and the Python SDK's: ASCII letters and
2
+ // digits pass through lowercased, everything else collapses to a single hyphen,
3
+ // and trailing hyphens are trimmed. Shared by the build CLI and the JS test API
4
+ // so a manifest that omits `slug` resolves to the same `<slug>.js` artifact name
5
+ // everywhere. Non-ASCII names (e.g. "Café") degrade noisily (-> "caf"), so pin
6
+ // `slug` in the manifest for accented or non-Latin display names.
7
+ function slugify(input) {
8
+ let out = "";
9
+ let prevHyphen = false;
10
+ for (const ch of input) {
11
+ const code = ch.codePointAt(0);
12
+ let lower = ch;
13
+ if (code >= 65 && code <= 90) lower = String.fromCodePoint(code + 32);
14
+ const lc = lower.codePointAt(0);
15
+ if ((lc >= 97 && lc <= 122) || (lc >= 48 && lc <= 57)) {
16
+ out += lower;
17
+ prevHyphen = false;
18
+ } else if (!prevHyphen && out.length > 0) {
19
+ out += "-";
20
+ prevHyphen = true;
21
+ }
22
+ }
23
+ return out.replace(/-+$/, "");
24
+ }
25
+
26
+ module.exports = { slugify };
package/testing.js CHANGED
@@ -28,35 +28,12 @@ const fs = require("fs");
28
28
  const os = require("os");
29
29
  const path = require("path");
30
30
  const { execFileSync } = require("child_process");
31
+ const { slugify } = require("./slug");
31
32
 
32
33
  // Find the directory holding the owncast-plugin-test binary. Check for that
33
34
  // binary specifically (not just any toolchain file) so we correctly fall
34
35
  // through to the dev tools/ dir when postinstall has only fetched part of the
35
36
  // toolchain (e.g., on a not-yet-released SDK version).
36
- // slugifyForTest mirrors the slugify in the build CLI + host SDKs so
37
- // this entrypoint can locate the .wasm file when a manifest omits
38
- // `slug`. ASCII letters and digits pass through lowercased;
39
- // everything else collapses to a single hyphen; trailing hyphens are
40
- // trimmed.
41
- function slugifyForTest(input) {
42
- let out = "";
43
- let prevHyphen = false;
44
- for (const ch of input) {
45
- const code = ch.codePointAt(0);
46
- let lower = ch;
47
- if (code >= 65 && code <= 90) lower = String.fromCodePoint(code + 32);
48
- const lc = lower.codePointAt(0);
49
- if ((lc >= 97 && lc <= 122) || (lc >= 48 && lc <= 57)) {
50
- out += lower;
51
- prevHyphen = false;
52
- } else if (!prevHyphen && out.length > 0) {
53
- out += "-";
54
- prevHyphen = true;
55
- }
56
- }
57
- return out.replace(/-+$/, "");
58
- }
59
-
60
37
  function findCacheDir() {
61
38
  const candidates = [
62
39
  path.join(__dirname, "bin", ".cache"), // installed under node_modules
@@ -81,7 +58,7 @@ function findCacheDir() {
81
58
  * Sets `process.exitCode` to non-zero if any scenario failed (never resets a
82
59
  * previously-failed code), and returns true on success / false on failure
83
60
  * WITHOUT exiting the process. That lets one node process run several test
84
- * files in a row (see runScenarioFiles); the process ends with the right code
61
+ * files in a row (see runScenarioFiles). The process ends with the right code
85
62
  * once the event loop drains.
86
63
  *
87
64
  * @param {Array<object>} scenarios, scenario objects: { name, given?, events, expect? }
@@ -113,20 +90,22 @@ function runScenarios(scenarios, opts = {}) {
113
90
  console.error("manifest.name is required");
114
91
  return fail(2);
115
92
  }
116
- // wasm + symlink filenames key off slug (the identifier), not the
117
- // display name. Derive the slug here the same way the build CLI
118
- // does so this entrypoint works on manifests that omit `slug`.
119
- const slug = manifest.slug || slugifyForTest(manifest.name);
93
+ // The built artifact + symlink filenames key off slug (the identifier), not
94
+ // the display name. Derive the slug here the same way the build CLI does so
95
+ // this entrypoint works on manifests that omit `slug`.
96
+ const slug = manifest.slug || slugify(manifest.name);
120
97
  if (!slug) {
121
98
  console.error(
122
99
  `could not derive slug from manifest.name ${JSON.stringify(manifest.name)}; set manifest.slug explicitly`,
123
100
  );
124
101
  return fail(2);
125
102
  }
126
- const wasmPath = path.join(cwd, `${slug}.wasm`);
127
- if (!fs.existsSync(wasmPath)) {
103
+ // Plugins ship as source: `owncast-plugin build` bundles src/plugin.{js,ts}
104
+ // into <slug>.js, which is what the host loads. (`npm test` builds first.)
105
+ const scriptPath = path.join(cwd, `${slug}.js`);
106
+ if (!fs.existsSync(scriptPath)) {
128
107
  console.error(
129
- `${slug}.wasm not found at ${wasmPath}, run \`owncast-plugin package\` first`,
108
+ `${slug}.js not found at ${scriptPath}, run \`owncast-plugin build\` first`,
130
109
  );
131
110
  return fail(2);
132
111
  }
@@ -141,32 +120,20 @@ function runScenarios(scenarios, opts = {}) {
141
120
  return fail(2);
142
121
  }
143
122
 
144
- // Build a temp project dir that links to the wasm + manifest and contains
145
- // only the scenarios we're running. The binary will auto-discover them.
123
+ // Build a temp project dir that links to the built script + manifest and
124
+ // contains only the scenarios we're running. The binary auto-discovers them.
146
125
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "owncast-plugin-test-"));
147
126
  try {
148
127
  fs.symlinkSync(manifestPath, path.join(tmp, "plugin.manifest.json"));
149
- fs.symlinkSync(wasmPath, path.join(tmp, `${slug}.wasm`));
128
+ fs.symlinkSync(scriptPath, path.join(tmp, `${slug}.js`));
150
129
  fs.mkdirSync(path.join(tmp, "__tests__"));
151
130
  fs.writeFileSync(
152
131
  path.join(tmp, "__tests__", "scenarios.test.json"),
153
132
  JSON.stringify(scenarios, null, 2),
154
133
  );
155
134
 
156
- // Match the build CLI: extism-js (and its wasm-merge/wasm-opt
157
- // children) needs LD_LIBRARY_PATH on Linux and DYLD_LIBRARY_PATH +
158
- // DYLD_FALLBACK_LIBRARY_PATH on macOS to find libbinaryen via
159
- // @rpath. Setting all three is safe on both OSes; the inactive
160
- // ones are ignored.
161
- const libDir = path.join(cache, "lib");
162
- const env = {
163
- ...process.env,
164
- LD_LIBRARY_PATH: `${libDir}:${process.env.LD_LIBRARY_PATH || ""}`,
165
- DYLD_LIBRARY_PATH: `${libDir}:${process.env.DYLD_LIBRARY_PATH || ""}`,
166
- DYLD_FALLBACK_LIBRARY_PATH: `${libDir}:${process.env.DYLD_FALLBACK_LIBRARY_PATH || "/usr/local/lib:/usr/lib"}`,
167
- };
168
135
  try {
169
- execFileSync(bin, [tmp], { stdio: "inherit", env });
136
+ execFileSync(bin, [tmp], { stdio: "inherit" });
170
137
  } catch (e) {
171
138
  return fail(typeof e.status === "number" ? e.status : 1);
172
139
  }