Skip to content

Commit 9561fff

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 16624c0 commit 9561fff

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
- Quick Fixes: Enable local language model fix now adds or updates `languageModel.enabled: true` for supported plugins only
1617
- Workspace: Added automatic prompt to recommend extension in `.vscode/extensions.json` when Dev Proxy config files are detected

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",
@@ -114,6 +121,11 @@
114121
"group": "navigation@1",
115122
"when": "!activeEditorIsDirty && isDevProxyConfigFile && !isDevProxyRunning"
116123
},
124+
{
125+
"command": "dev-proxy-toolkit.start-with-options",
126+
"group": "navigation@1",
127+
"when": "!activeEditorIsDirty && isDevProxyConfigFile && !isDevProxyRunning"
128+
},
117129
{
118130
"command": "dev-proxy-toolkit.stop",
119131
"group": "navigation@2",

src/commands/proxy.ts

Lines changed: 307 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getDevProxyExe } from '../detect';
77
import { VersionPreference } from '../enums';
88

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

27+
context.subscriptions.push(
28+
vscode.commands.registerCommand(Commands.startWithOptions, () =>
29+
startDevProxyWithOptions(devProxyExe)
30+
)
31+
);
32+
2733
context.subscriptions.push(
2834
vscode.commands.registerCommand(Commands.stop, () =>
2935
stopDevProxy(apiClient, devProxyExe, configuration)
@@ -50,6 +56,306 @@ async function startDevProxy(
5056
terminalService.sendCommand(terminal, command);
5157
}
5258

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