Adding functionality to check for Roo-Cline updates

This commit is contained in:
ColemanRoo 2024-11-26 11:17:35 -06:00
parent 86ce5236bc
commit 648f05c1d0
5 changed files with 297 additions and 4 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "roo-cline",
"version": "2.0.3",
"version": "2.1.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roo-cline",
"version": "2.0.3",
"version": "2.1.2",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",

View file

@ -2,9 +2,9 @@
"name": "roo-cline",
"displayName": "Roo Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "2.1.1",
"version": "2.1.2",
"files": [
"bin/roo-cline-2.1.1.vsix",
"bin/roo-cline-2.1.2.vsix",
"assets/icons/icon_Roo.png"
],
"icon": "assets/icons/icon_Roo.png",

View file

@ -6,6 +6,7 @@ import { ClineProvider } from "./core/webview/ClineProvider"
import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
import { scheduleUpdateChecks, checkForUpdates } from "./utils/version-check"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@ -26,6 +27,11 @@ export function activate(context: vscode.ExtensionContext) {
outputChannel.appendLine("Cline extension activated")
// Schedule periodic update checks
scheduleUpdateChecks(context).catch(error => {
outputChannel.appendLine(`Failed to initialize update checks: ${error}`)
})
const sidebarProvider = new ClineProvider(context, outputChannel)
context.subscriptions.push(
@ -37,6 +43,26 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cline.plusButtonClicked", async () => {
outputChannel.appendLine("Plus button Clicked")
// Check for updates before starting new task
const updateCheck = await checkForUpdates(context);
if (updateCheck?.updateAvailable) {
const choice = await vscode.window.showInformationMessage(
`A new version of Roo Cline (${updateCheck.latest}) is available. It's recommended to update before starting a new task.`,
'Update Now',
'Continue Anyway',
'Cancel'
);
if (choice === 'Cancel') {
return;
} else if (choice === 'Update Now') {
// User chose to update, don't start new task
return;
}
// If 'Continue Anyway', proceed with new task
}
await sidebarProvider.clearTask()
await sidebarProvider.postStateToWebview()
await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })

View file

@ -0,0 +1,151 @@
import * as vscode from 'vscode';
import { exec } from 'child_process';
import { checkForUpdates, VersionInfo } from '../version-check';
import * as fs from 'fs/promises';
// Mock dependencies
jest.mock('child_process');
jest.mock('fs/promises');
jest.mock('vscode', () => ({
window: {
showInformationMessage: jest.fn(),
showErrorMessage: jest.fn(),
withProgress: jest.fn(async (options, task) => task()),
},
commands: {
executeCommand: jest.fn(),
},
extensions: {
getExtension: jest.fn(),
},
}));
describe('version-check', () => {
let mockContext: vscode.ExtensionContext;
beforeEach(() => {
jest.clearAllMocks();
mockContext = {} as vscode.ExtensionContext;
// Mock extension version
(vscode.extensions.getExtension as jest.Mock).mockReturnValue({
packageJSON: { version: '1.0.0' }
});
});
describe('checkForUpdates', () => {
it('should detect when update is available', async () => {
// Mock CodeArtifact login script exists
(fs.access as jest.Mock).mockResolvedValue(undefined);
// Mock successful command executions
(exec as unknown as jest.Mock).mockImplementation((cmd, callback) => {
if (callback) {
if (cmd.includes('codeartifact-login.sh')) {
callback(null, { stdout: 'Login successful' }, '');
} else if (cmd.includes('npm view')) {
callback(null, { stdout: '1.1.0\n' }, '');
}
}
return {
stdout: cmd.includes('npm view') ? '1.1.0\n' : 'Login successful',
stderr: '',
};
});
const result = await checkForUpdates(mockContext);
expect(result).toEqual({
current: '1.0.0',
latest: '1.1.0',
updateAvailable: true
});
});
it('should handle when no update is available', async () => {
// Mock CodeArtifact login script exists
(fs.access as jest.Mock).mockResolvedValue(undefined);
// Mock successful command executions with same version
(exec as unknown as jest.Mock).mockImplementation((cmd, callback) => {
if (callback) {
if (cmd.includes('codeartifact-login.sh')) {
callback(null, { stdout: 'Login successful' }, '');
} else if (cmd.includes('npm view')) {
callback(null, { stdout: '1.0.0\n' }, '');
}
}
return {
stdout: cmd.includes('npm view') ? '1.0.0\n' : 'Login successful',
stderr: '',
};
});
const result = await checkForUpdates(mockContext);
expect(result).toEqual({
current: '1.0.0',
latest: '1.0.0',
updateAvailable: false
});
});
it('should fallback to direct auth token when login script missing', async () => {
// Mock CodeArtifact login script does not exist
(fs.access as jest.Mock).mockRejectedValue(new Error('ENOENT'));
// Mock successful command executions
(exec as unknown as jest.Mock).mockImplementation((cmd, callback) => {
if (callback) {
if (cmd.includes('get-authorization-token')) {
callback(null, { stdout: 'token123' }, '');
} else if (cmd.includes('npm view')) {
callback(null, { stdout: '1.1.0\n' }, '');
}
}
return {
stdout: cmd.includes('npm view') ? '1.1.0\n' : 'token123',
stderr: '',
};
});
const result = await checkForUpdates(mockContext);
expect(result).toEqual({
current: '1.0.0',
latest: '1.1.0',
updateAvailable: true
});
// Verify it tried to get auth token directly
expect(exec).toHaveBeenCalledWith(
expect.stringContaining('aws codeartifact get-authorization-token'),
expect.any(Function)
);
});
it('should handle errors and return null', async () => {
// Mock CodeArtifact login script exists but fails
(fs.access as jest.Mock).mockResolvedValue(undefined);
(exec as unknown as jest.Mock).mockImplementation((cmd, callback) => {
if (callback) {
callback(new Error('Command failed'), '', 'Error executing command');
}
throw new Error('Command failed');
});
const result = await checkForUpdates(mockContext);
expect(result).toBeNull();
});
it('should handle missing extension version', async () => {
// Mock extension not found
(vscode.extensions.getExtension as jest.Mock).mockReturnValue(undefined);
const result = await checkForUpdates(mockContext);
expect(result).toBeNull();
});
});
});

