feat: 版本轨道按分支彻底分开,自测分支不再共用 feature 轨

每个分支各有一条独立轨道(轨道名即分支名,master/main 沿用历史名 release),
轨道类型 kind 只决定构建号行为:release 上架自增、feature 自测不自增。

- branch_track 映射分支 → 轨道名,新增分支自动建同名轨道
- 迁移:旧的共用 release / feature 轨按分支拆开并复制已用构建号,
  拆完没有分支引用的旧共用轨直接删除;无自测分支时保留 feature 轨作模板
- 分支管理页的下拉只改轨道类型(是否自增),不再影响版本号归属
- 打包设置页按分支分块填写版本号

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJaZ913U5GVdTkYHiRpvDU
This commit is contained in:
shenlei
2026-09-02 10:51:35 +09:00
co-authored by Claude Opus 5
parent 9b4667cab5
commit 6867a3cb01
8 changed files with 216 additions and 163 deletions
+13 -11
View File
@@ -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()
+20 -28
View File
@@ -172,8 +172,8 @@
<td><strong>{{ b }}</strong></td>
<td>
<select :value="branchKind(b)" style="width: auto; padding: 4px 8px;" @change="setBranchTrack(b, $event.target.value)">
<option value="release">上架本分支独立版本号</option>
<option value="feature">自测共用 feature 版本号</option>
<option value="release">上架App_Store 打包构建号自增</option>
<option value="feature">自测构建号不自增</option>
</select>
</td>
<td class="action-btns">
@@ -289,39 +289,30 @@
<div v-if="tab === 'build'">
<h2>打包设置</h2>
<div style="max-width: 500px;">
<template v-for="t in releaseTracks" :key="t.name">
<h4 class="section-title">上架版本号{{ t.branches.join(' / ') }}</h4>
<template v-for="t in branchTracks" :key="t.name">
<h4 class="section-title">
{{ t.kind === 'release' ? '上架版本号' : '自测版本号' }}{{ t.branches.join(' / ') }}
</h4>
<div class="form-group">
<label>App_Ver应用版本</label>
<input type="text" v-model="versions[t.name].app_ver" placeholder="2.180.0" @input="onAppVerInput(t.name)">
<div style="font-size: 12px; color: #999; margin-top: 4px;">格式主版本.次版本.修订号该版本号只用于 {{ t.branches.join(' / ') }} 分支轨道 {{ t.name }}与其它分支互不影响</div>
<div style="font-size: 12px; color: #999; margin-top: 4px;">
格式主版本.次版本.修订号该版本号只用于 {{ t.branches.join(' / ') }} 分支轨道 {{ t.name }}与其它分支互不影响
</div>
</div>
<div class="form-group">
<label>Build_Ver构建号</label>
<input type="text" v-model="versions[t.name].build_ver" placeholder="2.180.0.0">
<div style="font-size: 12px; color: #999; margin-top: 4px;">
<div v-if="t.kind === 'release'" style="font-size: 12px; color: #999; margin-top: 4px;">
四段数字前三位须与 App_Ver 一致修改 App_Ver 会自动切换为该版本号已记录的构建号
每个版本号的构建号独立递增App_Store 打包时自动 +1一般无需手动修改
</div>
</div>
<button class="btn btn-primary" style="width: auto; padding: 10px 32px; margin-bottom: 24px;" @click="saveVersions(t.name)">保存 {{ t.branches.join(' / ') }} 版本号</button>
</template>
<template v-if="versions.feature">
<h4 class="section-title">自测版本号feature 分支</h4>
<div class="form-group">
<label>App_Ver应用版本</label>
<input type="text" v-model="versions.feature.app_ver" placeholder="2.180.0" @input="onAppVerInput('feature')">
<div style="font-size: 12px; color: #999; margin-top: 4px;">所有自测分支共用与上架版本号各自独立互不影响</div>
</div>
<div class="form-group">
<label>Build_Ver构建号</label>
<input type="text" v-model="versions.feature.build_ver" placeholder="2.180.0.0">
<div style="font-size: 12px; color: #999; margin-top: 4px;">
仅供自测打包时<strong>不会自动递增</strong>始终使用这里填写的值默认 .0需要区分时请手动修改
<div v-else style="font-size: 12px; color: #999; margin-top: 4px;">
四段数字前三位须与 App_Ver 一致自测轨打包时<strong>不会自动递增</strong>
始终使用这里填写的值默认 .0需要区分时请手动修改
</div>
</div>
<button class="btn btn-primary" style="width: auto; padding: 10px 32px; margin-bottom: 24px;" @click="saveVersions('feature')">保存自测版本号</button>
<button class="btn btn-primary" style="width: auto; padding: 10px 32px; margin-bottom: 24px;" @click="saveVersions(t.name)">保存 {{ t.branches.join(' / ') }} 版本号</button>
</template>
<template v-if="isAdmin">
@@ -658,8 +649,8 @@ const servers = ref({})
const branches = ref([])
const newBranch = ref('')
const buildSettings = ref({})
// 版本轨道:每个上架分支(master、develop 等)一条独立轨道,轨道名即分支名;
// 自测分支共用固定的 feature 轨。各轨道的版本号互不影响
// 版本轨道:每个分支一条独立轨道,轨道名即分支名(master/main 沿用历史名 release)。
// 轨道类型 kind 决定构建号行为:release=上架、App_Store 打包自增;feature=自测、不自增
const versions = ref({}) // 轨道名 -> { app_ver, build_ver }
const buildMap = ref({}) // 轨道名 -> { App_Ver: 已用构建号 }
const trackKind = ref({}) // 轨道名 -> release | feature
@@ -684,14 +675,15 @@ const applyVersions = (data) => {
// 分支所属轨道的类型(供分支管理里的下拉框显示)
const branchKind = (branch) => trackKind.value[branchTrack.value[branch]] || 'feature'
// 打包设置里按上架轨道分块展示;多个分支共用一条轨道时只显示一块
const releaseTracks = computed(() => {
// 打包设置里按分支分块展示版本号;多个分支共用一条轨道时只显示一块
const branchTracks = computed(() => {
const seen = new Set()
return branches.value
.map(b => branchTrack.value[b])
.filter(name => name && trackKind.value[name] === 'release' && !seen.has(name) && seen.add(name))
.filter(name => name && versions.value[name] && !seen.has(name) && seen.add(name))
.map(name => ({
name,
kind: trackKind.value[name] || 'release',
branches: branches.value.filter(b => branchTrack.value[b] === name),
}))
})