import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; const DEFAULT_BASE = 'https://acs.apps.bokdev.in'; const projectRoot = process.cwd(); function argValue(name, fallback) { const prefix = `--${name}=`; const inline = process.argv.find((arg) => arg.startsWith(prefix)); if (inline) return inline.slice(prefix.length); const index = process.argv.indexOf(`--${name}`); if (index >= 0 && process.argv[index + 1]) return process.argv[index + 1]; return fallback; } function ok(message) { console.log(`[ok] ${message}`); } function fail(message) { console.error(`[fail] ${message}`); process.exitCode = 1; } function warn(message) { console.warn(`[warn] ${message}`); } function readLocal(relativePath) { return readFileSync(path.join(projectRoot, relativePath), 'utf8'); } function git(args) { return execFileSync('git', args, { cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }).trim(); } async function checkJson(base, route) { const url = new URL(route, base).toString(); try { const response = await fetch(url); const body = await response.text(); if (!response.ok) { fail(`${route} returned HTTP ${response.status}: ${body.slice(0, 160)}`); return; } const parsed = JSON.parse(body); if (parsed?.ok !== true) { fail(`${route} response did not include ok=true: ${body.slice(0, 160)}`); return; } ok(`${route} ${response.status}`); } catch (error) { fail(`${route} check failed: ${error instanceof Error ? error.message : String(error)}`); } } function cookieHeaderFrom(response) { if (typeof response.headers.getSetCookie === 'function') { return response.headers.getSetCookie().map((cookie) => cookie.split(';')[0]).join('; '); } const cookie = response.headers.get('set-cookie'); return cookie ? cookie.split(',').map((part) => part.split(';')[0]).join('; ') : ''; } async function login(base, username, password) { if (!username || !password) return ''; const url = new URL('/api/auth/login', base).toString(); try { const response = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ username, password }), }); const body = await response.text(); if (!response.ok) { fail(`/api/auth/login returned HTTP ${response.status}: ${body.slice(0, 160)}`); return ''; } ok('/api/auth/login 200'); return cookieHeaderFrom(response); } catch (error) { fail(`/api/auth/login check failed: ${error instanceof Error ? error.message : String(error)}`); return ''; } } async function checkTemplate(base, cookie) { const route = '/api/visit-requests/template'; const url = new URL(route, base).toString(); try { const response = await fetch(url, cookie ? { headers: { cookie } } : undefined); const contentType = response.headers.get('content-type') ?? ''; const disposition = response.headers.get('content-disposition') ?? ''; if (!response.ok) { const body = await response.text(); fail(`${route} returned HTTP ${response.status}: ${body.slice(0, 200)}`); return; } if (!contentType.includes('spreadsheet') && !disposition.includes('.xlsx')) { fail(`${route} did not look like an xlsx download: content-type=${contentType || '-'}, content-disposition=${disposition || '-'}`); return; } ok(`${route} xlsx download`); } catch (error) { fail(`${route} check failed: ${error instanceof Error ? error.message : String(error)}`); } } function checkLocalTemplate() { const templatePath = 'docs/form_sample.xlsx'; if (!existsSync(path.join(projectRoot, templatePath))) { fail(`${templatePath} is missing locally`); return; } ok(`${templatePath} exists locally`); try { git(['ls-files', '--error-unmatch', templatePath]); ok(`${templatePath} is tracked by git`); } catch { fail(`${templatePath} is not tracked by git`); } } function checkDockerPackaging() { const dockerignore = readLocal('.dockerignore'); const dockerfile = readLocal('Dockerfile'); if (!dockerignore.includes('!docs/form_sample.xlsx')) { fail('.dockerignore does not allow docs/form_sample.xlsx into the build context'); } else { ok('.dockerignore allows docs/form_sample.xlsx'); } if (!dockerfile.includes('COPY docs/form_sample.xlsx ./docs/form_sample.xlsx')) { fail('Dockerfile does not copy docs/form_sample.xlsx into the runtime image'); } else { ok('Dockerfile copies docs/form_sample.xlsx into the runtime image'); } } function checkGitState() { try { const head = git(['rev-parse', '--short', 'HEAD']); ok(`local HEAD ${head}`); const upstream = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']); const aheadBehind = git(['rev-list', '--left-right', '--count', `${upstream}...HEAD`]).split(/\s+/); const behind = Number(aheadBehind[0] ?? 0); const ahead = Number(aheadBehind[1] ?? 0); if (ahead || behind) { warn(`branch differs from ${upstream}: ahead ${ahead}, behind ${behind}`); } else { ok(`branch matches ${upstream}`); } } catch (error) { warn(`git state check skipped: ${error instanceof Error ? error.message : String(error)}`); } } const base = argValue('base', DEFAULT_BASE).replace(/\/+$/, '/'); const username = argValue('username', process.env.ACS_CHECK_USER ?? ''); const password = argValue('password', process.env.ACS_CHECK_PASSWORD ?? ''); console.log(`[acs-deploy-check] base=${base}`); checkLocalTemplate(); checkDockerPackaging(); checkGitState(); await checkJson(base, '/healthz'); await checkJson(base, '/db'); const cookie = await login(base, username, password); await checkTemplate(base, cookie); if (process.exitCode) { console.error('[acs-deploy-check] failed'); process.exit(process.exitCode); } console.log('[acs-deploy-check] passed');