export interface CertInfo { domain: string subject: string issuer: string issuerCN: string notBefore: string notAfter: string expiringWarning: boolean expired: boolean } export function parseCertList(output: string): CertInfo[] { const certs: CertInfo[] = [] const blocks = output.split('CERTEND') for (const block of blocks) { const fileMatch = block.match(/CERTFILE:(.+)/) if (!fileMatch) continue const filePath = fileMatch[1].trim() const domain = filePath.split('/').filter(Boolean).pop()?.replace(/\.crt$/, '') ?? '' let subject = '' let issuer = '' let notBefore = '' let notAfter = '' for (const line of block.split('\n')) { const subjectMatch = line.match(/^subject=(.+)/) if (subjectMatch) subject = subjectMatch[1].trim() const issuerMatch = line.match(/^issuer=(.+)/) if (issuerMatch) issuer = issuerMatch[1].trim() const notBeforeMatch = line.match(/^notBefore=(.+)/) if (notBeforeMatch) notBefore = notBeforeMatch[1].trim() const notAfterMatch = line.match(/^notAfter=(.+)/) if (notAfterMatch) notAfter = notAfterMatch[1].trim() } const issuerCN = issuer.match(/CN\s*=\s*([^,]+)/)?.[1]?.trim() ?? issuer const now = Date.now() const notAfterDate = notAfter ? new Date(notAfter) : null const notAfterMs = notAfterDate?.getTime() ?? Infinity const expiringWarning = notAfterMs - now < 30 * 24 * 60 * 60 * 1000 const expired = notAfterMs < now certs.push({ domain, subject, issuer, issuerCN, notBefore, notAfter, expiringWarning, expired }) } return certs }