返回 html-video
context.ts
根目录 / packages / cli / src / context.ts
1 /**
2 * Bootstrap shared CLI context: project root, registries, stores, orchestrator.
3 */
4
5 import { existsSync } from 'node:fs';
6 import { join, dirname } from 'node:path';
7 import { fileURLToPath } from 'node:url';
8 import {
9 AssetStore,
10 EngineRegistry,
11 ProjectOrchestrator,
12 ProjectStore,
13 TemplateRegistry,
14 } from '@html-video/core';
15 import hfAdapter from '@html-video/adapter-hyperframes';
16 import remotionAdapter, { remotionInstalled } from '@html-video/adapter-remotion';
17 import { MediaConfigStore } from './media-config.js';
18
19 export interface CliContext {
20 projectRoot: string;
21 engines: EngineRegistry;
22 templates: TemplateRegistry;
23 projects: ProjectStore;
24 assets: AssetStore;
25 orchestrator: ProjectOrchestrator;
26 templatesDir: string;
27 mediaConfig: MediaConfigStore;
28 }
29
30 export function findProjectRoot(start: string = process.cwd()): string {
31 let dir = start;
32 for (let i = 0; i < 8; i++) {
33 if (existsSync(join(dir, '.html-video'))) return dir;
34 if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return dir;
35 if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'templates'))) return dir;
36 const parent = dirname(dir);
37 if (parent === dir) break;
38 dir = parent;
39 }
40 return start;
41 }
42
43 function findTemplatesDir(projectRoot: string): string {
44 const candidates = [
45 join(projectRoot, 'templates'),
46 // packages/cli/dist/context.js → up 3 levels → monorepo root → templates/
47 join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'templates'),
48 ];
49 for (const c of candidates) {
50 if (existsSync(c)) return c;
51 }
52 return candidates[0]!;
53 }
54
55 export async function bootstrap(opts: { cwd?: string } = {}): Promise<CliContext> {
56 const projectRoot = findProjectRoot(opts.cwd);
57
58 const engines = new EngineRegistry();
59 engines.register(hfAdapter);
60 // Register Remotion only when its peer deps are actually installed, so the
61 // registry never lists an engine that would throw on render. (RFC-08)
62 if (remotionInstalled()) {
63 engines.register(remotionAdapter);
64 }
65
66 const templates = new TemplateRegistry();
67 const templatesDir = findTemplatesDir(projectRoot);
68 await templates.scan(templatesDir);
69
70 const projects = new ProjectStore(projectRoot);
71 const assets = new AssetStore({ projectRoot });
72
73 const orchestrator = new ProjectOrchestrator({
74 projectRoot,
75 engines,
76 templates,
77 projects,
78 assets,
79 });
80
81 const mediaConfig = new MediaConfigStore(projectRoot);
82
83 return { projectRoot, engines, templates, projects, assets, orchestrator, templatesDir, mediaConfig };
84 }
85
85 lines TYPESCRIPT