[3.x] Support pages with more output file extensions (Foundation)#2525
Merged
Conversation
Adds a static $outputFileExtension property (default '.html') to HydePage,
used by outputPath() instead of the hardcoded HTML suffix, along with an
outputFileExtension() accessor matching the existing static accessors.
InMemoryPage identifiers can now declare a '.json', '.txt', or '.xml'
extension to compile to that path as-is, so
InMemoryPage::make('robots.txt', contents: ...) outputs '_site/robots.txt'.
This formalizes the route key convention already used by the documentation
search index: only the HTML extension is implicit in route keys, and pages
compiled to non-HTML files keep their extension in the route key. The
DocumentationSearchIndex getOutputPath() override is now redundant and removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds testOutputFileExtension to the BaseHydePageUnitTest contract with implementations for all page classes, HydePage baseline and child class override tests, and InMemoryPage tests covering identifier-declared extensions including edge cases like dotted identifiers and unsupported extensions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the user path of registering pages like robots.txt in a kernel booting callback and having them compiled to their declared output paths through the standard build command, including route registration, nested paths, and view-based compilation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the route key convention for page classes declaring a non-HTML output file extension: RouteKey::fromPage() now appends the declared extension to the key (unless the identifier already ends with it), so the route key always equals the output path with only the HTML extension implicit. Without this, a class declaring '.txt' would get the route key 'robots' but the output path 'robots.txt', breaking route resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records the resolved design decisions (identifier allowlist chosen for InMemoryPage, route key extension handling in RouteKey::fromPage, no setter, redirects intentionally not special-cased) and rewords the breaking-change note to reflect that double-extension output was never practically relied upon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v3/non-html-pages #2525 +/- ##
=====================================================
Coverage 100.00% 100.00%
- Complexity 1720 1736 +16
=====================================================
Files 175 175
Lines 4308 4336 +28
=====================================================
+ Hits 4308 4336 +28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Renames HydePage::$fileExtension to $sourceExtension, with the fileExtension() and setFileExtension() methods becoming sourceExtension() and setSourceExtension(). The old name was underspecified: it actually meant the source file extension, which becomes ambiguous now that pages also declare an output extension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pairs $outputExtension with $sourceExtension so the two declarations read as the symmetrical concepts they are, and documents the accessor by its purpose (values like `.html` or `.txt`) rather than leading with the serialization detail of the leading dot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ternary compressed the domain distinction (only the HTML extension is implicit in route keys) to the point of needing a narrating comment. The expanded conditional makes the rule visible on its own; the broader route-key rule is documented on the RouteKey class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renames InMemoryPage::identifierDeclaresOutputExtension() to identifierHasExplicitOutputExtension() and names the allowlist as the EXPLICIT_OUTPUT_EXTENSIONS constant, so subclasses can customize the recognized extensions by overriding data instead of replacing the algorithm. The docblocks now state the observable behavior (which extensions are recognized and what happens) instead of the vague "supported". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The helper returned either an extension or an empty-string sentinel, forcing the caller to concatenate an opaque fragment. Stating the whole route-key construction in one place makes the "only HTML is implicit" rule readable without control-flow-by-empty-string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the breaking-change release note and upgrade step for the $fileExtension to $sourceExtension rename, and starts an upgrade script rules section in the planning document so the mechanical migrations the release-time upgrade script must perform are collected as we make them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The EXPLICIT_OUTPUT_EXTENSIONS constant is the advertised extension point for subclasses, so cover that overriding it both recognizes the custom extension and replaces the default list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Page classes can now compile to non-HTML files, so the accessor should not describe the output as HTML. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The renamed methods are public and non-final, so a subclass override of fileExtension() or setFileExtension() would silently stop being called once the framework calls sourceExtension(). The upgrade script rule and the upgrade guide now include renaming such declarations. Also moves the upgrade script rules to their own section at the end of the planning document instead of sitting under the empty v2-branch heading. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Appending to the identifier before joining the base route key produced keys like feed/.xml instead of feed.xml for non-HTML pages with an empty identifier and an output directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
outputExtension() returned the raw static property, so a typo like 'txt' (missing the dot) silently produced output paths like documenttxt. The accessor now throws an InvalidArgumentException for values that do not start with a dot or that contain path separators, matching the normalization the source extension setter already applies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A page class still declaring $fileExtension (or overriding the renamed methods) is not discoverable under the new API, so a build would succeed while silently omitting the entire page type — including unmodified vendor packages the upgrade script never touches. Discovery now throws an actionable exception naming the class and the rename. This is a temporary upgrade aid that can be removed in a future release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A redirect declared for a path like legacy.json appears valid and produces a file, but the meta refresh markup cannot redirect when the file is served as non-HTML content. The configuration now fails fast with an InvalidConfigurationException instead of silently emitting a broken page. Revises the earlier document-only decision in the epic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ends the coverage gap where only InMemoryPage was exercised through the full build: a custom page class with a .md source extension and a .txt output extension is now tested through source discovery, route registration, the build command, and the output file name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
emmadesilva
force-pushed
the
v3/non-html-pages-foundation
branch
from
July 13, 2026 14:37
41a2326 to
67b6ca1
Compare
emmadesilva
marked this pull request as ready for review
July 13, 2026 14:37
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.