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
JavaScript
;
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