fix(web,server): плейсхолдер dist для воспроизводимой сборки + /api без слэша → API
Коммитим internal/web/dist/index.html как минимальный плейсхолдер, чтобы //go:embed all:dist находил совпадения на чистом клоне без npm/`make web` (CRITICAL: go build ./... падал с "pattern all:dist: no matching files found"). .gitignore теперь игнорирует только реальные build-ассеты (internal/web/dist/* кроме index.html); `make web` перезаписывает плейсхолдер настоящей сборкой. Также чинит MEDIUM: голый /api (без хвостового слэша) уходил в SPA-fallback вместо API-роутера — вынесен isAPIPath() с явной проверкой path == "/api", покрыт TestIsAPIPath. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
+8
-1
@@ -7,4 +7,11 @@
|
|||||||
# web (Vite frontend)
|
# web (Vite frontend)
|
||||||
web/node_modules/
|
web/node_modules/
|
||||||
web/dist/
|
web/dist/
|
||||||
internal/web/dist/
|
|
||||||
|
# internal/web/dist: real build output is generated by `make web` and
|
||||||
|
# gitignored, EXCEPT index.html — a minimal placeholder is committed so
|
||||||
|
# `go build ./...` (which //go:embed all:dist needs) works on a clean
|
||||||
|
# clone without npm/CI web-build step. `make web` overwrites the
|
||||||
|
# placeholder with the real built index.html.
|
||||||
|
internal/web/dist/*
|
||||||
|
!internal/web/dist/index.html
|
||||||
|
|||||||
+9
-1
@@ -18,6 +18,14 @@ import (
|
|||||||
"github.com/vasyakrg/dns-autoresolver/internal/web"
|
"github.com/vasyakrg/dns-autoresolver/internal/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// isAPIPath reports whether path must be routed to the API router rather
|
||||||
|
// than the SPA. "/api" (no trailing slash) counts as an API path too —
|
||||||
|
// only strings.HasPrefix(path, "/api/") would otherwise miss it and fall
|
||||||
|
// through to the SPA fallback.
|
||||||
|
func isAPIPath(path string) bool {
|
||||||
|
return path == "/api" || strings.HasPrefix(path, "/api/")
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
cfg, err := config.Load()
|
cfg, err := config.Load()
|
||||||
@@ -52,7 +60,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
mux := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
if isAPIPath(r.URL.Path) {
|
||||||
apiRouter.ServeHTTP(w, r)
|
apiRouter.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsAPIPath(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
path string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"/api", true},
|
||||||
|
{"/api/", true},
|
||||||
|
{"/api/domains", true},
|
||||||
|
{"/", false},
|
||||||
|
{"/domains/xyz", false},
|
||||||
|
{"/apix", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
if got := isAPIPath(c.path); got != c.want {
|
||||||
|
t.Errorf("isAPIPath(%q) = %v, want %v", c.path, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html><title>DNS Autoresolver</title><body>UI not built. Run: make web</body>
|
||||||
Reference in New Issue
Block a user