Check validity of URL rewrite rules for non-index projects.

In commit 600c8a7848, we have introduced
an unintentionally breaking change to how redirects are handled, thus
breaking every single-page application deployed to a non-index project.

This cannot be easily fixed due to Codeberg Pages already having a mix
of broken and fixed redirects in manifests without any way to tell them
apart, but we can at least emit a diagnostic for redirects that are
statically known to be broken at deploy time.

See also #273.
This commit is contained in:
Catherine
2026-10-20 22:28:23 +00:00
parent 541877ae48
commit caa2895f52
3 changed files with 17 additions and 6 deletions
+4 -2
View File
@@ -338,9 +338,11 @@ func CompressFiles(ctx context.Context, manifest *Manifest) {
// Apply post-processing steps to the manifest.
// At the moment, there isn't a good way to report errors except to log them on the terminal.
// (Perhaps in the future they could be exposed at `.git-pages/status.txt`?)
func PrepareManifest(ctx context.Context, manifest *Manifest) error {
func PrepareManifest(ctx context.Context, webRoot string, manifest *Manifest) error {
_, project, _ := strings.Cut(webRoot, "/")
// Parse Netlify-style `_redirects`.
if err := ProcessRedirectsFile(ctx, manifest); err != nil {
if err := ProcessRedirectsFile(ctx, project, manifest); err != nil {
logc.Printf(ctx, "redirects err: %s\n", err)
} else if len(manifest.Redirects) > 0 {
logc.Printf(ctx, "redirects ok: %d rules\n", len(manifest.Redirects))
+12 -3
View File
@@ -43,7 +43,7 @@ func Is3xxHTTPStatus(status int) bool {
return status >= 300 && status <= 399
}
func validateRedirectRule(rule *redirects.Rule) error {
func validateRedirectRule(project string, rule *redirects.Rule) error {
if len(rule.Params) > 0 {
return fmt.Errorf("rules with parameters are not supported")
}
@@ -76,11 +76,20 @@ func validateRedirectRule(rule *redirects.Rule) error {
return fmt.Errorf("'to' URL may only include a hostname for 3xx status rules")
}
}
// After v0.9.1, we've changed the redirect handling so that the 'to' URL for a rewrite rule
// must be a valid URL pointing to this site, rather than a valid path within the manifest
// (which was the old, incorrect interpretation). This broke every SPA on git-pages that was
// deployed to a non-index project. Unfortunately, we can't do much more than diagnosing
// the problematic pattern at deploy time.
if rule.Status == http.StatusOK && project != ".index" && toURL.Host == "" &&
!strings.HasPrefix(toURL.Path, fmt.Sprintf("/%s/", project)) {
return fmt.Errorf("'to' URL must start with '/%s/' for a 200 status rule", project)
}
return nil
}
// Parses redirects file and injects rules into the manifest.
func ProcessRedirectsFile(ctx context.Context, manifest *Manifest) error {
func ProcessRedirectsFile(ctx context.Context, project string, manifest *Manifest) error {
redirectsEntry := manifest.Contents[RedirectsFileName]
delete(manifest.Contents, RedirectsFileName)
if redirectsEntry == nil {
@@ -103,7 +112,7 @@ func ProcessRedirectsFile(ctx context.Context, manifest *Manifest) error {
}
for index, rule := range rules {
if err := validateRedirectRule(&rule); err != nil {
if err := validateRedirectRule(project, &rule); err != nil {
ruleString := redirects.Must(redirects.UnparseString([]redirects.Rule{rule}))
AddProblem(manifest, RedirectsFileName,
"rule #%d %q: %s", index+1, strings.TrimSpace(ruleString), err)
+1 -1
View File
@@ -70,7 +70,7 @@ func Update(
outcome = UpdateDeleted
}
}
} else if err = PrepareManifest(ctx, newManifest); err == nil {
} else if err = PrepareManifest(ctx, webRoot, newManifest); err == nil {
storedManifest, err = StoreManifest(ctx, webRoot, newManifest, opts)
if err == nil {
domain, _, _ := strings.Cut(webRoot, "/")