Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.23.1] - 2026-08-12

### Fixed

- 修复单源部署未提供官网 `images` 静态目录,导致产品截图被 SPA 首页回退响应替代的问题。
- 外部官网浏览器回归改用提交阶段导航,避免第三方观测脚本阻塞页面验证。

## [0.23.0] - 2026-08-12

### Added
Expand Down
2 changes: 1 addition & 1 deletion backend/cmd/asterrouter/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.23.0
0.23.1
5 changes: 4 additions & 1 deletion backend/internal/server/spa.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ func serveSPA(r *gin.Engine, dir string) {
return
}
r.Static("/assets", filepath.Join(abs, "assets"))
r.Static("/images", filepath.Join(abs, "images"))
r.NoRoute(func(c *gin.Context) {
path := c.Request.URL.Path
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/v1/") {
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/v1/") ||
path == "/assets" || strings.HasPrefix(path, "/assets/") ||
path == "/images" || strings.HasPrefix(path, "/images/") {
httpx.Error(c, http.StatusNotFound, 1404, "not found")
return
}
Expand Down
62 changes: 62 additions & 0 deletions backend/internal/server/spa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package server

import (
"bytes"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"github.com/gin-gonic/gin"
)

func TestServeSPAProvidesPublicImages(t *testing.T) {
gin.SetMode(gin.TestMode)
dist := t.TempDir()
if err := os.WriteFile(filepath.Join(dist, "index.html"), []byte("<div id=\"app\"></div>"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(filepath.Join(dist, "images"), 0o700); err != nil {
t.Fatal(err)
}
image := []byte("synthetic-webp")
if err := os.WriteFile(filepath.Join(dist, "images", "workbench.webp"), image, 0o600); err != nil {
t.Fatal(err)
}

router := gin.New()
serveSPA(router, dist)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/images/workbench.webp", nil))

if recorder.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
if contentType := recorder.Header().Get("Content-Type"); !strings.HasPrefix(contentType, "image/webp") {
t.Fatalf("content-type=%q", contentType)
}
if !bytes.Equal(recorder.Body.Bytes(), image) {
t.Fatalf("body=%q", recorder.Body.Bytes())
}
}

func TestServeSPADoesNotFallbackForMissingStaticFiles(t *testing.T) {
gin.SetMode(gin.TestMode)
dist := t.TempDir()
if err := os.WriteFile(filepath.Join(dist, "index.html"), []byte("<div id=\"app\"></div>"), 0o600); err != nil {
t.Fatal(err)
}

router := gin.New()
serveSPA(router, dist)

for _, path := range []string{"/assets/missing.js", "/images/missing.webp"} {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil))
if recorder.Code != http.StatusNotFound {
t.Fatalf("path=%s status=%d body=%s", path, recorder.Code, recorder.Body.String())
}
}
}
2 changes: 1 addition & 1 deletion frontend/e2e/marketing-home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { captureBrowserErrors, expectNoHorizontalOverflow } from './fixtures'

test('@marketing official website is public, localized, and responsive', async ({ page }, testInfo) => {
const errors = captureBrowserErrors(page)
await page.goto('/')
await page.goto('/', { waitUntil: 'commit' })

await expect(page).toHaveURL('/')
await expect(page.getByRole('heading', { level: 1, name: 'AsterRouter' })).toBeVisible()
Expand Down
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "asterrouter-frontend",
"private": true,
"version": "0.23.0",
"version": "0.23.1",
"type": "module",
"overrides": {
"nanoid": "^3.3.17"
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/i18n/locales/marketing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const marketingZh = {
description: '在企业应用与已授权 AI 供应商之间建立统一控制面。一次完成身份校验、访问准入、资源选择、成本约束和请求级证据记录。',
primaryAction: '进入企业控制台', secondaryAction: '查看请求决策', factsLabel: '产品事实',
factProtocols: '统一接入 OpenAI、Anthropic 与 Gemini 兼容协议', factEvidence: '策略、路由、用量、成本与 Trace 使用同一证据链',
imageAlt: 'AsterRouter 路由策略工作台实际界面', actualProduct: '实际产品界面', actualProductDetail: '路由策略工作台 · v0.23.0'
imageAlt: 'AsterRouter 路由策略工作台实际界面', actualProduct: '实际产品界面', actualProductDetail: '路由策略工作台 · v0.23.1'
},
decision: {
overline: 'ONE REQUEST, ONE EXPLAINABLE DECISION', title: '每一次模型请求,都经过同一条企业决策链',
Expand Down Expand Up @@ -59,7 +59,7 @@ export const marketingEn = {
description: 'Place one control plane between enterprise applications and authorized AI providers. Enforce identity, access, supply selection, cost boundaries, and request-level evidence on every call.',
primaryAction: 'Open enterprise console', secondaryAction: 'Inspect the decision flow', factsLabel: 'Product facts',
factProtocols: 'One gateway for OpenAI, Anthropic, and Gemini-compatible protocols', factEvidence: 'Policy, routing, usage, cost, and traces share one evidence chain',
imageAlt: 'Actual AsterRouter routing policy workbench', actualProduct: 'Actual product interface', actualProductDetail: 'Routing policy workbench · v0.23.0'
imageAlt: 'Actual AsterRouter routing policy workbench', actualProduct: 'Actual product interface', actualProductDetail: 'Routing policy workbench · v0.23.1'
},
decision: {
overline: 'ONE REQUEST, ONE EXPLAINABLE DECISION', title: 'Every model request follows the same enterprise decision chain',
Expand Down
1 change: 1 addition & 0 deletions scripts/test-single-origin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ curl -fsS "http://127.0.0.1:${PORT}/api/v1/settings/public" | grep -Fq '"demo_mo
for path in / /login /console/workbench /portal/overview; do
curl -fsS "http://127.0.0.1:${PORT}${path}" | grep -Fq '<div id="app"></div>'
done
curl -fsS "http://127.0.0.1:${PORT}/images/asterrouter-routing-workbench.webp" | cmp -s - "${ROOT_DIR}/frontend/dist/images/asterrouter-routing-workbench.webp"

echo "Single-origin smoke passed on port ${PORT}."
Loading