diff --git a/package.json b/package.json index 092f18eaf..ea622bd22 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,13 @@ "build:packages": "turbo run build -F=\"./packages/*\"", "build:engines": "turbo run build -F=\"./engines/*\"", "test": "vitest --coverage", - "test:run": "vitest run && pnpm run test-audio-pipelines-transcribe:run && pnpm run test-vishot:run && pnpm run test-ui:run", + "test:run": "vitest run && pnpm run test-audio-pipelines-transcribe:run && pnpm run test-ui:run", "test-audio-pipelines-transcribe:run": "vitest run --config packages/audio-pipelines-transcribe/vitest.config.ts", - "test-vishot:run": "vitest run --config packages/vishot-runtime/vitest.config.ts", "test-ui:run": "vitest run --config packages/stage-ui/vitest.config.ts", "lint": "moeru-lint .", "lint:fix": "moeru-lint --fix .", "lint:swift": "pnpm -rF @proj-airi/stage-pocket run lint:swift", - "capture:tamagotchi": "pnpm -F @proj-airi/vishot-runner-electron capture -- ./packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket.ts --output-dir ./packages/scenarios-stage-tamagotchi-browser/artifacts/raw", + "capture:tamagotchi": "pnpm exec vishot capture --target electron ./packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --app-entrypoint ./apps/stage-tamagotchi/out/main/index.js --cwd . --output-dir ./packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif", "to-avif": "tsx docs/scripts/avif.ts", "sponsors:generate": "sponsorkit --output-dir docs/content/public/assets/sponsors", "typecheck": "pnpm -rF=\"./packages/*\" -F=\"./apps/*\" -F=\"./docs\" --parallel typecheck", @@ -62,6 +61,7 @@ "@unocss/eslint-plugin": "catalog:", "@unocss/preset-mini": "catalog:", "@unocss/preset-web-fonts": "catalog:", + "@vishot/cli": "catalog:", "@vitest/browser-playwright": "catalog:vitest", "@vitest/coverage-v8": "catalog:vitest", "bumpp": "catalog:", diff --git a/packages/airi-screenshot/README.md b/packages/airi-screenshot/README.md deleted file mode 100644 index 99c1b9de6..000000000 --- a/packages/airi-screenshot/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# AIRI Screenshot - -Project-specific screenshot orchestration for the AIRI monorepo. - -## Purpose - -This package owns AIRI-specific screenshot knowledge: - -- workspace paths and default output directories -- named presets for AIRI screenshot scenarios -- target-level commands for AIRI surfaces such as `stage-tamagotchi` -- future GitHub slash-command wiring for PR screenshot previews - -It does not own generic screenshot capture primitives. Keep reusable browser, -Electron, Histoire, readiness, and artifact logic in the `vishot-*` packages so -those packages remain publishable without AIRI-specific behavior. - -## Usage - -From the repository root: - -```bash -pnpm -F @proj-airi/airi-screenshot capture tamagotchi --scenario settings-connection --output-dir .vishot/pr-123 -``` - -Use an explicit scenario path when no preset exists: - -```bash -pnpm -F @proj-airi/airi-screenshot capture tamagotchi --scenario packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts --output-dir .vishot/hearing --format avif -``` - -The CLI currently supports: - -- target: `tamagotchi` -- formats: `png`, `avif` -- presets: `settings-connection`, `demo-hearing-dialog` - -## Boundary - -Use this package when the command needs AIRI product knowledge. Use the -underlying `@proj-airi/vishot-runner-*` packages directly when authoring or -testing generic screenshot capture behavior. diff --git a/packages/airi-screenshot/package.json b/packages/airi-screenshot/package.json deleted file mode 100644 index ba345178b..000000000 --- a/packages/airi-screenshot/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "@proj-airi/airi-screenshot", - "type": "module", - "version": "0.11.3", - "private": true, - "description": "AIRI monorepo screenshot orchestration CLI", - "author": { - "name": "Moeru AI Project AIRI Team", - "email": "airi@moeru.ai", - "url": "https://github.com/moeru-ai" - }, - "license": "MIT", - "exports": "./src/index.ts", - "bin": { - "airi-screenshot": "./src/cli.ts" - }, - "scripts": { - "capture": "tsx src/cli.ts capture", - "test:run": "vitest run", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@moeru/std": "catalog:", - "cac": "catalog:", - "tinyexec": "catalog:" - } -} diff --git a/packages/airi-screenshot/src/cli.test.ts b/packages/airi-screenshot/src/cli.test.ts deleted file mode 100644 index f6d5eb641..000000000 --- a/packages/airi-screenshot/src/cli.test.ts +++ /dev/null @@ -1,201 +0,0 @@ -import path from 'node:path' - -import { mkdir, mkdtemp, writeFile } from 'node:fs/promises' -import { tmpdir } from 'node:os' - -import { x } from 'tinyexec' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { discoverTamagotchiScenarios, main, parseAiriScreenshotCliArguments } from './cli' - -vi.mock('tinyexec', () => ({ - x: vi.fn(async () => ({ - exitCode: 0, - stderr: '', - stdout: '', - })), -})) - -const xMock = vi.mocked(x) - -beforeEach(() => { - xMock.mockClear() -}) - -describe('parseAiriScreenshotCliArguments', () => { - it('parses a tamagotchi capture preset', () => { - expect(parseAiriScreenshotCliArguments([ - 'capture', - 'tamagotchi', - '--scenario', - 'settings-connection', - '--output-dir', - '.vishot/pr-123', - ])).toEqual({ - command: 'capture', - target: 'tamagotchi', - scenario: 'settings-connection', - outputDir: '.vishot/pr-123', - format: 'png', - }) - }) - - it('parses a tamagotchi scenario file path and output format', () => { - expect(parseAiriScreenshotCliArguments([ - 'capture', - 'tamagotchi', - '--scenario', - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - '--output-dir', - '.vishot/hearing', - '--format', - 'avif', - ])).toEqual({ - command: 'capture', - target: 'tamagotchi', - scenario: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - outputDir: '.vishot/hearing', - format: 'avif', - }) - }) - - it('uses a target-specific default output directory', () => { - expect(parseAiriScreenshotCliArguments([ - 'capture', - 'tamagotchi', - '--scenario', - 'settings-connection', - ])).toEqual({ - command: 'capture', - target: 'tamagotchi', - scenario: 'settings-connection', - outputDir: '.vishot/airi-screenshot/tamagotchi', - format: 'png', - }) - }) - - it('rejects unsupported targets', () => { - expect(() => parseAiriScreenshotCliArguments([ - 'capture', - 'stage-web', - '--route', - '/settings/characters', - ])).toThrow('Unsupported AIRI screenshot target "stage-web". Expected "tamagotchi".') - }) - - it('rejects missing tamagotchi scenario input', () => { - expect(() => parseAiriScreenshotCliArguments([ - 'capture', - 'tamagotchi', - ])).toThrow('Usage: airi-screenshot capture tamagotchi --scenario [--output-dir ]') - }) -}) - -describe('main', () => { - it('delegates a discovered scenario id to the existing Electron runner', async () => { - await main([ - 'capture', - 'tamagotchi', - '--scenario', - 'settings-connection', - '--output-dir', - '.vishot/pr-123', - ]) - - expect(xMock).toHaveBeenCalledWith( - 'pnpm', - [ - '-F', - '@proj-airi/vishot-runner-electron', - 'capture', - '../scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - '../../.vishot/pr-123', - '--format', - 'png', - ], - { - throwOnError: false, - nodeOptions: { - stdio: 'inherit', - }, - }, - ) - }) - - it('delegates explicit scenario paths to the existing Electron runner', async () => { - await main([ - 'capture', - 'tamagotchi', - '--scenario', - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - '--output-dir', - '.vishot/hearing', - '--format', - 'avif', - ]) - - expect(xMock).toHaveBeenCalledWith( - 'pnpm', - [ - '-F', - '@proj-airi/vishot-runner-electron', - 'capture', - '../scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - '--output-dir', - '../../.vishot/hearing', - '--format', - 'avif', - ], - { - throwOnError: false, - nodeOptions: { - stdio: 'inherit', - }, - }, - ) - }) - - it('rejects non-zero delegated runner exits', async () => { - xMock.mockResolvedValueOnce({ - exitCode: 1, - stderr: '', - stdout: '', - }) - - await expect(main([ - 'capture', - 'tamagotchi', - '--scenario', - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - ])).rejects.toThrow('AIRI screenshot command exited with code 1.') - }) -}) - -describe('discoverTamagotchiScenarios', () => { - it('discovers root scenario files and folder scenarios with index entrypoints', async () => { - const scenariosRoot = await mkdtemp(path.join(tmpdir(), 'airi-screenshot-scenarios-')) - const folderScenarioRoot = path.join(scenariosRoot, 'demo-controls-settings-chat-websocket') - - await mkdir(folderScenarioRoot) - await writeFile(path.join(scenariosRoot, 'settings-connection.ts'), '') - await writeFile(path.join(scenariosRoot, 'demo-hearing-dialog.ts'), '') - await writeFile(path.join(folderScenarioRoot, 'index.ts'), '') - await writeFile(path.join(folderScenarioRoot, 'manifest.ts'), '') - - await expect(discoverTamagotchiScenarios({ scenariosRoot })).resolves.toEqual({ - 'demo-controls-settings-chat-websocket': path.join(folderScenarioRoot, 'index.ts'), - 'demo-hearing-dialog': path.join(scenariosRoot, 'demo-hearing-dialog.ts'), - 'settings-connection': path.join(scenariosRoot, 'settings-connection.ts'), - }) - }) - - it('discovers a single root scenario file', async () => { - const scenariosRoot = await mkdtemp(path.join(tmpdir(), 'airi-screenshot-scenarios-')) - await writeFile(path.join(scenariosRoot, 'settings-connection.ts'), '') - - await expect(discoverTamagotchiScenarios({ scenariosRoot })).resolves.toEqual({ - 'settings-connection': path.join(scenariosRoot, 'settings-connection.ts'), - }) - }) -}) diff --git a/packages/airi-screenshot/src/cli.ts b/packages/airi-screenshot/src/cli.ts deleted file mode 100644 index 2d2c0ed43..000000000 --- a/packages/airi-screenshot/src/cli.ts +++ /dev/null @@ -1,251 +0,0 @@ -import path from 'node:path' -import process from 'node:process' - -import { readdir } from 'node:fs/promises' -import { fileURLToPath } from 'node:url' - -import { errorMessageFrom } from '@moeru/std' -import { cac } from 'cac' -import { x } from 'tinyexec' - -type AiriScreenshotCommand = 'capture' -type AiriScreenshotTarget = 'tamagotchi' -type AiriScreenshotFormat = 'png' | 'avif' - -/** Repository root resolved from this package's source location. */ -const repoRootPath = path.resolve(fileURLToPath(new URL('../../..', import.meta.url))) -/** Path to the generic Electron runner package from the repository root. */ -const electronRunnerPackagePath = path.join(repoRootPath, 'packages', 'vishot-runner-electron') -/** Root containing product-owned Electron capture scenarios. */ -const tamagotchiScenariosRootPath = path.join(repoRootPath, 'packages', 'scenarios-stage-tamagotchi-electron', 'src', 'scenarios') - -/** - * Represents one AIRI screenshot CLI capture request. - * - * @param command The top-level CLI command selected by the user. - */ -export interface AiriScreenshotCliArguments { - /** Screenshot operation to run. */ - command: AiriScreenshotCommand - /** AIRI surface to capture. */ - target: AiriScreenshotTarget - /** Preset key or repository-root-relative scenario path. */ - scenario: string - /** Repository-root-relative output directory for generated images. */ - outputDir: string - /** Image format passed through to the underlying runner. */ - format: AiriScreenshotFormat -} - -const usageMessage = 'Usage: airi-screenshot capture tamagotchi --scenario [--output-dir ]' - -function normalizeCliArgv(argv: string[]): string[] { - return argv[0] === '--' ? argv.slice(1) : argv -} - -function parseFormat(format: string | undefined): AiriScreenshotFormat { - if (format === undefined || format.length === 0) { - return 'png' - } - - if (format === 'png' || format === 'avif') { - return format - } - - throw new Error(`Unsupported screenshot format "${format}". Expected "png" or "avif".`) -} - -function createAiriScreenshotCli() { - const cli = cac('airi-screenshot') - - cli - .command('capture ', 'Capture screenshots for an AIRI surface') - .option('--scenario ', 'Scenario id discovered from the target scenario directory, or an explicit scenario file path') - .option('--output-dir, -o ', 'Directory to write generated screenshots') - .option('--format ', 'Output format: png or avif') - - return cli -} - -/** - * Discovers tamagotchi Electron scenario entrypoints. - * - * Use when: - * - Resolving slash-command scenario ids without maintaining a hardcoded list. - * - Keeping scenario ownership in `packages/scenarios-stage-tamagotchi-electron`. - * - * Expects: - * - Root `*.ts` files are runnable scenario entrypoints. - * - First-level folders with `index.ts` are runnable scenario entrypoints. - * - * Returns: - * - A stable id-to-file-path map sorted by scenario id. - */ -export async function discoverTamagotchiScenarios(options: { - scenariosRoot?: string -} = {}): Promise> { - const scenariosRoot = options.scenariosRoot ?? tamagotchiScenariosRootPath - const dirents = await readdir(scenariosRoot, { withFileTypes: true }) - const scenarios: Record = {} - - for (const dirent of dirents) { - if (dirent.isFile() && dirent.name.endsWith('.ts')) { - scenarios[path.basename(dirent.name, '.ts')] = path.join(scenariosRoot, dirent.name) - continue - } - - if (!dirent.isDirectory()) { - continue - } - - const scenarioEntryPath = path.join(scenariosRoot, dirent.name, 'index.ts') - - try { - const nestedDirents = await readdir(path.join(scenariosRoot, dirent.name), { withFileTypes: true }) - if (nestedDirents.some(nestedDirent => nestedDirent.isFile() && nestedDirent.name === 'index.ts')) { - scenarios[dirent.name] = scenarioEntryPath - } - } - catch { - // Directory vanished between the root scan and nested scan; ignore it for discovery. - } - } - - return Object.fromEntries( - Object.entries(scenarios).sort(([left], [right]) => left.localeCompare(right)), - ) -} - -async function resolveTamagotchiScenarioPath( - scenario: string, - options: { - scenariosRoot?: string - } = {}, -): Promise { - if (scenario.endsWith('.ts') || scenario.includes('/') || scenario.includes('\\')) { - return path.isAbsolute(scenario) ? scenario : path.join(repoRootPath, scenario) - } - - const scenarios = await discoverTamagotchiScenarios(options) - const scenarioPath = scenarios[scenario] - - if (scenarioPath) { - return scenarioPath - } - - const availableScenarios = Object.keys(scenarios) - const availableText = availableScenarios.length > 0 - ? availableScenarios.join(', ') - : 'none' - - throw new Error(`Unknown tamagotchi scenario "${scenario}". Available scenarios: ${availableText}.`) -} - -/** - * Parses AIRI screenshot CLI arguments into a typed capture request. - * - * Use when: - * - Translating slash-command payloads into local CLI invocations. - * - Testing AIRI-specific defaults without launching browsers or Electron. - * - * Expects: - * - `argv` starts with `capture tamagotchi`. - * - Scenario values are either known preset keys or repository-root-relative file paths. - * - * Returns: - * - A normalized request with default format and output directory applied. - */ -export function parseAiriScreenshotCliArguments(argv: string[]): AiriScreenshotCliArguments { - const normalizedArgv = normalizeCliArgv(argv) - const cli = createAiriScreenshotCli() - const parsed = cli.parse(['node', 'airi-screenshot', ...normalizedArgv], { run: false }) - const [command] = normalizedArgv - const [target] = parsed.args - - if (command !== 'capture') { - throw new Error(usageMessage) - } - - if (target !== 'tamagotchi') { - throw new Error(`Unsupported AIRI screenshot target "${target ?? ''}". Expected "tamagotchi".`) - } - - const scenario = typeof parsed.options.scenario === 'string' ? parsed.options.scenario : undefined - const outputDir = typeof parsed.options.outputDir === 'string' ? parsed.options.outputDir : undefined - const format = typeof parsed.options.format === 'string' ? parsed.options.format : undefined - - if (!scenario) { - throw new Error(usageMessage) - } - - return { - command, - target, - scenario, - outputDir: outputDir ?? path.join('.vishot', 'airi-screenshot', 'tamagotchi'), - format: parseFormat(format), - } -} - -/** - * Runs the AIRI screenshot CLI. - * - * Use when: - * - Invoked through `pnpm -F @proj-airi/airi-screenshot capture ...`. - * - Reusing AIRI-specific screenshot presets from local automation or CI. - * - * Expects: - * - The current working directory is the AIRI repository root. - * - * Returns: - * - Resolves after the delegated Vishot runner exits successfully. - * - * Call stack: - * - * main - * -> {@link parseAiriScreenshotCliArguments} - * -> {@link resolveTamagotchiScenarioPath} - * -> x - */ -export async function main(argv: string[] = process.argv.slice(2)): Promise { - const request = parseAiriScreenshotCliArguments(argv) - const scenarioPath = await resolveTamagotchiScenarioPath(request.scenario) - const outputDir = path.isAbsolute(request.outputDir) - ? request.outputDir - : path.join(repoRootPath, request.outputDir) - - // The filtered pnpm command runs from packages/vishot-runner-electron. - const runnerScenarioPath = scenarioPath.startsWith(repoRootPath) - ? path.relative(electronRunnerPackagePath, scenarioPath) - : scenarioPath - const runnerOutputDir = outputDir.startsWith(repoRootPath) - ? path.relative(electronRunnerPackagePath, outputDir) - : outputDir - - const output = await x('pnpm', [ - '-F', - '@proj-airi/vishot-runner-electron', - 'capture', - runnerScenarioPath, - '--output-dir', - runnerOutputDir, - '--format', - request.format, - ], { - throwOnError: false, - nodeOptions: { - stdio: 'inherit', - }, - }) - - if (output.exitCode !== 0) { - throw new Error(`AIRI screenshot command exited with code ${output.exitCode ?? 'unknown'}.`) - } -} - -if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { - void main().catch((error) => { - console.error(errorMessageFrom(error) ?? usageMessage) - process.exitCode = 1 - }) -} diff --git a/packages/airi-screenshot/src/index.ts b/packages/airi-screenshot/src/index.ts deleted file mode 100644 index b3e6cd693..000000000 --- a/packages/airi-screenshot/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { - discoverTamagotchiScenarios, - main, - parseAiriScreenshotCliArguments, -} from './cli' - -export type { - AiriScreenshotCliArguments, -} from './cli' diff --git a/packages/airi-screenshot/tsconfig.json b/packages/airi-screenshot/tsconfig.json deleted file mode 100644 index 97068867e..000000000 --- a/packages/airi-screenshot/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2023", - "module": "ESNext", - "moduleResolution": "Bundler", - "types": [ - "node" - ], - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts", - "vitest.config.ts" - ] -} diff --git a/packages/airi-screenshot/vitest.config.ts b/packages/airi-screenshot/vitest.config.ts deleted file mode 100644 index 647f39364..000000000 --- a/packages/airi-screenshot/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - include: ['src/**/*.test.ts'], - }, -}) diff --git a/packages/scenarios-stage-tamagotchi-browser/README.md b/packages/scenarios-stage-tamagotchi-browser/README.md index f4e54df9b..6b0ece42e 100644 --- a/packages/scenarios-stage-tamagotchi-browser/README.md +++ b/packages/scenarios-stage-tamagotchi-browser/README.md @@ -9,7 +9,7 @@ This package is the tamagotchi browser composition layer. It contains: - the Vite/Vue scene app - the scene composition components and capture roots - file-based scene routing from `src/scenes/**` via `unplugin-vue-router` -- the `capture` script that exports final browser captures through `@proj-airi/vishot-runner-browser` +- the `capture` script that exports final browser captures through `@vishot/renderer-browser` It expects raw business screenshots to exist in `artifacts/raw` before final export runs. @@ -31,7 +31,7 @@ Current docs capture target route: From repo root, run: ```bash -pnpm -F @proj-airi/vishot-runner-electron capture ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif +pnpm exec vishot capture --target electron ./packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --app-entrypoint ./apps/stage-tamagotchi/out/main/index.js --cwd . --output-dir ./packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/intro-chat' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800 pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/intro-websocket' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800 pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/main-window' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800 diff --git a/packages/scenarios-stage-tamagotchi-browser/package.json b/packages/scenarios-stage-tamagotchi-browser/package.json index 3d2577e13..573e9072d 100644 --- a/packages/scenarios-stage-tamagotchi-browser/package.json +++ b/packages/scenarios-stage-tamagotchi-browser/package.json @@ -24,8 +24,8 @@ "dependencies": { "@moeru/std": "catalog:", "@napi-rs/image": "catalog:", - "@proj-airi/vishot-runner-browser": "workspace:^", - "@proj-airi/vishot-runtime": "workspace:^", + "@vishot/mockup-desktop-vue": "catalog:", + "@vishot/renderer-browser": "catalog:", "@vueuse/core": "catalog:", "date-fns": "catalog:", "reka-ui": "catalog:", diff --git a/packages/scenarios-stage-tamagotchi-browser/scripts/capture.ts b/packages/scenarios-stage-tamagotchi-browser/scripts/capture.ts index b44df4468..eacfd85d5 100644 --- a/packages/scenarios-stage-tamagotchi-browser/scripts/capture.ts +++ b/packages/scenarios-stage-tamagotchi-browser/scripts/capture.ts @@ -1,4 +1,4 @@ -import type { ArtifactTransformer } from '@proj-airi/vishot-runner-browser' +import type { ArtifactTransformer } from '@vishot/renderer-browser' import path from 'node:path' @@ -6,7 +6,7 @@ import { readFile, rm, writeFile } from 'node:fs/promises' import { argv, cwd } from 'node:process' import { Transformer } from '@napi-rs/image' -import { captureBrowserRoots } from '@proj-airi/vishot-runner-browser' +import { captureBrowserRoots } from '@vishot/renderer-browser' const DEFAULT_AVIF_MAX_WIDTH = 1200 const DEFAULT_AVIF_QUALITY = 35 diff --git a/packages/scenarios-stage-tamagotchi-browser/src/App.vue b/packages/scenarios-stage-tamagotchi-browser/src/App.vue index 09e198952..5c0682aa3 100644 --- a/packages/scenarios-stage-tamagotchi-browser/src/App.vue +++ b/packages/scenarios-stage-tamagotchi-browser/src/App.vue @@ -1,5 +1,5 @@ -
-
-

