feat(refactor): move plugins to typescript (#557)

* feat(refactor): move plugins to typescript

* chore: add timeout to plugin build function
This commit is contained in:
Simos Mikelatos
2026-03-18 14:44:07 +01:00
committed by GitHub
parent 88c60b70b0
commit 612390db53
2 changed files with 54 additions and 5 deletions

View File

@@ -93,6 +93,55 @@ export function validateManifest(manifest) {
return { valid: true };
}
const BUILD_TIMEOUT_MS = 60_000;
/** Run `npm run build` if the plugin's package.json declares a build script. */
function runBuildIfNeeded(dir, packageJsonPath, onSuccess, onError) {
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
if (!pkg.scripts?.build) {
return onSuccess();
}
} catch {
return onSuccess(); // Unreadable package.json — skip build
}
const buildProcess = spawn('npm', ['run', 'build'], {
cwd: dir,
stdio: ['ignore', 'pipe', 'pipe'],
});
let stderr = '';
let settled = false;
const timer = setTimeout(() => {
if (settled) return;
settled = true;
buildProcess.removeAllListeners();
buildProcess.kill();
onError(new Error('npm run build timed out'));
}, BUILD_TIMEOUT_MS);
buildProcess.stderr.on('data', (data) => { stderr += data.toString(); });
buildProcess.on('close', (code) => {
if (settled) return;
settled = true;
clearTimeout(timer);
if (code !== 0) {
return onError(new Error(`npm run build failed (exit code ${code}): ${stderr.trim()}`));
}
onSuccess();
});
buildProcess.on('error', (err) => {
if (settled) return;
settled = true;
clearTimeout(timer);
onError(new Error(`Failed to spawn build: ${err.message}`));
});
}
export function scanPlugins() {
const pluginsDir = getPluginsDir();
const config = getPluginsConfig();
@@ -289,7 +338,7 @@ export function installPluginFromGit(url) {
// --ignore-scripts prevents postinstall hooks from executing arbitrary code.
const packageJsonPath = path.join(tempDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const npmProcess = spawn('npm', ['install', '--production', '--ignore-scripts'], {
const npmProcess = spawn('npm', ['install', '--ignore-scripts'], {
cwd: tempDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
@@ -299,7 +348,7 @@ export function installPluginFromGit(url) {
cleanupTemp();
return reject(new Error(`npm install for ${repoName} failed (exit code ${npmCode})`));
}
finalize(manifest);
runBuildIfNeeded(tempDir, packageJsonPath, () => finalize(manifest), (err) => { cleanupTemp(); reject(err); });
});
npmProcess.on('error', (err) => {
@@ -356,7 +405,7 @@ export function updatePluginFromGit(name) {
// Re-run npm install if package.json exists
const packageJsonPath = path.join(pluginDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const npmProcess = spawn('npm', ['install', '--production', '--ignore-scripts'], {
const npmProcess = spawn('npm', ['install', '--ignore-scripts'], {
cwd: pluginDir,
stdio: ['ignore', 'pipe', 'pipe'],
});
@@ -364,7 +413,7 @@ export function updatePluginFromGit(name) {
if (npmCode !== 0) {
return reject(new Error(`npm install for ${name} failed (exit code ${npmCode})`));
}
resolve(manifest);
runBuildIfNeeded(pluginDir, packageJsonPath, () => resolve(manifest), (err) => reject(err));
});
npmProcess.on('error', (err) => reject(err));
} else {