mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
103 lines
3.8 KiB
JavaScript
103 lines
3.8 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import path from 'node:path'
|
|
import { splitPatchEntries } from './xterm-patch-text.mjs'
|
|
|
|
function mappedSourcePath(source) {
|
|
return source.match(/^(?:webpack:\/\/@xterm\/xterm\/\.\/|\.\.\/\.\.\/)(src\/.+)$/)?.[1]
|
|
}
|
|
|
|
export function mappedSourceFiles(map) {
|
|
const files = new Map()
|
|
for (const [index, source] of map.sources.entries()) {
|
|
const relative = mappedSourcePath(source)
|
|
if (!relative) {
|
|
continue
|
|
}
|
|
if (relative.split('/').includes('..')) {
|
|
throw new Error(`Unsafe mapped source: ${relative}`)
|
|
}
|
|
if (files.has(relative)) {
|
|
throw new Error(`Duplicate mapped source: ${relative}`)
|
|
}
|
|
if (typeof map.sourcesContent[index] !== 'string') {
|
|
throw new Error(`Missing mapped content: ${relative}`)
|
|
}
|
|
files.set(relative, map.sourcesContent[index])
|
|
}
|
|
if (files.size === 0) {
|
|
throw new Error('Published source map contains no upstream sources')
|
|
}
|
|
return files
|
|
}
|
|
|
|
// Headless publishes source only inside its map; require exact-byte provenance.
|
|
export function assertMappedSourcesMatch(mapPath, checkoutRoot) {
|
|
const files = mappedSourceFiles(JSON.parse(readFileSync(mapPath, 'utf8')))
|
|
for (const [relative, content] of files) {
|
|
if (readFileSync(path.join(checkoutRoot, relative), 'utf8') !== content) {
|
|
throw new Error(`Mapped source differs from pinned checkout: ${relative}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function assertMappedPatchDerivation(sourcePatch, generatedPatch, sourceMap) {
|
|
const entry = splitPatchEntries(generatedPatch).find((entry) => entry.path === sourceMap)
|
|
const readSide = (sign) => {
|
|
const lines = entry?.text.split('\n') ?? []
|
|
const hunks = lines.filter((line) => line.startsWith('@@'))
|
|
if (hunks.length !== 1 || !/^@@ -1(?:,\d+)? \+1(?:,\d+)? @@/.test(hunks[0])) {
|
|
throw new Error(`Source map patch must contain one complete JSON hunk: ${sourceMap}`)
|
|
}
|
|
const body = lines
|
|
.slice(lines.indexOf(hunks[0]) + 1)
|
|
.filter((line) => line.startsWith(sign) || line.startsWith(' '))
|
|
.map((line) => line.slice(1))
|
|
.join('\n')
|
|
return JSON.parse(body)
|
|
}
|
|
const beforeMap = readSide('-'),
|
|
afterMap = readSide('+')
|
|
if (JSON.stringify(beforeMap.sources) !== JSON.stringify(afterMap.sources)) {
|
|
throw new Error('Source map source names changed')
|
|
}
|
|
const before = mappedSourceFiles(beforeMap),
|
|
after = mappedSourceFiles(afterMap)
|
|
for (const [index, source] of beforeMap.sources.entries()) {
|
|
if (
|
|
!mappedSourcePath(source) &&
|
|
beforeMap.sourcesContent[index] !== afterMap.sourcesContent[index]
|
|
) {
|
|
throw new Error(`Unrecognized mapped source changed: ${source}`)
|
|
}
|
|
}
|
|
const directory = mkdtempSync(path.join(tmpdir(), 'orca-xterm-mapped-source-'))
|
|
try {
|
|
for (const [relative, content] of before) {
|
|
mkdirSync(path.dirname(path.join(directory, relative)), { recursive: true })
|
|
writeFileSync(path.join(directory, relative), content)
|
|
}
|
|
execFileSync('git', ['apply', '--whitespace=nowarn', '-'], {
|
|
cwd: directory,
|
|
input: sourcePatch,
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
})
|
|
for (const entry of splitPatchEntries(sourcePatch)) {
|
|
if (!after.has(entry.path)) {
|
|
throw new Error(`Changed source missing from generated map: ${entry.path}`)
|
|
}
|
|
}
|
|
if (before.size !== after.size) {
|
|
throw new Error('Source map source set changed')
|
|
}
|
|
for (const [relative, content] of after) {
|
|
if (readFileSync(path.join(directory, relative), 'utf8') !== content) {
|
|
throw new Error(`Source patch and generated map disagree: ${relative}`)
|
|
}
|
|
}
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true })
|
|
}
|
|
}
|