feat(desktop): embed BoosAPI Desktop client as a subproject

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.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,237 @@
#!/usr/bin/env node
// Measure Monaco load/render/stream timings using the streaming-demo.
import net from 'node:net'
import process from 'node:process'
import { spawn } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.resolve(__dirname, '..')
const demoDir = path.join(repoRoot, 'examples', 'streaming-demo')
function isPortOpen(port, host = '127.0.0.1') {
return new Promise((resolve) => {
const sock = net.createConnection({ host, port })
sock.on('connect', () => {
sock.end()
resolve(true)
})
sock.on('error', () => {
sock.destroy()
resolve(false)
})
})
}
async function findFreePort(start = 5173, end = 5190) {
for (let port = start; port <= end; port++) {
// eslint-disable-next-line no-await-in-loop
const open = await isPortOpen(port)
if (!open)
return port
}
throw new Error(`No free port found in ${start}-${end}`)
}
async function waitForPort(port, ms = 20000) {
const start = Date.now()
while (true) {
// eslint-disable-next-line no-await-in-loop
const open = await isPortOpen(port)
if (open)
return
if (Date.now() - start > ms)
throw new Error(`Timed out waiting for port ${port}`)
// eslint-disable-next-line no-await-in-loop
await new Promise(r => setTimeout(r, 150))
}
}
function killProcessTree(child) {
if (!child || child.killed)
return
try {
if (child.pid)
process.kill(-child.pid, 'SIGTERM')
else
child.kill('SIGTERM')
}
catch {}
setTimeout(() => {
try {
if (child.pid)
process.kill(-child.pid, 'SIGKILL')
else if (!child.killed)
child.kill('SIGKILL')
}
catch {}
}, 3000).unref?.()
}
function waitForExit(child, ms = 5000) {
return new Promise((resolve) => {
if (!child)
return resolve(true)
if (child.exitCode != null)
return resolve(true)
const timer = setTimeout(() => resolve(false), ms)
child.once('exit', () => {
clearTimeout(timer)
resolve(true)
})
})
}
function spanFor(entries) {
if (!entries.length)
return null
const start = Math.min(...entries.map(e => e.startTime))
const end = Math.max(...entries.map(e => e.startTime + e.duration))
return { count: entries.length, start, end, duration: end - start }
}
function markMap(marks) {
const map = {}
for (const m of marks || [])
map[m.name] = m.t
return map
}
async function run() {
const port = process.env.PORT ? Number(process.env.PORT) : await findFreePort()
if (!Number.isFinite(port))
throw new Error(`Invalid PORT: ${process.env.PORT}`)
const vite = spawn(
'pnpm',
['-C', demoDir, 'dev', '--host', '127.0.0.1', '--port', String(port), '--strictPort'],
{ stdio: ['ignore', 'pipe', 'pipe'], env: process.env, detached: true },
)
const logs = []
vite.stdout.on('data', d => logs.push(String(d)))
vite.stderr.on('data', d => logs.push(String(d)))
const onExit = () => killProcessTree(vite)
process.on('SIGINT', onExit)
process.on('SIGTERM', onExit)
process.on('exit', onExit)
try {
await waitForPort(port)
const url = `http://127.0.0.1:${port}/`
const browser = await chromium.launch({ headless: true })
const page = await browser.newPage()
await page.goto(url, { waitUntil: 'networkidle' })
// Wait for key marks (best-effort)
await page.waitForFunction(() => {
const m = (window).__streamMonacoPerf?.marks || []
return m.some(x => x.name === 'createEditor_done')
}, null, { timeout: 20000 })
await page.waitForFunction(() => {
const m = (window).__streamMonacoPerf?.marks || []
return m.some(x => x.name === 'stream_done')
}, null, { timeout: 60000 })
const pageResult = await page.evaluate(() => {
const perf = (window).__streamMonacoPerf || { marks: [] }
const marks = perf.marks || []
const nav = performance.getEntriesByType('navigation')[0]
const resources = performance.getEntriesByType('resource')
const by = (re) => resources.filter(r => re.test(r.name))
const monaco = by(/monaco-editor/i)
const shiki = by(/shiki|@shikijs/i)
const workers = by(/worker\.js|workerMain\.js/i)
return {
url: location.href,
marks,
longTasks: perf.longTasks || 0,
nav: nav ? {
startTime: nav.startTime,
domContentLoaded: nav.domContentLoadedEventEnd,
loadEvent: nav.loadEventEnd,
} : null,
resourceSpans: {
monaco: monaco.length ? {
count: monaco.length,
start: Math.min(...monaco.map(e => e.startTime)),
end: Math.max(...monaco.map(e => e.startTime + e.duration)),
} : null,
shiki: shiki.length ? {
count: shiki.length,
start: Math.min(...shiki.map(e => e.startTime)),
end: Math.max(...shiki.map(e => e.startTime + e.duration)),
} : null,
workers: workers.length ? {
count: workers.length,
start: Math.min(...workers.map(e => e.startTime)),
end: Math.max(...workers.map(e => e.startTime + e.duration)),
} : null,
},
}
})
await browser.close()
const m = markMap(pageResult.marks)
const durations = {
to_monaco_dom: m.monaco_dom ?? null,
to_view_lines_text: m.view_lines_text ?? null,
to_view_lines_text_growth: m.view_lines_text_growth ?? null,
to_create_editor_done: m.createEditor_done ?? null,
create_editor_duration: (m.createEditor_done != null && m.createEditor_start != null)
? (m.createEditor_done - m.createEditor_start)
: null,
to_first_stream_update: m.stream_first_update ?? null,
to_stream_done: m.stream_done ?? null,
stream_duration: (m.stream_done != null && m.stream_first_update != null)
? (m.stream_done - m.stream_first_update)
: null,
}
const spans = pageResult.resourceSpans
const spansOut = {
monaco: spans.monaco
? { ...spans.monaco, duration: spans.monaco.end - spans.monaco.start }
: null,
shiki: spans.shiki
? { ...spans.shiki, duration: spans.shiki.end - spans.shiki.start }
: null,
workers: spans.workers
? { ...spans.workers, duration: spans.workers.end - spans.workers.start }
: null,
}
const out = {
ok: true,
url: pageResult.url,
longTasks: pageResult.longTasks,
nav: pageResult.nav,
durations,
resourceSpans: spansOut,
marks: pageResult.marks,
}
console.log(JSON.stringify(out, null, 2))
}
catch (err) {
console.error(err)
console.error('Recent Vite logs:\n', logs.slice(-40).join(''))
process.exitCode = 1
}
finally {
killProcessTree(vite)
await waitForExit(vite, 5000)
}
}
run().catch((err) => {
console.error(err)
process.exit(1)
})