99 lines
3.8 KiB
JavaScript
99 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// Dev-only: link the in-repo @paperclipai/plugin-sdk into plugin packages that
|
|
// are excluded from the pnpm workspace (see pnpm-workspace.yaml). Workspace
|
|
// members get their SDK link from pnpm automatically; excluded packages do not.
|
|
//
|
|
// Invoked from the root postinstall. Intentionally NOT referenced from any
|
|
// plugin's own package.json so the published tarballs cannot carry a lifecycle
|
|
// script that escapes their package directory at install time.
|
|
|
|
import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from "node:fs";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = resolve(__dirname, "..");
|
|
const sdkDir = join(repoRoot, "packages", "plugins", "sdk");
|
|
|
|
// Plugin packages excluded from the workspace that still need @paperclipai/plugin-sdk
|
|
// linked in for local dev. Keep in sync with pnpm-workspace.yaml exclusions.
|
|
function excludedPluginDirs() {
|
|
return [
|
|
...readPluginsUnder(join(repoRoot, "packages", "plugins", "sandbox-providers")),
|
|
join(repoRoot, "packages", "plugins", "examples", "plugin-orchestration-smoke-example"),
|
|
];
|
|
}
|
|
|
|
export function linkExcludedPlugins() {
|
|
let linked = 0;
|
|
let skipped = 0;
|
|
for (const packageDir of excludedPluginDirs()) {
|
|
if (!existsSync(join(packageDir, "package.json"))) continue;
|
|
if (linkSdkInto(packageDir)) {
|
|
linked += 1;
|
|
} else {
|
|
skipped += 1;
|
|
}
|
|
}
|
|
return { linked, skipped };
|
|
}
|
|
|
|
// Run as a CLI (root postinstall) when invoked directly, but stay importable so
|
|
// repo-internal scripts (e.g. the standalone package builder) can relink a
|
|
// single package after a fresh install.
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
const { linked, skipped } = linkExcludedPlugins();
|
|
console.log(` ✓ Linked @paperclipai/plugin-sdk into ${linked} excluded plugin(s) (skipped ${skipped})`);
|
|
}
|
|
|
|
// Recursively collect package directories (those containing a package.json)
|
|
// under parentDir. The matching pnpm-workspace.yaml exclusion uses a recursive
|
|
// glob (e.g. "!packages/plugins/sandbox-providers/**"), so a provider nested
|
|
// deeper than one level must still be discovered here.
|
|
export function readPluginsUnder(parentDir) {
|
|
if (!existsSync(parentDir)) return [];
|
|
const found = [];
|
|
for (const entry of readdirSync(parentDir, { withFileTypes: true })) {
|
|
// Skip symlinked directories so discovery can't be steered outside the
|
|
// intended plugin subtree, and skip node_modules.
|
|
if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name === "node_modules") continue;
|
|
const childDir = join(parentDir, entry.name);
|
|
if (existsSync(join(childDir, "package.json"))) {
|
|
found.push(childDir);
|
|
} else {
|
|
found.push(...readPluginsUnder(childDir));
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
export function linkSdkInto(packageDir) {
|
|
const scopeDir = join(packageDir, "node_modules", "@paperclipai");
|
|
const linkTarget = join(scopeDir, "plugin-sdk");
|
|
const relativeSdkDir = relative(scopeDir, sdkDir);
|
|
|
|
mkdirSync(scopeDir, { recursive: true });
|
|
|
|
try {
|
|
const stat = lstatSync(linkTarget);
|
|
if (stat.isSymbolicLink()) {
|
|
if (readlinkSync(linkTarget) === relativeSdkDir) {
|
|
// Already linked to the in-repo SDK; nothing to do.
|
|
return false;
|
|
}
|
|
rmSync(linkTarget, { force: true });
|
|
} else {
|
|
// A real install has already populated @paperclipai/plugin-sdk (e.g. the
|
|
// plugin host did `npm install` of the published tarball). Leave it.
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
// A missing target is expected (nothing linked yet); surface anything else.
|
|
if (error?.code !== "ENOENT") throw error;
|
|
}
|
|
|
|
symlinkSync(relativeSdkDir, linkTarget, "dir");
|
|
return true;
|
|
}
|