Skip to content

Commit bb4beb0

Browse files
Copilotgarrytrinder
andcommitted
Add Start with Options interactive command for Dev Proxy
Co-authored-by: garrytrinder <11563347+garrytrinder@users.noreply.github.com>
1 parent e129d97 commit bb4beb0

6 files changed

Lines changed: 329 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Added:
1313

14+
- Command: Added `Start with Options...` to launch Dev Proxy with interactive prompts for CLI settings
1415
- Install: Added automated install and upgrade support for Linux using official setup scripts
1516
- Notification: Detect outdated Dev Proxy config files in workspace and show warning when schema versions don't match installed version
1617
- Command: `dev-proxy-toolkit.upgrade-configs` - Upgrade config files with Copilot Chat using Dev Proxy MCP tools

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Control Dev Proxy directly from VS Code via the Command Palette (`Cmd+Shift+P` /
2525
| Command | When Available |
2626
|---------|----------------|
2727
| Start | Dev Proxy not running |
28+
| Start with Options... | Dev Proxy not running |
2829
| Stop | Dev Proxy running |
2930
| Restart | Dev Proxy running |
3031
| Raise mock request | Dev Proxy running |

package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
"icon": "$(debug-start)",
2929
"enablement": "!isDevProxyRunning"
3030
},
31+
{
32+
"command": "dev-proxy-toolkit.start-with-options",
33+
"title": "Start with Options...",
34+
"category": "Dev Proxy Toolkit",
35+
"icon": "$(debug-alt)",
36+
"enablement": "!isDevProxyRunning"
37+
},
3138
{
3239
"command": "dev-proxy-toolkit.stop",
3340
"title": "Stop",
@@ -120,6 +127,11 @@
120127
"group": "navigation@1",
121128
"when": "!activeEditorIsDirty && isDevProxyConfigFile && !isDevProxyRunning"
122129
},
130+
{
131+
"command": "dev-proxy-toolkit.start-with-options",
132+
"group": "navigation@1",
133+
"when": "!activeEditorIsDirty && isDevProxyConfigFile && !isDevProxyRunning"
134+
},
123135
{
124136
"command": "dev-proxy-toolkit.stop",
125137
"group": "navigation@2",

src/commands/proxy.ts

Lines changed: 307 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { VersionPreference } from '../enums';
88
import * as logger from '../logger';
99

1010
/**
11-
* Proxy lifecycle commands: start, stop, restart.
11+
* Proxy lifecycle commands: start, start with options, stop, restart.
1212
*
1313
* These commands control the Dev Proxy process.
1414
*/
@@ -25,6 +25,12 @@ export function registerProxyCommands(
2525
vscode.commands.registerCommand(Commands.start, () => startDevProxy(configuration, devProxyExe))
2626
);
2727

28+
context.subscriptions.push(
29+
vscode.commands.registerCommand(Commands.startWithOptions, () =>
30+
startDevProxyWithOptions(devProxyExe)
31+
)
32+
);
33+
2834
context.subscriptions.push(
2935
vscode.commands.registerCommand(Commands.stop, () =>
3036
stopDevProxy(apiClient, devProxyExe, configuration)
@@ -52,6 +58,306 @@ async function startDevProxy(
5258
terminalService.sendCommand(terminal, command);
5359
}
5460

61+
async function startDevProxyWithOptions(devProxyExe: string): Promise<void> {
62+
const args: string[] = [];
63+
64+
// Config file
65+
const configFiles = await vscode.workspace.findFiles(
66+
'**/devproxyrc.{json,jsonc}',
67+
'**/node_modules/**'
68+
);
69+
const configItems: vscode.QuickPickItem[] = [
70+
{ label: '$(remove) None', description: 'Use default configuration' },
71+
...configFiles.map(f => ({
72+
label: vscode.workspace.asRelativePath(f),
73+
description: f.fsPath,
74+
})),
75+
];
76+
77+
const selectedConfig = await vscode.window.showQuickPick(configItems, {
78+
title: 'Start with Options (1/13): Config file',
79+
placeHolder: 'Select a config file',
80+
});
81+
if (selectedConfig === undefined) {
82+
return;
83+
}
84+
if (selectedConfig.description && selectedConfig.description !== 'Use default configuration') {
85+
args.push('--config-file', `"${selectedConfig.description}"`);
86+
}
87+
88+
// Port
89+
const port = await vscode.window.showInputBox({
90+
title: 'Start with Options (2/13): Port',
91+
prompt: 'Enter the proxy port number',
92+
value: '8000',
93+
validateInput: validatePortNumber,
94+
});
95+
if (port === undefined) {
96+
return;
97+
}
98+
if (port && port !== '8000') {
99+
args.push('--port', port);
100+
}
101+
102+
// API port
103+
const apiPort = await vscode.window.showInputBox({
104+
title: 'Start with Options (3/13): API port',
105+
prompt: 'Enter the API port number',
106+
value: '8897',
107+
validateInput: validatePortNumber,
108+
});
109+
if (apiPort === undefined) {
110+
return;
111+
}
112+
if (apiPort && apiPort !== '8897') {
113+
args.push('--api-port', apiPort);
114+
}
115+
116+
// IP address
117+
const ipAddress = await vscode.window.showInputBox({
118+
title: 'Start with Options (4/13): IP address',
119+
prompt: 'Enter the IP address to listen on',
120+
value: '127.0.0.1',
121+
validateInput: validateIpAddress,
122+
});
123+
if (ipAddress === undefined) {
124+
return;
125+
}
126+
if (ipAddress && ipAddress !== '127.0.0.1') {
127+
args.push('--ip-address', ipAddress);
128+
}
129+
130+
// As system proxy
131+
const asSystemProxy = await vscode.window.showQuickPick(
132+
[
133+
{ label: 'Yes', description: 'Register as system proxy (default)' },
134+
{ label: 'No', description: 'Do not register as system proxy' },
135+
],
136+
{
137+
title: 'Start with Options (5/13): As system proxy',
138+
placeHolder: 'Register Dev Proxy as a system proxy?',
139+
}
140+
);
141+
if (asSystemProxy === undefined) {
142+
return;
143+
}
144+
if (asSystemProxy.label === 'No') {
145+
args.push('--as-system-proxy', 'false');
146+
}
147+
148+
// Install cert
149+
const installCert = await vscode.window.showQuickPick(
150+
[
151+
{ label: 'Yes', description: 'Install certificate (default)' },
152+
{ label: 'No', description: 'Do not install certificate' },
153+
],
154+
{
155+
title: 'Start with Options (6/13): Install certificate',
156+
placeHolder: 'Install the root certificate?',
157+
}
158+
);
159+
if (installCert === undefined) {
160+
return;
161+
}
162+
if (installCert.label === 'No') {
163+
args.push('--install-cert', 'false');
164+
}
165+
166+
// Log level
167+
const logLevel = await vscode.window.showQuickPick(
168+
['trace', 'debug', 'information', 'warning', 'error'],
169+
{
170+
title: 'Start with Options (7/13): Log level',
171+
placeHolder: 'Select a log level',
172+
}
173+
);
174+
if (logLevel === undefined) {
175+
return;
176+
}
177+
if (logLevel !== 'information') {
178+
args.push('--log-level', logLevel);
179+
}
180+
181+
// Failure rate
182+
const failureRate = await vscode.window.showInputBox({
183+
title: 'Start with Options (8/13): Failure rate',
184+
prompt: 'Enter the failure rate (0-100)',
185+
value: '50',
186+
validateInput: validateFailureRate,
187+
});
188+
if (failureRate === undefined) {
189+
return;
190+
}
191+
if (failureRate && failureRate !== '50') {
192+
args.push('--failure-rate', failureRate);
193+
}
194+
195+
// URLs to watch
196+
const urlsToWatch = await vscode.window.showInputBox({
197+
title: 'Start with Options (9/13): URLs to watch',
198+
prompt: 'Enter URLs to watch (space separated). Leave empty to use config file values.',
199+
placeHolder: 'https://api.example.com/* https://graph.microsoft.com/v1.0/*',
200+
value: '',
201+
});
202+
if (urlsToWatch === undefined) {
203+
return;
204+
}
205+
if (urlsToWatch.trim()) {
206+
args.push('--urls-to-watch', urlsToWatch.trim());
207+
}
208+
209+
// Record
210+
const record = await vscode.window.showQuickPick(
211+
[
212+
{ label: 'No', description: 'Do not record (default)' },
213+
{ label: 'Yes', description: 'Start recording immediately' },
214+
],
215+
{
216+
title: 'Start with Options (10/13): Record',
217+
placeHolder: 'Start recording on launch?',
218+
}
219+
);
220+
if (record === undefined) {
221+
return;
222+
}
223+
if (record.label === 'Yes') {
224+
args.push('--record');
225+
}
226+
227+
// No first run
228+
const noFirstRun = await vscode.window.showQuickPick(
229+
[
230+
{ label: 'No', description: 'Show first run experience (default)' },
231+
{ label: 'Yes', description: 'Skip first run experience' },
232+
],
233+
{
234+
title: 'Start with Options (11/13): No first run',
235+
placeHolder: 'Skip the first run experience?',
236+
}
237+
);
238+
if (noFirstRun === undefined) {
239+
return;
240+
}
241+
if (noFirstRun.label === 'Yes') {
242+
args.push('--no-first-run');
243+
}
244+
245+
// Timeout
246+
const timeout = await vscode.window.showInputBox({
247+
title: 'Start with Options (12/13): Timeout',
248+
prompt: 'Enter timeout in seconds. Leave empty for no timeout.',
249+
value: '',
250+
validateInput: validateTimeout,
251+
});
252+
if (timeout === undefined) {
253+
return;
254+
}
255+
if (timeout.trim()) {
256+
args.push('--timeout', timeout.trim());
257+
}
258+
259+
// Watch PIDs
260+
const watchPids = await vscode.window.showInputBox({
261+
title: 'Start with Options (13/13): Watch PIDs and process names',
262+
prompt: 'Enter process IDs to watch (space separated). Leave empty to skip.',
263+
placeHolder: '1234 5678',
264+
value: '',
265+
validateInput: validateWatchPids,
266+
});
267+
if (watchPids === undefined) {
268+
return;
269+
}
270+
if (watchPids.trim()) {
271+
args.push('--watch-pids', watchPids.trim());
272+
}
273+
274+
// Watch process names
275+
const watchProcessNames = await vscode.window.showInputBox({
276+
prompt: 'Enter process names to watch (space separated). Leave empty to skip.',
277+
placeHolder: 'msedge chrome',
278+
value: '',
279+
validateInput: validateProcessNames,
280+
});
281+
if (watchProcessNames === undefined) {
282+
return;
283+
}
284+
if (watchProcessNames.trim()) {
285+
args.push('--watch-process-names', watchProcessNames.trim());
286+
}
287+
288+
const command = [devProxyExe, ...args].join(' ');
289+
const terminalService = TerminalService.fromConfiguration();
290+
const terminal = terminalService.getOrCreateTerminal();
291+
terminalService.sendCommand(terminal, command);
292+
}
293+
294+
function validatePortNumber(value: string): string | undefined {
295+
if (!value) {
296+
return undefined;
297+
}
298+
const num = Number(value);
299+
if (!Number.isInteger(num) || num < 1 || num > 65535) {
300+
return 'Port must be an integer between 1 and 65535';
301+
}
302+
return undefined;
303+
}
304+
305+
function validateIpAddress(value: string): string | undefined {
306+
if (!value) {
307+
return undefined;
308+
}
309+
if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(value)) {
310+
return 'Enter a valid IPv4 address (e.g. 127.0.0.1)';
311+
}
312+
return undefined;
313+
}
314+
315+
function validateFailureRate(value: string): string | undefined {
316+
if (!value) {
317+
return undefined;
318+
}
319+
const num = Number(value);
320+
if (!Number.isInteger(num) || num < 0 || num > 100) {
321+
return 'Failure rate must be an integer between 0 and 100';
322+
}
323+
return undefined;
324+
}
325+
326+
function validateTimeout(value: string): string | undefined {
327+
if (!value) {
328+
return undefined;
329+
}
330+
const num = Number(value);
331+
if (!Number.isInteger(num) || num < 1) {
332+
return 'Timeout must be a positive integer';
333+
}
334+
return undefined;
335+
}
336+
337+
function validateWatchPids(value: string): string | undefined {
338+
if (!value) {
339+
return undefined;
340+
}
341+
const parts = value.trim().split(/\s+/);
342+
for (const part of parts) {
343+
const num = Number(part);
344+
if (!Number.isInteger(num) || num < 1) {
345+
return 'PIDs must be positive integers separated by spaces';
346+
}
347+
}
348+
return undefined;
349+
}
350+
351+
function validateProcessNames(value: string): string | undefined {
352+
if (!value) {
353+
return undefined;
354+
}
355+
if (!/^[a-zA-Z0-9\s]+$/.test(value)) {
356+
return 'Process names can only contain alphanumeric characters and spaces';
357+
}
358+
return undefined;
359+
}
360+
55361
async function stopDevProxy(
56362
apiClient: DevProxyApiClient,
57363
devProxyExe: string,

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
export const Commands = {
1111
// Proxy lifecycle commands
1212
start: 'dev-proxy-toolkit.start',
13+
startWithOptions: 'dev-proxy-toolkit.start-with-options',
1314
stop: 'dev-proxy-toolkit.stop',
1415
restart: 'dev-proxy-toolkit.restart',
1516

src/test/commands.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ suite('Command Registration', () => {
2121
);
2222
});
2323

24+
test('start-with-options command should be registered', () => {
25+
assert.ok(
26+
registeredCommands.includes(Commands.startWithOptions),
27+
`Command ${Commands.startWithOptions} should be registered`
28+
);
29+
});
30+
2431
test('stop command should be registered', () => {
2532
assert.ok(
2633
registeredCommands.includes(Commands.stop),

0 commit comments

Comments
 (0)