import { createRequire } from 'node:module' import { spawn } from 'node:child_process' import { createServer } from 'node:net' import { createHash } from 'node:crypto' import { mkdir, readFile, rm, writeFile } from 'node:fs/promises' import { fileURLToPath } from 'node:url' import { isDeepStrictEqual } from 'node:util' import os from 'node:os' import path from 'node:path' import { setTimeout as delay } from 'node:timers/promises' const require = createRequire(import.meta.url) const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') const referencePath = path.resolve(projectRoot, process.argv[2] ?? '.tmp/rea-font-platform-report.json') const resultPath = path.resolve(projectRoot, process.argv[3] ?? '.tmp/ycode-font-platform-report.json') const requiredCategories = ['assistant-name', 'chinese-body', 'english-body', 'user-bubble', 'code'] function fail(message) { throw new Error(message) } function categoryOf(value) { const key = String(value ?? '').toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]/g, '') if (/assistant.*name|agent.*name|助手名称|助手名/.test(key)) return 'assistant-name' if (/chinese.*body|body.*chinese|zh.*body|中文正文|中文文本/.test(key)) return 'chinese-body' if (/english.*body|body.*english|en.*body|英文正文|英文文本/.test(key)) return 'english-body' if (/user.*bubble|bubble.*user|用户气泡|用户消息/.test(key)) return 'user-bubble' if (/code|代码块|代码/.test(key)) return 'code' return null } function normalizeFonts(fonts, label) { if (!Array.isArray(fonts) || fonts.length === 0) fail(`${label}: platformFonts is empty; no actual glyph usage was recorded`) return fonts.map((font) => { const familyName = font.familyName ?? font.family ?? font.name const postScriptName = font.postScriptName ?? font.postscriptName ?? font.postScript ?? null const isCustomFont = font.isCustomFont ?? font.customFont ?? font.custom ?? false const glyphCount = Number(font.glyphCount ?? font.glyphs) if (!familyName || !Number.isInteger(glyphCount) || glyphCount < 0) { fail(`${label}: each platform font needs familyName and an integer glyphCount`) } return { familyName: String(familyName), postScriptName: postScriptName == null ? null : String(postScriptName), isCustomFont: Boolean(isCustomFont), glyphCount } }).sort(compareFonts) } function compareFonts(left, right) { return `${left.familyName}\0${left.postScriptName ?? ''}\0${left.isCustomFont}\0${left.glyphCount}` .localeCompare(`${right.familyName}\0${right.postScriptName ?? ''}\0${right.isCustomFont}\0${right.glyphCount}`) } function canonicalJson(value) { if (value === null || typeof value !== 'object') return JSON.stringify(value) if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]` const entries = Object.keys(value).filter((key) => value[key] !== undefined).sort() return `{${entries.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}` } function computedEvidenceId(evidence) { const semantic = { subject: evidence.subject === null ? null : { digest: evidence.subject.digest, format: evidence.subject.format, architecture: evidence.subject.architecture }, provider: evidence.provider, analysis_profile: evidence.analysis_profile, predicate_type: evidence.predicate_type, operation: evidence.operation, parameters: evidence.parameters, raw_result: evidence.raw_result, normalized_result: evidence.normalized_result, confidence: evidence.confidence, authority: evidence.authority, environment: evidence.environment, limitations: evidence.limitations, ...(evidence.relationship_coverage === null ? {} : { relationship_coverage: evidence.relationship_coverage }), locations: evidence.locations, evidence_links: evidence.evidence_links } return `ev_${createHash('sha256').update(canonicalJson(semantic)).digest('hex')}` } function normalizeReference(report) { const kind = report.source?.kind ?? report.kind if (!['rea-workbuddy', 'ycode-electron-self-test'].includes(kind)) { fail(`Unsupported font report source kind: ${kind ?? 'missing'}`) } const samples = report.samples ?? report.fontSamples ?? report.result?.samples if (!Array.isArray(samples)) fail('REA report needs a samples array') const records = new Map() const missing = new Map() for (const sample of samples) { const category = categoryOf(sample.category ?? sample.kind ?? sample.id ?? sample.label) if (!category) continue if (records.has(category)) fail(`REA report contains more than one ${category} sample`) const text = sample.text ?? sample.content ?? sample.textContent if (typeof text !== 'string' || text.length === 0) fail(`REA ${category} sample has no non-empty text`) const codeContext = category === 'code' ? sample.codeContext ?? sample.context ?? 'fenced' : null if (category === 'code' && !['inline', 'fenced'].includes(codeContext)) { fail(`REA code codeContext must be inline or fenced, received ${String(codeContext)}`) } records.set(category, { category, text, fontFamily: sample.fontFamily ?? sample.computedFontFamily ?? sample.computed?.fontFamily ?? sample.styles?.['font-family'] ?? null, fontSize: sample.fontSize ?? sample.computed?.fontSize ?? sample.styles?.['font-size'] ?? null, fontWeight: sample.fontWeight ?? sample.computed?.fontWeight ?? sample.styles?.['font-weight'] ?? null, codeContext, platformFonts: normalizeFonts(sample.platformFonts ?? sample.fonts, `REA ${category}`) }) } for (const entry of report.missing ?? report.missingSamples ?? []) { const category = categoryOf(entry.category ?? entry.kind ?? entry.id ?? entry.label) if (!category) continue if (records.has(category) || missing.has(category)) fail(`REA report lists ${category} more than once`) if (typeof entry.reason !== 'string' || !entry.reason.trim()) fail(`REA missing ${category} entry needs a reason`) missing.set(category, { category, reason: entry.reason }) } const undeclared = requiredCategories.filter((category) => !records.has(category) && !missing.has(category)) if (undeclared.length) fail(`REA report must explicitly sample or mark missing: ${undeclared.join(', ')}`) const evidenceId = report.evidenceId ?? report.evidence?.id ?? report.source?.evidenceId ?? report.reference?.evidenceId const version = report.version ?? report.workbuddyVersion ?? report.source?.version ?? report.reference?.version if (!evidenceId || !version) fail('REA report must record WorkBuddy version and Evidence ID') return { kind, evidenceId: String(evidenceId), version: String(version), environment: report.environment ?? report.runtime ?? report.source?.environment ?? null, samples: requiredCategories.filter((category) => records.has(category)).map((category) => records.get(category)), missing: requiredCategories.filter((category) => missing.has(category)).map((category) => missing.get(category)) } } async function validateReferenceReport(report, referencePath) { const reference = normalizeReference(report) if (reference.kind === 'ycode-electron-self-test') { if (report.archive || report.evidenceSha256) fail('A local YCode self-test must not claim a REA archive') return { ...reference, archive: null, archiveSha256: null, archiveVerified: false, versionObserved: true } } if (!/^ev_[a-f0-9]{64}$/.test(reference.evidenceId)) fail('REA report has an invalid Evidence ID') if (typeof report.archive !== 'string' || !report.archive) fail('REA report is missing its archived Evidence path') if (!/^[a-f0-9]{64}$/i.test(report.evidenceSha256 ?? '')) fail('REA report is missing a valid evidenceSha256') const archivePath = path.resolve(projectRoot, report.archive) const expectedArchivePath = path.resolve(projectRoot, '.tmp/rea-css-evidence', `${reference.evidenceId}.json`) const archivePathKey = process.platform === 'win32' ? archivePath.toLowerCase() : archivePath const expectedArchivePathKey = process.platform === 'win32' ? expectedArchivePath.toLowerCase() : expectedArchivePath if (archivePathKey !== expectedArchivePathKey) fail('REA archive path does not match the Evidence ID archive') const archiveBytes = await readFile(archivePath).catch(() => null) if (!archiveBytes) fail(`REA Evidence archive not found: ${archivePath}`) const archiveSha256 = createHash('sha256').update(archiveBytes).digest('hex') if (archiveSha256 !== report.evidenceSha256.toLowerCase()) fail('REA archive SHA-256 does not match evidenceSha256') let evidence try { evidence = JSON.parse(archiveBytes.toString('utf8')) } catch { fail('REA Evidence archive is not valid JSON') } if (evidence.evidence_id !== reference.evidenceId) fail('REA archive Evidence ID does not match the report') if (computedEvidenceId(evidence) !== evidence.evidence_id) fail('REA archive Evidence ID does not match its semantic content') const expectedAuthority = evidence.parameters?.visual_probe?.actions?.length ? 'controlled-replay' : 'external-service' if (evidence.operation !== 'inspect_electron_page' || evidence.authority !== expectedAuthority || evidence.provider?.id !== 'rea-cdp-electron') { fail('REA archive is not an authorized Electron page observation') } const targetPath = evidence.normalized_result?.target?.file_path ?? '' if (!/workbuddy[\\/]resources[\\/]app\.asar[\\/]renderer[\\/]index\.html/i.test(targetPath)) { fail('REA archive target is not the WorkBuddy renderer') } const visuals = evidence.normalized_result?.visuals if (!visuals || !Array.isArray(visuals.font_samples)) fail('REA archive has no font_samples observation') if (!isDeepStrictEqual(report.samples, visuals.font_samples)) fail('REA report samples differ from archived font_samples') if (!isDeepStrictEqual(report.missing ?? [], visuals.font_missing ?? [])) fail('REA report missing samples differ from archived font_missing') const userAgent = evidence.normalized_result?.browser?.user_agent ?? '' const archiveVersion = userAgent.match(/WorkBuddy[ /]([\d.]+)/i)?.[1] ?? 'unobserved' if (reference.version !== archiveVersion) fail(`REA report version ${reference.version} does not match archived user agent version ${archiveVersion}`) return { ...reference, archive: archivePath, archiveSha256, archiveVerified: true, versionObserved: reference.version.toLowerCase() !== 'unobserved' } } async function reservePort() { const server = createServer() await new Promise((resolve, reject) => server.once('error', reject).listen(0, '127.0.0.1', resolve)) const address = server.address() if (!address || typeof address === 'string') fail('Could not reserve an Electron debugging port') await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) return address.port } function connectCdp(url) { const socket = new WebSocket(url) const pending = new Map() let nextId = 0 const opened = new Promise((resolve, reject) => { socket.addEventListener('open', resolve, { once: true }) socket.addEventListener('error', () => reject(new Error('Could not connect to local Electron DevTools Protocol')), { once: true }) }) socket.addEventListener('message', (event) => { let message try { message = JSON.parse(String(event.data)) } catch { return } if (!message.id) return const request = pending.get(message.id) if (!request) return clearTimeout(request.timer) pending.delete(message.id) if (message.error) request.reject(new Error(`${request.method}: ${message.error.message}`)) else request.resolve(message.result) }) socket.addEventListener('close', () => { for (const request of pending.values()) { clearTimeout(request.timer) request.reject(new Error('Local Electron DevTools connection closed')) } pending.clear() }) return { opened, send(method, params = {}) { const id = ++nextId socket.send(JSON.stringify({ id, method, params })) return new Promise((resolve, reject) => { const timer = setTimeout(() => { pending.delete(id) reject(new Error(`Timed out waiting for ${method}`)) }, 15000) pending.set(id, { method, resolve, reject, timer }) }) }, close() { socket.close() } } } async function waitForDevtools(port, child) { const deadline = Date.now() + 30000 let lastError while (Date.now() < deadline) { if (child.exitCode !== null) fail(`YCode Electron exited early with code ${child.exitCode}`) try { const response = await fetch(`http://127.0.0.1:${port}/json/version`) if (response.ok) return await response.json() } catch (error) { lastError = error } await delay(250) } fail(`YCode Electron did not expose DevTools on port ${port}${lastError ? `: ${lastError.message}` : ''}`) } async function waitForPageTarget(port, child) { const deadline = Date.now() + 30000 while (Date.now() < deadline) { if (child.exitCode !== null) fail(`YCode Electron exited early with code ${child.exitCode}`) try { const response = await fetch(`http://127.0.0.1:${port}/json/list`) if (response.ok) { const targets = await response.json() const target = targets.find((item) => item.type === 'page' && item.webSocketDebuggerUrl) if (target) return target } } catch { /* Electron is still starting. */ } await delay(250) } fail('YCode Electron did not create a renderer page target') } function buildProbeExpression(samples) { const serialized = JSON.stringify(samples.map(({ category, text, codeContext }) => ({ category, text, codeContext }))) return `(() => { const samples = ${serialized}; const root = document.createElement('section'); root.id = 'yc-font-platform-probe'; root.className = 'chat-pane cr-theme'; root.dataset.crTheme = 'light'; Object.assign(root.style, { position: 'fixed', left: '-20000px', top: '0', width: '900px', zIndex: '-1', pointerEvents: 'none' }); const add = (category, className, parent) => { const node = document.createElement('span'); node.dataset.fontCategory = category; node.className = className; node.textContent = samples.find((item) => item.category === category).text; parent.append(node); }; for (const item of samples) { const message = document.createElement('div'); message.className = item.category === 'user-bubble' ? 'msg--user cr-frame cr-frame--right' : 'msg--assistant cr-frame cr-frame--left'; const frame = document.createElement('div'); frame.className = 'cr-frame__content'; message.append(frame); if (item.category === 'assistant-name') { const agent = document.createElement('div'); agent.className = 'cr-agent'; const header = document.createElement('div'); header.className = 'msg__header cr-agent__header'; const name = document.createElement('div'); name.dataset.fontCategory = item.category; name.className = 'msg__name cr-agent__name'; name.textContent = item.text; header.append(name); agent.append(header); frame.append(agent); } else if (item.category === 'chinese-body' || item.category === 'english-body') { const agent = document.createElement('div'); agent.className = 'cr-agent'; const row = document.createElement('div'); row.className = 'cr-agent__body-row'; const body = document.createElement('div'); body.className = 'cr-agent__body'; const content = document.createElement('div'); content.className = 'cr-agent__content markdown-content'; const paragraph = document.createElement('p'); paragraph.dataset.fontCategory = item.category; paragraph.className = 'cr-text-block'; paragraph.textContent = item.text; content.append(paragraph); body.append(content); row.append(body); agent.append(row); frame.append(agent); } else if (item.category === 'user-bubble') { const self = document.createElement('div'); self.className = 'cr-self-message'; const content = document.createElement('div'); content.className = 'cr-self-message__content'; const bubble = document.createElement('div'); bubble.className = 'bubble cr-self-bubble'; const userContent = document.createElement('div'); userContent.className = 'cr-user-content'; const editable = document.createElement('div'); editable.className = 'yc-user-editable'; const paragraph = document.createElement('p'); const leaf = document.createElement('span'); leaf.className = 'yc-user-leaf'; add(item.category, '', leaf); paragraph.append(leaf); editable.append(paragraph); userContent.append(editable); bubble.append(userContent); content.append(bubble); self.append(content); frame.append(self); } else if (item.category === 'code' && item.codeContext === 'inline') { const markdown = document.createElement('div'); markdown.className = 'cr-markdown markdown-content'; const list = document.createElement('ul'); const listItem = document.createElement('li'); const code = document.createElement('code'); code.dataset.fontCategory = item.category; code.className = 'markdown-content__inline-code cr-inline-code'; code.textContent = item.text; listItem.append(code); list.append(listItem); markdown.append(list); frame.append(markdown); } else { const markdown = document.createElement('div'); markdown.className = 'markdown-content'; const pre = document.createElement('pre'); pre.className = 'markdown-content__pre cr-code-block__pre'; const code = document.createElement('code'); code.dataset.fontCategory = item.category; code.className = 'language-typescript'; code.textContent = item.text; pre.append(code); markdown.append(pre); frame.append(markdown); } root.append(message); } (document.querySelector('.app-shell') || document.body).append(root); return { count: root.querySelectorAll('[data-font-category]').length, text: samples.map((item) => item.text) }; })()` } export async function captureYcodePlatformFonts(samples) { const port = await reservePort() const userDataDir = path.join(projectRoot, '.tmp', `rea-font-probe-profile-${process.pid}`) await mkdir(userDataDir, { recursive: true }) const electronPath = process.env.YCODE_ELECTRON_PATH || require('electron') const child = spawn(electronPath, [ `--remote-debugging-port=${port}`, `--user-data-dir=${userDataDir}`, '--no-first-run', projectRoot ], { cwd: projectRoot, stdio: 'ignore', windowsHide: true }) let cdp try { const browserInfo = await waitForDevtools(port, child) const target = await waitForPageTarget(port, child) cdp = connectCdp(target.webSocketDebuggerUrl) await cdp.opened await cdp.send('Page.enable') await cdp.send('Runtime.enable') await cdp.send('DOM.enable') await cdp.send('CSS.enable') const ready = await cdp.send('Runtime.evaluate', { expression: `new Promise((resolve) => { const start = Date.now(); const tick = () => { if (document.querySelector('.app-shell')) resolve(true); else if (Date.now() - start > 25000) resolve(false); else setTimeout(tick, 100); }; tick(); })`, awaitPromise: true, returnByValue: true }) if (ready.result?.value !== true) fail('YCode renderer did not reach its app shell') await cdp.send('Runtime.evaluate', { expression: 'document.fonts.ready.then(() => true)', awaitPromise: true, returnByValue: true }) const injected = await cdp.send('Runtime.evaluate', { expression: buildProbeExpression(samples), returnByValue: true }) if (injected.exceptionDetails) fail(`Could not inject font probes: ${injected.exceptionDetails.text}`) await cdp.send('Runtime.evaluate', { expression: 'document.fonts.ready.then(() => true)', awaitPromise: true, returnByValue: true }) await cdp.send('Runtime.evaluate', { expression: 'new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))', awaitPromise: true, returnByValue: true }) const document = await cdp.send('DOM.getDocument', { depth: -1, pierce: true }) const capturedSamples = [] for (const sample of samples) { const selector = `#yc-font-platform-probe [data-font-category="${sample.category}"]` const { nodeId } = await cdp.send('DOM.querySelector', { nodeId: document.root.nodeId, selector }) if (!nodeId) fail(`YCode probe node not found for ${sample.category}`) const details = await cdp.send('Runtime.evaluate', { expression: `(() => { const node = document.querySelector(${JSON.stringify(selector)}); const style = getComputedStyle(node); const rect = node.getBoundingClientRect(); return { text: node.textContent, fontFamily: style.fontFamily, fontSize: style.fontSize, fontWeight: style.fontWeight, width: rect.width, height: rect.height }; })()`, returnByValue: true }) if (details.exceptionDetails) fail(`Could not read YCode ${sample.category} computed style`) const platform = await cdp.send('CSS.getPlatformFontsForNode', { nodeId }) const value = details.result?.value if (!value || value.text !== sample.text) fail(`YCode ${sample.category} probe text differs from the REA sample`) capturedSamples.push({ category: sample.category, text: value.text, fontFamily: value.fontFamily, fontSize: value.fontSize, fontWeight: value.fontWeight, ...(sample.category === 'code' ? { codeContext: sample.codeContext ?? 'fenced' } : {}), platformFonts: normalizeFonts(platform.fonts, `YCode ${sample.category}`) }) } const env = { platform: process.platform, osRelease: os.release(), osVersion: os.version(), arch: process.arch, electron: require('electron/package.json').version, chromium: browserInfo.Browser ?? null, userAgent: browserInfo['User-Agent'] ?? null } return { environment: env, samples: capturedSamples } } finally { if (cdp) { try { await cdp.send('Runtime.evaluate', { expression: 'window.close()' }) } catch { /* The renderer may already be gone. */ } } cdp?.close() if (child.exitCode === null) { await Promise.race([ new Promise((resolve) => child.once('exit', resolve)), delay(3000) ]) } if (child.exitCode === null) child.kill() await rm(userDataDir, { recursive: true, force: true }) } } async function main() { const referenceBytes = await readFile(referencePath).catch(() => null) if (!referenceBytes) fail(`REA platform-font report not found: ${referencePath}`) const referenceReport = JSON.parse(referenceBytes.toString('utf8')) const reference = await validateReferenceReport(referenceReport, referencePath) const reportSha256 = createHash('sha256').update(referenceBytes).digest('hex') const local = await captureYcodePlatformFonts(reference.samples) const localSamples = local.samples const comparisons = reference.samples.map((expected, index) => { const actual = localSamples[index] const sameFonts = JSON.stringify(expected.platformFonts) === JSON.stringify(actual.platformFonts) const referenceSize = expected.fontSize ? Number.parseFloat(expected.fontSize) : null const localSize = actual.fontSize ? Number.parseFloat(actual.fontSize) : null const fontSizeMatches = referenceSize === null || (localSize !== null && Math.abs(referenceSize - localSize) <= 0.01) const fontWeightMatches = expected.fontWeight === null || expected.fontWeight === actual.fontWeight const mismatchReasons = [] if (expected.text !== actual.text) mismatchReasons.push('sample text differs') if (!sameFonts) mismatchReasons.push('actual platform font records differ') if (!fontSizeMatches) mismatchReasons.push(`font size differs (${expected.fontSize} vs ${actual.fontSize})`) if (!fontWeightMatches) mismatchReasons.push(`font weight differs (${expected.fontWeight} vs ${actual.fontWeight})`) if (expected.fontFamily && expected.fontFamily !== actual.fontFamily) mismatchReasons.push('computed CSS font-family stack differs') return { category: expected.category, status: expected.text === actual.text && sameFonts && fontSizeMatches && fontWeightMatches ? 'matched' : 'mismatched', mismatchReasons, textMatches: expected.text === actual.text, cssFontFamilyMatches: expected.fontFamily ? expected.fontFamily === actual.fontFamily : null, fontSizeMatches, referenceFontSize: expected.fontSize, ycodeFontSize: actual.fontSize, fontWeightMatches, referenceFontWeight: expected.fontWeight, ycodeFontWeight: actual.fontWeight, ...(expected.category === 'code' ? { codeContext: expected.codeContext } : {}), referenceFontFamily: expected.fontFamily, ycodeFontFamily: actual.fontFamily, referencePlatformFonts: expected.platformFonts, ycodePlatformFonts: actual.platformFonts, platformFontsMatch: sameFonts } }) comparisons.push(...reference.missing.map((entry) => ({ category: entry.category, status: 'missing', reason: entry.reason, platformFontsMatch: null }))) const referenceEnvironment = reference.environment const environmentMatches = !referenceEnvironment || ( (!referenceEnvironment.platform || referenceEnvironment.platform === local.environment.platform) && (!referenceEnvironment.osRelease || referenceEnvironment.osRelease === local.environment.osRelease) ) const samplesComplete = reference.missing.length === 0 && reference.samples.length === requiredCategories.length const complete = samplesComplete && reference.versionObserved const passed = complete && environmentMatches && comparisons.every((item) => item.status === 'matched') const status = !samplesComplete || !reference.versionObserved ? 'incomplete' : reference.kind === 'ycode-electron-self-test' ? passed ? 'self-test-passed' : 'self-test-failed' : passed ? 'passed' : 'failed' const result = { schemaVersion: 1, reference: { kind: reference.kind, acceptanceScope: reference.kind === 'rea-workbuddy' ? 'workbuddy-parity' : 'local-electron-self-test-only', originalParity: reference.kind === 'rea-workbuddy' && status === 'passed', path: path.relative(projectRoot, referencePath).replaceAll('\\', '/'), reportSha256, evidenceId: reference.evidenceId, version: reference.version, versionObserved: reference.versionObserved, archive: reference.archive, evidenceSha256: reference.archiveSha256, archiveVerified: reference.archiveVerified, environment: referenceEnvironment }, ycode: { environment: local.environment, samples: localSamples }, environmentMatches, samplesComplete, complete, comparisons, status, passed } if (!reference.versionObserved) result.incompleteReasons = ['WorkBuddy version is unobserved in the archived REA Evidence'] else if (!samplesComplete) result.incompleteReasons = reference.missing.map((entry) => `${entry.category}: ${entry.reason}`) await mkdir(path.dirname(resultPath), { recursive: true }) await writeFile(resultPath, `${JSON.stringify(result, null, 2)}\n`) console.log(JSON.stringify({ status, passed, complete, reference: { kind: reference.kind, acceptanceScope: result.reference.acceptanceScope, originalParity: result.reference.originalParity, evidenceId: reference.evidenceId, version: reference.version, reportSha256, evidenceSha256: reference.archiveSha256, archiveVerified: reference.archiveVerified }, environmentMatches, samplesComplete, samples: comparisons.map(({ category, status: sampleStatus, reason, mismatchReasons, codeContext, cssFontFamilyMatches, fontSizeMatches, referenceFontSize, ycodeFontSize, fontWeightMatches, referenceFontWeight, ycodeFontWeight, platformFontsMatch, referencePlatformFonts, ycodePlatformFonts }) => ({ category, status: sampleStatus, reason, mismatchReasons, codeContext, cssFontFamilyMatches, fontSizeMatches, referenceFontSize, ycodeFontSize, fontWeightMatches, referenceFontWeight, ycodeFontWeight, platformFontsMatch, referencePlatformFonts, ycodePlatformFonts })), incompleteReasons: result.incompleteReasons, resultPath: path.relative(projectRoot, resultPath).replaceAll('\\', '/') }, null, 2)) if (!passed) process.exitCode = complete ? 1 : 2 } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)) process.exitCode = 1 }) }