mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
116 lines
3.6 KiB
JavaScript
116 lines
3.6 KiB
JavaScript
export const LOC_BLOCK_START = '<!-- orca-pr-loc -->'
|
||
export const LOC_BLOCK_END = '<!-- /orca-pr-loc -->'
|
||
export const LOC_HANDS_OFF_COMMENT =
|
||
'<!-- Generated by the "PR test LoC" GitHub Action and rewritten on every push. Humans and AI agents: do not edit, move, or remove this block when updating the PR body. -->'
|
||
|
||
// __-wrapped segments unambiguously mark test data; bare fixtures/ could be production, and this classifier bills every PR.
|
||
const TEST_DIR_SEGMENT = /(?:^|\/)(?:__tests__|__fixtures__|__snapshots__|e2e|tests)(?:\/|$)/i
|
||
const TEST_FILENAME = /\.(?:test|spec|e2e)\.[^/]+$/i
|
||
|
||
export function isTestPath(path) {
|
||
const normalized = path.replaceAll('\\', '/')
|
||
return TEST_DIR_SEGMENT.test(normalized) || TEST_FILENAME.test(normalized)
|
||
}
|
||
|
||
export function emptyLocTotals() {
|
||
return {
|
||
test: { files: 0, added: 0, deleted: 0 },
|
||
nonTest: { files: 0, added: 0, deleted: 0 }
|
||
}
|
||
}
|
||
|
||
export function sumChangedFiles(files) {
|
||
const totals = emptyLocTotals()
|
||
for (const file of files) {
|
||
const path = file.filename
|
||
if (path == null) {
|
||
continue
|
||
}
|
||
const bucket = isTestPath(path) ? totals.test : totals.nonTest
|
||
bucket.files += 1
|
||
bucket.added += Number(file.additions ?? 0)
|
||
bucket.deleted += Number(file.deletions ?? 0)
|
||
}
|
||
return totals
|
||
}
|
||
|
||
const COLOR_ADDED = '#1a7f37'
|
||
const COLOR_DELETED = '#cf222e'
|
||
|
||
function diffCell(count) {
|
||
if (count === 0) {
|
||
return '0'
|
||
}
|
||
if (count > 0) {
|
||
return `$\\color{${COLOR_ADDED}}{\\Huge{\\mathbf{+}}}$\u200b${count}`
|
||
}
|
||
return `$\\color{${COLOR_DELETED}}{\\Huge{\\mathbf{−}}}$\u200b${Math.abs(count)}`
|
||
}
|
||
|
||
function locTableRow(label, bucket) {
|
||
const added = bucket.added ?? 0
|
||
const deleted = bucket.deleted ?? 0
|
||
return `| ${label} | ${bucket.files ?? 0} | ${diffCell(added)} | ${diffCell(-deleted)} | ${diffCell(added - deleted)} |`
|
||
}
|
||
|
||
export function formatLocTable({ test, nonTest }) {
|
||
return [
|
||
'| | Files | Added | Deleted | Net |',
|
||
'| :--- | ---: | ---: | ---: | ---: |',
|
||
locTableRow('Test', test),
|
||
locTableRow('Prod', nonTest)
|
||
].join('\n')
|
||
}
|
||
|
||
export function renderLocBlock(totals) {
|
||
return [
|
||
LOC_BLOCK_START,
|
||
LOC_HANDS_OFF_COMMENT,
|
||
'',
|
||
formatLocTable(totals),
|
||
'',
|
||
LOC_BLOCK_END
|
||
].join('\n')
|
||
}
|
||
|
||
function stripLocBlock(body) {
|
||
const start = body.indexOf(LOC_BLOCK_START)
|
||
const end = body.indexOf(LOC_BLOCK_END)
|
||
if (start === -1 || end === -1 || end < start) {
|
||
return body
|
||
}
|
||
const before = body.slice(0, start).trimEnd()
|
||
const after = body.slice(end + LOC_BLOCK_END.length).trimStart()
|
||
return before.length > 0 && after.length > 0 ? `${before}\n\n${after}` : before || after
|
||
}
|
||
|
||
const ELI5_HEADING = /^(#{1,6})[ \t]*ELI5\b.*$/im
|
||
|
||
// Index of the heading that ends the ELI5 section, or -1 when absent or it runs to the end.
|
||
function eli5SectionEnd(body) {
|
||
const heading = ELI5_HEADING.exec(body)
|
||
if (heading == null) {
|
||
return -1
|
||
}
|
||
const level = heading[1].length
|
||
const sectionStart = heading.index + heading[0].length
|
||
const nextHeading = new RegExp(`^#{1,${level}}[ \\t]`, 'm').exec(body.slice(sectionStart))
|
||
return nextHeading == null ? -1 : sectionStart + nextHeading.index
|
||
}
|
||
|
||
// Slack/GitHub link unfurls show the start of the body, so the table goes after ELI5 (or at the end), never first.
|
||
export function mergeLocBlock(body, totals) {
|
||
const block = renderLocBlock(totals)
|
||
const rest = stripLocBlock(body ?? '')
|
||
|
||
if (rest.trim().length === 0) {
|
||
return `${block}\n`
|
||
}
|
||
|
||
const insertAt = eli5SectionEnd(rest)
|
||
if (insertAt === -1) {
|
||
return `${rest.trimEnd()}\n\n${block}\n`
|
||
}
|
||
return `${rest.slice(0, insertAt).trimEnd()}\n\n${block}\n\n${rest.slice(insertAt)}`
|
||
}
|