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
1 change: 1 addition & 0 deletions .bun-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.3.10
14 changes: 14 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ permissions:
jobs:
dev-build-deploy:
runs-on: ubuntu-latest
timeout-minutes: 90
# 跳过由GitHub Actions创建的提交,避免死循环
if: github.event.pusher.name != 'github-actions[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
steps:
Expand Down Expand Up @@ -61,9 +62,22 @@ jobs:

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies, type check, and format code
timeout-minutes: 15
run: |
# No lockfile is committed in this repository, so use a non-frozen install.
bun install
bun run typecheck
bun run format
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,34 @@ jobs:
code-quality:
runs-on: ubuntu-latest
name: Code Quality Check
timeout-minutes: 15

steps:
- name: 📥 Checkout repository
uses: actions/checkout@v5

- name: 📦 Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: 🟢 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: 📦 Install dependencies
timeout-minutes: 8
# No lockfile is committed in this repository, so use a non-frozen install.
run: bun install

- name: 🔍 Type check
Expand All @@ -53,6 +67,7 @@ jobs:
build-check:
runs-on: ubuntu-latest
name: Docker Build Test
timeout-minutes: 45

steps:
- name: 📥 Checkout repository
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ permissions:
jobs:
release-pipeline:
runs-on: ubuntu-latest
timeout-minutes: 120
# 跳过由GitHub Actions创建的提交,避免死循环 (仅对push事件生效)
if: |
github.event_name == 'workflow_dispatch' ||
Expand Down Expand Up @@ -197,10 +198,24 @@ jobs:
- name: Setup Bun
if: steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch'
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Cache Bun package cache
if: steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch'
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies, type check, and format code
if: steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch'
timeout-minutes: 15
run: |
# No lockfile is committed in this repository, so use a non-frozen install.
bun install
bun run typecheck
bun run format
Expand Down
82 changes: 74 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
quality:
name: 📋 Code Quality
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout code
Expand All @@ -24,10 +25,21 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version-file: .bun-version

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile
timeout-minutes: 8
# No lockfile is committed in this repository, so use a non-frozen install.
run: bun install

- name: Run linting
run: bun run lint
Expand All @@ -42,16 +54,30 @@ jobs:
unit-tests:
name: ⚡ Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile
timeout-minutes: 8
# No lockfile is committed in this repository, so use a non-frozen install.
run: bun install

- name: Run unit tests
run: bun run test -- tests/unit/ --passWithNoTests
Expand All @@ -60,6 +86,7 @@ jobs:
integration-tests:
name: 🔗 Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 25

services:
postgres:
Expand Down Expand Up @@ -93,31 +120,46 @@ jobs:
AUTO_MIGRATE: true
ENABLE_RATE_LIMIT: true
SESSION_TTL: 300
VITEST_STATEFUL_MAX_WORKERS: 2
VITEST_STATEFUL_MAX_CONCURRENCY: 3

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile
timeout-minutes: 8
# No lockfile is committed in this repository, so use a non-frozen install.
run: bun install

- name: Run database migrations
run: bun run db:migrate

- name: Run integration tests
run: >
bunx vitest run
tests/integration/usage-ledger.test.ts
tests/integration/my-usage-imported-ledger.test.ts
bun x vitest run
--config tests/configs/integration.config.ts
--passWithNoTests

# ==================== API 测试(需要运行服务)====================
api-tests:
name: 🌐 API Tests
runs-on: ubuntu-latest
timeout-minutes: 35

services:
postgres:
Expand Down Expand Up @@ -152,21 +194,45 @@ jobs:
PORT: 13500
ENABLE_RATE_LIMIT: true
SESSION_TTL: 300
VITEST_STATEFUL_MAX_WORKERS: 1
VITEST_STATEFUL_MAX_CONCURRENCY: 3

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: .bun-version

- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
# This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs.
key: ${{ runner.os }}-bun-${{ hashFiles('package.json', '.bun-version') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile
timeout-minutes: 8
# No lockfile is committed in this repository, so use a non-frozen install.
run: bun install

- name: Run database migrations
run: bun run db:migrate

- name: Cache Next.js build cache
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('package.json', '.bun-version') }}-${{ hashFiles('src/**/*.js', 'src/**/*.jsx', 'src/**/*.ts', 'src/**/*.tsx', 'next.config.*', 'tsconfig.json', 'postcss.config.*') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('package.json', '.bun-version') }}-

- name: Build application
timeout-minutes: 15
run: bun run build

- name: Start server (background)
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# syntax=docker/dockerfile:1
FROM oven/bun:debian AS deps
WORKDIR /app
COPY package.json bun.lockb* ./
RUN bun install --frozen-lockfile
COPY package.json ./
RUN bun install
Comment thread
ding113 marked this conversation as resolved.

