diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f8095e8..80dd0a3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,8 @@ name: Docker on: push: branches: [main] + # Релиз: тег vX.Y.Z → образ ghcr.io/…:X.Y.Z и :X.Y + tags: ['v*'] pull_request: branches: [main] @@ -20,7 +22,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: github.event_name == 'push' uses: docker/login-action@v3 with: registry: ghcr.io @@ -34,12 +36,14 @@ jobs: images: ghcr.io/${{ github.repository }} tags: | type=raw,value=latest,enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} type=sha - name: Build and push uses: docker/build-push-action@v6 with: context: . - push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + push: ${{ github.event_name == 'push' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/README.md b/README.md index 68a2132..15220ab 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ docker stop vps-tracker && docker rm vps-tracker С Compose: `git pull && docker compose up -d` (`pull_policy: always` подтянет свежий образ). +Версии: каждый релиз — git-тег `vX.Y.Z` и образ `ghcr.io/denozordec/vps-tracker:X.Y.Z` (и `:X.Y`); `latest` — текущий `main`. Чтобы не обновляться неожиданно, закрепите версию: `ghcr.io/denozordec/vps-tracker:1.0.0`. Текущая версия видна в меню пользователя и в ответе `GET /health`. + Другой порт снаружи: `-p 8080:3001` → `http://localhost:8080`. Сменить порт внутри контейнера: `-e PORT=8080 -p 8080:8080`. --- diff --git a/apps/api/package.json b/apps/api/package.json index 17e6434..b014d3b 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,7 +1,7 @@ { "name": "@cfdm/api", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "pnpm --filter @cfdm/shared build && pnpm --filter @cfdm/db build && tsx watch src/index.ts", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 5979d6c..463e24c 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,7 +3,7 @@ import cors from '@fastify/cors' import sensible from '@fastify/sensible' import staticPlugin from '@fastify/static' import rateLimit from '@fastify/rate-limit' -import { existsSync } from 'node:fs' +import { existsSync, readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -38,6 +38,11 @@ import { portalUsersRoutes } from './routes/portal-users.js' const __dirname = dirname(fileURLToPath(import.meta.url)) +/** Версия из apps/api/package.json (src/ и dist/ лежат на одном уровне с ним). */ +export const APP_VERSION: string = ( + JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string } +).version + export interface BuildAppOptions { dbPath?: string staticDir?: string @@ -67,7 +72,7 @@ export async function buildApp(opts: BuildAppOptions = {}) { await app.register(authPlugin) await app.register(spacePlugin) - app.get('/health', async () => ({ ok: true })) + app.get('/health', async () => ({ ok: true, version: APP_VERSION })) await app.register(launcherRoutes) await app.register(spacesRoutes) diff --git a/apps/api/src/routes/review-guards.test.ts b/apps/api/src/routes/review-guards.test.ts index f97b50d..fbf3225 100644 --- a/apps/api/src/routes/review-guards.test.ts +++ b/apps/api/src/routes/review-guards.test.ts @@ -187,3 +187,17 @@ describe('dashboard stats', () => { await app.close() }) }) + +describe('/health', () => { + beforeEach(() => resetTestDb()) + afterEach(() => closeDb()) + + it('returns the release version from package.json', async () => { + const { version } = (await import('../../package.json', { with: { type: 'json' } })).default + const app = await buildApp() + const res = await app.inject({ url: '/health' }) + expect(res.json()).toEqual({ ok: true, version }) + expect(version).toMatch(/^\d+\.\d+\.\d+$/) + await app.close() + }) +}) diff --git a/apps/web/package.json b/apps/web/package.json index 6d61bff..dd41889 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,7 +1,7 @@ { "name": "@cfdm/web", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/web/src/components/layout/nav-user.tsx b/apps/web/src/components/layout/nav-user.tsx index 7a610a0..10dc71b 100644 --- a/apps/web/src/components/layout/nav-user.tsx +++ b/apps/web/src/components/layout/nav-user.tsx @@ -221,6 +221,12 @@ export function NavUser() { ) : null} + + + + VPS Tracker v{__APP_VERSION__} + + diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts index 2c41b58..3687953 100644 --- a/apps/web/src/vite-env.d.ts +++ b/apps/web/src/vite-env.d.ts @@ -10,3 +10,6 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv } + +/** Версия релиза (vite define из корневого package.json). */ +declare const __APP_VERSION__: string diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 71ade29..cbe4b18 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -3,8 +3,13 @@ import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import { TanStackRouterVite as TanStackRouterPlugin } from '@tanstack/router-plugin/vite' import path from 'node:path' +import { readFileSync } from 'node:fs' + +// Версия релиза одна на monorepo — из корневого package.json. +const APP_VERSION = (JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8')) as { version: string }).version export default defineConfig({ + define: { __APP_VERSION__: JSON.stringify(APP_VERSION) }, plugins: [ TanStackRouterPlugin({ target: 'react', autoCodeSplitting: true }), react(), diff --git a/package.json b/package.json index 5b895a3..c02ef70 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vps-tracker", "private": true, - "version": "0.0.0", + "version": "1.0.0", "type": "module", "scripts": { "dev": "pnpm --filter web dev", diff --git a/packages/db/package.json b/packages/db/package.json index 3b66924..476091e 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@cfdm/db", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/shared/package.json b/packages/shared/package.json index e10d57e..c5323b4 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@cfdm/shared", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/packages/ui/package.json b/packages/ui/package.json index 1271bd1..2c29f1e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@cfdm/ui", - "version": "0.0.0", + "version": "1.0.0", "private": true, "type": "module", "exports": {