From 27f2b442cb1945c958e046cff5aec8165e7971a9 Mon Sep 17 00:00:00 2001 From: win Date: Wed, 20 May 2026 12:15:25 +0800 Subject: [PATCH] chore: skip AppleDouble migrations + Alpine mirror for CN builds - migrations_runner: filter ._* / .hidden.sql so macOS metadata files don't break the migration scanner; new helper + regression tests cover this. - Docker images switch Alpine repo to mirrors.aliyun.com (CN build speed) and pin frontend pnpm to 9.15.9 to dodge v10 interactive build prompts. - dockerignore (root + backend): exclude **/.DS_Store and ._* AppleDouble artefacts so they don't sneak into image context. --- .dockerignore | 3 +++ Dockerfile | 11 +++++++-- backend/.dockerignore | 3 +++ .../internal/repository/migrations_runner.go | 24 +++++++++++++++---- .../migrations_runner_extra_test.go | 16 ++++++++++++- 5 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.dockerignore b/.dockerignore index 770145cb..19e455b0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -20,6 +20,9 @@ docs/ # OS files .DS_Store +**/.DS_Store +._* +**/._* Thumbs.db # Build artifacts diff --git a/Dockerfile b/Dockerfile index 7befb464..aacc7bfb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,10 +18,13 @@ ARG GOSUMDB=sum.golang.google.cn # ----------------------------------------------------------------------------- FROM ${NODE_IMAGE} AS frontend-builder +RUN sed -i 's#https://dl-cdn.alpinelinux.org/alpine#https://mirrors.aliyun.com/alpine#g' /etc/apk/repositories + WORKDIR /app/frontend -# Install pnpm -RUN corepack enable && corepack prepare pnpm@latest --activate +# Install pnpm. Keep this on v9 so Docker builds do not fail on pnpm v10's +# interactive build-script approval flow. +RUN corepack enable && corepack prepare pnpm@9.15.9 --activate # Install dependencies first (better caching) COPY frontend/package.json frontend/pnpm-lock.yaml ./ @@ -36,6 +39,8 @@ RUN pnpm run build # ----------------------------------------------------------------------------- FROM ${GOLANG_IMAGE} AS backend-builder +RUN sed -i 's#https://dl-cdn.alpinelinux.org/alpine#https://mirrors.aliyun.com/alpine#g' /etc/apk/repositories + # Build arguments for version info (set by CI) ARG VERSION= ARG COMMIT=docker @@ -83,6 +88,8 @@ FROM ${POSTGRES_IMAGE} AS pg-client # ----------------------------------------------------------------------------- FROM ${ALPINE_IMAGE} +RUN sed -i 's#https://dl-cdn.alpinelinux.org/alpine#https://mirrors.aliyun.com/alpine#g' /etc/apk/repositories + # Labels LABEL maintainer="Wei-Shaw " LABEL description="Sub2API - AI API Gateway Platform" diff --git a/backend/.dockerignore b/backend/.dockerignore index c1c2a854..010fcfbe 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -1,2 +1,5 @@ .cache/ .DS_Store +**/.DS_Store +._* +**/._* diff --git a/backend/internal/repository/migrations_runner.go b/backend/internal/repository/migrations_runner.go index 6dbb9fbd..9d65164c 100644 --- a/backend/internal/repository/migrations_runner.go +++ b/backend/internal/repository/migrations_runner.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io/fs" + "path/filepath" "sort" "strings" "time" @@ -144,11 +145,10 @@ func applyMigrationsFS(ctx context.Context, db *sql.DB, fsys fs.FS) error { // 获取所有 .sql 迁移文件并按文件名排序。 // 命名规范:使用零填充数字前缀(如 001_init.sql, 002_add_users.sql)。 - files, err := fs.Glob(fsys, "*.sql") + files, err := migrationFiles(fsys) if err != nil { return fmt.Errorf("list migrations: %w", err) } - sort.Strings(files) // 确保按文件名顺序执行迁移 for _, name := range files { // 读取迁移文件内容 @@ -392,14 +392,13 @@ func tableExists(ctx context.Context, db *sql.DB, tableName string) (bool, error } func latestMigrationBaseline(fsys fs.FS) (string, string, string, error) { - files, err := fs.Glob(fsys, "*.sql") + files, err := migrationFiles(fsys) if err != nil { return "", "", "", err } if len(files) == 0 { return "baseline", "baseline", "", nil } - sort.Strings(files) name := files[len(files)-1] contentBytes, err := fs.ReadFile(fsys, name) if err != nil { @@ -412,6 +411,23 @@ func latestMigrationBaseline(fsys fs.FS) (string, string, string, error) { return version, version, hash, nil } +func migrationFiles(fsys fs.FS) ([]string, error) { + files, err := fs.Glob(fsys, "*.sql") + if err != nil { + return nil, err + } + out := files[:0] + for _, name := range files { + base := filepath.Base(name) + if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "._") { + continue + } + out = append(out, name) + } + sort.Strings(out) + return out, nil +} + func checksumSet(values ...string) map[string]struct{} { out := make(map[string]struct{}, len(values)) for _, value := range values { diff --git a/backend/internal/repository/migrations_runner_extra_test.go b/backend/internal/repository/migrations_runner_extra_test.go index 5d67665e..10774d15 100644 --- a/backend/internal/repository/migrations_runner_extra_test.go +++ b/backend/internal/repository/migrations_runner_extra_test.go @@ -47,7 +47,8 @@ func TestLatestMigrationBaseline(t *testing.T) { t.Run("uses_latest_sorted_sql_file", func(t *testing.T) { fsys := fstest.MapFS{ - "001_init.sql": &fstest.MapFile{Data: []byte("CREATE TABLE t1(id int);")}, + "._999_final.sql": &fstest.MapFile{Data: []byte("AppleDouble metadata")}, + "001_init.sql": &fstest.MapFile{Data: []byte("CREATE TABLE t1(id int);")}, "010_final.sql": &fstest.MapFile{ Data: []byte("CREATE TABLE t2(id int);"), }, @@ -68,6 +69,19 @@ func TestLatestMigrationBaseline(t *testing.T) { }) } +func TestMigrationFiles_SkipsHiddenAppleDoubleSQL(t *testing.T) { + fsys := fstest.MapFS{ + "._001_init.sql": &fstest.MapFile{Data: []byte("AppleDouble metadata")}, + ".hidden.sql": &fstest.MapFile{Data: []byte("hidden")}, + "001_init.sql": &fstest.MapFile{Data: []byte("SELECT 1;")}, + "002_next.sql": &fstest.MapFile{Data: []byte("SELECT 2;")}, + } + + files, err := migrationFiles(fsys) + require.NoError(t, err) + require.Equal(t, []string{"001_init.sql", "002_next.sql"}, files) +} + func TestIsMigrationChecksumCompatible_AdditionalCases(t *testing.T) { require.False(t, isMigrationChecksumCompatible("unknown.sql", "db", "file"))