Repository navigation
Defer JavaScript installer when the app uses WithExplicitStart - #21504
David Fowler (davidfowl) wants to merge 2 commits into
Conversation
When a JavaScript app is configured with WithExplicitStart() and its package manager installs dependencies (install: true), the generated installer resource started automatically during AppHost startup. The installer now inherits the app's explicit start, and starting the app starts the deferred installer once so the app's WaitForCompletion dependency is satisfied. Fixes #20186 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 21504Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 21504" |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
🟡 Changes recommended
A startup race can skip the installer and leave the parent blocked indefinitely, while the current tests bypass the runtime ordering needed to detect it.
2 open issues
What changed in this PR
This PR addresses #20186 by deferring JavaScript package installers alongside apps configured with WithExplicitStart(), then starting the installer when the parent app is explicitly started.
Changes:
- Evaluate the parent’s explicit-start configuration after builder calls finish.
- Start deferred installers on parent startup, with serialized state checks to avoid rerunning completed installers.
- Add tests for configuration order, parent startup and restart, and preservation of automatic-start and
install: falsebehavior.
| File | Description |
|---|---|
tests/Aspire.Hosting.JavaScript.Tests/PackageInstallationTests.cs |
Adds installer deferral and parent-start regression tests. |
src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs |
Propagates explicit-start behavior and starts deferred installers with their parent. |
Review effort: Balanced
Resources are initialized concurrently, so an explicit-start app can be started before its installer publishes its initial state. Wait for that state instead of skipping the installer, and add a deterministic regression test plus a real DCP/npm functional test covering deferral, start-on-demand and restart. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests selector10 / 100 PR test projects · 4 PR jobs, from 3 changed files. Selected PR test projects (10 / 100)
Selected PR jobs (4)
How these were chosen — grouped by what changed
🔧 📦 affected project 🧪 🧪 Job reasons
Selection computed for commit |
|
CI notes for head ceaf0f2 (run 41146506214). All JavaScript and Hosting jobs passed on attempt 1. The only failures were in jobs this change doesn't touch:
The tree-actions spec uses a single-file .NET AppHost and never reaches 🤖 Posted by AspireAgent (AI Agent) |


Description
WithNpm(install: true)(likewise Yarn, pnpm, Bun and Deno) creates a<app>-installerresource, and the app waits for it to complete. If the app also usesWithExplicitStart(), the installer ignored that. It still started automatically during AppHost startup and rannpm install, even though the app itself was deferred.Root cause
In
JavaScriptHostingExtensions(AddInstaller), the generatedJavaScriptInstallerResourceonly gets anExplicitStartupAnnotationforinstall: false. Nothing looks at the parent'sExplicitStartupAnnotation. Simply copying the annotation when the installer is created isn't enough, for two reasons:WithExplicitStart()is commonly called afterWithNpm(...), as in the issue's repro.WaitForCompletion(installer)never completes, because no orchestrator mechanism starts explicit-start dependencies.Fix
OnBeforeStartcallback, which runs after all builder calls, the installer is marked explicit-start when the parent is explicit-start and waits for the installer (install: true).BeforeResourceStartedEventsubscription on the parent starts the deferred installer through the public start command (ResourceCommandService), but only if the installer is stillNotStarted. Resources are initialized concurrently, so the handler first waits for the installer to publish its initial state rather than skipping it. A semaphore serializes the check and start across replicas. When the app is restarted, an installer that has already finished is not rerun. Builder-time subscriptions run before the orchestrator'sWaitForhandler, which is registered when the app starts, so the installer is started before the app begins waiting on it.install: falseand non-explicit apps keep their existing behavior.Repro / before-after evidence
New tests in
PackageInstallationTests:InstallerIsDeferredWhenParentUsesExplicitStart(explicitStartBeforePackageManager: True)Assert.True() Failure: installer has noExplicitStartupAnnotationInstallerIsDeferredWhenParentUsesExplicitStart(explicitStartBeforePackageManager: False)(issue's exact repro order)Assert.True() FailureStartingExplicitStartParentStartsDeferredInstallerOnceAssert.Equal() Failure(expected 1 installer start, got 0)StartingParentBeforeInstallerStateIsPublishedStillStartsInstaller(startup ordering race from review)Assert.False() Failure(also fails on this PR's first head, 6789435)ExplicitStartInstallerFunctionalTests.ExplicitStartAppDefersInstallerUntilAppIsStarted(real DCP + npm: the installer staysNotStarted, runs once when the app starts, and is not rerun on app restart)Expected: "NotStarted" Actual: "Starting"(the reported bug)InstallerIsNotDeferredWhenParentStartsAutomaticallyStartingParentDoesNotStartInstallerDisabledWithInstallFalseE2E evidence (real DCP + npm)
ExplicitStartInstallerFunctionalTests.ExplicitStartAppDefersInstallerUntilAppIsStartedruns a real AppHost under DCP with a real npm project. The test:NotStartedand that thepostinstallmarker file doesn't exist.ResourceCommandService. The installer exits 0, and the app reachesRunningand serves HTTP.On main this test fails with
Expected: "NotStarted" Actual: "Starting", which is the bug reported in #20186. With this PR it passes locally and in CI on head ceaf0f2: the Hosting.JavaScript jobs on ubuntu-latest and windows-latest each passed 426/426 with 0 skipped.Local results:
PackageInstallationTests+ExplicitStartInstallerFunctionalTests+NodeFunctionalTests: 33/33 passed. On main, 5 of these 31 targeted tests fail.Aspire.Hosting.JavaScript.Tests: 410 of 429 passed. The 19 local failures are all environmental and also unaffected by this run-mode-only change:/root/is scrubbed to{UserProfile}. These run in publish mode.Fixes #20186
Checklist
<remarks />and<code />elements on your triple slash comments?🤖 Posted by AspireAgent (AI Agent)