feat: expose verifiable deployment release

This commit is contained in:
2026-09-15 15:17:24 +03:00
parent a7495a1fe6
commit 4d191a07b1
7 changed files with 164 additions and 8 deletions
+3
View File
@@ -65,6 +65,7 @@ import { subscribe, list as listSubs, remove as removeSub, deliveryQueue, resolv
import { bildirimleriIsle } from './notifier.mjs';
import { scanSite } from './site-scan.mjs';
import { createDetailCache } from './detail-cache.mjs';
import { releaseInfo } from './release-info.mjs';
// ilan.gov.tr sunucusu ara sertifikayı (GeoTrust TLS RSA CA G1) göndermiyor;
// zinciri tamamlamak için PEM'i yerelden ekliyoruz.
@@ -75,6 +76,7 @@ const CA_BUNDLE = [...tls.rootCertificates, EXTRA_CA];
const PORT = process.env.PORT || 3001;
const ILAN_BASE = 'https://www.ilan.gov.tr';
const CORS_ORIGINS = String(process.env.CORS_ORIGINS || '').split(',').map(value => value.trim()).filter(Boolean);
const RUNTIME_RELEASE = releaseInfo();
function readiness() {
const directories = {
@@ -1732,6 +1734,7 @@ const server = http.createServer(async (req, res) => {
}
if (url.pathname === '/api/health') return json(res, 200, { ok: true });
if (url.pathname === '/api/version') return json(res, 200, { ok: true, ...RUNTIME_RELEASE });
if (url.pathname === '/api/ready') {
const status = readiness();
return json(res, status.ready ? 200 : 503, { ok: status.ready, ...status });
+50
View File
@@ -0,0 +1,50 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
const DEFAULT_ASSET_ROOTS = [
'/usr/share/nginx/html',
path.resolve(process.cwd(), 'dist'),
];
function cleanRelease(value) {
const release = String(value || '').trim();
if (!release || release === 'unknown' || release.length > 128) return null;
return release;
}
export function deployedRelease(env = process.env) {
return cleanRelease(env.APP_RELEASE) || cleanRelease(env.SOURCE_COMMIT);
}
export function frontendAssetInfo(roots = DEFAULT_ASSET_ROOTS) {
for (const root of roots) {
try {
const indexPath = path.join(root, 'index.html');
const html = fs.readFileSync(indexPath, 'utf8');
const match = html.match(/<script[^>]+src=["']([^"']*\/assets\/[^"']+\.js(?:\?[^"']*)?)["']/i);
if (!match) continue;
const publicPath = new URL(match[1], 'https://ihaleist.invalid/').pathname;
const assetPath = path.resolve(root, `.${publicPath}`);
const rootPath = path.resolve(root) + path.sep;
if (!assetPath.startsWith(rootPath)) continue;
const bytes = fs.readFileSync(assetPath);
return {
path: publicPath,
sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
bytes: bytes.length,
};
} catch {
// Bu kökte derlenmiş arayüz yoksa sıradaki olası köke bakılır.
}
}
return null;
}
export function releaseInfo({ env = process.env, roots = DEFAULT_ASSET_ROOTS } = {}) {
return {
release: deployedRelease(env),
version: '1.0.0',
frontendAsset: frontendAssetInfo(roots),
};
}