Skip to content

[release/13.2] Fix legacy settings migration path adjustment for appHostPath#15352

Merged
joperezr merged 9 commits intorelease/13.2from
fix/migration-path-adjustment
Mar 19, 2026
Merged

[release/13.2] Fix legacy settings migration path adjustment for appHostPath#15352
joperezr merged 9 commits intorelease/13.2from
fix/migration-path-adjustment

Conversation

@mitchdenny
Copy link
Member

Description

Fix legacy .aspire/settings.jsonaspire.config.json migration producing incorrect relative paths for the appHostPath.

Problem: When migrating from the legacy .aspire/settings.json format to the new aspire.config.json, the appHostPath was copied verbatim by FromLegacy(). However, the legacy format stores paths relative to the .aspire/ directory (e.g., ../src/apphost.ts), while the new format stores paths relative to the config file's own directory (the project root). This caused migrated paths to resolve incorrectly — for example, ../src/apphost.ts (valid from .aspire/) becomes invalid from the project root because it resolves above the repo.

Fix: After FromLegacy() migration in LoadOrCreate(), the path is re-based: resolved against the legacy .aspire/ directory, then made relative to the config directory. So ../src/apphost.ts correctly becomes src/apphost.ts.

Fixes # (issue)

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
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

When migrating from .aspire/settings.json to aspire.config.json, the
appHostPath was copied verbatim. But .aspire/settings.json stores paths
relative to the .aspire/ directory (e.g., '../src/apphost.ts'), while
aspire.config.json stores paths relative to its own directory (the
project root). This caused the migrated path to resolve incorrectly.

The fix re-bases the appHostPath during migration: resolves it against
the legacy .aspire/ directory, then makes it relative to the config
directory. For example, '../src/apphost.ts' becomes 'src/apphost.ts'.

Includes unit tests for the path adjustment and an E2E test that
verifies the full migration scenario with a TypeScript apphost in a
subdirectory.

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

github-actions bot commented Mar 18, 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/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15352

Or

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

Copy link
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

Fixes the legacy .aspire/settings.jsonaspire.config.json migration so appHostPath is re-based from “relative to .aspire/” to “relative to the project root”, and adds tests to prevent regressions.

Changes:

  • Re-base migrated appHostPath during AspireConfigFile.LoadOrCreate() legacy migration.
  • Add unit tests covering relative, root, already-.aspire-local, saved-config, and absolute-path cases.
  • Add an end-to-end Docker test verifying the migration produces the corrected path in aspire.config.json.

Reviewed changes

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

File Description
src/Aspire.Cli/Configuration/AspireConfigFile.cs Adjusts legacy-migrated appHostPath to be relative to the config directory.
tests/Aspire.Cli.Tests/Configuration/AspireConfigFileTests.cs Adds unit coverage for the re-basing behavior and persistence.
tests/Aspire.Cli.EndToEnd.Tests/LocalConfigMigrationTests.cs Adds an E2E regression test ensuring migration works in a real CLI run flow.

You can also share your feedback on Copilot code review. Take the survey.

The terminal buffer accumulates all output, so the earlier echo of
legacy settings.json content (containing '../src/apphost.ts') would
cause false matches when verifying the migrated aspire.config.json.

Use 'grep' with the exact JSON key-value pattern to isolate the check,
and rely on the host-side file verification as the primary assertion.

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

The transient CI rerun workflow requested reruns for the following jobs after analyzing the failed attempt.
GitHub's job rerun API also reruns dependent jobs, so the retry is being tracked in the rerun attempt.
The job links below point to the failed attempt that matched the retry-safe transient failure rules.

  • Tests / Infrastructure / Infrastructure (ubuntu-latest) - Failed step 'Build test project' will be retried because the job log shows a likely transient infrastructure network failure. Matched pattern: /Unable to load the service index for source https://(?:pkgs.dev.azure.com/dnceng|dnceng.pkgs.visualstudio.com)/public/_packaging//i.

