Skip to content

Validate TypeScript AppHosts before startup#16689

Open
sebastienros wants to merge 3 commits intomainfrom
sebastienros/sebros-fix-ts-apphost-compile-errors
Open

Validate TypeScript AppHosts before startup#16689
sebastienros wants to merge 3 commits intomainfrom
sebastienros/sebros-fix-ts-apphost-compile-errors

Conversation

@sebastienros
Copy link
Copy Markdown
Contributor

@sebastienros sebastienros commented May 2, 2026

Description

TypeScript AppHosts currently run through tsx, which transpiles without type validation. This means aspire run and aspire start can launch the dashboard even when the AppHost has TypeScript compile errors, leaving users with a partially started and confusing app.

This adds a PreExecute runtime hook and uses it for TypeScript AppHosts to run tsc --noEmit -p tsconfig.apphost.json before normal run and publish execution. Watch mode embeds the same validation into the nodemon restart command so the watcher can still start when there are initial type errors and recover as the user edits.

Measured startup impact on a new React TypeScript AppHost template: the added tsc --noEmit -p tsconfig.apphost.json validation costs about 0.8s on warm runs, with a cold first run around 1.0s.

Example compile error:

const shouldBeNumber: number = "not a number";

With this change, aspire run stops before the AppHost starts and displays the tsc diagnostic:

apphost.ts(22,7): error TS2322: Type 'string' is not assignable to type 'number'.

Fixes #16645

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Add a pre-execute runtime hook and use it to run TypeScript type validation before starting or publishing TypeScript AppHosts. Watch mode validates on every restart so startup is not blocked by existing errors while editing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 2, 2026 00:18
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 2, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16689

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 16689"

@sebastienros sebastienros requested a review from eerhardt May 2, 2026 00:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new RuntimeSpec.PreExecute hook to run validation commands before executing guest-language AppHosts, and uses it to type-check TypeScript AppHosts with tsc --noEmit so aspire run/start fails fast instead of partially starting (e.g., launching the dashboard while the AppHost has TS compile errors).

Changes:

  • Introduces RuntimeSpec.PreExecute (public API) and updates GuestRuntime to run pre-execution commands before Execute/PublishExecute.
  • Updates TypeScript runtime specs (and toolchain overrides for bun/yarn/pnpm) to run tsc --noEmit -p tsconfig.apphost.json before normal execution; watch commands embed the same validation in the watcher restart command.
  • Adds/updates unit tests to validate PreExecute behavior and the generated command lines for TypeScript runtimes.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs Verifies TypeScript runtime spec now includes pre-execution tsc validation and updated watch exec command.
tests/Aspire.Cli.Tests/Projects/TypeScriptAppHostToolchainResolverTests.cs Extends toolchain resolver tests to assert PreExecute and updated watch command strings; adds pnpm coverage.
tests/Aspire.Cli.Tests/Projects/GuestRuntimeTests.cs Adds tests ensuring PreExecute runs before execute/publish and is skipped when watch spec is used; improves recording launcher.
src/Aspire.TypeSystem/RuntimeSpec.cs Adds new public PreExecute property to runtime spec model.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs Emits PreExecute (npx ... tsc --noEmit) and embeds type-check in the watch nodemon --exec command.
src/Aspire.Cli/Projects/TypeScriptAppHostToolchainResolver.cs Adds toolchain-specific PreExecute + updates watch commands (notably bun now uses bun x nodemon ...).
src/Aspire.Cli/Projects/GuestRuntime.cs Executes PreExecute before Execute/PublishExecute, skipping it when using WatchExecute; factors env merge into a helper.

Comment thread src/Aspire.TypeSystem/RuntimeSpec.cs Outdated
Comment thread src/Aspire.Cli/Projects/TypeScriptAppHostToolchainResolver.cs Outdated
Comment thread src/Aspire.Cli/Projects/TypeScriptAppHostToolchainResolver.cs Outdated
sebastienros and others added 2 commits May 1, 2026 17:34
Clarify the PreExecute contract and make Bun typecheck/watch commands use project-local binaries via bun run instead of bun x.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use Yarn run for project-local TypeScript tools so arguments are forwarded correctly, and update the TypeScript publish E2E fixture to use the typed dashboard options shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros
Copy link
Copy Markdown
Contributor Author

Since this adds around 1s (on an M4) for each run or change (watch) ... should we try tsgo ?

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 2, 2026

🎬 CLI E2E Test Recordings — 75 recordings uploaded (commit 904293a)

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DeployK8sBasicApiService ▶️ View Recording
DeployK8sWithGarnet ▶️ View Recording
DeployK8sWithMongoDB ▶️ View Recording
DeployK8sWithMySql ▶️ View Recording
DeployK8sWithPostgres ▶️ View Recording
DeployK8sWithRabbitMQ ▶️ View Recording
DeployK8sWithRedis ▶️ View Recording
DeployK8sWithSqlServer ▶️ View Recording
DeployK8sWithValkey ▶️ View Recording
DeployTypeScriptAppToKubernetes ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View Recording
DoListStepsShowsPipelineSteps ▶️ View Recording
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InteractiveCSharpInitCreatesExpectedFiles ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LatestCliCanStartStableChannelAppHost ▶️ View Recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterAppCore ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ View Recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #25239644372

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented May 3, 2026

Does this need to be done up front? Could we instead try to run the AppHost, see the failure, and then run this to give a good error message. That makes it zero cost for good files, which would be the vast majority or the time.

@JamesNK
Copy link
Copy Markdown
Member

JamesNK commented May 3, 2026

Alternatively, cache the result, and only run the check when the apphost changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aspire run of a TS apphost runs even if there are compile errors

3 participants