Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
const fs = require("fs");
const path = require("path");
const { run, readJson, isStale } = require("./lib/shell-utils");
const REPO_ROOT = path.resolve(__dirname, "..");
const PLUGINS_ROOT = path.join(REPO_ROOT, "plugins");
const PLUGIN_SDK_DIR = path.join(REPO_ROOT, "packages", "plugin-sdk");
const PLUGIN_SDK_ENTRY = path.join(PLUGIN_SDK_DIR, "dist", "index.js");
const PLUGIN_SDK_PACKAGE = "@sienci/gsender-plugin-sdk";
// Inputs that invalidate the SDK's dist/ and a plugin's ui/ respectively.
// Missing paths are ignored, so listing every vite config variant is safe.
const PLUGIN_SDK_SOURCES = ["src", "package.json", "tsup.config.ts"].map(
(entry) => path.join(PLUGIN_SDK_DIR, entry),
);
const PLUGIN_SOURCES = [
"src",
"index.html",
"package.json",
"gsender-plugin.json",
"vite.config.js",
"vite.config.ts",
"vite.config.mjs",
];
const FORCE = process.env.GSENDER_FORCE_PLUGIN_BUILD === "1";
const STRICT = process.env.GSENDER_STRICT_DEV_PLUGINS === "1";
const detectPackageManager = (dir) =>
fs.existsSync(path.join(dir, "yarn.lock")) ? "yarn" : "npm";
const install = (dir) => {
if (detectPackageManager(dir) === "yarn") {
run(
"yarn",
["--cwd", path.relative(REPO_ROOT, dir), "install", "--non-interactive"],
REPO_ROOT,
);
} else {
run("npm", ["install"], dir);
}
};
const ensureDependenciesInstalled = (dir) => {
if (!fs.existsSync(path.join(dir, "package.json"))) {
return;
}
if (!FORCE && fs.existsSync(path.join(dir, "node_modules"))) {
return;
}
install(dir);
};
const ensurePluginSdkBuilt = () => {
const packageJsonPath = path.join(PLUGIN_SDK_DIR, "package.json");
if (!fs.existsSync(packageJsonPath)) {
return;
}
if (!FORCE && !isStale(PLUGIN_SDK_ENTRY, PLUGIN_SDK_SOURCES)) {
console.log(
"Plugin SDK is up to date, skipping (set GSENDER_FORCE_PLUGIN_BUILD=1 to force).",
);
return;
}
console.log("Building @sienci/gsender-plugin-sdk...");
ensureDependenciesInstalled(PLUGIN_SDK_DIR);
run(detectPackageManager(PLUGIN_SDK_DIR), ["run", "build"], PLUGIN_SDK_DIR);
};
// npm links the SDK's `file:` dependency, so those plugins always see the build
// we just made. yarn (and npm in some setups) copies it instead, which freezes
// whatever dist/ existed at install time — the copy then stays on a stale
// bundle and the plugin fails to build against SDK exports added since.
// Re-running the package manager doesn't help: the SDK version is unchanged, so
// yarn just serves the same copy back out of its cache. Refresh it in place.
const refreshCopiedPluginSdk = (pluginDir, name) => {
const installed = path.join(
pluginDir,
"node_modules",
...PLUGIN_SDK_PACKAGE.split("/"),
);
let stats;
try {
stats = fs.lstatSync(installed);
} catch {
return; // SDK isn't installed here
}
if (stats.isSymbolicLink()) {
return; // live link — already sees the current build
}
if (!isStale(path.join(installed, "dist", "index.js"), [PLUGIN_SDK_ENTRY])) {
return;
}
console.log(`${name}: refreshing copied plugin SDK.`);
fs.cpSync(path.join(PLUGIN_SDK_DIR, "dist"), path.join(installed, "dist"), {
recursive: true,
});
fs.copyFileSync(
path.join(PLUGIN_SDK_DIR, "package.json"),
path.join(installed, "package.json"),
);
};
const discoverPluginDirs = () => {
if (!fs.existsSync(PLUGINS_ROOT)) {
return [];
}
return fs
.readdirSync(PLUGINS_ROOT, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((name) =>
fs.existsSync(path.join(PLUGINS_ROOT, name, "gsender-plugin.json")),
);
};
const buildPlugin = (name) => {
const pluginDir = path.join(PLUGINS_ROOT, name);
const packageJsonPath = path.join(pluginDir, "package.json");
if (!fs.existsSync(packageJsonPath)) {
return;
}
const pkg = readJson(packageJsonPath);
if (!pkg.scripts || !pkg.scripts.build) {
return;
}
ensureDependenciesInstalled(pluginDir);
refreshCopiedPluginSdk(pluginDir, name);
const manifest = readJson(path.join(pluginDir, "gsender-plugin.json"));
const entry = manifest?.ui?.entry;
const entryPath = entry ? path.join(pluginDir, entry) : null;
// The SDK's own output is an input here: a plugin that inlines the SDK
// carries a copy of it, so a fresh SDK means a stale plugin bundle.
const sources = [
...PLUGIN_SOURCES.map((source) => path.join(pluginDir, source)),
PLUGIN_SDK_ENTRY,
];
if (!FORCE && entryPath && !isStale(entryPath, sources)) {
console.log(`${name}: up to date, skipping.`);
return;
}
console.log(`Building plugin: ${name}`);
run(detectPackageManager(pluginDir), ["run", "build"], pluginDir);
};
const main = () => {
ensurePluginSdkBuilt();
const names = discoverPluginDirs();
if (names.length === 0) {
console.log("No plugins found under plugins/. Skipping.");
return;
}
const failed = [];
names.forEach((name) => {
try {
buildPlugin(name);
} catch (err) {
console.error(`Failed to build plugin "${name}": ${err.message}`);
failed.push(name);
}
});
if (failed.length > 0) {
console.warn(
`${failed.length} plugin(s) failed to build: ${failed.join(", ")}`,
);
if (STRICT) {
process.exit(1);
}
}
};
main();
c8108e1c4This file is pinned to the selected release. Download the original to open it in your design tool.