mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* feat(devex): component fixtures with headless screenshots for fast UI checks Colocated *.fixture.tsx files export named fixtures (render + optional store seed, setup, interact, width). A dev-only Vite entry (vite.fixtures.config.ts, extending vite.web.config.ts) renders one fixture in isolation with the real I18nProvider, TooltipProvider, theme class, default-seeded store, and the web client's preload bridge (installWebPreloadApi) as window.api. `pnpm fixtures:shot <id|glob> [--theme dark|light|both]` starts or reuses a detached server (Vite + one headless Chromium) and writes PNGs to .cache/fixtures/. A warm page re-renders in place (~100ms/shot) and reloads only after source changes. A stdio MCP server (registered in .mcp.json) exposes list_fixtures and screenshot_fixture. Fixtures load only through import.meta.glob in the fixture entry, which is not a production build input; oxlint rejects static imports of *.fixture. * Keep fixture config and agent config inside reviewed root entries * Baseline the fixture page's default-settings seed in the focus-read ratchet * docs(agents): shorten fixtures pointer * Allowlist the dev-only fixture explorer's labels in localization coverage
42 lines
1.6 KiB
JavaScript
42 lines
1.6 KiB
JavaScript
import { readdirSync, statSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
export const repoRoot = path.resolve(import.meta.dirname, '../../..')
|
|
export const fixtureCacheDir = path.join(repoRoot, '.cache', 'fixtures')
|
|
export const serverStatePath = path.join(fixtureCacheDir, 'server.json')
|
|
export const serverLockPath = path.join(fixtureCacheDir, 'server.lock')
|
|
export const serverLogPath = path.join(fixtureCacheDir, 'server.log')
|
|
export const serverScriptPath = path.join(
|
|
repoRoot,
|
|
'config',
|
|
'scripts',
|
|
'component-fixtures',
|
|
'fixture-server.mjs'
|
|
)
|
|
|
|
// Why: a detached server outlives edits to these scripts; clients restart it on mismatch.
|
|
export function fixtureServerCodeStamp() {
|
|
const scriptDir = path.dirname(serverScriptPath)
|
|
const configPath = path.join(repoRoot, 'config', 'vite.fixtures.config.ts')
|
|
const files = [...readdirSync(scriptDir).map((name) => path.join(scriptDir, name)), configPath]
|
|
return String(Math.max(...files.map((file) => statSync(file).mtimeMs)))
|
|
}
|
|
|
|
export function fixtureScreenshotPath(outDir, id, theme) {
|
|
const fileStem = id
|
|
.replaceAll('/', '__')
|
|
.replaceAll(':', '--')
|
|
.replace(/[^\w.-]/g, '_')
|
|
return path.join(outDir, `${fileStem}.${theme}.png`)
|
|
}
|
|
|
|
// `*` matches within the id; a bare component id (no `:`) matches all of its fixtures.
|
|
export function matchFixtureIds(knownIds, pattern) {
|
|
if (!pattern.includes('*')) {
|
|
return knownIds.filter((id) => id === pattern || id.startsWith(`${pattern}:`))
|
|
}
|
|
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replaceAll('*', '.*')
|
|
const matcher = new RegExp(`^${escaped}$`)
|
|
return knownIds.filter((id) => matcher.test(id))
|
|
}
|