133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const scanDist = process.argv.includes('--dist');
|
|
|
|
const forbiddenPaths = [
|
|
'src/pages/dashboard',
|
|
'src/pages/detail',
|
|
'src/pages/form',
|
|
'src/pages/list',
|
|
'src/pages/user',
|
|
'src/components/common-table',
|
|
'src/components/product-card',
|
|
'src/components/trend',
|
|
'src/api/finance.ts',
|
|
'src/api/list.ts',
|
|
'src/api/detail.ts',
|
|
'src/api/permission.ts',
|
|
'src/api/model/listModel.ts',
|
|
'src/api/model/detailModel.ts',
|
|
'src/api/model/permissionModel.ts',
|
|
'src/utils/route/index.ts',
|
|
'src/assets/assets-empty.svg',
|
|
'src/assets/assets-logo-full.svg',
|
|
'src/assets/assets-t-logo.svg',
|
|
'src/assets/assets-tencent-logo.png',
|
|
'src/assets/assets-product-1.svg',
|
|
'src/assets/assets-product-2.svg',
|
|
'src/assets/assets-product-3.svg',
|
|
'src/assets/assets-product-4.svg',
|
|
'src/assets/assets-setting-auto.svg',
|
|
'src/assets/assets-setting-dark.svg',
|
|
'src/assets/assets-setting-light.svg',
|
|
];
|
|
|
|
const sourceFragments = [
|
|
"import.meta.glob('../../pages/**/*.vue')",
|
|
'/dashboard/base',
|
|
'/detail/base',
|
|
'/list/base',
|
|
'demo-select-base',
|
|
'assets-tencent-logo',
|
|
'assets-product-',
|
|
'TDesign Vue Next Starter',
|
|
'tdesign.gtimg.com/starter/brand-logo',
|
|
];
|
|
|
|
const distFragments = [
|
|
'dashboardBase',
|
|
'dashboardDetail',
|
|
'detailBase',
|
|
'formBase',
|
|
'listBase',
|
|
'demo-select-base',
|
|
'assets-tencent-logo',
|
|
'TDesign Vue Next Starter',
|
|
'tdesign.gtimg.com/starter/brand-logo',
|
|
];
|
|
|
|
async function exists(relativePath) {
|
|
try {
|
|
await stat(path.join(root, relativePath));
|
|
return true;
|
|
} catch (error) {
|
|
if (error?.code === 'ENOENT') return false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function filesUnder(relativePath) {
|
|
const absolutePath = path.join(root, relativePath);
|
|
if (!(await exists(relativePath))) return [];
|
|
const entries = await readdir(absolutePath, { withFileTypes: true });
|
|
const files = [];
|
|
for (const entry of entries) {
|
|
const child = path.join(relativePath, entry.name);
|
|
if (entry.isDirectory()) files.push(...(await filesUnder(child)));
|
|
else if (entry.isFile()) files.push(child);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
async function findFragments(relativeFiles, fragments) {
|
|
const violations = [];
|
|
for (const relativeFile of relativeFiles) {
|
|
const content = (await readFile(path.join(root, relativeFile))).toString('utf8');
|
|
for (const fragment of fragments) {
|
|
if (content.includes(fragment)) violations.push(`${relativeFile}: ${fragment}`);
|
|
}
|
|
}
|
|
return violations;
|
|
}
|
|
|
|
const violations = [];
|
|
for (const relativePath of forbiddenPaths) {
|
|
if (await exists(relativePath)) violations.push(`${relativePath}: forbidden production path`);
|
|
}
|
|
|
|
const mockFiles = await filesUnder('mock');
|
|
for (const file of mockFiles) violations.push(`${file}: Mock fixture is forbidden in production frontend`);
|
|
|
|
const sourceFiles = (await filesUnder('src')).filter((file) =>
|
|
/\.(?:css|html|js|json|jsx|less|ts|tsx|vue)$/.test(file),
|
|
);
|
|
violations.push(...(await findFragments(sourceFiles, sourceFragments)));
|
|
|
|
let distFiles = [];
|
|
if (scanDist) {
|
|
if (!(await exists('dist'))) {
|
|
violations.push('dist: production build output is missing');
|
|
} else {
|
|
distFiles = await filesUnder('dist');
|
|
for (const file of distFiles) {
|
|
if (/assets-(?:empty|logo-full|product-|setting-|t-logo|tencent-logo)/.test(path.basename(file))) {
|
|
violations.push(`${file}: forbidden Starter asset`);
|
|
}
|
|
}
|
|
violations.push(...(await findFragments(distFiles, distFragments)));
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error(`Production hygiene check failed with ${violations.length} violation(s):`);
|
|
for (const violation of violations) console.error(`- ${violation}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(
|
|
`Production hygiene passed: ${sourceFiles.length} source files${scanDist ? ` and ${distFiles.length} dist files` : ''} scanned.`,
|
|
);
|