Skip to content

Commit c9dcb3b

Browse files
Detect outdated Dev Proxy config files and offer Copilot-powered upgrade (#349)
* Initial plan * feat: detect outdated Dev Proxy config files and offer Copilot upgrade Add version mismatch detection for Dev Proxy config files in the workspace. When the schema version in config files doesn't match the installed Dev Proxy version, show a warning notification offering to upgrade using Copilot Chat. - Add extractVersionFromSchemaUrl() to extract version from schema URLs - Add findOutdatedConfigFiles() to scan workspace for outdated configs - Add upgradeConfigs command that opens Copilot Chat with upgrade prompt - Add handleOutdatedConfigFilesNotification() for the warning UI - Add tests for version extraction and command registration Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * refactor: address code review feedback - Use async vscode.workspace.fs.readFile instead of sync fs.readFileSync - Add type guard for parse result before casting to ObjectNode - Extract getNormalizedVersion() as shared utility to reduce duplication - Add regex pattern documentation and named constant - Check for workbench.action.chat.open command availability before invoking Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * docs: add changelog and readme entries for config upgrade feature - CHANGELOG: Add notification and upgrade-configs command entries under [1.13.0] - README: Add Upgrade config files command to Commands table - README: Add Notifications section with outdated config files description Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> * fix: broaden outdated config detection to all Dev Proxy schema files --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com> Co-authored-by: Garry Trinder <garry@trinder365.co.uk>
1 parent ced10ec commit c9dcb3b

13 files changed

Lines changed: 245 additions & 6 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added:
1313

1414
- Install: Added automated install and upgrade support for Linux using official setup scripts
15+
- Notification: Detect outdated Dev Proxy config files in workspace and show warning when schema versions don't match installed version
16+
- Command: `dev-proxy-toolkit.upgrade-configs` - Upgrade config files with Copilot Chat using Dev Proxy MCP tools
1517
- Quick Fixes: Enable local language model fix now adds or updates `languageModel.enabled: true` for supported plugins only
1618
- Workspace: Added automatic prompt to recommend extension in `.vscode/extensions.json` when Dev Proxy config files are detected
1719
- Command: Added `Add to Workspace Recommendations` to manually add extension to workspace recommendations

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Control Dev Proxy directly from VS Code via the Command Palette (`Cmd+Shift+P` /
3636
| Generate JWT | Dev Proxy installed |
3737
| Add to Workspace Recommendations | Always |
3838
| Reset State | Always |
39+
| Upgrade config files | Dev Proxy installed |
3940

4041
### Snippets
4142

@@ -217,6 +218,10 @@ The prompt offers three options:
217218

218219
You can also manually add the extension to recommendations at any time using the `Add to Workspace Recommendations` command, or use `Reset State` to clear all extension state including prompt preferences.
219220

221+
### Notifications
222+
223+
- **Outdated config files** - On activation, scans workspace for Dev Proxy config files with a schema version that doesn't match the installed Dev Proxy version. Offers a one-click upgrade using Copilot Chat and Dev Proxy MCP tools.
224+
220225
## Configuration
221226

222227
| Setting | Type | Default | Description |

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
"command": "dev-proxy-toolkit.reset-state",
100100
"title": "Reset State",
101101
"category": "Dev Proxy Toolkit"
102+
},
103+
{
104+
"command": "dev-proxy-toolkit.upgrade-configs",
105+
"title": "Upgrade config files",
106+
"category": "Dev Proxy Toolkit",
107+
"enablement": "isDevProxyInstalled"
102108
}
103109
],
104110
"mcpServerDefinitionProviders": [

src/commands/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { registerDiscoveryCommands } from './discovery';
88
import { registerDocCommands } from './docs';
99
import { Commands } from '../constants';
1010
import { addExtensionToRecommendations } from '../utils';
11+
import { registerUpgradeConfigCommands } from './upgrade-config';
1112

1213
/**
1314
* Register all commands for the extension.
@@ -58,6 +59,8 @@ export function registerCommands(
5859
});
5960
})
6061
);
62+
63+
registerUpgradeConfigCommands(context);
6164
}
6265

6366
// Re-export individual modules for testing and direct access
@@ -68,3 +71,4 @@ export { registerInstallCommands } from './install';
6871
export { registerJwtCommands } from './jwt';
6972
export { registerDiscoveryCommands } from './discovery';
7073
export { registerDocCommands } from './docs';
74+
export { registerUpgradeConfigCommands } from './upgrade-config';

src/commands/upgrade-config.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as vscode from 'vscode';
2+
import { Commands } from '../constants';
3+
import { DevProxyInstall } from '../types';
4+
import { getNormalizedVersion } from '../utils';
5+
6+
/**
7+
* Config upgrade commands.
8+
*/
9+
10+
export function registerUpgradeConfigCommands(
11+
context: vscode.ExtensionContext,
12+
): void {
13+
context.subscriptions.push(
14+
vscode.commands.registerCommand(Commands.upgradeConfigs, (fileUris?: vscode.Uri[]) =>
15+
upgradeConfigsWithCopilot(context, fileUris)
16+
)
17+
);
18+
}
19+
20+
async function upgradeConfigsWithCopilot(
21+
context: vscode.ExtensionContext,
22+
fileUris?: vscode.Uri[],
23+
): Promise<void> {
24+
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
25+
if (!devProxyInstall?.isInstalled) {
26+
return;
27+
}
28+
29+
const devProxyVersion = getNormalizedVersion(devProxyInstall);
30+
31+
const fileList = fileUris?.length
32+
? fileUris.map(uri => `- ${vscode.workspace.asRelativePath(uri)}`).join('\n')
33+
: 'all Dev Proxy config files in the workspace';
34+
35+
const prompt = [
36+
`Upgrade the following Dev Proxy configuration files to version v${devProxyVersion}:`,
37+
'',
38+
fileList,
39+
'',
40+
`Use the Dev Proxy MCP tools to get the latest schema information for v${devProxyVersion} and update each config file.`,
41+
'Update the $schema URLs and make any necessary configuration changes for the new version.',
42+
].join('\n');
43+
44+
try {
45+
// workbench.action.chat.open requires GitHub Copilot Chat extension
46+
const allCommands = await vscode.commands.getCommands();
47+
if (!allCommands.includes('workbench.action.chat.open')) {
48+
vscode.window.showWarningMessage(
49+
'GitHub Copilot Chat is not available. Please install the GitHub Copilot extension to use this feature.'
50+
);
51+
return;
52+
}
53+
54+
await vscode.commands.executeCommand('workbench.action.chat.open', {
55+
query: prompt,
56+
isPartialQuery: false,
57+
});
58+
} catch {
59+
vscode.window.showWarningMessage(
60+
'Could not open Copilot Chat. Please make sure GitHub Copilot is installed and enabled.'
61+
);
62+
}
63+
}

src/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ export const Commands = {
4040
// Language model commands
4141
addLanguageModelConfig: 'dev-proxy-toolkit.addLanguageModelConfig',
4242

43-
// Workspace commands
43+
// Workspace commands
4444
addToRecommendations: 'dev-proxy-toolkit.add-to-recommendations',
4545
resetState: 'dev-proxy-toolkit.reset-state',
46+
47+
// Config upgrade commands
48+
upgradeConfigs: 'dev-proxy-toolkit.upgrade-configs',
4649
} as const;
4750

4851
/**

src/detect.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,13 @@ export const getDevProxyExe = (versionPreference: VersionPreference) => {
103103
? VersionExeName.Stable
104104
: VersionExeName.Beta;
105105
};
106+
107+
/**
108+
* Get the normalized Dev Proxy version from an install object.
109+
* Strips the beta pre-release suffix for version comparison.
110+
*/
111+
export const getNormalizedVersion = (devProxyInstall: DevProxyInstall): string => {
112+
return devProxyInstall.isBeta
113+
? devProxyInstall.version.split('-')[0]
114+
: devProxyInstall.version;
115+
};

src/extension.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { registerCommands } from './commands';
3-
import { handleStartNotification, processNotification } from './notifications';
3+
import { handleStartNotification, processNotification, handleOutdatedConfigFilesNotification } from './notifications';
44
import { registerDocumentListeners } from './documents';
55
import { registerCodeLens } from './code-lens';
66
import { createStatusBar, statusBarLoop, updateStatusBar } from './status-bar';
@@ -36,9 +36,11 @@ export const activate = async (context: vscode.ExtensionContext): Promise<vscode
3636
const notification = handleStartNotification(context);
3737
processNotification(notification);
3838

39-
// Prompt for workspace recommendations
39+
// Prompt for workspace recommendations
4040
promptForWorkspaceRecommendation(context);
4141

42+
handleOutdatedConfigFilesNotification(context);
43+
4244
updateStatusBar(context, statusBar);
4345

4446
// Store the interval reference for proper cleanup

src/notifications.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as vscode from 'vscode';
22
import { DevProxyInstall } from './types';
3+
import { Commands } from './constants';
4+
import { findOutdatedConfigFiles, getNormalizedVersion } from './utils';
35

46
export const handleStartNotification = (context: vscode.ExtensionContext) => {
57
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
@@ -50,3 +52,41 @@ export const handleStartNotification = (context: vscode.ExtensionContext) => {
5052
export const processNotification = (notification: (() => { message: string; show: () => Promise<void>; }) | undefined) => {
5153
if (notification) { notification().show(); };
5254
};
55+
56+
/**
57+
* Check for outdated config files and notify the user.
58+
*
59+
* Scans the workspace for Dev Proxy config files whose schema version
60+
* doesn't match the installed Dev Proxy version and offers to upgrade
61+
* them using Copilot Chat.
62+
*/
63+
export async function handleOutdatedConfigFilesNotification(
64+
context: vscode.ExtensionContext,
65+
): Promise<void> {
66+
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
67+
if (!devProxyInstall?.isInstalled) {
68+
return;
69+
}
70+
71+
const devProxyVersion = getNormalizedVersion(devProxyInstall);
72+
73+
const outdatedFiles = await findOutdatedConfigFiles(devProxyVersion);
74+
75+
if (outdatedFiles.length === 0) {
76+
return;
77+
}
78+
79+
const fileCount = outdatedFiles.length;
80+
const fileWord = fileCount === 1 ? 'file' : 'files';
81+
const message = `${fileCount} Dev Proxy config ${fileWord} found with a schema version that doesn't match the installed version (v${devProxyVersion}).`;
82+
83+
const result = await vscode.window.showWarningMessage(
84+
message,
85+
'Upgrade with Copilot',
86+
'Dismiss',
87+
);
88+
89+
if (result === 'Upgrade with Copilot') {
90+
await vscode.commands.executeCommand(Commands.upgradeConfigs, outdatedFiles);
91+
}
92+
}

src/test/config-detection.test.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/**
22
* Config file detection tests.
33
* Verifies isConfigFile correctly identifies Dev Proxy configuration files.
4+
* Verifies extractVersionFromSchemaUrl correctly extracts versions from schema URLs.
45
*/
56
import * as assert from 'assert';
67
import * as vscode from 'vscode';
7-
import { isConfigFile, sleep } from '../utils';
8+
import { isConfigFile, extractVersionFromSchemaUrl, sleep } from '../utils';
89
import { getFixturePath, testDevProxyInstall, getExtensionContext } from './helpers';
910

1011
suite('isConfigFile', () => {
@@ -83,3 +84,29 @@ suite('isConfigFile', () => {
8384
assert.strictEqual(actual, expected);
8485
});
8586
});
87+
88+
suite('extractVersionFromSchemaUrl', () => {
89+
test('should extract version from standard schema URL', () => {
90+
const url = 'https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.24.0/rc.schema.json';
91+
assert.strictEqual(extractVersionFromSchemaUrl(url), '0.24.0');
92+
});
93+
94+
test('should extract version from legacy schema URL', () => {
95+
const url = 'https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v0.14.1/rc.schema.json';
96+
assert.strictEqual(extractVersionFromSchemaUrl(url), '0.14.1');
97+
});
98+
99+
test('should extract pre-release version from schema URL', () => {
100+
const url = 'https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v0.24.0-beta.1/rc.schema.json';
101+
assert.strictEqual(extractVersionFromSchemaUrl(url), '0.24.0-beta.1');
102+
});
103+
104+
test('should return empty string for URL without version', () => {
105+
const url = 'https://example.com/schema.json';
106+
assert.strictEqual(extractVersionFromSchemaUrl(url), '');
107+
});
108+
109+
test('should return empty string for empty string', () => {
110+
assert.strictEqual(extractVersionFromSchemaUrl(''), '');
111+
});
112+
});

0 commit comments

Comments
 (0)