From b846726a9fe9f1a3a8b0c971615250830ff2f718 Mon Sep 17 00:00:00 2001 From: "aspire-repo-bot[bot]" <268009190+aspire-repo-bot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 07:00:10 +0000 Subject: [PATCH 1/2] Add docs for withProcessCommand and withProcessCommandFactory polyglot APIs Documents the TypeScript (polyglot) process-backed resource command APIs introduced in microsoft/aspire#16972: - withProcessCommand: static process spec registered at AppHost startup - withProcessCommandFactory: dynamic spec built from ExecuteCommandContext arguments at invocation time Adds fundamentals/process-commands.mdx and registers it in the sidebar between Custom HTTP commands and Custom resource URLs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/frontend/config/sidebar/docs.topics.ts | 8 + .../docs/fundamentals/process-commands.mdx | 184 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 src/frontend/src/content/docs/fundamentals/process-commands.mdx diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts index 02565d14d..9e038c2f3 100644 --- a/src/frontend/config/sidebar/docs.topics.ts +++ b/src/frontend/config/sidebar/docs.topics.ts @@ -1065,6 +1065,14 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = { ja: 'カスタム HTTP コマンド', }, }, + { + label: 'Custom process commands', + slug: 'fundamentals/process-commands', + translations: { + en: 'Custom process commands', + ja: 'カスタム プロセス コマンド', + }, + }, { label: 'Custom resource URLs', slug: 'fundamentals/custom-resource-urls', diff --git a/src/frontend/src/content/docs/fundamentals/process-commands.mdx b/src/frontend/src/content/docs/fundamentals/process-commands.mdx new file mode 100644 index 000000000..363a73acd --- /dev/null +++ b/src/frontend/src/content/docs/fundamentals/process-commands.mdx @@ -0,0 +1,184 @@ +--- +title: Custom process commands +description: Learn how to create custom process-backed resource commands in polyglot Aspire AppHosts. +--- + +import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; +import LearnMore from '@components/LearnMore.astro'; + +In a polyglot (TypeScript) Aspire AppHost, you can attach commands to any resource that launch a local process when triggered from the dashboard or the CLI. Two APIs are available: + +| API | When to use | +|-----|-------------| +| `withProcessCommand` | The process spec (executable, arguments, environment) is **fixed at registration time**. | +| `withProcessCommandFactory` | The process spec is **built at invocation time** from the arguments the user supplies when running the command. | + +Both APIs produce the same dashboard and CLI experience as a [custom resource command](/fundamentals/custom-resource-commands/), but the process is executed on the machine running the AppHost rather than inside the resource itself. + + + +## Static process commands (`withProcessCommand`) + +Use `withProcessCommand` when the executable path and all arguments are known when you register the command. The complete process specification is provided as the third argument. + +```typescript title="apphost.ts" +import { ExecuteCommandContext, InputType } from '@microsoft/aspire-sdk'; + +const builder = await appHostBuilder(); +const cache = await builder.addRedis('cache'); + +await cache.withProcessCommand( + 'run-migration', + 'Run migration', + { + executablePath: 'node', + arguments: ['./scripts/migrate.js'], + environmentVariables: { + NODE_ENV: 'development', + }, + standardInputContent: '', + maxOutputLineCount: 200, + commandOptions: { + description: 'Runs the database migration script.', + iconName: 'DatabaseArrowRight', + }, + }); +``` + +### `ProcessCommandExportOptions` reference + +| Property | Type | Description | +|----------|------|-------------| +| `executablePath` | `string` | Path to the executable. | +| `arguments` | `string[]` | Command-line arguments passed to the executable. | +| `workingDirectory` | `string` | Working directory for the process. Defaults to the AppHost project directory. | +| `environmentVariables` | `Record` | Additional environment variables injected into the process. | +| `inheritEnvironmentVariables` | `boolean` | Whether the process inherits the AppHost environment variables. Defaults to `true`. | +| `standardInputContent` | `string` | Content written to the process standard input stream before it starts. | +| `killEntireProcessTree` | `boolean` | Whether to kill the entire process tree when the command completes. | +| `maxOutputLineCount` | `number` | Maximum number of output lines displayed in the dashboard. | +| `displayImmediately` | `boolean` | Whether output is streamed to the dashboard immediately as it arrives. | +| `successExitCodes` | `number[]` | Exit codes that the command treats as success. Defaults to `[0]`. | +| `commandOptions` | `CommandOptions` | Display name, description, icon, and argument definitions for the command. | + +## Dynamic process commands (`withProcessCommandFactory`) + +Use `withProcessCommandFactory` when the process spec needs to depend on arguments the user provides when they invoke the command. A factory callback receives an `ExecuteCommandContext` and returns a `ProcessCommandSpecExportData` object. + +```typescript title="apphost.ts" +import { ExecuteCommandContext, InputType } from '@microsoft/aspire-sdk'; + +const builder = await appHostBuilder(); +const cache = await builder.addRedis('cache'); + +await cache.withProcessCommandFactory( + 'run-migration-for-tenant', + 'Run migration for tenant', + async (context: ExecuteCommandContext) => { + const args = await context.arguments(); + const tenant = await args.requiredValue('tenant'); + + return { + executablePath: 'node', + arguments: ['./scripts/migrate.js', '--tenant', tenant], + environmentVariables: { + NODE_ENV: 'development', + }, + standardInputContent: '', + }; + }, + { + commandOptions: { + description: 'Runs the database migration script for the specified tenant.', + iconName: 'DatabaseArrowRight', + arguments: [ + { + name: 'tenant', + label: 'Tenant ID', + inputType: InputType.Text, + required: true, + }, + ], + }, + maxOutputLineCount: 200, + }); +``` + +When the user triggers the command from the dashboard or CLI, they are prompted for the `tenant` argument. The factory callback then builds the process spec incorporating that value before the process is launched. + +### Invoke from the CLI + +The `aspire resource` command can trigger a process command and pass arguments directly: + +```bash +aspire resource run-migration-for-tenant -- --tenant acme +``` + +### `ProcessCommandSpecExportData` reference + +The object returned by the factory callback. + +| Property | Type | Description | +|----------|------|-------------| +| `executablePath` | `string` | Path to the executable. | +| `arguments` | `string[]` | Command-line arguments passed to the executable. | +| `workingDirectory` | `string` | Working directory for the process. | +| `environmentVariables` | `Record` | Additional environment variables injected into the process. | +| `inheritEnvironmentVariables` | `boolean` | Whether the process inherits the AppHost environment variables. | +| `standardInputContent` | `string` | Content written to the process standard input stream. | +| `killEntireProcessTree` | `boolean` | Whether to kill the entire process tree on command completion. | + +### `ProcessCommandResultExportOptions` reference + +The optional fourth argument to `withProcessCommandFactory`. + +| Property | Type | Description | +|----------|------|-------------| +| `commandOptions` | `CommandOptions` | Display name, description, icon, and argument definitions for the command. | +| `maxOutputLineCount` | `number` | Maximum number of output lines displayed in the dashboard. | +| `displayImmediately` | `boolean` | Whether output is streamed to the dashboard immediately. | +| `successExitCodes` | `number[]` | Exit codes treated as success. Defaults to `[0]`. | + +## C# equivalent + +In a C# AppHost, use `WithProcessCommand` and `WithProcessCommandFactory` on `IResourceBuilder`: + +```csharp title="AppHost.cs" +var cache = builder.AddRedis("cache"); + +// Static spec: +cache.WithProcessCommand( + commandName: "run-migration", + displayName: "Run migration", + options: new ProcessCommandOptions + { + ExecutablePath = "node", + Arguments = ["./scripts/migrate.js"], + }); + +// Factory (dynamic spec): +cache.WithProcessCommandFactory( + commandName: "run-migration-for-tenant", + displayName: "Run migration for tenant", + createProcessSpec: context => + { + var tenant = context.Arguments.GetRequiredValue("tenant"); + return Task.FromResult(new ProcessCommandSpec + { + ExecutablePath = "node", + Arguments = ["./scripts/migrate.js", "--tenant", tenant], + }); + }, + options: new ProcessCommandOptions { /* ... */ }); +``` + + + +- [Custom resource commands](/fundamentals/custom-resource-commands/) +- [Custom HTTP commands](/fundamentals/http-commands/) +- [`aspire resource` CLI reference](/reference/cli/commands/aspire-resource/) + + From 29c96574207ac1f3715e02dbde113ebd3cde5274 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 12 May 2026 01:05:51 -0700 Subject: [PATCH 2/2] Consolidate process command docs Move the TypeScript process command factory example into the existing custom resource commands page and remove the duplicate standalone page/navigation entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/frontend/config/sidebar/docs.topics.ts | 8 - .../fundamentals/custom-resource-commands.mdx | 113 ++++++----- .../docs/fundamentals/process-commands.mdx | 184 ------------------ 3 files changed, 66 insertions(+), 239 deletions(-) delete mode 100644 src/frontend/src/content/docs/fundamentals/process-commands.mdx diff --git a/src/frontend/config/sidebar/docs.topics.ts b/src/frontend/config/sidebar/docs.topics.ts index 9e038c2f3..02565d14d 100644 --- a/src/frontend/config/sidebar/docs.topics.ts +++ b/src/frontend/config/sidebar/docs.topics.ts @@ -1065,14 +1065,6 @@ export const docsTopics: StarlightSidebarTopicsUserConfig = { ja: 'カスタム HTTP コマンド', }, }, - { - label: 'Custom process commands', - slug: 'fundamentals/process-commands', - translations: { - en: 'Custom process commands', - ja: 'カスタム プロセス コマンド', - }, - }, { label: 'Custom resource URLs', slug: 'fundamentals/custom-resource-urls', diff --git a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx index 6539d4679..76c3abfa8 100644 --- a/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx +++ b/src/frontend/src/content/docs/fundamentals/custom-resource-commands.mdx @@ -1243,10 +1243,14 @@ When the command runs in the dashboard or CLI, `dotnet --version` (C#) or `node ### Dynamic process command -When the command arguments depend on runtime context — for example, a dataset name supplied by the user through the dashboard's argument dialog — use the callback overload (C# only) to build the `ProcessCommandSpec` dynamically: +When the command arguments depend on runtime context — for example, a dataset name supplied by the user through the dashboard's argument dialog — build the process specification from the command execution context: + + + ```csharp title="AppHost.cs" #pragma warning disable ASPIREPROCESSCOMMAND001 +#pragma warning disable ASPIREINTERACTION001 using Aspire.Hosting.ApplicationModel; @@ -1268,16 +1272,73 @@ builder.AddRedis("cache") ], EnvironmentVariables = { ["ConnectionStrings__db"] = "Host=localhost;Database=db" }, }, - options: new ProcessCommandOptions { MaxOutputLineCount = 20 }); + options: new ProcessCommandOptions + { + MaxOutputLineCount = 20, + Arguments = + [ + new InteractionInput { Name = "dataset", Label = "Dataset", InputType = InputType.Text, Required = true }, + ], + }); builder.Build().Run(); ``` -The `context` parameter is an `ExecuteCommandContext`, so you can read user-supplied arguments via `context.Arguments.GetString(name)`, resolve services, and check the cancellation token. + + + +```typescript title="apphost.ts" +import { + createBuilder, + type ExecuteCommandContext, + InputType, +} from './.modules/aspire.js'; + +const builder = await createBuilder(); + +const cache = await builder.addRedis('cache'); + +await cache.withProcessCommandFactory( + 'seed-data', + 'Seed data', + async (context: ExecuteCommandContext) => { + const args = await context.arguments(); + const dataset = await args.requiredValue('dataset'); + + return { + executablePath: 'node', + arguments: ['./scripts/seed-data.js', '--dataset', dataset], + environmentVariables: { + NODE_ENV: 'development', + }, + }; + }, + { + commandOptions: { + arguments: [ + { + name: 'dataset', + label: 'Dataset', + inputType: InputType.Text, + required: true, + }, + ], + }, + maxOutputLineCount: 20, + } +); + +await builder.build().run(); +``` + + + + +In C#, the callback overload receives an `ExecuteCommandContext`. In TypeScript, `withProcessCommandFactory` receives the same execution context and returns the process specification. The dashboard renders the configured arguments as a prompt dialog before starting the process, and the entered values are available through the command context. ### `ProcessCommandSpec` properties -The following configuration options are available for the process to run. In C#, these map to `ProcessCommandSpec` properties. In TypeScript, they are provided as fields in the options object passed to `withProcessCommand`. +The following configuration options are available for the process to run. In C#, these map to `ProcessCommandSpec` properties. In TypeScript, provide them as fields in the options object passed to `withProcessCommand`, or return them from the `withProcessCommandFactory` callback. - **Executable path** — the path to the process to launch. Short names (no directory separator) are resolved from the AppHost's `PATH`. - **Arguments** — a list of arguments passed to the process. Each entry is treated as a separate argument, so no shell quoting or escaping is needed. @@ -1289,7 +1350,7 @@ The following configuration options are available for the process to run. In C#, ### `ProcessCommandOptions` -The following options control how `WithProcessCommand` handles the process result. In C#, they are set on `ProcessCommandOptions`. In TypeScript, they are fields in the same options object as the process configuration above. +The following options control how `WithProcessCommand` handles the process result. In C#, set them on `ProcessCommandOptions`. In TypeScript, provide them in the same options object as the process configuration for `withProcessCommand`, or in the fourth argument to `withProcessCommandFactory`. - **Max output line count** — the maximum number of combined stdout/stderr lines captured and returned as the command result. Defaults to 50. Lines beyond this limit are silently discarded (oldest lines first). - **Display immediately** — when `true` (the default), the captured output is automatically shown in the dashboard as soon as the command finishes. @@ -1303,45 +1364,3 @@ The following options control how `WithProcessCommand` handles the process resul 2. Otherwise, the AppHost process's `PATH` is searched for the named executable. On Windows, `PATHEXT` extensions are also tried. This means you can reference tools such as `dotnet`, `node`, or `docker` by short name as long as they are on the AppHost's PATH when the app starts. - -### Combining with command arguments - -`WithProcessCommand` uses `WithCommand` internally. In C# dynamic process commands, you can combine it with the [Command arguments](#command-arguments) feature to prompt the user for input before the process runs: - -```csharp title="AppHost.cs" -#pragma warning disable ASPIREPROCESSCOMMAND001 -#pragma warning disable ASPIREINTERACTION001 - -using Aspire.Hosting.ApplicationModel; - -var builder = DistributedApplication.CreateBuilder(args); - -builder.AddPostgres("postgres") - .WithProcessCommand( - name: "run-migration", - displayName: "Run migration", - createProcessSpec: context => new ProcessCommandSpec("dotnet") - { - Arguments = - [ - "run", - "--project", - "tools/Migrations", - "--", - "--target", - context.Arguments.GetString("target") ?? "latest", - ], - }, - options: new ProcessCommandOptions - { - MaxOutputLineCount = 50, - Arguments = - [ - new InteractionInput { Name = "target", Label = "Target migration", InputType = InputType.Text }, - ], - }); - -builder.Build().Run(); -``` - -The dashboard renders the `Arguments` as a prompt dialog before starting the process, and the entered values are accessible via `context.Arguments`. diff --git a/src/frontend/src/content/docs/fundamentals/process-commands.mdx b/src/frontend/src/content/docs/fundamentals/process-commands.mdx deleted file mode 100644 index 363a73acd..000000000 --- a/src/frontend/src/content/docs/fundamentals/process-commands.mdx +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Custom process commands -description: Learn how to create custom process-backed resource commands in polyglot Aspire AppHosts. ---- - -import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; -import LearnMore from '@components/LearnMore.astro'; - -In a polyglot (TypeScript) Aspire AppHost, you can attach commands to any resource that launch a local process when triggered from the dashboard or the CLI. Two APIs are available: - -| API | When to use | -|-----|-------------| -| `withProcessCommand` | The process spec (executable, arguments, environment) is **fixed at registration time**. | -| `withProcessCommandFactory` | The process spec is **built at invocation time** from the arguments the user supplies when running the command. | - -Both APIs produce the same dashboard and CLI experience as a [custom resource command](/fundamentals/custom-resource-commands/), but the process is executed on the machine running the AppHost rather than inside the resource itself. - - - -## Static process commands (`withProcessCommand`) - -Use `withProcessCommand` when the executable path and all arguments are known when you register the command. The complete process specification is provided as the third argument. - -```typescript title="apphost.ts" -import { ExecuteCommandContext, InputType } from '@microsoft/aspire-sdk'; - -const builder = await appHostBuilder(); -const cache = await builder.addRedis('cache'); - -await cache.withProcessCommand( - 'run-migration', - 'Run migration', - { - executablePath: 'node', - arguments: ['./scripts/migrate.js'], - environmentVariables: { - NODE_ENV: 'development', - }, - standardInputContent: '', - maxOutputLineCount: 200, - commandOptions: { - description: 'Runs the database migration script.', - iconName: 'DatabaseArrowRight', - }, - }); -``` - -### `ProcessCommandExportOptions` reference - -| Property | Type | Description | -|----------|------|-------------| -| `executablePath` | `string` | Path to the executable. | -| `arguments` | `string[]` | Command-line arguments passed to the executable. | -| `workingDirectory` | `string` | Working directory for the process. Defaults to the AppHost project directory. | -| `environmentVariables` | `Record` | Additional environment variables injected into the process. | -| `inheritEnvironmentVariables` | `boolean` | Whether the process inherits the AppHost environment variables. Defaults to `true`. | -| `standardInputContent` | `string` | Content written to the process standard input stream before it starts. | -| `killEntireProcessTree` | `boolean` | Whether to kill the entire process tree when the command completes. | -| `maxOutputLineCount` | `number` | Maximum number of output lines displayed in the dashboard. | -| `displayImmediately` | `boolean` | Whether output is streamed to the dashboard immediately as it arrives. | -| `successExitCodes` | `number[]` | Exit codes that the command treats as success. Defaults to `[0]`. | -| `commandOptions` | `CommandOptions` | Display name, description, icon, and argument definitions for the command. | - -## Dynamic process commands (`withProcessCommandFactory`) - -Use `withProcessCommandFactory` when the process spec needs to depend on arguments the user provides when they invoke the command. A factory callback receives an `ExecuteCommandContext` and returns a `ProcessCommandSpecExportData` object. - -```typescript title="apphost.ts" -import { ExecuteCommandContext, InputType } from '@microsoft/aspire-sdk'; - -const builder = await appHostBuilder(); -const cache = await builder.addRedis('cache'); - -await cache.withProcessCommandFactory( - 'run-migration-for-tenant', - 'Run migration for tenant', - async (context: ExecuteCommandContext) => { - const args = await context.arguments(); - const tenant = await args.requiredValue('tenant'); - - return { - executablePath: 'node', - arguments: ['./scripts/migrate.js', '--tenant', tenant], - environmentVariables: { - NODE_ENV: 'development', - }, - standardInputContent: '', - }; - }, - { - commandOptions: { - description: 'Runs the database migration script for the specified tenant.', - iconName: 'DatabaseArrowRight', - arguments: [ - { - name: 'tenant', - label: 'Tenant ID', - inputType: InputType.Text, - required: true, - }, - ], - }, - maxOutputLineCount: 200, - }); -``` - -When the user triggers the command from the dashboard or CLI, they are prompted for the `tenant` argument. The factory callback then builds the process spec incorporating that value before the process is launched. - -### Invoke from the CLI - -The `aspire resource` command can trigger a process command and pass arguments directly: - -```bash -aspire resource run-migration-for-tenant -- --tenant acme -``` - -### `ProcessCommandSpecExportData` reference - -The object returned by the factory callback. - -| Property | Type | Description | -|----------|------|-------------| -| `executablePath` | `string` | Path to the executable. | -| `arguments` | `string[]` | Command-line arguments passed to the executable. | -| `workingDirectory` | `string` | Working directory for the process. | -| `environmentVariables` | `Record` | Additional environment variables injected into the process. | -| `inheritEnvironmentVariables` | `boolean` | Whether the process inherits the AppHost environment variables. | -| `standardInputContent` | `string` | Content written to the process standard input stream. | -| `killEntireProcessTree` | `boolean` | Whether to kill the entire process tree on command completion. | - -### `ProcessCommandResultExportOptions` reference - -The optional fourth argument to `withProcessCommandFactory`. - -| Property | Type | Description | -|----------|------|-------------| -| `commandOptions` | `CommandOptions` | Display name, description, icon, and argument definitions for the command. | -| `maxOutputLineCount` | `number` | Maximum number of output lines displayed in the dashboard. | -| `displayImmediately` | `boolean` | Whether output is streamed to the dashboard immediately. | -| `successExitCodes` | `number[]` | Exit codes treated as success. Defaults to `[0]`. | - -## C# equivalent - -In a C# AppHost, use `WithProcessCommand` and `WithProcessCommandFactory` on `IResourceBuilder`: - -```csharp title="AppHost.cs" -var cache = builder.AddRedis("cache"); - -// Static spec: -cache.WithProcessCommand( - commandName: "run-migration", - displayName: "Run migration", - options: new ProcessCommandOptions - { - ExecutablePath = "node", - Arguments = ["./scripts/migrate.js"], - }); - -// Factory (dynamic spec): -cache.WithProcessCommandFactory( - commandName: "run-migration-for-tenant", - displayName: "Run migration for tenant", - createProcessSpec: context => - { - var tenant = context.Arguments.GetRequiredValue("tenant"); - return Task.FromResult(new ProcessCommandSpec - { - ExecutablePath = "node", - Arguments = ["./scripts/migrate.js", "--tenant", tenant], - }); - }, - options: new ProcessCommandOptions { /* ... */ }); -``` - - - -- [Custom resource commands](/fundamentals/custom-resource-commands/) -- [Custom HTTP commands](/fundamentals/http-commands/) -- [`aspire resource` CLI reference](/reference/cli/commands/aspire-resource/) - -