verify-medtech-theme.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { readFileSync, readdirSync, statSync } from 'node:fs'
  2. import { join, relative } from 'node:path'
  3. const root = new URL('..', import.meta.url).pathname
  4. const src = join(root, 'src')
  5. const requiredFiles = [
  6. 'src/styles/theme.css',
  7. 'src/styles/components.css',
  8. 'src/styles/element-plus.css'
  9. ]
  10. const requiredTokens = [
  11. '--color-brand: #2b1f99',
  12. '--color-accent: #3ad4d8',
  13. '--color-bg:',
  14. '--color-surface:',
  15. '--color-border:',
  16. '--color-text:',
  17. '--radius-xs: 4px',
  18. '--radius-sm: 6px',
  19. '--radius-md: 8px'
  20. ]
  21. const bannedEmoji = /[📋📌⚡⏳✅❌🎯🚀💡🔥]/
  22. const bannedDecor = /(radial-gradient|gradient orb|bokeh|blur\(60px\)|border-radius:\s*(1[2-9]|[2-9]\d)px)/
  23. const migratedMarkers = [
  24. 'clinical-shell',
  25. 'internal-login',
  26. 'period-workbench',
  27. 'period-status-strip',
  28. 'okr-workbench',
  29. 'team-okr-workbench',
  30. 'review-workbench',
  31. 'scoring-worklist',
  32. 'result-workbench',
  33. 'history-workbench',
  34. 'feedback-workbench',
  35. 'org-workbench',
  36. 'log-workbench',
  37. 'dimension-workbench',
  38. 'template-workbench'
  39. ]
  40. function walk(dir) {
  41. return readdirSync(dir).flatMap((name) => {
  42. const path = join(dir, name)
  43. return statSync(path).isDirectory() ? walk(path) : [path]
  44. })
  45. }
  46. const failures = []
  47. for (const file of requiredFiles) {
  48. try {
  49. readFileSync(join(root, file), 'utf8')
  50. } catch {
  51. failures.push(`missing required style file: ${file}`)
  52. }
  53. }
  54. const themePath = join(root, 'src/styles/theme.css')
  55. let theme = ''
  56. try {
  57. theme = readFileSync(themePath, 'utf8')
  58. } catch {}
  59. for (const token of requiredTokens) {
  60. if (!theme.includes(token)) failures.push(`missing theme token: ${token}`)
  61. }
  62. const main = readFileSync(join(root, 'src/main.js'), 'utf8')
  63. for (const file of requiredFiles) {
  64. const importPath = './' + file.replace('src/', '')
  65. if (!main.includes(importPath)) failures.push(`main.js must import ${importPath}`)
  66. }
  67. const layout = readFileSync(join(root, 'src/components/MainLayout.vue'), 'utf8')
  68. const login = readFileSync(join(root, 'src/views/auth/LoginView.vue'), 'utf8')
  69. const periodList = readFileSync(join(root, 'src/views/period/PeriodListView.vue'), 'utf8')
  70. const periodBanner = readFileSync(join(root, 'src/components/PeriodBanner.vue'), 'utf8')
  71. if (!layout.includes('clinical-shell')) failures.push('MainLayout must use clinical-shell')
  72. if (!layout.includes('clinical-sidebar')) failures.push('MainLayout must use clinical-sidebar')
  73. if (!login.includes('internal-login')) failures.push('LoginView must use internal-login')
  74. if (login.includes('radial-gradient')) failures.push('LoginView must not use radial-gradient')
  75. if (!periodList.includes('period-workbench')) failures.push('PeriodListView must use period-workbench')
  76. if (!periodList.includes('period-metrics')) failures.push('PeriodListView must show period-metrics')
  77. if (!periodBanner.includes('period-status-strip')) failures.push('PeriodBanner must use period-status-strip')
  78. const vueAndCssFiles = walk(src).filter((file) => /\.(vue|css)$/.test(file))
  79. for (const file of vueAndCssFiles) {
  80. const text = readFileSync(file, 'utf8')
  81. const rel = relative(root, file)
  82. const mustFollowMedtechRules = rel.startsWith('src/styles/') || migratedMarkers.some((marker) => text.includes(marker))
  83. if (mustFollowMedtechRules && bannedEmoji.test(text)) failures.push(`banned emoji in ${rel}`)
  84. if (mustFollowMedtechRules && bannedDecor.test(text)) failures.push(`banned decorative style in ${rel}`)
  85. }
  86. if (failures.length) {
  87. console.error(failures.join('\n'))
  88. process.exit(1)
  89. }
  90. console.log('medtech theme checks passed')