feat(sdk): warn when local server image is behind latest (#20352)

Closes #20328.

## Summary
- Adds a CLI-side check that warns when `twenty-app-dev` is older than
the latest published Docker Hub tag.
- Reads `APP_VERSION` from the running container via `docker inspect` —
no server endpoint, no version exposed publicly. (`APP_VERSION` is
already baked in by `packages/twenty-docker/twenty/Dockerfile` for both
`twenty` and `twenty-app-dev` targets.)
- Fetches latest semver tag from Docker Hub (same API the admin panel
already uses) and caches the result for 24h in
`~/.twenty/version-check-cache.json`.
- Wired into `twenty dev`, `twenty install`, and `twenty server start`.
- Best-effort: silent on container-missing / docker / network errors,
never blocks a command.

## Why CLI-side instead of a `/healthz` extension
The original issue suggested comparing the running server version
against Docker Hub. Exposing the running version on a public endpoint
has a small but real security cost (helps attackers fingerprint
vulnerable deployments), and the version is already inside the image —
so the CLI can read it directly without ever calling the server.

## Test plan
- [x] `nx run twenty-sdk:test` — added unit tests for `parseSemver` /
`compareSemver`
- [x] `nx run twenty-sdk:typecheck`
- [x] `nx run twenty-sdk:lint`
- [ ] Manual: with an old `twenty-app-dev` image running, run `yarn
twenty install` → see warning
- [ ] Manual: with an up-to-date image, run `yarn twenty dev` → no
warning, cache file written
- [ ] Manual: no container at all → no warning, no error
This commit is contained in:
Félix Malfait
2026-05-08 14:11:34 +02:00
committed by GitHub
parent 7f4f2e932c
commit 0f8ee5714f
17 changed files with 462 additions and 3 deletions
@@ -18,6 +18,7 @@ import {
checkServerHealth,
detectLocalServer,
} from '@/cli/utilities/server/detect-local-server';
import { checkServerVersionCompatibility } from '@/cli/utilities/version/check-server-version-compatibility';
import { execSync, spawn, spawnSync } from 'node:child_process';
import chalk from 'chalk';
@@ -274,10 +275,19 @@ const innerServerStart = async (
return { success: true, data: { port, url } };
};
export const serverStart = (
export const serverStart = async (
options?: ServerStartOptions,
): Promise<CommandResult<ServerStartResult>> =>
runSafe(
): Promise<CommandResult<ServerStartResult>> => {
const result = await runSafe(
() => innerServerStart(options),
SERVER_ERROR_CODES.CONTAINER_START_FAILED,
);
if (result.success) {
const containerName = options?.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
await checkServerVersionCompatibility(containerName);
}
return result;
};