Skip to content
Draft
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
9 changes: 9 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.

10 changes: 10 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@
"title": "%command.new%",
"category": "Aspire"
},
{
"command": "aspire-vscode.addIntegrationTestProject",
"title": "%command.addIntegrationTestProject%",
"category": "Aspire",
"enablement": "aspire.addIntegrationTestProjectSupported"
},
{
"command": "aspire-vscode.init",
"title": "%command.init%",
Expand Down Expand Up @@ -656,6 +662,10 @@
}
],
"commandPalette": [
{
"command": "aspire-vscode.addIntegrationTestProject",
"when": "aspire.addIntegrationTestProjectSupported"
},
{
"command": "aspire-vscode.createWithAspire",
"when": "false"
Expand Down
3 changes: 3 additions & 0 deletions extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"extension.debug.defaultConfiguration.name": "Aspire: Launch default AppHost",
"extension.debug.defaultConfiguration.description": "Launch the effective Aspire AppHost in your workspace",
"command.add": "Add an integration",
"command.addIntegrationTestProject": "Add integration test project",
"command.new": "New Aspire project",
"command.init": "Initialize Aspire in an existing codebase",
"command.createWithAspire": "Set up Aspire",
Expand Down Expand Up @@ -59,6 +60,8 @@
"command.runPipelineStepAppHost": "Run pipeline step",
"command.debugPipelineStepAppHost": "Debug pipeline step",
"aspire-vscode.strings.noCsprojFound": "No AppHost found in the current workspace.",
"aspire-vscode.strings.addIntegrationTestProjectRequiresCSharpAppHost": "Integration test project scaffolding requires a C# AppHost.",
"aspire-vscode.strings.addIntegrationTestProjectUnsupported": "The selected Aspire CLI does not advertise integration test project scaffolding. Update or verify the Aspire CLI installation and try again.",
"aspire-vscode.strings.error": "Error: {0}",
"aspire-vscode.strings.yes": "Yes",
"aspire-vscode.strings.no": "No",
Expand Down
16 changes: 16 additions & 0 deletions extension/src/activation/registerCliCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { isE2eBridgeEnabled } from '../testing/e2eStateFileBridge';
import { registerInstrumentedCommand } from './instrumentedCommand';
import { AppHostCommandTarget, getAppHostArgs } from '../utils/appHostArgs';
import { getCliPathTargetForUri } from '../utils/cliPathVariables';
import { addIntegrationTestProject } from '../commands/addIntegrationTestProject';

interface CommandInvocation {
readonly target: CliPathResolutionTarget;
Expand Down Expand Up @@ -56,6 +57,20 @@ export function registerCliCommands(
const openGlobalSettingsCommandRegistration = vscode.commands.registerCommand('aspire-vscode.openGlobalSettings', () => tryExecuteCommand('aspire-vscode.openGlobalSettings', terminalProvider, openGlobalSettingsCommand));
const runAppHostCommandRegistration = registerInstrumentedCommand('aspire-vscode.runAppHostCommand', 'editor', () => editorCommandProvider.tryExecuteRunAppHost(true));
const debugAppHostCommandRegistration = registerInstrumentedCommand('aspire-vscode.debugAppHostCommand', 'editor', () => editorCommandProvider.tryExecuteRunAppHost(false));
const addIntegrationTestProjectRegistration = vscode.commands.registerCommand(
'aspire-vscode.addIntegrationTestProject',
() => tryExecuteCommand(
'aspire-vscode.addIntegrationTestProject',
terminalProvider,
(tp, invocation, cliPath) => {
const appHostPath = invocation.appHost?.appHostPath;
if (!appHostPath) {
throw new vscode.CancellationError();
}

return addIntegrationTestProject(tp, configInfoProvider, appHostPath, invocation.target, cliPath);
},
() => selectAppHostCommandInvocation(editorCommandProvider, true)));

// Walkthrough commands (no CLI check - the CLI may not be installed yet).
const installCliRegistration = registerInstrumentedCommand('aspire-vscode.installCli', 'walkthrough', installCliCommand);
Expand All @@ -78,6 +93,7 @@ export function registerCliCommands(
openGlobalSettingsCommandRegistration,
runAppHostCommandRegistration,
debugAppHostCommandRegistration,
addIntegrationTestProjectRegistration,
installCliRegistration,
verifyCliInstalledRegistration,
];
Expand Down
106 changes: 106 additions & 0 deletions extension/src/commands/addIntegrationTestProject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as path from 'path';
import * as vscode from 'vscode';
import {
addIntegrationTestProjectRequiresCSharpAppHost,
addIntegrationTestProjectUnsupported,
} from '../loc/strings';
import { aspireTestAppHostCapability } from '../types/configInfo';
import { AspireTerminalProvider } from '../utils/AspireTerminalProvider';
import { ConfigInfoProvider } from '../utils/configInfoProvider';
import {
CliPathResolutionTarget,
windowCliPathTarget,
workspaceFolderCliPathTarget,
} from '../utils/cliPathVariables';
import { extensionLogOutputChannel } from '../utils/logging';

export const addIntegrationTestProjectSupportedContext = 'aspire.addIntegrationTestProjectSupported';

export class AddIntegrationTestProjectAvailability implements vscode.Disposable {
private _refreshGeneration = 0;
private _disposed = false;

constructor(private readonly _configInfoProvider: ConfigInfoProvider) {
}

async refresh(forceRefresh = false): Promise<void> {
const generation = ++this._refreshGeneration;
await this._publish(false, generation);
if (this._disposed || generation !== this._refreshGeneration) {
return;
}

try {
const supported = await this._configInfoProvider.hasCapability(
aspireTestAppHostCapability,
{
target: getAvailabilityTarget(),
forceRefresh,
suppressErrors: true,
});
await this._publish(supported, generation);
}
catch (error) {
if (!this._disposed && generation === this._refreshGeneration) {
extensionLogOutputChannel.warn(`Unable to determine integration test scaffolding availability: ${String(error)}`);
}
}
}

dispose(): void {
this._disposed = true;
this._refreshGeneration++;
void vscode.commands.executeCommand('setContext', addIntegrationTestProjectSupportedContext, false);
}

private async _publish(supported: boolean, generation: number): Promise<void> {
if (!this._disposed && generation === this._refreshGeneration) {
await vscode.commands.executeCommand(
'setContext',
addIntegrationTestProjectSupportedContext,
supported);
}
}
}

export async function addIntegrationTestProject(
terminalProvider: AspireTerminalProvider,
configInfoProvider: ConfigInfoProvider,
appHostPath: string,
target: CliPathResolutionTarget,
cliPath: string,
): Promise<void> {
if (path.extname(appHostPath).toLowerCase() !== '.csproj') {
await vscode.window.showErrorMessage(addIntegrationTestProjectRequiresCSharpAppHost);
return;
Comment on lines +73 to +75
}

const supported = await configInfoProvider.hasCapability(
aspireTestAppHostCapability,
{
cliPath,
target,
Comment on lines +78 to +82
forceRefresh: true,
suppressErrors: true,
});
if (!supported) {
await vscode.window.showErrorMessage(addIntegrationTestProjectUnsupported);
return;
}

await terminalProvider.sendAspireCommandToAspireTerminal(
['new', 'aspire-test'],
true,
['--apphost', appHostPath],
{ cliPath, target });
Comment thread
ellahathaway marked this conversation as resolved.
}

function getAvailabilityTarget(): CliPathResolutionTarget {
const activeUri = vscode.window.activeTextEditor?.document.uri;
const workspaceFolder = activeUri
? vscode.workspace.getWorkspaceFolder(activeUri)
: vscode.workspace.workspaceFolders?.[0];
return workspaceFolder
? workspaceFolderCliPathTarget(workspaceFolder)
: windowCliPathTarget;
}
14 changes: 14 additions & 0 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { registerInstrumentedCommand } from './activation/instrumentedCommand';
import { registerCliCommands } from './activation/registerCliCommands';
import { registerTreeViewCommands } from './activation/registerTreeViewCommands';
import { registerCodeLensCommands } from './activation/registerCodeLensCommands';
import { AddIntegrationTestProjectAvailability } from './commands/addIntegrationTestProject';

let aspireExtensionContext = new AspireExtensionContext();

Expand Down Expand Up @@ -159,6 +160,19 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.executeCommand('setContext', 'aspire.noRunningAppHosts', true);
vscode.commands.executeCommand('setContext', 'aspire.loading', true);

const addIntegrationTestProjectAvailability = new AddIntegrationTestProjectAvailability(configInfoProvider);
context.subscriptions.push(
addIntegrationTestProjectAvailability,
vscode.window.onDidChangeActiveTextEditor(() => void addIntegrationTestProjectAvailability.refresh()),
vscode.workspace.onDidChangeWorkspaceFolders(() => void addIntegrationTestProjectAvailability.refresh()),
cliPathResolver.onDidChangeForwarding(() => void addIntegrationTestProjectAvailability.refresh(true)),
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('aspire.aspireCliExecutablePath')) {
void addIntegrationTestProjectAvailability.refresh(true);
}
}));
void addIntegrationTestProjectAvailability.refresh();

// Activate the data repository. Workspace describe watching and global polling begin when the panel is visible.
dataRepository.activate();

Expand Down
2 changes: 2 additions & 0 deletions extension/src/loc/strings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as vscode from 'vscode';

export const noCsprojFound = vscode.l10n.t('No AppHost found in the current workspace.');
export const addIntegrationTestProjectRequiresCSharpAppHost = vscode.l10n.t('Integration test project scaffolding requires a C# AppHost.');
export const addIntegrationTestProjectUnsupported = vscode.l10n.t('The selected Aspire CLI does not advertise integration test project scaffolding. Update or verify the Aspire CLI installation and try again.');
// l10n.t only substitutes primitives, so passing an Error left the message as the literal "Error: {0}".
export const errorMessage = (error: unknown) => vscode.l10n.t('Error: {0}', error instanceof Error ? error.message : String(error));
export const yesLabel = vscode.l10n.t('Yes');
Expand Down
Loading
Loading