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();
6bc89d73dThis file is pinned to the selected release. Download the original to open it in your design tool.