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/)
-
-