feat(deploy): add Contabo release workflow and portal payload gate

This commit is contained in:
DeCentN2Madness authored and AIAMarketing committed 2026-11-05 00:40:18 -07:00
1 parent 5fa54719b2
commit ba93a54942
18 files changed
+697 -28

No files matched your search

+27 -4
View File
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { createHash } from 'node:crypto';
import fg from 'fast-glob';
import { validatePortalPayload } from '../src/utils/portal-crypto.mjs';
const readText = async (file) => fs.readFile(file, 'utf8');
@@ -36,6 +37,12 @@ function hasTerminalSlash(url) {
return pathname !== '/' && pathname.endsWith('/');
}
function portalPayload(html) {
const match = html.match(/<script[^>]*data-portal-payload[^>]*>([\s\S]*?)<\/script>/i);
if (!match || match[1].trim() === 'null') return undefined;
try { return validatePortalPayload(JSON.parse(match[1])); } catch { return null; }
}
const formAContactLandingUrls = new Map([
['/contact', 'https://form.jotform.com/262946078314059?formContext=contact&language=en'],
['/es/contact', 'https://form.jotform.com/262946078314059?formContext=contact&language=es'],
@@ -89,9 +96,11 @@ function unsafeSignals(html, allowApprovedFormA = false) {
return signals;
}
export async function verifyArtifact(root, mode = 'staging', forms = 'live') {
export async function verifyArtifact(root, mode = 'staging', forms = 'live', portal = { mode: 'none' }) {
if (!['staging', 'indexing'].includes(mode)) throw new Error('Artifact mode must be staging or indexing; form availability is selected separately with --forms=live|off|mock.');
if (!['live', 'off', 'mock'].includes(forms)) throw new Error('Artifact forms must be live, off, or mock.');
if (!portal || !['none', 'synthetic', 'production'].includes(portal.mode)) throw new Error('Portal artifact mode must be none, synthetic, or production.');
if (portal.mode === 'production' && (!/^[0-9a-f]{64}$/.test(portal.sha256 ?? '') || mode !== 'indexing')) throw new Error('Production portal verification requires indexing mode and its expected payload SHA-256.');
const formAEnabled = forms === 'live';
const stagingPolicy = mode === 'staging';
const staticFormAUrls = formAEnabled ? formAStaticUrls : new Map();
@@ -142,7 +151,18 @@ export async function verifyArtifact(root, mode = 'staging', forms = 'live') {
}
if (expectedFormAUrl && deferredIframeSources(html).length) errors.push(`${route}: unexpected deferred frame`);
if (expectedDeferredFormAUrl && iframeSources(html).length) errors.push(`${route}: drawer must not load before activation`);
if (/<script[^>]*data-portal-payload[^>]*>\s*(?!null\s*<)[\s\S]*?\"streams\"[\s\S]*?<\/script>/i.test(html)) errors.push(`${route}: private portal ciphertext is not an approved release artifact`);
const payload = portalPayload(html);
if (payload === null) errors.push(`${route}: malformed portal ciphertext`);
if (payload && route !== '/employee-portal') errors.push(`${route}: portal ciphertext appears outside /employee-portal`);
if (route === '/employee-portal') {
if (portal.mode === 'none' && payload) errors.push(`${route}: private portal ciphertext is not approved for this artifact`);
if (portal.mode === 'production' && !payload) errors.push(`${route}: approved production portal ciphertext is missing`);
if (portal.mode === 'synthetic' && !payload) errors.push(`${route}: synthetic portal ciphertext is missing`);
if (payload && portal.mode === 'production') {
const actual = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
if (actual !== portal.sha256) errors.push(`${route}: portal ciphertext does not match the approved build input`);
}
}
if (!title) errors.push(`${route}: missing title`);
if (!canonical) errors.push(`${route}: missing canonical`);
if (!robots) errors.push(`${route}: missing robots directive`);
@@ -199,7 +219,7 @@ export async function verifyArtifact(root, mode = 'staging', forms = 'live') {
const digest = createHash('sha256');
for (const file of files) {
const relative = path.relative(root, file).replaceAll(path.sep, '/');
if (/(?:^|\/)(?:private|hubfs)(?:\/|$)/i.test(relative)) errors.push('private/legacy portal resource directory in artifact');
if (/(?:^|\/)(?:private|hubfs|staff-resources|management-resources)(?:\/|$)/i.test(relative)) errors.push('private/legacy portal resource directory in artifact');
if (/\.(?:html?|js|mjs|css|json|xml|txt)$/i.test(relative)) {
const contents = await fs.readFile(file, 'utf8');
if (/262946078314059/i.test(contents)) formAResourceFiles.push(relative);
@@ -221,6 +241,7 @@ export async function verifyArtifact(root, mode = 'staging', forms = 'live') {
return {
mode,
forms,
portal: portal.mode,
htmlRoutes: htmlFiles.length,
sitemapUrls: sitemapRoutes.length,
hreflangLinks,
@@ -241,7 +262,9 @@ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(im
const mode = process.argv.find((arg) => arg.startsWith('--mode='))?.slice('--mode='.length) ?? 'staging';
const root = process.argv.find((arg) => arg.startsWith('--root='))?.slice('--root='.length) ?? 'dist';
const forms = process.argv.find((arg) => arg.startsWith('--forms='))?.slice('--forms='.length) ?? 'live';
const result = await verifyArtifact(path.resolve(root), mode, forms);
const portalMode = process.argv.find((arg) => arg.startsWith('--portal='))?.slice('--portal='.length) ?? 'none';
const portalSha256 = process.argv.find((arg) => arg.startsWith('--portal-sha256='))?.slice('--portal-sha256='.length);
const result = await verifyArtifact(path.resolve(root), mode, forms, { mode: portalMode, sha256: portalSha256 });
console.log(JSON.stringify(result, null, 2));
if (!result.passed) process.exitCode = 1;
}