From 0d498b793ae29bfe02babc50afd713980b470b5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:34:36 +0000 Subject: [PATCH 1/4] Initial plan From 607689a139a198ffe897a53458b3d8e7f81fc0a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:39:02 +0000 Subject: [PATCH 2/4] Add workspace recommendations feature to prompt users to add extension to recommendations Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/constants.ts | 8 ++ src/extension.ts | 4 + src/test/workspace-recommendations.test.ts | 77 +++++++++++ src/utils/index.ts | 8 ++ src/utils/workspace-recommendations.ts | 142 +++++++++++++++++++++ 5 files changed, 239 insertions(+) create mode 100644 src/test/workspace-recommendations.test.ts create mode 100644 src/utils/workspace-recommendations.ts 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..da7ff39 --- /dev/null +++ b/src/utils/workspace-recommendations.ts @@ -0,0 +1,142 @@ +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.toString() ?? ''; + const storageKey = `recommendation-prompted-${workspaceKey}`; + + 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.'); + } + } +} From db14d3875a4900ac7855bd2c4a2a650d4e2da758 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 20 Jan 2026 09:40:30 +0000 Subject: [PATCH 3/4] Fix storage key handling for workspace recommendations prompt Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/utils/workspace-recommendations.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/workspace-recommendations.ts b/src/utils/workspace-recommendations.ts index da7ff39..6b992ca 100644 --- a/src/utils/workspace-recommendations.ts +++ b/src/utils/workspace-recommendations.ts @@ -104,8 +104,9 @@ export async function addExtensionToRecommendations(): Promise { */ export async function promptForWorkspaceRecommendation(context: vscode.ExtensionContext): Promise { // Check if we've already prompted for this workspace - const workspaceKey = vscode.workspace.workspaceFolders?.[0]?.uri.toString() ?? ''; - const storageKey = `recommendation-prompted-${workspaceKey}`; + 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 From 8e4b5ecbb298fc3e4359e238d7068da1394816ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:24:39 +0000 Subject: [PATCH 4/4] Fix test fixture path resolution for Windows compatibility Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> --- src/test/helpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/helpers.ts b/src/test/helpers.ts index 58ed363..e748348 100644 --- a/src/test/helpers.ts +++ b/src/test/helpers.ts @@ -8,8 +8,9 @@ import { DevProxyInstall } from '../types'; * avoiding the need to copy them to out/ during build. */ export function getFixturePath(fileName: string): string { - // process.cwd() is the workspace root when running tests via VS Code test runner - return path.resolve(process.cwd(), 'src', 'test', 'examples', fileName); + // __dirname is out/test when tests are compiled + // Go up two levels to reach the workspace root, then into src/test/examples + return path.resolve(__dirname, '..', '..', 'src', 'test', 'examples', fileName); } /**