Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/commands/upgradeRxivMaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import {
} from '../utils/installDetector';
import { getRxivMakerVersion, forceCheckForUpdates } from '../utils/versionChecker';

// Command name constants to avoid typos
const COMMANDS = {
INSTALL: 'rxiv-maker.installRxivMaker',
UPGRADE: 'rxiv-maker.upgrade'
} as const;

/**
* Upgrade rxiv-maker to the latest version.
*
Expand All @@ -30,8 +36,13 @@ export async function upgradeRxivMakerCommand(context: vscode.ExtensionContext):
);

if (install === 'Install') {
// Trigger the install command
vscode.commands.executeCommand('rxiv-maker.install');
// Verify command exists before executing
const commands = await vscode.commands.getCommands();
if (commands.includes(COMMANDS.INSTALL)) {
vscode.commands.executeCommand(COMMANDS.INSTALL);
} else {
vscode.window.showErrorMessage('Install command not available. Please reinstall the extension.');
}
}
return;
}
Expand Down
27 changes: 22 additions & 5 deletions src/utils/installDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@

import { exec } from 'child_process';
import { promisify } from 'util';
import * as vscode from 'vscode';

const execAsync = promisify(exec);

// Output channel for logging
let outputChannel: vscode.OutputChannel | null = null;

function getOutputChannel(): vscode.OutputChannel {
if (!outputChannel) {
outputChannel = vscode.window.createOutputChannel('Rxiv-Maker');
}
return outputChannel;
}

export type InstallMethod = 'homebrew' | 'pipx' | 'uv' | 'pip-user' | 'pip' | 'dev' | 'unknown';

/**
Expand All @@ -20,7 +31,10 @@ export type InstallMethod = 'homebrew' | 'pipx' | 'uv' | 'pip-user' | 'pip' | 'd
export async function detectInstallMethod(): Promise<InstallMethod> {
try {
// Get the path to rxiv executable
const { stdout: whichOutput } = await execAsync('which rxiv 2>/dev/null || where rxiv 2>nul');
// Use platform-specific command to avoid shell compatibility issues
const isWindows = process.platform === 'win32';
const cmd = isWindows ? 'where rxiv 2>nul' : 'which rxiv 2>/dev/null';
const { stdout: whichOutput } = await execAsync(cmd);
const executablePath = whichOutput.trim();

if (!executablePath) {
Expand All @@ -37,8 +51,8 @@ export async function detectInstallMethod(): Promise<InstallMethod> {

for (const prefix of homebrewPrefixes) {
if (executablePath.startsWith(prefix)) {
// Additional verification: check if Cellar path exists
if (executablePath.includes('/Cellar/') || executablePath.includes('/opt/')) {
// Additional verification: check if Cellar or opt/homebrew path exists
if (executablePath.includes('/Cellar/') || executablePath.includes('/opt/homebrew/')) {
return 'homebrew';
}
}
Expand Down Expand Up @@ -80,7 +94,8 @@ export async function detectInstallMethod(): Promise<InstallMethod> {

return 'unknown';
} catch (error) {
console.error('Error detecting install method:', error);
const output = getOutputChannel();
output.appendLine(`Error detecting install method: ${error}`);
return 'unknown';
}
}
Expand Down Expand Up @@ -130,7 +145,9 @@ export function getFriendlyInstallName(installMethod: InstallMethod): string {
*/
export async function isRxivMakerInstalled(): Promise<boolean> {
try {
const { stdout } = await execAsync('rxiv --version 2>/dev/null || rxiv.exe --version 2>nul');
const isWindows = process.platform === 'win32';
const cmd = isWindows ? 'rxiv.exe --version 2>nul' : 'rxiv --version 2>/dev/null';
const { stdout } = await execAsync(cmd);
return stdout.trim().length > 0;
} catch (error) {
return false;
Expand Down
36 changes: 29 additions & 7 deletions src/utils/versionChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ import { detectInstallMethod, type InstallMethod } from './installDetector';

const execAsync = promisify(exec);

// Output channel for logging
let outputChannel: vscode.OutputChannel | null = null;

function getOutputChannel(): vscode.OutputChannel {
if (!outputChannel) {
outputChannel = vscode.window.createOutputChannel('Rxiv-Maker');
}
return outputChannel;
}

interface VersionInfo {
current: string | null;
latest: string | null;
Expand All @@ -32,9 +42,12 @@ interface CacheData {
*/
export async function getRxivMakerVersion(): Promise<string | null> {
try {
const { stdout } = await execAsync('rxiv --version 2>/dev/null || rxiv.exe --version 2>nul');
// Parse output like "rxiv-maker version 1.8.8"
const match = stdout.trim().match(/(\d+\.\d+\.\d+)/);
const isWindows = process.platform === 'win32';
const cmd = isWindows ? 'rxiv.exe --version 2>nul' : 'rxiv --version 2>/dev/null';
const { stdout } = await execAsync(cmd);
// Parse output like "rxiv-maker version 1.8.8" or "rxiv-maker version 1.8.8-beta1"
// Support semantic versioning with optional pre-release identifiers
const match = stdout.trim().match(/(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)/);
return match ? match[1] : null;
} catch (error) {
return null;
Expand Down Expand Up @@ -81,8 +94,9 @@ export async function checkHomebrewOutdated(): Promise<{ current: string; latest
export async function fetchLatestVersion(): Promise<string | null> {
return new Promise((resolve) => {
const url = 'https://pypi.org/pypi/rxiv-maker/json';
const output = getOutputChannel();

https.get(url, (res) => {
const req = https.get(url, { timeout: 10000 }, (res) => {
let data = '';

res.on('data', (chunk) => {
Expand All @@ -94,12 +108,20 @@ export async function fetchLatestVersion(): Promise<string | null> {
const json = JSON.parse(data);
resolve(json.info.version);
} catch (error) {
console.error('Error parsing PyPI response:', error);
output.appendLine(`Error parsing PyPI response: ${error}`);
resolve(null);
}
});
}).on('error', (error) => {
console.error('Error fetching latest version:', error);
});

req.on('timeout', () => {
output.appendLine('PyPI request timed out after 10 seconds');
req.destroy();
resolve(null);
});

req.on('error', (error) => {
output.appendLine(`Error fetching latest version from PyPI: ${error}`);
resolve(null);
});
});
Expand Down
Loading