116
src/utils/version-check.ts Normal file
View file

@ -0,0 +1,116 @@
import * as vscode from 'vscode';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as path from 'path';
import * as fs from 'fs/promises';
import * as os from 'os';
const execAsync = promisify(exec);
export interface VersionInfo {
current: string;
latest: string;
updateAvailable: boolean;
}
export async function checkForUpdates(context: vscode.ExtensionContext): Promise<VersionInfo | null> {
try {
// Get current version from package.json
const currentVersion = vscode.extensions.getExtension('roo-vet.roo-cline')?.packageJSON.version;
// Run CodeArtifact login to ensure we have valid credentials
const loginScript = path.join(__dirname, '..', '..', 'scripts', 'codeartifact-login.sh');
if (await fs.access(loginScript).then(() => true).catch(() => false)) {
await execAsync(`bash "${loginScript}"`);
} else {
// If login script doesn't exist, try to get auth token directly
await execAsync('aws codeartifact get-authorization-token --domain roo --query authorizationToken --output text');
}
// Get latest version from CodeArtifact
const { stdout } = await execAsync('npm view roo-cline version');
const latestVersion = stdout.trim();
return {
current: currentVersion,
latest: latestVersion,
updateAvailable: currentVersion !== latestVersion
};
} catch (error) {
console.error('Failed to check for updates:', error);
return null;
}
}
export async function scheduleUpdateChecks(context: vscode.ExtensionContext) {
// Check for updates on startup
const initialCheck = await checkForUpdates(context);
if (initialCheck?.updateAvailable) {
showUpdateNotification(initialCheck.latest);
}
// Check for updates every 24 hours
setInterval(async () => {
const check = await checkForUpdates(context);
if (check?.updateAvailable) {
showUpdateNotification(check.latest);
}
}, 24 * 60 * 60 * 1000); // 24 hours in milliseconds
}
function showUpdateNotification(latestVersion: string) {
vscode.window.showInformationMessage(
`A new version of Roo Cline (${latestVersion}) is available!`,
'Update Now',
'Later'
).then(selection => {
if (selection === 'Update Now') {
installUpdate(latestVersion);
}
});
}
async function installUpdate(version: string) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Updating Roo Cline...",
cancellable: false
}, async (progress) => {
try {
// Create temp directory for installation
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'roo-cline-update-'));
progress.report({ message: 'Installing new version...' });
// Install roo-cline in temp directory
await execAsync('npm init -y', { cwd: tempDir });
await execAsync('npm install roo-cline', { cwd: tempDir });
// Get path to vsix file
const vsixPath = path.join(tempDir, 'node_modules', 'roo-cline', 'bin', `roo-cline-${version}.vsix`);
progress.report({ message: 'Installing extension...' });
// Install the extension
const editor = process.env.TERM_PROGRAM === 'vscode' ? 'code' : 'cursor';
await execAsync(`${editor} --install-extension "${vsixPath}"`);
// Clean up temp directory
await fs.rm(tempDir, { recursive: true, force: true });
// Show success message with reload button
const action = await vscode.window.showInformationMessage(
'Roo Cline has been updated successfully!',
'Reload Now'
);
if (action === 'Reload Now') {
// Reload the window to activate the new version
await vscode.commands.executeCommand('workbench.action.reloadWindow');
}
} catch (error) {
vscode.window.showErrorMessage('Failed to update Roo Cline. Please try again later.');
console.error('Update failed:', error);
}
});
}