import fs from 'node:fs/promises'; import path from 'node:path'; import { createHash } from 'node:crypto'; import fg from 'fast-glob'; const readText = async (file) => fs.readFile(file, 'utf8'); function hrefValues(html, rel) { const values = []; const pattern = new RegExp(`]*\\brel=["']${rel}["'][^>]*\\bhref=["']([^"']+)["']`, 'gi'); for (const match of html.matchAll(pattern)) values.push(match[1]); return values; } function routeFromHtml(file, root) { const relative = path.relative(root, file).replaceAll(path.sep, '/'); if (relative === 'index.html') return '/'; if (relative.endsWith('/index.html')) return `/${relative.slice(0, -'/index.html'.length)}`; return `/${relative.replace(/\.html$/, '')}`; } function formActions(html) { return [...html.matchAll(/]*)>/gi)].map((match) => match[1].match(/\baction=["']([^"']*)["']/i)?.[1] ?? null); } function rootRelativeUrls(html) { const urls = []; for (const match of html.matchAll(/\b(?:href|action)=["']([^"']+)["']/gi)) urls.push(match[1]); return urls; } function hasTerminalSlash(url) { if (!url.startsWith('/') || url.startsWith('//')) return false; const pathname = url.split(/[?#]/, 1)[0]; return pathname !== '/' && pathname.endsWith('/'); } 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'], ['/lp/aba-therapy', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'], ['/lp/aba-insurance', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'], ['/lp/aba-ahcccs', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'], ['/lp/aba-private-pay', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'], ['/lp/early-intervention', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'], ]); const formAConsultationStaticUrls = new Map([ ['/client-consultation', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'], ['/es/client-consultation', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'], ['/schedule-consultation', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'], ]); const formAConsultationDrawerUrls = new Map([ ['/aba-therapy', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'], ['/autism-evaluations', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'], ['/learner-social-club', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'], ['/es/aba-therapy', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'], ['/es/autism-evaluations', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'], ['/es/learner-social-club', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'], ]); const formAStaticUrls = new Map([...formAContactLandingUrls, ...formAConsultationStaticUrls]); function iframeSources(html) { // Astro correctly escapes query separators in serialized HTML attributes. // Normalize that representation before comparing the fixed allowlisted URL. return [...html.matchAll(/]*\bsrc=["']([^"']+)["'][^>]*>/gi)].map((match) => match[1].replaceAll('&', '&')); } function deferredIframeSources(html) { return [...html.matchAll(/]*\bdata-jotform-iframe-url=["']([^"']+)["'][^>]*>/gi)] .map((match) => match[1].replaceAll('&', '&')); } function formAAdapters(html) { return [...html.matchAll(/]*\bdata-jotform-form-a\b[^>]*>/gi)].map((match) => match[0]); } function unsafeSignals(html, allowApprovedFormA = false) { const signals = []; if (!allowApprovedFormA && /]*\bsrc=["'][^"']*jotform/i.test(html)) signals.push('active Jotform iframe'); if (!allowApprovedFormA && /262946078314059/i.test(html)) signals.push('disabled Form A identifier'); if (/]*\bsrc=["'][^"']*(?:jotform|googletagmanager|google-analytics|doubleclick)/i.test(html)) signals.push('active vendor tracking/form script'); if (!allowApprovedFormA && /data-(?:form|forms)-mode=["']live["']/i.test(html)) signals.push('live form mode'); if (/api\.azinstitute4autism\.com/i.test(html)) signals.push('production API origin'); if (/AIza[0-9A-Za-z_-]{20,}|-----BEGIN (?:RSA|OPENSSH|EC) PRIVATE KEY-----|(?:api[_-]?key|secret|password)\s*[:=]/i.test(html)) signals.push('credential-like content'); return signals; } export async function verifyArtifact(root, mode = 'staging', forms = 'live') { 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.'); const formAEnabled = forms === 'live'; const stagingPolicy = mode === 'staging'; const staticFormAUrls = formAEnabled ? formAStaticUrls : new Map(); const deferredFormAUrls = formAEnabled ? formAConsultationDrawerUrls : new Map(); const htmlFiles = (await fg('**/*.html', { cwd: root, absolute: true })).sort(); const routes = []; const canonicalMap = new Map(); const errors = []; const warnings = []; let hreflangLinks = 0; let activeForms = 0; let secretSignals = []; let formAIframes = 0; let deferredFormAIframes = 0; for (const file of htmlFiles) { const html = await readText(file); const route = routeFromHtml(file, root); const robots = html.match(/]*\bname=["']robots["'][^>]*\bcontent=["']([^"']*)["']/i)?.[1] ?? null; const canonical = html.match(/]*\brel=["']canonical["'][^>]*\bhref=["']([^"']+)["']/i)?.[1] ?? null; const title = html.match(/([^<]*)<\/title>/i)?.[1]?.trim() ?? ''; const actions = formActions(html); const nonSearchActions = actions.filter((action) => action && action !== '/search'); const trailingSlashUrls = rootRelativeUrls(html).filter(hasTerminalSlash); const expectedFormAUrl = formAEnabled ? staticFormAUrls.get(route) : undefined; const expectedDeferredFormAUrl = formAEnabled ? deferredFormAUrls.get(route) : undefined; const signals = unsafeSignals(html, formAEnabled); const jotformFrames = iframeSources(html).filter((src) => { try { return new URL(src).origin === 'https://form.jotform.com'; } catch { return false; } }); const deferredJotformFrames = deferredIframeSources(html).filter((src) => { try { return new URL(src).origin === 'https://form.jotform.com'; } catch { return false; } }); const adapters = formAAdapters(html); for (const url of iframeSources(html)) { if (url !== expectedFormAUrl) errors.push(`${route}: unauthorized iframe source`); } for (const url of deferredIframeSources(html)) { if (url !== expectedDeferredFormAUrl) errors.push(`${route}: unauthorized deferred iframe source`); } 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`); if (!title) errors.push(`${route}: missing title`); if (!canonical) errors.push(`${route}: missing canonical`); if (!robots) errors.push(`${route}: missing robots directive`); if (trailingSlashUrls.length) errors.push(`${route}: root-relative URL(s) end in a slash: ${trailingSlashUrls.join(', ')}`); if (stagingPolicy && robots !== 'noindex,nofollow') errors.push(`${route}: staging robots is ${robots ?? 'missing'}`); if (!stagingPolicy && robots === 'noindex,nofollow') errors.push(`${route}: production artifact is noindex`); if (canonical) { const prior = canonicalMap.get(canonical); if (prior) errors.push(`${route}: duplicate canonical ${canonical} (also ${prior})`); canonicalMap.set(canonical, route); } hreflangLinks += hrefValues(html, 'alternate').filter((href) => href).length; if (signals.length) secretSignals.push({ route, signals }); if (formAEnabled) { if (expectedFormAUrl) { if (jotformFrames.length !== 1 || jotformFrames[0] !== expectedFormAUrl) errors.push(`${route}: missing or invalid approved Form A iframe URL`); if (adapters.length !== 1 || !adapters[0].includes('data-form-mode="live"')) { errors.push(`${route}: Form A adapter lacks live mode`); } formAIframes += jotformFrames.length; } else if (expectedDeferredFormAUrl) { if (jotformFrames.length || deferredJotformFrames.length !== 1 || deferredJotformFrames[0] !== expectedDeferredFormAUrl) { errors.push(`${route}: missing or invalid deferred consultation Form A iframe URL`); } if (adapters.length !== 1 || !adapters[0].includes('data-form-mode="live"') || !adapters[0].includes('data-jotform-deferred="true"')) { errors.push(`${route}: deferred consultation adapter lacks the live deferred configuration`); } deferredFormAIframes += deferredJotformFrames.length; } else if (jotformFrames.length || deferredJotformFrames.length || adapters.length || /262946078314059/i.test(html)) { errors.push(`${route}: unauthorized Form A iframe or identifier in ${mode} artifact`); } } if (nonSearchActions.length) activeForms += nonSearchActions.length; routes.push({ route, title, robots, canonical, nativeForms: actions.length, nonSearchActions: nonSearchActions.length }); } const sitemapFile = path.join(root, 'sitemap.xml'); const sitemap = await fs.readFile(sitemapFile, 'utf8').catch(() => ''); const sitemapRoutes = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/gi)].map((match) => match[1]); const reviewIndex = htmlFiles.some((file) => routeFromHtml(file, root) === '/lp'); if (stagingPolicy) { if (sitemapRoutes.length) errors.push(`staging sitemap contains ${sitemapRoutes.length} URLs`); if (hreflangLinks) errors.push(`staging emits ${hreflangLinks} hreflang links`); if (!reviewIndex) errors.push('staging /lp review index is missing'); } else { if (!sitemapRoutes.length) errors.push('indexing-enabled sitemap is empty'); if (reviewIndex) errors.push('indexing-enabled artifact contains staging-only /lp review index'); } if (activeForms) errors.push(`found ${activeForms} non-search native form action(s)`); if (secretSignals.length) errors.push(`unsafe artifact signals: ${JSON.stringify(secretSignals)}`); const files = (await fg('**/*', { cwd: root, absolute: true, onlyFiles: true })).sort(); const formAResourceFiles = []; 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 (/\.(?: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); } digest.update(relative); digest.update('\0'); digest.update(createHash('sha256').update(await fs.readFile(file)).digest('hex')); digest.update('\n'); } if (formAEnabled) { const expectedFormAFiles = new Set([...staticFormAUrls.keys(), ...deferredFormAUrls.keys()].map((route) => route === '/' ? 'index.html' : `${route.slice(1)}/index.html`)); const unexpectedFormAFiles = formAResourceFiles.filter((file) => !expectedFormAFiles.has(file)); if (unexpectedFormAFiles.length) errors.push(`Form A identifier appears outside approved routes: ${unexpectedFormAFiles.join(', ')}`); if (formAIframes !== staticFormAUrls.size) errors.push(`${mode} artifact has ${formAIframes} approved static Form A iframe(s), expected ${staticFormAUrls.size}`); if (deferredFormAIframes !== deferredFormAUrls.size) errors.push(`${mode} artifact has ${deferredFormAIframes} approved deferred Form A iframe(s), expected ${deferredFormAUrls.size}`); } else if (formAResourceFiles.length) { errors.push(`disabled Form A identifier appears in artifact: ${formAResourceFiles.join(', ')}`); } return { mode, forms, htmlRoutes: htmlFiles.length, sitemapUrls: sitemapRoutes.length, hreflangLinks, reviewIndex, activeNativeFormActions: activeForms, formAIframes, deferredFormAIframes, artifactFiles: files.length, artifactSha256: digest.digest('hex'), routes, errors, warnings, passed: errors.length === 0, }; } if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) { 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); console.log(JSON.stringify(result, null, 2)); if (!result.passed) process.exitCode = 1; }