- version 1.0.0 во всех package.json monorepo - версия в GET /health и в меню пользователя (vite define) - Docker: тег vX.Y.Z собирает образ :X.Y.Z и :X.Y, latest остаётся за main - README: как закрепить версию образа Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
85ac5b15cb
commit
68c5774df9
13 files changed
+49
-10
No files matched your search
@@ -3,6 +3,8 @@ name: Docker
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
# Релиз: тег vX.Y.Z → образ ghcr.io/…:X.Y.Z и :X.Y
|
||||||
|
tags: ['v*']
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- 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
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -34,12 +36,14 @@ jobs:
|
|||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,enable=${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
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
|
type=sha
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
push: ${{ github.event_name == 'push' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -129,6 +129,8 @@ docker stop vps-tracker && docker rm vps-tracker
|
|||||||
|
|
||||||
С Compose: `git pull && docker compose up -d` (`pull_policy: always` подтянет свежий образ).
|
С 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`.
|
Другой порт снаружи: `-p 8080:3001` → `http://localhost:8080`. Сменить порт внутри контейнера: `-e PORT=8080 -p 8080:8080`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@cfdm/api",
|
"name": "@cfdm/api",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter @cfdm/shared build && pnpm --filter @cfdm/db build && tsx watch src/index.ts",
|
"dev": "pnpm --filter @cfdm/shared build && pnpm --filter @cfdm/db build && tsx watch src/index.ts",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import cors from '@fastify/cors'
|
|||||||
import sensible from '@fastify/sensible'
|
import sensible from '@fastify/sensible'
|
||||||
import staticPlugin from '@fastify/static'
|
import staticPlugin from '@fastify/static'
|
||||||
import rateLimit from '@fastify/rate-limit'
|
import rateLimit from '@fastify/rate-limit'
|
||||||
import { existsSync } from 'node:fs'
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
@@ -38,6 +38,11 @@ import { portalUsersRoutes } from './routes/portal-users.js'
|
|||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
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 {
|
export interface BuildAppOptions {
|
||||||
dbPath?: string
|
dbPath?: string
|
||||||
staticDir?: string
|
staticDir?: string
|
||||||
@@ -67,7 +72,7 @@ export async function buildApp(opts: BuildAppOptions = {}) {
|
|||||||
await app.register(authPlugin)
|
await app.register(authPlugin)
|
||||||
await app.register(spacePlugin)
|
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(launcherRoutes)
|
||||||
|
|
||||||
await app.register(spacesRoutes)
|
await app.register(spacesRoutes)
|
||||||
|
|||||||
@@ -187,3 +187,17 @@ describe('dashboard stats', () => {
|
|||||||
await app.close()
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@cfdm/web",
|
"name": "@cfdm/web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -221,6 +221,12 @@ export function NavUser() {
|
|||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel className="text-muted-foreground py-1.5 text-xs font-normal tabular-nums">
|
||||||
|
VPS Tracker v{__APP_VERSION__}
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
</DropdownMenuGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|||||||
Vendored
+3
@@ -10,3 +10,6 @@ interface ImportMetaEnv {
|
|||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Версия релиза (vite define из корневого package.json). */
|
||||||
|
declare const __APP_VERSION__: string
|
||||||
@@ -3,8 +3,13 @@ import react from '@vitejs/plugin-react'
|
|||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import { TanStackRouterVite as TanStackRouterPlugin } from '@tanstack/router-plugin/vite'
|
import { TanStackRouterVite as TanStackRouterPlugin } from '@tanstack/router-plugin/vite'
|
||||||
import path from 'node:path'
|
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({
|
export default defineConfig({
|
||||||
|
define: { __APP_VERSION__: JSON.stringify(APP_VERSION) },
|
||||||
plugins: [
|
plugins: [
|
||||||
TanStackRouterPlugin({ target: 'react', autoCodeSplitting: true }),
|
TanStackRouterPlugin({ target: 'react', autoCodeSplitting: true }),
|
||||||
react(),
|
react(),
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vps-tracker",
|
"name": "vps-tracker",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --filter web dev",
|
"dev": "pnpm --filter web dev",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cfdm/db",
|
"name": "@cfdm/db",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cfdm/shared",
|
"name": "@cfdm/shared",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@cfdm/ui",
|
"name": "@cfdm/ui",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
Reference in new issue
Block a user