#!/usr/bin/env node /** * 混淆构建脚本 * 在 Next.js 构建后对敏感代码进行混淆 * * 使用方法: * 1. npm run build * 2. node scripts/build-obfuscated.js * * 或者使用组合命令: * npm run build:prod */ const fs = require('fs'); const path = require('path'); const { glob } = require('glob'); // 检查是否安装了 javascript-obfuscator let JavaScriptObfuscator; try { JavaScriptObfuscator = require('javascript-obfuscator'); } catch { console.error('❌ javascript-obfuscator 未安装,生产混淆不能跳过'); console.error(' 请先运行: npm install'); process.exit(1); } const { obfuscatorOptions } = require('./obfuscate-config'); // 构建输出目录 const BUILD_DIR = path.join(process.cwd(), '.next'); const STANDALONE_DIR = path.join(BUILD_DIR, 'standalone'); // 需要混淆的文件模式(在构建输出中) const PATTERNS = [ // 服务端 chunks(包含许可证相关代码) 'server/chunks/*.js', // API 路由 'server/app/api/license/**/*.js', // standalone 目录 'standalone/.next/server/chunks/*.js', 'standalone/.next/server/app/api/license/**/*.js', ]; // 排除的文件 const EXCLUDE = [ '**/node_modules/**', '**/*.map', ]; async function main() { console.log('🔒 开始代码混淆...\n'); // 检查构建目录是否存在 if (!fs.existsSync(BUILD_DIR)) { console.error('❌ 构建目录不存在,请先运行 npm run build'); process.exit(1); } let totalFiles = 0; let targetFiles = 0; let obfuscatedFiles = 0; const failedFiles = []; // 查找需要混淆的文件 for (const pattern of PATTERNS) { const files = await glob(pattern, { cwd: BUILD_DIR, ignore: EXCLUDE, absolute: false, }); for (const file of files) { const filePath = path.join(BUILD_DIR, file); // 跳过不存在的文件 if (!fs.existsSync(filePath)) continue; totalFiles++; try { const code = fs.readFileSync(filePath, 'utf-8'); // 跳过太小的文件(可能是空文件或只有导出) if (code.length < 100) continue; targetFiles++; console.log(` 混淆: ${file}`); const result = JavaScriptObfuscator.obfuscate(code, { ...obfuscatorOptions, sourceMap: false, // 生产环境不生成 source map }); fs.writeFileSync(filePath, result.getObfuscatedCode()); obfuscatedFiles++; } catch (error) { failedFiles.push(`${file}: ${error.message}`); console.warn(` ⚠️ 混淆失败 ${file}: ${error.message}`); } } } // 同样处理 standalone 目录(如果存在) if (fs.existsSync(STANDALONE_DIR)) { for (const pattern of PATTERNS) { const files = await glob(pattern, { cwd: STANDALONE_DIR, ignore: EXCLUDE, absolute: false, }); for (const file of files) { const filePath = path.join(STANDALONE_DIR, file); if (!fs.existsSync(filePath)) continue; totalFiles++; try { const code = fs.readFileSync(filePath, 'utf-8'); if (code.length < 100) continue; targetFiles++; console.log(` 混淆 (standalone): ${file}`); const result = JavaScriptObfuscator.obfuscate(code, { ...obfuscatorOptions, sourceMap: false, }); fs.writeFileSync(filePath, result.getObfuscatedCode()); obfuscatedFiles++; } catch (error) { failedFiles.push(`${file}: ${error.message}`); console.warn(` ⚠️ 混淆失败 ${file}: ${error.message}`); } } } } if (targetFiles === 0) { console.error('\n❌ 没有找到需要混淆的目标文件,生产镜像不能继续出包'); process.exit(1); } if (failedFiles.length > 0) { console.error('\n❌ 混淆失败的目标文件:'); for (const failedFile of failedFiles) { console.error(` - ${failedFile}`); } process.exit(1); } console.log(`\n✅ 混淆完成!`); console.log(` 扫描文件: ${totalFiles}`); console.log(` 目标文件: ${targetFiles}`); console.log(` 混淆文件: ${obfuscatedFiles}`); } main().catch((error) => { console.error('❌ 混淆失败:', error); process.exit(1); });