@mitchdenny mitchdenny marked this pull request as draft March 18, 2026 11:39
Mitch Denny and others added 2 commits March 18, 2026 22:39
The previous test moved apphost.ts to src/ but left .modules/ in the
workspace root, breaking TS imports. aspire run couldn't start, so the
migration never triggered and aspire.config.json was never created.

Changes:
- Keep apphost.ts at workspace root (project stays valid)
- Test re-basing with '../apphost.ts' -> 'apphost.ts' instead
  (same code path as '../src/apphost.ts' -> 'src/apphost.ts')
- Wait for 'Press CTRL+C' or 'Apphost failed' instead of generic
  'ERR:' which could false-match shell prompt patterns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previous approach failed because:
1. aspire run error messages varied by failure mode
2. Terminal buffer pattern matching was fragile across exit codes

Now polls the host-side filesystem via bind mount for aspire.config.json,
which is created during apphost discovery before the actual run attempt.
This works regardless of whether aspire run succeeds or fails.

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

The transient CI rerun workflow requested reruns for the following jobs after analyzing the failed attempt.
GitHub's job rerun API also reruns dependent jobs, so the retry is being tracked in the rerun attempt.
The job links below point to the failed attempt that matched the retry-safe transient failure rules.

Mitch Denny and others added 2 commits March 18, 2026 23:24
- Normalize backslash separators to OS-native before Path operations,
  then always output forward slashes (matching storage convention)
- Guard against empty/whitespace paths to avoid converting '' to '.'
- Use { Length: > 0 } pattern match instead of separate null check
- Rename misleading test to clarify .aspire/-relative semantics
- Add tests: backslash normalization, output always uses '/',
  deeply nested paths, empty path preservation, null path handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous test used 'apphost.ts' without '../' prefix, which would
mean the apphost lives inside .aspire/ — an impossible real-world
scenario. Replace with a realistic .csproj-in-subdirectory case:
'../MyApp.AppHost/MyApp.AppHost.csproj' -> 'MyApp.AppHost/MyApp.AppHost.csproj'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@joperezr joperezr added the Servicing-approved Approved for servicing release label Mar 18, 2026
@joperezr
Copy link
Member

I know you are still working on this but marking as servicing approved as it will be important for existing users. Let me know when it's ready for review and ready to go.

@joperezr joperezr added this to the 13.2 milestone Mar 18, 2026
@mitchdenny mitchdenny marked this pull request as ready for review March 18, 2026 22:35
Replace manual slash normalization + GetFullPath with the existing
PathNormalizer.NormalizePathForCurrentPlatform() utility, which does
the same thing in one call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mitch Denny and others added 2 commits March 19, 2026 10:32
Address review feedback:
- Extract PathNormalizer.NormalizePathForStorage() for the common
  backslash-to-forward-slash normalization used when persisting paths.
- Add tests for ./path, bare relative path, Unix rooted, and Windows
  rooted legacy migration scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid test paths that imply apphost lives under .aspire/ since that
never happens in practice.

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

Re-running the failed jobs in the CI workflow for this pull request because 2 jobs were identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

  • Tests / Hosting.Garnet / Hosting.Garnet (ubuntu-latest) - Failed step 'Build test project' will be retried because the job log shows a likely transient infrastructure network failure. Matched pattern: /Unable to load the service index for source https:\/\/(?:pkgs\.dev\.azure\.com\/dnceng|dnceng\.pkgs\.visualstudio\.com)\/public\/_packaging\//i.
  • Tests / Hosting.GitHub.Models / Hosting.GitHub.Models (windows-latest) - Failed step 'Build test project' will be retried because the job log shows a likely transient infrastructure network failure. Matched pattern: /Unable to load the service index for source https:\/\/(?:pkgs\.dev\.azure\.com\/dnceng|dnceng\.pkgs\.visualstudio\.com)\/public\/_packaging\//i.

@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 53 recordings uploaded (commit 1fb4b61)

View recordings
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
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ 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
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23272314953

