85 lines
3.8 KiB
JavaScript
85 lines
3.8 KiB
JavaScript
import { readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const source = readFileSync(resolve(root, 'src/api/resources.ts'), 'utf8');
|
|
const routes = readFileSync(resolve(root, 'src/router/routes/modules/platform.ts'), 'utf8');
|
|
const searchSource = readFileSync(
|
|
resolve(root, 'src/views/shared/use-resource-list-search.ts'),
|
|
'utf8',
|
|
);
|
|
const listPageSource = readFileSync(
|
|
resolve(root, 'src/views/shared/CrudListPage.vue'),
|
|
'utf8',
|
|
);
|
|
const fieldFormSource = readFileSync(
|
|
resolve(root, 'src/views/resource/ResourceFieldForm.vue'),
|
|
'utf8',
|
|
);
|
|
const contract = JSON.parse(
|
|
readFileSync(resolve(root, 'src/contracts/gas-resources.json'), 'utf8'),
|
|
);
|
|
const backend = new Map(contract.resources.map((item) => [item.name, item]));
|
|
const gasSource = source.slice(source.indexOf('const gasOverrides'));
|
|
const frontendDefinitions = [
|
|
...gasSource.matchAll(/define\('([^']+)',\s*'[^']+',\s*'([^']+)'/g),
|
|
].map((match) => ({ name: match[1], mode: match[2] }));
|
|
const frontendNames = frontendDefinitions.map((item) => item.name);
|
|
const embeddedResources = new Set();
|
|
|
|
for (const name of frontendNames) {
|
|
const item = backend.get(name);
|
|
if (!item) throw new Error(`前端配置了后端不存在的资源:${name}`);
|
|
if (item.path !== `/${name}`) throw new Error(`资源路径不一致:${name}`);
|
|
}
|
|
for (const item of contract.resources) {
|
|
if (!frontendNames.includes(item.name))
|
|
throw new Error(`缺少后端资源配置:${item.name}`);
|
|
const frontend = frontendDefinitions.find(
|
|
(definition) => definition.name === item.name,
|
|
);
|
|
if (frontend?.mode !== item.mode)
|
|
throw new Error(
|
|
`资源模式不一致:${item.name},后端=${item.mode},前端=${frontend?.mode}`,
|
|
);
|
|
if (!embeddedResources.has(item.name) && !routes.includes(`'/${item.name}'`))
|
|
throw new Error(`缺少后端资源路由:${item.name}`);
|
|
}
|
|
|
|
const staffSearchFields = backend.get('staff_account')?.searchFields ?? [];
|
|
if (staffSearchFields.map((field) => field.key).join(',') !== 'username,role_code')
|
|
throw new Error('工作人员搜索契约必须明确为:用户名、角色');
|
|
if ((backend.get('user_address')?.searchFields ?? []).length > 0)
|
|
throw new Error('不支持模糊搜索的用户地址资源不得公开搜索字段');
|
|
if (!searchSource.includes('searchableFields.value.length > 0'))
|
|
throw new Error('列表搜索必须由资源搜索契约决定是否显示');
|
|
if (searchSource.includes('关键字段模糊搜索'))
|
|
throw new Error('列表搜索不得使用含义不明确的兜底提示');
|
|
if (!listPageSource.includes('<a-form v-if="searchEnabled"'))
|
|
throw new Error('不支持搜索的资源必须隐藏完整搜索区域');
|
|
if (!source.includes("defaultValue: 'bank'"))
|
|
throw new Error('提现申请必须默认选择银行卡提现');
|
|
for (const option of [
|
|
"{ label: '银行卡提现', value: 'bank' }",
|
|
"{ label: '支付宝提现', value: 'alipay', disabled: true }",
|
|
"{ label: '微信提现', value: 'wechat', disabled: true }",
|
|
]) {
|
|
if (!source.includes(option)) throw new Error(`提现渠道配置缺失:${option}`);
|
|
}
|
|
if (!fieldFormSource.includes(':disabled="option.disabled"'))
|
|
throw new Error('未开通的提现渠道必须在下拉框中禁用');
|
|
|
|
const forbidden = [
|
|
'/platform/platform_', '/gas/gas_', '/delivery/delivery_', '/staff/',
|
|
'/user/', '/ec/ec_', '/finance/fin_', '/wallet/wallet',
|
|
'delivery_task', 'delivery_track', 'delivery_track_point',
|
|
'dev_device_binding', 'dev_smart_cylinder_valve', 'dev_telemetry',
|
|
'wallet_ledger', 'wallet_recharge', 'wallet_withdrawal',
|
|
];
|
|
for (const value of forbidden) {
|
|
if (source.includes(value) || routes.includes(value))
|
|
throw new Error(`仍存在旧资源或旧路径:${value}`);
|
|
}
|
|
console.log(`契约检查通过:${frontendNames.length} 个资源`);
|