e0a5f57793
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as a monorepo subdirectory under desktop/. Source only — node_modules, dist, Cargo target and the upstream .git history are excluded. desktop/ keeps its own independent Cargo workspace (desktop/src-tauri with crates/*); it is not part of the backend boosapi crate, so a `cargo check` at the metapi root still compiles only the backend. Build the desktop app from desktop/src-tauri per desktop/README.md. Note: app icons are still placeholder assets pending the final BoosAPI logo. The updater signing keys live outside the repo and are not included.
644 lines
25 KiB
JavaScript
644 lines
25 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawn } from 'node:child_process'
|
|
import { existsSync } from 'node:fs'
|
|
import net from 'node:net'
|
|
import path from 'node:path'
|
|
import process from 'node:process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { chromium } from 'playwright-core'
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
const repoRoot = path.resolve(__dirname, '..')
|
|
const playgroundDir = path.join(repoRoot, 'playground')
|
|
const host = '127.0.0.1'
|
|
|
|
const diffSample = [
|
|
'```diff json:package.json',
|
|
'{',
|
|
' "name": "markstream-vue",',
|
|
'- "version": "0.0.49",',
|
|
'+ "version": "0.0.54-beta.1",',
|
|
' "packageManager": "pnpm@10.16.1",',
|
|
'}',
|
|
'```',
|
|
].join('\n')
|
|
|
|
const sampleFrameIntervalMs = 25
|
|
const sampleStreamCharDelayMs = 80
|
|
const estimatedStreamingDurationMs
|
|
= diffSample.length * sampleStreamCharDelayMs
|
|
const samplingDurationMs = Math.max(
|
|
12000,
|
|
estimatedStreamingDurationMs + 4000,
|
|
)
|
|
const samplingFrameCount = Math.ceil(
|
|
samplingDurationMs / sampleFrameIntervalMs,
|
|
)
|
|
|
|
function isPortOpen(port) {
|
|
return new Promise((resolve) => {
|
|
const socket = net.createConnection({ host, port })
|
|
socket.on('connect', () => {
|
|
socket.end()
|
|
resolve(true)
|
|
})
|
|
socket.on('error', () => {
|
|
socket.destroy()
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
async function findFreePort(start = 4370, end = 4410) {
|
|
for (let port = start; port <= end; port++) {
|
|
if (!await isPortOpen(port))
|
|
return port
|
|
}
|
|
throw new Error(`No free port found in ${start}-${end}`)
|
|
}
|
|
|
|
async function waitForPort(port, timeoutMs = 60000) {
|
|
const startedAt = Date.now()
|
|
while (Date.now() - startedAt < timeoutMs) {
|
|
if (await isPortOpen(port))
|
|
return
|
|
await new Promise(resolve => setTimeout(resolve, 150))
|
|
}
|
|
throw new Error(`Timed out waiting for ${host}:${port}`)
|
|
}
|
|
|
|
function killProcessTree(child) {
|
|
if (!child || child.killed)
|
|
return
|
|
try {
|
|
child.kill('SIGTERM')
|
|
}
|
|
catch {}
|
|
setTimeout(() => {
|
|
try {
|
|
if (!child.killed)
|
|
child.kill('SIGKILL')
|
|
}
|
|
catch {}
|
|
}, 3000).unref?.()
|
|
}
|
|
|
|
function resolveChromeLaunchOptions() {
|
|
const candidates = [
|
|
process.env.PLAYWRIGHT_CHROME_PATH,
|
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
'/usr/bin/google-chrome',
|
|
'/usr/bin/chromium',
|
|
'/usr/bin/chromium-browser',
|
|
].filter(Boolean)
|
|
|
|
for (const candidate of candidates) {
|
|
if (existsSync(candidate)) {
|
|
return {
|
|
executablePath: candidate,
|
|
headless: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
channel: 'chrome',
|
|
headless: true,
|
|
}
|
|
}
|
|
|
|
function startDevServer(port) {
|
|
const logs = []
|
|
const child = spawn(
|
|
'pnpm',
|
|
['-C', playgroundDir, 'exec', 'vite', '--host', host, '--port', String(port), '--strictPort'],
|
|
{
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: {
|
|
...process.env,
|
|
CI: '1',
|
|
},
|
|
},
|
|
)
|
|
|
|
child.stdout.on('data', chunk => logs.push(String(chunk)))
|
|
child.stderr.on('data', chunk => logs.push(String(chunk)))
|
|
|
|
return {
|
|
child,
|
|
getLogs() {
|
|
return logs.join('')
|
|
},
|
|
}
|
|
}
|
|
|
|
function range(values) {
|
|
if (!values.length)
|
|
return null
|
|
return Math.max(...values) - Math.min(...values)
|
|
}
|
|
|
|
async function main() {
|
|
const port = await findFreePort()
|
|
const server = startDevServer(port)
|
|
|
|
try {
|
|
await waitForPort(port)
|
|
|
|
const browser = await chromium.launch(resolveChromeLaunchOptions())
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1600, height: 1200 },
|
|
storageState: {
|
|
cookies: [],
|
|
origins: [
|
|
{
|
|
origin: `http://${host}:${port}`,
|
|
localStorage: [
|
|
{ name: 'vmr-test-render-mode', value: 'monaco' },
|
|
{ name: 'vmr-test-code-stream', value: 'true' },
|
|
{ name: 'vmr-test-stream-chunk-size-min', value: '1' },
|
|
{ name: 'vmr-test-stream-chunk-size-max', value: '1' },
|
|
{ name: 'vmr-test-stream-delay-min', value: '80' },
|
|
{ name: 'vmr-test-stream-delay-max', value: '80' },
|
|
{ name: 'vmr-test-stream-burstiness', value: '0' },
|
|
{ name: 'vmr-test-stream-slice-mode', value: 'pure-random' },
|
|
{ name: 'vmr-test-stream-transport-mode', value: 'scheduler' },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
const page = await context.newPage()
|
|
|
|
await page.goto(`http://${host}:${port}/test`, { waitUntil: 'load' })
|
|
await page.waitForSelector('.editor-textarea')
|
|
await page.evaluate((sample) => {
|
|
const textarea = document.querySelector('.editor-textarea')
|
|
textarea.value = sample
|
|
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
|
}, diffSample)
|
|
await page.getByRole('button', { name: '开始流式渲染' }).click()
|
|
await page.waitForSelector('.code-block-container')
|
|
await page.evaluate(() => {
|
|
document.querySelector('.code-block-container')?.scrollIntoView({
|
|
block: 'center',
|
|
inline: 'nearest',
|
|
})
|
|
})
|
|
|
|
const frames = []
|
|
for (let index = 0; index < samplingFrameCount; index++) {
|
|
await page.waitForTimeout(sampleFrameIntervalMs)
|
|
const frame = await page.evaluate(() => {
|
|
const preview = document.querySelector('.preview-surface')
|
|
const viewportCenter = window.innerHeight / 2
|
|
const diffRoots = Array.from(
|
|
document.querySelectorAll('.preview-surface .stream-monaco-diff-root'),
|
|
)
|
|
const diffRoot = diffRoots
|
|
.map((root) => {
|
|
const rect = root.getBoundingClientRect()
|
|
const visibleHeight = Math.max(
|
|
0,
|
|
Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0),
|
|
)
|
|
return {
|
|
root,
|
|
rect,
|
|
visibleHeight,
|
|
distance: Math.abs((rect.top + rect.bottom) / 2 - viewportCenter),
|
|
}
|
|
})
|
|
.filter(entry => entry.visibleHeight > 0)
|
|
.sort((a, b) => {
|
|
if (b.visibleHeight !== a.visibleHeight)
|
|
return b.visibleHeight - a.visibleHeight
|
|
return a.distance - b.distance
|
|
})[0]
|
|
?.root ?? null
|
|
const block = diffRoot?.closest('.code-block-container') ?? null
|
|
const diffRootDataset = diffRoot
|
|
? {
|
|
streamingInlineControlledRaw: diffRoot.dataset.streamingInlineControlledRaw ?? null,
|
|
streamingInlineNextSpecCount: diffRoot.dataset.streamingInlineNextSpecCount ?? null,
|
|
streamingInlineVisibleSpecCount: diffRoot.dataset.streamingInlineVisibleSpecCount ?? null,
|
|
streamingInlineRenderedSpecCount: diffRoot.dataset.streamingInlineRenderedSpecCount ?? null,
|
|
streamingInlineNodeCount: diffRoot.dataset.streamingInlineNodeCount ?? null,
|
|
streamingInlineNodesConnected: diffRoot.dataset.streamingInlineNodesConnected ?? null,
|
|
streamingInlineReuse: diffRoot.dataset.streamingInlineReuse ?? null,
|
|
streamingInlineDefer: diffRoot.dataset.streamingInlineDefer ?? null,
|
|
streamingInlineRenderedMode: diffRoot.dataset.streamingInlineRenderedMode ?? null,
|
|
}
|
|
: null
|
|
const modifiedScroll = diffRoot?.querySelector(
|
|
'.editor.modified .monaco-scrollable-element.editor-scrollable',
|
|
) ?? null
|
|
const diffStatsText = block?.querySelector('.code-diff-stats')?.textContent?.trim() ?? null
|
|
const versionLine = Array.from(
|
|
diffRoot?.querySelectorAll('.editor.modified .view-line') ?? [],
|
|
).find((element) => {
|
|
if (!(element.textContent || '').includes('0.0.54-beta.1'))
|
|
return false
|
|
const style = getComputedStyle(element)
|
|
return style.display !== 'none'
|
|
&& style.visibility !== 'hidden'
|
|
&& Number.parseFloat(style.opacity || '1') > 0.01
|
|
})
|
|
const versionLineDetail = versionLine
|
|
? (() => {
|
|
const rect = versionLine.getBoundingClientRect()
|
|
const style = getComputedStyle(versionLine)
|
|
const parent = versionLine.parentElement
|
|
const parentRect = parent?.getBoundingClientRect() ?? null
|
|
const parentStyle = parent ? getComputedStyle(parent) : null
|
|
return {
|
|
text: (versionLine.textContent || '').trim(),
|
|
top: Math.round(rect.top),
|
|
height: Math.round(rect.height),
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
lineHeight: style.lineHeight,
|
|
innerHTML: versionLine.innerHTML.slice(0, 300),
|
|
parentClassName: typeof parent?.className === 'string' ? parent.className : '',
|
|
parentTop: parentRect ? Math.round(parentRect.top) : null,
|
|
parentHeight: parentRect ? Math.round(parentRect.height) : null,
|
|
parentDisplay: parentStyle?.display ?? null,
|
|
parentVisibility: parentStyle?.visibility ?? null,
|
|
parentOpacity: parentStyle?.opacity ?? null,
|
|
}
|
|
})()
|
|
: null
|
|
|
|
const visibleDiffTexts = Array.from(
|
|
diffRoot?.querySelectorAll(
|
|
'.editor.modified .view-line, .editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .inline-deleted-text, .editor.modified .view-lines.line-delete',
|
|
) ?? [],
|
|
)
|
|
.map((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const text = (element.textContent || '').replace(/\u00A0/g, ' ').trim()
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
text,
|
|
top: Math.round(rect.top),
|
|
height: Math.round(rect.height),
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
}
|
|
})
|
|
.filter(entry =>
|
|
entry.height > 0
|
|
&& entry.display !== 'none'
|
|
&& entry.visibility !== 'hidden'
|
|
&& Number.parseFloat(entry.opacity || '1') > 0.01
|
|
&& (entry.text.includes('version')
|
|
|| entry.text.includes('0.0.49')
|
|
|| entry.text.includes('0.0.54')),
|
|
)
|
|
.sort((left, right) => {
|
|
if (left.top !== right.top)
|
|
return left.top - right.top
|
|
return left.text.localeCompare(right.text)
|
|
})
|
|
const uniqueVisibleDiffTexts = []
|
|
const visibleDiffKeys = new Set()
|
|
for (const entry of visibleDiffTexts) {
|
|
const key = `${entry.top}:${entry.text}`
|
|
if (visibleDiffKeys.has(key))
|
|
continue
|
|
visibleDiffKeys.add(key)
|
|
uniqueVisibleDiffTexts.push(entry)
|
|
}
|
|
|
|
const visibleOldNodes = Array.from(
|
|
diffRoot?.querySelectorAll(
|
|
'.editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .stream-monaco-fallback-inline-delete-line, .editor.modified .inline-deleted-text, .editor.modified .view-line.line-delete, .editor.modified .view-lines.line-delete',
|
|
) ?? [],
|
|
)
|
|
.map((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
text: (element.textContent || '').trim(),
|
|
className: typeof element.className === 'string' ? element.className : '',
|
|
parentClassName: typeof element.parentElement?.className === 'string'
|
|
? element.parentElement.className
|
|
: '',
|
|
inViewZones: !!element.closest('.view-zones'),
|
|
inOverlayRoot: !!element.closest('.stream-monaco-fallback-inline-delete-overlay-root'),
|
|
width: rect.width,
|
|
height: rect.height,
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
}
|
|
})
|
|
.filter(entry =>
|
|
entry.width > 0
|
|
&& entry.height > 0
|
|
&& entry.display !== 'none'
|
|
&& entry.visibility !== 'hidden'
|
|
&& Number.parseFloat(entry.opacity || '1') > 0.01
|
|
&& entry.text.includes('0.0.49'),
|
|
)
|
|
const allFallbackZones = Array.from(
|
|
diffRoot?.querySelectorAll(
|
|
'.editor.modified .stream-monaco-fallback-inline-delete-zone, .editor.modified .stream-monaco-fallback-inline-delete-line',
|
|
) ?? [],
|
|
)
|
|
.map((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
text: (element.textContent || '').trim(),
|
|
className: typeof element.className === 'string' ? element.className : '',
|
|
parentClassName: typeof element.parentElement?.className === 'string'
|
|
? element.parentElement.className
|
|
: '',
|
|
inViewZones: !!element.closest('.view-zones'),
|
|
inOverlayRoot: !!element.closest('.stream-monaco-fallback-inline-delete-overlay-root'),
|
|
width: Math.round(rect.width),
|
|
height: Math.round(rect.height),
|
|
top: Math.round(rect.top),
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
}
|
|
})
|
|
const extraFallbackZones = allFallbackZones.filter((entry) => {
|
|
return entry.text.length > 0
|
|
&& !entry.text.includes('"version"')
|
|
&& !entry.text.includes('0.0.49')
|
|
})
|
|
const nearbyModifiedRows = Array.from(
|
|
diffRoot?.querySelectorAll(
|
|
'.editor.modified .view-line, .editor.modified .view-zones > div, .editor.modified .margin-view-zones > div',
|
|
) ?? [],
|
|
)
|
|
.map((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
text: (element.textContent || '').trim(),
|
|
className: typeof element.className === 'string' ? element.className : '',
|
|
width: Math.round(rect.width),
|
|
height: Math.round(rect.height),
|
|
top: Math.round(rect.top),
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
}
|
|
})
|
|
.filter(entry => entry.height > 0 && entry.top >= 740 && entry.top <= 880)
|
|
const visibleViewportRows = Array.from(
|
|
diffRoot?.querySelectorAll(
|
|
'.editor.modified .view-line, .editor.modified .view-zones > div, .editor.modified .margin-view-zones > div, .editor.modified .margin-view-overlays > div',
|
|
) ?? [],
|
|
)
|
|
.map((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const style = getComputedStyle(element)
|
|
return {
|
|
text: (element.textContent || '').trim().slice(0, 160),
|
|
className: typeof element.className === 'string' ? element.className : '',
|
|
width: Math.round(rect.width),
|
|
height: Math.round(rect.height),
|
|
top: Math.round(rect.top),
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
}
|
|
})
|
|
.filter(entry => entry.height > 0 && entry.top >= 620 && entry.top <= 1180)
|
|
|
|
const sampleVisibleLineParents = Array.from(
|
|
diffRoot?.querySelectorAll('.editor.modified .view-line') ?? [],
|
|
)
|
|
.filter((element) => {
|
|
const rect = element.getBoundingClientRect()
|
|
const style = getComputedStyle(element)
|
|
return rect.width > 0
|
|
&& rect.height > 0
|
|
&& style.display !== 'none'
|
|
&& style.visibility !== 'hidden'
|
|
&& Number.parseFloat(style.opacity || '1') > 0.01
|
|
})
|
|
.slice(0, 6)
|
|
.map((element) => {
|
|
const chain = []
|
|
let current = element
|
|
for (let index = 0; index < 6 && current; index++) {
|
|
chain.push({
|
|
tag: current.tagName,
|
|
className: typeof current.className === 'string' ? current.className : '',
|
|
})
|
|
current = current.parentElement
|
|
}
|
|
return {
|
|
text: (element.textContent || '').trim().slice(0, 80),
|
|
chain,
|
|
}
|
|
})
|
|
|
|
const visibleViewLineGroups = Array.from(
|
|
diffRoot?.querySelectorAll('.editor.modified .view-lines') ?? [],
|
|
)
|
|
.map((element) => {
|
|
const style = getComputedStyle(element)
|
|
const visibleLines = Array.from(
|
|
element.querySelectorAll < HTMLElement > ('.view-line'),
|
|
).filter((line) => {
|
|
const lineRect = line.getBoundingClientRect()
|
|
const lineStyle = getComputedStyle(line)
|
|
return lineRect.width > 0
|
|
&& lineRect.height > 0
|
|
&& lineStyle.display !== 'none'
|
|
&& lineStyle.visibility !== 'hidden'
|
|
&& Number.parseFloat(lineStyle.opacity || '1') > 0.01
|
|
}).length
|
|
const tops = Array.from(
|
|
element.querySelectorAll < HTMLElement > ('.view-line'),
|
|
)
|
|
.map((line) => {
|
|
const lineRect = line.getBoundingClientRect()
|
|
const lineStyle = getComputedStyle(line)
|
|
if (
|
|
lineRect.width <= 0
|
|
|| lineRect.height <= 0
|
|
|| lineStyle.display === 'none'
|
|
|| lineStyle.visibility === 'hidden'
|
|
|| Number.parseFloat(lineStyle.opacity || '1') <= 0.01
|
|
) {
|
|
return null
|
|
}
|
|
return Math.round(lineRect.top)
|
|
})
|
|
.filter(top => Number.isFinite(top))
|
|
return {
|
|
display: style.display,
|
|
visibility: style.visibility,
|
|
opacity: style.opacity,
|
|
visibleLineCount: visibleLines,
|
|
minTop: tops.length ? Math.min(...tops) : null,
|
|
maxTop: tops.length ? Math.max(...tops) : null,
|
|
}
|
|
})
|
|
.filter(entry =>
|
|
entry.display !== 'none'
|
|
&& entry.visibility !== 'hidden'
|
|
&& Number.parseFloat(entry.opacity || '1') > 0.01
|
|
&& entry.visibleLineCount > 0,
|
|
)
|
|
|
|
return {
|
|
hasRoot: !!diffRoot,
|
|
targetRootTop: diffRoot ? Math.round(diffRoot.getBoundingClientRect().top) : null,
|
|
streaming: !!diffRoot && diffRoot.classList.contains('stream-monaco-diff-streaming-active'),
|
|
diffStatsText,
|
|
diffRootDataset,
|
|
previewScrollTop: preview ? Math.round(preview.scrollTop) : null,
|
|
blockTop: block ? Math.round(block.getBoundingClientRect().top) : null,
|
|
modifiedScrollTop: modifiedScroll ? Math.round(modifiedScroll.scrollTop) : null,
|
|
versionTop: versionLine ? Math.round(versionLine.getBoundingClientRect().top) : null,
|
|
versionLineDetail,
|
|
visibleDiffTexts: uniqueVisibleDiffTexts,
|
|
visibleOldNodes,
|
|
allFallbackZones,
|
|
extraFallbackZones,
|
|
nearbyModifiedRows,
|
|
visibleViewportRows,
|
|
visibleViewLineGroups,
|
|
sampleVisibleLineParents,
|
|
}
|
|
})
|
|
frames.push(frame)
|
|
}
|
|
|
|
const activeFrames = frames.filter(frame => frame.hasRoot || frame.diffStatsText?.includes('-1'))
|
|
const firstStablePairIndex = activeFrames.findIndex((frame) => {
|
|
if (frame.visibleDiffTexts.length !== 2)
|
|
return false
|
|
const texts = frame.visibleDiffTexts.map(item => item.text)
|
|
return texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
|
|
&& texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
|
|
})
|
|
const stableFrames = firstStablePairIndex === -1
|
|
? []
|
|
: activeFrames.slice(firstStablePairIndex)
|
|
const oldOnlyFramesBeforePair = (firstStablePairIndex === -1
|
|
? activeFrames
|
|
: activeFrames.slice(0, firstStablePairIndex))
|
|
.filter((frame) => {
|
|
return frame.visibleDiffTexts.some(item => item.text.includes('0.0.49'))
|
|
&& !frame.visibleDiffTexts.some(item => item.text.includes('0.0.54-beta.1'))
|
|
})
|
|
const newOnlyFramesBeforePair = (firstStablePairIndex === -1
|
|
? activeFrames
|
|
: activeFrames.slice(0, firstStablePairIndex))
|
|
.filter((frame) => {
|
|
return frame.visibleDiffTexts.some(item => item.text.includes('0.0.54-beta.1'))
|
|
&& !frame.visibleDiffTexts.some(item => item.text.includes('0.0.49'))
|
|
})
|
|
const versionFrames = stableFrames.filter(frame => Number.isFinite(frame.versionTop))
|
|
const missingVersionFrames = stableFrames.filter(frame => !Number.isFinite(frame.versionTop))
|
|
const signatureValues = versionFrames.map(frame =>
|
|
JSON.stringify(frame.visibleDiffTexts.map(item => item.text)),
|
|
)
|
|
const zeroDiffStatsFrames = activeFrames.filter((frame) => {
|
|
if (!frame.streaming)
|
|
return false
|
|
const stats = (frame.diffStatsText || '').replace(/\s+/g, '')
|
|
return stats === '-0+0' || stats === '-0+0…'
|
|
})
|
|
const blankViewportFrames = activeFrames.filter(frame =>
|
|
frame.visibleViewportRows.filter(row => !row.text).length >= 4,
|
|
)
|
|
const result = {
|
|
activeFrameCount: activeFrames.length,
|
|
stableFrameCount: stableFrames.length,
|
|
versionFrameCount: versionFrames.length,
|
|
oldOnlyFramesBeforePair: oldOnlyFramesBeforePair.length,
|
|
newOnlyFramesBeforePair: newOnlyFramesBeforePair.length,
|
|
previewScrollTopRange: range(versionFrames.map(frame => frame.previewScrollTop).filter(Number.isFinite)),
|
|
blockTopRange: range(versionFrames.map(frame => frame.blockTop).filter(Number.isFinite)),
|
|
modifiedScrollTopRange: range(versionFrames.map(frame => frame.modifiedScrollTop).filter(Number.isFinite)),
|
|
versionTopRange: range(versionFrames.map(frame => frame.versionTop).filter(Number.isFinite)),
|
|
diffSignatureCount: new Set(signatureValues).size,
|
|
duplicateOldVisibleFrames: versionFrames.filter(frame =>
|
|
frame.visibleOldNodes.filter(node => node.className.includes('stream-monaco-fallback-inline-delete-zone')).length > 1,
|
|
).length,
|
|
missingVersionFrames: missingVersionFrames.length,
|
|
zeroDiffStatsFrames: zeroDiffStatsFrames.length,
|
|
blankViewportFrames: blankViewportFrames.length,
|
|
extraFallbackZoneFrames: versionFrames.filter(frame => frame.extraFallbackZones.length > 0).length,
|
|
multipleVisibleViewLineGroupFrames: versionFrames.filter(frame => frame.visibleViewLineGroups.length > 1).length,
|
|
badSignatureFrames: versionFrames.filter((frame) => {
|
|
if (frame.visibleDiffTexts.length !== 2)
|
|
return true
|
|
const texts = frame.visibleDiffTexts.map(item => item.text)
|
|
return !texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
|
|
|| !texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
|
|
}).length,
|
|
shiftedFrames: versionFrames
|
|
.filter(frame =>
|
|
frame.versionTop !== versionFrames[0]?.versionTop,
|
|
)
|
|
.slice(0, 12),
|
|
firstVersionFrame: versionFrames[0] ?? null,
|
|
firstActiveFrame: activeFrames[0] ?? null,
|
|
firstBadOldFrame: versionFrames.find(frame =>
|
|
frame.visibleOldNodes.filter(node => node.className.includes('stream-monaco-fallback-inline-delete-zone')).length > 1,
|
|
) ?? null,
|
|
firstOldOnlyFrame: oldOnlyFramesBeforePair[0] ?? null,
|
|
firstNewOnlyFrame: newOnlyFramesBeforePair[0] ?? null,
|
|
firstMissingVersionFrame: missingVersionFrames[0] ?? null,
|
|
firstZeroDiffStatsFrame: zeroDiffStatsFrames[0] ?? null,
|
|
firstBlankViewportFrame: blankViewportFrames[0] ?? null,
|
|
firstBadSignatureFrame: versionFrames.find((frame) => {
|
|
if (frame.visibleDiffTexts.length !== 2)
|
|
return true
|
|
const texts = frame.visibleDiffTexts.map(item => item.text)
|
|
return !texts.some(text => text.includes('"version"') && text.includes('0.0.49'))
|
|
|| !texts.some(text => text.includes('"version"') && text.includes('0.0.54-beta.1'))
|
|
}) ?? null,
|
|
}
|
|
|
|
const ok = result.activeFrameCount >= 12
|
|
&& result.stableFrameCount > 0
|
|
&& result.versionFrameCount > 0
|
|
&& result.oldOnlyFramesBeforePair === 0
|
|
&& result.newOnlyFramesBeforePair === 0
|
|
&& result.previewScrollTopRange === 0
|
|
&& result.blockTopRange === 0
|
|
&& result.modifiedScrollTopRange === 0
|
|
&& result.missingVersionFrames === 0
|
|
&& result.zeroDiffStatsFrames === 0
|
|
&& result.blankViewportFrames === 0
|
|
&& result.versionTopRange === 0
|
|
&& result.diffSignatureCount === 1
|
|
&& result.duplicateOldVisibleFrames === 0
|
|
&& result.extraFallbackZoneFrames === 0
|
|
&& result.multipleVisibleViewLineGroupFrames === 0
|
|
&& result.badSignatureFrames === 0
|
|
|
|
console.log(JSON.stringify({ ok, ...result }, null, 2))
|
|
|
|
await context.close()
|
|
await browser.close()
|
|
killProcessTree(server.child)
|
|
process.exit(ok ? 0 : 1)
|
|
}
|
|
catch (error) {
|
|
killProcessTree(server.child)
|
|
console.error(server.getLogs())
|
|
console.error(error)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
main()
|