Skip to content

Fix aspire docs cache and output rendering#16304

Open
davidfowl wants to merge 2 commits intomainfrom
davidfowl/fix-docs-command
Open

Fix aspire docs cache and output rendering#16304
davidfowl wants to merge 2 commits intomainfrom
davidfowl/fix-docs-command

Conversation

@davidfowl
Copy link
Copy Markdown
Contributor

Description

Fixes stale aspire docs cache behavior and the broken rendering path in aspire docs get.

  • Revalidates the cached parsed docs index against the fetched docs source so stale cached indexes are refreshed when llms-small.txt changes, while still falling back to cached content if refresh fails.
  • Reconstructs minified inline markdown tables, avoids wrapping table rows, and uses a plain-text markdown path for redirected or non-interactive output so links and values like Azure:SubscriptionId remain readable.
  • Adds regression coverage for stale cache refresh, table normalization, non-interactive markdown output, and table wrapping behavior.

Fixes #16302

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?

Copilot AI review requested due to automatic review settings April 18, 2026 23:32
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 16304

Or

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

Revalidate the parsed docs index against the fetched docs source so cached docs refresh when the source changes, while still falling back to cached content when refresh fails.

Also improve docs markdown output by reconstructing inline tables, avoiding table-row wrapping, and using a plain-text path for redirected or non-interactive output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidfowl davidfowl force-pushed the davidfowl/fix-docs-command branch from aea83d9 to d9c8c3f Compare April 18, 2026 23:34
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

Fixes aspire docs behavior by preventing stale cached indexes when the underlying docs source changes, and by improving CLI markdown rendering for tables/links—especially when output is redirected or non-interactive.

Changes:

  • Revalidates the cached parsed docs index using a computed source fingerprint, refreshing when the fetched source content changes and falling back to cache when refresh fails.
  • Improves docs-get rendering by (a) normalizing minified inline markdown tables and (b) avoiding table-row wrapping; adds a plain-text rendering path for redirected/non-interactive output.
  • Adds/updates regression tests for cache refresh behavior and markdown rendering behavior.
Show a summary per file
File Description
tests/Aspire.Hosting.JavaScript.Tests/AddViteAppTests.cs Updates Node-version selection test coverage for generated Dockerfiles (includes new .tool-versions tab case and package.json engines expectations).
tests/Aspire.Cli.Tests/Utils/MarkdownToSpectreConverterTests.cs Adds coverage for the new plain-text markdown conversion behavior.
tests/Aspire.Cli.Tests/Mcp/Docs/DocsIndexServiceTests.cs Adds coverage for cached index revalidation/refresh and inline table normalization.
tests/Aspire.Cli.Tests/Interaction/ConsoleInteractionServiceTests.cs Verifies non-interactive output uses a plain-text markdown fallback.
tests/Aspire.Cli.Tests/Commands/DocsCommandTests.cs Adds regression coverage ensuring table rows are not wrapped for console output.
src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs Changes Node.js version resolution logic for Dockerfile base image selection.
src/Aspire.Cli/Utils/MarkdownToSpectreConverter.cs Adds ConvertToPlainText for redirected/non-interactive output rendering.
src/Aspire.Cli/Interaction/ConsoleInteractionService.cs Routes markdown output to plain-text conversion when non-interactive or redirected.
src/Aspire.Cli/Documentation/Docs/DocsIndexService.cs Implements fingerprint-based cache revalidation and inline table normalization during content normalization.
src/Aspire.Cli/Commands/DocsGetCommand.cs Prevents wrapping of markdown table lines during console wrapping.

Copilot's findings

  • Files reviewed: 8/8 changed files
  • Comments generated: 1

Comment on lines +75 to +78
/// Converts markdown to plain text suitable for redirected or non-interactive output.
/// </summary>
/// <param name="markdown">The markdown text to convert.</param>
/// <returns>Markdown with links rewritten to plain text and image references removed.</returns>
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

The XML doc for ConvertToPlainText is inaccurate: the implementation removes headers and basic formatting (bold/italic/strikethrough) in addition to rewriting links and removing images. Please update the text (and/or summary) to describe the full set of transformations so callers know this is a lossy plain-text conversion, not just a link/image rewrite.

Suggested change
/// Converts markdown to plain text suitable for redirected or non-interactive output.
/// </summary>
/// <param name="markdown">The markdown text to convert.</param>
/// <returns>Markdown with links rewritten to plain text and image references removed.</returns>
/// Converts markdown to a lossy plain-text representation suitable for redirected or non-interactive output.
/// </summary>
/// <param name="markdown">The markdown text to convert.</param>
/// <returns>Plain text with links rewritten to <c>text (url)</c>, image references removed, header markers stripped, and basic formatting markers for bold, italic, and strikethrough removed.</returns>

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was 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.

Update the ConvertToPlainText XML documentation so it accurately describes the lossy plain-text conversion performed for redirected and non-interactive output.

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

🎬 CLI E2E Test Recordings — 72 recordings uploaded (commit 6ccb0a6)

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
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 ▶️ 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
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
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ 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
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24618970596

Comment on lines +79 to +102
public static string ConvertToPlainText(string markdown)
{
if (string.IsNullOrWhiteSpace(markdown))
{
return markdown;
}

var result = markdown.Replace("\r\n", "\n").Replace("\r", "\n");
result = ConvertImages(result);
result = ConvertLinksToPlainText(result);
result = HeaderLevel6Regex().Replace(result, "$1");
result = HeaderLevel5Regex().Replace(result, "$1");
result = HeaderLevel4Regex().Replace(result, "$1");
result = HeaderLevel3Regex().Replace(result, "$1");
result = HeaderLevel2Regex().Replace(result, "$1");
result = HeaderLevel1Regex().Replace(result, "$1");
result = BoldDoubleAsterisksRegex().Replace(result, "$1");
result = BoldDoubleUnderscoresRegex().Replace(result, "$1");
result = ItalicSingleAsteriskRegex().Replace(result, "$1");
result = ItalicSingleUnderscoreRegex().Replace(result, "$1");
result = StrikethroughRegex().Replace(result, "$1");

return result;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we're going to keep running into issues with using regex. For example, markdown tables aren't converted to Spectre.Console tables.

We should consider using Markdig. We can parse to a markdown DOM, then transform to whatever format we want.

Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

LGTM. Cache revalidation logic, table normalization regexes, and non-interactive plain-text fallback all look correct. No issues found.

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 docs get mangles table and link output when rendered in the CLI

4 participants