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
This commit is contained in:
158
src/commands/update.js
Normal file
158
src/commands/update.js
Normal file
@@ -0,0 +1,158 @@
|
||||
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}`));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user