Files
eSIM-Tools/scripts/pre-commit-check.js
Abner be2c4e4dfb feat: 移除遗留页面及相关配置,新增运行时公共函数库
- 从 netlify.toml 中移除 `/giffgaff-legacy` 和 `/simyo-legacy` 重定向规则,清理不再使用的旧版页面路由
- 新增 runtime-common.sh 文件,提供跨语言(中英文)共享的运行时函数,包括 JSON 转义、安全检测、系统信息缓存等核心功能
- 更新 legacy-freeze.json,清空冻结文件列表,反映不再需要保护旧版页面文件的状态
- 修改多个脚本文件(pre-commit-check.js、pre-push-check.js、security-check.js),移除对已弃用页面的保护逻辑
- 调整服务入口文件(server.js、simyo_proxy_server.js)和 Service Worker(sw.js),删除对旧版 HTML 文件的引用和服务逻辑
2026-05-07 22:48:34 +08:00

249 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
#!/usr/bin/env node
/**
* pre-commit 检查:
* 1) 仅处理 staged 文件安全格式化LF、行尾空白、文件尾换行
* 2) 检查 staged JS 语法;
* 3) 检查 staged JSON 语法;
* 4) 拦截代码文件中的智能引号,避免引号边界问题;
* 5) 保护 legacy 完整版页面,禁止提交其变更。
*/
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync, spawnSync } = require('child_process');
let esbuild = null;
try {
esbuild = require('esbuild');
} catch (_) {
esbuild = null;
}
const TEXT_EXTENSIONS = new Set([
'.js',
'.mjs',
'.cjs',
'.json',
'.md',
'.css',
'.html',
'.yml',
'.yaml',
'.sh'
]);
const JS_EXTENSIONS = new Set(['.js', '.mjs', '.cjs']);
const SMART_QUOTES_RE = /[\u2018\u2019\u201C\u201D]/u;
const PROTECTED_FILES = [];
function git(args, options = {}) {
return execFileSync('git', args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
...options
});
}
function getStagedFiles() {
const output = git([
'diff',
'--cached',
'--name-only',
'--diff-filter=ACMR',
'-z'
]);
return output.split('\0').filter(Boolean);
}
function getTouchedProtectedFiles() {
const output = git([
'diff',
'--cached',
'--name-only',
'-z',
'--',
...PROTECTED_FILES
]);
return output.split('\0').filter(Boolean);
}
function getUpstreamRef() {
try {
return git([
'rev-parse',
'--abbrev-ref',
'--symbolic-full-name',
'@{u}'
]).trim();
} catch (_) {
return '';
}
}
function syncWithRemote() {
const upstreamRef = getUpstreamRef();
if (!upstreamRef) {
console.log(' 当前分支未配置 upstream跳过远程同步');
return;
}
console.log(`🔄 提交前同步远程更新(${upstreamRef}...`);
try {
git(['pull', '--rebase', '--autostash', '--quiet'], {
stdio: 'inherit'
});
console.log('✅ 远程更新同步完成');
} catch (_) {
console.error('\n❌ pre-commit 远程同步失败:');
console.error(' - 请先手动执行 `git pull --rebase` 解决冲突后再提交。');
process.exit(1);
}
}
function shouldFormat(relPath) {
const ext = path.extname(relPath).toLowerCase();
if (TEXT_EXTENSIONS.has(ext)) {
return true;
}
const baseName = path.basename(relPath);
return baseName === '.gitignore' || baseName === '.gitattributes';
}
function normalizeText(content) {
let next = content.replace(/\r\n/g, '\n');
next = next.replace(/[ \t]+$/gm, '');
if (next.length > 0 && !next.endsWith('\n')) {
next += '\n';
}
return next;
}
function formatStagedFiles(stagedFiles) {
const updated = [];
for (const relPath of stagedFiles) {
if (!shouldFormat(relPath)) {
continue;
}
const absPath = path.resolve(relPath);
if (!fs.existsSync(absPath)) {
continue;
}
const original = fs.readFileSync(absPath, 'utf8');
const normalized = normalizeText(original);
if (normalized !== original) {
fs.writeFileSync(absPath, normalized, 'utf8');
git(['add', '--', relPath]);
updated.push(relPath);
}
}
return updated;
}
function readStagedContent(relPath) {
return git(['show', `:${relPath}`]);
}
function fallbackNodeSyntaxCheck(relPath, content) {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'esim-precommit-'));
const ext = path.extname(relPath).toLowerCase() || '.js';
const tempFile = path.join(tempDir, `check${ext}`);
try {
fs.writeFileSync(tempFile, content, 'utf8');
const result = spawnSync(process.execPath, ['--check', tempFile], {
encoding: 'utf8'
});
if (result.status !== 0) {
throw new Error((result.stderr || result.stdout || '').trim());
}
} finally {
fs.rmSync(tempDir, { recursive: true, force: true });
}
}
function checkJavaScriptSyntax(relPath, content) {
if (esbuild) {
esbuild.transformSync(content, {
loader: 'js',
sourcefile: relPath,
charset: 'utf8'
});
return;
}
fallbackNodeSyntaxCheck(relPath, content);
}
function runChecks(stagedFiles) {
const failures = [];
for (const relPath of stagedFiles) {
const ext = path.extname(relPath).toLowerCase();
if (!JS_EXTENSIONS.has(ext) && ext !== '.json') {
continue;
}
const content = readStagedContent(relPath);
if (SMART_QUOTES_RE.test(content)) {
failures.push(
`${relPath}: 检测到智能引号U+201C/U+201D/U+2018/U+2019请改为标准半角引号。`
);
continue;
}
try {
if (JS_EXTENSIONS.has(ext)) {
checkJavaScriptSyntax(relPath, content);
} else if (ext === '.json') {
JSON.parse(content);
}
} catch (error) {
failures.push(`${relPath}: ${error.message}`);
}
}
return failures;
}
function main() {
const touchedProtectedFiles = PROTECTED_FILES.length > 0 ? getTouchedProtectedFiles() : [];
if (touchedProtectedFiles.length > 0) {
console.error('\n❌ pre-commit 检查失败:');
for (const relPath of touchedProtectedFiles) {
console.error(` - ${relPath}: 该文件受保护,禁止提交改动。`);
}
console.error(' - 如确需调整,请改为维护对应 modular 版本文件。');
process.exit(1);
}
syncWithRemote();
const stagedFiles = getStagedFiles();
if (stagedFiles.length === 0) {
process.exit(0);
}
const formatted = formatStagedFiles(stagedFiles);
if (formatted.length > 0) {
console.log(`✅ 已格式化并重新暂存 ${formatted.length} 个文件`);
}
const failures = runChecks(stagedFiles);
if (failures.length > 0) {
console.error('\n❌ pre-commit 检查失败:');
for (const failure of failures) {
console.error(` - ${failure}`);
}
process.exit(1);
}
console.log('✅ pre-commit 检查通过');
}
main();