@mitchdenny mitchdenny changed the title Fix legacy settings migration path adjustment for appHostPath [release/13.2] Fix legacy settings migration path adjustment for appHostPath Mar 19, 2026
@joperezr joperezr merged commit 12532e0 into release/13.2 Mar 19, 2026
505 of 509 checks passed
@joperezr joperezr deleted the fix/migration-path-adjustment branch March 19, 2026 04:07
@dotnet-policy-service dotnet-policy-service bot removed this from the 13.2 milestone Mar 19, 2026
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 19, 2026
Copilot AI pushed a commit that referenced this pull request Mar 19, 2026
…ostPath (#15352)

* Fix legacy settings migration path adjustment

When migrating from .aspire/settings.json to aspire.config.json, the
appHostPath was copied verbatim. But .aspire/settings.json stores paths
relative to the .aspire/ directory (e.g., '../src/apphost.ts'), while
aspire.config.json stores paths relative to its own directory (the
project root). This caused the migrated path to resolve incorrectly.

The fix re-bases the appHostPath during migration: resolves it against
the legacy .aspire/ directory, then makes it relative to the config
directory. For example, '../src/apphost.ts' becomes 'src/apphost.ts'.

Includes unit tests for the path adjustment and an E2E test that
verifies the full migration scenario with a TypeScript apphost in a
subdirectory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix E2E test: use grep instead of terminal buffer check

The terminal buffer accumulates all output, so the earlier echo of
legacy settings.json content (containing '../src/apphost.ts') would
cause false matches when verifying the migrated aspire.config.json.

Use 'grep' with the exact JSON key-value pattern to isolate the check,
and rely on the host-side file verification as the primary assertion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix E2E test: keep TS project intact, use robust patterns

The previous test moved apphost.ts to src/ but left .modules/ in the
workspace root, breaking TS imports. aspire run couldn't start, so the
migration never triggered and aspire.config.json was never created.

Changes:
- Keep apphost.ts at workspace root (project stays valid)
- Test re-basing with '../apphost.ts' -> 'apphost.ts' instead
  (same code path as '../src/apphost.ts' -> 'src/apphost.ts')
- Wait for 'Press CTRL+C' or 'Apphost failed' instead of generic
  'ERR:' which could false-match shell prompt patterns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix E2E test: poll host filesystem instead of parsing terminal

Previous approach failed because:
1. aspire run error messages varied by failure mode
2. Terminal buffer pattern matching was fragile across exit codes

Now polls the host-side filesystem via bind mount for aspire.config.json,
which is created during apphost discovery before the actual run attempt.
This works regardless of whether aspire run succeeds or fails.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR feedback: normalize separators, guard empty paths, add tests

- Normalize backslash separators to OS-native before Path operations,
  then always output forward slashes (matching storage convention)
- Guard against empty/whitespace paths to avoid converting '' to '.'
- Use { Length: > 0 } pattern match instead of separate null check
- Rename misleading test to clarify .aspire/-relative semantics
- Add tests: backslash normalization, output always uses '/',
  deeply nested paths, empty path preservation, null path handling

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Replace unrealistic test with realistic subdirectory path scenario

The previous test used 'apphost.ts' without '../' prefix, which would
mean the apphost lives inside .aspire/ — an impossible real-world
scenario. Replace with a realistic .csproj-in-subdirectory case:
'../MyApp.AppHost/MyApp.AppHost.csproj' -> 'MyApp.AppHost/MyApp.AppHost.csproj'

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use PathNormalizer for migration path re-basing

Replace manual slash normalization + GetFullPath with the existing
PathNormalizer.NormalizePathForCurrentPlatform() utility, which does
the same thing in one call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add NormalizePathForStorage helper and additional path tests

Address review feedback:
- Extract PathNormalizer.NormalizePathForStorage() for the common
  backslash-to-forward-slash normalization used when persisting paths.
- Add tests for ./path, bare relative path, Unix rooted, and Windows
  rooted legacy migration scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use realistic paths in migration tests

Avoid test paths that imply apphost lives under .aspire/ since that
never happens in practice.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants