lhci-collect
Version:
155 lines • 6.21 kB
JavaScript
import { Command } from 'commander';
import got from 'got';
import { XMLParser } from 'fast-xml-parser';
import { writeFileSync, readFileSync, existsSync } from 'fs';
import { stringify as yamlStringify } from 'yaml';
export const program = new Command();
program
.name('collect')
.description('Generate Lighthouse CI configuration from sitemap URLs')
.requiredOption('-u, --sitemap-url <urls...>', 'One or more sitemap URLs (supports http/https URLs or local file paths)')
.option('-f, --format <format>', 'Output format (json, yaml, js)', 'json')
.option('-o, --output <file>', 'Output file path (default: stdout)')
.option('-r, --runs <number>', 'Number of Lighthouse runs per URL', '1')
.option('-s, --start-cmd <cmd>', 'Start server command example: pnpm start,default no start server')
.option('--preset <preset>', 'Lighthouse preset', 'desktop')
.option('--upload-url <url>', 'LHCI server base URL', 'http://localhost:9001')
.option('--upload-token <token>', 'LHCI token')
.option('--assert-preset <preset>', 'Assert preset', 'lighthouse:all')
.option('--chrome-flags <flags>', 'Chrome flags for Lighthouse: --no-sandbox --headless --disable-gpu --disable-dev-shm-usage,default: --no-sandbox --headless --disable-gpu', '--no-sandbox --headless --disable-gpu')
.action(main);
const parser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '@_',
});
async function fetchSitemapUrls(url) {
try {
let xmlData;
// 检查是否是本地文件路径
if (url.startsWith('file://')) {
const filePath = url.replace('file://', '');
if (!existsSync(filePath)) {
throw new Error(`Local file not found: ${filePath}`);
}
xmlData = readFileSync(filePath, 'utf-8');
}
else if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {
// 相对或绝对本地路径
if (!existsSync(url)) {
throw new Error(`Local file not found: ${url}`);
}
xmlData = readFileSync(url, 'utf-8');
}
else {
// HTTP/HTTPS URL
const res = await got(url);
xmlData = res.body;
}
const json = parser.parse(xmlData);
// sitemap index: <sitemapindex><sitemap><loc>...</loc></sitemap></sitemapindex>
if (json.sitemapindex && json.sitemapindex.sitemap) {
const sitemaps = json.sitemapindex.sitemap;
const list = Array.isArray(sitemaps) ? sitemaps : [sitemaps];
// 递归获取所有子 sitemap URLs
const results = await Promise.all(list.map((sm) => fetchSitemapUrls(sm.loc)));
return results.flat();
}
// urlset: <urlset><url><loc>...</loc></url></urlset>
if (json.urlset && json.urlset.url) {
const urls = json.urlset.url;
const list = Array.isArray(urls) ? urls : [urls];
return list.map((u) => u.loc).filter(Boolean);
}
console.warn(`Warning: Unrecognized sitemap format at ${url}`);
return [];
}
catch (e) {
console.error(`Failed to fetch or parse sitemap ${url}: ${e.message}`);
return [];
}
}
function formatConfig(config, format) {
switch (format.toLowerCase()) {
case 'yaml':
case 'yml':
return yamlStringify(config);
case 'js':
return `module.exports = ${JSON.stringify(config, null, 2)};`;
case 'json':
default:
return JSON.stringify(config, null, 2);
}
}
function getDefaultFileName(format) {
switch (format.toLowerCase()) {
case 'yaml':
return 'lighthouserc.yaml';
case 'yml':
return 'lighthouserc.yml';
case 'js':
return 'lighthouserc.js';
case 'json':
default:
return 'lighthouserc.json';
}
}
async function main(options) {
const sitemapUrls = options.sitemapUrl;
console.log(`🔍 Fetching URLs from ${sitemapUrls.length} sitemap(s)...`);
// 收集所有 urls
let allUrls = [];
for (const sitemapUrl of sitemapUrls) {
console.log(`📄 Processing sitemap: ${sitemapUrl}`);
const urls = await fetchSitemapUrls(sitemapUrl);
console.log(` Found ${urls.length} URLs`);
allUrls = allUrls.concat(urls);
}
// 去重
const uniqueUrls = [...new Set(allUrls)];
console.log(`✨ Total unique URLs: ${uniqueUrls.length} (removed ${allUrls.length - uniqueUrls.length} duplicates)`);
if (uniqueUrls.length === 0) {
console.error('❌ No URLs found in sitemaps');
process.exit(1);
}
// 生成 LHCI 配置
const collectConfig = {
url: uniqueUrls,
numberOfRuns: Number(options.runs),
settings: {
preset: options.preset,
chromeFlags: options.chromeFlags,
},
};
// 只有当用户明确提供了 startCmd 参数时,才添加 startServerCommand
if (options.startCmd) {
collectConfig.startServerCommand = options.startCmd;
}
const config = {
ci: {
assert: { preset: options.assertPreset },
upload: {
target: 'lhci',
serverBaseUrl: options.uploadUrl,
token: options.uploadToken,
},
collect: collectConfig,
},
};
// 格式化配置
const formattedConfig = formatConfig(config, options.format);
// 输出到文件或控制台
if (options.output) {
writeFileSync(options.output, formattedConfig);
console.log(`✅ Configuration saved to: ${options.output}`);
}
else {
// 如果没有指定输出文件,输出到控制台
console.log('\n📋 Generated Lighthouse CI configuration:\n');
console.log(formattedConfig);
// 提示用户可以保存到默认文件
const defaultFileName = getDefaultFileName(options.format);
console.log(`\n💡 Tip: You can save this to ${defaultFileName} using the -o option`);
}
}
//# sourceMappingURL=index.js.map