270 lines
7.2 KiB
JavaScript
270 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* 一键构建所有用户手册 GIF
|
||
*
|
||
* 工作流程:
|
||
* 1. 加载所有 GIF 定义 (*.gif.json)
|
||
* 2. 运行 Playwright 录制长视频
|
||
* 3. 更新清单中的视频路径
|
||
* 4. 根据清单批量裁剪生成 GIF
|
||
* 5. 重建用户手册
|
||
*
|
||
* 使用方法:
|
||
* node scripts/build-all-gifs.js # 构建所有 GIF
|
||
* node scripts/build-all-gifs.js <id> # 只构建指定 GIF
|
||
* node scripts/build-all-gifs.js --list # 列出所有可用的 GIF 定义
|
||
*/
|
||
|
||
const { execSync, spawnSync } = require('child_process');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const GIFS_DIR = 'apps/web-e2e/src/manual-gen/gifs';
|
||
const MANIFEST_PATH = 'apps/web-e2e/test-results/gif-manifest.json';
|
||
const TEST_RESULTS_DIR = 'apps/web-e2e/test-results';
|
||
|
||
/**
|
||
* 加载所有 GIF 定义
|
||
*/
|
||
function loadGifDefinitions() {
|
||
if (!fs.existsSync(GIFS_DIR)) {
|
||
return [];
|
||
}
|
||
|
||
const files = fs.readdirSync(GIFS_DIR).filter(f => f.endsWith('.gif.json'));
|
||
const definitions = [];
|
||
|
||
for (const file of files) {
|
||
try {
|
||
const content = fs.readFileSync(path.join(GIFS_DIR, file), 'utf-8');
|
||
definitions.push(JSON.parse(content));
|
||
} catch (error) {
|
||
console.error(`❌ 加载失败: ${file}`, error.message);
|
||
}
|
||
}
|
||
|
||
return definitions.sort((a, b) => a.id.localeCompare(b.id));
|
||
}
|
||
|
||
/**
|
||
* 查找最新的视频文件
|
||
*/
|
||
function findLatestVideo() {
|
||
if (!fs.existsSync(TEST_RESULTS_DIR)) {
|
||
return null;
|
||
}
|
||
|
||
let latestVideo = null;
|
||
let latestTime = 0;
|
||
|
||
function searchDir(dir) {
|
||
const items = fs.readdirSync(dir);
|
||
for (const item of items) {
|
||
const fullPath = path.join(dir, item);
|
||
const stat = fs.statSync(fullPath);
|
||
|
||
if (stat.isDirectory()) {
|
||
searchDir(fullPath);
|
||
} else if (item.endsWith('.webm')) {
|
||
if (stat.mtimeMs > latestTime) {
|
||
latestTime = stat.mtimeMs;
|
||
latestVideo = fullPath;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
searchDir(TEST_RESULTS_DIR);
|
||
return latestVideo;
|
||
}
|
||
|
||
/**
|
||
* 运行命令并显示输出
|
||
*/
|
||
function runCommand(command, options = {}) {
|
||
console.log(`\n$ ${command}\n`);
|
||
try {
|
||
execSync(command, { stdio: 'inherit', ...options });
|
||
return true;
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 列出所有 GIF 定义
|
||
*/
|
||
function listDefinitions() {
|
||
const definitions = loadGifDefinitions();
|
||
|
||
if (definitions.length === 0) {
|
||
console.log('⚠️ 没有找到 GIF 定义');
|
||
console.log(` 请在 ${GIFS_DIR} 目录创建 *.gif.json 文件`);
|
||
process.exit(0);
|
||
}
|
||
|
||
console.log(`📋 可用的 GIF 定义 (共 ${definitions.length} 个):\n`);
|
||
for (const def of definitions) {
|
||
console.log(` ${def.id}`);
|
||
console.log(` 名称: ${def.name}`);
|
||
console.log(` 输出: ${def.output}`);
|
||
console.log(` 目标: ${def.targetPage}`);
|
||
console.log('');
|
||
}
|
||
process.exit(0);
|
||
}
|
||
|
||
/**
|
||
* 构建所有 GIF
|
||
*/
|
||
async function buildAll() {
|
||
const definitions = loadGifDefinitions();
|
||
|
||
if (definitions.length === 0) {
|
||
console.log('⚠️ 没有找到 GIF 定义');
|
||
console.log(` 请在 ${GIFS_DIR} 目录创建 *.gif.json 文件`);
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(`📚 找到 ${definitions.length} 个 GIF 定义\n`);
|
||
|
||
// 步骤 1: 运行 Playwright 录制
|
||
console.log('🎬 步骤 1/4: 录制视频...');
|
||
const recordSuccess = runCommand(
|
||
'cd apps/web-e2e && CI= npx playwright test --project=manual-video -g "录制所有 GIF"',
|
||
{ timeout: 600000 } // 10 分钟超时
|
||
);
|
||
|
||
if (!recordSuccess) {
|
||
console.error('❌ 视频录制失败');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 2: 查找视频并更新清单
|
||
console.log('\n📹 步骤 2/4: 更新清单...');
|
||
const videoPath = findLatestVideo();
|
||
|
||
if (!videoPath) {
|
||
console.error('❌ 未找到录制的视频文件');
|
||
process.exit(1);
|
||
}
|
||
|
||
if (fs.existsSync(MANIFEST_PATH)) {
|
||
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf-8'));
|
||
manifest.videoPath = videoPath;
|
||
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
||
console.log(` 视频路径: ${videoPath}`);
|
||
} else {
|
||
console.error('❌ 未找到清单文件');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 3: 根据清单生成 GIF
|
||
console.log('\n✂️ 步骤 3/4: 生成 GIF...');
|
||
const gifSuccess = runCommand(`node scripts/video-to-gif.js --manifest ${MANIFEST_PATH}`);
|
||
|
||
if (!gifSuccess) {
|
||
console.error('❌ GIF 生成失败');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 4: 重建用户手册
|
||
console.log('\n📖 步骤 4/4: 重建用户手册...');
|
||
runCommand('pnpm manual:build');
|
||
|
||
console.log('\n🎉 所有 GIF 构建完成!');
|
||
}
|
||
|
||
/**
|
||
* 构建指定 GIF
|
||
*/
|
||
async function buildOne(gifId) {
|
||
const definitions = loadGifDefinitions();
|
||
const definition = definitions.find(d => d.id === gifId);
|
||
|
||
if (!definition) {
|
||
console.error(`❌ 未找到 GIF 定义: ${gifId}`);
|
||
console.log('\n可用的 GIF ID:');
|
||
for (const def of definitions) {
|
||
console.log(` ${def.id}`);
|
||
}
|
||
process.exit(1);
|
||
}
|
||
|
||
console.log(`📄 构建 GIF: ${definition.name}\n`);
|
||
|
||
// 步骤 1: 运行 Playwright 录制
|
||
console.log('🎬 步骤 1/4: 录制视频...');
|
||
const recordSuccess = runCommand(
|
||
`cd apps/web-e2e && GIF_ID=${gifId} CI= npx playwright test --project=manual-video -g "录制指定 GIF"`,
|
||
{ timeout: 300000 } // 5 分钟超时
|
||
);
|
||
|
||
if (!recordSuccess) {
|
||
console.error('❌ 视频录制失败');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 2: 查找视频并更新清单
|
||
console.log('\n📹 步骤 2/4: 更新清单...');
|
||
const videoPath = findLatestVideo();
|
||
|
||
if (!videoPath) {
|
||
console.error('❌ 未找到录制的视频文件');
|
||
process.exit(1);
|
||
}
|
||
|
||
if (fs.existsSync(MANIFEST_PATH)) {
|
||
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf-8'));
|
||
manifest.videoPath = videoPath;
|
||
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
||
console.log(` 视频路径: ${videoPath}`);
|
||
} else {
|
||
console.error('❌ 未找到清单文件');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 3: 根据清单生成 GIF
|
||
console.log('\n✂️ 步骤 3/4: 生成 GIF...');
|
||
const gifSuccess = runCommand(`node scripts/video-to-gif.js --manifest ${MANIFEST_PATH}`);
|
||
|
||
if (!gifSuccess) {
|
||
console.error('❌ GIF 生成失败');
|
||
process.exit(1);
|
||
}
|
||
|
||
// 步骤 4: 重建用户手册
|
||
console.log('\n📖 步骤 4/4: 重建用户手册...');
|
||
runCommand('pnpm manual:build');
|
||
|
||
console.log(`\n🎉 GIF 构建完成: ${definition.output}`);
|
||
}
|
||
|
||
// 主函数
|
||
async function main() {
|
||
console.log('🎬 用户手册 GIF 构建工具\n');
|
||
|
||
const args = process.argv.slice(2);
|
||
|
||
if (args.includes('--list') || args.includes('-l')) {
|
||
listDefinitions();
|
||
return;
|
||
}
|
||
|
||
if (args.includes('--help') || args.includes('-h')) {
|
||
console.log('使用方法:');
|
||
console.log(' node scripts/build-all-gifs.js # 构建所有 GIF');
|
||
console.log(' node scripts/build-all-gifs.js <id> # 只构建指定 GIF');
|
||
console.log(' node scripts/build-all-gifs.js --list # 列出所有可用的 GIF 定义');
|
||
return;
|
||
}
|
||
|
||
if (args.length > 0 && !args[0].startsWith('-')) {
|
||
await buildOne(args[0]);
|
||
} else {
|
||
await buildAll();
|
||
}
|
||
}
|
||
|
||
main().catch(console.error);
|