From 3223a5ced74956efa85cf1a9d4be0d51a17b2297 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 11 May 2026 14:45:04 +0300 Subject: [PATCH] fix(version): channel-based label, no semver fallback for branch builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stale package.json создавал ложное впечатление rollback'а: staging показывал "v1.2.0" хотя последний tag — v1.2.2 (package.json не bump'ился со времён v1.2.0 release). Vote A — branch builds НЕ используют package.json как version label. Vite определяет channel: serverTag != none → что прод/staging RUNS clientTag → что build'ил CI tag pipeline (CI_COMMIT_TAG set) CI без tag → 'dev' literal (staging / branch CI build) локально → 'local' literal (pnpm dev) Detection через новый __APP_CI__ define из process.env.CI (GitLab сетает CI=true). Local pnpm dev: CI undefined → channel='local'. package.json.version всё ещё инжектится как __APP_VERSION__ и видна в tooltip badge'а как 'floor vX.Y.Z' — не как primary label. Files: - vite.config.ts: + APP_CI from process.env.CI - src/vite-env.d.ts: + __APP_CI__ declare - src/components/version/useAppVersion.ts: + clientCI - src/components/version/VersionBadge.tsx: channel logic вместо fallback Verify all three modes: local → 'local · {commit}' CI branch → 'dev · {commit}' CI tag v1.3.1 → 'v1.3.1 · {commit}' Tests: 116 pass. --- .../src/components/version/VersionBadge.tsx | 24 +++++++++++++++---- .../src/components/version/useAppVersion.ts | 6 ++++- ordinis-admin-ui/src/vite-env.d.ts | 1 + ordinis-admin-ui/vite.config.ts | 5 ++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/ordinis-admin-ui/src/components/version/VersionBadge.tsx b/ordinis-admin-ui/src/components/version/VersionBadge.tsx index a1f92e9..b0b7559 100644 --- a/ordinis-admin-ui/src/components/version/VersionBadge.tsx +++ b/ordinis-admin-ui/src/components/version/VersionBadge.tsx @@ -14,6 +14,7 @@ export function VersionBadge() { const { clientVersion, clientTag, + clientCI, clientCommit, clientBuiltAt, serverVersion, @@ -22,18 +23,31 @@ export function VersionBadge() { updateAvailable, } = useAppVersion() - // Priority: serverTag (что прод RUNS) → clientTag (что build'илось на tag - // pipeline) → v${clientVersion} (package.json fallback для local / branch). - // 'none' — Spring Boot build-info sentinel когда tag отсутствует. + // Channel-based label per "Vote A" — НЕ показываем package.json fallback + // как версию когда tag'а нет. Stale package.json (e.g. 1.2.0 когда уже + // выпущен v1.2.2) создавал ложное впечатление rollback'а. + // + // Channels: + // serverTag real — что прод/staging RUNS (most authoritative) + // clientTag — что build'ил CI tag pipeline + // CI без tag — 'dev' (staging / branch CI build) + // no CI no tag — 'local' (pnpm dev на машине разработчика) + // + // package.json.version используется ТОЛЬКО в tooltip как build-time floor + // — пользователь не видит его если не наведёт. const displayVersion = serverTag && serverTag !== 'none' ? serverTag : clientTag ? `v${clientTag}` - : `v${clientVersion}` + : clientCI + ? 'dev' + : 'local' const tooltip = [ - `client: ${clientTag ? `v${clientTag}` : `v${clientVersion}`} (${clientCommit})`, + `client: ${ + clientTag ? `v${clientTag}` : clientCI ? `dev (floor v${clientVersion})` : `local (floor v${clientVersion})` + } (${clientCommit})`, `server: ${serverTag || serverVersion} (${serverCommit ?? '...'})`, `built: ${clientBuiltAt}`, ].join('\n') diff --git a/ordinis-admin-ui/src/components/version/useAppVersion.ts b/ordinis-admin-ui/src/components/version/useAppVersion.ts index 7203643..4850b86 100644 --- a/ordinis-admin-ui/src/components/version/useAppVersion.ts +++ b/ordinis-admin-ui/src/components/version/useAppVersion.ts @@ -21,8 +21,11 @@ export function useAppVersion() { const clientCommit = __APP_COMMIT__ const clientVersion = __APP_VERSION__ // Empty string when build wasn't на tag pipeline. VersionBadge / UpdateBanner - // решают priority: serverTag → clientTag → fallback v${clientVersion}. + // решают priority: serverTag → clientTag → channel-based label. const clientTag = __APP_TAG__ + // true когда GitLab CI собрал bundle. Отличает branch build (dev) от + // local pnpm dev (local) — оба не имеют tag'а, но семантически разные. + const clientCI = __APP_CI__ const clientBuiltAt = __APP_BUILT_AT__ const knownClient = clientCommit !== 'unknown' && clientCommit !== '' @@ -33,6 +36,7 @@ export function useAppVersion() { return { clientVersion, clientTag, + clientCI, clientCommit, clientBuiltAt, serverVersion: server?.version, diff --git a/ordinis-admin-ui/src/vite-env.d.ts b/ordinis-admin-ui/src/vite-env.d.ts index 29dea28..21ecf53 100644 --- a/ordinis-admin-ui/src/vite-env.d.ts +++ b/ordinis-admin-ui/src/vite-env.d.ts @@ -24,5 +24,6 @@ interface ImportMeta { */ declare const __APP_VERSION__: string declare const __APP_TAG__: string +declare const __APP_CI__: boolean declare const __APP_COMMIT__: string declare const __APP_BUILT_AT__: string diff --git a/ordinis-admin-ui/vite.config.ts b/ordinis-admin-ui/vite.config.ts index a09486f..89108bb 100644 --- a/ordinis-admin-ui/vite.config.ts +++ b/ordinis-admin-ui/vite.config.ts @@ -28,6 +28,10 @@ const APP_VERSION: string = JSON.parse( // --build-arg GIT_TAG="${CI_COMMIT_TAG:-none}"). Filter → пустая строка. const RAW_TAG = process.env.CI_COMMIT_TAG ?? '' const APP_TAG: string = RAW_TAG === 'none' ? '' : RAW_TAG.replace(/^v/, '') +// GitLab CI ставит CI=true. Используем для discriminate "CI branch build" +// (нет tag'а, но есть pipeline) vs "local pnpm dev" (нет ни того ни другого). +// VersionBadge показывает 'dev' для первого, 'local' для второго. +const APP_CI: boolean = process.env.CI === 'true' const APP_COMMIT: string = process.env.CI_COMMIT_SHORT_SHA ?? (() => { @@ -45,6 +49,7 @@ export default defineConfig({ define: { __APP_VERSION__: JSON.stringify(APP_VERSION), __APP_TAG__: JSON.stringify(APP_TAG), + __APP_CI__: JSON.stringify(APP_CI), __APP_COMMIT__: JSON.stringify(APP_COMMIT), __APP_BUILT_AT__: JSON.stringify(APP_BUILT_AT), },