mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
- 从 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 文件的引用和服务逻辑
249 lines
5.8 KiB
JavaScript
249 lines
5.8 KiB
JavaScript
#!/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();
|