#!/usr/bin/env node import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; const base = (process.argv[2] || 'http://127.0.0.1:8080').replace(/\/$/, ''); const manifestPath = resolve(process.cwd(), 'asset-manifest.json'); const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); const assets = [...new Set([...(manifest.entrypoints || []), ...(manifest.publicAssets || [])])]; const failures = []; const secretPattern = /(api[_-]?key|secret|token|password|private[_-]?key|authorization)\s*[:=]\s*["'][^"']+/i; async function check(path, expected = {}) { const response = await fetch(`${base}/${path}`); const body = await response.text(); if (!response.ok) failures.push(`${path}: HTTP ${response.status}`); if (expected.contentType && !response.headers.get('content-type')?.includes(expected.contentType)) failures.push(`${path}: unexpected content type`); if (expected.marker && !body.includes(expected.marker)) failures.push(`${path}: missing marker ${expected.marker}`); if (expected.integrity) { const digest = `sha256-${createHash('sha256').update(body).digest('hex')}`; if (digest !== expected.integrity) failures.push(`${path}: integrity mismatch`); } if (secretPattern.test(body)) failures.push(`${path}: possible hardcoded secret`); console.log(`${response.ok ? 'PASS' : 'FAIL'} ${path} (${response.status})`); } const expectations = { 'index.html': { contentType: 'text/html', marker: 'ProspectOS' }, 'health.html': { contentType: 'text/html', marker: 'Ready' }, 'error.html': { contentType: 'text/html', marker: 'Something went wrong' }, healthz: { marker: 'ok' } }; for (const asset of assets) await check(asset, { ...expectations[asset], integrity: manifest.integrity?.[asset] }); if (failures.length) { console.error(`\n${failures.length} deployment smoke check(s) failed`); for (const failure of failures) console.error(`- ${failure}`); process.exitCode = 1; } else console.log(`\nDeployment smoke checks passed for ${base}`);