lintpage 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +0,0 @@
1
- import e from"chalk";function t(e){let t=0,n=``;for(let r of e){let e=encodeURIComponent(r).length;if(t+e>255)break;t+=e,n+=r}return n}function n(e,n,r){let i=new URLSearchParams({utm_source:`lintpage`,utm_medium:e,utm_campaign:`fix-it`});return n&&i.set(`utm_content`,t(n)),r&&i.set(`utm_term`,`scan_${r}`),`https://calendly.com/bludesksoft/30min?${i.toString()}`}const r={CRITICAL:e.red.bold(`✗`),WARNING:e.yellow(`⚠`),INFO:e.blue(`ℹ`)};function i(e,t){let n=`${e.warn} ${e.warn===1?`warning`:`warnings`}`;return t===`FAIL`?`DEPLOY BLOCKED · ${e.crit} critical · ${n} · ${e.info} info`:t===`AI-FAIL`?`AI VISIBILITY FAIL · ${e.crit} AI critical · ${n} · ${e.info} info`:`READY TO SHIP · ${n} · ${e.info} info`}function a(t,a){let s=[],c={crit:t.results.filter(e=>e.severity===`CRITICAL`).length,warn:t.results.filter(e=>e.severity===`WARNING`).length,info:t.results.filter(e=>e.severity===`INFO`).length};if(a)return s.push(` ${e.red.bold(`${c.crit} critical`)} ${e.yellow(`${c.warn} ${c.warn===1?`warning`:`warnings`}`)} ${e.blue(`${c.info} info`)}`),s.push(``),t.localhost&&(s.push(e.dim(` localhost: ${t.localhost.notRun.length} not run, ${t.localhost.downgraded.length} downgraded`)),s.push(``)),s.join(`
2
- `);if(s.push(``),t.results.length===0)s.push(e.green(` No issues found.`)),s.push(``);else{let n=[...t.results].sort((e,t)=>o(t.severity)-o(e.severity));n.forEach((t,i)=>{let a=r[t.severity],o=`${t.category.toLowerCase()}/${t.ruleId}`;s.push(`${a} ${e.bold(o)} — ${t.message}`),t.fix&&s.push(` ${e.dim(t.fix)}`),i<n.length-1&&s.push(``)}),s.push(``)}let l=i(c,t.overallStatus),u=t.overallStatus===`FAIL`?e.red.bold:t.overallStatus===`AI-FAIL`?e.yellow.bold:e.green.bold;return s.push(u(l)),s.push(``),t.localhost&&(s.push(e.dim(` localhost mode (${t.localhost.host}): ${t.localhost.notRun.length} checks not run, ${t.localhost.downgraded.length} downgraded. They need a public origin.`)),s.push(``)),c.crit>0&&(s.push(e.dim(` Want these fixed for you? Free 30-min call with the LintPage team: ${n(`cli`,t.url)}`)),s.push(``)),s.join(`
3
- `)}function o(e){return e===`CRITICAL`?3:e===`WARNING`?2:1}function s(e){return JSON.stringify(e,null,2)}const c={CRITICAL:`error`,WARNING:`warning`,INFO:`notice`};function l(e){return e.replace(/%/g,`%25`).replace(/\r/g,`%0D`).replace(/\n/g,`%0A`).replace(/:/g,`%3A`).replace(/,/g,`%2C`)}function u(e){let t=[];for(let n of e.results){let e=c[n.severity],r=l(`[${n.ruleId}] ${n.ruleName}`),i=l(n.message);t.push(`::${e} title=${r}::${i}`)}let n=i({crit:e.results.filter(e=>e.severity===`CRITICAL`).length,warn:e.results.filter(e=>e.severity===`WARNING`).length,info:e.results.filter(e=>e.severity===`INFO`).length},e.overallStatus),r=l(`${e.url} — ${n}`);return t.push(`::notice title=LintPage Verdict::${r}`),e.localhost&&t.push(`::notice title=LintPage Localhost::${l(`${e.localhost.notRun.length} checks not run against ${e.localhost.host}: ${e.localhost.notRun.join(`, `)}`)}`),t.join(`
4
- `)}function d(e,t,n){switch(t){case`json`:return s(e);case`github`:return u(e);default:return a(e,n)}}export{a as i,u as n,s as r,d as t};
5
- //# sourceMappingURL=formatter-Bg4mtE_M.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formatter-Bg4mtE_M.mjs","names":[],"sources":["../../shared/src/fix-it-url.ts","../src/formatter.ts"],"sourcesContent":["const BOOKING_URL = 'https://calendly.com/bludesksoft/30min';\n\n/**\n * Calendly drops UTM values longer than 255 characters (\"UTM values must be\n * 255 characters or less. If they're longer, they may not be tracked.\" -\n * calendly.com/help/how-to-track-conversions-with-utm-parameters). A scanned\n * URL with a long query string can pass that, so it is truncated here: a\n * clipped site URL still tells us which booking is which, whereas an\n * over-length one is dropped entirely and tells us nothing.\n *\n * The 255 is counted on the percent-encoded form, which is what actually\n * travels in the query string and so the only length Calendly can be\n * measuring. Clamping the raw string instead would let a URL full of escaped\n * characters through at well over the limit (a 255-character raw value can\n * encode to 290+). If Calendly turns out to count the decoded value this is\n * merely conservative, so it is the safe reading either way.\n */\nconst UTM_MAX_LENGTH = 255;\n\n/**\n * Iterating with for...of walks whole code points, so an emoji or accent in\n * the URL is never split into a lone surrogate that encodeURIComponent would\n * reject.\n */\nfunction clampUtm(value: string): string {\n let encodedLength = 0;\n let clamped = '';\n for (const char of value) {\n const charLength = encodeURIComponent(char).length;\n if (encodedLength + charLength > UTM_MAX_LENGTH) break;\n encodedLength += charLength;\n clamped += char;\n }\n return clamped;\n}\n\n/**\n * Every surface that offers the paid fix-it service. Keeping this a union\n * rather than a bare string is what stops a typo'd medium from silently\n * splitting the attribution reports.\n */\nexport type FixItMedium =\n | 'home'\n | 'dashboard'\n | 'incident'\n | 'cli'\n | 'email-report'\n | 'email-rescan';\n\n/**\n * Single source of truth for the \"fix it for me\" booking link. The web CTA,\n * the CLI footer and the transactional emails all route through here so an\n * attribution change lands on every surface at once.\n */\nexport function buildFixItUrl(\n medium: FixItMedium,\n scannedUrl?: string,\n scanId?: string,\n): string {\n const params = new URLSearchParams({\n utm_source: 'lintpage',\n utm_medium: medium,\n utm_campaign: 'fix-it',\n });\n // Calendly stores utm_* on the booked event, so the scanned site travels\n // with the booking without needing a custom question.\n if (scannedUrl) params.set('utm_content', clampUtm(scannedUrl));\n // utm_term carries the scans.id primary key so a booking can be matched to\n // the exact stored report (select * from scans where id = '<uuid>').\n if (scanId) params.set('utm_term', `scan_${scanId}`);\n return `${BOOKING_URL}?${params.toString()}`;\n}\n","import chalk from 'chalk';\nimport type { PageResult, Severity } from '@lintpage/core';\nimport { buildFixItUrl } from '@lintpage/shared';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nfunction buildVerdict(\n counts: { crit: number; warn: number; info: number },\n status: PageResult['overallStatus'],\n): string {\n const w = `${counts.warn} ${counts.warn === 1 ? 'warning' : 'warnings'}`;\n if (status === 'FAIL') {\n return `DEPLOY BLOCKED · ${counts.crit} critical · ${w} · ${counts.info} info`;\n }\n if (status === 'AI-FAIL') {\n return `AI VISIBILITY FAIL · ${counts.crit} AI critical · ${w} · ${counts.info} info`;\n }\n return `READY TO SHIP · ${w} · ${counts.info} info`;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n const counts = {\n crit: result.results.filter((r) => r.severity === 'CRITICAL').length,\n warn: result.results.filter((r) => r.severity === 'WARNING').length,\n info: result.results.filter((r) => r.severity === 'INFO').length,\n };\n\n if (quiet) {\n lines.push(\n ` ${chalk.red.bold(`${counts.crit} critical`)} ${chalk.yellow(\n `${counts.warn} ${counts.warn === 1 ? 'warning' : 'warnings'}`,\n )} ${chalk.blue(`${counts.info} info`)}`,\n );\n lines.push('');\n\n if (result.localhost) {\n lines.push(\n chalk.dim(\n ` localhost: ${result.localhost.notRun.length} not run, ${result.localhost.downgraded.length} downgraded`,\n ),\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found.'));\n lines.push('');\n } else {\n const sorted = [...result.results].sort(\n (a, b) => severityRank(b.severity) - severityRank(a.severity),\n );\n sorted.forEach((issue, idx) => {\n const icon = SEVERITY_ICON[issue.severity];\n const path = `${issue.category.toLowerCase()}/${issue.ruleId}`;\n lines.push(`${icon} ${chalk.bold(path)} — ${issue.message}`);\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n if (idx < sorted.length - 1) {\n lines.push('');\n }\n });\n lines.push('');\n }\n\n const verdict = buildVerdict(counts, result.overallStatus);\n const colour =\n result.overallStatus === 'FAIL'\n ? chalk.red.bold\n : result.overallStatus === 'AI-FAIL'\n ? chalk.yellow.bold\n : chalk.green.bold;\n lines.push(colour(verdict));\n lines.push('');\n\n if (result.localhost) {\n lines.push(\n chalk.dim(\n ` localhost mode (${result.localhost.host}): ${result.localhost.notRun.length} checks not run, ${result.localhost.downgraded.length} downgraded. They need a public origin.`,\n ),\n );\n lines.push('');\n }\n\n if (counts.crit > 0) {\n lines.push(\n chalk.dim(\n ` Want these fixed for you? Free 30-min call with the LintPage team: ${buildFixItUrl('cli', result.url)}`,\n ),\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nfunction severityRank(s: Severity): number {\n return s === 'CRITICAL' ? 3 : s === 'WARNING' ? 2 : 1;\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\n/**\n * Sanitize a string for use in GitHub Actions workflow commands.\n * Escapes characters that could break or inject annotations.\n * @see https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts\n */\nfunction sanitizeAnnotation(str: string): string {\n return str\n .replace(/%/g, '%25')\n .replace(/\\r/g, '%0D')\n .replace(/\\n/g, '%0A')\n .replace(/:/g, '%3A')\n .replace(/,/g, '%2C');\n}\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = sanitizeAnnotation(`[${issue.ruleId}] ${issue.ruleName}`);\n const message = sanitizeAnnotation(issue.message);\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${message}`);\n }\n\n // Summary annotation\n const counts = {\n crit: result.results.filter((r) => r.severity === 'CRITICAL').length,\n warn: result.results.filter((r) => r.severity === 'WARNING').length,\n info: result.results.filter((r) => r.severity === 'INFO').length,\n };\n const verdict = buildVerdict(counts, result.overallStatus);\n const summaryMessage = sanitizeAnnotation(`${result.url} — ${verdict}`);\n lines.push(`::notice title=LintPage Verdict::${summaryMessage}`);\n\n if (result.localhost) {\n lines.push(\n `::notice title=LintPage Localhost::${sanitizeAnnotation(\n `${result.localhost.notRun.length} checks not run against ${result.localhost.host}: ${result.localhost.notRun.join(', ')}`,\n )}`,\n );\n }\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n"],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let l=require(`chalk`);l=c(l);function u(e){let t=0,n=``;for(let r of e){let e=encodeURIComponent(r).length;if(t+e>255)break;t+=e,n+=r}return n}function d(e,t,n){let r=new URLSearchParams({utm_source:`lintpage`,utm_medium:e,utm_campaign:`fix-it`});return t&&r.set(`utm_content`,u(t)),n&&r.set(`utm_term`,`scan_${n}`),`https://calendly.com/bludesksoft/30min?${r.toString()}`}const f={CRITICAL:l.default.red.bold(`✗`),WARNING:l.default.yellow(`⚠`),INFO:l.default.blue(`ℹ`)};function p(e,t){let n=`${e.warn} ${e.warn===1?`warning`:`warnings`}`;return t===`FAIL`?`DEPLOY BLOCKED · ${e.crit} critical · ${n} · ${e.info} info`:t===`AI-FAIL`?`AI VISIBILITY FAIL · ${e.crit} AI critical · ${n} · ${e.info} info`:`READY TO SHIP · ${n} · ${e.info} info`}function m(e,t){let n=[],r={crit:e.results.filter(e=>e.severity===`CRITICAL`).length,warn:e.results.filter(e=>e.severity===`WARNING`).length,info:e.results.filter(e=>e.severity===`INFO`).length};if(t)return n.push(` ${l.default.red.bold(`${r.crit} critical`)} ${l.default.yellow(`${r.warn} ${r.warn===1?`warning`:`warnings`}`)} ${l.default.blue(`${r.info} info`)}`),n.push(``),e.localhost&&(n.push(l.default.dim(` localhost: ${e.localhost.notRun.length} not run, ${e.localhost.downgraded.length} downgraded`)),n.push(``)),n.join(`
2
- `);if(n.push(``),e.results.length===0)n.push(l.default.green(` No issues found.`)),n.push(``);else{let t=[...e.results].sort((e,t)=>h(t.severity)-h(e.severity));t.forEach((e,r)=>{let i=f[e.severity],a=`${e.category.toLowerCase()}/${e.ruleId}`;n.push(`${i} ${l.default.bold(a)} — ${e.message}`),e.fix&&n.push(` ${l.default.dim(e.fix)}`),r<t.length-1&&n.push(``)}),n.push(``)}let i=p(r,e.overallStatus),a=e.overallStatus===`FAIL`?l.default.red.bold:e.overallStatus===`AI-FAIL`?l.default.yellow.bold:l.default.green.bold;return n.push(a(i)),n.push(``),e.localhost&&(n.push(l.default.dim(` localhost mode (${e.localhost.host}): ${e.localhost.notRun.length} checks not run, ${e.localhost.downgraded.length} downgraded. They need a public origin.`)),n.push(``)),r.crit>0&&(n.push(l.default.dim(` Want these fixed for you? Free 30-min call with the LintPage team: ${d(`cli`,e.url)}`)),n.push(``)),n.join(`
3
- `)}function h(e){return e===`CRITICAL`?3:e===`WARNING`?2:1}function g(e){return JSON.stringify(e,null,2)}const _={CRITICAL:`error`,WARNING:`warning`,INFO:`notice`};function v(e){return e.replace(/%/g,`%25`).replace(/\r/g,`%0D`).replace(/\n/g,`%0A`).replace(/:/g,`%3A`).replace(/,/g,`%2C`)}function y(e){let t=[];for(let n of e.results){let e=_[n.severity],r=v(`[${n.ruleId}] ${n.ruleName}`),i=v(n.message);t.push(`::${e} title=${r}::${i}`)}let n=p({crit:e.results.filter(e=>e.severity===`CRITICAL`).length,warn:e.results.filter(e=>e.severity===`WARNING`).length,info:e.results.filter(e=>e.severity===`INFO`).length},e.overallStatus),r=v(`${e.url} — ${n}`);return t.push(`::notice title=LintPage Verdict::${r}`),e.localhost&&t.push(`::notice title=LintPage Localhost::${v(`${e.localhost.notRun.length} checks not run against ${e.localhost.host}: ${e.localhost.notRun.join(`, `)}`)}`),t.join(`
4
- `)}function b(e,t,n){switch(t){case`json`:return g(e);case`github`:return y(e);default:return m(e,n)}}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return y}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return b}});
5
- //# sourceMappingURL=formatter-DGoiZDhP.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formatter-DGoiZDhP.cjs","names":[],"sources":["../../shared/src/fix-it-url.ts","../src/formatter.ts"],"sourcesContent":["const BOOKING_URL = 'https://calendly.com/bludesksoft/30min';\n\n/**\n * Calendly drops UTM values longer than 255 characters (\"UTM values must be\n * 255 characters or less. If they're longer, they may not be tracked.\" -\n * calendly.com/help/how-to-track-conversions-with-utm-parameters). A scanned\n * URL with a long query string can pass that, so it is truncated here: a\n * clipped site URL still tells us which booking is which, whereas an\n * over-length one is dropped entirely and tells us nothing.\n *\n * The 255 is counted on the percent-encoded form, which is what actually\n * travels in the query string and so the only length Calendly can be\n * measuring. Clamping the raw string instead would let a URL full of escaped\n * characters through at well over the limit (a 255-character raw value can\n * encode to 290+). If Calendly turns out to count the decoded value this is\n * merely conservative, so it is the safe reading either way.\n */\nconst UTM_MAX_LENGTH = 255;\n\n/**\n * Iterating with for...of walks whole code points, so an emoji or accent in\n * the URL is never split into a lone surrogate that encodeURIComponent would\n * reject.\n */\nfunction clampUtm(value: string): string {\n let encodedLength = 0;\n let clamped = '';\n for (const char of value) {\n const charLength = encodeURIComponent(char).length;\n if (encodedLength + charLength > UTM_MAX_LENGTH) break;\n encodedLength += charLength;\n clamped += char;\n }\n return clamped;\n}\n\n/**\n * Every surface that offers the paid fix-it service. Keeping this a union\n * rather than a bare string is what stops a typo'd medium from silently\n * splitting the attribution reports.\n */\nexport type FixItMedium =\n | 'home'\n | 'dashboard'\n | 'incident'\n | 'cli'\n | 'email-report'\n | 'email-rescan';\n\n/**\n * Single source of truth for the \"fix it for me\" booking link. The web CTA,\n * the CLI footer and the transactional emails all route through here so an\n * attribution change lands on every surface at once.\n */\nexport function buildFixItUrl(\n medium: FixItMedium,\n scannedUrl?: string,\n scanId?: string,\n): string {\n const params = new URLSearchParams({\n utm_source: 'lintpage',\n utm_medium: medium,\n utm_campaign: 'fix-it',\n });\n // Calendly stores utm_* on the booked event, so the scanned site travels\n // with the booking without needing a custom question.\n if (scannedUrl) params.set('utm_content', clampUtm(scannedUrl));\n // utm_term carries the scans.id primary key so a booking can be matched to\n // the exact stored report (select * from scans where id = '<uuid>').\n if (scanId) params.set('utm_term', `scan_${scanId}`);\n return `${BOOKING_URL}?${params.toString()}`;\n}\n","import chalk from 'chalk';\nimport type { PageResult, Severity } from '@lintpage/core';\nimport { buildFixItUrl } from '@lintpage/shared';\n\nexport type Format = 'text' | 'json' | 'github';\n\n// ── Text formatter ─────────────────────────────────────────────\n\nconst SEVERITY_ICON: Record<Severity, string> = {\n CRITICAL: chalk.red.bold('✗'),\n WARNING: chalk.yellow('⚠'),\n INFO: chalk.blue('ℹ'),\n};\n\nfunction buildVerdict(\n counts: { crit: number; warn: number; info: number },\n status: PageResult['overallStatus'],\n): string {\n const w = `${counts.warn} ${counts.warn === 1 ? 'warning' : 'warnings'}`;\n if (status === 'FAIL') {\n return `DEPLOY BLOCKED · ${counts.crit} critical · ${w} · ${counts.info} info`;\n }\n if (status === 'AI-FAIL') {\n return `AI VISIBILITY FAIL · ${counts.crit} AI critical · ${w} · ${counts.info} info`;\n }\n return `READY TO SHIP · ${w} · ${counts.info} info`;\n}\n\nexport function formatText(result: PageResult, quiet: boolean): string {\n const lines: string[] = [];\n const counts = {\n crit: result.results.filter((r) => r.severity === 'CRITICAL').length,\n warn: result.results.filter((r) => r.severity === 'WARNING').length,\n info: result.results.filter((r) => r.severity === 'INFO').length,\n };\n\n if (quiet) {\n lines.push(\n ` ${chalk.red.bold(`${counts.crit} critical`)} ${chalk.yellow(\n `${counts.warn} ${counts.warn === 1 ? 'warning' : 'warnings'}`,\n )} ${chalk.blue(`${counts.info} info`)}`,\n );\n lines.push('');\n\n if (result.localhost) {\n lines.push(\n chalk.dim(\n ` localhost: ${result.localhost.notRun.length} not run, ${result.localhost.downgraded.length} downgraded`,\n ),\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n\n lines.push('');\n\n if (result.results.length === 0) {\n lines.push(chalk.green(' No issues found.'));\n lines.push('');\n } else {\n const sorted = [...result.results].sort(\n (a, b) => severityRank(b.severity) - severityRank(a.severity),\n );\n sorted.forEach((issue, idx) => {\n const icon = SEVERITY_ICON[issue.severity];\n const path = `${issue.category.toLowerCase()}/${issue.ruleId}`;\n lines.push(`${icon} ${chalk.bold(path)} — ${issue.message}`);\n if (issue.fix) {\n lines.push(` ${chalk.dim(issue.fix)}`);\n }\n if (idx < sorted.length - 1) {\n lines.push('');\n }\n });\n lines.push('');\n }\n\n const verdict = buildVerdict(counts, result.overallStatus);\n const colour =\n result.overallStatus === 'FAIL'\n ? chalk.red.bold\n : result.overallStatus === 'AI-FAIL'\n ? chalk.yellow.bold\n : chalk.green.bold;\n lines.push(colour(verdict));\n lines.push('');\n\n if (result.localhost) {\n lines.push(\n chalk.dim(\n ` localhost mode (${result.localhost.host}): ${result.localhost.notRun.length} checks not run, ${result.localhost.downgraded.length} downgraded. They need a public origin.`,\n ),\n );\n lines.push('');\n }\n\n if (counts.crit > 0) {\n lines.push(\n chalk.dim(\n ` Want these fixed for you? Free 30-min call with the LintPage team: ${buildFixItUrl('cli', result.url)}`,\n ),\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nfunction severityRank(s: Severity): number {\n return s === 'CRITICAL' ? 3 : s === 'WARNING' ? 2 : 1;\n}\n\n// ── JSON formatter ─────────────────────────────────────────────\n\nexport function formatJson(result: PageResult): string {\n return JSON.stringify(result, null, 2);\n}\n\n// ── GitHub Actions formatter ───────────────────────────────────\n\nconst GH_LEVEL: Record<Severity, string> = {\n CRITICAL: 'error',\n WARNING: 'warning',\n INFO: 'notice',\n};\n\n/**\n * Sanitize a string for use in GitHub Actions workflow commands.\n * Escapes characters that could break or inject annotations.\n * @see https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts\n */\nfunction sanitizeAnnotation(str: string): string {\n return str\n .replace(/%/g, '%25')\n .replace(/\\r/g, '%0D')\n .replace(/\\n/g, '%0A')\n .replace(/:/g, '%3A')\n .replace(/,/g, '%2C');\n}\n\nexport function formatGithub(result: PageResult): string {\n const lines: string[] = [];\n\n for (const issue of result.results) {\n const level = GH_LEVEL[issue.severity];\n const title = sanitizeAnnotation(`[${issue.ruleId}] ${issue.ruleName}`);\n const message = sanitizeAnnotation(issue.message);\n // GitHub annotation format: ::level title=TITLE::MESSAGE\n lines.push(`::${level} title=${title}::${message}`);\n }\n\n // Summary annotation\n const counts = {\n crit: result.results.filter((r) => r.severity === 'CRITICAL').length,\n warn: result.results.filter((r) => r.severity === 'WARNING').length,\n info: result.results.filter((r) => r.severity === 'INFO').length,\n };\n const verdict = buildVerdict(counts, result.overallStatus);\n const summaryMessage = sanitizeAnnotation(`${result.url} — ${verdict}`);\n lines.push(`::notice title=LintPage Verdict::${summaryMessage}`);\n\n if (result.localhost) {\n lines.push(\n `::notice title=LintPage Localhost::${sanitizeAnnotation(\n `${result.localhost.notRun.length} checks not run against ${result.localhost.host}: ${result.localhost.notRun.join(', ')}`,\n )}`,\n );\n }\n\n return lines.join('\\n');\n}\n\n// ── Dispatcher ─────────────────────────────────────────────────\n\nexport function format(\n result: PageResult,\n fmt: Format,\n quiet: boolean\n): string {\n switch (fmt) {\n case 'json':\n return formatJson(result);\n case 'github':\n return formatGithub(result);\n case 'text':\n default:\n return formatText(result, quiet);\n }\n}\n"],"mappings":"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"}