74 lines
3.8 KiB
JavaScript
74 lines
3.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
import { gzipSync } from 'node:zlib';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = fileURLToPath(new URL('..', import.meta.url));
|
|
const read = (path) => readFileSync(join(root, path), 'utf8');
|
|
const sourceOnly = process.argv.includes('--source-only');
|
|
const distOnly = process.argv.includes('--dist-only');
|
|
|
|
if (!distOnly) {
|
|
const packageJson = JSON.parse(read('admin/package.json'));
|
|
assert.ok(packageJson.dependencies['vue-router']);
|
|
assert.ok(packageJson.devDependencies['unplugin-vue-components']);
|
|
|
|
const main = read('admin/src/main.ts');
|
|
assert.match(main, /use\(router\)/);
|
|
assert.doesNotMatch(main, /ElementPlus|element-plus\/dist\/index\.css/);
|
|
|
|
const app = read('admin/src/App.vue');
|
|
for (const pattern of ['RouterLink', 'RouterView', 'Suspense', 'routeLoadError',
|
|
'retryLazyRoute', 'restoreSession', 'qipai:admin-refreshed']) assert.match(app, new RegExp(pattern));
|
|
assert.doesNotMatch(app, /import\s+(?!AdminLoginPanel\b)\w+Panel\s+from/);
|
|
|
|
const router = read('admin/src/router.ts');
|
|
for (const path of ['/overview', '/stores', '/orders', '/payments', '/third-party',
|
|
'/people', '/cleaning', '/devices', '/apps', '/content', '/franchise', '/system']) {
|
|
assert.match(read('admin/src/navigation.mjs'), new RegExp(`path: '${path.replace('/', '\\/')}'`));
|
|
}
|
|
assert.equal((router.match(/\(\) => import\('/g) || []).length, 12);
|
|
assert.match(router, /router\.beforeEach/);
|
|
assert.match(router, /isNavigationAllowed/);
|
|
|
|
const api = read('admin/src/api.ts');
|
|
assert.match(api, /let refreshInFlight: Promise<AdminSessionPayload> \| null = null/);
|
|
assert.match(api, /if \(!refreshInFlight\)/);
|
|
assert.match(api, /refreshInFlight = publicRequest<AdminSessionPayload>\('\/auth\/refresh'/);
|
|
assert.match(api, /\.finally\(\(\) => \{ refreshInFlight = null; \}\)/);
|
|
assert.match(api, /return refreshInFlight/);
|
|
|
|
const vite = read('admin/vite.config.ts');
|
|
assert.match(vite, /ElementPlusResolver/);
|
|
assert.doesNotMatch(vite, /chunkSizeWarningLimit/);
|
|
assert.doesNotMatch(vite, /element:\s*\['element-plus'\]/);
|
|
|
|
const styles = read('admin/src/styles.css');
|
|
for (const pattern of ['@media (max-width: 980px)', '@media (max-width: 768px)',
|
|
'@media (max-width: 560px)', '.workspace .data-table .el-table__row',
|
|
'.workspace .el-dialog', '.session-recovery', '.route-state']) assert.ok(styles.includes(pattern));
|
|
}
|
|
|
|
if (!sourceOnly) {
|
|
const dist = join(root, 'admin/dist');
|
|
assert.ok(existsSync(join(dist, 'index.html')), 'admin production build is missing');
|
|
const assets = join(dist, 'assets');
|
|
const javascript = readdirSync(assets).filter((name) => name.endsWith('.js'));
|
|
const largest = javascript
|
|
.map((name) => ({ name, bytes: statSync(join(assets, name)).size }))
|
|
.sort((left, right) => right.bytes - left.bytes)[0];
|
|
assert.ok(largest.bytes < 500 * 1024, `chunk ${largest.name} exceeds 500 KiB`);
|
|
for (const chunk of ['OperationsOverviewPanel', 'StoresRoomsPanel', 'OrdersPanel',
|
|
'PaymentsPanel', 'ThirdPartyPanel', 'MembersStaffPanel', 'CleaningWorkspace',
|
|
'DevicesPanel', 'PlatformAppsPanel', 'ContentManagementPanel', 'FranchisePanel',
|
|
'LogsSystemPanel']) assert.ok(javascript.some((name) => name.startsWith(`${chunk}-`)), `${chunk} is not lazy`);
|
|
const entry = javascript.find((name) => name.startsWith('index-'));
|
|
assert.ok(entry);
|
|
assert.ok(gzipSync(readFileSync(join(assets, entry))).length < 120 * 1024, 'entry gzip exceeds 120 KiB');
|
|
assert.doesNotMatch(read('admin/dist/index.html'), /CleaningWorkspace|StoresRoomsPanel|OrdersPanel/);
|
|
console.log(`PASS: M08-D-R1 lazy chunks are below 500 KiB; largest=${largest.name}:${largest.bytes}.`);
|
|
} else {
|
|
console.log('PASS: M08-D-R1 route, permission, recovery and responsive source gates are present.');
|
|
}
|