返回 html-video
doctor.ts
根目录 / packages / cli / src / commands / doctor.ts
1 import { execSync } from 'node:child_process';
2 import type { CliContext } from '../context.js';
3 import { ok } from '../output.js';
4
5 interface Check {
6 name: string;
7 status: 'ok' | 'warning' | 'missing' | 'error';
8 value?: string;
9 install_hint?: string;
10 detail?: string;
11 }
12
13 function which(cmd: string): string | null {
14 try {
15 return execSync(`which ${cmd}`, { stdio: ['ignore', 'pipe', 'ignore'] })
16 .toString()
17 .trim() || null;
18 } catch {
19 return null;
20 }
21 }
22
23 function version(cmd: string, args = '--version'): string | null {
24 try {
25 return execSync(`${cmd} ${args}`, { stdio: ['ignore', 'pipe', 'ignore'] })
26 .toString()
27 .trim()
28 .split('\n')[0]
29 ?? null;
30 } catch {
31 return null;
32 }
33 }
34
35 export async function runDoctor(ctx: CliContext): Promise<void> {
36 const checks: Check[] = [];
37
38 // Node
39 const nodeV = process.version;
40 checks.push({
41 name: 'node-version',
42 status: parseInt(nodeV.slice(1)) >= 20 ? 'ok' : 'warning',
43 value: nodeV,
44 detail: 'html-video targets Node 20+',
45 });
46
47 // ffmpeg
48 if (which('ffmpeg')) {
49 checks.push({ name: 'ffmpeg', status: 'ok', value: version('ffmpeg', '-version')?.split(' ')[2] ?? '?' });
50 } else {
51 checks.push({
52 name: 'ffmpeg',
53 status: 'missing',
54 install_hint: 'brew install ffmpeg (macOS) / apt install ffmpeg (Linux)',
55 });
56 }
57
58 // chromium / chrome (for HF puppeteer)
59 const chromePaths = [
60 '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
61 '/usr/bin/chromium',
62 '/usr/bin/google-chrome',
63 ];
64 const chromiumOk = chromePaths.some((p) => {
65 try {
66 execSync(`test -x "${p}"`);
67 return true;
68 } catch {
69 return false;
70 }
71 });
72 checks.push({
73 name: 'chromium',
74 status: chromiumOk ? 'ok' : 'warning',
75 detail: chromiumOk ? 'Chrome found in standard location' : 'Chrome/Chromium not detected; HF render will need a browser',
76 });
77
78 // Engines
79 for (const engine of ctx.engines.list()) {
80 checks.push({
81 name: `adapter-${engine.id}`,
82 status: 'ok',
83 value: engine.upstreamVersion,
84 detail: `${engine.name} adapter loaded`,
85 });
86 }
87
88 // Templates
89 const tcount = ctx.templates.list().length;
90 checks.push({
91 name: 'templates',
92 status: tcount >= 1 ? 'ok' : 'warning',
93 value: `${tcount} discovered`,
94 detail: tcount === 0 ? 'No templates found in templates/ — install or scaffold some' : undefined,
95 });
96
97 const overall: 'ok' | 'warning' | 'error' = checks.some((c) => c.status === 'error')
98 ? 'error'
99 : checks.some((c) => c.status === 'missing' || c.status === 'warning')
100 ? 'warning'
101 : 'ok';
102
103 ok({
104 overall,
105 project_root: ctx.projectRoot,
106 checks,
107 });
108 }
109
109 lines TYPESCRIPT