diff --git a/extension/src/dcp/types.ts b/extension/src/dcp/types.ts index e76e7f3bc21..7c93ec2b6be 100644 --- a/extension/src/dcp/types.ts +++ b/extension/src/dcp/types.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; import { AspireDebugSession } from '../debugger/AspireDebugSession'; +import { ServerReadyAction } from '../debugger/launchProfiles'; export interface ErrorResponse { error: ErrorDetails; @@ -110,6 +111,7 @@ export interface AspireResourceExtendedDebugConfiguration extends vscode.DebugCo runId: string; debugSessionId: string | null; projectFile?: string; + serverReadyAction?: ServerReadyAction; } export interface AspireExtendedDebugConfiguration extends vscode.DebugConfiguration { diff --git a/extension/src/debugger/languages/dotnet.ts b/extension/src/debugger/languages/dotnet.ts index d7f50e9c076..3650cb7781d 100644 --- a/extension/src/debugger/languages/dotnet.ts +++ b/extension/src/debugger/languages/dotnet.ts @@ -291,7 +291,7 @@ export function createProjectDebuggerExtension(dotNetServiceProducer: (debugSess // The apphost's application URL is the Aspire dashboard URL. We already get the dashboard login URL later on, // so we should just avoid setting up serverReadyAction and manually open the browser ourselves. if (!launchOptions.isApphost) { - debugConfiguration.serverReadyAction = determineServerReadyAction(baseProfile?.launchBrowser, baseProfile?.applicationUrl); + debugConfiguration.serverReadyAction = determineServerReadyAction(baseProfile?.launchBrowser, baseProfile?.applicationUrl, debugConfiguration.serverReadyAction); } // Temporarily disable GH Copilot on the dashboard before the extension implementation is approved diff --git a/extension/src/debugger/launchProfiles.ts b/extension/src/debugger/launchProfiles.ts index d08cfe6f0c8..c80b5f33fe2 100644 --- a/extension/src/debugger/launchProfiles.ts +++ b/extension/src/debugger/launchProfiles.ts @@ -50,7 +50,14 @@ export async function readLaunchSettings(projectPath: string): Promise { assert.strictEqual(debugConfig.executablePath, 'exePath'); assert.strictEqual(debugConfig.checkForDevCert, true); - // serverReadyAction should be present with the applicationUrl + // serverReadyAction should be present when launchBrowser is true assert.notStrictEqual(debugConfig.serverReadyAction, undefined); - assert.strictEqual(debugConfig.serverReadyAction.uriFormat, 'https://localhost:5001'); + assert.strictEqual(debugConfig.serverReadyAction?.uriFormat, 'https://localhost:5001'); + assert.strictEqual(debugConfig.serverReadyAction?.pattern, '\\bNow listening on:\\s+(https?://\\S+)'); // cleanup fs.rmSync(tempDir, { recursive: true, force: true }); }); + }); diff --git a/extension/src/test/launchProfiles.test.ts b/extension/src/test/launchProfiles.test.ts index 2a04c153294..a63f3f1459a 100644 --- a/extension/src/test/launchProfiles.test.ts +++ b/extension/src/test/launchProfiles.test.ts @@ -86,8 +86,8 @@ suite('Launch Profile Tests', () => { const result = determineBaseLaunchProfile(launchConfig, sampleLaunchSettings); - assert.strictEqual(result.profile, null); - assert.strictEqual(result.profileName, null); + assert.strictEqual(result.profile, null); + assert.strictEqual(result.profileName, null); }); test('returns first profile with commandName=Project when no explicit profile specified', () => { @@ -335,17 +335,22 @@ suite('Launch Profile Tests', () => { }); suite('determineWorkingDirectory', () => { - const projectPath = path.join('C:', 'project', 'MyApp.csproj'); + // Keep these tests cross-platform by deriving the platform's root from the current process. + // On Windows this is typically something like "C:\\" (drive-dependent), and on POSIX it's "/". + const systemRoot = path.parse(process.cwd()).root; + const projectPath = path.join(systemRoot, 'project', 'MyApp.csproj'); + const projectDir = path.dirname(projectPath); + const absoluteWorkingDir = path.join(systemRoot, 'custom', 'working', 'dir'); test('uses absolute working directory from launch profile', () => { const baseProfile: LaunchProfile = { commandName: 'Project', - workingDirectory: path.join('C:', 'custom', 'working', 'dir') + workingDirectory: absoluteWorkingDir }; const result = determineWorkingDirectory(projectPath, baseProfile); - assert.strictEqual(result, path.join('C:', 'custom', 'working', 'dir')); + assert.strictEqual(result, absoluteWorkingDir); }); test('resolves relative working directory from launch profile', () => { @@ -356,7 +361,7 @@ suite('Launch Profile Tests', () => { const result = determineWorkingDirectory(projectPath, baseProfile); - assert.strictEqual(result, path.join('C:', 'project', 'custom')); + assert.strictEqual(result, path.join(projectDir, 'custom')); }); test('uses project directory when no working directory specified', () => { @@ -366,13 +371,13 @@ suite('Launch Profile Tests', () => { const result = determineWorkingDirectory(projectPath, baseProfile); - assert.strictEqual(result, path.join('C:', 'project')); + assert.strictEqual(result, projectDir); }); test('uses project directory when base profile is null', () => { const result = determineWorkingDirectory(projectPath, null); - assert.strictEqual(result, path.join('C:', 'project')); + assert.strictEqual(result, projectDir); }); }); @@ -382,8 +387,34 @@ suite('Launch Profile Tests', () => { assert.strictEqual(result, undefined); }); - test('returns undefined when applicationUrl is undefined', () => { - const result = determineServerReadyAction(true, undefined); + test('returns undefined when applicationUrl is undefined and no launch config serverReadyAction', () => { + const result = determineServerReadyAction(true, undefined, undefined); + assert.strictEqual(result, undefined); + }); + + test('returns existing when launchBrowser is undefined, applicationUrl is undefined and existing launch config serverReadyAction', () => { + const result = determineServerReadyAction(undefined, undefined, { action: 'openExternally', uriFormat: 'https://localhost:5001', pattern: '\\bNow listening on:\\s+(https?://\\S+)' }); + assert.notStrictEqual(result, undefined); + assert.strictEqual(result?.action, 'openExternally'); + assert.strictEqual(result?.uriFormat, 'https://localhost:5001'); + assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+(https?://\\S+)'); + }); + + test('returns existing when launchBrowser is true, applicationUrl is undefined and existing launch config serverReadyAction', () => { + const result = determineServerReadyAction(true, undefined, { action: 'openExternally', uriFormat: 'https://localhost:5001', pattern: '\\bNow listening on:\\s+(https?://\\S+)' }); + assert.notStrictEqual(result, undefined); + assert.strictEqual(result?.action, 'openExternally'); + assert.strictEqual(result?.uriFormat, 'https://localhost:5001'); + assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+(https?://\\S+)'); + }); + + test('returns undefined when launchBrowser is false, applicationUrl is undefined and existing launch config serverReadyAction', () => { + const result = determineServerReadyAction(false, undefined, { action: 'openExternally', uriFormat: 'https://localhost:5001', pattern: '\\bNow listening on:\\s+(https?://\\S+)' }); + assert.strictEqual(result, undefined); + }); + + test('returns undefined when launchBrowser is false, applicationUrl is not undefined and existing launch config serverReadyAction', () => { + const result = determineServerReadyAction(false, 'https://localhost:5001', { action: 'openExternally', uriFormat: 'https://localhost:5001', pattern: '\\bNow listening on:\\s+(https?://\\S+)' }); assert.strictEqual(result, undefined); }); @@ -394,7 +425,7 @@ suite('Launch Profile Tests', () => { assert.notStrictEqual(result, undefined); assert.strictEqual(result?.action, 'openExternally'); assert.strictEqual(result?.uriFormat, applicationUrl); - assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+https?://\\S+'); + assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+(https?://\\S+)'); }); test('returns serverReadyAction with first URL when multiple URLs separated by semicolon', () => { @@ -404,7 +435,7 @@ suite('Launch Profile Tests', () => { assert.notStrictEqual(result, undefined); assert.strictEqual(result?.action, 'openExternally'); assert.strictEqual(result?.uriFormat, 'https://localhost:5001'); - assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+https?://\\S+'); + assert.strictEqual(result?.pattern, '\\bNow listening on:\\s+(https?://\\S+)'); }); });