feat(web): add templated route fallback pipeline task#314
feat(web): add templated route fallback pipeline task#314PrzemyslawKlys merged 4 commits intomainfrom
Conversation
Code ReviewOverall this is a well-structured addition. The feature is well-thought-out with good path-traversal protection, clear error messages, and solid documentation. A few items worth addressing: Bugs / Correctness1. In var rootContent = ApplyRouteFallbackReplacements(templateContent, rootReplacements, null, htmlEncode);
2. Empty If a user sets Performance3. Regex re-compiled on every call in return Regex.Replace(template, "\\{(?<name>[^{}]+)\\}", match => { ... });
[GeneratedRegex(@"\{(?<name>[^{}]+)\}", RegexOptions.CultureInvariant)]
private static partial Regex PlaceholderPattern();4. var json = JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true });
Test Coverage Gaps5. No test for 6. No test for 7. No test for a flat JSON array (non-nested items) 8. No test for a custom 9. Path-traversal test covers route items but not Code Quality10. Test class has no namespace public class WebPipelineRunnerRouteFallbacksTests
{If the rest of the test project uses a namespace, this class is an outlier and may cause tooling confusion. Verify this is consistent with other test files in the project. 11. The method does case-insensitive property lookup via Minor Nits
The path-traversal protection, HTML-encode-by-default, and the three targeted tests are all highlights of this PR. The items above are mostly gaps to fill out rather than blockers. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49b2f74ee8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var templatePath = ResolvePath(baseDir, | ||
| GetString(step, "template") ?? | ||
| GetString(step, "templatePath") ?? | ||
| GetString(step, "template-path")); | ||
| var itemsPath = ResolvePath(baseDir, |
There was a problem hiding this comment.
Include route manifest/template files in cache fingerprint
This task consumes external files via template/items, but those property names are not part of the fingerprinted path key set used by pipeline caching. As a result, when cache: true is enabled, changing routes.json or the HTML template can still produce a cache hit and skip regeneration, leaving stale fallback metadata in _site. Please add these new input keys (and aliases) to the fingerprint path list, or mark this task as non-cacheable until fingerprinting is complete.
Useful? React with 👍 / 👎.
| outputs.AddRange(ResolveOutputCandidates(baseDir, GetString(step, "siteRoot") ?? GetString(step, "site-root"))); | ||
| outputs.AddRange(ResolveOutputCandidates(baseDir, GetString(step, "reportPath") ?? GetString(step, "report-path"))); |
There was a problem hiding this comment.
Verify concrete fallback outputs before accepting cache hit
The expected-output check for route-fallbacks only probes siteRoot (and optional reportPath). Because siteRoot can exist even when generated route files are missing, cache validation can report success and skip the step after route HTML files were deleted or partially cleaned, resulting in missing pages at runtime. Add deterministic output paths (for example rootOutput and/or a tracked generated-file list) so missing fallback files invalidate the cache.
Useful? React with 👍 / 👎.
Code Review:
|
| Scenario | Why it matters |
|---|---|
htmlEncode: false |
The encoding path is non-trivial; confirm raw values land verbatim |
| Items file is a bare JSON array (not wrapped in object) | ResolveRouteFallbackItems has a special branch for this |
itemsProperty override |
The custom array key path in ResolveRouteFallbackItems is otherwise untested |
| Template missing a replacement token | ValidateRouteFallbackTokens throws — confirm message is actionable |
rootValues without rootOutput |
Exposes the validation inconsistency noted above |
Workflow change (.github/workflows/powerforge-website-run.yml)
The fix — switching the CLI --powerforge-repository / --powerforge-ref arguments from the raw inputs.* values to steps.workflow_source.outputs.* — is correct and consistent with the actions/checkout step at line 132–133 which already used the resolved outputs. Previously the tool could receive an unresolved ref while the checkout used the resolved SHA, which would cause a mismatch. No issues here, just noting it's a good catch.
Nits
NormalizeRouteFallbackRelativePathusesnormalized.Substring(1)instead ofnormalized[1..]. Minor, just consistency with modern C#.ReadRouteFallbackStringMapduplicates theEnumerateObject/ null-skip / GetString-or-ToString pattern that also appears inBuildRouteFallbackValueMap. A single privateReadStringDictionary(JsonElement obj)helper would remove the duplication.- The
RouteFallbackReportandRouteFallbackReportEntryprivate classes could berecordtypes since they're only serialised, not mutated after construction — but this is a minor style point.
The path-traversal security check (IsPathWithinRoot after Path.GetFullPath) is solid, HTML encoding defaults are the right call for SPA route shells, and the schema allOf/anyOf required-field enforcement is thorough. Fixing items 1–3 above and adding a couple more tests would make this merge-ready.
Code ReviewOverall this is a well-structured addition — the feature is clearly scoped, the security-sensitive path traversal check is present, HTML encoding defaults to Notable issues1.
The exclusion list covers // Current behaviour — Microsoft.Extensions.Logging → true (probed from host)
if (assemblyName.StartsWith("Microsoft.Win32.", ...))
return false;
return true; // catch-all2.
When
Minor observations3.
4. Report-write failure after all files already committed
5. Regex allocation in
[GeneratedRegex(@"\{(?<name>[^{}]+)\}", RegexOptions.CultureInvariant)]
private static partial Regex RouteFallbackTokenRegex();6. Test gaps
Workflow fix (commit 3ccf923)The change from Assembly load order change (commit fc6fbcf)Moving the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc6fbcf85d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| '--powerforge-repository', '${{ steps.workflow_source.outputs.repository }}', | ||
| '--powerforge-ref', '${{ steps.workflow_source.outputs.ref }}', |
There was a problem hiding this comment.
Preserve lock-based engine resolution
Passing steps.workflow_source.outputs.repository/ref into website-runner makes --powerforge-repository and --powerforge-ref non-empty on every run, so source mode no longer falls back to .powerforge/engine-lock.json (the fallback in WebWebsiteRunner.RunSourceMode only happens when either value is empty). In the common case where callers leave these inputs blank, this silently ignores the committed engine lock and forces the workflow SHA instead, which can run a different engine commit than the site’s pinned lock policy.
Useful? React with 👍 / 👎.
Code Review:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2d24d850e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var templateContent = File.ReadAllText(templatePath); | ||
| ValidateRouteFallbackTokens(templateContent, templatePath, routeReplacements, rootReplacements); |
There was a problem hiding this comment.
Skip root token validation when rootOutput is absent
rootValues are always included in ValidateRouteFallbackTokens, even when rootOutput is not configured, so a step that only generates per-route files can fail with “template does not contain required token …” for unused root-only tokens. This makes optional root metadata effectively mandatory in mixed configs and breaks valid route-only runs whenever rootValues is present but not applicable.
Useful? React with 👍 / 👎.
| if (!string.IsNullOrWhiteSpace(itemsProperty)) | ||
| candidateNames.Add(itemsProperty); | ||
| candidateNames.Add("items"); | ||
| candidateNames.Add("routes"); | ||
| candidateNames.Add("entries"); |
There was a problem hiding this comment.
Fail when configured itemsProperty is not a readable array
When itemsProperty is supplied, the resolver still falls back to default names (items, routes, entries) if that configured property is missing or not an array. In manifests that contain multiple arrays, a typo or schema drift in itemsProperty will silently select a different dataset and generate incorrect fallback pages instead of failing fast on the misconfiguration.
Useful? React with 👍 / 👎.
Summary
route-fallbackspipeline task for manifest-driven SPA/Blazor route shellsValidation
dotnet test .\\PowerForge.Tests\\PowerForge.Tests.csproj --filter "FullyQualifiedName~RouteFallbacks" -c Release