fix(plugins): enhance plugin installation and script handling
- Updated the `pnpm install` command in `plugin-install.js` to include the `--ignore-workspace` flag, ensuring proper installation of plugins within the app tree. - Improved the `instantiateScriptSource` function in `script-sandbox.js` to resolve `pluginDir` more effectively, allowing for better package management. - Added a new smoke test in `plugins-smoke.js` to validate the ability to require additional packages from plugin directories, enhancing testing coverage for plugin functionality. - Updated documentation in `AGENTS.md` to reflect changes in the installation command.
This commit is contained in:
@@ -78,7 +78,7 @@ Copy the layout from `plugins/joplin-api`, `plugins/send-sms`, or `examples/plug
|
|||||||
Add `dependencies` only if the script `require()`s extra npm packages. Then:
|
Add `dependencies` only if the script `require()`s extra npm packages. Then:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install --dir plugins/<id> --ignore-scripts --prefer-offline
|
pnpm install --dir plugins/<id> --ignore-scripts --prefer-offline --ignore-workspace
|
||||||
```
|
```
|
||||||
|
|
||||||
`plugins/*/node_modules/` is gitignored. Host-allowlisted modules (`axios`, `jsonata`, …) come from the server; extra deps resolve from the plugin directory.
|
`plugins/*/node_modules/` is gitignored. Host-allowlisted modules (`axios`, `jsonata`, …) come from the server; extra deps resolve from the plugin directory.
|
||||||
|
|||||||
@@ -51,9 +51,18 @@ export async function pnpmInstallPlugin(dir) {
|
|||||||
}
|
}
|
||||||
if (!hasDeps) return { skipped: true };
|
if (!hasDeps) return { skipped: true };
|
||||||
|
|
||||||
|
// --ignore-workspace: plugins live under the app tree but are not workspace
|
||||||
|
// packages; without this, pnpm install --dir can no-op against the root monorepo.
|
||||||
await execFileAsync(
|
await execFileAsync(
|
||||||
"pnpm",
|
"pnpm",
|
||||||
["install", "--dir", dir, "--ignore-scripts", "--prefer-offline"],
|
[
|
||||||
|
"install",
|
||||||
|
"--dir",
|
||||||
|
dir,
|
||||||
|
"--ignore-scripts",
|
||||||
|
"--prefer-offline",
|
||||||
|
"--ignore-workspace",
|
||||||
|
],
|
||||||
{
|
{
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
env: { ...process.env, CI: "1" },
|
env: { ...process.env, CI: "1" },
|
||||||
|
|||||||
@@ -557,16 +557,23 @@ function instantiateCompiled(
|
|||||||
* $workflows?: { trigger: (name: string, data?: unknown) => Promise<unknown> },
|
* $workflows?: { trigger: (name: string, data?: unknown) => Promise<unknown> },
|
||||||
* pluginDir?: string | null,
|
* pluginDir?: string | null,
|
||||||
* }} [opts]
|
* }} [opts]
|
||||||
|
*
|
||||||
|
* When `pluginDir` is omitted, it is resolved from `script` so inspect/dry-run
|
||||||
|
* of `plugin/<id>` can `require()` extra packages from that plugin's node_modules.
|
||||||
*/
|
*/
|
||||||
export function instantiateScriptSource(script, source, opts = {}) {
|
export function instantiateScriptSource(script, source, opts = {}) {
|
||||||
const compiled = compileScriptSource(source, script);
|
const compiled = compileScriptSource(source, script);
|
||||||
|
const pluginDir =
|
||||||
|
"pluginDir" in opts
|
||||||
|
? opts.pluginDir ?? null
|
||||||
|
: (resolveScriptRef(script).pluginDir ?? null);
|
||||||
const fn = instantiateCompiled(compiled, {
|
const fn = instantiateCompiled(compiled, {
|
||||||
log: opts.log ?? inspectLog,
|
log: opts.log ?? inspectLog,
|
||||||
script,
|
script,
|
||||||
workflowName: opts.workflowName ?? "inspect",
|
workflowName: opts.workflowName ?? "inspect",
|
||||||
owner: opts.owner ?? DEFAULT_OWNER,
|
owner: opts.owner ?? DEFAULT_OWNER,
|
||||||
$workflows: opts.$workflows,
|
$workflows: opts.$workflows,
|
||||||
pluginDir: opts.pluginDir ?? null,
|
pluginDir,
|
||||||
});
|
});
|
||||||
return { fn, ...extractScriptMeta(fn) };
|
return { fn, ...extractScriptMeta(fn) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "node:path";
|
||||||
import { migrate, db } from "../db.js";
|
import { migrate, db } from "../db.js";
|
||||||
import { getAppVersion, satisfiesRange } from "../app-version.js";
|
import { getAppVersion, satisfiesRange } from "../app-version.js";
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
uninstallPlugin,
|
uninstallPlugin,
|
||||||
listInstalledPlugins,
|
listInstalledPlugins,
|
||||||
createBlankPlugin,
|
createBlankPlugin,
|
||||||
|
pluginDir,
|
||||||
} from "../plugin-store.js";
|
} from "../plugin-store.js";
|
||||||
import { installExamplePlugin } from "../plugin-install.js";
|
import { installExamplePlugin } from "../plugin-install.js";
|
||||||
import {
|
import {
|
||||||
@@ -119,11 +121,58 @@ async function main() {
|
|||||||
);
|
);
|
||||||
assert.ok(meta);
|
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"));
|
assert.ok(listInstalledPlugins().some((p) => p.id === "get-current-time"));
|
||||||
|
|
||||||
uninstallPlugin("jsonata-smoke-fork");
|
uninstallPlugin("jsonata-smoke-fork");
|
||||||
uninstallPlugin("blank-smoke");
|
uninstallPlugin("blank-smoke");
|
||||||
uninstallPlugin("blank-smoke-copy");
|
uninstallPlugin("blank-smoke-copy");
|
||||||
|
uninstallPlugin("extra-require-smoke");
|
||||||
uninstallPlugin("get-current-time");
|
uninstallPlugin("get-current-time");
|
||||||
|
|
||||||
console.log("plugins-smoke: ok");
|
console.log("plugins-smoke: ok");
|
||||||
|
|||||||
Reference in New Issue
Block a user