@owncast/plugin-sdk 0.3.0 → 0.4.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/bin/owncast-plugin.js +70 -50
- package/package.json +1 -1
- package/testing.js +9 -1
package/bin/owncast-plugin.js
CHANGED
|
@@ -25,6 +25,27 @@ 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
|
+
|
|
28
49
|
// slugPattern matches a valid plugin slug: a lowercase letter
|
|
29
50
|
// followed by lowercase letters/digits/hyphens, up to 64 chars total.
|
|
30
51
|
// Same shape the host + SDK + registry all validate against.
|
|
@@ -106,10 +127,7 @@ function runBinary(name, args) {
|
|
|
106
127
|
);
|
|
107
128
|
process.exit(1);
|
|
108
129
|
}
|
|
109
|
-
const env =
|
|
110
|
-
...process.env,
|
|
111
|
-
LD_LIBRARY_PATH: `${path.join(cache, "lib")}:${process.env.LD_LIBRARY_PATH || ""}`,
|
|
112
|
-
};
|
|
130
|
+
const env = toolchainEnv(cache);
|
|
113
131
|
try {
|
|
114
132
|
execFileSync(bin, args.length > 0 ? args : [process.cwd()], {
|
|
115
133
|
stdio: "inherit",
|
|
@@ -212,11 +230,7 @@ module.exports = { register, on_event, on_filter, on_http_request };
|
|
|
212
230
|
`extism-js not found at ${extismJs}, run \`npm install\` to fetch the toolchain`,
|
|
213
231
|
);
|
|
214
232
|
}
|
|
215
|
-
const env =
|
|
216
|
-
...process.env,
|
|
217
|
-
PATH: `${cache}:${process.env.PATH}`,
|
|
218
|
-
LD_LIBRARY_PATH: `${path.join(cache, "lib")}:${process.env.LD_LIBRARY_PATH || ""}`,
|
|
219
|
-
};
|
|
233
|
+
const env = toolchainEnv(cache);
|
|
220
234
|
|
|
221
235
|
const wasmOut = path.join(cwd, `${slug}.wasm`);
|
|
222
236
|
execFileSync(extismJs, [bundledJS, "-i", dts, "-o", wasmOut], {
|
|
@@ -224,50 +238,18 @@ module.exports = { register, on_event, on_filter, on_http_request };
|
|
|
224
238
|
env,
|
|
225
239
|
});
|
|
226
240
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// without
|
|
230
|
-
//
|
|
231
|
-
const assetsSrc = path.join(cwd, "assets");
|
|
232
|
-
if (fs.existsSync(assetsSrc) && fs.statSync(assetsSrc).isDirectory()) {
|
|
233
|
-
const assetsDest = path.join(cwd, `${slug}-assets`);
|
|
234
|
-
let needsLink = true;
|
|
235
|
-
// Use lstatSync (not existsSync), existsSync follows symlinks and
|
|
236
|
-
// returns false for a dangling link, but the link's inode is still
|
|
237
|
-
// there and would make symlinkSync below fail with EEXIST. lstatSync
|
|
238
|
-
// sees the link itself regardless of whether its target resolves.
|
|
239
|
-
let st;
|
|
240
|
-
try {
|
|
241
|
-
st = fs.lstatSync(assetsDest);
|
|
242
|
-
} catch {
|
|
243
|
-
// path doesn't exist at all, fall through to create it.
|
|
244
|
-
}
|
|
245
|
-
if (st) {
|
|
246
|
-
let target;
|
|
247
|
-
if (st.isSymbolicLink()) {
|
|
248
|
-
// realpathSync throws on dangling links; treat that as "doesn't
|
|
249
|
-
// match, replace it" rather than letting it abort the build.
|
|
250
|
-
try {
|
|
251
|
-
target = fs.realpathSync(assetsDest);
|
|
252
|
-
} catch {}
|
|
253
|
-
}
|
|
254
|
-
if (target && target === fs.realpathSync(assetsSrc)) {
|
|
255
|
-
needsLink = false;
|
|
256
|
-
} else {
|
|
257
|
-
fs.rmSync(assetsDest, { recursive: true, force: true });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (needsLink) {
|
|
261
|
-
fs.symlinkSync(path.resolve(assetsSrc), assetsDest, "dir");
|
|
262
|
-
}
|
|
263
|
-
}
|
|
241
|
+
// public/ and assets/ live at the source root; the host's
|
|
242
|
+
// loose-files loader picks them up as siblings of the built
|
|
243
|
+
// <slug>.wasm without any rename, so the build CLI doesn't need to
|
|
244
|
+
// create or mirror anything for them.
|
|
264
245
|
|
|
265
246
|
console.log(`built ${path.relative(cwd, wasmOut)}`);
|
|
266
247
|
}
|
|
267
248
|
|
|
268
249
|
// `owncast-plugin package`, bundle the project into a single .ocpkg file
|
|
269
|
-
// (zip archive with plugin.manifest.json, plugin.wasm, and optional
|
|
270
|
-
// Builds the wasm first if it
|
|
250
|
+
// (zip archive with plugin.manifest.json, plugin.wasm, and optional
|
|
251
|
+
// public/ and assets/ directories). Builds the wasm first if it
|
|
252
|
+
// doesn't exist.
|
|
271
253
|
async function packageMain() {
|
|
272
254
|
const cwd = process.cwd();
|
|
273
255
|
const manifestPath = path.join(cwd, "plugin.manifest.json");
|
|
@@ -282,6 +264,7 @@ async function packageMain() {
|
|
|
282
264
|
await buildMain();
|
|
283
265
|
}
|
|
284
266
|
|
|
267
|
+
const publicDir = path.join(cwd, "public");
|
|
285
268
|
const assetsDir = path.join(cwd, "assets");
|
|
286
269
|
const zip = new JSZip();
|
|
287
270
|
zip.file("plugin.manifest.json", fs.readFileSync(manifestPath));
|
|
@@ -296,6 +279,26 @@ async function packageMain() {
|
|
|
296
279
|
zip.file("icon.png", fs.readFileSync(iconPath));
|
|
297
280
|
fileCount++;
|
|
298
281
|
}
|
|
282
|
+
// Bundle a top-level INSTRUCTIONS.md if the plugin source root has one.
|
|
283
|
+
// The host serves it to the admin (which renders it as markdown in a
|
|
284
|
+
// details tab); like icon.png it needs no manifest field and no
|
|
285
|
+
// http.serve permission. The filename is fixed for simplicity.
|
|
286
|
+
const instructionsPath = path.join(cwd, "INSTRUCTIONS.md");
|
|
287
|
+
if (fs.existsSync(instructionsPath) && fs.statSync(instructionsPath).isFile()) {
|
|
288
|
+
zip.file("INSTRUCTIONS.md", fs.readFileSync(instructionsPath));
|
|
289
|
+
fileCount++;
|
|
290
|
+
}
|
|
291
|
+
// public/ → /plugins/<slug>/<path>, served by the host.
|
|
292
|
+
if (fs.existsSync(publicDir) && fs.statSync(publicDir).isDirectory()) {
|
|
293
|
+
for (const file of walkFiles(publicDir)) {
|
|
294
|
+
const rel = path.relative(publicDir, file).split(path.sep).join("/");
|
|
295
|
+
zip.file(`public/${rel}`, fs.readFileSync(file));
|
|
296
|
+
fileCount++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// assets/ → host reads internally for manifest fields that inline
|
|
300
|
+
// file contents (styles, scripts, extraPageContent). Not served at
|
|
301
|
+
// a URL.
|
|
299
302
|
if (fs.existsSync(assetsDir) && fs.statSync(assetsDir).isDirectory()) {
|
|
300
303
|
for (const file of walkFiles(assetsDir)) {
|
|
301
304
|
const rel = path.relative(assetsDir, file).split(path.sep).join("/");
|
|
@@ -315,12 +318,29 @@ async function packageMain() {
|
|
|
315
318
|
console.log(
|
|
316
319
|
`packaged ${path.relative(cwd, outPath)} (${sizeKb} KB, ${fileCount} files)`,
|
|
317
320
|
);
|
|
321
|
+
|
|
322
|
+
// Drop the intermediate <slug>.wasm now that it's bundled inside the
|
|
323
|
+
// .ocpkg. The .ocpkg is the only artifact authors care about: leaving
|
|
324
|
+
// the loose .wasm next to it just confuses "what do I ship". Only
|
|
325
|
+
// runs on a successful package so a mid-pipeline failure leaves the
|
|
326
|
+
// last good build in place for debugging.
|
|
327
|
+
try {
|
|
328
|
+
fs.unlinkSync(wasmPath);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
// Don't fail the package step over a cleanup miss. The .ocpkg is
|
|
331
|
+
// already written; surface the warning so the author notices the
|
|
332
|
+
// straggler but treat the run as successful.
|
|
333
|
+
if (e.code !== "ENOENT") {
|
|
334
|
+
console.warn(`warning: could not clean up ${path.relative(cwd, wasmPath)}: ${e.message}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
318
337
|
}
|
|
319
338
|
|
|
320
339
|
function* walkFiles(dir) {
|
|
321
340
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
322
|
-
//
|
|
323
|
-
//
|
|
341
|
+
// statSync (not lstatSync) so a symlinked file or directory in
|
|
342
|
+
// the source tree resolves to its target and we read its contents
|
|
343
|
+
// rather than skipping it.
|
|
324
344
|
const full = path.join(dir, entry.name);
|
|
325
345
|
let info;
|
|
326
346
|
try {
|
package/package.json
CHANGED
package/testing.js
CHANGED
|
@@ -141,9 +141,17 @@ function runScenarios(scenarios, opts = {}) {
|
|
|
141
141
|
JSON.stringify(scenarios, null, 2),
|
|
142
142
|
);
|
|
143
143
|
|
|
144
|
+
// Match the build CLI: extism-js (and its wasm-merge/wasm-opt
|
|
145
|
+
// children) needs LD_LIBRARY_PATH on Linux and DYLD_LIBRARY_PATH +
|
|
146
|
+
// DYLD_FALLBACK_LIBRARY_PATH on macOS to find libbinaryen via
|
|
147
|
+
// @rpath. Setting all three is safe on both OSes; the inactive
|
|
148
|
+
// ones are ignored.
|
|
149
|
+
const libDir = path.join(cache, "lib");
|
|
144
150
|
const env = {
|
|
145
151
|
...process.env,
|
|
146
|
-
LD_LIBRARY_PATH: `${
|
|
152
|
+
LD_LIBRARY_PATH: `${libDir}:${process.env.LD_LIBRARY_PATH || ""}`,
|
|
153
|
+
DYLD_LIBRARY_PATH: `${libDir}:${process.env.DYLD_LIBRARY_PATH || ""}`,
|
|
154
|
+
DYLD_FALLBACK_LIBRARY_PATH: `${libDir}:${process.env.DYLD_FALLBACK_LIBRARY_PATH || "/usr/local/lib:/usr/lib"}`,
|
|
147
155
|
};
|
|
148
156
|
try {
|
|
149
157
|
execFileSync(bin, [tmp], { stdio: "inherit", env });
|