1
0
mirror of https://github.com/ZeroCatDev/Classworks.git synced 2026-08-12 17:40:32 +00:00
Classworks/scripts/validate-pwa-build.js
2026-06-28 14:26:30 +08:00

135 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from 'node:fs';
import path from 'node:path';
const distDir = path.resolve('dist');
const manifestPath = path.join(distDir, 'manifest.webmanifest');
const indexPath = path.join(distDir, 'index.html');
const serviceWorkerPath = path.join(distDir, 'sw.js');
const errors = [];
function fail(message) {
errors.push(message);
}
function readJson(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (error) {
fail(`无法读取或解析 ${path.relative(process.cwd(), filePath)}: ${error.message}`);
return null;
}
}
function fileExistsFromManifest(src) {
const normalized = src.replace(/^\.?\//, '').split('?')[0];
return fs.existsSync(path.join(distDir, normalized));
}
if (!fs.existsSync(distDir)) {
fail('dist 目录不存在,请先运行构建。');
}
const manifest = fs.existsSync(manifestPath) ? readJson(manifestPath) : null;
if (!manifest) {
fail('dist/manifest.webmanifest 不存在。');
} else {
for (const field of ['id', 'name', 'short_name', 'description', 'start_url', 'scope', 'display', 'theme_color', 'background_color']) {
if (!manifest[field]) {
fail(`manifest 缺少 ${field}`);
}
}
if (manifest.id !== '7C24F2B3.ClassworksPWA') {
fail('manifest.id 必须是 7C24F2B3.ClassworksPWA。');
}
if (!['standalone', 'fullscreen', 'minimal-ui'].includes(manifest.display)) {
fail('manifest.display 应为 standalone、fullscreen 或 minimal-ui。');
}
const icons = Array.isArray(manifest.icons) ? manifest.icons : [];
const has192 = icons.some((icon) => icon.sizes?.includes('192x192') && fileExistsFromManifest(icon.src));
const has512 = icons.some((icon) => icon.sizes?.includes('512x512') && fileExistsFromManifest(icon.src));
const hasMaskable = icons.some((icon) => icon.sizes?.includes('512x512') && icon.purpose?.includes('maskable') && fileExistsFromManifest(icon.src));
if (!has192) fail('manifest 缺少可访问的 192x192 图标。');
if (!has512) fail('manifest 缺少可访问的 512x512 图标。');
if (!hasMaskable) fail('manifest 缺少可访问的 512x512 maskable 图标。');
if (!Array.isArray(manifest.categories) || manifest.categories.length === 0) {
fail('manifest 缺少 categoriesMicrosoft Store/PWABuilder 会降低质量评分。');
}
const screenshots = Array.isArray(manifest.screenshots) ? manifest.screenshots : [];
if (screenshots.length === 0) {
fail('manifest 缺少 screenshots。');
}
for (const screenshot of screenshots) {
if (!screenshot.src || !screenshot.sizes || !screenshot.type) {
fail(`截图条目不完整: ${JSON.stringify(screenshot)}`);
} else if (!fileExistsFromManifest(screenshot.src)) {
fail(`截图文件不存在: ${screenshot.src}`);
}
}
const fileHandlers = Array.isArray(manifest.file_handlers) ? manifest.file_handlers : [];
const fileExtensions = new Set(
fileHandlers.flatMap((handler) => Object.values(handler.accept || {}).flat())
);
if (!fileExtensions.has('.csb') || !fileExtensions.has('.csi')) {
fail('manifest.file_handlers 必须允许 .csb 和 .csi。');
}
const protocolHandlers = Array.isArray(manifest.protocol_handlers) ? manifest.protocol_handlers : [];
if (!protocolHandlers.some((handler) => handler.protocol === 'cs' && handler.url?.includes('%s'))) {
fail('manifest.protocol_handlers 必须声明 cs:// 协议处理。');
}
if (manifest.edge_side_panel) {
fail('manifest 不应声明 edge_side_panel。');
}
}
if (!fs.existsSync(indexPath)) {
fail('dist/index.html 不存在。');
} else {
const html = fs.readFileSync(indexPath, 'utf8');
if (!html.includes('rel="manifest"')) {
fail('index.html 缺少 manifest 链接。');
}
if (!html.includes('apple-touch-icon')) {
fail('index.html 缺少 apple-touch-icon。');
}
if (!html.includes('msapplication-TileImage')) {
fail('index.html 缺少 Windows tile 图标声明。');
}
}
if (!fs.existsSync(serviceWorkerPath)) {
fail('dist/sw.js 不存在PWA 离线能力未生成。');
} else {
const sw = fs.readFileSync(serviceWorkerPath, 'utf8');
if (!sw.includes('sw-cache-manager.js')) {
fail('sw.js 未导入 sw-cache-manager.js。');
}
if (!sw.includes('index.html')) {
fail('sw.js 未包含导航回退,离线打开应用可能失败。');
}
}
if (!fs.existsSync(path.join(distDir, 'sw-cache-manager.js'))) {
fail('dist/sw-cache-manager.js 不存在。');
}
if (errors.length > 0) {
console.error('PWA 构建校验失败:');
for (const error of errors) {
console.error(`- ${error}`);
}
process.exit(1);
}
console.log('PWA 构建校验通过。');