repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
3640880
parent
6a3121d
author
Eric Bower
date
2026-04-04 20:01:33 -0400 EDT
fix(pgs): force redirect from root
3 files changed,  +59, -4
M pkg/apps/pgs/calc_route.go
+24, -4
 1@@ -112,6 +112,11 @@ func correlatePlaceholder(orig, pattern string) (string, string) {
 2 		_type = "variable"
 3 	}
 4 
 5+	// special case: root path matches root path
 6+	if orig == "/" && pattern == "/" {
 7+		return "/", "match"
 8+	}
 9+
10 	return filepath.Join(nextList...), _type
11 }
12 
13@@ -250,10 +255,25 @@ func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpRe
14 			userReply := []*HttpReply{}
15 			var rule *HttpReply
16 			if redirect.To != "" {
17-				rule = &HttpReply{
18-					Filepath: route,
19-					Status:   redirect.Status,
20-					Query:    redirect.Query,
21+				// expand redirect target to find actual file (e.g., directory -> index.html)
22+				// but only if Force is true, it's not a full URL, and it's a directory path (ends with / but not just /)
23+				if redirect.Force && !hasProtocol(redirect.To) && strings.HasSuffix(route, "/") && route != "/" {
24+					expanded := expandRoute(projectName, route, redirect.Status)
25+					if len(expanded) > 0 {
26+						rule = expanded[0]
27+					} else {
28+						rule = &HttpReply{
29+							Filepath: route,
30+							Status:   redirect.Status,
31+							Query:    redirect.Query,
32+						}
33+					}
34+				} else {
35+					rule = &HttpReply{
36+						Filepath: route,
37+						Status:   redirect.Status,
38+						Query:    redirect.Query,
39+					}
40 				}
41 				userReply = append(userReply, rule)
42 			}
M pkg/apps/pgs/calc_route_test.go
+19, -0
 1@@ -663,6 +663,25 @@ func TestCalcRoutes(t *testing.T) {
 2 				{Filepath: "public/404.html", Status: 404},
 3 			},
 4 		},
 5+		{
 6+			Name: "root-redirect",
 7+			Actual: calcRoutes(
 8+				"public",
 9+				"/",
10+				[]*RedirectRule{
11+					{
12+						From:   "/",
13+						To:     "/dax/cool/wow/",
14+						Status: 302,
15+						Force:  true,
16+					},
17+				},
18+			),
19+			Expected: []*HttpReply{
20+				{Filepath: "public/dax/cool/wow/index.html", Status: 302},
21+				{Filepath: "public/404.html", Status: 404},
22+			},
23+		},
24 	}
25 
26 	for _, fixture := range fixtures {
M pkg/apps/pgs/redirect_test.go
+16, -0
 1@@ -141,6 +141,21 @@ func TestParseRedirectText(t *testing.T) {
 2 		},
 3 	}
 4 
 5+	rootRedirect := RedirectFixture{
 6+		name:  "root-redirect",
 7+		input: "/ /dax/cool/wow/ 302!",
 8+		expect: []*RedirectRule{
 9+			{
10+				From:       "/",
11+				To:         "/dax/cool/wow/",
12+				Status:     302,
13+				Query:      empty,
14+				Conditions: empty,
15+				Force:      true,
16+			},
17+		},
18+	}
19+
20 	fixtures := []RedirectFixture{
21 		spa,
22 		rss,
23@@ -153,6 +168,7 @@ func TestParseRedirectText(t *testing.T) {
24 		selfReferentialWithVariables,
25 		externalUrlNotSelfRef,
26 		validPathRedirect,
27+		rootRedirect,
28 	}
29 
30 	for _, fixture := range fixtures {