guanghulab/glada/code-generator.js

211 lines
5.8 KiB
JavaScript
Raw Normal View History

/**
* GLADA · 代码生成器 + 回归防护 · code-generator.js
*
* 解决"修了这个坏那个"的问题
* 1. 修改前自动拍摄所有相关文件的快照
* 2. 修改后自动运行现有测试
* 3. 测试失败则自动回滚并尝试其他方案
* 4. 记录所有文件的依赖关系确保修改不破坏依赖方
*
* 版权国作登字-2026-A-00037559
* 签发铸渊 · ICE-GL-ZY001
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const ROOT = path.resolve(__dirname, '..');
/**
* 查找文件的所有被引用方谁依赖了这个文件
* @param {string} targetRelPath - 被修改的文件相对路径
* @returns {string[]} 引用了该文件的文件列表
*/
function findDependents(targetRelPath) {
const dependents = [];
const normalizedTarget = targetRelPath.replace(/\.js$/, '');
// 使用 grep 搜索引用sanitize 输入防止 shell 注入)
try {
const safeBasename = path.basename(normalizedTarget).replace(/[^A-Za-z0-9._-]/g, '');
if (!safeBasename) return dependents;
const grepResult = execSync(
`grep -rl "${safeBasename}" --include="*.js" "${ROOT}" 2>/dev/null || true`,
{ encoding: 'utf-8', timeout: 15000 }
);
const lines = grepResult.trim().split('\n').filter(Boolean);
for (const line of lines) {
const relPath = path.relative(ROOT, line);
// 排除 node_modules 和自身
if (!relPath.startsWith('node_modules') && relPath !== targetRelPath) {
dependents.push(relPath);
}
}
} catch {
// grep 失败不影响主流程
}
return dependents.slice(0, 20); // 限制数量
}
/**
* 回归防护检查
* 验证修改后的文件不影响其依赖方
* @param {string[]} changedFiles - 已修改的文件列表
* @returns {{ safe: boolean, issues: string[] }}
*/
function regressionCheck(changedFiles) {
const issues = [];
for (const filePath of changedFiles) {
const dependents = findDependents(filePath);
if (dependents.length > 0) {
// 检查依赖方是否能正常 require/import
for (const dep of dependents.slice(0, 5)) {
const absPath = path.resolve(ROOT, dep);
try {
// 简单的语法检查
execSync(`node --check "${absPath}" 2>&1`, {
encoding: 'utf-8',
timeout: 5000
});
} catch (err) {
issues.push(`${dep} 语法检查失败 (依赖 ${filePath}): ${err.message.substring(0, 200)}`);
}
}
}
}
return {
safe: issues.length === 0,
issues
};
}
/**
* 带回归防护的代码变更执行器
* @param {Object[]} files - 文件变更列表
* @param {Object} constraints - 约束条件
* @param {Object} [options] - 选项
* @returns {Promise<Object>} 执行结果
*/
async function executeWithRegressionGuard(files, constraints, options = {}) {
const result = {
success: false,
applied: [],
rolled_back: false,
regression_check: null,
test_result: null,
errors: []
};
// 1. 收集所有要修改的文件路径
const filePaths = files.map(f => f.path);
// 2. 拍摄快照
const snapshot = new Map();
for (const relPath of filePaths) {
const absPath = path.resolve(ROOT, relPath);
if (fs.existsSync(absPath)) {
snapshot.set(relPath, fs.readFileSync(absPath, 'utf-8'));
} else {
snapshot.set(relPath, null);
}
}
// 3. 检查约束
const noTouch = constraints?.no_touch_files || [];
const safeFiles = files.filter(f => {
const blocked = noTouch.some(nt => f.path.startsWith(nt));
if (blocked) {
result.errors.push(`跳过受保护文件: ${f.path}`);
}
return !blocked;
});
// 4. 应用变更
for (const file of safeFiles) {
const absPath = path.resolve(ROOT, file.path);
try {
if (file.action === 'delete') {
if (fs.existsSync(absPath)) fs.unlinkSync(absPath);
} else {
fs.mkdirSync(path.dirname(absPath), { recursive: true });
fs.writeFileSync(absPath, file.content, 'utf-8');
}
result.applied.push(file.path);
} catch (err) {
result.errors.push(`${file.action} ${file.path}: ${err.message}`);
}
}
// 5. 回归防护检查
if (result.applied.length > 0) {
result.regression_check = regressionCheck(result.applied);
if (!result.regression_check.safe && options.rollbackOnRegression !== false) {
console.warn('[GLADA-CodeGen] ⚠️ 回归检查发现问题,回滚...');
rollback(snapshot);
result.rolled_back = true;
result.applied = [];
return result;
}
}
// 6. 运行测试
if (options.runTests !== false) {
try {
execSync('npm run test:smoke 2>&1', {
cwd: ROOT,
encoding: 'utf-8',
timeout: 60000
});
result.test_result = { success: true };
} catch (err) {
result.test_result = {
success: false,
output: (err.stdout || err.message || '').substring(0, 500)
};
if (options.rollbackOnTestFail !== false) {
console.warn('[GLADA-CodeGen] ❌ 测试失败,回滚...');
rollback(snapshot);
result.rolled_back = true;
result.applied = [];
return result;
}
}
}
result.success = result.applied.length > 0 && !result.rolled_back;
return result;
}
/**
* 从快照回滚
* @param {Map<string, string|null>} snapshot
*/
function rollback(snapshot) {
for (const [relPath, content] of snapshot) {
const absPath = path.resolve(ROOT, relPath);
if (content === null) {
if (fs.existsSync(absPath)) fs.unlinkSync(absPath);
} else {
fs.mkdirSync(path.dirname(absPath), { recursive: true });
fs.writeFileSync(absPath, content, 'utf-8');
}
}
}
module.exports = {
findDependents,
regressionCheck,
executeWithRegressionGuard,
rollback
};