feat(health): expose app version in /health response
Reads application version from package.json via env.ts readPackageVersion() and returns it alongside status so deploy verification is instant: curl https://upload.asepharyana.my.id/health -> {status: ok, version: X.Y.Z}
This commit is contained in:
+36
@@ -2,6 +2,8 @@ import logger from './shared/logger/index';
|
|||||||
import { TELEGRAM_CHUNK_SIZE_MAX_BYTES } from './shared/utils/validation';
|
import { TELEGRAM_CHUNK_SIZE_MAX_BYTES } from './shared/utils/validation';
|
||||||
|
|
||||||
interface AppConfig {
|
interface AppConfig {
|
||||||
|
/** Application version (read from package.json, kept in sync by semantic-release prepare.mjs) */
|
||||||
|
appVersion: string;
|
||||||
/** All bot tokens merged from BOT_TOKENS (or BOT_TOKEN + ADDITIONAL_BOT_TOKENS fallback) */
|
/** All bot tokens merged from BOT_TOKENS (or BOT_TOKEN + ADDITIONAL_BOT_TOKENS fallback) */
|
||||||
botTokens: string[];
|
botTokens: string[];
|
||||||
/** Per-bot concurrency for Telegram API calls (default 1). */
|
/** Per-bot concurrency for Telegram API calls (default 1). */
|
||||||
@@ -131,7 +133,41 @@ if (telegramChunkSizeBytes > TELEGRAM_CHUNK_SIZE_MAX_BYTES) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the application version from package.json.
|
||||||
|
*
|
||||||
|
* Prefers the resolved node_modules/teleuploader package version (set by
|
||||||
|
* semantic-release when the repo is installed as a published artifact) and
|
||||||
|
* falls back to reading the repo's own package.json when running from a
|
||||||
|
* checkout. Both files are updated by prepare.mjs on every release, so the
|
||||||
|
* value always matches the deployed build.
|
||||||
|
*/
|
||||||
|
const readPackageVersion = (): string => {
|
||||||
|
try {
|
||||||
|
const candidates = [
|
||||||
|
// node_modules resolution is only available when installed as a dep
|
||||||
|
import.meta.dir ? `${import.meta.dir}/../package.json` : undefined,
|
||||||
|
`${process.cwd()}/package.json`,
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!candidate) continue;
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(import.meta.require(candidate).default ?? '{}') as {
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
if (pkg.version) return pkg.version;
|
||||||
|
} catch {
|
||||||
|
// try next candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
return '0.0.0';
|
||||||
|
};
|
||||||
|
|
||||||
export const config: AppConfig = {
|
export const config: AppConfig = {
|
||||||
|
appVersion: readPackageVersion(),
|
||||||
botTokens: parseTokens(botTokensRaw),
|
botTokens: parseTokens(botTokensRaw),
|
||||||
telegramBotConcurrency: parseNumber(process.env.TELEGRAM_BOT_CONCURRENCY, 1),
|
telegramBotConcurrency: parseNumber(process.env.TELEGRAM_BOT_CONCURRENCY, 1),
|
||||||
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID!, 10),
|
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID!, 10),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
import { config } from '../../../env';
|
||||||
import { db } from '../../../infrastructure/persistence/drizzle/index';
|
import { db } from '../../../infrastructure/persistence/drizzle/index';
|
||||||
import logger from '../../../shared/logger/index';
|
import logger from '../../../shared/logger/index';
|
||||||
import { getErrorMessage } from '../../../shared/utils/file';
|
import { getErrorMessage } from '../../../shared/utils/file';
|
||||||
@@ -16,7 +17,7 @@ import { getErrorMessage } from '../../../shared/utils/file';
|
|||||||
export const handleHealth = async (_req: Request): Promise<Response> => {
|
export const handleHealth = async (_req: Request): Promise<Response> => {
|
||||||
try {
|
try {
|
||||||
await db.execute(sql`SELECT 1`);
|
await db.execute(sql`SELECT 1`);
|
||||||
return Response.json({ status: 'ok' }, { status: 200 });
|
return Response.json({ status: 'ok', version: config.appVersion }, { status: 200 });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const message = getErrorMessage(error);
|
const message = getErrorMessage(error);
|
||||||
logger.error('Health check failed', { error: message });
|
logger.error('Health check failed', { error: message });
|
||||||
|
|||||||
Reference in New Issue
Block a user