Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions extension/loc/xlf/aspire-vscode.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"aspire-vscode.strings.lookingForDevkitBuildTask": "C# Dev Kit is installed, looking for C# Dev Kit build task...",
"aspire-vscode.strings.csharpDevKitNotInstalled": "C# Dev Kit is not installed, building using dotnet CLI...",
"aspire-vscode.strings.cliNotAvailable": "Aspire CLI is not available on PATH. Please install it and restart VS Code.",
"aspire-vscode.strings.cliFoundAtDefaultPath": "Aspire CLI found at {0}. The extension will use this path.",
"aspire-vscode.strings.openCliInstallInstructions": "See CLI installation instructions",
"aspire-vscode.strings.dismissLabel": "Dismiss"
}
2 changes: 1 addition & 1 deletion extension/src/commands/add.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';

export async function addCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('add');
await terminalProvider.sendAspireCommandToAspireTerminal('add');
}
2 changes: 1 addition & 1 deletion extension/src/commands/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';

export async function deployCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('deploy');
await terminalProvider.sendAspireCommandToAspireTerminal('deploy');
}
2 changes: 1 addition & 1 deletion extension/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from "../utils/AspireTerminalProvider";

export async function initCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('init');
await terminalProvider.sendAspireCommandToAspireTerminal('init');
};
2 changes: 1 addition & 1 deletion extension/src/commands/new.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from "../utils/AspireTerminalProvider";

export async function newCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('new');
await terminalProvider.sendAspireCommandToAspireTerminal('new');
};
2 changes: 1 addition & 1 deletion extension/src/commands/publish.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';

export async function publishCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('publish');
await terminalProvider.sendAspireCommandToAspireTerminal('publish');
}
2 changes: 1 addition & 1 deletion extension/src/commands/update.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';

export async function updateCommand(terminalProvider: AspireTerminalProvider) {
terminalProvider.sendAspireCommandToAspireTerminal('update');
await terminalProvider.sendAspireCommandToAspireTerminal('update');
}
12 changes: 2 additions & 10 deletions extension/src/debugger/AspireDebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import * as vscode from 'vscode';
import { defaultConfigurationName } from '../loc/strings';
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';
import { checkCliAvailableOrRedirect } from '../utils/workspace';

export class AspireDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
private _terminalProvider: AspireTerminalProvider;

constructor(terminalProvider: AspireTerminalProvider) {
this._terminalProvider = terminalProvider;
}

async provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration[]> {
if (folder === undefined) {
return [];
Expand All @@ -28,9 +21,8 @@ export class AspireDebugConfigurationProvider implements vscode.DebugConfigurati

async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration | null | undefined> {
// Check if CLI is available before starting debug session
const cliPath = this._terminalProvider.getAspireCliExecutablePath();
const isCliAvailable = await checkCliAvailableOrRedirect(cliPath);
if (!isCliAvailable) {
const result = await checkCliAvailableOrRedirect();
if (!result.available) {
return undefined; // Cancel the debug session
}

Expand Down
8 changes: 4 additions & 4 deletions extension/src/debugger/AspireDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ export class AspireDebugSession implements vscode.DebugAdapter {
if (isDirectory(appHostPath)) {
this.sendMessageWithEmoji("📁", launchingWithDirectory(appHostPath));

this.spawnRunCommand(args, appHostPath, noDebug);
void this.spawnRunCommand(args, appHostPath, noDebug);
}
else {
this.sendMessageWithEmoji("📂", launchingWithAppHost(appHostPath));

const workspaceFolder = path.dirname(appHostPath);
args.push('--project', appHostPath);
this.spawnRunCommand(args, workspaceFolder, noDebug);
void this.spawnRunCommand(args, workspaceFolder, noDebug);
}
Comment on lines +96 to 104
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spawnRunCommand is now async and is invoked with void ... (fire-and-forget). If getAspireCliExecutablePath()/resolveCliPath() throws (e.g., configuration update failure), this becomes an unhandled promise rejection. Please attach a .catch(...) (and surface/log an error) or wrap the body of spawnRunCommand in a try/catch and handle failures explicitly.

Copilot uses AI. Check for mistakes.
}
else if (message.command === 'disconnect' || message.command === 'terminate') {
Expand Down Expand Up @@ -133,7 +133,7 @@ export class AspireDebugSession implements vscode.DebugAdapter {
}
}

spawnRunCommand(args: string[], workingDirectory: string | undefined, noDebug: boolean) {
async spawnRunCommand(args: string[], workingDirectory: string | undefined, noDebug: boolean) {
const disposable = this._rpcServer.onNewConnection((client: ICliRpcClient) => {
if (client.debugSessionId === this.debugSessionId) {
this._rpcClient = client;
Expand All @@ -143,7 +143,7 @@ export class AspireDebugSession implements vscode.DebugAdapter {

spawnCliProcess(
this._terminalProvider,
this._terminalProvider.getAspireCliExecutablePath(),
await this._terminalProvider.getAspireCliExecutablePath(),
args,
{
stdoutCallback: (data) => {
Expand Down
7 changes: 3 additions & 4 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(cliAddCommandRegistration, cliNewCommandRegistration, cliInitCommandRegistration, cliDeployCommandRegistration, cliPublishCommandRegistration, openTerminalCommandRegistration, configureLaunchJsonCommandRegistration);
context.subscriptions.push(cliUpdateCommandRegistration, settingsCommandRegistration, openLocalSettingsCommandRegistration, openGlobalSettingsCommandRegistration, runAppHostCommandRegistration, debugAppHostCommandRegistration);

const debugConfigProvider = new AspireDebugConfigurationProvider(terminalProvider);
const debugConfigProvider = new AspireDebugConfigurationProvider();
context.subscriptions.push(
vscode.debug.registerDebugConfigurationProvider('aspire', debugConfigProvider, vscode.DebugConfigurationProviderTriggerKind.Dynamic)
);
Expand Down Expand Up @@ -114,9 +114,8 @@ async function tryExecuteCommand(commandName: string, terminalProvider: AspireTe
const cliCheckExcludedCommands: string[] = ["aspire-vscode.settings", "aspire-vscode.configureLaunchJson"];

if (!cliCheckExcludedCommands.includes(commandName)) {
const cliPath = terminalProvider.getAspireCliExecutablePath();
const isCliAvailable = await checkCliAvailableOrRedirect(cliPath);
if (!isCliAvailable) {
const result = await checkCliAvailableOrRedirect();
if (!result.available) {
return;
}
}
Expand Down
1 change: 1 addition & 0 deletions extension/src/loc/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ export const csharpDevKitNotInstalled = vscode.l10n.t('C# Dev Kit is not install
export const dismissLabel = vscode.l10n.t('Dismiss');
export const openCliInstallInstructions = vscode.l10n.t('See CLI installation instructions');
export const cliNotAvailable = vscode.l10n.t('Aspire CLI is not available on PATH. Please install it and restart VS Code.');
export const cliFoundAtDefaultPath = (path: string) => vscode.l10n.t('Aspire CLI found at {0}. The extension will use this path.', path);
76 changes: 20 additions & 56 deletions extension/src/test/aspireTerminalProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,94 +2,58 @@ import * as assert from 'assert';
import * as vscode from 'vscode';
import * as sinon from 'sinon';
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';
import * as cliPathModule from '../utils/cliPath';

suite('AspireTerminalProvider tests', () => {
let terminalProvider: AspireTerminalProvider;
let configStub: sinon.SinonStub;
let resolveCliPathStub: sinon.SinonStub;
let subscriptions: vscode.Disposable[];

setup(() => {
subscriptions = [];
terminalProvider = new AspireTerminalProvider(subscriptions);
configStub = sinon.stub(vscode.workspace, 'getConfiguration');
resolveCliPathStub = sinon.stub(cliPathModule, 'resolveCliPath');
});

teardown(() => {
configStub.restore();
resolveCliPathStub.restore();
subscriptions.forEach(s => s.dispose());
});

suite('getAspireCliExecutablePath', () => {
test('returns "aspire" when no custom path is configured', () => {
configStub.returns({
get: sinon.stub().returns('')
});
test('returns "aspire" when CLI is on PATH', async () => {
resolveCliPathStub.resolves({ cliPath: 'aspire', available: true, source: 'path' });

const result = terminalProvider.getAspireCliExecutablePath();
const result = await terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, 'aspire');
});

test('returns custom path when configured', () => {
configStub.returns({
get: sinon.stub().returns('/usr/local/bin/aspire')
});
test('returns resolved path when CLI found at default install location', async () => {
resolveCliPathStub.resolves({ cliPath: '/home/user/.aspire/bin/aspire', available: true, source: 'default-install' });

const result = terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, '/usr/local/bin/aspire');
const result = await terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, '/home/user/.aspire/bin/aspire');
});

test('returns custom path with spaces', () => {
configStub.returns({
get: sinon.stub().returns('/my path/with spaces/aspire')
});

const result = terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, '/my path/with spaces/aspire');
});
test('returns configured custom path', async () => {
resolveCliPathStub.resolves({ cliPath: '/usr/local/bin/aspire', available: true, source: 'configured' });

test('trims whitespace from configured path', () => {
configStub.returns({
get: sinon.stub().returns(' /usr/local/bin/aspire ')
});

const result = terminalProvider.getAspireCliExecutablePath();
const result = await terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, '/usr/local/bin/aspire');
});

test('returns "aspire" when configured path is only whitespace', () => {
configStub.returns({
get: sinon.stub().returns(' ')
});
test('returns "aspire" when CLI is not found', async () => {
resolveCliPathStub.resolves({ cliPath: 'aspire', available: false, source: 'not-found' });

const result = terminalProvider.getAspireCliExecutablePath();
const result = await terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, 'aspire');
});

test('handles Windows-style paths', () => {
configStub.returns({
get: sinon.stub().returns('C:\\Program Files\\Aspire\\aspire.exe')
});
test('handles Windows-style paths', async () => {
resolveCliPathStub.resolves({ cliPath: 'C:\\Program Files\\Aspire\\aspire.exe', available: true, source: 'configured' });

const result = terminalProvider.getAspireCliExecutablePath();
const result = await terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, 'C:\\Program Files\\Aspire\\aspire.exe');
});

test('handles Windows-style paths without spaces', () => {
configStub.returns({
get: sinon.stub().returns('C:\\aspire\\aspire.exe')
});

const result = terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, 'C:\\aspire\\aspire.exe');
});

test('handles paths with special characters', () => {
configStub.returns({
get: sinon.stub().returns('/path/with$dollar/aspire')
});

const result = terminalProvider.getAspireCliExecutablePath();
assert.strictEqual(result, '/path/with$dollar/aspire');
});
});
});
Loading
Loading