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
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ jobs:
working-directory: /usr/share/dotnet
run: dotnet new install Aspire.ProjectTemplates@13.6.4

# Install Aspire CLI
- name: Install Aspire CLI
run: |
curl -fsSL https://aspire.dev/install.sh | bash -s -- --version 13.6.4
echo "$HOME/.aspire/bin" >> "$GITHUB_PATH"

# Install MAUI workload
- name: Install MAUI workload
working-directory: /usr/share/dotnet
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@

## [Unreleased]

### Added

- Publish and deploy Aspire AppHosts actions

### Changed

- Update Rider SDK version to 2026.3-RC1

### Removed

- Aspire manifest generation actions and service

### Fixed

- [RIDER-144901](https://youtrack.jetbrains.com/issue/RIDER-144901) Exit code is missing when stopping a service from the Services tool window
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ The plugin provides support for Aspire projects in JetBrains Rider.
**Features:**
* Support for running and debugging of Aspire projects
* Resource dashboard
* Manifest generation
* Automatic database connection

**Documentation**:
Expand Down
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ dependencies {
testBundledModule("intellij.rd.client")
testBundledPlugin("intellij.structureView.plugin")
testBundledPlugin("intellij.bookmarks.plugin")
testBundledPlugin("org.jetbrains.plugins.terminal")
testBundledModule("intellij.terminal.frontend")
}

compileOnly(libs.serializationJson)
Expand All @@ -83,6 +85,7 @@ intellijPlatform {
}

pluginVerification {
ignoredProblemsFile = layout.projectDirectory.file("gradle/plugin-verifier-ignored-problems.txt")
failureLevel = listOf(
VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS,
VerifyPluginTask.FailureLevel.OVERRIDE_ONLY_API_USAGES,
Expand Down
2 changes: 2 additions & 0 deletions core.frontend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dependencies {
}

bundledModule("intellij.platform.frontend")
bundledPlugin("org.jetbrains.plugins.terminal")
bundledModule("intellij.terminal.frontend")
}

implementation(project(":shared"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.jetbrains.aspire.actions.dashboard.host

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.jetbrains.aspire.deployment.AspireAppHostDeploymentService
import com.jetbrains.aspire.deployment.AspireDeploymentOperation
import com.jetbrains.aspire.deployment.AspireDeploymentTerminalRunner
import com.jetbrains.aspire.worker.AspireAppHostData
import kotlinx.coroutines.launch

internal abstract class AspireAppHostDeploymentAction(
private val operation: AspireDeploymentOperation,
) : AspireAppHostBaseAction() {
override fun performAction(event: AnActionEvent, appHostData: AspireAppHostData, project: Project) {
event.coroutineScope.launch {
val commandLine = project.service<AspireAppHostDeploymentService>()
.prepareCommand(appHostData.id, operation) ?: return@launch
project.service<AspireDeploymentTerminalRunner>().run(operation, commandLine)
}
}

override fun updateAction(event: AnActionEvent, appHostData: AspireAppHostData) {
event.presentation.isEnabledAndVisible = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jetbrains.aspire.actions.dashboard.host

import com.jetbrains.aspire.deployment.AspireDeploymentOperation

internal class DeployAppHostAction : AspireAppHostDeploymentAction(AspireDeploymentOperation.Deploy)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.jetbrains.aspire.actions.dashboard.host

import com.jetbrains.aspire.deployment.AspireDeploymentOperation

internal class PublishAppHostAction : AspireAppHostDeploymentAction(AspireDeploymentOperation.Publish)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@file:Suppress("UnstableApiUsage")

package com.jetbrains.aspire.deployment

import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.terminal.frontend.toolwindow.TerminalToolWindowTabsManager
import com.jetbrains.aspire.AspireCoreFrontendBundle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.plugins.terminal.startup.TerminalProcessType

@Service(Service.Level.PROJECT)
internal class AspireDeploymentTerminalRunner(private val project: Project) {
suspend fun run(
operation: AspireDeploymentOperation,
commandLine: AspireDeploymentCommandLine,
) = withContext(Dispatchers.EDT) {
// The terminal selects its execution environment from the frontend's NIO path.
val workingDirectory = EelPath.parse(commandLine.workingDirectory.value, project.getEelDescriptor()).asNioPath()
val tabName = when (operation) {
AspireDeploymentOperation.Publish ->
AspireCoreFrontendBundle.message("deployment.terminal.publish.title", commandLine.appHostName)
AspireDeploymentOperation.Deploy ->
AspireCoreFrontendBundle.message("deployment.terminal.deploy.title", commandLine.appHostName)
}
TerminalToolWindowTabsManager
.getInstance(project)
.createTabBuilder()
.shellCommand(commandLine.command)
.workingDirectory(workingDirectory.toString())
.processType(TerminalProcessType.NON_SHELL)
.tabName(tabName)
.requestFocus(true)
.deferSessionStartUntilUiShown(false)
.closeOnProcessTermination(false)
.restoreOnProjectReopen(false)
.createTab()
}
}
15 changes: 15 additions & 0 deletions core.frontend/src/main/resources/intellij.aspire.core.frontend.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<module name="intellij.platform.frontend"/>
<module name="intellij.aspire.shared"/>
<module name="intellij.aspire.core"/>
<plugin id="org.jetbrains.plugins.terminal"/>
<module name="intellij.terminal.frontend"/>
</dependencies>

<resource-bundle>messages.AspireCoreFrontendBundle</resource-bundle>
Expand Down Expand Up @@ -40,6 +42,16 @@
icon="AllIcons.Actions.Suspend">
<override-text place="GoToAction"/>
</action>
<action id="Aspire.Host.Publish"
class="com.jetbrains.aspire.actions.dashboard.host.PublishAppHostAction"
icon="AllIcons.Actions.Upload">
<override-text place="GoToAction"/>
</action>
<action id="Aspire.Host.Deploy"
class="com.jetbrains.aspire.actions.dashboard.host.DeployAppHostAction"
icon="AllIcons.Nodes.Deploy">
<override-text place="GoToAction"/>
</action>
<action id="Aspire.Host.Dashboard"
class="com.jetbrains.aspire.actions.dashboard.host.AspireOpenDashboardAction"
icon="AllIcons.General.Web">
Expand All @@ -51,6 +63,9 @@
<reference ref="Aspire.Host.Debug"/>
<reference ref="Aspire.Host.Stop"/>
<separator/>
<reference ref="Aspire.Host.Publish"/>
<reference ref="Aspire.Host.Deploy"/>
<separator/>
<reference ref="Aspire.Host.Dashboard"/>
<separator/>
<reference ref="Aspire.Settings"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ action.Aspire.Host.Debug.description=Debug Aspire AppHost
action.Aspire.Host.Stop.text=Stop
action.Aspire.Host.Stop.GoToAction.text=Stop Aspire AppHost
action.Aspire.Host.Stop.description=Stop Aspire AppHost
action.Aspire.Host.Publish.text=Publish
action.Aspire.Host.Publish.GoToAction.text=Publish Aspire AppHost
action.Aspire.Host.Publish.description=Publish Aspire AppHost in a terminal
action.Aspire.Host.Deploy.text=Deploy
action.Aspire.Host.Deploy.GoToAction.text=Deploy Aspire AppHost
action.Aspire.Host.Deploy.description=Deploy Aspire AppHost in a terminal
action.Aspire.Resource.Start.text=Start
action.Aspire.Resource.Start.GoToAction.text=Start Aspire Resource
action.Aspire.Resource.Start.description=Start Aspire resource
Expand Down Expand Up @@ -55,3 +61,6 @@ configurable.Aspire.show.environment.variables=Show environment variables on the
configurable.Aspire.open.console.view=Prefer to open a resource console view

resource.choose.command.popup.header=Choose a Resource Command

deployment.terminal.publish.title=Aspire Publish: {0}
deployment.terminal.deploy.title=Aspire Deploy: {0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@file:Suppress("UnstableApiUsage")

package com.jetbrains.aspire.deployment

import com.intellij.diagnostic.rethrowControlFlowException
import com.intellij.ide.BrowserUtil
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.platform.eel.provider.asEelPath
import com.jetbrains.aspire.AspireCoreBundle
import com.jetbrains.aspire.cli.AspireCliLocator
import com.jetbrains.aspire.worker.AspireAppHostId
import com.jetbrains.aspire.worker.AspirePath
import com.jetbrains.aspire.worker.AspireWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.io.path.isRegularFile

internal class AspireAppHostDeploymentServiceImpl(private val project: Project) : AspireAppHostDeploymentService {
companion object {
private val LOG = logger<AspireAppHostDeploymentServiceImpl>()
}

override suspend fun prepareCommand(
appHostId: AspireAppHostId,
operation: AspireDeploymentOperation,
): AspireDeploymentCommandLine? = withContext(Dispatchers.IO) {
try {
val appHost = AspireWorker.getInstance(project).getAppHostById(appHostId)
if (appHost == null || !appHost.appHostPath.isRegularFile()) {
Notification(
"Aspire",
AspireCoreBundle.message("notification.aspire.deployment.app.host.not.found.title"),
AspireCoreBundle.message("notification.aspire.deployment.app.host.not.found.content"),
NotificationType.WARNING,
).notify(project)
return@withContext null
}

val appHostPath = appHost.appHostPath.toAbsolutePath()
val appHostEelPath = appHostPath.asEelPath()
val cliPath = AspireCliLocator.getInstance(project).locate()
if (cliPath == null) {
Notification(
"Aspire",
AspireCoreBundle.message("notification.aspire.cli.not.installed.title"),
AspireCoreBundle.message("notification.aspire.cli.not.installed.content"),
NotificationType.WARNING,
).addAction(
NotificationAction.createSimple(AspireCoreBundle.message("notification.aspire.cli.install.action")) {
BrowserUtil.browse("https://aspire.dev/get-started/install-cli/")
}
).notify(project)
return@withContext null
}

val command = listOf(cliPath.toString(), operation.commandName, "--apphost", appHostEelPath.toString())
val workingDirectory = AspirePath(appHostPath.parent.asEelPath().toString())
AspireDeploymentCommandLine(appHost.name, command, workingDirectory)
} catch (e: Exception) {
rethrowControlFlowException(e)
LOG.warn("Unable to prepare Aspire deployment command", e)
Notification(
"Aspire",
AspireCoreBundle.message("notification.aspire.deployment.prepare.failed.title"),
AspireCoreBundle.message("notification.aspire.deployment.prepare.failed.content", e.message ?: ""),
NotificationType.ERROR,
).notify(project)
null
}
}
}
3 changes: 3 additions & 0 deletions core/src/main/resources/intellij.aspire.core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
</projectListeners>

<extensions defaultExtensionNs="com.intellij">
<projectService serviceInterface="com.jetbrains.aspire.deployment.AspireAppHostDeploymentService"
serviceImplementation="com.jetbrains.aspire.deployment.AspireAppHostDeploymentServiceImpl"/>

<applicationService serviceInterface="com.jetbrains.aspire.grpc.GrpcResourceClientFactory"
serviceImplementation="com.jetbrains.aspire.grpc.GrpcResourceClientFactoryImpl"/>
<applicationService serviceInterface="com.jetbrains.aspire.cli.AspireCliResourceClientFactory"
Expand Down
9 changes: 5 additions & 4 deletions core/src/main/resources/messages/AspireCoreBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ run.editor.podman.runtime=Use Podman container runtime
run.editor.open.browser=Open browser
run.editor.url=URL

progress.generating.aspire.manifest=Generating Aspire Manifest

notification.manifest.unable.to.generate=Unable to generate Aspire manifest
notification.unable.to.find.running.container.runtime=Unable to find a running container runtime
notification.ensure.that.container.runtime.is.running=Ensure that the container runtime is running and accessible
notification.dev.certificate.needs.attention=HTTPS development certificate needs attention
Expand All @@ -65,5 +62,9 @@ notification.dev.certificate.learn.more=Learn more
notification.unable.to.find.app.host.run.config.title=Unable to find Aspire run configuration
notification.unable.to.find.app.host.run.config.description=Unable to find any Aspire run configuration for the given AppHost file
notification.aspire.cli.not.installed.title=Aspire CLI not found
notification.aspire.cli.not.installed.content=The Aspire CLI (aspire) could not be found on the PATH. Install it to run Aspire CLI configurations.
notification.aspire.cli.not.installed.content=The Aspire CLI (aspire) could not be found. Install it or configure its path in the Aspire settings.
notification.aspire.cli.install.action=Open Aspire CLI installation guide
notification.aspire.deployment.app.host.not.found.title=Aspire AppHost not found
notification.aspire.deployment.app.host.not.found.content=The selected Aspire AppHost is no longer available.
notification.aspire.deployment.prepare.failed.title=Unable to prepare Aspire command
notification.aspire.deployment.prepare.failed.content=Unable to prepare the Aspire deployment command: {0}
2 changes: 1 addition & 1 deletion docs/agents/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
| `core` | Core plugin logic: embedded session servers, CLI, sessions, OTLP | `shared` + IntelliJ IDEA SDK |
| `core.frontend` | Settings UI, help, and dashboard actions shared by Services and Rider | `shared` + `core` |
| `services` | Aspire Services View UI and view models | `shared` + `core` + `core.frontend` + Services View plugin |
| `rider` | Rider-specific: orchestration, debugging, manifests, launch cfg | `shared` + `core` + `core.frontend` |
| `rider` | Rider-specific: orchestration, debugging, launch cfg | `shared` + `core` + `core.frontend` |
| `diagram` | Architecture visualization (bundled Diagram plugin dependency) | `shared` |
| `docker` | Docker container integration | `core` |
| `database` | Database connection support | `core` |
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ pluginRepositoryUrl = https://github.com/JetBrains/aspire-plugin
# EAP: 2020.3-EAP2-SNAPSHOT
# Nightly: 2020.3-SNAPSHOT
ideaVersion = 263.7307.44
riderVersion = 2026.3-EAP8-SNAPSHOT
riderVersion = 2026.3-RC1-SNAPSHOT

# Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl
# See https://jb.gg/intellij-platform-builds-list for available build versions.
pluginVerificationIdeVersion = 2026.3-EAP8-SNAPSHOT
pluginVerificationIdeVersion = 2026.3-RC1-SNAPSHOT

dotnetBuildConfiguration = Release

Expand Down
10 changes: 10 additions & 0 deletions gradle/plugin-verifier-ignored-problems.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Temporary workaround for Plugin Verifier 1.411 against Rider EAP8 (RD-263.7307.48)
// and RC1 (RD-263.7307.198).
// Aspire is bundled in Rider. When verifying its update, the verifier uses the bundled
// plugin's layout and resolves Terminal's main JAR without its frontend content module.
// intellij.terminal.frontend is declared in intellij.aspire.core.frontend.xml and its
// classes are present in the SDK. The same artifact with only its plugin ID changed
// to an unbundled ID verifies as Compatible, including all Terminal API calls.
// Remove this exception when the verifier handles updates of bundled plugins correctly.
// Scope it to 2.9.9 so the workaround must be reconsidered for the next plugin version.
me.rafaelldi.aspire:2.9.9:Package 'com\.intellij\.terminal\.frontend' is not found

This file was deleted.

Loading
Loading