206 lines
6.6 KiB
JavaScript
206 lines
6.6 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════
|
|||
|
|
* 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001
|
|||
|
|
* 📜 Copyright: 国作登字-2026-A-00037559
|
|||
|
|
* ═══════════════════════════════════════════════
|
|||
|
|
*
|
|||
|
|
* 铸渊 · 密钥配置验证器
|
|||
|
|
* scripts/secrets-validator.js
|
|||
|
|
*
|
|||
|
|
* 功能:验证所有必需的 GitHub Secrets 是否已配置
|
|||
|
|
* 用法:node scripts/secrets-validator.js [--check] [--report]
|
|||
|
|
*
|
|||
|
|
* 指令:SY-CMD-KEY-012 · 密钥全量清理与统一替换
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
// ━━━ 加载密钥清单 ━━━
|
|||
|
|
const manifestPath = path.join(__dirname, '..', 'brain', 'secrets-manifest.json');
|
|||
|
|
let manifest;
|
|||
|
|
try {
|
|||
|
|
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('❌ 无法加载密钥清单: brain/secrets-manifest.json');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 从环境变量检查密钥状态 ━━━
|
|||
|
|
function checkSecrets() {
|
|||
|
|
const results = {
|
|||
|
|
configured: [],
|
|||
|
|
missing: [],
|
|||
|
|
optional_configured: [],
|
|||
|
|
optional_missing: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 检查必需密钥
|
|||
|
|
for (const secret of manifest.secrets.required) {
|
|||
|
|
const envName = secret.name;
|
|||
|
|
if (process.env[envName] && process.env[envName].length > 0) {
|
|||
|
|
results.configured.push(secret);
|
|||
|
|
} else {
|
|||
|
|
results.missing.push(secret);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查可选密钥
|
|||
|
|
for (const secret of manifest.secrets.optional) {
|
|||
|
|
const envName = secret.name;
|
|||
|
|
if (process.env[envName] && process.env[envName].length > 0) {
|
|||
|
|
results.optional_configured.push(secret);
|
|||
|
|
} else {
|
|||
|
|
results.optional_missing.push(secret);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return results;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 扫描工作流文件验证密钥引用一致性 ━━━
|
|||
|
|
function scanWorkflows() {
|
|||
|
|
const workflowDir = path.join(__dirname, '..', '.github', 'workflows');
|
|||
|
|
const issues = [];
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(workflowDir)) {
|
|||
|
|
return { issues: ['工作流目录不存在'], stats: {} };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const files = fs.readdirSync(workflowDir).filter(f => f.endsWith('.yml'));
|
|||
|
|
const secretRefs = new Map();
|
|||
|
|
let oldSecretRefs = [];
|
|||
|
|
|
|||
|
|
// 旧密钥名称列表(不应再出现)
|
|||
|
|
const oldSecretNames = Object.keys(manifest.migration_mapping.mappings);
|
|||
|
|
|
|||
|
|
for (const file of files) {
|
|||
|
|
const content = fs.readFileSync(path.join(workflowDir, file), 'utf8');
|
|||
|
|
const matches = content.match(/secrets\.([A-Z_0-9]+)/g) || [];
|
|||
|
|
|
|||
|
|
for (const match of matches) {
|
|||
|
|
const secretName = match.replace('secrets.', '');
|
|||
|
|
|
|||
|
|
// 跳过 GITHUB_TOKEN(自动提供)
|
|||
|
|
if (secretName === 'GITHUB_TOKEN') continue;
|
|||
|
|
|
|||
|
|
if (!secretRefs.has(secretName)) {
|
|||
|
|
secretRefs.set(secretName, []);
|
|||
|
|
}
|
|||
|
|
secretRefs.get(secretName).push(file);
|
|||
|
|
|
|||
|
|
// 检查是否使用了旧密钥名称
|
|||
|
|
if (oldSecretNames.includes(secretName)) {
|
|||
|
|
oldSecretRefs.push({ secret: secretName, file, newName: manifest.migration_mapping.mappings[secretName] });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (oldSecretRefs.length > 0) {
|
|||
|
|
issues.push(`⚠️ 发现 ${oldSecretRefs.length} 处旧密钥名称引用未迁移`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
issues,
|
|||
|
|
oldSecretRefs,
|
|||
|
|
stats: {
|
|||
|
|
total_workflows: files.length,
|
|||
|
|
unique_secrets: secretRefs.size,
|
|||
|
|
secret_refs: Object.fromEntries(secretRefs)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 生成报告 ━━━
|
|||
|
|
function generateReport(mode) {
|
|||
|
|
const envCheck = checkSecrets();
|
|||
|
|
const workflowScan = scanWorkflows();
|
|||
|
|
|
|||
|
|
if (mode === 'check') {
|
|||
|
|
// CI模式:仅检查工作流引用是否已迁移到ZY_*
|
|||
|
|
console.log('🔍 铸渊密钥迁移验证');
|
|||
|
|
console.log('═'.repeat(50));
|
|||
|
|
|
|||
|
|
if (workflowScan.oldSecretRefs && workflowScan.oldSecretRefs.length > 0) {
|
|||
|
|
console.log(`\n❌ 发现 ${workflowScan.oldSecretRefs.length} 处旧密钥引用:`);
|
|||
|
|
for (const ref of workflowScan.oldSecretRefs) {
|
|||
|
|
console.log(` ${ref.file}: ${ref.secret} → 应改为 ${ref.newName}`);
|
|||
|
|
}
|
|||
|
|
process.exit(1);
|
|||
|
|
} else {
|
|||
|
|
console.log('\n✅ 所有工作流密钥引用已迁移到 ZY_* 命名体系');
|
|||
|
|
console.log(` 工作流总数: ${workflowScan.stats.total_workflows}`);
|
|||
|
|
console.log(` 唯一密钥数: ${workflowScan.stats.unique_secrets}`);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 完整报告
|
|||
|
|
console.log('');
|
|||
|
|
console.log('🔺 铸渊密钥配置验证报告');
|
|||
|
|
console.log('═'.repeat(60));
|
|||
|
|
console.log(`📋 密钥清单版本: ${manifest._meta.manifest_id}`);
|
|||
|
|
console.log(`📅 清单创建时间: ${manifest._meta.created}`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// 必需密钥状态
|
|||
|
|
console.log('━━━ 必需密钥 (Required) ━━━');
|
|||
|
|
console.log(` ✅ 已配置: ${envCheck.configured.length}/${manifest.secrets.required.length}`);
|
|||
|
|
console.log(` ❌ 未配置: ${envCheck.missing.length}/${manifest.secrets.required.length}`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
if (envCheck.missing.length > 0) {
|
|||
|
|
console.log(' 未配置的必需密钥:');
|
|||
|
|
for (const s of envCheck.missing) {
|
|||
|
|
console.log(` ❌ ${s.name} — ${s.description}`);
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (envCheck.configured.length > 0) {
|
|||
|
|
console.log(' 已配置的必需密钥:');
|
|||
|
|
for (const s of envCheck.configured) {
|
|||
|
|
console.log(` ✅ ${s.name}`);
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 可选密钥状态
|
|||
|
|
console.log('━━━ 可选密钥 (Optional) ━━━');
|
|||
|
|
console.log(` ✅ 已配置: ${envCheck.optional_configured.length}/${manifest.secrets.optional.length}`);
|
|||
|
|
console.log(` ⬜ 未配置: ${envCheck.optional_missing.length}/${manifest.secrets.optional.length}`);
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// 工作流扫描
|
|||
|
|
console.log('━━━ 工作流密钥引用扫描 ━━━');
|
|||
|
|
console.log(` 工作流总数: ${workflowScan.stats.total_workflows}`);
|
|||
|
|
console.log(` 唯一密钥数: ${workflowScan.stats.unique_secrets}`);
|
|||
|
|
|
|||
|
|
if (workflowScan.issues.length > 0) {
|
|||
|
|
for (const issue of workflowScan.issues) {
|
|||
|
|
console.log(` ${issue}`);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.log(' ✅ 所有引用已使用 ZY_* 命名');
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
|
|||
|
|
// 总结
|
|||
|
|
const allRequired = envCheck.missing.length === 0;
|
|||
|
|
console.log('━━━ 总结 ━━━');
|
|||
|
|
if (allRequired) {
|
|||
|
|
console.log(' 🟢 所有必需密钥已配置 · 系统可以正常运行');
|
|||
|
|
} else {
|
|||
|
|
console.log(` 🔴 还有 ${envCheck.missing.length} 个必需密钥未配置`);
|
|||
|
|
console.log(' 请参考 brain/secrets-manifest.json 逐一配置');
|
|||
|
|
}
|
|||
|
|
console.log('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 主程序 ━━━
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
const mode = args.includes('--check') ? 'check' : 'report';
|
|||
|
|
|
|||
|
|
generateReport(mode);
|