UNPKG

vsix-extension-manager

Version:

VSIX Extension Manager: A comprehensive CLI tool to download, export, import, and manage VS Code/Cursor extensions as VSIX files

343 lines 14.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.updateInstalled = updateInstalled; exports.runUpdateInstalledUI = runUpdateInstalledUI; const p = __importStar(require("@clack/prompts")); const progress_1 = require("../core/ui/progress"); const helpers_1 = require("../core/helpers"); const update_1 = require("../features/update"); const install_1 = require("../features/install"); const export_1 = require("../features/export"); async function updateInstalled(options) { console.clear(); p.intro("⬆️ Update Installed Extensions"); try { const quiet = Boolean(options.quiet); const json = Boolean(options.json); const service = (0, update_1.getUpdateInstalledService)(); const editorService = (0, install_1.getEditorService)(); const spinner = p.spinner(); // Show spinner during editor detection (can take up to 15 seconds for Cursor) if (!quiet && !json) { spinner.start("Detecting installed editors..."); } // Editor selection - always prompt when multiple are available const availableEditors = await editorService.getAvailableEditors(); if (!quiet && !json) { spinner.stop("Editor detection complete"); } let chosenEditor = options.editor || "auto"; if (chosenEditor === "auto") { if (availableEditors.length === 0) { throw new Error("No editors found. Please install VS Code or Cursor."); } if (availableEditors.length === 1) { const detected = availableEditors[0]; if (!quiet && !json) { p.log.info(`🔍 Auto-detected ${detected.displayName} at ${detected.binaryPath}`); } chosenEditor = detected.name; } else { // Multiple editors found - ALWAYS prompt the user to select if (quiet || json) { // In quiet/json mode with multiple editors, we cannot auto-select throw new Error(`Multiple editors found (${availableEditors.map((e) => e.displayName).join(", ")}). ` + `Please specify which editor to update using --editor vscode or --editor cursor`); } // Interactive mode - let user choose const result = await p.select({ message: "Multiple editors found. Select which editor to update:", options: availableEditors.map((editor) => ({ value: editor.name, label: `${editor.displayName} (${editor.binaryPath})`, })), }); if (p.isCancel(result)) { p.cancel("Operation cancelled."); process.exit(0); } chosenEditor = result; } } options.editor = chosenEditor; // Interactive mode: Ask for update mode (all vs selected) let selectedExtensions; if (!quiet && !json) { const updateMode = await p.select({ message: "Choose update mode:", options: [ { value: "all", label: "Update all extensions", hint: "Update all installed extensions to latest", }, { value: "selected", label: "Update selected extensions", hint: "Choose which extensions to update", }, ], }); if (p.isCancel(updateMode)) { p.cancel("Operation cancelled."); process.exit(0); } if (updateMode === "selected") { // Get installed extensions for selection const installed = await (0, export_1.getInstalledExtensions)(chosenEditor); if (installed.length === 0) { p.log.warn("⚠️ No extensions found to update"); return; } // Check which extensions have updates available spinner.start("Checking for available updates..."); const { resolveVersion } = await Promise.resolve().then(() => __importStar(require("../core/registry"))); const sourcePref = options.source || "auto"; const preRelease = Boolean(options.preRelease); const retry = Number(options.retry ?? 2); const retryDelay = Number(options.retryDelay ?? 1000); const extensionsWithUpdates = []; const extensionsUpToDate = []; let extensionsFailed = 0; for (const ext of installed) { try { // Use retry logic for version resolution const latest = await withRetry(async () => resolveVersion(ext.id, "latest", preRelease, sourcePref), retry, retryDelay); const hasUpdate = latest && isVersionNewer(latest, ext.version); if (hasUpdate) { extensionsWithUpdates.push({ value: ext.id, label: `${ext.displayName || ext.id} (v${ext.version} → v${latest} ⬆️)`, }); } else { extensionsUpToDate.push({ id: ext.id, name: ext.displayName || ext.id, version: ext.version, }); } } catch { // Count failed version checks extensionsFailed++; } } spinner.stop("Version check completed"); // Show summary of what was found if (extensionsWithUpdates.length === 0) { p.log.info("✅ All extensions are already up-to-date!"); if (extensionsFailed > 0) { p.log.warn(`⚠️ ${extensionsFailed} extension(s) had version check failures`); } return; } // Show summary before selection p.note(`Extensions with updates: ${extensionsWithUpdates.length}\nUp-to-date: ${extensionsUpToDate.length}${extensionsFailed > 0 ? `\nVersion check failed: ${extensionsFailed}` : ""}`, "Update Status"); const extensionChoices = await p.multiselect({ message: `Select extensions to update (${extensionsWithUpdates.length} available):`, options: extensionsWithUpdates, required: false, }); if (p.isCancel(extensionChoices)) { p.cancel("Operation cancelled."); process.exit(0); } selectedExtensions = extensionChoices; if (selectedExtensions.length === 0) { p.log.info("No extensions selected for update."); return; } } } let lastUpdateMap = {}; if (!quiet) spinner.start("Preparing update plan..."); const summary = await service.updateInstalled({ editor: chosenEditor, // Use the determined editor, not the raw option preRelease: options.preRelease, source: options.source || "auto", parallel: options.parallel, retry: options.retry, retryDelay: options.retryDelay, quiet, json, dryRun: options.dryRun, summary: options.summary, codeBin: options.codeBin, cursorBin: options.cursorBin, allowMismatchedBinary: options.allowMismatchedBinary, selectedExtensions, skipBackup: options.skipBackup, backupDir: options.backupDir, }, (message) => { if (!quiet) spinner.message(message); }, (id, progress) => { if (quiet) return; const now = Date.now(); const last = lastUpdateMap[id] || 0; if (!(0, helpers_1.shouldUpdateProgress)(last, now)) return; const bar = (0, progress_1.createProgressBar)(progress.percentage, 20); spinner.message(`${(0, helpers_1.truncateMiddle)(id, 42)} ${bar} ${(0, progress_1.formatBytes)(progress.downloaded)}/${(0, progress_1.formatBytes)(progress.total)}`); lastUpdateMap[id] = now; }); if (!quiet) spinner.stop("Update process finished"); const summaryLines = [ `Detected: ${summary.totalDetected}`, `Up-to-date: ${summary.upToDate}`, `To update: ${summary.toUpdate}`, `Updated: ${summary.updated}`, `Skipped: ${summary.skipped}`, `Failed: ${summary.failed}`, `Backups created: ${summary.backups.length}`, `Duration: ${Math.round(summary.elapsedMs / 1000)}s`, ].join("\n"); if (json) { console.log(JSON.stringify(summary, null, 2)); return; } p.note(summaryLines, "Update Summary"); const failed = summary.items.filter((i) => i.status === "failed"); if (failed.length > 0 && !quiet) { p.log.error("❌ Failed updates:"); failed.forEach((i) => p.log.error(` • ${i.id}: ${i.error || "Unknown error"}`)); } if (!quiet) { if (options.dryRun) { if (summary.toUpdate > 0) { p.outro(`🔍 Dry run complete! ${summary.toUpdate} extension(s) would be updated.`); } else { p.outro("✅ Dry run complete! All extensions are already up-to-date."); } } else if (summary.updated === 0 && summary.failed === 0 && summary.toUpdate === 0) { p.outro("✅ All extensions are already up-to-date!"); } else if (summary.updated > 0) { p.outro(`✨ Update completed! ${summary.updated} extension(s) updated.`); } else if (summary.failed > 0) { p.outro("⚠️ Update completed with issues. Check failed updates above."); } else { p.outro("✅ Update check complete."); } } } catch (error) { p.log.error("❌ Error: " + (error instanceof Error ? error.message : String(error))); process.exit(1); } } /** * Retry helper function */ async function withRetry(fn, retry, delayMs) { let lastErr; for (let attempt = 0; attempt <= retry; attempt++) { try { return await fn(); } catch (e) { lastErr = e; if (attempt < retry) { await new Promise((r) => setTimeout(r, delayMs * Math.pow(2, attempt))); } } } throw lastErr instanceof Error ? lastErr : new Error(String(lastErr)); } /** * Compare versions semantically - returns true if newVersion > currentVersion */ function isVersionNewer(newVersion, currentVersion) { // If versions are identical, no update needed if (newVersion === currentVersion) { return false; } // Parse semantic versions const parseVersion = (v) => { const match = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); if (!match) return null; return { major: parseInt(match[1], 10), minor: parseInt(match[2], 10), patch: parseInt(match[3], 10), prerelease: match[4] || null, }; }; const newParsed = parseVersion(newVersion); const currentParsed = parseVersion(currentVersion); // If either version can't be parsed, fall back to string comparison if (!newParsed || !currentParsed) { return newVersion !== currentVersion; } // Compare major.minor.patch if (newParsed.major !== currentParsed.major) { return newParsed.major > currentParsed.major; } if (newParsed.minor !== currentParsed.minor) { return newParsed.minor > currentParsed.minor; } if (newParsed.patch !== currentParsed.patch) { return newParsed.patch > currentParsed.patch; } // Same major.minor.patch - check prerelease // Stable (no prerelease) > prerelease if (!newParsed.prerelease && currentParsed.prerelease) { return true; // new stable > current prerelease } if (newParsed.prerelease && !currentParsed.prerelease) { return false; // new prerelease < current stable } // Both prerelease or both stable - compare prerelease strings if (newParsed.prerelease && currentParsed.prerelease) { return newParsed.prerelease > currentParsed.prerelease; } // Same version return false; } async function runUpdateInstalledUI(options) { await updateInstalled(options); } //# sourceMappingURL=updateInstalled.js.map