Chat window

-

Capture root fixture.

-
-
-

WebSocket settings

-

Capture root fixture.

-
-
- - -`, - ) - - await writeFile( - path.join(rootDir, 'vite.config.ts'), - `import { defineConfig } from 'vite' - -export default defineConfig({ - publicDir: 'artifacts', -}) -`, - ) - - return rootDir -} - -async function cleanupFixtureRoots(): Promise { - await Promise.allSettled( - Array.from(fixtureRoots, async (rootDir) => { - await rm(rootDir, { force: true, recursive: true }) - fixtureRoots.delete(rootDir) - }), - ) -} - -afterEach(async () => { - await cleanupFixtureRoots() -}) - -afterAll(async () => { - await cleanupFixtureRoots() -}) - -describe('captureBrowserRoots', () => { - it('captures all roots from a vite-served scene app', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - - const artifacts = await captureBrowserRoots({ - sceneAppRoot, - routePath: '/', - outputDir, - }) - - expect(artifacts.map(item => item.artifactName)).toEqual([ - 'intro-chat-window', - 'intro-websocket-settings', - ]) - expect(artifacts.every(item => item.filePath.startsWith(outputDir))).toBe(true) - }, 120000) - - it('applies image transformers per root and returns transformed image artifacts', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - const transformer = vi.fn(async (artifact) => { - const derivedFilePath = artifact.filePath.replace(/\.png$/, '.avif') - await writeFile(derivedFilePath, `derived from ${artifact.filePath}\n`) - - return { - ...artifact, - filePath: derivedFilePath, - format: 'avif', - } - }) - - const artifacts = await captureBrowserRoots({ - imageTransformers: [transformer], - sceneAppRoot, - routePath: '/', - outputDir, - }) - - expect(transformer).toHaveBeenCalledTimes(2) - expect(transformer.mock.calls.map(([artifact]) => artifact.artifactName)).toEqual([ - 'intro-chat-window', - 'intro-websocket-settings', - ]) - expect(artifacts).toEqual([ - expect.objectContaining({ - artifactName: 'intro-chat-window', - filePath: path.join(outputDir, 'intro-chat-window.avif'), - format: 'avif', - kind: 'image', - stage: 'browser-final', - }), - expect.objectContaining({ - artifactName: 'intro-websocket-settings', - filePath: path.join(outputDir, 'intro-websocket-settings.avif'), - format: 'avif', - kind: 'image', - stage: 'browser-final', - }), - ]) - await expect(access(path.join(outputDir, 'intro-chat-window.png'))).rejects.toThrow() - await expect(access(path.join(outputDir, 'intro-websocket-settings.png'))).rejects.toThrow() - await expect(access(path.join(outputDir, 'intro-chat-window.avif'))).resolves.toBeUndefined() - await expect(access(path.join(outputDir, 'intro-websocket-settings.avif'))).resolves.toBeUndefined() - }, 120000) - - it('rejects non-browser-final transformer outputs in the browser image pipeline', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - - await expect(captureBrowserRoots({ - imageTransformers: [async artifact => ({ - ...artifact, - stage: 'electron-raw', - })], - sceneAppRoot, - routePath: '/', - outputDir, - })).rejects.toThrow('must return image artifacts with stage "browser-final"') - }, 120000) - - it('rejects missing transformed output files in the browser image pipeline', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - - await expect(captureBrowserRoots({ - imageTransformers: [async artifact => ({ - ...artifact, - filePath: artifact.filePath.replace(/\.png$/, '.avif'), - format: 'avif', - })], - sceneAppRoot, - routePath: '/', - outputDir, - })).rejects.toThrow('must point to an existing file on disk') - }, 120000) - - it('rejects duplicate transformed output file paths', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - - await expect(captureBrowserRoots({ - imageTransformers: [async (artifact) => { - const duplicateFilePath = path.join(outputDir, 'duplicate.avif') - await writeFile(duplicateFilePath, 'duplicate output') - - return [ - { - ...artifact, - filePath: duplicateFilePath, - format: 'avif', - }, - { - ...artifact, - filePath: duplicateFilePath, - format: 'avif', - }, - ] - }], - sceneAppRoot, - routePath: '/', - outputDir, - })).rejects.toThrow('Artifact outputs') - }, 120000) - - it('rejects duplicate final artifact paths across roots', async () => { - const sceneAppRoot = await createViteSceneFixture() - const outputDir = path.join(sceneAppRoot, 'artifacts', 'final-test') - const sharedOutputPath = path.join(outputDir, 'shared.avif') - - await expect(captureBrowserRoots({ - imageTransformers: [async (artifact) => { - await writeFile(sharedOutputPath, `shared output for ${artifact.artifactName}\n`) - - return { - ...artifact, - filePath: sharedOutputPath, - format: 'avif', - } - }], - sceneAppRoot, - routePath: '/', - outputDir, - })).rejects.toThrow('both resolve to') - - await expect(access(path.join(outputDir, 'intro-chat-window.png'))).resolves.toBeUndefined() - }, 120000) -}) diff --git a/packages/vishot-runner-browser/src/runtime/selectors.ts b/packages/vishot-runner-browser/src/runtime/selectors.ts deleted file mode 100644 index cf2167045..000000000 --- a/packages/vishot-runner-browser/src/runtime/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function captureRootSelector(rootName: string): string { - return `[data-scenario-capture-root=${JSON.stringify(rootName)}]` -} diff --git a/packages/vishot-runner-browser/src/runtime/types.ts b/packages/vishot-runner-browser/src/runtime/types.ts deleted file mode 100644 index 943a61ef1..000000000 --- a/packages/vishot-runner-browser/src/runtime/types.ts +++ /dev/null @@ -1,35 +0,0 @@ -export interface CaptureBrowserCliArguments { - renderEntry: string - outputDir: string - rootNames: string[] -} - -export interface BrowserCaptureRequest { - sceneAppRoot?: string - baseUrl?: string - routePath: string - outputDir: string - settleMs?: number - rootNames?: string[] - imageTransformers?: ArtifactTransformer[] - viewport?: { - width: number - height: number - deviceScaleFactor?: number - } -} -export type VishotArtifactKind = 'image' -export type VishotArtifactStage = 'browser-final' | 'electron-raw' - -export interface VishotArtifact { - kind: VishotArtifactKind - stage: VishotArtifactStage - artifactName: string - filePath: string - format: string - metadata?: Record -} - -export type ArtifactTransformer = ( - artifact: VishotArtifact, -) => Promise diff --git a/packages/vishot-runner-browser/src/runtime/vite-server.ts b/packages/vishot-runner-browser/src/runtime/vite-server.ts deleted file mode 100644 index 09311d756..000000000 --- a/packages/vishot-runner-browser/src/runtime/vite-server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { AddressInfo } from 'node:net' - -import type { ViteDevServer } from 'vite' - -import { createServer } from 'vite' - -export interface ManagedViteServer { - baseUrl: string - close: () => Promise -} - -function resolveViteServerUrl(server: ViteDevServer, sceneAppRoot: string): string { - const url = server.resolvedUrls?.local?.[0] ?? server.resolvedUrls?.network?.[0] - - if (url) { - return url - } - - const address = server.httpServer?.address() - - if (!address || typeof address === 'string') { - throw new Error(`Unable to determine Vite dev server address for ${sceneAppRoot}`) - } - - const { address: host, port } = address as AddressInfo - - return `http://${host}:${port}` -} - -export async function startSceneViteServer(sceneAppRoot: string): Promise { - const server = await createServer({ - root: sceneAppRoot, - server: { - host: '127.0.0.1', - port: 41731, - strictPort: false, - // NOTICE: - // Disabling the file watcher prevents asynchronous file unlink/add events from firing. - // During test teardown, deleting temporary fixture roots triggers file watcher events after server shutdown. - // packages/vishot-runner-browser/src/runtime/vite-server.ts - // Can be safely removed if scene capture runs in a persistent environment without rapid cleanup. - watch: null, - }, - }) - - await server.listen() - - return { - baseUrl: resolveViteServerUrl(server, sceneAppRoot), - close: async () => { - await server.close() - }, - } -} diff --git a/packages/vishot-runner-browser/tsconfig.json b/packages/vishot-runner-browser/tsconfig.json deleted file mode 100644 index 97068867e..000000000 --- a/packages/vishot-runner-browser/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2023", - "module": "ESNext", - "moduleResolution": "Bundler", - "types": [ - "node" - ], - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts", - "vitest.config.ts" - ] -} diff --git a/packages/vishot-runner-electron/README.md b/packages/vishot-runner-electron/README.md deleted file mode 100644 index 89e766b88..000000000 --- a/packages/vishot-runner-electron/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# Vishot Runner - Electron - -Capture raw screenshots from the built `stage-tamagotchi` Electron app with TypeScript scenarios. - -## Purpose - -This package is the Electron capture runner. It provides: - -- a runtime surface in `src/index.ts` -- the `capture` CLI in `src/cli/capture.ts` -- the `defineScenario()` authoring helper for scenario modules -- a typed raw-artifact surface for screenshot outputs -- reusable helpers for the controls island, settings window, dialogs, drawers, and stage windows - -This package stops at raw business screenshots. It does not own the scenario modules themselves; those live in `@proj-airi/scenarios-stage-tamagotchi-electron`. - -## Workflow - -```bash -pnpm -F @proj-airi/stage-tamagotchi build -pnpm -F @proj-airi/vishot-runner-electron capture -- ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw -``` - -To emit AVIF files instead of PNG files: - -```bash -pnpm -F @proj-airi/vishot-runner-electron capture -- ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif -``` - -This writes the raw inputs consumed by the browser scene package: - -- `packages/scenarios-stage-tamagotchi-browser/artifacts/raw/00-stage-tamagotchi.png` -- `packages/scenarios-stage-tamagotchi-browser/artifacts/raw/01-controls-island-expanded.png` -- `packages/scenarios-stage-tamagotchi-browser/artifacts/raw/02-settings-window.png` -- `packages/scenarios-stage-tamagotchi-browser/artifacts/raw/03-websocket-settings.png` - -If you pass `--format avif`, the same capture names are emitted as `.avif` files instead. - -Then export the composed browser assets: - -```bash -pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture -``` - -In this environment, the raw capture command worked end-to-end after running outside the sandbox because `tsx` pipe creation was denied inside the sandbox (`listen EPERM` on the temporary `tsx` IPC pipe). - -## Electron Profile Note (Plugin Host Root) - -`vishot-runner-electron` launches the built Electron app entrypoint. In local development this can resolve `app.getPath('userData')` to the `Electron` profile directory, which means plugin discovery happens under: - -- `~/Library/Application Support/Electron/plugins/v1` - -This may differ from `dev:tamagotchi`, which often uses: - -- `~/Library/Application Support/@proj-airi/stage-tamagotchi/plugins/v1` - -If plugin-host devtools shows `Discovered 0`, `Plugin manifest not found`, or module registration errors during Vishot scenarios, mirror your plugin symlink into the Electron profile plugins directory: - -```bash -mkdir -p "$HOME/Library/Application Support/Electron/plugins/v1" -ln -sfn "/absolute/path/to/airi-plugin-game-chess/dist" "$HOME/Library/Application Support/Electron/plugins/v1/airi-plugin-game-chess" -``` - -## Additional Examples - -To verify the controls-island hearing button specifically: - -```bash -pnpm -F @proj-airi/stage-tamagotchi build -pnpm -F @proj-airi/vishot-runner-electron capture -- ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts --output-dir ./artifacts/hearing-demo -``` - -Expected file: - -- `packages/vishot-runner-electron/artifacts/hearing-demo/hearing-dialog.png` - -## Scenario Authoring - -```ts -import { defineScenario } from '@proj-airi/vishot-runner-electron' - -export default defineScenario({ - id: 'settings-connection', - async run({ controlsIsland, settingsWindow, stageWindows, capture }) { - const main = await stageWindows.waitFor('main') - await controlsIsland.expand(main.page) - const settings = await controlsIsland.openSettings(main.page) - const page = await settingsWindow.goToConnection(settings.page) - await capture('connection-settings', page) - }, -}) -``` - -For the controls-island hearing trigger, the runtime also provides: - -- `controlsIsland.openHearing(page)` - -## Dialog And Drawer Helpers - -For surfaces built with `DialogRoot` or `DrawerRoot`, the runtime now exposes: - -- `dialogs.dismiss(page)` -- `drawers.swipeDown(page)` -- `drawers.dismiss(page)` - -Example: - -```ts -import { defineScenario } from '@proj-airi/vishot-runner-electron' - -export default defineScenario({ - id: 'dismiss-helpers', - async run({ dialogs, drawers, stageWindows }) { - const main = await stageWindows.waitFor('main') - - await dialogs.dismiss(main.page) - await drawers.swipeDown(main.page) - await drawers.dismiss(main.page) - }, -}) -``` - -These are best-effort automation helpers. The current behavior is: - -- dialog dismiss: `Escape`, then overlay-corner click fallback -- drawer dismiss: swipe down, then `Escape`, then overlay-corner click fallback - -They are intended for scenarios where you already opened the dialog or drawer and need a reusable close step. - -## Settings Window Helpers - -The `settingsWindow` surface is navigation-only: - -- `settingsWindow.waitFor(timeout?)` -- `settingsWindow.goToConnection(page)` - -It does not open the settings window from the main window for you. The intended flow is: - -1. `stageWindows.waitFor('main')` -2. `controlsIsland.expand(main.page)` -3. `controlsIsland.openSettings(main.page)` -4. `settingsWindow.goToConnection(settings.page)` - -## Notes - -- Importing `@proj-airi/vishot-runner-electron` resolves to `src/index.ts` via the package export surface. -- The package is no longer a Playwright test suite package. -- Final composed exports live in `packages/scenarios-stage-tamagotchi-browser/artifacts/final`, not this package. -- Screenshot capture now returns typed `image` artifacts and can run transformer hooks before those raw files are handed to downstream consumers. -- The CLI supports `--format png|avif`; AVIF remains an opt-in post-processing step on top of the raw PNG screenshot primitive. diff --git a/packages/vishot-runner-electron/package.json b/packages/vishot-runner-electron/package.json deleted file mode 100644 index d5c77d0d0..000000000 --- a/packages/vishot-runner-electron/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@proj-airi/vishot-runner-electron", - "type": "module", - "version": "0.11.3", - "private": true, - "description": "Playwright-driven Electron capture runner for stage-tamagotchi", - "author": { - "name": "Moeru AI Project AIRI Team", - "email": "airi@moeru.ai", - "url": "https://github.com/moeru-ai" - }, - "license": "MIT", - "exports": "./src/index.ts", - "scripts": { - "capture": "tsx src/cli/capture.ts", - "typecheck": "tsc --noEmit" - }, - "devDependencies": { - "@moeru/std": "catalog:", - "@napi-rs/image": "catalog:", - "meow": "catalog:", - "playwright": "catalog:" - } -} diff --git a/packages/vishot-runner-electron/src/cli/capture.test.ts b/packages/vishot-runner-electron/src/cli/capture.test.ts deleted file mode 100644 index 683e51862..000000000 --- a/packages/vishot-runner-electron/src/cli/capture.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { parseCaptureCliArguments } from './capture' - -describe('parseCaptureCliArguments', () => { - it('accepts a scenario path with --output-dir', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'png', - }) - }) - - it('accepts the -o alias', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '-o', - './artifacts/manual-run', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'png', - }) - }) - - it('accepts --output-dir=value', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir=./artifacts/manual-run', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'png', - }) - }) - - it('accepts an optional --format flag', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'avif', - avif: { - maxWidth: 1920, - quality: 50, - speed: 6, - }, - }) - }) - - it('accepts optional AVIF tuning flags', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - '--avif-max-width', - '1200', - '--avif-quality', - '35', - '--avif-speed', - '4', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'avif', - avif: { - maxWidth: 1200, - quality: 35, - speed: 4, - }, - }) - }) - - it('applies default AVIF settings when format is avif', () => { - expect(parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - ])).toEqual({ - scenarioPath: 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - outputDir: './artifacts/manual-run', - format: 'avif', - avif: { - maxWidth: 1920, - quality: 50, - speed: 6, - }, - }) - }) - - it('rejects missing scenario path', () => { - expect(() => parseCaptureCliArguments([ - '--output-dir', - './artifacts/manual-run', - ])).toThrow('Usage: capture --output-dir ') - }) - - it('rejects missing output directory', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - ])).toThrow('Usage: capture --output-dir ') - }) - - it('rejects unsupported output formats', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'webp', - ])).toThrow('Unsupported capture format "webp". Expected "png" or "avif".') - }) - - it('rejects invalid AVIF quality values', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - '--avif-quality', - '200', - ])).toThrow('Unsupported AVIF quality "200". Expected an integer between 0 and 100.') - }) - - it('rejects invalid AVIF speed values', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - '--avif-speed', - '0', - ])).toThrow('Unsupported AVIF speed "0". Expected an integer between 1 and 10.') - }) - - it('rejects invalid AVIF max width values', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - '--output-dir', - './artifacts/manual-run', - '--format', - 'avif', - '--avif-max-width', - '0', - ])).toThrow('Unsupported AVIF max width "0". Expected an integer >= 1.') - }) - - it('rejects extra positional arguments', () => { - expect(() => parseCaptureCliArguments([ - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts', - 'packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-hearing-dialog.ts', - '--output-dir', - './artifacts/manual-run', - ])).toThrow('Usage: capture --output-dir ') - }) -}) diff --git a/packages/vishot-runner-electron/src/cli/capture.ts b/packages/vishot-runner-electron/src/cli/capture.ts deleted file mode 100644 index c77e11b0c..000000000 --- a/packages/vishot-runner-electron/src/cli/capture.ts +++ /dev/null @@ -1,244 +0,0 @@ -import type { ArtifactTransformer } from '../runtime/types' - -import path from 'node:path' -import process from 'node:process' - -import { mkdir, readFile, rm, writeFile } from 'node:fs/promises' -import { fileURLToPath } from 'node:url' - -import meow from 'meow' - -import { errorMessageFrom } from '@moeru/std' -import { Transformer } from '@napi-rs/image' -import { _electron as electron } from 'playwright' - -import { createScenarioContext } from '../runtime/context' -import { loadScenarioModule } from '../runtime/load-scenario' -import { resolveElectronAppInfo } from '../utils/app-path' - -type CaptureFormat = 'png' | 'avif' - -/** Max width for AVIF output — anything wider is downscaled proportionally. */ -const DEFAULT_AVIF_MAX_WIDTH = 1920 -/** AVIF quality (0-100, 100 = lossless). 50 is nearly indistinguishable for UI screenshots. */ -const DEFAULT_AVIF_QUALITY = 50 -/** rav1e speed preset (1 = slow/best, 10 = fast/worst). 6 balances size vs encode time. */ -const DEFAULT_AVIF_SPEED = 6 - -interface AvifCaptureOptions { - maxWidth: number - quality: number - speed: number -} - -interface CaptureCliArguments { - scenarioPath: string - outputDir: string - format: CaptureFormat - avif?: AvifCaptureOptions -} - -const captureHelpText = ` - Capture screenshots for a given scenario by running the Electron app and executing the scenario's steps. - - Usage - $ capture --output-dir - - Options - --output-dir, -o Directory to write PNG screenshots into - --format Output format: png or avif - --avif-max-width Max width for AVIF output (default: 1920) - --avif-quality AVIF quality between 0-100 (default: 50) - --avif-speed AVIF speed between 1-10 (default: 6) - - Examples - $ capture packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts --output-dir ./artifacts/manual-run - $ capture packages/scenarios-stage-tamagotchi-electron/src/scenarios/settings-connection.ts -o ./artifacts/manual-run -` - -const captureUsageMessage = 'Usage: capture --output-dir ' - -function normalizeCliArgv(argv: string[]): string[] { - return argv[0] === '--' ? argv.slice(1) : argv -} - -function isDirectExecution(): boolean { - if (!process.argv[1]) { - return false - } - - return path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) -} - -function parseCaptureFormat(format: string | undefined): CaptureFormat { - if (format === undefined || format.length === 0) { - return 'png' - } - - if (format === 'png' || format === 'avif') { - return format - } - - throw new Error(`Unsupported capture format "${format}". Expected "png" or "avif".`) -} - -function parsePositiveInteger(value: string, description: string): number { - if (!/^\d+$/.test(value)) { - throw new Error(`Unsupported ${description} "${value}". Expected a whole number.`) - } - - return Number(value) -} - -function parseAvifCaptureOptions(flags: { - avifMaxWidth?: string - avifQuality?: string - avifSpeed?: string -}): AvifCaptureOptions { - const maxWidth = flags.avifMaxWidth === undefined - ? DEFAULT_AVIF_MAX_WIDTH - : parsePositiveInteger(flags.avifMaxWidth, 'AVIF max width') - const quality = flags.avifQuality === undefined - ? DEFAULT_AVIF_QUALITY - : parsePositiveInteger(flags.avifQuality, 'AVIF quality') - const speed = flags.avifSpeed === undefined - ? DEFAULT_AVIF_SPEED - : parsePositiveInteger(flags.avifSpeed, 'AVIF speed') - - if (maxWidth < 1) { - throw new Error(`Unsupported AVIF max width "${maxWidth}". Expected an integer >= 1.`) - } - - if (quality < 0 || quality > 100) { - throw new Error(`Unsupported AVIF quality "${quality}". Expected an integer between 0 and 100.`) - } - - if (speed < 1 || speed > 10) { - throw new Error(`Unsupported AVIF speed "${speed}". Expected an integer between 1 and 10.`) - } - - return { - maxWidth, - quality, - speed, - } -} - -function createAvifTransformer(options: AvifCaptureOptions): ArtifactTransformer { - return async (artifact) => { - const derivedFilePath = artifact.filePath.replace(/\.png$/i, '.avif') - - const transformer = new Transformer(await readFile(artifact.filePath)) - const metadata = await transformer.metadata() - - // Downscale images wider than maxWidth, keeping aspect ratio. - if (metadata.width > options.maxWidth) { - const scale = options.maxWidth / metadata.width - transformer.resize(options.maxWidth, Math.round(metadata.height * scale)) - } - - const avifBuffer = await transformer.avif({ - quality: options.quality, - speed: options.speed, - }) - - await writeFile(derivedFilePath, avifBuffer) - await rm(artifact.filePath, { force: true }) - - return { - ...artifact, - filePath: derivedFilePath, - format: 'avif', - } - } -} - -export function parseCaptureCliArguments(argv: string[]): CaptureCliArguments { - const cli = meow(captureHelpText, { - argv: normalizeCliArgv(argv), - importMeta: import.meta, - flags: { - outputDir: { - shortFlag: 'o', - type: 'string', - }, - format: { - type: 'string', - }, - avifMaxWidth: { - type: 'string', - }, - avifQuality: { - type: 'string', - }, - avifSpeed: { - type: 'string', - }, - }, - }) - - if (cli.input.length !== 1 - || typeof cli.flags.outputDir !== 'string' - || cli.flags.outputDir.length === 0) { - throw new Error(captureUsageMessage) - } - - const format = parseCaptureFormat(cli.flags.format) - - return { - scenarioPath: cli.input[0], - outputDir: cli.flags.outputDir, - format, - avif: format === 'avif' - ? parseAvifCaptureOptions({ - avifMaxWidth: cli.flags.avifMaxWidth, - avifQuality: cli.flags.avifQuality, - avifSpeed: cli.flags.avifSpeed, - }) - : undefined, - } -} - -async function main(): Promise { - const { scenarioPath, outputDir, format, avif } = parseCaptureCliArguments(process.argv.slice(2)) - const resolvedOutputDir = path.resolve(process.cwd(), outputDir) - - await mkdir(resolvedOutputDir, { recursive: true }) - - const [appInfo, loadedScenario] = await Promise.all([ - resolveElectronAppInfo(), - loadScenarioModule(scenarioPath), - ]) - - const electronApp = await electron.launch({ - args: [appInfo.mainEntrypoint], - cwd: appInfo.repoRoot, - }) - - try { - const context = createScenarioContext( - electronApp, - resolvedOutputDir, - format === 'avif' - ? { - transformers: [createAvifTransformer(avif ?? { - maxWidth: DEFAULT_AVIF_MAX_WIDTH, - quality: DEFAULT_AVIF_QUALITY, - speed: DEFAULT_AVIF_SPEED, - })], - } - : undefined, - ) - await loadedScenario.scenario.run(context) - } - finally { - await electronApp.close() - } -} - -if (isDirectExecution()) { - void main().catch((error) => { - console.error(errorMessageFrom(error) ?? 'Unknown CLI error') - process.exitCode = 1 - }) -} diff --git a/packages/vishot-runner-electron/src/index.ts b/packages/vishot-runner-electron/src/index.ts deleted file mode 100644 index 4cbb7a6e4..000000000 --- a/packages/vishot-runner-electron/src/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { applyArtifactTransformers, createImageArtifact } from './runtime/artifacts' -export { capturePage } from './runtime/capture' -export { createScenarioContext } from './runtime/context' -export { defineScenario } from './runtime/define-scenario' -export { loadScenarioModule } from './runtime/load-scenario' -export type { LoadedScenarioModule } from './runtime/load-scenario' -export type { - ArtifactTransformer, - CaptureOptions, - ControlsIslandApi, - DialogsApi, - DrawersApi, - ElectronScenario, - ScenarioContext, - SettingsWindowApi, - StageWindowsApi, - VishotArtifact, - VishotArtifactKind, - VishotArtifactStage, -} from './runtime/types' -export { resolveElectronAppInfo } from './utils/app-path' -export type { ElectronAppInfo } from './utils/app-path' diff --git a/packages/vishot-runner-electron/src/runtime/artifacts.test.ts b/packages/vishot-runner-electron/src/runtime/artifacts.test.ts deleted file mode 100644 index 8a606e009..000000000 --- a/packages/vishot-runner-electron/src/runtime/artifacts.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -import { applyArtifactTransformers, createImageArtifact } from './artifacts' - -describe('createImageArtifact', () => { - it('marks Electron screenshot outputs as electron-raw image artifacts', () => { - expect(createImageArtifact({ - artifactName: 'settings-window', - filePath: '/tmp/settings-window.png', - stage: 'electron-raw', - })).toEqual({ - artifactName: 'settings-window', - filePath: '/tmp/settings-window.png', - format: 'png', - kind: 'image', - metadata: undefined, - stage: 'electron-raw', - }) - }) -}) - -describe('applyArtifactTransformers', () => { - it('returns the original artifact when no transformers are configured', async () => { - const artifact = createImageArtifact({ - artifactName: 'settings-window', - filePath: '/tmp/settings-window.png', - stage: 'electron-raw', - }) - - await expect(applyArtifactTransformers(artifact, [])).resolves.toEqual([artifact]) - }) - - it('passes transformed artifacts through in order', async () => { - const first = vi.fn(async artifact => ({ - ...artifact, - filePath: '/tmp/settings-window.avif', - format: 'avif', - })) - const second = vi.fn(async artifact => ({ - ...artifact, - metadata: { optimized: true }, - })) - - const artifact = createImageArtifact({ - artifactName: 'settings-window', - filePath: '/tmp/settings-window.png', - stage: 'electron-raw', - }) - - const result = await applyArtifactTransformers(artifact, [first, second]) - - expect(first).toHaveBeenCalledTimes(1) - expect(second).toHaveBeenCalledTimes(1) - expect(result).toEqual([ - expect.objectContaining({ - filePath: '/tmp/settings-window.avif', - format: 'avif', - metadata: { optimized: true }, - }), - ]) - }) -}) diff --git a/packages/vishot-runner-electron/src/runtime/artifacts.ts b/packages/vishot-runner-electron/src/runtime/artifacts.ts deleted file mode 100644 index ade096b2b..000000000 --- a/packages/vishot-runner-electron/src/runtime/artifacts.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { ArtifactTransformer, VishotArtifact, VishotArtifactStage } from './types' - -export function createImageArtifact(options: { - artifactName: string - filePath: string - stage: VishotArtifactStage - metadata?: Record -}): VishotArtifact { - return { - artifactName: options.artifactName, - filePath: options.filePath, - format: 'png', - kind: 'image', - metadata: options.metadata, - stage: options.stage, - } -} - -export async function applyArtifactTransformers( - artifact: VishotArtifact, - transformers: ArtifactTransformer[] | undefined, -): Promise { - let currentArtifacts: VishotArtifact[] = [artifact] - - for (const transformer of transformers ?? []) { - const nextArtifacts: VishotArtifact[] = [] - - for (const currentArtifact of currentArtifacts) { - const transformed = await transformer(currentArtifact) - nextArtifacts.push(...(Array.isArray(transformed) ? transformed : [transformed])) - } - - currentArtifacts = nextArtifacts - } - - return currentArtifacts -} diff --git a/packages/vishot-runner-electron/src/runtime/capture.ts b/packages/vishot-runner-electron/src/runtime/capture.ts deleted file mode 100644 index 945543725..000000000 --- a/packages/vishot-runner-electron/src/runtime/capture.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Page } from 'playwright' - -import type { CaptureOptions, VishotArtifact } from './types' - -import path from 'node:path' - -import { mkdir } from 'node:fs/promises' - -import { applyArtifactTransformers, createImageArtifact } from './artifacts' - -const nonFilenameCharactersPattern = /[^a-z0-9-_]+/g -const edgeDashPattern = /^-+|-+$/g - -function sanitizeCaptureName(name: string): string { - const sanitized = name - .trim() - .toLowerCase() - .replace(nonFilenameCharactersPattern, '-') - .replace(edgeDashPattern, '') - - return sanitized.length > 0 ? sanitized : 'capture' -} - -export async function capturePage( - outputDir: string, - name: string, - page: Page, - options?: CaptureOptions, -): Promise { - const filePath = path.resolve(outputDir, `${sanitizeCaptureName(name)}.png`) - - await mkdir(outputDir, { recursive: true }) - await page.screenshot({ - animations: 'disabled', - fullPage: options?.fullPage ?? false, - path: filePath, - }) - - return applyArtifactTransformers( - createImageArtifact({ - artifactName: name, - filePath, - stage: 'electron-raw', - }), - options?.transformers, - ) -} diff --git a/packages/vishot-runner-electron/src/runtime/context.test.ts b/packages/vishot-runner-electron/src/runtime/context.test.ts deleted file mode 100644 index dd51af08d..000000000 --- a/packages/vishot-runner-electron/src/runtime/context.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' - -import { createScenarioContext } from './context' - -vi.mock('./capture', () => { - return { - capturePage: vi.fn(async (_outputDir, name, _page, options) => [{ name, options }]), - } -}) - -describe('createScenarioContext', () => { - it('merges default transformers with per-capture transformers', async () => { - const { capturePage } = await import('./capture') - const defaultTransformer = vi.fn(async artifact => artifact) - const perCaptureTransformer = vi.fn(async artifact => artifact) - const electronApp = {} as never - const page = {} as never - const context = createScenarioContext(electronApp, '/tmp/output', { - transformers: [defaultTransformer], - }) - - await context.capture('settings-window', page, { - fullPage: true, - transformers: [perCaptureTransformer], - }) - - expect(capturePage).toHaveBeenCalledWith( - '/tmp/output', - 'settings-window', - page, - expect.objectContaining({ - fullPage: true, - transformers: [defaultTransformer, perCaptureTransformer], - }), - ) - }) -}) diff --git a/packages/vishot-runner-electron/src/runtime/context.ts b/packages/vishot-runner-electron/src/runtime/context.ts deleted file mode 100644 index 7709c5031..000000000 --- a/packages/vishot-runner-electron/src/runtime/context.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { ElectronApplication, Page } from 'playwright' - -import type { CaptureOptions, ScenarioContext } from './types' - -import { dismissDialog, dismissDrawer, swipeDownDrawer } from '../utils/overlays' -import { expandControlsIsland, openChatFromControlsIsland, openHearingFromControlsIsland, openSettingsFromControlsIsland, waitForControlsIslandReady } from '../utils/selectors' -import { goToSettingsConnectionPage, goToSettingsRoute } from '../utils/settings' -import { waitForStageWindow } from '../utils/windows' -import { capturePage } from './capture' - -function mergeCaptureOptions(defaultOptions?: CaptureOptions, options?: CaptureOptions): CaptureOptions | undefined { - const transformers = [ - ...(defaultOptions?.transformers ?? []), - ...(options?.transformers ?? []), - ] - - if (!defaultOptions && !options) { - return undefined - } - - return { - fullPage: options?.fullPage ?? defaultOptions?.fullPage, - transformers: transformers.length > 0 ? transformers : undefined, - } -} - -export function createScenarioContext( - electronApp: ElectronApplication, - outputDir: string, - defaultCaptureOptions?: CaptureOptions, -): ScenarioContext { - return { - electronApp, - outputDir, - capture(name: string, page: Page, options?: CaptureOptions) { - return capturePage(outputDir, name, page, mergeCaptureOptions(defaultCaptureOptions, options)) - }, - stageWindows: { - waitFor(name, timeout) { - return waitForStageWindow(electronApp, name, timeout) - }, - }, - controlsIsland: { - waitForReady(page) { - return waitForControlsIslandReady(page) - }, - async expand(page) { - await expandControlsIsland(page) - }, - async openSettings(page) { - await openSettingsFromControlsIsland(page) - return waitForStageWindow(electronApp, 'settings') - }, - async openChat(page) { - await openChatFromControlsIsland(page) - return waitForStageWindow(electronApp, 'chat') - }, - openHearing(page) { - return openHearingFromControlsIsland(page) - }, - }, - settingsWindow: { - waitFor(timeout) { - return waitForStageWindow(electronApp, 'settings', timeout) - }, - goToConnection(page) { - return goToSettingsConnectionPage(page) - }, - goToRoute(page, routePath) { - return goToSettingsRoute(page, routePath) - }, - }, - dialogs: { - dismiss(page) { - return dismissDialog(page) - }, - }, - drawers: { - swipeDown(page) { - return swipeDownDrawer(page) - }, - dismiss(page) { - return dismissDrawer(page) - }, - }, - } -} diff --git a/packages/vishot-runner-electron/src/runtime/define-scenario.ts b/packages/vishot-runner-electron/src/runtime/define-scenario.ts deleted file mode 100644 index b9cd05dc2..000000000 --- a/packages/vishot-runner-electron/src/runtime/define-scenario.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { ElectronScenario } from './types' - -export function defineScenario(scenario: ElectronScenario): ElectronScenario { - return scenario -} diff --git a/packages/vishot-runner-electron/src/runtime/load-scenario.ts b/packages/vishot-runner-electron/src/runtime/load-scenario.ts deleted file mode 100644 index 9d6a9518b..000000000 --- a/packages/vishot-runner-electron/src/runtime/load-scenario.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { ElectronScenario } from './types' - -import process from 'node:process' - -import { access } from 'node:fs/promises' -import { resolve } from 'node:path' -import { pathToFileURL } from 'node:url' - -export interface LoadedScenarioModule { - modulePath: string - scenario: ElectronScenario -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - return error.message - } - - return String(error) -} - -function isElectronScenario(value: unknown): value is ElectronScenario { - if (typeof value !== 'object' || value === null) { - return false - } - - const scenario = value as Partial - return typeof scenario.id === 'string' && typeof scenario.run === 'function' -} - -export async function loadScenarioModule(scenarioPath: string): Promise { - const modulePath = resolve(process.cwd(), scenarioPath) - - await access(modulePath).catch(() => { - throw new Error(`Scenario module not found at ${modulePath}`) - }) - - let moduleNamespace: { default?: unknown } - - try { - moduleNamespace = await import(pathToFileURL(modulePath).href) - } - catch (error) { - throw new Error(`Failed to load scenario module at ${modulePath}: ${getErrorMessage(error)}`) - } - - if (!isElectronScenario(moduleNamespace.default)) { - const exportedKeys = Object.keys(moduleNamespace).join(', ') || '(none)' - throw new Error( - `Scenario module at ${modulePath} must export a default scenario object with id and run(ctx). Exported keys: ${exportedKeys}`, - ) - } - - return { - modulePath, - scenario: moduleNamespace.default, - } -} diff --git a/packages/vishot-runner-electron/src/runtime/types.ts b/packages/vishot-runner-electron/src/runtime/types.ts deleted file mode 100644 index cd330d14a..000000000 --- a/packages/vishot-runner-electron/src/runtime/types.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { ElectronApplication, Page } from 'playwright' - -import type { StageWindowName, StageWindowSnapshot } from '../utils/windows' - -export type VishotArtifactKind = 'image' -export type VishotArtifactStage = 'browser-final' | 'electron-raw' - -export interface VishotArtifact { - kind: VishotArtifactKind - stage: VishotArtifactStage - artifactName: string - filePath: string - format: string - metadata?: Record -} - -export type ArtifactTransformer = ( - artifact: VishotArtifact, -) => Promise - -export interface CaptureOptions { - fullPage?: boolean - transformers?: ArtifactTransformer[] -} - -export interface StageWindowsApi { - waitFor: (name: StageWindowName, timeout?: number) => Promise -} - -export interface ControlsIslandApi { - waitForReady: (page: Page) => Promise - expand: (page: Page) => Promise - openSettings: (page: Page) => Promise - openChat: (page: Page) => Promise - openHearing: (page: Page) => Promise -} - -export interface SettingsWindowApi { - waitFor: (timeout?: number) => Promise - goToConnection: (page: Page) => Promise - goToRoute: (page: Page, routePath: string) => Promise -} - -export interface DialogsApi { - dismiss: (page: Page) => Promise -} - -export interface DrawersApi { - swipeDown: (page: Page) => Promise - dismiss: (page: Page) => Promise -} - -export interface ScenarioContext { - electronApp: ElectronApplication - outputDir: string - capture: (name: string, page: Page, options?: CaptureOptions) => Promise - stageWindows: StageWindowsApi - controlsIsland: ControlsIslandApi - settingsWindow: SettingsWindowApi - dialogs: DialogsApi - drawers: DrawersApi -} - -export interface ElectronScenario { - id: string - run: (context: ScenarioContext) => Promise -} diff --git a/packages/vishot-runner-electron/src/utils/app-path.ts b/packages/vishot-runner-electron/src/utils/app-path.ts deleted file mode 100644 index c5308ff19..000000000 --- a/packages/vishot-runner-electron/src/utils/app-path.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { access } from 'node:fs/promises' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' - -export interface ElectronAppInfo { - repoRoot: string - mainEntrypoint: string -} - -export async function resolveElectronAppInfo(): Promise { - const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..') - const repoRoot = resolve(packageRoot, '..', '..') - const stageTamagotchiRoot = resolve(repoRoot, 'apps', 'stage-tamagotchi') - const mainEntrypoint = resolve(stageTamagotchiRoot, 'out', 'main', 'index.js') - - await access(mainEntrypoint).catch(() => { - throw new Error(`Built Electron entrypoint not found at ${mainEntrypoint}. Run "pnpm -F @proj-airi/stage-tamagotchi build" first.`) - }) - - return { - repoRoot, - mainEntrypoint, - } -} diff --git a/packages/vishot-runner-electron/tsconfig.json b/packages/vishot-runner-electron/tsconfig.json deleted file mode 100644 index 9a907f02b..000000000 --- a/packages/vishot-runner-electron/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2023", - "module": "ESNext", - "moduleResolution": "Bundler", - "types": [ - "node", - "playwright" - ], - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/packages/vishot-runner-electron/vitest.config.ts b/packages/vishot-runner-electron/vitest.config.ts deleted file mode 100644 index 647f39364..000000000 --- a/packages/vishot-runner-electron/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - include: ['src/**/*.test.ts'], - }, -}) diff --git a/packages/vishot-runtime/README.md b/packages/vishot-runtime/README.md deleted file mode 100644 index ff7d34f0c..000000000 --- a/packages/vishot-runtime/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Vishot Runtime - -Shared runtime contracts and Vue bindings for Vishot scene capture. - -## Purpose - -This package owns the browser-side runtime surface used by scene packages. It provides: - -- readiness helpers via `markScenarioReady()` and `resetScenarioReady()` -- the shared `ScenarioCaptureRootProps` type -- framework-specific bindings through subpath exports such as `@proj-airi/vishot-runtime/vue` - - Vue bindings include `ScenarioCanvas`, `ScenarioCaptureRoot`, `useSceneReady()` - - Vue bindings also export screen-specific components/composables (router, navigator, markup, and navigation helpers) - -## Usage - -```ts -import { markScenarioReady, resetScenarioReady } from '@proj-airi/vishot-runtime' -import { - ScenarioCanvas, - ScenarioCaptureRoot, - ScreenNavigator, - ScreenRouterProvider, - useSceneNavigation, -} from '@proj-airi/vishot-runtime/vue' -``` - -Keep framework-agnostic contracts at the package root and import Vue SFC bindings from `./vue`. - -```ts -import { - markScenarioReady, - resetScenarioReady, -} from '@proj-airi/vishot-runtime' -``` diff --git a/packages/vishot-runtime/package.json b/packages/vishot-runtime/package.json deleted file mode 100644 index b29f3ce7b..000000000 --- a/packages/vishot-runtime/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@proj-airi/vishot-runtime", - "type": "module", - "version": "0.11.3", - "private": true, - "description": "Runtime capture contracts and Vue bindings for Vishot", - "author": { - "name": "Moeru AI Project AIRI Team", - "email": "airi@moeru.ai", - "url": "https://github.com/moeru-ai" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/moeru-ai/airi.git", - "directory": "packages/vishot-runtime" - }, - "exports": { - ".": "./src/index.ts", - "./vue": "./src/vue/index.ts", - "./vue/components/*": "./src/vue/components/*.vue", - "./runtime/*": "./src/runtime/*.ts", - "./*": "./src/*.ts" - }, - "scripts": { - "build": "echo \"No build step required\"", - "test:run": "vitest run", - "typecheck": "vue-tsc --noEmit" - }, - "dependencies": { - "@vueuse/core": "catalog:", - "reka-ui": "catalog:", - "vue": "catalog:", - "vue-router": "catalog:" - }, - "devDependencies": { - "@vitejs/plugin-vue": "catalog:", - "vue-tsc": "catalog:" - } -} diff --git a/packages/vishot-runtime/src/env.d.ts b/packages/vishot-runtime/src/env.d.ts deleted file mode 100644 index ab333dd6f..000000000 --- a/packages/vishot-runtime/src/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -declare module '*.vue' { - import type { DefineComponent } from 'vue' - - const component: DefineComponent, Record, unknown> - export default component -} diff --git a/packages/vishot-runtime/src/index.ts b/packages/vishot-runtime/src/index.ts deleted file mode 100644 index 815c7f15f..000000000 --- a/packages/vishot-runtime/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { markScenarioReady, resetScenarioReady } from './runtime/ready' -export type { ScenarioCaptureRootProps } from './runtime/types' diff --git a/packages/vishot-runtime/src/runtime/ready.test.ts b/packages/vishot-runtime/src/runtime/ready.test.ts deleted file mode 100644 index 897326933..000000000 --- a/packages/vishot-runtime/src/runtime/ready.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' - -import { markScenarioReady, resetScenarioReady } from './ready' - -describe('scenario ready helpers', () => { - afterEach(() => { - vi.unstubAllGlobals() - }) - - it('marks the browser runtime as ready', () => { - const dispatchEvent = vi.fn() - - vi.stubGlobal('window', { - __SCENARIO_CAPTURE_READY__: undefined, - dispatchEvent, - } as unknown as Window) - - resetScenarioReady() - markScenarioReady() - - expect(window.__SCENARIO_CAPTURE_READY__).toBe(true) - expect(dispatchEvent).toHaveBeenCalledTimes(1) - expect(dispatchEvent.mock.calls[0]?.[0]).toMatchObject({ - type: 'scenario-capture:ready', - }) - }) -}) diff --git a/packages/vishot-runtime/src/runtime/ready.ts b/packages/vishot-runtime/src/runtime/ready.ts deleted file mode 100644 index 4cc81342a..000000000 --- a/packages/vishot-runtime/src/runtime/ready.ts +++ /dev/null @@ -1,20 +0,0 @@ -const scenarioCaptureReadyEventType = 'scenario-capture:ready' - -type ScenarioReadyWindow = Window & { - __SCENARIO_CAPTURE_READY__?: boolean -} - -export function resetScenarioReady(): void { - window.__SCENARIO_CAPTURE_READY__ = false -} - -export function markScenarioReady(): void { - const scenarioReadyWindow = window as ScenarioReadyWindow - - scenarioReadyWindow.__SCENARIO_CAPTURE_READY__ = true - scenarioReadyWindow.dispatchEvent( - typeof Event === 'function' - ? new Event(scenarioCaptureReadyEventType) - : ({ type: scenarioCaptureReadyEventType } as Event), - ) -} diff --git a/packages/vishot-runtime/src/runtime/scene-canvas.test.ts b/packages/vishot-runtime/src/runtime/scene-canvas.test.ts deleted file mode 100644 index ecd3acc67..000000000 --- a/packages/vishot-runtime/src/runtime/scene-canvas.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -// @vitest-environment jsdom -import { describe, expect, it } from 'vitest' -import { createApp, h, nextTick } from 'vue' - -import ScenarioCanvas from '../vue/components/scenario-canvas.vue' - -import { computeScenarioCanvasScale } from './scene-canvas' - -describe('computeScenarioCanvasScale', () => { - it('fits the canvas to the smallest viewport ratio', () => { - expect(computeScenarioCanvasScale({ - viewportWidth: 1440, - viewportHeight: 900, - canvasWidth: 1920, - canvasHeight: 1080, - })).toBe(0.75) - }) - - it('falls back to 1 when dimensions are missing', () => { - expect(computeScenarioCanvasScale({ - viewportWidth: 0, - viewportHeight: 900, - canvasWidth: 1920, - canvasHeight: 1080, - })).toBe(1) - }) - - it('applies an additional multiplier to the fitted scale', () => { - expect(computeScenarioCanvasScale({ - viewportWidth: 1440, - viewportHeight: 900, - canvasWidth: 1920, - canvasHeight: 1080, - scaleMultiplier: 0.75, - })).toBe(0.5625) - }) - - it('renders a fixed logical surface for absolute-positioned scene content', async () => { - const host = document.createElement('div') - document.body.appendChild(host) - - createApp({ - render: () => h( - ScenarioCanvas, - { - width: 1920, - height: 1080, - }, - { - default: () => h('div', { id: 'scene-content' }), - }, - ), - }).mount(host) - - await nextTick() - - const surface = host.querySelector('[data-scenario-canvas-surface]') - - expect(surface).not.toBeNull() - expect(surface?.style.width).toBe('1920px') - expect(surface?.style.height).toBe('1080px') - }) - - it('applies scale multiplier to the rendered surface transform', async () => { - const originalInnerWidth = window.innerWidth - const originalInnerHeight = window.innerHeight - Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1440 }) - Object.defineProperty(window, 'innerHeight', { configurable: true, value: 900 }) - - const host = document.createElement('div') - document.body.appendChild(host) - - createApp({ - render: () => h( - ScenarioCanvas, - { - width: 1920, - height: 1080, - scaleMultiplier: 0.75, - }, - { - default: () => h('div', { id: 'scene-content' }), - }, - ), - }).mount(host) - - await nextTick() - - const surface = host.querySelector('[data-scenario-canvas-surface]') - - expect(surface).not.toBeNull() - expect(surface?.style.transform).toBe('scale(0.75)') - - Object.defineProperty(window, 'innerWidth', { configurable: true, value: originalInnerWidth }) - Object.defineProperty(window, 'innerHeight', { configurable: true, value: originalInnerHeight }) - }) -}) diff --git a/packages/vishot-runtime/src/runtime/scene-canvas.ts b/packages/vishot-runtime/src/runtime/scene-canvas.ts deleted file mode 100644 index c7ca208eb..000000000 --- a/packages/vishot-runtime/src/runtime/scene-canvas.ts +++ /dev/null @@ -1,37 +0,0 @@ -export interface ScenarioCanvasScaleOptions { - viewportWidth: number - viewportHeight: number - canvasWidth: number - canvasHeight: number - scaleMultiplier?: number -} - -/** - * Computes the single scale factor used to fit a fixed logical scene surface into - * the current viewport. - * - * This mirrors the core idea used by Slidev: keep scene coordinates stable by - * defining a fixed inner canvas size, then scale that whole surface as one unit - * instead of positioning content directly in a responsive box. - * - * Slidev references: - * - `slidev/packages/client/internals/SlideContainer.vue` - * - `slidev/packages/client/internals/SlideWrapper.vue` - * - `slidev/packages/client/env.ts` - */ -export function computeScenarioCanvasScale(options: ScenarioCanvasScaleOptions): number { - const { viewportWidth, viewportHeight, canvasWidth, canvasHeight } = options - const scaleMultiplier = options.scaleMultiplier && options.scaleMultiplier > 0 - ? options.scaleMultiplier - : 1 - - if (viewportWidth <= 0 || viewportHeight <= 0 || canvasWidth <= 0 || canvasHeight <= 0) - return scaleMultiplier - - const fittedScale = Math.min( - viewportWidth / canvasWidth, - viewportHeight / canvasHeight, - ) - - return fittedScale * scaleMultiplier -} diff --git a/packages/vishot-runtime/src/runtime/types.ts b/packages/vishot-runtime/src/runtime/types.ts deleted file mode 100644 index 7e9c28fd6..000000000 --- a/packages/vishot-runtime/src/runtime/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare global { - interface Window { - __SCENARIO_CAPTURE_READY__?: boolean - } -} - -export interface ScenarioCaptureRootProps { - name: string - padding?: string -} diff --git a/packages/vishot-runtime/src/vue/components/scenario-canvas.vue b/packages/vishot-runtime/src/vue/components/scenario-canvas.vue deleted file mode 100644 index 6ebe8ae02..000000000 --- a/packages/vishot-runtime/src/vue/components/scenario-canvas.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/scenario-capture-root.vue b/packages/vishot-runtime/src/vue/components/scenario-capture-root.vue deleted file mode 100644 index ed1ec1824..000000000 --- a/packages/vishot-runtime/src/vue/components/scenario-capture-root.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-marups/index.ts b/packages/vishot-runtime/src/vue/components/screen-marups/index.ts deleted file mode 100644 index 9a55e669f..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-marups/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as ScreenFocusMask } from './screen-focus-mask.vue' -export { default as ScreenHighlightRegion } from './screen-highlight-region.vue' -export { default as ScreenMarupsProvider } from './screen-marups-provider.vue' -export { default as ScreenOutlineRegion } from './screen-outline-region.vue' diff --git a/packages/vishot-runtime/src/vue/components/screen-marups/screen-focus-mask.vue b/packages/vishot-runtime/src/vue/components/screen-marups/screen-focus-mask.vue deleted file mode 100644 index b79642f0a..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-marups/screen-focus-mask.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-marups/screen-highlight-region.vue b/packages/vishot-runtime/src/vue/components/screen-marups/screen-highlight-region.vue deleted file mode 100644 index e40787bfb..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-marups/screen-highlight-region.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-marups/screen-marups-provider.vue b/packages/vishot-runtime/src/vue/components/screen-marups/screen-marups-provider.vue deleted file mode 100644 index 203e5fc80..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-marups/screen-marups-provider.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-marups/screen-outline-region.vue b/packages/vishot-runtime/src/vue/components/screen-marups/screen-outline-region.vue deleted file mode 100644 index 72432b727..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-marups/screen-outline-region.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-navigator/index.ts b/packages/vishot-runtime/src/vue/components/screen-navigator/index.ts deleted file mode 100644 index f63014b05..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-navigator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as ScreenNavigator } from './screen-navigator.vue' diff --git a/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.browser.test.ts b/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.browser.test.ts deleted file mode 100644 index f6c5de05e..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.browser.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { userEvent } from 'vitest/browser' -import { createApp, h, nextTick, ref } from 'vue' -import { createMemoryHistory, createRouter } from 'vue-router' - -import ScreenNavigator from './screen-navigator.vue' - -const scenes = [ - { id: 'intro-chat', title: 'Intro Chat' }, - { id: 'intro-websocket', title: 'Intro WebSocket' }, -] - -async function mountSceneNavigator(onNavigate = vi.fn()) { - const host = document.createElement('div') - document.body.appendChild(host) - const router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/intro-chat', component: { template: '
' } }, - { path: '/intro-websocket', component: { template: '
' } }, - ], - }) - - await router.push('/intro-chat') - await router.isReady() - - const app = createApp({ - render: () => h(ScreenNavigator, { - currentSceneId: ref('intro-chat'), - onNavigate, - scenes, - }), - }) - app.use(router) - - app.mount(host) - - return { - app, - host, - } -} - -describe('scene-navigator', () => { - it('renders only compact prev/jump/next controls by default', async () => { - const { app, host } = await mountSceneNavigator() - - expect(host.querySelector('[data-scene-nav-prev]')).not.toBeNull() - expect(host.querySelector('[data-scene-nav-jump]')).not.toBeNull() - expect(host.querySelector('[data-scene-nav-next]')).not.toBeNull() - expect(host.querySelector('[data-scene-nav-panel]')).toBeNull() - expect(host.textContent).not.toContain('Current scene') - - app.unmount() - host.remove() - }) - - it('opens palette from jump button and selects a scene', async () => { - const onNavigate = vi.fn() - const { app, host } = await mountSceneNavigator(onNavigate) - - host.querySelector('[data-scene-nav-jump]')?.click() - await nextTick() - - expect(document.querySelector('[data-scene-nav-palette][data-state="open"]')).not.toBeNull() - - document.querySelector('[data-scene-nav-item="intro-websocket"]')?.click() - await nextTick() - - expect(onNavigate).toHaveBeenCalledWith('intro-websocket') - expect(document.querySelector('[data-scene-nav-palette][data-state="open"]')).toBeNull() - - app.unmount() - host.remove() - }) - - it('opens palette on Ctrl+K and closes with Escape', async () => { - const { app, host } = await mountSceneNavigator() - - await userEvent.keyboard('{Control>}k{/Control}') - await nextTick() - await vi.waitFor(() => { - expect(document.querySelector('[data-scene-nav-palette][data-state="open"]')).not.toBeNull() - }) - - await userEvent.keyboard('{Escape}') - await nextTick() - await vi.waitFor(() => { - expect(document.querySelector('[data-scene-nav-palette][data-state="open"]')).toBeNull() - }) - - app.unmount() - host.remove() - }) - - it('navigates with prev/next buttons', async () => { - const onNavigate = vi.fn() - const host = document.createElement('div') - document.body.appendChild(host) - const router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/intro-chat', component: { template: '
' } }, - { path: '/intro-websocket', component: { template: '
' } }, - ], - }) - await router.push('/intro-chat') - await router.isReady() - - const currentSceneId = ref('intro-websocket') - const app = createApp({ - render: () => h(ScreenNavigator, { - currentSceneId, - onNavigate, - scenes, - }), - }) - app.use(router) - - app.mount(host) - await nextTick() - - host.querySelector('[data-scene-nav-prev]')?.click() - expect(onNavigate).toHaveBeenCalledWith('intro-chat') - - onNavigate.mockClear() - currentSceneId.value = 'intro-chat' - await nextTick() - host.querySelector('[data-scene-nav-next]')?.click() - expect(onNavigate).toHaveBeenCalledWith('intro-websocket') - - app.unmount() - host.remove() - }) - - it('supports ArrowLeft, ArrowRight, and Space shortcuts for scene navigation', async () => { - const onNavigate = vi.fn() - const host = document.createElement('div') - document.body.appendChild(host) - const router = createRouter({ - history: createMemoryHistory(), - routes: [ - { path: '/intro-chat', component: { template: '
' } }, - { path: '/intro-websocket', component: { template: '
' } }, - ], - }) - await router.push('/intro-chat') - await router.isReady() - - const currentSceneId = ref('intro-websocket') - const app = createApp({ - render: () => h(ScreenNavigator, { - currentSceneId, - onNavigate, - scenes, - }), - }) - app.use(router) - - app.mount(host) - await nextTick() - - await userEvent.keyboard('{ArrowLeft}') - await nextTick() - expect(onNavigate).toHaveBeenCalledWith('intro-chat') - - onNavigate.mockClear() - currentSceneId.value = 'intro-chat' - await nextTick() - await userEvent.keyboard('{ArrowRight}') - await nextTick() - expect(onNavigate).toHaveBeenCalledWith('intro-websocket') - - onNavigate.mockClear() - currentSceneId.value = 'intro-chat' - await nextTick() - await userEvent.keyboard('{Space}') - await nextTick() - expect(onNavigate).toHaveBeenCalledWith('intro-websocket') - - app.unmount() - host.remove() - }) - - it('highlights the current scene in the jump dialog', async () => { - const { app, host } = await mountSceneNavigator() - - host.querySelector('[data-scene-nav-jump]')?.click() - await nextTick() - - await vi.waitFor(() => { - expect(document.querySelector('[data-scene-nav-item="intro-chat"][data-highlighted]')).not.toBeNull() - }) - - app.unmount() - host.remove() - }) -}) diff --git a/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.vue b/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.vue deleted file mode 100644 index 058592e6e..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-navigator/screen-navigator.vue +++ /dev/null @@ -1,378 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-router/context.ts b/packages/vishot-runtime/src/vue/components/screen-router/context.ts deleted file mode 100644 index b6fa6c602..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-router/context.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ComputedRef, InjectionKey } from 'vue' - -export interface SceneRouterCaptureRoot { - id: string - routePath: string - title: string -} - -export interface SceneRouterStore { - activeCaptureRootId: ComputedRef - currentRouteCaptureRoots: ComputedRef - navigateToCaptureRoot: (id: string) => Promise - registerCaptureRoot: (root: SceneRouterCaptureRoot) => void - unregisterCaptureRoot: (routePath: string, id: string) => void -} - -export const injectSceneRouterStore: InjectionKey = Symbol('vishot:scene-router-store') diff --git a/packages/vishot-runtime/src/vue/components/screen-router/index.ts b/packages/vishot-runtime/src/vue/components/screen-router/index.ts deleted file mode 100644 index a69876a69..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-router/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as ScreenRouterCaptureRoot } from './screen-router-capture-root.vue' -export { default as ScreenRouterProvider } from './screen-router-provider.vue' diff --git a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-capture-root.vue b/packages/vishot-runtime/src/vue/components/screen-router/screen-router-capture-root.vue deleted file mode 100644 index 0ab60dfeb..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-capture-root.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.test.ts b/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.test.ts deleted file mode 100644 index 004580a8b..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -// @vitest-environment jsdom -import { describe, expect, it } from 'vitest' -import { createApp, h, inject, nextTick, onMounted, ref } from 'vue' -import { createMemoryHistory, createRouter } from 'vue-router' - -import ScreenRouterProvider from './screen-router-provider.vue' - -import { injectSceneRouterStore } from './context' - -function mountProviderWithQuery() { - const host = document.createElement('div') - document.body.appendChild(host) - - const activeCaptureRootId = ref(null) - - const Probe = { - setup() { - const sceneRouterStore = inject(injectSceneRouterStore, null) - - onMounted(() => { - if (!sceneRouterStore) { - return - } - - const routePath = '/docs/setup-and-use' - sceneRouterStore.registerCaptureRoot({ - id: 'manual-chat-window', - routePath, - title: 'Manual Chat Window', - }) - sceneRouterStore.registerCaptureRoot({ - id: 'intro-chat-window', - routePath, - title: 'Intro Chat Window', - }) - - activeCaptureRootId.value = sceneRouterStore.activeCaptureRootId.value - }) - - return () => h('div') - }, - } - - const router = createRouter({ - history: createMemoryHistory(), - routes: [{ path: '/docs/setup-and-use', component: Probe }], - }) - - const app = createApp({ - render: () => h(ScreenRouterProvider, null, { default: () => h(Probe) }), - }) - app.use(router) - - return { app, host, router, activeCaptureRootId } -} - -describe('scene-router-provider', () => { - it('ignores stale capture query ids that are not registered on the current route', async () => { - const { app, host, router, activeCaptureRootId } = mountProviderWithQuery() - - await router.push({ path: '/docs/setup-and-use', query: { capture: 'manual-settings-window' } }) - await router.isReady() - app.mount(host) - await nextTick() - await nextTick() - - expect(activeCaptureRootId.value).toBeNull() - - app.unmount() - host.remove() - }) - - it('keeps capture query ids that are registered on the current route', async () => { - const { app, host, router, activeCaptureRootId } = mountProviderWithQuery() - - await router.push({ path: '/docs/setup-and-use', query: { capture: 'manual-chat-window' } }) - await router.isReady() - app.mount(host) - await nextTick() - await nextTick() - - expect(activeCaptureRootId.value).toBe('manual-chat-window') - - app.unmount() - host.remove() - }) -}) diff --git a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.vue b/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.vue deleted file mode 100644 index eb7faad6b..000000000 --- a/packages/vishot-runtime/src/vue/components/screen-router/screen-router-provider.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - diff --git a/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.test.ts b/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.test.ts deleted file mode 100644 index 700d7cbba..000000000 --- a/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { ref } from 'vue' - -import { useSceneNavigation } from './use-screen-navigation' - -const scenes = [ - { id: 'intro-chat', title: 'Intro Chat' }, - { id: 'intro-websocket', title: 'Intro WebSocket' }, - { id: 'settings-only', title: 'Settings Only' }, -] - -describe('useSceneNavigation', () => { - it('derives active, prev, and next scenes from current id', () => { - const currentSceneId = ref('intro-websocket') - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: () => {}, - }) - - expect(nav.activeScene.value?.id).toBe('intro-websocket') - expect(nav.canGoPrev.value).toBe(true) - expect(nav.canGoNext.value).toBe(true) - expect(nav.prevScene.value?.id).toBe('intro-chat') - expect(nav.nextScene.value?.id).toBe('settings-only') - }) - - it('returns unknown active scene fallback when current id is not in registry', () => { - const currentSceneId = ref('missing') - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: () => {}, - }) - - expect(nav.activeScene.value).toBeUndefined() - expect(nav.activeSceneLabel.value).toBe('Unknown scene') - expect(nav.canGoPrev.value).toBe(false) - expect(nav.canGoNext.value).toBe(false) - }) - - it('filters by query and keeps active scene first in palette items', () => { - const currentSceneId = ref('intro-websocket') - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: () => {}, - }) - - nav.searchQuery.value = 'intro' - expect(nav.filteredScenes.value.map(scene => scene.id)).toEqual(['intro-chat', 'intro-websocket']) - expect(nav.paletteItems.value[0]?.id).toBe('intro-websocket') - }) - - it('invokes onNavigate with selected scene id', () => { - const currentSceneId = ref('intro-chat') - const calls: string[] = [] - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: id => calls.push(id), - }) - - nav.goToScene('settings-only') - expect(calls).toEqual(['settings-only']) - }) - - it('goes to the previous scene when available', () => { - const currentSceneId = ref('intro-websocket') - const calls: string[] = [] - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: id => calls.push(id), - }) - - nav.goPrev() - expect(calls).toEqual(['intro-chat']) - }) - - it('goes to the next scene when available', () => { - const currentSceneId = ref('intro-websocket') - const calls: string[] = [] - const nav = useSceneNavigation({ - scenes, - currentSceneId, - onNavigate: id => calls.push(id), - }) - - nav.goNext() - expect(calls).toEqual(['settings-only']) - }) - - it('does not navigate backward at the first scene or forward at the last scene', () => { - const firstSceneCalls: string[] = [] - const firstSceneNav = useSceneNavigation({ - scenes, - currentSceneId: ref('intro-chat'), - onNavigate: id => firstSceneCalls.push(id), - }) - - firstSceneNav.goPrev() - expect(firstSceneCalls).toEqual([]) - - const lastSceneCalls: string[] = [] - const lastSceneNav = useSceneNavigation({ - scenes, - currentSceneId: ref('settings-only'), - onNavigate: id => lastSceneCalls.push(id), - }) - - lastSceneNav.goNext() - expect(lastSceneCalls).toEqual([]) - }) -}) diff --git a/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.ts b/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.ts deleted file mode 100644 index f0c3b2659..000000000 --- a/packages/vishot-runtime/src/vue/composables/screen-navigation/use-screen-navigation.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Ref } from 'vue' - -import { computed, ref, unref } from 'vue' - -export interface SceneNavigationScene { - id: string - title: string -} - -export interface UseSceneNavigationOptions { - currentSceneId: Ref - onNavigate: Ref<(sceneId: string) => void> | ((sceneId: string) => void) - scenes: Ref | readonly TScene[] -} - -export function useSceneNavigation( - options: UseSceneNavigationOptions, -) { - const searchQuery = ref('') - const resolvedScenes = computed(() => unref(options.scenes)) - const resolvedNavigate = computed(() => unref(options.onNavigate)) - - const normalizedQuery = computed(() => searchQuery.value.trim().toLowerCase()) - const activeScene = computed(() => resolvedScenes.value.find(scene => scene.id === options.currentSceneId.value)) - const activeSceneLabel = computed(() => activeScene.value?.title ?? 'Unknown scene') - const activeIndex = computed(() => resolvedScenes.value.findIndex(scene => scene.id === options.currentSceneId.value)) - - const prevScene = computed(() => ( - activeIndex.value > 0 ? resolvedScenes.value[activeIndex.value - 1] : undefined - )) - const nextScene = computed(() => ( - activeIndex.value >= 0 && activeIndex.value < resolvedScenes.value.length - 1 - ? resolvedScenes.value[activeIndex.value + 1] - : undefined - )) - - const canGoPrev = computed(() => prevScene.value !== undefined) - const canGoNext = computed(() => nextScene.value !== undefined) - - const filteredScenes = computed(() => { - if (!normalizedQuery.value) { - return resolvedScenes.value - } - - return resolvedScenes.value.filter(scene => ( - scene.id.toLowerCase().includes(normalizedQuery.value) - || scene.title.toLowerCase().includes(normalizedQuery.value) - )) - }) - - const paletteItems = computed(() => { - const filtered = filteredScenes.value - const activeId = options.currentSceneId.value - const active = filtered.find(scene => scene.id === activeId) - - if (!active) { - return filtered - } - - return [ - active, - ...filtered.filter(scene => scene.id !== activeId), - ] - }) - - function goToScene(sceneId: string) { - resolvedNavigate.value(sceneId) - } - - function goPrev() { - if (prevScene.value) { - goToScene(prevScene.value.id) - } - } - - function goNext() { - if (nextScene.value) { - goToScene(nextScene.value.id) - } - } - - return { - activeScene, - activeSceneLabel, - canGoNext, - canGoPrev, - filteredScenes, - goNext, - goPrev, - goToScene, - nextScene, - paletteItems, - prevScene, - searchQuery, - } -} diff --git a/packages/vishot-runtime/src/vue/index.ts b/packages/vishot-runtime/src/vue/index.ts deleted file mode 100644 index fc72c4fcb..000000000 --- a/packages/vishot-runtime/src/vue/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { default as ScenarioCanvas } from './components/scenario-canvas.vue' -export { default as ScenarioCaptureRoot } from './components/scenario-capture-root.vue' -export * from './components/screen-marups' -export * from './components/screen-navigator' -export * from './components/screen-router' -export * from './composables/screen-navigation/use-screen-navigation' -export { useSceneReady } from './use-scene-ready' diff --git a/packages/vishot-runtime/src/vue/use-scene-ready.test.ts b/packages/vishot-runtime/src/vue/use-scene-ready.test.ts deleted file mode 100644 index 50888e38f..000000000 --- a/packages/vishot-runtime/src/vue/use-scene-ready.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -// @vitest-environment jsdom -import { describe, expect, it, vi } from 'vitest' -import { createApp, h } from 'vue' - -const readyMocks = vi.hoisted(() => ({ - markScenarioReady: vi.fn(), - resetScenarioReady: vi.fn(), -})) - -vi.mock('../runtime/ready', () => ({ - markScenarioReady: readyMocks.markScenarioReady, - resetScenarioReady: readyMocks.resetScenarioReady, -})) - -class MockImage { - private _src = '' - - addEventListener(type: string, listener: () => void): void { - if (type === 'load') { - queueMicrotask(listener) - } - } - - set src(value: string) { - this._src = value - } - - get src(): string { - return this._src - } -} - -describe('useSceneReady', () => { - it('resets first and marks ready after fonts and image sources resolve', async () => { - const originalImage = globalThis.Image - const originalFonts = document.fonts - - // @ts-expect-error test shim - globalThis.Image = MockImage - Object.defineProperty(document, 'fonts', { - configurable: true, - value: { - ready: Promise.resolve(), - }, - }) - - const { useSceneReady } = await import('./index') - - const host = document.createElement('div') - document.body.appendChild(host) - - const app = createApp({ - setup() { - useSceneReady(['a.png', 'b.png']) - return () => h('div') - }, - }) - - app.mount(host) - await Promise.resolve() - await Promise.resolve() - await new Promise(resolve => setTimeout(resolve, 0)) - - expect(readyMocks.resetScenarioReady).toHaveBeenCalledTimes(1) - expect(readyMocks.markScenarioReady).toHaveBeenCalledTimes(1) - - app.unmount() - host.remove() - - if (originalImage) { - globalThis.Image = originalImage - } - Object.defineProperty(document, 'fonts', { - configurable: true, - value: originalFonts, - }) - }) -}) diff --git a/packages/vishot-runtime/src/vue/use-scene-ready.ts b/packages/vishot-runtime/src/vue/use-scene-ready.ts deleted file mode 100644 index 1ab2c92d8..000000000 --- a/packages/vishot-runtime/src/vue/use-scene-ready.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { onMounted } from 'vue' - -import { markScenarioReady, resetScenarioReady } from '../runtime/ready' - -async function waitForImageSource(source: string): Promise { - await new Promise((resolve, reject) => { - const image = new Image() - - image.addEventListener('load', () => resolve(), { once: true }) - image.addEventListener('error', () => reject(new Error(`Scenario image failed to load: ${source}`)), { once: true }) - image.src = source - }) -} - -export function useSceneReady(sources: string[]): void { - onMounted(async () => { - resetScenarioReady() - try { - await Promise.all([ - document.fonts.ready, - ...sources.map(source => waitForImageSource(source)), - ]) - markScenarioReady() - } - catch (error) { - console.error(error) - } - }) -} diff --git a/packages/vishot-runtime/tsconfig.json b/packages/vishot-runtime/tsconfig.json deleted file mode 100644 index 226fbfe79..000000000 --- a/packages/vishot-runtime/tsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "jsx": "preserve", - "lib": [ - "DOM", - "ESNext", - "DOM.AsyncIterable" - ], - "module": "ESNext", - "moduleResolution": "Bundler", - "resolveJsonModule": true, - "types": [ - "vitest", - "vite/client" - ], - "allowJs": true, - "strict": true, - "strictNullChecks": true, - "noUnusedLocals": true, - "noEmit": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "verbatimModuleSyntax": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts", - "src/**/*.d.ts", - "src/**/*.vue", - "vite.config.ts", - "vitest.config.ts" - ], - "exclude": [ - "dist", - "node_modules" - ] -} diff --git a/packages/vishot-runtime/vite.config.ts b/packages/vishot-runtime/vite.config.ts deleted file mode 100644 index a72c03b58..000000000 --- a/packages/vishot-runtime/vite.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { defineConfig } from 'vite' - -export default defineConfig({ -}) diff --git a/packages/vishot-runtime/vitest.config.ts b/packages/vishot-runtime/vitest.config.ts deleted file mode 100644 index 3de9f5ff0..000000000 --- a/packages/vishot-runtime/vitest.config.ts +++ /dev/null @@ -1,36 +0,0 @@ -import Vue from '@vitejs/plugin-vue' - -import { playwright } from '@vitest/browser-playwright' -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - root: import.meta.dirname, - plugins: [Vue()], - test: { - projects: [ - { - extends: true, - test: { - name: 'node', - include: ['src/**/*.test.ts'], - exclude: ['src/**/*.browser.test.ts'], - }, - }, - { - extends: true, - test: { - name: 'browser', - include: ['src/**/*.browser.{spec,test}.ts'], - exclude: ['**/node_modules/**'], - browser: { - enabled: true, - provider: playwright(), - instances: [ - { browser: 'chromium' }, - ], - }, - }, - }, - ], - }, -}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 444eb2abd..4218a26ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -498,6 +498,18 @@ catalogs: '@velin-dev/core': specifier: ^0.4.0 version: 0.4.0 + '@vishot/cli': + specifier: ^0.0.3 + version: 0.0.3 + '@vishot/mockup-desktop-vue': + specifier: ^0.0.3 + version: 0.0.3 + '@vishot/renderer-browser': + specifier: ^0.0.3 + version: 0.0.3 + '@vishot/source-electron': + specifier: ^0.0.3 + version: 0.0.3 '@vitejs/plugin-vue': specifier: ^6.0.6 version: 6.0.6 @@ -777,9 +789,6 @@ catalogs: mediabunny: specifier: ^1.40.1 version: 1.40.1 - meow: - specifier: ^14.1.0 - version: 14.1.0 meta-png: specifier: ^1.0.6 version: 1.0.6 @@ -1243,6 +1252,9 @@ importers: '@unocss/preset-web-fonts': specifier: 'catalog:' version: 66.6.8 + '@vishot/cli': + specifier: 'catalog:' + version: 0.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@vitest/browser-playwright': specifier: catalog:vitest version: 4.1.4(bufferutil@4.1.0)(playwright@1.60.0)(utf-8-validate@5.0.10)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) @@ -2275,7 +2287,7 @@ importers: version: 3.0.2(electron@41.2.1) '@electron-toolkit/tsconfig': specifier: 'catalog:' - version: 2.0.0(@types/node@24.12.2) + version: 2.0.0(@types/node@25.6.0) '@electron-toolkit/utils': specifier: 'catalog:' version: 4.0.0(electron@41.2.1) @@ -2314,7 +2326,7 @@ importers: version: 3.1.0 '@intlify/unplugin-vue-i18n': specifier: 'catalog:' - version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + version: 11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) @@ -2350,10 +2362,10 @@ importers: version: link:../../packages/ui-transitions '@proj-airi/unplugin-fetch': specifier: 'catalog:' - version: 0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@proj-airi/unplugin-live2d-sdk': specifier: 'catalog:' - version: 0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@types/audioworklet': specifier: 'catalog:' version: 0.0.97 @@ -2380,7 +2392,7 @@ importers: version: 2.10.3 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) + version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) '@vue-macros/volar': specifier: 'catalog:' version: 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) @@ -2413,7 +2425,7 @@ importers: version: 6.8.3 electron-vite: specifier: 'catalog:' - version: 5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) get-port-please: specifier: 'catalog:' version: 3.2.0 @@ -2434,31 +2446,31 @@ importers: version: 2.2.6 unocss-preset-scrollbar: specifier: 'catalog:' - version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) + version: 4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) unplugin-info: specifier: 'catalog:' - version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) unplugin-yaml: specifier: 'catalog:' - version: 4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.0(@nuxt/kit@3.20.2(magicast@0.5.2))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite: specifier: 'catalog:' - version: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-bundle-visualizer: specifier: 'catalog:' version: 1.2.1(rolldown@1.0.0-rc.16)(rollup@4.60.1) vite-plugin-mkcert: specifier: 'catalog:' - version: 2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite-plugin-vue-devtools: specifier: 'catalog:' - version: 8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) + version: 8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) vite-plugin-vue-layouts: specifier: 'catalog:' - version: 0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + version: 0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) vue-macros: specifier: 'catalog:' - version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) + version: 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) vue-tsc: specifier: 'catalog:' version: 3.2.6(typescript@5.9.3) @@ -3227,18 +3239,6 @@ importers: specifier: 'catalog:' version: 1.6.0 - packages/airi-screenshot: - dependencies: - '@moeru/std': - specifier: 'catalog:' - version: 0.1.0-beta.17 - cac: - specifier: 'catalog:' - version: 7.0.0 - tinyexec: - specifier: 'catalog:' - version: 1.2.4 - packages/audio: dependencies: '@alexanderolsen/libsamplerate-js': @@ -3607,12 +3607,12 @@ importers: '@napi-rs/image': specifier: 'catalog:' version: 1.12.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) - '@proj-airi/vishot-runner-browser': - specifier: workspace:^ - version: link:../vishot-runner-browser - '@proj-airi/vishot-runtime': - specifier: workspace:^ - version: link:../vishot-runtime + '@vishot/mockup-desktop-vue': + specifier: 'catalog:' + version: 0.0.3(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(reka-ui@2.9.6(vue@3.5.32(typescript@5.9.3)))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + '@vishot/renderer-browser': + specifier: 'catalog:' + version: 0.0.3(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@vueuse/core': specifier: 'catalog:' version: 14.2.1(vue@3.5.32(typescript@5.9.3)) @@ -3659,9 +3659,12 @@ importers: '@moeru/std': specifier: 'catalog:' version: 0.1.0-beta.17 - '@proj-airi/vishot-runner-electron': - specifier: workspace:^ - version: link:../vishot-runner-electron + '@vishot/source-electron': + specifier: 'catalog:' + version: 0.0.3 + playwright: + specifier: 'catalog:' + version: 1.60.0 packages/server-runtime: dependencies: @@ -4840,55 +4843,6 @@ importers: specifier: '>=66' version: 66.6.8 - packages/vishot-runner-browser: - devDependencies: - '@moeru/std': - specifier: 'catalog:' - version: 0.1.0-beta.17 - meow: - specifier: 'catalog:' - version: 14.1.0 - playwright: - specifier: 'catalog:' - version: 1.60.0 - - packages/vishot-runner-electron: - devDependencies: - '@moeru/std': - specifier: 'catalog:' - version: 0.1.0-beta.17 - '@napi-rs/image': - specifier: 'catalog:' - version: 1.12.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) - meow: - specifier: 'catalog:' - version: 14.1.0 - playwright: - specifier: 'catalog:' - version: 1.60.0 - - packages/vishot-runtime: - dependencies: - '@vueuse/core': - specifier: 'catalog:' - version: 14.2.1(vue@3.5.32(typescript@5.9.3)) - reka-ui: - specifier: 'catalog:' - version: 2.9.6(vue@3.5.32(typescript@5.9.3)) - vue: - specifier: 'catalog:' - version: 3.5.32(typescript@5.9.3) - vue-router: - specifier: 'catalog:' - version: 5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) - devDependencies: - '@vitejs/plugin-vue': - specifier: 'catalog:' - version: 6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)) - vue-tsc: - specifier: 'catalog:' - version: 3.2.6(typescript@5.9.3) - plugins/airi-plugin-bilibili-laplace: dependencies: '@guiiai/logg': @@ -11165,6 +11119,27 @@ packages: '@vibrant/worker@4.0.4': resolution: {integrity: sha512-Q/R6PYhSMWCXEk/IcXbWIzIu7Z4b58ABkGvcdF8Y+q/7g+KnpxKW5x/jfQ/6ciyYSby13wZZoEdNr3QQVgsdBQ==} + '@vishot/cli@0.0.3': + resolution: {integrity: sha512-Yge2xFGq8BLCzRzXpNMX9w0oUoRsHzqt/xe1DQY1jWoi9PvldrfAulibxvLgvhPKA7y2ZzPN8kVQRUbOY/AdXA==} + hasBin: true + + '@vishot/core@0.0.3': + resolution: {integrity: sha512-NydFb3Fr9LXGSs/c473LThsL1aBbhUWiSCeoWWvYCLZwPhMnZOvLmXwBcbp1ufVZgW1FJv1KeSti1yZDY57ZwQ==} + + '@vishot/mockup-desktop-vue@0.0.3': + resolution: {integrity: sha512-81zte5unPP0/Ej5beYrq4u5DqZDVxalgMcr1tFlFfn1NF9FijZtBaU7pmxEdY2p0UP9xGUsOLYw8UrnJdG9Yog==} + peerDependencies: + '@vueuse/core': ^14.2.1 + reka-ui: ^2.6.1 + vue: ^3.5.26 + vue-router: ^4.6.4 + + '@vishot/renderer-browser@0.0.3': + resolution: {integrity: sha512-9A0HIrKtdGQnKlJ/H1yam3b4BXeEJGMilW9XTBUwgoY817H6XsMIWlderp0V0HKjssbegarAFTC1rlzSdmc0Zg==} + + '@vishot/source-electron@0.0.3': + resolution: {integrity: sha512-yqAW6yFX3NaoAYE/c4R8hC6qz23w7qczUzwRcvHCHcJKjD9KEdVUr+tBVQKFIsFSvINBS7JRdYs08Ow8VYztfg==} + '@vitejs/devtools-kit@0.1.13': resolution: {integrity: sha512-8TqyrrPTB8KNGb2ukVHNo4aMhGYJgUypVNMnqOvxaWYln3QAXK6CFxifK3lZGOHWKAUqWAiTmZUsYzV4S0Kn7g==} peerDependencies: @@ -20825,9 +20800,9 @@ snapshots: dependencies: electron: 41.2.1 - '@electron-toolkit/tsconfig@2.0.0(@types/node@24.12.2)': + '@electron-toolkit/tsconfig@2.0.0(@types/node@25.6.0)': dependencies: - '@types/node': 24.12.2 + '@types/node': 25.6.0 '@electron-toolkit/utils@4.0.0(electron@41.2.1)': dependencies: @@ -21737,31 +21712,6 @@ snapshots: - supports-color - typescript - '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) - '@intlify/bundle-utils': 11.0.7(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3))) - '@intlify/shared': 11.3.2 - '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.3.2)(@vue/compiler-dom@3.5.32)(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) - '@rollup/pluginutils': 5.3.0(rollup@4.60.1) - '@typescript-eslint/scope-manager': 8.63.0 - '@typescript-eslint/typescript-estree': 8.63.0(typescript@5.9.3) - debug: 4.4.3(supports-color@10.2.2) - fast-glob: 3.3.3 - pathe: 2.0.3 - picocolors: 1.1.1 - unplugin: 2.3.11 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vue: 3.5.32(typescript@5.9.3) - optionalDependencies: - vue-i18n: 11.3.2(vue@3.5.32(typescript@5.9.3)) - transitivePeerDependencies: - - '@vue/compiler-dom' - - eslint - - rollup - - supports-color - - typescript - '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.32)(eslint@10.2.1(jiti@2.6.1))(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-i18n@11.3.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) @@ -23772,35 +23722,11 @@ snapshots: transitivePeerDependencies: - magicast - '@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - ofetch: 1.5.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@proj-airi/unplugin-fetch@0.2.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: ofetch: 1.5.1 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - '@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': - dependencies: - ofetch: 1.5.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - yauzl: 3.3.0 - transitivePeerDependencies: - - '@types/node' - - '@vitejs/devtools' - - esbuild - - jiti - - less - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - '@proj-airi/unplugin-live2d-sdk@0.1.7(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': dependencies: ofetch: 1.5.1 @@ -25376,6 +25302,82 @@ snapshots: dependencies: '@vibrant/types': 4.0.4 + '@vishot/cli@0.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': + dependencies: + '@moeru/std': 0.1.0-beta.19 + '@napi-rs/image': 1.12.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@vishot/core': 0.0.3 + '@vishot/renderer-browser': 0.0.3(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@vishot/source-electron': 0.0.3 + cac: 6.7.14 + playwright: 1.60.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + '@vishot/core@0.0.3': {} + + '@vishot/mockup-desktop-vue@0.0.3(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(reka-ui@2.9.6(vue@3.5.32(typescript@5.9.3)))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3))': + dependencies: + '@vishot/core': 0.0.3 + '@vueuse/core': 14.2.1(vue@3.5.32(typescript@5.9.3)) + reka-ui: 2.9.6(vue@3.5.32(typescript@5.9.3)) + vue: 3.5.32(typescript@5.9.3) + vue-router: 5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) + + '@vishot/renderer-browser@0.0.3(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': + dependencies: + '@vishot/core': 0.0.3 + playwright: 1.60.0 + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + '@vishot/renderer-browser@0.0.3(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)': + dependencies: + '@vishot/core': 0.0.3 + playwright: 1.60.0 + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + '@vishot/source-electron@0.0.3': + dependencies: + '@moeru/std': 0.1.0-beta.19 + '@vishot/core': 0.0.3 + playwright: 1.60.0 + '@vitejs/devtools-kit@0.1.13(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(ws@8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: '@vitejs/devtools-rpc': 0.1.13(typescript@5.9.3)(ws@8.20.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)) @@ -25398,12 +25400,6 @@ snapshots: transitivePeerDependencies: - typescript - '@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))': - dependencies: - '@rolldown/pluginutils': 1.0.0-rc.13 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vue: 3.5.32(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.6(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.13 @@ -25681,15 +25677,6 @@ snapshots: transitivePeerDependencies: - vue - '@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - sirv: 3.0.2 - vue: 3.5.32(typescript@5.9.3) - optionalDependencies: - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - typescript - '@vue-macros/devtools@3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: sirv: 3.0.2 @@ -27769,7 +27756,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@5.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + electron-vite@5.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) @@ -27777,7 +27764,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -33774,6 +33761,11 @@ snapshots: '@unocss/preset-mini': 66.6.8 unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + unocss-preset-scrollbar@4.0.0(unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))): + dependencies: + '@unocss/preset-mini': 66.6.8 + unocss: 66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + unocss@66.6.8(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@unocss/cli': 66.6.8 @@ -33840,14 +33832,6 @@ snapshots: unplugin: 2.3.11 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - esbuild: 0.27.2 - rolldown: 1.0.0-rc.16 - rollup: 4.60.1 - unplugin: 2.3.11 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - unplugin-combine@2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: esbuild: 0.27.2 @@ -33869,19 +33853,6 @@ snapshots: transitivePeerDependencies: - supports-color - unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - ci-info: 4.4.0 - git-url-parse: 16.1.0 - simple-git: 3.36.0 - unplugin: 2.3.11 - optionalDependencies: - esbuild: 0.27.2 - rollup: 4.60.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - unplugin-info@1.3.2(esbuild@0.27.2)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: ci-info: 4.4.0 @@ -33975,17 +33946,6 @@ snapshots: rollup: 2.80.0 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - unplugin-yaml@4.1.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.1) - unplugin: 3.0.0 - yaml: 2.8.3 - optionalDependencies: - esbuild: 0.27.2 - rolldown: 1.0.0-rc.16 - rollup: 4.60.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -34213,22 +34173,12 @@ snapshots: - rollup - supports-color - vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - birpc: 2.9.0 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-hot-client: 2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vite-dev-rpc@1.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: birpc: 2.9.0 vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-hot-client: 2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vite-hot-client@2.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-hot-client@2.1.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) @@ -34274,21 +34224,6 @@ snapshots: - tsx - yaml - vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - ansis: 4.2.0 - debug: 4.4.3(supports-color@10.2.2) - error-stack-parser-es: 1.0.5 - ohash: 2.0.11 - open: 10.2.0 - perfect-debounce: 2.1.0 - sirv: 3.0.2 - unplugin-utils: 0.3.1 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-dev-rpc: 1.1.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - transitivePeerDependencies: - - supports-color - vite-plugin-inspect@11.3.3(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: ansis: 4.2.0 @@ -34320,13 +34255,6 @@ snapshots: - typescript - ws - vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - supports-color: 10.2.2 - undici: 8.1.0 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-mkcert@2.0.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -34345,20 +34273,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)): - dependencies: - '@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3)) - '@vue/devtools-kit': 8.1.1 - '@vue/devtools-shared': 8.1.1 - sirv: 3.0.2 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-inspect: 11.3.3(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vite-plugin-vue-inspector: 5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - transitivePeerDependencies: - - '@nuxt/kit' - - supports-color - - vue - vite-plugin-vue-devtools@8.1.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@5.9.3)): dependencies: '@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@5.9.3)) @@ -34373,21 +34287,6 @@ snapshots: - supports-color - vue - vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0) - '@vue/compiler-dom': 3.5.32 - kolorist: 1.8.0 - magic-string: 0.30.21 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - vite-plugin-vue-inspector@5.3.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 @@ -34403,16 +34302,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - fast-glob: 3.3.3 - vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - vue: 3.5.32(typescript@5.9.3) - vue-router: 5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) - transitivePeerDependencies: - - supports-color - vite-plugin-vue-layouts@0.11.0(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-router@5.0.4(@pinia/colada@1.2.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -34442,6 +34331,23 @@ snapshots: yaml: 2.8.3 optional: true + vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.10 + rollup: 4.60.1 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.2 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.6.4 + terser: 5.46.1 + tsx: 4.21.0 + yaml: 2.8.3 + vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.2 @@ -34680,54 +34586,6 @@ snapshots: - vue-tsc - webpack - vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)): - dependencies: - '@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/boolean-prop': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/chain-call': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/common': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/config': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-emit': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-models': 3.1.2(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-prop': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-props': 3.1.2(@vue-macros/reactivity-transform@3.1.2(vue@3.5.32(typescript@5.9.3)))(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-props-refs': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-render': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-slots': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/define-stylex': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/devtools': 3.1.2(typescript@5.9.3)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vue-macros/export-expose': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/export-props': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/export-render': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/hoist-static': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/jsx-directive': 3.1.2(typescript@5.9.3) - '@vue-macros/named-template': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/reactivity-transform': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/script-lang': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/setup-block': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/setup-component': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/setup-sfc': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/short-bind': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/short-emits': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/short-vmodel': 3.1.2(vue@3.5.32(typescript@5.9.3)) - '@vue-macros/volar': 3.1.2(typescript@5.9.3)(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)) - unplugin: 2.3.11 - unplugin-combine: 2.3.0(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(unplugin@2.3.11)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - unplugin-vue-define-options: 3.1.2(vue@3.5.32(typescript@5.9.3)) - vue: 3.5.32(typescript@5.9.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - - '@rspack/core' - - '@vueuse/core' - - esbuild - - rolldown - - rollup - - typescript - - vite - - vue-tsc - - webpack - vue-macros@3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@vueuse/core@14.2.1(vue@3.5.32(typescript@5.9.3)))(esbuild@0.27.2)(rolldown@1.0.0-rc.16)(rollup@4.60.1)(typescript@5.9.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.6.4)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vue-tsc@3.2.6(typescript@5.9.3))(vue@3.5.32(typescript@5.9.3)): dependencies: '@vue-macros/better-define': 3.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(vue@3.5.32(typescript@5.9.3)) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b1fe156a3..b6096b72a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -195,6 +195,10 @@ catalog: '@unocss/reset': ^66.6.8 '@valibot/to-json-schema': 1.0.0-rc.0 '@velin-dev/core': ^0.4.0 + '@vishot/cli': ^0.0.3 + '@vishot/mockup-desktop-vue': ^0.0.3 + '@vishot/renderer-browser': ^0.0.3 + '@vishot/source-electron': ^0.0.3 '@vitejs/plugin-vue': ^6.0.6 '@vue-macros/volar': ^3.1.2 '@vue/test-utils': ^2.4.6 @@ -288,7 +292,6 @@ catalog: markdown-it: ^14.1.1 markdown-it-anchor: ^9.2.0 mediabunny: ^1.40.1 - meow: ^14.1.0 meta-png: ^1.0.6 minecraft-data: 3.102.3 mineflayer: ^4.33.0 diff --git a/vitest.config.ts b/vitest.config.ts index f18d5d115..17293779b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,10 +8,11 @@ export default defineConfig({ 'apps/stage-tamagotchi', 'packages/cap-vite', 'packages/core-agent', - 'packages/vishot-runner-browser', 'packages/better-ws', 'packages/plugin-sdk', 'packages/plugin-sdk-tamagotchi', + 'packages/scenarios-stage-tamagotchi-browser', + 'packages/scenarios-stage-tamagotchi-electron', 'packages/server-runtime', 'packages/server-sdk', 'packages/stage-shared',