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 \| null = null/); assert.match(api, /if \(!refreshInFlight\)/); assert.match(api, /refreshInFlight = publicRequest\('\/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.'); }