From 6867a3cb0196523fdf9d10184d52a0d3422141c2 Mon Sep 17 00:00:00 2001 From: shenlei Date: Wed, 2 Sep 2026 10:51:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=89=88=E6=9C=AC=E8=BD=A8=E9=81=93?= =?UTF-8?q?=E6=8C=89=E5=88=86=E6=94=AF=E5=BD=BB=E5=BA=95=E5=88=86=E5=BC=80?= =?UTF-8?q?=EF=BC=8C=E8=87=AA=E6=B5=8B=E5=88=86=E6=94=AF=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E5=85=B1=E7=94=A8=20feature=20=E8=BD=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 每个分支各有一条独立轨道(轨道名即分支名,master/main 沿用历史名 release), 轨道类型 kind 只决定构建号行为:release 上架自增、feature 自测不自增。 - branch_track 映射分支 → 轨道名,新增分支自动建同名轨道 - 迁移:旧的共用 release / feature 轨按分支拆开并复制已用构建号, 拆完没有分支引用的旧共用轨直接删除;无自测分支时保留 feature 轨作模板 - 分支管理页的下拉只改轨道类型(是否自增),不再影响版本号归属 - 打包设置页按分支分块填写版本号 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BJaZ913U5GVdTkYHiRpvDU --- CLAUDE.md | 2 +- README.md | 31 ++-- backend/routers/config.py | 196 +++++++++++++--------- config.example.json | 10 +- frontend/src/__tests__/ConfigView.test.js | 24 +-- frontend/src/views/ConfigView.vue | 48 +++--- tests/test_api_config.py | 57 ++++--- tests/test_build_service.py | 11 +- 8 files changed, 216 insertions(+), 163 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f6aeaad..7b3531c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ FastAPI + Vue 3 iOS app auto-packaging service. Supports Ad_Hoc and App_Store bu - `backend/config.py` — loads .env, defines all config constants - `frontend/src/views/` — BuildView, HistoryView, ConfigView - `config.json` — runtime config (apps, schemes, servers, upload, branches, versions) managed via web UI -- Versions are per-track: each release branch has its own track (master/main keeps the historical `release` track, others are named after the branch, e.g. `develop`; build number auto-increments on App_Store), and all self-test branches share the `feature` track (never increments); each branch's track name lives in `config["branch_track"]`, track records in `config["versions"]["tracks"]` carry `kind` (release/feature) +- Versions are per-branch: every branch owns a track in `config["versions"]["tracks"]`, named after the branch (master/main keeps the historical name `release`); `config["branch_track"]` maps branch → track name, and each track's `kind` is `release` (build number auto-increments on App_Store) or `feature` (never increments) ## Conventions diff --git a/README.md b/README.md index b7fa9cb..b2e941a 100644 --- a/README.md +++ b/README.md @@ -187,25 +187,36 @@ GIT_PASSWORD=<仅 read_repository 权限的 Personal Access Token> | App 管理 | 应用名称、Scheme、服务器环境、upload_key 等 | | Scheme 管理 | 打包 Scheme 配置 | | 服务器环境 | 测试/正式/自定义环境的 API 地址、Universal Link 等 | -| 分支管理 | 可打包的代码分支列表,以及每个分支所属的版本轨道(上架分支各自独立) | +| 分支管理 | 可打包的代码分支列表,以及每个分支的版本轨道类型(上架 / 自测) | | 版本号 | 服务端统一管理 App_Ver 和 Build_Ver,不修改分支源码;按版本轨道分开管理(见下) | | 上传配置 | 服务端内置 OSS / WebDAV 分发,生成 IPA、manifest、下载页和二维码 | ### 版本号与版本轨道 -版本号按 **轨道(track)** 分开管理,各轨道的 App_Ver 各自独立填写、互不影响: +**每个分支各有一条版本轨道(track)**,App_Ver 与构建号完全独立、互不影响。轨道名即分支名(master / main 沿用历史名称 `release`),轨道类型 `kind` 决定构建号行为: -| 轨道 | 适用分支 | 构建号行为 | -|------|----------|-----------| -| `release`(上架) | master / main | App_Store 打包时按当前 App_Ver 自动 +1 | -| `develop` 等以分支名命名的上架轨 | develop、release-*、hotfix-* 等其它上架分支,每个分支一条 | 同上,与 `release` 轨互不影响 | -| `feature`(自测) | feature/* 等其他分支,共用一条 | 永不自增,始终使用配置页填写的值(默认 `.0`) | +| 轨道类型 | 默认适用分支 | 构建号行为 | +|----------|--------------|-----------| +| `release`(上架) | master、main、develop、dev、release-*、hotfix-* | App_Store 打包时按当前 App_Ver 自动 +1 | +| `feature`(自测) | 其余分支 | 永不自增,始终使用配置页填写的值(默认 `.0`) | -- 分支归属默认按名字推断(master/main → `release` 轨,develop/dev/release-*/hotfix-* → 以分支名命名的轨,其余 → `feature`),可在「分支管理」页逐个改;改成「上架」即为该分支建一条独立轨道。 -- 「打包设置」页按上架分支分块填写版本号,最后一块是共用的自测版本号。 +```jsonc +"versions": { + "tracks": { + "release": { "kind": "release", "app_ver": "2.195.0", "build_map": { "2.195.0": 7 } }, // master + "develop": { "kind": "release", "app_ver": "2.196.0", "build_map": {} }, + "feature/login": { "kind": "feature", "app_ver": "2.100.0", "build_map": {} } + } +}, +"branch_track": { "master": "release", "develop": "develop", "feature/login": "feature/login" } +``` + +- 新增分支时自动建一条同名轨道,类型按分支名推断,可在「分支管理」页逐个改(只改构建号是否自增,版本号仍各自独立)。 +- 「打包设置」页按分支分块填写版本号。 - 每条轨道内的构建号按 App_Ver 独立记录(`build_map`),回退到打过包的旧版本号时会从它自己的最大值继续,不会与 App Store Connect 上已传构建号冲突。 - App Store 的构建号唯一性按 App_Ver 全局判定,因此两个上架分支若临时用了同一个 App_Ver,递增时会跨轨道取最大值 +1,避免重号被拒。 -- 旧配置(master/develop 共用的 `release` 轨)首次启动时自动拆开:master 留在 `release` 轨,develop 新建 `develop` 轨并复制一份已用构建号。 +- 旧配置(master/develop 共用的 `release` 轨、各自测分支共用的 `feature` 轨)首次启动时自动拆开:master 留在 `release` 轨,其余分支新建同名轨道并复制一份已用构建号。 +- 需要两个分支共用同一条版本线时,可直接把 `branch_track` 里的值指向另一条轨道名。 ### 服务端自动化资源 diff --git a/backend/routers/config.py b/backend/routers/config.py index 3d87aba..0f05f9f 100644 --- a/backend/routers/config.py +++ b/backend/routers/config.py @@ -93,16 +93,17 @@ DEFAULT_SERVERS = { DEFAULT_APP_VER = "2.180.0" -# 版本轨道:每个上架分支各有一条独立轨道,App_Store 打包时构建号自增; -# 所有自测分支共用一条 feature 轨,构建号永不自增。各轨道的 App_Ver 互不影响。 -# 轨道名:master/main 沿用历史名称 release,其余上架分支以分支名命名(develop、hotfix-x…)。 +# 版本轨道:每个分支各有一条独立轨道,轨道名即分支名(master/main 沿用历史名称 release), +# 各分支的 App_Ver 与构建号互不影响。轨道类型(kind)决定构建号行为: +# release(上架)— App_Store 打包时自增;feature(自测)— 永不自增。 KIND_RELEASE = "release" KIND_FEATURE = "feature" -TRACK_RELEASE = "release" # master/main 的上架轨(沿用旧名,历史构建号原地保留) -TRACK_FEATURE = "feature" # 自测轨的固定轨道名 +KINDS = (KIND_RELEASE, KIND_FEATURE) +TRACK_RELEASE = "release" # master/main 的轨道名(沿用旧名,历史构建号原地保留) +TRACK_FEATURE = "feature" # 旧版所有自测分支共用的轨道名,迁移时按分支拆开 FIXED_TRACKS = (TRACK_RELEASE, TRACK_FEATURE) -# 按名字推断轨道的默认规则;config["branch_track"] 中的显式配置优先。 +# 按名字推断轨道类型的默认规则;轨道记录里的 kind 为准,此处只用于新建轨道。 MASTER_BRANCH_NAMES = {"master", "main"} RELEASE_BRANCH_NAMES = MASTER_BRANCH_NAMES | {"develop", "dev"} RELEASE_BRANCH_PREFIXES = ("release/", "release-", "hotfix/", "hotfix-") @@ -114,16 +115,19 @@ def _is_release_branch(branch: str) -> bool: return name in RELEASE_BRANCH_NAMES or name.startswith(RELEASE_BRANCH_PREFIXES) -def _release_track_name(branch: str) -> str: - """上架分支的轨道名:master/main 为 release,其余即分支名(与固定轨重名时避让)。""" - if (branch or "").strip().lower() in MASTER_BRANCH_NAMES: - return TRACK_RELEASE - return f"release:{branch}" if branch in FIXED_TRACKS else branch +def _default_kind_for_branch(branch: str) -> str: + """新建轨道时按分支名推断轨道类型。""" + return KIND_RELEASE if _is_release_branch(branch) else KIND_FEATURE def _default_track_for_branch(branch: str) -> str: - """按分支名推断所属版本轨道名。""" - return _release_track_name(branch) if _is_release_branch(branch) else TRACK_FEATURE + """分支自己的轨道名:master/main 沿用历史名 release,其余即分支名。 + + 分支恰好叫 release / feature(且不是 master)时加前缀避让,免得撞上固定轨道名。 + """ + if (branch or "").strip().lower() in MASTER_BRANCH_NAMES: + return TRACK_RELEASE + return f"branch:{branch}" if branch in FIXED_TRACKS else branch def _new_track(app_ver: str = DEFAULT_APP_VER, kind: str = KIND_RELEASE) -> dict: @@ -133,28 +137,40 @@ def _new_track(app_ver: str = DEFAULT_APP_VER, kind: str = KIND_RELEASE) -> dict return {"kind": kind, "app_ver": app_ver, "build_map": {}} -def _seed_release_track(tracks: dict, legacy: dict = None) -> dict: - """新建上架轨的初值:优先沿用旧共用轨(含已用构建号),否则沿用任一上架轨的 App_Ver。""" +def _seed_track(tracks: dict, kind: str, legacy: dict = None) -> dict: + """新建轨道的初值:优先沿用被拆分的旧共用轨(含已用构建号), + 否则沿用任一同类型轨道的 App_Ver,都没有则用默认版本号。""" if legacy: - return {"kind": KIND_RELEASE, "app_ver": legacy["app_ver"], "build_map": dict(legacy["build_map"])} + return {"kind": kind, "app_ver": legacy["app_ver"], "build_map": dict(legacy["build_map"])} for track in tracks.values(): - if track.get("kind") == KIND_RELEASE: - return _new_track(track.get("app_ver") or DEFAULT_APP_VER) - return _new_track() + if isinstance(track, dict) and track.get("kind") == kind: + return _new_track(track.get("app_ver") or DEFAULT_APP_VER, kind) + return _new_track(kind=kind) -def _ensure_track(config: dict, track_name: str) -> dict: - """确保轨道存在;新建的上架轨沿用已有上架轨的 App_Ver,构建号从 .0 起。""" +def _ensure_track(config: dict, track_name: str, kind: str = None) -> dict: + """确保轨道存在(新建时沿用同类型轨道的 App_Ver,构建号从 .0 起); + 显式传 kind 时同时把轨道类型改成它。""" tracks = config.setdefault("versions", {}).setdefault("tracks", {}) track = tracks.get(track_name) if not isinstance(track, dict): - track = tracks[track_name] = ( - _new_track(kind=KIND_FEATURE) if track_name == TRACK_FEATURE - else _seed_release_track(tracks) + track = tracks[track_name] = _seed_track( + tracks, kind or _default_kind_for_track(track_name) ) + elif kind: + track["kind"] = kind return track +def _default_kind_for_track(track_name: str) -> str: + """轨道记录缺 kind 时按轨道名推断(轨道名即分支名)。""" + if track_name == TRACK_RELEASE: + return KIND_RELEASE + if track_name == TRACK_FEATURE: + return KIND_FEATURE + return _default_kind_for_branch(track_name) + + def _legacy_single_track(versions: dict) -> dict: """把最旧的单轨结构读成 {app_ver, build_map}。""" app_ver = versions.get("app_ver") or DEFAULT_APP_VER @@ -173,13 +189,25 @@ def _legacy_single_track(versions: dict) -> dict: return {"app_ver": app_ver, "build_map": build_map} -def _ensure_versions(config: dict) -> bool: - """确保 versions 为「每个上架分支一条轨 + 一条 feature 轨」结构,并迁移旧配置。 +def _legacy_shared_track(tracks: dict, name: str) -> dict: + """取出待拆分的旧共用轨(迁移后的轨道都带 kind,没有 kind 的即旧共用轨)。""" + track = tracks.get(name) + if not isinstance(track, dict) or "kind" in track: + return None + build_map = track.get("build_map") + return { + "app_ver": track.get("app_ver") or DEFAULT_APP_VER, + "build_map": build_map if isinstance(build_map, dict) else {}, + } - 旧式单轨 ``{"app_ver": ..., "build_map": ...}`` 和旧的 master/develop 共用 - ``release`` 轨,都会按上架分支拆开:master/main 留在 release 轨(历史构建号原地 - 保留),develop 等其它上架分支新建以分支名命名的轨道,并复制一份已用构建号, - 使拆分后每个分支都从原来的构建号继续递增。 + +def _ensure_versions(config: dict) -> bool: + """确保 versions 为「每个分支一条轨道」结构,并迁移旧配置。 + + 旧式单轨 ``{"app_ver": ..., "build_map": ...}``、旧的 master/develop 共用 + ``release`` 轨和所有自测分支共用的 ``feature`` 轨,都会按分支拆开: + master/main 留在 release 轨(历史构建号原地保留),其余分支新建以分支名命名的 + 轨道并复制一份已用构建号,使拆分后每个分支都从原来的构建号继续。 """ versions = config.get("versions") changed = False @@ -188,36 +216,28 @@ def _ensure_versions(config: dict) -> bool: config["versions"] = versions changed = True - legacy = None # 需要拆分到各上架分支的旧共用轨 + # 待拆分到各分支的旧共用轨(按类型各一条) + legacy = {KIND_RELEASE: None, KIND_FEATURE: None} tracks = versions.get("tracks") if not isinstance(tracks, dict): - legacy = _legacy_single_track(versions) + legacy[KIND_RELEASE] = _legacy_single_track(versions) + legacy[KIND_FEATURE] = dict(legacy[KIND_RELEASE], build_map={}) tracks = {} versions["tracks"] = tracks changed = True else: - # 迁移后的轨道都带 kind;没有 kind 的 release 轨即旧的 master/develop 共用轨, - # 它继续作为 master/main 的轨道,同时作为其它上架分支新轨的初值。 - shared = tracks.get(TRACK_RELEASE) - if isinstance(shared, dict) and "kind" not in shared: - build_map = shared.get("build_map") - legacy = { - "app_ver": shared.get("app_ver") or DEFAULT_APP_VER, - "build_map": build_map if isinstance(build_map, dict) else {}, - } - - feature = tracks.get(TRACK_FEATURE) - if not isinstance(feature, dict): - tracks[TRACK_FEATURE] = _new_track((legacy or {}).get("app_ver") or DEFAULT_APP_VER, KIND_FEATURE) - changed = True + # release 轨拆分后继续作为 master/main 的轨道,feature 轨拆分后成为无人引用的模板 + legacy[KIND_RELEASE] = _legacy_shared_track(tracks, TRACK_RELEASE) + legacy[KIND_FEATURE] = _legacy_shared_track(tracks, TRACK_FEATURE) + splitting = any(legacy.values()) + split_kinds = set() # 实际被拆分出去的旧共用轨类型 for name, track in tracks.items(): if not isinstance(track, dict): - track = tracks[name] = _new_track() + track = tracks[name] = _new_track(kind=_default_kind_for_track(name)) changed = True - kind = KIND_FEATURE if name == TRACK_FEATURE else KIND_RELEASE - if track.get("kind") != kind: - track["kind"] = kind + if track.get("kind") not in KINDS: + track["kind"] = _default_kind_for_track(name) changed = True if not track.get("app_ver"): track["app_ver"] = DEFAULT_APP_VER @@ -226,7 +246,7 @@ def _ensure_versions(config: dict) -> bool: track["build_map"] = {} changed = True - # 分支归属:值为轨道名,缺省按分支名规则推断 + # 分支归属:值为轨道名,缺省用分支自己的轨道 branch_track = config.get("branch_track") if not isinstance(branch_track, dict): branch_track = {} @@ -234,31 +254,44 @@ def _ensure_versions(config: dict) -> bool: changed = True for branch in config.get("branches", []) or []: name = branch_track.get(branch) - if name == TRACK_RELEASE and legacy is not None: - # 旧的共用上架轨 → master/main 留在 release 轨,其余分支拆到自己的轨 - name = _release_track_name(branch) + if splitting and name in FIXED_TRACKS: + # 旧的共用轨 → 各分支自己的轨道(master/main 仍留在 release 轨) + name = _default_track_for_branch(branch) if not isinstance(name, str) or not name: name = _default_track_for_branch(branch) if branch_track.get(branch) != name: branch_track[branch] = name changed = True if name not in tracks: - tracks[name] = _seed_release_track(tracks, legacy) + kind = _default_kind_for_branch(branch) + tracks[name] = _seed_track(tracks, kind, legacy[kind]) + if legacy[kind]: + split_kinds.add(kind) + changed = True + + # 内容已复制到各分支轨道、又没有分支引用的旧共用轨,直接删掉,免得配置里留下无用条目 + for name in FIXED_TRACKS: + kind = _default_kind_for_track(name) + if kind in split_kinds and name in tracks and name not in branch_track.values(): + tracks.pop(name) changed = True return changed def _primary_track_name(config: dict) -> str: - """主上架轨:第一个上架分支所属的轨道;没有上架分支时退回 feature 轨。 + """主上架轨:第一个上架分支(kind=release)所属的轨道;都没有则取第一个分支的轨道。 仅用于顶层 app_ver/build_ver 的兼容字段和未指定轨道时的缺省值。 """ - for branch in config.get("branches", []) or []: + fallback = TRACK_RELEASE + for index, branch in enumerate(config.get("branches", []) or []): name = get_track_name(config, branch) - if name != TRACK_FEATURE: + if index == 0: + fallback = name + if is_release_track(get_track(config, name)): return name - return TRACK_FEATURE + return fallback def get_track_name(config: dict, branch: str) -> str: @@ -273,14 +306,14 @@ def get_track_name(config: dict, branch: str) -> str: def get_track(config: dict, track_name: str) -> dict: """取某条轨道的版本记录副本(不存在时返回默认值;只读,不写回配置)。 - 未迁移的旧记录没有 kind,按轨道名补上:只有固定的 feature 轨是自测轨。 + 未迁移的旧记录没有 kind,按轨道名(即分支名)推断补上。 """ tracks = (config.get("versions") or {}).get("tracks") or {} - kind = KIND_FEATURE if track_name == TRACK_FEATURE else KIND_RELEASE track = tracks.get(track_name) + kind = _default_kind_for_track(track_name) if not isinstance(track, dict): return _new_track(kind=kind) - return {**track, "kind": track.get("kind") or kind} + return {**track, "kind": track.get("kind") if track.get("kind") in KINDS else kind} def is_release_track(track: dict) -> bool: @@ -303,21 +336,19 @@ def next_build_no(tracks: dict, app_ver: str) -> int: return max(used, default=0) + 1 -def resolve_track(config: dict, branch: str, track: str) -> str: - """把用户传入的轨道值解析为轨道名。 +def resolve_track(config: dict, branch: str, track: str): + """把用户传入的轨道值解析为 (轨道名, 轨道类型)。 - 兼容按类型指定:``release`` 表示该分支自己的上架轨,``feature`` 表示共用自测轨; - 也可直接传已存在的轨道名(多个分支共用同一条版本线)。 + 传类型(release / feature)时用分支自己的轨道,并把它设成该类型; + 也可直接传已存在的轨道名,让这个分支与另一个分支共用同一条版本线(类型不变)。 """ value = (track or "").strip() if not value: - return _default_track_for_branch(branch) - if value == KIND_FEATURE: - return TRACK_FEATURE - if value == KIND_RELEASE: - return _release_track_name(branch) + return _default_track_for_branch(branch), _default_kind_for_branch(branch) + if value in KINDS: + return _default_track_for_branch(branch), value if value in ((config.get("versions") or {}).get("tracks") or {}): - return value + return value, None raise HTTPException(status_code=400, detail=f"未知的版本轨道: {track}") @@ -777,9 +808,10 @@ async def get_branches(): @router.post("/branches") async def add_branch(data: dict): - """新增分支,可指定所属版本轨道(缺省按分支名推断)。 + """新增分支,可指定版本轨道类型(缺省按分支名推断)。 - track 传 release 表示这个分支用自己独立的上架版本号,feature 表示共用自测版本号; + 每个分支都有自己独立的一条轨道(轨道名即分支名):track 传 release 表示上架用、 + App_Store 打包时构建号自增,feature 表示自测用、构建号不自增; 也可直接传已有轨道名,让该分支与另一个分支共用同一条版本线。 """ name = data.get("name", "").strip() @@ -791,21 +823,21 @@ async def add_branch(data: dict): branches = config.get("branches", ["main"]) if name in branches: raise HTTPException(status_code=400, detail="分支已存在") - track = resolve_track(config, name, data.get("track")) + track, kind = resolve_track(config, name, data.get("track")) branches.append(name) config["branches"] = branches config.setdefault("branch_track", {})[name] = track - _ensure_track(config, track) + _ensure_track(config, track, kind) save_config(config) return {"message": "分支已添加", "track": track} @router.put("/branches/track") async def update_branch_track(data: dict): - """修改分支所属的版本轨道(release=该分支独立的上架版本号,feature=共用自测版本号)。 + """修改分支的版本轨道类型(release=上架,构建号自增;feature=自测,不自增)。 - 改成 release 时会为这个分支建一条以分支名命名的轨道(初值沿用已有上架轨的 App_Ver), - 此后它的版本号与其它分支互不影响。分支名可能含 /(如 feature/login),因此走请求体而非路径参数。 + 每个分支用自己的一条轨道,切换类型只改这条轨道的行为,版本号仍与其它分支互不影响。 + 分支名可能含 /(如 feature/login),因此走请求体而非路径参数。 """ branch = (data.get("branch") or "").strip() if not (data.get("track") or "").strip(): @@ -815,9 +847,9 @@ async def update_branch_track(data: dict): config = load_config() if branch not in config.get("branches", []): raise HTTPException(status_code=404, detail="分支不存在") - track = resolve_track(config, branch, data.get("track")) + track, kind = resolve_track(config, branch, data.get("track")) config.setdefault("branch_track", {})[branch] = track - _ensure_track(config, track) + _ensure_track(config, track, kind) save_config(config) return {"message": "分支版本轨道已更新", "branch": branch, "track": track} @@ -894,7 +926,7 @@ async def get_versions(): config = load_config() tracks = { name: { - "kind": KIND_FEATURE if name == TRACK_FEATURE else KIND_RELEASE, + "kind": track.get("kind") if track.get("kind") in KINDS else _default_kind_for_track(name), "app_ver": track.get("app_ver") or DEFAULT_APP_VER, "build_ver": track_build_ver(track), "build_map": track.get("build_map") or {}, diff --git a/config.example.json b/config.example.json index 4509d0b..c8ef74e 100644 --- a/config.example.json +++ b/config.example.json @@ -47,7 +47,7 @@ } }, "next_app_id_prefix": 3, - "branches": ["master", "develop"], + "branches": ["master", "develop", "feature/示例分支"], "upload": { "mode": "webdav", "oss": { @@ -76,8 +76,12 @@ "tracks": { "release": { "kind": "release", "app_ver": "2.180.0", "build_map": {} }, "develop": { "kind": "release", "app_ver": "2.180.0", "build_map": {} }, - "feature": { "kind": "feature", "app_ver": "2.180.0", "build_map": {} } + "feature/示例分支": { "kind": "feature", "app_ver": "2.180.0", "build_map": {} } } }, - "branch_track": { "master": "release", "develop": "develop" } + "branch_track": { + "master": "release", + "develop": "develop", + "feature/示例分支": "feature/示例分支" + } } diff --git a/frontend/src/__tests__/ConfigView.test.js b/frontend/src/__tests__/ConfigView.test.js index 0376f43..59d3be7 100644 --- a/frontend/src/__tests__/ConfigView.test.js +++ b/frontend/src/__tests__/ConfigView.test.js @@ -30,8 +30,7 @@ function mockFetch(url) { primary_track: 'release', tracks: { release: { kind: 'release', app_ver: '2.195.0', build_ver: '2.195.0.0', build_map: {} }, - dev: { kind: 'release', app_ver: '2.196.0', build_ver: '2.196.0.0', build_map: {} }, - feature: { kind: 'feature', app_ver: '2.100.0', build_ver: '2.100.0.0', build_map: {} }, + dev: { kind: 'feature', app_ver: '2.100.0', build_ver: '2.100.0.0', build_map: {} }, }, branch_track: { main: 'release', dev: 'dev' }, }, @@ -193,7 +192,7 @@ describe('ConfigView.vue', () => { expect(fetch.mock.calls.map(c => c[0])).toEqual(['/api/config/apps']) }) - it('每个上架分支与自测轨各自独立展示与保存', async () => { + it('每个分支的版本号各自独立展示与保存', async () => { const calls = [] fetch.mockImplementation((url, opts) => { if (url === '/api/config/versions' && opts?.method === 'PUT') { @@ -214,17 +213,20 @@ describe('ConfigView.vue', () => { await buildMenu.trigger('click') await wrapper.vm.$nextTick() - const inputs = wrapper.findAll('input[type="text"]') - expect(inputs[0].element.value).toBe('2.195.0') // main 分支(release 轨)App_Ver - expect(inputs[2].element.value).toBe('2.196.0') // dev 分支(dev 轨)App_Ver - expect(inputs[4].element.value).toBe('2.100.0') // feature App_Ver + // 每个分支一块:main(上架,release 轨)、dev(自测,dev 轨) + expect(wrapper.text()).toContain('上架版本号(main)') + expect(wrapper.text()).toContain('自测版本号(dev)') - await inputs[4].setValue('2.101.0') - const saveBtn = wrapper.findAll('.btn-primary').find(b => b.text() === '保存自测版本号') + const inputs = wrapper.findAll('input[type="text"]') + expect(inputs[0].element.value).toBe('2.195.0') // main 分支 App_Ver + expect(inputs[2].element.value).toBe('2.100.0') // dev 分支 App_Ver + + await inputs[2].setValue('2.101.0') + const saveBtn = wrapper.findAll('.btn-primary').find(b => b.text() === '保存 dev 版本号') await saveBtn.trigger('click') await flushPromises() - expect(calls).toEqual([{ track: 'feature', app_ver: '2.101.0', build_ver: '2.101.0.0' }]) + expect(calls).toEqual([{ track: 'dev', app_ver: '2.101.0', build_ver: '2.101.0.0' }]) }) it('分支管理可切换版本轨道', async () => { @@ -245,7 +247,7 @@ describe('ConfigView.vue', () => { await wrapper.vm.$nextTick() const select = wrapper.find('.config-table select') - expect(select.element.value).toBe('release') + expect(select.element.value).toBe('release') // main → release 轨(上架) await select.setValue('feature') await flushPromises() diff --git a/frontend/src/views/ConfigView.vue b/frontend/src/views/ConfigView.vue index 0ed1de4..f3f1320 100644 --- a/frontend/src/views/ConfigView.vue +++ b/frontend/src/views/ConfigView.vue @@ -172,8 +172,8 @@ {{ b }} @@ -289,39 +289,30 @@

打包设置

-