/** * Smoke: core vs plugin scripts, fork, example install, resolve, run. * * Run: * JFLOW_DATA_DIR=packages/server/data \ * JFLOW_PLUGINS_DIR=packages/server/data/plugins-smoke-test \ * JFLOW_DB_PATH=packages/server/data/plugins-smoke.db \ * node packages/server/test/plugins-smoke.js */ import assert from "node:assert/strict"; import fs from "fs"; import path from "node:path"; import { migrate, db } from "../db.js"; import { getAppVersion, satisfiesRange } from "../app-version.js"; import { forkCoreScript, duplicatePlugin, resolveScriptRef, uninstallPlugin, listInstalledPlugins, createBlankPlugin, pluginDir, } from "../plugin-store.js"; import { installExamplePlugin } from "../plugin-install.js"; import { runScript, clearScriptCache, inspectScriptSource, } from "../script-sandbox.js"; import { PLUGINS_DIR } from "../paths.js"; import pino from "pino"; const silent = pino({ level: "silent" }); async function main() { assert.equal(getAppVersion(), "0.1.0"); assert.equal(satisfiesRange("0.1.0", ">=0.1.0 <1.0.0"), true); assert.equal(satisfiesRange("1.0.0", ">=0.1.0 <1.0.0"), false); assert.equal(satisfiesRange("0.2.0", ">=0.1.0 <1.0.0"), true); await migrate(); if (fs.existsSync(PLUGINS_DIR)) { fs.rmSync(PLUGINS_DIR, { recursive: true, force: true }); } fs.mkdirSync(PLUGINS_DIR, { recursive: true }); const core = resolveScriptRef("fetch-http.js"); assert.equal(core.kind, "core"); assert.ok(core.filePath && fs.existsSync(core.filePath)); assert.ok(resolveScriptRef("nope.js").error?.includes("not found")); const example = await installExamplePlugin("get-current-time", { overwrite: true, }); assert.equal(example.id, "get-current-time"); assert.equal(example.scriptRef, "plugin/get-current-time"); clearScriptCache(); const pluginResolved = resolveScriptRef("plugin/get-current-time"); assert.equal(pluginResolved.kind, "plugin"); assert.ok(pluginResolved.filePath); const result = await runScript( "plugin/get-current-time", { data: null, context: {}, config: null }, { log: silent, workflowName: "smoke", owner: "default" }, ); assert.ok(result?.output?.datetime); const forked = forkCoreScript("jsonata.js", "jsonata-smoke-fork"); assert.equal(forked.scriptRef, "plugin/jsonata-smoke-fork"); clearScriptCache(); assert.equal(resolveScriptRef("plugin/jsonata-smoke-fork").kind, "plugin"); const blank = createBlankPlugin( "blank-smoke", `export default async function main(ctx) { return { output: { ok: true }, context: ctx.context ?? {} }; }`, ); assert.equal(blank.scriptRef, "plugin/blank-smoke"); clearScriptCache(); const blankRun = await runScript( "plugin/blank-smoke", { data: 1, context: {}, config: null }, { log: silent, workflowName: "smoke", owner: "default" }, ); assert.equal(blankRun.output.ok, true); const duplicated = duplicatePlugin("blank-smoke", "blank-smoke-copy"); assert.equal(duplicated.scriptRef, "plugin/blank-smoke-copy"); clearScriptCache(); assert.equal(resolveScriptRef("plugin/blank-smoke-copy").kind, "plugin"); const dupRun = await runScript( "plugin/blank-smoke-copy", { data: 1, context: {}, config: null }, { log: silent, workflowName: "smoke", owner: "default" }, ); assert.equal(dupRun.output.ok, true); let hitDupSelf = false; try { duplicatePlugin("blank-smoke", "blank-smoke"); } catch (err) { hitDupSelf = true; assert.match(String(err.message), /itself/); } assert.equal(hitDupSelf, true); let hit = false; try { forkCoreScript("ntfy.js", "ntfy"); } catch (err) { hit = true; assert.match(String(err.message), /collides/); } assert.equal(hit, true); const meta = inspectScriptSource( "fetch-http.js", fs.readFileSync(core.filePath, "utf8"), ); assert.ok(meta); const extra = createBlankPlugin( "extra-require-smoke", `const extra = require("smoke-extra"); async function main() { return { output: { n: extra.n } }; } main.meta = { description: "smoke extra require", config: {}, input: {}, output: { n: { type: "number" } }, example: { data: {}, config: {} }, }; export default main; `, ); assert.equal(extra.scriptRef, "plugin/extra-require-smoke"); const extraPkg = path.join( pluginDir("extra-require-smoke"), "node_modules", "smoke-extra", ); fs.mkdirSync(extraPkg, { recursive: true }); fs.writeFileSync( path.join(extraPkg, "package.json"), `${JSON.stringify({ name: "smoke-extra", main: "index.js" })}\n`, ); fs.writeFileSync(path.join(extraPkg, "index.js"), "module.exports = { n: 9 };\n"); clearScriptCache(); const extraSource = fs.readFileSync( path.join(pluginDir("extra-require-smoke"), "script.js"), "utf8", ); const extraInspect = inspectScriptSource( "plugin/extra-require-smoke", extraSource, ); assert.equal(extraInspect.metaError, null, extraInspect.metaError); assert.equal(extraInspect.meta?.description, "smoke extra require"); const extraRun = await runScript( "plugin/extra-require-smoke", { data: null, context: {}, config: null }, { log: silent, workflowName: "smoke", owner: "default" }, ); assert.equal(extraRun.output.n, 9); assert.ok(listInstalledPlugins().some((p) => p.id === "get-current-time")); uninstallPlugin("jsonata-smoke-fork"); uninstallPlugin("blank-smoke"); uninstallPlugin("blank-smoke-copy"); uninstallPlugin("extra-require-smoke"); uninstallPlugin("get-current-time"); console.log("plugins-smoke: ok"); await db.destroy(); } main().catch(async (err) => { console.error(err); try { await db.destroy(); } catch { // ignore } process.exit(1); });