R26-1 R26-2 R26-3: 修复 env 注入并补部署校验与 smoke

这个提交包含在:
2026-03-22 18:36:30 +08:00
父节点 073aa4f730
当前提交 25c045aa51
共修改 6 个文件,包含 514 行新增和 228 行删除
@@ -0,0 +1,136 @@
require('dotenv/config');
const { createPrismaClient } = require('../prismaClient');
const { getConfig } = require('../config');
const {
FEATURE_FLAG_KEYS,
evaluateFeatureFlag,
} = require('../services/featureFlags');
const { getWorkspaceSetting, getSystemNavigation } = require('../services/workspaceSettings');
const { resolvePermissionsForRoles, hasPermission } = require('../constants/permissions');
const REQUIRED_ENV_VALUES = {
PORTAL_HOME_ENABLED: 'true',
ANNOUNCEMENTS_ENABLED: 'true',
PRESENCE_BOARD_ENABLED: 'true',
WEEKLY_REPORT_ENABLED: 'true',
EMPLOYEE_LIFECYCLE_ENABLED: 'true',
TODO_BOARD_ENABLED: 'true',
PROJECT_DOMAIN_ENABLED: 'true',
PROJECT_COMMUNICATION_LOG_ENABLED: 'true',
PROJECT_CLOSURE_CHECKLIST_ENABLED: 'true',
COOKIE_SECURE: 'true',
};
const REQUIRED_NON_EMPTY_ENV_KEYS = ['APP_ORIGIN'];
const REQUIRED_FLAGS = [
FEATURE_FLAG_KEYS.portalHome,
FEATURE_FLAG_KEYS.announcements,
FEATURE_FLAG_KEYS.presenceBoard,
FEATURE_FLAG_KEYS.weeklyReport,
FEATURE_FLAG_KEYS.employeeLifecycle,
FEATURE_FLAG_KEYS.todoBoard,
FEATURE_FLAG_KEYS.projectDomain,
FEATURE_FLAG_KEYS.projectCommunicationLog,
FEATURE_FLAG_KEYS.projectClosureChecklist,
];
function assertCondition(condition, message) {
if (!condition) {
throw new Error(message);
}
}
async function main() {
const config = getConfig();
const { prisma, pool } = createPrismaClient();
await prisma.$connect();
try {
console.log('[R26] 开始 trialrun runtime smoke');
await prisma.$queryRaw`SELECT 1`;
console.log('[PASS] Prisma 连接正常');
for (const [key, expected] of Object.entries(REQUIRED_ENV_VALUES)) {
const actual = process.env[key];
assertCondition(actual === expected, `${key} 期望为 ${expected},实际为 ${actual ?? '<empty>'}`);
console.log(`[PASS] ${key}=${actual}`);
}
for (const key of REQUIRED_NON_EMPTY_ENV_KEYS) {
const actual = process.env[key];
assertCondition(typeof actual === 'string' && actual.trim().length > 0, `${key} 不能为空`);
console.log(`[PASS] ${key} 已配置`);
}
const adminEmail = (process.env.TRIALRUN_ADMIN_EMAIL || '[email protected]').trim();
const adminEmployee = await prisma.employee.findFirst({
where: { email: adminEmail },
select: {
id: true,
name: true,
email: true,
status: true,
department_id: true,
roles: { select: { role_code: true } },
},
});
assertCondition(Boolean(adminEmployee), `未找到 trialrun admin 员工:${adminEmail}`);
assertCondition(adminEmployee.status === 'active', `trialrun admin 状态异常:${adminEmployee.status}`);
console.log(`[PASS] 已找到 active admin 员工:${adminEmployee.email}`);
const workspace = await getWorkspaceSetting(prisma);
assertCondition(Boolean(workspace), 'WorkspaceSetting 读取失败');
console.log(`[PASS] 工作区设置可读,profile=${workspace.profile}`);
const employeeForGate = {
...adminEmployee,
permissionCodes: resolvePermissionsForRoles(adminEmployee.roles.map((item) => item.role_code)),
roleCodes: adminEmployee.roles.map((item) => item.role_code),
};
for (const flagKey of REQUIRED_FLAGS) {
const result = await evaluateFeatureFlag({
prisma,
config,
flagKey,
departmentId: employeeForGate.department_id ?? null,
strictDepartment: false,
});
assertCondition(
result.enabled,
`${flagKey} 求值失败:enabled=${result.enabled} reason=${result.reason} envEnabled=${result.envEnabled}`,
);
console.log(`[PASS] ${flagKey} => enabled (${result.reason})`);
}
const navigation = await getSystemNavigation({
prisma,
config,
employee: employeeForGate,
hasPermission: (employee, permissionCode) => hasPermission(employee.permissionCodes ?? [], permissionCode),
});
assertCondition(Array.isArray(navigation.sections?.main), '系统导航主区块生成失败');
assertCondition(
navigation.sections.main.some((item) => item.key === 'portal'),
'系统导航未生成 portal 入口',
);
console.log(`[PASS] 导航构建成功,main=${navigation.sections.main.length} admin=${navigation.sections.admin.length}`);
console.log('[R26][PASS] trialrun runtime smoke 通过');
} finally {
await prisma.$disconnect();
await pool.end();
}
}
main().catch((error) => {
console.error('[R26][FAIL] trialrun runtime smoke 失败');
console.error(error instanceof Error ? error.message : error);
process.exit(1);
});