R74-A2 A3 A4 压榨试运行自动化空间

这个提交包含在:
2026-04-28 22:06:15 +08:00
父节点 2370a7745e
当前提交 5eeeebcdf7
共修改 13 个文件,包含 611 行新增和 40 行删除
+115 -22
查看文件
@@ -11,37 +11,144 @@ const { resolvePermissionsForRoles, hasPermission } = require('../constants/perm
const REQUIRED_ENV_VALUES = {
PORTAL_HOME_ENABLED: 'true',
GLOBAL_SEARCH_ENABLED: 'true',
ANNOUNCEMENTS_ENABLED: 'true',
PRESENCE_BOARD_ENABLED: 'true',
ATTENDANCE_ENABLED: 'true',
WEEKLY_REPORT_ENABLED: 'true',
EMPLOYEE_LIFECYCLE_ENABLED: 'true',
TODO_BOARD_ENABLED: 'true',
PROJECT_DOMAIN_ENABLED: 'true',
PROJECT_LINKING_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.globalSearch,
FEATURE_FLAG_KEYS.announcements,
FEATURE_FLAG_KEYS.presenceBoard,
FEATURE_FLAG_KEYS.attendance,
FEATURE_FLAG_KEYS.weeklyReport,
FEATURE_FLAG_KEYS.employeeLifecycle,
FEATURE_FLAG_KEYS.todoBoard,
FEATURE_FLAG_KEYS.projectDomain,
FEATURE_FLAG_KEYS.projectLinking,
FEATURE_FLAG_KEYS.projectCommunicationLog,
FEATURE_FLAG_KEYS.projectClosureChecklist,
];
const REQUIRED_MAIN_NAV_KEYS = [
'portal',
'team',
'leaveRequests',
'reimbursements',
'assets',
'weeklyReports',
'todos',
'announcements',
];
const REQUIRED_ADMIN_NAV_KEYS = [
'workspace',
'people',
'systemSettings',
'featureFlags',
'auditLogs',
];
const REQUIRED_WORKSPACE_VISIBILITY = {
portal: 'main',
team: 'main',
leaveRequests: 'main',
reimbursements: 'main',
assets: 'main',
weeklyReports: 'main',
todos: 'main',
announcements: 'main',
people: 'admin',
workspace: 'admin',
systemSettings: 'admin',
featureFlags: 'admin',
auditLogs: 'admin',
};
function assertCondition(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function resolveRuntimeMode() {
const explicit = String(process.env.TRIALRUN_RUNTIME_MODE ?? '').trim().toLowerCase();
if (explicit === 'local' || explicit === 'deploy') {
return explicit;
}
const appOrigin = String(process.env.APP_ORIGIN ?? '').trim();
if (appOrigin.startsWith('https://') || process.env.COOKIE_SECURE === 'true') {
return 'deploy';
}
return 'local';
}
function assertEnvValues(runtimeMode) {
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}`);
}
const appOrigin = String(process.env.APP_ORIGIN ?? '').trim();
if (runtimeMode === 'deploy') {
assertCondition(appOrigin.length > 0, 'APP_ORIGIN 不能为空');
assertCondition(appOrigin.startsWith('https://'), `deploy 模式下 APP_ORIGIN 必须为 https,实际为 ${appOrigin}`);
assertCondition(process.env.COOKIE_SECURE === 'true', `deploy 模式下 COOKIE_SECURE 期望为 true,实际为 ${process.env.COOKIE_SECURE ?? '<empty>'}`);
console.log(`[PASS] APP_ORIGIN=${appOrigin}`);
console.log('[PASS] COOKIE_SECURE=true');
return;
}
if (appOrigin) {
assertCondition(/^https?:\/\//.test(appOrigin), `local 模式下 APP_ORIGIN 如提供,必须为 http/https,实际为 ${appOrigin}`);
console.log(`[PASS] APP_ORIGIN=${appOrigin}`);
} else {
console.log('[PASS] APP_ORIGIN=<empty>(local 模式允许)');
}
const cookieSecure = process.env.COOKIE_SECURE;
assertCondition(cookieSecure == null || cookieSecure === '' || cookieSecure === 'false', `local 模式下 COOKIE_SECURE 应为空或 false,实际为 ${cookieSecure ?? '<empty>'}`);
console.log(`[PASS] COOKIE_SECURE=${cookieSecure ?? '<empty>'}(local 模式允许)`);
}
function assertWorkspaceSetting(workspace) {
assertCondition(Boolean(workspace), 'WorkspaceSetting 读取失败');
console.log(`[PASS] 工作区设置可读,profile=${workspace.profile}`);
for (const [key, expected] of Object.entries(REQUIRED_WORKSPACE_VISIBILITY)) {
const actual = workspace.moduleVisibility?.[key];
assertCondition(actual === expected, `workspace.moduleVisibility.${key} 期望为 ${expected},实际为 ${actual ?? '<empty>'}`);
}
console.log(`[PASS] 工作区模块显隐符合 trialrun 基线,共校验 ${Object.keys(REQUIRED_WORKSPACE_VISIBILITY).length} 项`);
}
function assertNavigation(navigation) {
assertCondition(Array.isArray(navigation.sections?.main), '系统导航主区块生成失败');
assertCondition(Array.isArray(navigation.sections?.admin), '系统导航管理区块生成失败');
const mainKeys = new Set((navigation.sections.main ?? []).map((item) => item.key));
const adminKeys = new Set((navigation.sections.admin ?? []).map((item) => item.key));
for (const key of REQUIRED_MAIN_NAV_KEYS) {
assertCondition(mainKeys.has(key), `系统导航 main 缺少 ${key} 入口`);
}
for (const key of REQUIRED_ADMIN_NAV_KEYS) {
assertCondition(adminKeys.has(key), `系统导航 admin 缺少 ${key} 入口`);
}
console.log(`[PASS] 导航构建成功,main=${navigation.sections.main.length} admin=${navigation.sections.admin.length}`);
}
async function main() {
const config = getConfig();
const { prisma, pool } = createPrismaClient();
@@ -49,21 +156,13 @@ async function main() {
try {
console.log('[R26] 开始 trialrun runtime smoke');
const runtimeMode = resolveRuntimeMode();
console.log(`[R26] runtimeMode=${runtimeMode}`);
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} 已配置`);
}
assertEnvValues(runtimeMode);
const adminEmail = (process.env.TRIALRUN_ADMIN_EMAIL || '[email protected]').trim();
const adminEmployee = await prisma.employee.findFirst({
@@ -83,8 +182,7 @@ async function main() {
console.log(`[PASS] 已找到 active admin 员工:${adminEmployee.email}`);
const workspace = await getWorkspaceSetting(prisma);
assertCondition(Boolean(workspace), 'WorkspaceSetting 读取失败');
console.log(`[PASS] 工作区设置可读,profile=${workspace.profile}`);
assertWorkspaceSetting(workspace);
const employeeForGate = {
...adminEmployee,
@@ -115,12 +213,7 @@ async function main() {
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}`);
assertNavigation(navigation);
console.log('[R26][PASS] trialrun runtime smoke 通过');
} finally {