| File: AppHost.cs | Web Access |
| Project: src\playground\Terminals\Terminals.AppHost\Terminals.AppHost.csproj (Terminals.AppHost) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // WithTerminal() is currently experimental (ASPIRETERMINAL001). Suppressed here // because this playground project intentionally exercises the experimental API. #pragma warning disable ASPIRETERMINAL001 using Terminals.AppHost; var builder = DistributedApplication.CreateBuilder(args); builder.AddPostgres("postgres").WithRepl(); builder.AddRedis("redis").WithRepl(); builder.AddValkey("valkey").WithRepl(); builder.AddMongoDB("mongo").WithRepl(); builder.AddMySql("mysql").WithRepl(); builder.AddSqlServer("sqlserver").WithRepl(); // A multi-replica project that calls `WithTerminal()` so each replica gets its // own pseudo-terminal and the dashboard can attach to any of them via // `/api/terminal?resource=repl&replica=<i>`. The replica index is forwarded as // an environment variable so the REPL can stamp it on its banner. builder.AddProject<Projects.Terminals_Repl>("repl") .WithReplicas(2) .WithEnvironment("ASPIRE_RESOURCE_NAME", "repl") .WithTerminal(options => { options.Columns = 120; options.Rows = 32; options.ShowTerminalHost = true; }) // Opens a shell owned by the AppHost rather than orchestrated by Aspire. It has nothing to do with `repl`; // commands just need a host resource to hang off. .WithAppHostShellCommand() // Drives an interactive console program from AppHost code: the terminal is shown in a dialog, but the guesses // are typed by the AppHost, which reads each reply back off the screen and bisects until it wins. .WithNumberGuessCommand() // Automates a terminal the AppHost does NOT own: it joins `shell`'s existing terminal as an extra viewer and // types into it, which is what shelling into a resource from AppHost code looks like. .WithAutomateResourceTerminalCommand("shell"); // Long-running container that the "Shell into container" interaction command execs into. Aspire is not orchestrating // the exec — the AppHost shells out to `docker exec` — so the container needs a stable, predictable name. var shellbox = builder.AddContainer("shellbox", "alpine") .WithContainerName("terminals-playground-shellbox") .WithArgs("sleep", "infinity") .WithContainerShellCommand() // Same shell, but delivered as a tab in the dashboard's terminal dock (backtick shortcut) rather than a modal dialog. .WithDockShellCommand(); // Latest Node.js image, kept alive so the "Node REPL" interaction command can exec into it. The Node REPL is a // readline app, so it exercises cursor addressing, history, and tab completion across the tunnel in a way a plain // shell prompt does not. `sleep infinity` replaces the image's default CMD ("node") — an interactive REPL with no TTY // attached would exit immediately. The entrypoint (docker-entrypoint.sh) is left alone so PATH is set up normally. builder.AddContainer("noderepl", "node", "latest") .WithContainerName("terminals-playground-noderepl") .WithArgs("sleep", "infinity") .WithNodeReplCommand(); if (OperatingSystem.IsWindows()) { // Local PowerShell launched directly by Hex1b, providing a Docker- and DCP-independent PTY debugging path. shellbox .WithPowerShellDockCommand() .WithCommandPromptDockCommand(); // Single-replica executable wrapping cmd.exe to demonstrate that // WithTerminal() also works for arbitrary executables, not just projects. builder.AddExecutable("shell", "cmd.exe", ".") .WithTerminal(options => options.ShowTerminalHost = true); // A/B control resource: same cmd.exe but WITHOUT WithTerminal(). Used to // bisect whether the "Stop kills the dashboard" symptom is specific to // PTY-attached resources or applies to any DCP-managed Windows process. // Without a PTY, cmd.exe with redirected stdin would exit immediately, so // we wrap it in `/c ping -t 127.0.0.1` (continuous ping to localhost // until killed) so the resource stays in the Running state long enough // for "Stop" to be clicked from the dashboard. builder.AddExecutable("shell2", "cmd.exe", ".", "/c", "ping -t 127.0.0.1 > NUL"); // Container resource exercising the container-side WithTerminal() path. // Launches a long-running Node.js LTS image with an interactive bash so // the terminal attaches to a shell where `npx`, `node`, and friends are // available. We override the image CMD (not the entrypoint!) so the // image's docker-entrypoint.sh keeps running and exec's bash for us; // overriding the entrypoint instead would leave the image's CMD // ("node") appended after bash and immediately exit. builder.AddContainer("nodebox", "node", "lts") .WithArgs("bash", "-l") .WithTerminal(options => options.ShowTerminalHost = true); } else { // Unix counterpart of `shell`: an interactive login bash so the terminal // exercises the executable-with-PTY path on Linux/macOS. `-i` keeps bash // in interactive mode (prompt, line editing, job control) and `-l` makes // it source the user's login profile so `PATH`, aliases etc. behave the // same as a normal terminal session. builder.AddExecutable("shell", "/bin/bash", ".", "-i", "-l") .WithTerminal(options => options.ShowTerminalHost = true); } builder.AddDockerfile("notcurses", "../Terminals.Notcurses") .WithTerminal(options => { // The demo recommends at least 80x45 for its graphics and Unicode workloads: // https://manpages.ubuntu.com/manpages/noble/man1/notcurses-demo.1.html options.Columns = 120; options.Rows = 45; options.ShowTerminalHost = true; }); #if !SKIP_DASHBOARD_REFERENCE // This project is only added in playground projects to support development/debugging // of the dashboard. It is not required in end developer code. Comment out this code // or build with `/p:SkipDashboardReference=true`, to test end developer // dashboard launch experience, Refer to Directory.Build.props for the path to // the dashboard binary (defaults to the Aspire.Dashboard bin output in the // artifacts dir). builder.AddProject<Projects.Aspire_Dashboard>(KnownResourceNames.AspireDashboard); #endif builder.Build().Run();