FROM oven/bun:debian AS builder
WORKDIR /app
Expand Down
1 change: 1 addition & 0 deletions messages/en/settings/statusPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"intervalOption": "{minutes} min",
"slug": "Slug",
"slugTooltip": "URL identifier for this group's dedicated status page. Leave empty to auto-generate from the public display name. Lowercase letters, digits, and hyphens only.",
"duplicateSlug": "Slug {slug} is used by multiple public groups. Please make each group slug unique before saving.",
"copy": "Copy",
"sortOrder": "Sort Order",
"sortOrderTooltip": "Display order on the public status page. Lower values appear first (same convention as provider priority). Equal values fall back to group name.",
Expand Down
1 change: 1 addition & 0 deletions messages/ja/settings/statusPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"intervalOption": "{minutes} 分",
"slug": "Slug",
"slugTooltip": "グループ専用ステータスページの URL 識別子。空欄の場合は公開表示名から自動生成されます。小文字、数字、ハイフンのみ使用可能です。",
"duplicateSlug": "Slug {slug} は複数の公開グループで使われています。保存前に各グループの slug を一意にしてください。",
"copy": "説明文",
"sortOrder": "並び順",
"sortOrderTooltip": "公開ステータスページでの表示順。値が小さいほど先頭に表示されます(プロバイダー優先度と同じ規則)。同値の場合はグループ名順となります。",
Expand Down
1 change: 1 addition & 0 deletions messages/ru/settings/statusPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"intervalOption": "{minutes} мин",
"slug": "Slug",
"slugTooltip": "Идентификатор URL для выделенной страницы статуса этой группы. Если оставить пустым, будет создан автоматически из публичного имени. Разрешены только строчные буквы, цифры и дефисы.",
"duplicateSlug": "Slug {slug} используется несколькими публичными группами. Перед сохранением сделайте slug каждой группы уникальным.",
"copy": "Пояснение",
"sortOrder": "Порядок",
"sortOrderTooltip": "Порядок отображения на публичной странице статуса. Меньшие значения — выше (как у приоритета провайдеров). При равных значениях порядок определяется по имени группы.",
Expand Down
1 change: 1 addition & 0 deletions messages/zh-CN/settings/statusPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"intervalOption": "{minutes} 分钟",
"slug": "Slug",
"slugTooltip": "用于分组独立状态页的 URL 标识。留空将基于对外显示名自动生成。只能包含小写字母、数字和连字符。",
"duplicateSlug": "Slug {slug} 被多个公开分组使用。请先修改为互不重复的 slug 再保存。",
"copy": "说明文案",
"sortOrder": "排序",
"sortOrderTooltip": "控制公开状态页中分组的显示顺序。数值越小越靠前(与供应商优先级一致)。相同数值按分组名排序。",
Expand Down
1 change: 1 addition & 0 deletions messages/zh-TW/settings/statusPage.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"intervalOption": "{minutes} 分鐘",
"slug": "Slug",
"slugTooltip": "用於分組獨立狀態頁的 URL 識別碼。留空將根據對外顯示名自動產生。只能包含小寫字母、數字與連字號。",
"duplicateSlug": "Slug {slug} 被多個公開分組使用。請先修改為互不重複的 slug 再儲存。",
"copy": "說明文案",
"sortOrder": "排序",
"sortOrderTooltip": "控制公開狀態頁中分組的顯示順序。數值越小越靠前(與供應商優先級一致)。相同數值按分組名排序。",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-syntax-highlighter": "^15",
"@typescript/native-preview": "7.0.0-dev.20260321.1",
"@typescript/native-preview": "7.0.0-dev.20260425.1",
"@vitest/coverage-v8": "^4",
"@vitest/ui": "^4",
"bun-types": "^1",
Expand Down
3 changes: 2 additions & 1 deletion src/app/[locale]/internal/dashboard/big-screen/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
import useSWR from "swr";
import { getDashboardRealtimeData } from "@/actions/dashboard-realtime";
import { type Locale, localeLabels, locales } from "@/i18n/config";
import { normalizePathnameForLocaleNavigation } from "@/i18n/pathname";
import { usePathname, useRouter } from "@/i18n/routing";
import { CURRENCY_CONFIG, type CurrencyCode } from "@/lib/utils/currency";

Expand Down Expand Up @@ -726,7 +727,7 @@ export default function BigScreenPage() {
const nextIndex = (currentIndex + 1) % locales.length;
const nextLocale = locales[nextIndex];

router.push(pathname || "/dashboard", { locale: nextLocale });
router.push(normalizePathnameForLocaleNavigation(pathname), { locale: nextLocale });
};

const theme = THEMES[themeMode as keyof typeof THEMES];
Expand Down
7 changes: 5 additions & 2 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "../globals.css";
import { headers } from "next/headers";
import { notFound } from "next/navigation";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";
import { getMessages, setRequestLocale } from "next-intl/server";
import { Footer } from "@/components/customs/footer";
import { Toaster } from "@/components/ui/sonner";
import { type Locale, locales } from "@/i18n/config";
Expand Down Expand Up @@ -82,6 +82,9 @@ export default async function RootLayout({
notFound();
}

// 将路由段 locale 固定到 next-intl 请求上下文,避免后续导航回落到默认语言。
setRequestLocale(locale);

// Load translation messages
const messages = await getMessages({ locale });
const timeZone = isPublicStatusRequest
Expand All @@ -93,7 +96,7 @@ export default async function RootLayout({
return (
<html lang={locale} suppressHydrationWarning>
<body className="antialiased">
<NextIntlClientProvider messages={messages} timeZone={timeZone} now={now}>
<NextIntlClientProvider locale={locale} messages={messages} timeZone={timeZone} now={now}>
<AppProviders>
<div className="flex min-h-[var(--cch-viewport-height,100vh)] flex-col bg-background text-foreground">
<div className="flex-1">{children}</div>
Expand Down
4 changes: 3 additions & 1 deletion src/app/[locale]/login/redirect-safety.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { normalizePathnameForLocaleNavigation } from "@/i18n/pathname";

const DEFAULT_REDIRECT_PATH = "/dashboard";
const PROTOCOL_LIKE_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/;

Expand Down Expand Up @@ -25,7 +27,7 @@ export function sanitizeRedirectPath(from: string): string {
return DEFAULT_REDIRECT_PATH;
}

return candidate;
return normalizePathnameForLocaleNavigation(candidate, DEFAULT_REDIRECT_PATH);
}

export function resolveLoginRedirectTarget(redirectTo: unknown, from: string): string {
Expand Down
Loading
Loading