Files
manyplug/src/commands/update.js
synt-xerror 649c7bb05a feat: add sync, update, and remove commands
- sync: scans plugins and updates registry.json
- update: checks and updates plugins to match registry
- remove: removes installed plugin with --yes and --remove-deps options
- registry.json stored at project root with plugin metadata and _sourcePath
2026-04-09 01:19:33 -03:00

159 lines
5.3 KiB
JavaScript

import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import { execSync } from 'child_process';
const PLUGINS_DIR = path.resolve('src/plugins');
const REGISTRY_PATH = path.resolve('registry.json');
/**
* Check and update plugins to match registry versions
* @param {string} pluginName - specific plugin to update, or undefined for all
* @param {object} options - command options
*/
export async function updateCommand(pluginName, options) {
// Check if registry exists
if (!await fs.pathExists(REGISTRY_PATH)) {
console.error(chalk.red('❌ registry.json not found. Run "manyplug sync" first.'));
process.exit(1);
}
const registry = await fs.readJson(REGISTRY_PATH);
if (pluginName) {
// Update specific plugin
await updatePlugin(pluginName, registry.plugins[pluginName]);
} else {
// Update all plugins
const plugins = Object.keys(registry.plugins);
if (plugins.length === 0) {
console.log(chalk.gray('No plugins in registry'));
return;
}
console.log(chalk.blue(`Checking ${plugins.length} plugin(s)...\n`));
let updated = 0;
let upToDate = 0;
let notInstalled = 0;
let errors = 0;
for (const name of plugins) {
const result = await updatePlugin(name, registry.plugins[name], { silent: true });
if (result === 'updated') updated++;
else if (result === 'upToDate') upToDate++;
else if (result === 'notInstalled') notInstalled++;
else errors++;
}
console.log(chalk.bold('\nUpdate Summary:'));
console.log(chalk.green(` Updated: ${updated}`));
console.log(chalk.gray(` Up to date: ${upToDate}`));
if (notInstalled > 0) console.log(chalk.yellow(` Not installed: ${notInstalled}`));
if (errors > 0) console.log(chalk.red(` Errors: ${errors}`));
}
}
async function updatePlugin(name, registryEntry, { silent = false } = {}) {
if (!registryEntry) {
if (!silent) console.error(chalk.red(`❌ "${name}" not found in registry`));
return 'error';
}
const pluginDir = path.join(PLUGINS_DIR, name);
const manifestPath = path.join(pluginDir, 'manyplug.json');
// Check if plugin is installed
if (!await fs.pathExists(manifestPath)) {
// Plugin exists in registry but not installed - try to install it
if (registryEntry._sourcePath) {
if (!silent) console.log(chalk.blue(`Installing "${name}" from registry...`));
await installFromSource(name, registryEntry);
return 'updated';
}
if (!silent) console.log(chalk.yellow(`⚠️ "${name}" not installed (run "manyplug install ${name}")`));
return 'notInstalled';
}
// Read installed manifest
const installed = await fs.readJson(manifestPath);
// Compare versions
if (installed.version === registryEntry.version) {
if (!silent) console.log(chalk.gray(` ${name}: ${installed.version} (up to date)`));
return 'upToDate';
}
// Version differs - update
if (!silent) console.log(chalk.blue(`Updating "${name}"...`));
console.log(chalk.gray(` ${installed.version}${registryEntry.version}`));
// If we have a source path in registry, re-copy from there
if (registryEntry._sourcePath && await fs.pathExists(registryEntry._sourcePath)) {
// Backup current
const backupDir = `${pluginDir}.backup-${Date.now()}`;
await fs.copy(pluginDir, backupDir);
try {
// Remove old version
await fs.remove(pluginDir);
// Copy new version
await fs.copy(registryEntry._sourcePath, pluginDir);
// Install dependencies
if (registryEntry.dependencies && Object.keys(registryEntry.dependencies).length > 0) {
await installNpmDeps(registryEntry.dependencies);
}
if (!silent) console.log(chalk.green(` ✅ Updated to ${registryEntry.version}`));
return 'updated';
} catch (err) {
// Restore backup on error
console.error(chalk.red(` ❌ Update failed: ${err.message}`));
console.log(chalk.yellow(' Restoring backup...'));
await fs.copy(backupDir, pluginDir);
await fs.remove(backupDir);
return 'error';
}
}
// No source path - just update the version in manifest (metadata update)
await fs.writeJson(manifestPath, { ...installed, version: registryEntry.version }, { spaces: 2 });
if (!silent) console.log(chalk.yellow(` ⚠️ Updated version only (no source available)`));
return 'updated';
}
async function installFromSource(name, registryEntry) {
const sourcePath = registryEntry._sourcePath;
const targetDir = path.join(PLUGINS_DIR, name);
if (!await fs.pathExists(sourcePath)) {
console.error(chalk.red(` ❌ Source not found: ${sourcePath}`));
return;
}
await fs.ensureDir(PLUGINS_DIR);
await fs.copy(sourcePath, targetDir);
if (registryEntry.dependencies && Object.keys(registryEntry.dependencies).length > 0) {
await installNpmDeps(registryEntry.dependencies);
}
console.log(chalk.green(` ✅ Installed "${name}" v${registryEntry.version}`));
}
async function installNpmDeps(dependencies) {
const deps = Object.entries(dependencies)
.map(([name, version]) => `${name}@${version === '*' ? 'latest' : version}`)
.join(' ');
if (!deps) return;
try {
execSync(`npm install ${deps}`, { cwd: process.cwd(), stdio: 'pipe' });
} catch (err) {
console.warn(chalk.yellow(` ⚠️ Failed to install dependencies: ${err.message}`));
}
}