diff --git a/src/constants.ts b/src/constants.ts index 7315398..41a04dd 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -89,3 +89,11 @@ export const Urls = { schemaBase: 'https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas', diagnosticsDoc: 'https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/technical-reference/toolkit-diagnostics', } as const; + +/** + * Extension-related constants. + */ +export const Extension = { + id: 'garrytrinder.dev-proxy-toolkit', + extensionsJsonPath: '.vscode/extensions.json', +} as const; diff --git a/src/extension.ts b/src/extension.ts index de6d4a5..e760d77 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { updateGlobalState } from './state'; import { VersionPreference } from './enums'; import { registerMcpServer } from './mcp'; import { registerTaskProvider } from './task-provider'; +import { promptForWorkspaceRecommendation } from './utils'; // Global variable to track the interval let statusBarInterval: NodeJS.Timeout | undefined; @@ -35,6 +36,9 @@ export const activate = async (context: vscode.ExtensionContext): Promise { + let tempWorkspaceFolder: vscode.WorkspaceFolder; + let tempDir: string; + + setup(async () => { + const context = await getExtensionContext(); + await context.globalState.update('devProxyInstall', testDevProxyInstall); + + // Create a temporary directory for test files + tempDir = path.join(process.cwd(), '.test-workspace-' + Date.now()); + try { + await vscode.workspace.fs.createDirectory(vscode.Uri.file(tempDir)); + } catch { + // Directory might already exist + } + + tempWorkspaceFolder = { + uri: vscode.Uri.file(tempDir), + name: 'test-workspace', + index: 0, + }; + }); + + teardown(async () => { + // Clean up test files + try { + await vscode.workspace.fs.delete(vscode.Uri.file(tempDir), { recursive: true }); + } catch { + // Ignore errors + } + }); + + test('hasDevProxyConfig should return false when no config files exist', async () => { + const result = await hasDevProxyConfig(); + // In the actual workspace, we don't expect config files unless they're in test/examples + // This is a best-effort test + assert.ok(result !== undefined); + }); + + test('isExtensionRecommended should return false when extensions.json does not exist', async () => { + // This test requires a workspace folder, but we can't easily mock it + // Just ensure the function runs without error + const result = await isExtensionRecommended(); + assert.ok(result === false || result === true); + }); + + test('addExtensionToRecommendations should create extensions.json if it does not exist', async () => { + // This test requires manipulating workspace folders, which is difficult in tests + // We'll just ensure the function is callable + const result = await addExtensionToRecommendations(); + assert.ok(result === false || result === true); + }); + + test('Extension constant should have correct ID', () => { + assert.strictEqual(Extension.id, 'garrytrinder.dev-proxy-toolkit'); + }); + + test('Extension constant should have correct extensions.json path', () => { + assert.strictEqual(Extension.extensionsJsonPath, '.vscode/extensions.json'); + }); +}); diff --git a/src/utils/index.ts b/src/utils/index.ts index 7826806..bc3bbbc 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -29,3 +29,11 @@ export { // Re-export from detect for convenience export { getDevProxyExe } from '../detect'; + +// Workspace recommendations utilities +export { + hasDevProxyConfig, + isExtensionRecommended, + addExtensionToRecommendations, + promptForWorkspaceRecommendation, +} from './workspace-recommendations'; diff --git a/src/utils/workspace-recommendations.ts b/src/utils/workspace-recommendations.ts new file mode 100644 index 0000000..6b992ca --- /dev/null +++ b/src/utils/workspace-recommendations.ts @@ -0,0 +1,143 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { Extension } from '../constants'; + +/** + * Utilities for managing workspace extension recommendations. + */ + +/** + * Check if workspace contains Dev Proxy config files. + */ +export async function hasDevProxyConfig(): Promise { + const files = await vscode.workspace.findFiles( + '{devproxyrc.json,devproxyrc.jsonc}', + '**/node_modules/**' + ); + return files.length > 0; +} + +/** + * Check if the Dev Proxy Toolkit extension is already in workspace recommendations. + */ +export async function isExtensionRecommended(): Promise { + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + return false; + } + + const workspaceFolder = vscode.workspace.workspaceFolders[0]; + const extensionsJsonPath = path.join(workspaceFolder.uri.fsPath, Extension.extensionsJsonPath); + + try { + const uri = vscode.Uri.file(extensionsJsonPath); + const document = await vscode.workspace.openTextDocument(uri); + const content = document.getText(); + const json = JSON.parse(content); + + if (json.recommendations && Array.isArray(json.recommendations)) { + return json.recommendations.includes(Extension.id); + } + } catch (error) { + // File doesn't exist or can't be parsed + return false; + } + + return false; +} + +/** + * Add the Dev Proxy Toolkit extension to workspace recommendations. + */ +export async function addExtensionToRecommendations(): Promise { + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + return false; + } + + const workspaceFolder = vscode.workspace.workspaceFolders[0]; + const vscodeFolderPath = path.join(workspaceFolder.uri.fsPath, '.vscode'); + const extensionsJsonPath = path.join(workspaceFolder.uri.fsPath, Extension.extensionsJsonPath); + + try { + let json: { recommendations?: string[] } = {}; + + // Try to read existing file + try { + const uri = vscode.Uri.file(extensionsJsonPath); + const document = await vscode.workspace.openTextDocument(uri); + json = JSON.parse(document.getText()); + } catch { + // File doesn't exist or can't be parsed, create new structure + json = { recommendations: [] }; + } + + // Ensure recommendations array exists + if (!json.recommendations) { + json.recommendations = []; + } + + // Add extension if not already present + if (!json.recommendations.includes(Extension.id)) { + json.recommendations.push(Extension.id); + } + + // Create .vscode directory if it doesn't exist + try { + await vscode.workspace.fs.createDirectory(vscode.Uri.file(vscodeFolderPath)); + } catch { + // Directory might already exist + } + + // Write the updated file + const uri = vscode.Uri.file(extensionsJsonPath); + const content = JSON.stringify(json, null, 2); + await vscode.workspace.fs.writeFile(uri, Buffer.from(content, 'utf8')); + + return true; + } catch (error) { + console.error('Error adding extension to recommendations:', error); + return false; + } +} + +/** + * Prompt user to add the extension to workspace recommendations. + */ +export async function promptForWorkspaceRecommendation(context: vscode.ExtensionContext): Promise { + // Check if we've already prompted for this workspace + const workspaceKey = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ''; + // Use a safe storage key by replacing path separators with underscores + const storageKey = `recommendation-prompted-${workspaceKey.replace(/[/\\:]/g, '_')}`; + + if (context.globalState.get(storageKey)) { + // Already prompted for this workspace + return; + } + + // Check if workspace has Dev Proxy config + const hasConfig = await hasDevProxyConfig(); + if (!hasConfig) { + return; + } + + // Check if extension is already recommended + const isRecommended = await isExtensionRecommended(); + if (isRecommended) { + return; + } + + // Mark as prompted to avoid showing again + await context.globalState.update(storageKey, true); + + // Show prompt + const message = 'This workspace contains Dev Proxy configuration files. Would you like to add the Dev Proxy Toolkit extension to workspace recommendations?'; + const result = await vscode.window.showInformationMessage(message, 'Yes', 'No'); + + if (result === 'Yes') { + const success = await addExtensionToRecommendations(); + if (success) { + vscode.window.showInformationMessage('Dev Proxy Toolkit added to workspace recommendations.'); + } else { + vscode.window.showErrorMessage('Failed to add extension to workspace recommendations.'); + } + } +}