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,366 @@
#!/usr/bin/env node
// Validate async custom onDiffHunkAction patch/stash interception flow.
import net from 'node:net'
import path from 'node:path'
import process from 'node:process'
import { spawn } from 'node:child_process'
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')
const fixtures = {
inserted: {
pair: {
original: [
'const alpha = 1',
'const beta = 2',
'const gamma = 3',
].join('\n'),
modified: [
'const alpha = 1',
'const beta = 2',
'const inserted = 99',
'const gamma = 3',
].join('\n'),
},
hoverRootSelector: '.editor.modified',
hoverLineText: 'inserted = 99',
},
deleted: {
pair: {
original: [
'const alpha = 1',
'const legacy = 0',
'const beta = 2',
'const gamma = 3',
].join('\n'),
modified: [
'const alpha = 1',
'const beta = 2',
'const gamma = 3',
].join('\n'),
},
hoverRootSelector: '.editor.original',
hoverLineText: 'legacy = 0',
},
}
const cases = [
{
name: 'custom-patch-revert-lower',
fixture: 'inserted',
side: 'lower',
action: 'revert',
expected: {
original: fixtures.inserted.pair.original,
modified: fixtures.inserted.pair.original,
},
expectedDiffCount: 0,
expectedActionLogPrefix: 'REVERT lower |',
expectedPatchLogPrefix: 'PATCH FLOW REVERT lower |',
},
{
name: 'custom-patch-stage-upper',
fixture: 'deleted',
side: 'upper',
action: 'stage',
expected: {
original: fixtures.deleted.pair.modified,
modified: fixtures.deleted.pair.modified,
},
expectedDiffCount: 0,
expectedActionLogPrefix: 'STAGE upper |',
expectedPatchLogPrefix: 'PATCH FLOW STAGE upper |',
},
]
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 = 5290) {
for (let port = start; port <= end; port++) {
const open = await isPortOpen(port)
if (!open) return port
}
throw new Error(`No free port found in ${start}-${end}`)
}
async function waitForPort(port, ms = 30000) {
const start = Date.now()
while (true) {
const open = await isPortOpen(port)
if (open) return
if (Date.now() - start > ms)
throw new Error(`Timed out waiting for port ${port}`)
await new Promise((resolve) => setTimeout(resolve, 150))
}
}
function killProcessTree(child) {
if (!child || child.killed) return
try {
child.kill('SIGTERM')
} catch {}
setTimeout(() => {
try {
if (!child.killed) child.kill('SIGKILL')
} catch {}
}, 3000).unref?.()
}
async function invokeDiffTestApi(page, methodName, ...args) {
return page.evaluate(
({ methodName, args }) => {
const api = window.__streamMonacoDiffTestApi
const method = api?.[methodName]
if (typeof method !== 'function')
throw new Error(`Missing diff test API method: ${methodName}`)
return method(...args)
},
{ methodName, args },
)
}
async function waitForDiffFixture(page) {
await page.waitForFunction(() => {
const api = window.__streamMonacoDiffTestApi
const values = api?.getDiffValues?.()
const summary = api?.getDiffSummary?.()
return (
values &&
summary &&
values.original !== values.modified &&
summary.diffCount > 0
)
}, { timeout: 8000 })
}
async function hoverDiffLine(page, rootSelector, lineText) {
const line = page
.locator(`${rootSelector} .view-lines .view-line`)
.filter({ hasText: lineText })
.first()
await line.waitFor({ state: 'visible', timeout: 5000 })
await line.hover()
await page.waitForTimeout(400)
}
async function clickHunkAction(page, side, action) {
const button = page.locator(
`.stream-monaco-diff-hunk-actions[data-side="${side}"] button[data-action="${action}"]`,
)
await button.waitFor({ state: 'visible', timeout: 5000 })
if (await button.isDisabled()) {
throw new Error(`Expected ${side}/${action} button to be enabled`)
}
await button.click()
}
async function waitForExpectedState(page, expected, expectedDiffCount) {
await page.waitForFunction(
({ expected, expectedDiffCount }) => {
const api = window.__streamMonacoDiffTestApi
const values = api?.getDiffValues?.()
const summary = api?.getDiffSummary?.()
return (
values &&
summary &&
values.original === expected.original &&
values.modified === expected.modified &&
summary.diffCount === expectedDiffCount
)
},
{ expected, expectedDiffCount },
{ timeout: 8000 },
)
}
async function runCase(page, scenario) {
const fixture = fixtures[scenario.fixture]
if (!fixture) throw new Error(`Unknown fixture: ${scenario.fixture}`)
let before = null
try {
await invokeDiffTestApi(page, 'setHunkActionMode', 'custom-patch')
await invokeDiffTestApi(page, 'setDiffPair', fixture.pair, {
preserveViewState: false,
})
await waitForDiffFixture(page)
before = await invokeDiffTestApi(page, 'getDiffValues')
await hoverDiffLine(page, fixture.hoverRootSelector, fixture.hoverLineText)
await clickHunkAction(page, scenario.side, scenario.action)
await waitForExpectedState(
page,
scenario.expected,
scenario.expectedDiffCount,
)
const after = await invokeDiffTestApi(page, 'getDiffValues')
const summary = await invokeDiffTestApi(page, 'getDiffSummary')
const actionLogs = await invokeDiffTestApi(page, 'getActionLogs')
const patchFlowLogs = await invokeDiffTestApi(page, 'getPatchFlowLogs')
const mode = await invokeDiffTestApi(page, 'getHunkActionMode')
const firstActionLog = actionLogs[0] ?? null
const firstPatchLog = patchFlowLogs[0] ?? null
const ok =
mode === 'custom-patch'
&& after.original === scenario.expected.original
&& after.modified === scenario.expected.modified
&& summary.diffCount === scenario.expectedDiffCount
&& typeof firstActionLog === 'string'
&& firstActionLog.startsWith(scenario.expectedActionLogPrefix)
&& typeof firstPatchLog === 'string'
&& firstPatchLog.startsWith(scenario.expectedPatchLogPrefix)
return {
name: scenario.name,
side: scenario.side,
action: scenario.action,
ok,
mode,
firstActionLog,
firstPatchLog,
before,
after,
summary,
}
} catch (error) {
const after =
await invokeDiffTestApi(page, 'getDiffValues').catch(() => null)
const summary =
await invokeDiffTestApi(page, 'getDiffSummary').catch(() => null)
const actionLogs =
await invokeDiffTestApi(page, 'getActionLogs').catch(() => [])
const patchFlowLogs =
await invokeDiffTestApi(page, 'getPatchFlowLogs').catch(() => [])
const mode =
await invokeDiffTestApi(page, 'getHunkActionMode').catch(() => null)
return {
name: scenario.name,
side: scenario.side,
action: scenario.action,
ok: false,
error: String(error),
mode,
before,
after,
summary,
actionLogs,
patchFlowLogs,
}
}
}
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 },
)
const logs = []
vite.stdout.on('data', (chunk) => logs.push(String(chunk)))
vite.stderr.on('data', (chunk) => logs.push(String(chunk)))
const onExit = () => killProcessTree(vite)
process.on('SIGINT', onExit)
process.on('SIGTERM', onExit)
process.on('exit', onExit)
try {
await waitForPort(port)
const browser = await chromium.launch({ headless: true })
const page = await browser.newPage({
viewport: { width: 1440, height: 1100 },
deviceScaleFactor: 1,
})
const pageErrors = []
const consoleErrors = []
page.on('pageerror', (error) => {
pageErrors.push(String(error))
})
page.on('console', (message) => {
if (message.type() === 'error') consoleErrors.push(message.text())
})
const url = `http://127.0.0.1:${port}/diff-ux?style=background&scenario=streaming&theme=vitesse-light&appearance=light`
await page.goto(url, { waitUntil: 'networkidle' })
await page.waitForSelector('.editor-card', { timeout: 20000 })
await page.waitForSelector('.monaco-diff-editor', { timeout: 20000 })
await page.waitForFunction(
() => typeof window.__streamMonacoDiffTestApi?.setHunkActionMode === 'function',
{ timeout: 20000 },
)
await page.waitForTimeout(800)
const results = []
for (const scenario of cases) {
results.push(await runCase(page, scenario))
}
await invokeDiffTestApi(page, 'setHunkActionMode', 'default')
await browser.close()
const ok =
pageErrors.length === 0
&& consoleErrors.length === 0
&& results.every((result) => result.ok)
const result = {
ok,
url,
pageErrors,
consoleErrors,
results,
}
console.log(JSON.stringify(result, null, 2))
if (!ok) {
console.error('Custom diff hunk patch-flow validation failed; recent Vite logs:\n')
console.error(logs.slice(-60).join(''))
process.exit(1)
}
} finally {
killProcessTree(vite)
}
}
run().catch((error) => {
console.error(error)
process.exit(1)
})