/** * Renders the provided audit results to well-formatted and valid HTML. * * Do note that the rendered result is not an HTML document, it's rather * just a component with results. */ export async function renderAuditResultsToHTML(results) { const grouped = { total: 0, ok: [], notice: [], warn: [], error: [], }; for (const result of results) { grouped.total++; if (result.status === 'ok') { grouped[result.status].push(result); } else { grouped[result.status].push(result); } } let report = '* This report was auto-generated by graphql-http\n'; report += '\n'; report += '

GraphQL over HTTP audit report

\n'; report += '\n'; report += '\n'; report += '\n'; if (grouped.ok.length) { report += '

Passing

\n'; report += '
    \n'; for (const [, result] of grouped.ok.entries()) { report += `
  1. ${result.id} ${result.name}
  2. \n`; } report += '
\n'; report += '\n'; } if (grouped.notice.length) { report += `

Notices

\n`; report += 'The server MAY support these, but are truly optional. These are suggestions following recommended conventions.\n'; report += '
    \n'; for (const [, result] of grouped.notice.entries()) { report += await printAuditFail(result); } report += '
\n'; report += '\n'; } if (grouped.warn.length) { report += `

Warnings

\n`; report += 'The server SHOULD support these, but is not required.\n'; report += '
    \n'; for (const [, result] of grouped.warn.entries()) { report += await printAuditFail(result); } report += '
\n'; report += '\n'; } if (grouped.error.length) { report += `

Errors

\n`; report += 'The server MUST support these.\n'; report += '
    \n'; for (const [, result] of grouped.error.entries()) { report += await printAuditFail(result); } report += '
\n'; } return report; } async function printAuditFail(result) { let report = ''; report += `
  • ${result.id} ${result.name}\n`; report += '
    \n'; report += `${truncate(result.reason)}\n`; report += '
    '; // no "\n" because they count in HTML pre tags
        const res = result.response;
        const headers = {};
        for (const [key, val] of res.headers.entries()) {
            // some headers change on each run, dont report it
            if (key === 'date') {
                headers[key] = '';
            }
            else if (['cf-ray', 'server-timing'].includes(key)) {
                headers[key] = '';
            }
            else {
                headers[key] = val;
            }
        }
        let text = '', json;
        try {
            text = await res.text();
            json = JSON.parse(text);
        }
        catch (_a) {
            // noop
        }
        const stringified = JSON.stringify({
            status: res.status,
            statusText: res.statusText,
            headers,
            body: json || ((text === null || text === void 0 ? void 0 : text.length) > 5120 ? '' : text) || null,
        }, (_k, v) => {
            if (v != null && typeof v === 'object' && !Array.isArray(v)) {
                // sort object fields for stable stringify
                const acc = {};
                return Object.keys(v)
                    .sort()
                    .reverse() // body on bottom
                    .reduce((acc, k) => {
                    acc[k] = v[k];
                    return acc;
                }, acc);
            }
            return v;
        }, 2);
        report += stringified + '\n';
        report += '
    \n'; report += '
    \n'; report += '
  • \n'; return report; } function truncate(str, len = 1024) { if (str.length > len) { return str.substring(0, len) + '...'; } return str; }