desy-html 16.0.3 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (531) hide show
  1. package/.node-version +1 -1
  2. package/.nvmrc +1 -1
  3. package/AGENTS.md +207 -181
  4. package/README.md +203 -203
  5. package/TESTING_PLAN.md +379 -376
  6. package/branding/BRANDING.md +398 -395
  7. package/branding/branding.config.js +76 -76
  8. package/branding/branding.config.yourorganization.js +72 -65
  9. package/branding/logos/aragon-compact.svg +1 -1
  10. package/branding/logos/aragon-expanded.svg +1 -1
  11. package/branding/logos/aragon-mini.svg +1 -1
  12. package/branding/logos/favicon.svg +1 -1
  13. package/branding/logos/yourlogo-compact.svg +13 -13
  14. package/branding/logos/yourlogo-expanded.svg +17 -17
  15. package/branding/logos/yourlogo-mini.svg +17 -17
  16. package/branding/logos/yourorganization-apple-touch-icon.png +0 -0
  17. package/branding/logos/yourorganization-favicon.ico +0 -0
  18. package/branding/logos/yourorganization-favicon.svg +13 -0
  19. package/branding/vite-branding-plugin.js +221 -155
  20. package/docs/_global.content-placeholder.njk +7 -7
  21. package/docs/_global.foot.njk +11 -11
  22. package/docs/_global.head.njk +42 -43
  23. package/docs/_include.template-header.njk +391 -386
  24. package/docs/_macro.component-example.njk +6 -6
  25. package/docs/_macro.example-render.njk +350 -346
  26. package/docs/_macro.load-component-params.njk +2 -2
  27. package/docs/_macro.load-component-template.njk +3 -3
  28. package/docs/_macro.render-caller.njk +3 -3
  29. package/docs/_macro.show-code-from-file.njk +57 -57
  30. package/docs/_macro.show-html-from-file.njk +42 -42
  31. package/docs/_template.default.njk +8 -8
  32. package/docs/_template.examples.njk +13 -13
  33. package/docs/catalogo.html +27 -27
  34. package/docs/componentes.html +187 -184
  35. package/docs/ds/_ds.example.accordion.njk +109 -109
  36. package/docs/ds/_ds.example.border.njk +39 -39
  37. package/docs/ds/_ds.example.botones-alert-sm.njk +264 -264
  38. package/docs/ds/_ds.example.botones-alert.njk +264 -264
  39. package/docs/ds/_ds.example.botones-default-sm.njk +264 -264
  40. package/docs/ds/_ds.example.botones-default.njk +258 -258
  41. package/docs/ds/_ds.example.botones-primary-lg.njk +264 -264
  42. package/docs/ds/_ds.example.botones-primary-sm.njk +264 -264
  43. package/docs/ds/_ds.example.botones-primary.njk +264 -264
  44. package/docs/ds/_ds.example.botones-transparent-sm.njk +264 -264
  45. package/docs/ds/_ds.example.botones-transparent.njk +264 -264
  46. package/docs/ds/_ds.example.breadcrumbs.njk +65 -65
  47. package/docs/ds/_ds.example.card.njk +198 -198
  48. package/docs/ds/_ds.example.checkboxes.njk +377 -377
  49. package/docs/ds/_ds.example.collapsible.njk +18 -18
  50. package/docs/ds/_ds.example.color-de-interaccion.njk +16 -16
  51. package/docs/ds/_ds.example.colores-cabecera.njk +15 -15
  52. package/docs/ds/_ds.example.colores-de-soporte.njk +82 -82
  53. package/docs/ds/_ds.example.colores-neutros.njk +26 -26
  54. package/docs/ds/_ds.example.combobox.njk +91 -0
  55. package/docs/ds/_ds.example.date-input.njk +363 -363
  56. package/docs/ds/_ds.example.description-list.njk +289 -289
  57. package/docs/ds/_ds.example.details.njk +23 -23
  58. package/docs/ds/_ds.example.dropdowns-default.njk +186 -186
  59. package/docs/ds/_ds.example.dropdowns-en-uso.njk +105 -105
  60. package/docs/ds/_ds.example.dropdowns-variaciones.njk +224 -224
  61. package/docs/ds/_ds.example.error-summary.njk +34 -34
  62. package/docs/ds/_ds.example.file-upload.njk +54 -54
  63. package/docs/ds/_ds.example.footer.njk +28 -28
  64. package/docs/ds/_ds.example.header-advanced.njk +70 -70
  65. package/docs/ds/_ds.example.header.njk +211 -211
  66. package/docs/ds/_ds.example.input-group.njk +242 -242
  67. package/docs/ds/_ds.example.input.njk +164 -164
  68. package/docs/ds/_ds.example.item.njk +176 -176
  69. package/docs/ds/_ds.example.layout-escritorio.njk +38 -38
  70. package/docs/ds/_ds.example.layout-movil.njk +14 -14
  71. package/docs/ds/_ds.example.layout-sidebar.njk +11 -11
  72. package/docs/ds/_ds.example.links-list.njk +170 -170
  73. package/docs/ds/_ds.example.listbox-default.njk +677 -677
  74. package/docs/ds/_ds.example.listbox-variaciones.njk +824 -824
  75. package/docs/ds/_ds.example.menu-horizontal.njk +66 -66
  76. package/docs/ds/_ds.example.menu-navigation.njk +493 -493
  77. package/docs/ds/_ds.example.menu-vertical.njk +129 -129
  78. package/docs/ds/_ds.example.menubar-en-uso.njk +244 -244
  79. package/docs/ds/_ds.example.menubar-variaciones.njk +785 -785
  80. package/docs/ds/_ds.example.modal.njk +210 -210
  81. package/docs/ds/_ds.example.nav.njk +158 -158
  82. package/docs/ds/_ds.example.notification.njk +122 -122
  83. package/docs/ds/_ds.example.pagination.njk +190 -190
  84. package/docs/ds/_ds.example.pills.njk +215 -215
  85. package/docs/ds/_ds.example.radios.njk +419 -419
  86. package/docs/ds/_ds.example.searchbar.njk +87 -87
  87. package/docs/ds/_ds.example.select.njk +390 -390
  88. package/docs/ds/_ds.example.status-item.njk +239 -239
  89. package/docs/ds/_ds.example.status.njk +47 -47
  90. package/docs/ds/_ds.example.table.njk +740 -740
  91. package/docs/ds/_ds.example.tabs.njk +136 -136
  92. package/docs/ds/_ds.example.textarea.njk +237 -237
  93. package/docs/ds/_ds.example.textos.njk +32 -32
  94. package/docs/ds/_ds.example.toggle.njk +75 -75
  95. package/docs/ds/_ds.example.tree.njk +456 -456
  96. package/docs/ds/_ds.example.treegrid.njk +810 -810
  97. package/docs/ds/_ds.example.typography.njk +225 -225
  98. package/docs/ds/_ds.macro.code-snippet.njk +32 -32
  99. package/docs/ds/_ds.macro.section-title.njk +2 -2
  100. package/docs/ds/_ds.macro.subsection-title.njk +2 -2
  101. package/docs/ds/_ds.section.avisos.njk +22 -22
  102. package/docs/ds/_ds.section.botones.njk +53 -53
  103. package/docs/ds/_ds.section.campo-y-area-de-texto.njk +19 -19
  104. package/docs/ds/_ds.section.color.njk +147 -147
  105. package/docs/ds/_ds.section.datos.njk +42 -42
  106. package/docs/ds/_ds.section.dropdowns.njk +23 -23
  107. package/docs/ds/_ds.section.espaciado.njk +296 -296
  108. package/docs/ds/_ds.section.forms.njk +73 -68
  109. package/docs/ds/_ds.section.informacion.njk +12 -12
  110. package/docs/ds/_ds.section.layout.njk +63 -63
  111. package/docs/ds/_ds.section.menubar.njk +18 -18
  112. package/docs/ds/_ds.section.mostrar-ocultar.njk +33 -33
  113. package/docs/ds/_ds.section.navigation.njk +57 -57
  114. package/docs/ds/_ds.section.textos.njk +287 -287
  115. package/docs/ds/_ds.section.typography.njk +28 -28
  116. package/docs/estilos.html +37 -37
  117. package/docs/examples-accordion-history.html +8 -8
  118. package/docs/examples-accordion.html +8 -8
  119. package/docs/examples-alert.html +8 -8
  120. package/docs/examples-breadcrumbs.html +8 -8
  121. package/docs/examples-button-loader.html +8 -8
  122. package/docs/examples-button.html +8 -8
  123. package/docs/examples-card.html +8 -8
  124. package/docs/examples-character-count.html +8 -8
  125. package/docs/examples-checkboxes.html +8 -8
  126. package/docs/examples-collapsible.html +8 -8
  127. package/docs/examples-combobox.html +8 -0
  128. package/docs/examples-date-input.html +8 -8
  129. package/docs/examples-datepicker.html +8 -8
  130. package/docs/examples-description-list.html +8 -8
  131. package/docs/examples-details.html +8 -8
  132. package/docs/examples-dialog.html +8 -8
  133. package/docs/examples-dropdown.html +8 -8
  134. package/docs/examples-error-message.html +8 -8
  135. package/docs/examples-error-summary.html +8 -8
  136. package/docs/examples-fieldset.html +8 -8
  137. package/docs/examples-file-upload.html +8 -8
  138. package/docs/examples-footer.html +8 -8
  139. package/docs/examples-header-advanced.html +8 -8
  140. package/docs/examples-header-mini.html +8 -8
  141. package/docs/examples-header.html +8 -8
  142. package/docs/examples-hint.html +8 -8
  143. package/docs/examples-input-group.html +8 -8
  144. package/docs/examples-input.html +8 -8
  145. package/docs/examples-item.html +8 -8
  146. package/docs/examples-label.html +8 -8
  147. package/docs/examples-links-list.html +8 -8
  148. package/docs/examples-listbox.html +8 -8
  149. package/docs/examples-media-object.html +8 -8
  150. package/docs/examples-menu-horizontal.html +8 -8
  151. package/docs/examples-menu-navigation.html +8 -8
  152. package/docs/examples-menu-vertical.html +8 -8
  153. package/docs/examples-menubar.html +8 -8
  154. package/docs/examples-modal.html +8 -8
  155. package/docs/examples-nav.html +8 -8
  156. package/docs/examples-notification.html +8 -8
  157. package/docs/examples-pagination.html +8 -8
  158. package/docs/examples-pill.html +8 -8
  159. package/docs/examples-radios.html +8 -8
  160. package/docs/examples-searchbar.html +8 -8
  161. package/docs/examples-select.html +8 -8
  162. package/docs/examples-skip-link.html +8 -8
  163. package/docs/examples-spinner.html +8 -8
  164. package/docs/examples-status-item.html +8 -8
  165. package/docs/examples-status.html +8 -8
  166. package/docs/examples-table-advanced.html +8 -8
  167. package/docs/examples-table.html +8 -8
  168. package/docs/examples-tabs.html +8 -8
  169. package/docs/examples-textarea.html +8 -8
  170. package/docs/examples-toggle.html +8 -8
  171. package/docs/examples-tooltip.html +8 -8
  172. package/docs/examples-tree.html +8 -8
  173. package/docs/examples-treegrid.html +8 -8
  174. package/docs/index.html +914 -902
  175. package/docs/pagina-accesibilidad.html +110 -110
  176. package/docs/pagina-mapa-web.html +137 -137
  177. package/docs/pagina-prueba.html +95 -95
  178. package/docs/plantilla-con-header-advanced.html +14 -14
  179. package/docs/plantilla-editar-con-cabecera-fija-y-sidebar-sticky.html +154 -154
  180. package/docs/plantilla-editar-con-cabecera-fija.html +17 -17
  181. package/docs/plantilla-logueado-con-cabecera-fija-headroom.html +15 -15
  182. package/docs/plantilla-logueado-con-cabecera-fija.html +15 -15
  183. package/docs/plantilla-logueado-con-selector-de-app-y-sidebar.html +164 -164
  184. package/docs/plantilla-logueado-con-selector-de-app-y-subheader.html +16 -16
  185. package/docs/plantilla-logueado-con-selector-de-app.html +14 -14
  186. package/docs/plantilla-logueado-con-titulo-de-app.html +14 -14
  187. package/docs/plantilla-sin-loguear.html +14 -14
  188. package/docs/plantillas.html +89 -89
  189. package/docs/spinner-plantilla-con-header-advanced.html +2 -2
  190. package/docs/spinner-plantilla-editar-con-cabecera-fija.html +4 -4
  191. package/docs/spinner-plantilla-logueado-con-cabecera-fija.html +2 -2
  192. package/docs/spinner-plantilla-logueado-con-selector-de-app-y-subheader.html +2 -2
  193. package/docs/spinner-plantilla-logueado-con-titulo-de-app.html +2 -2
  194. package/docs/spinner-plantilla-sin-loguear.html +18 -18
  195. package/docs/sprints/01-combobox-template.md +433 -0
  196. package/docs/sprints/02-combobox-styles.md +291 -0
  197. package/docs/sprints/03-combobox-javascript.md +589 -0
  198. package/docs/sprints/04-combobox-integration.md +386 -0
  199. package/docs/sprints/05-combobox-refinamiento-w3c.md +55 -0
  200. package/package.json +61 -58
  201. package/public/images/demo-icon.svg +9 -0
  202. package/replit.md +74 -74
  203. package/src/EUPL-1.2.txt +287 -287
  204. package/src/css/base.css +44 -44
  205. package/src/css/component.form-group.css +24 -24
  206. package/src/css/component.headroom.css +32 -32
  207. package/src/css/component.text.css +169 -169
  208. package/src/css/styles.css +247 -246
  209. package/src/js/aria/MenuHorizontal.js +65 -65
  210. package/src/js/aria/MenuNavigation.js +34 -2
  211. package/src/js/aria/MenuVertical.js +60 -60
  212. package/src/js/aria/Nav.js +60 -60
  213. package/src/js/aria/PopupMenuAction.js +30 -12
  214. package/src/js/aria/accordion.js +264 -264
  215. package/src/js/aria/checkBoxes.js +52 -52
  216. package/src/js/aria/collapsible.js +44 -44
  217. package/src/js/aria/combobox.js +584 -0
  218. package/src/js/aria/dataGrid.js +950 -950
  219. package/src/js/aria/linksList.js +42 -42
  220. package/src/js/aria/notification.js +56 -56
  221. package/src/js/aria/radioButton.js +50 -50
  222. package/src/js/aria/toggle.js +61 -61
  223. package/src/js/aria/treegrid.js +507 -507
  224. package/src/js/aria/utils.js +219 -219
  225. package/src/js/desy-html.js +860 -813
  226. package/src/js/headroom.min.js +6 -6
  227. package/src/js/index.js +74 -72
  228. package/src/main.js +2 -2
  229. package/src/templates/components/accordion/_examples.accordion.njk +482 -482
  230. package/src/templates/components/accordion/_macro.accordion.njk +3 -3
  231. package/src/templates/components/accordion/_template.accordion.njk +131 -131
  232. package/src/templates/components/accordion/params.accordion.yaml +125 -125
  233. package/src/templates/components/accordion-history/_examples.accordion-history.njk +521 -521
  234. package/src/templates/components/accordion-history/_macro.accordion-history.njk +3 -3
  235. package/src/templates/components/accordion-history/_template.accordion-history.njk +193 -193
  236. package/src/templates/components/accordion-history/params.accordion-history.yaml +129 -129
  237. package/src/templates/components/alert/_examples.alert.njk +91 -91
  238. package/src/templates/components/alert/_macro.alert.njk +3 -3
  239. package/src/templates/components/alert/_styles.alert.css +9 -9
  240. package/src/templates/components/alert/_template.alert.njk +16 -16
  241. package/src/templates/components/alert/params.alert.yaml +25 -25
  242. package/src/templates/components/breadcrumbs/_examples.breadcrumbs.njk +464 -464
  243. package/src/templates/components/breadcrumbs/_macro.breadcrumbs.njk +3 -3
  244. package/src/templates/components/breadcrumbs/_styles.breadcrumbs.css +81 -81
  245. package/src/templates/components/breadcrumbs/_template.breadcrumbs.njk +77 -77
  246. package/src/templates/components/breadcrumbs/params.breadcrumbs.yaml +44 -44
  247. package/src/templates/components/button/_examples.button.njk +518 -511
  248. package/src/templates/components/button/_macro.button.njk +3 -3
  249. package/src/templates/components/button/_styles.button.css +180 -180
  250. package/src/templates/components/button/_template.button.njk +49 -49
  251. package/src/templates/components/button/params.button.yaml +48 -48
  252. package/src/templates/components/button-loader/_examples.button-loader.njk +499 -492
  253. package/src/templates/components/button-loader/_macro.button-loader.njk +3 -3
  254. package/src/templates/components/button-loader/_styles.button-loader.css +194 -194
  255. package/src/templates/components/button-loader/_template.button-loader.njk +84 -84
  256. package/src/templates/components/button-loader/params.button-loader.yaml +74 -74
  257. package/src/templates/components/card/_examples.card.njk +381 -381
  258. package/src/templates/components/card/_macro.card.njk +3 -3
  259. package/src/templates/components/card/_template.card.njk +34 -34
  260. package/src/templates/components/card/params.card.yaml +112 -112
  261. package/src/templates/components/character-count/_examples.character-count.njk +195 -195
  262. package/src/templates/components/character-count/_macro.character-count.njk +5 -5
  263. package/src/templates/components/character-count/_template.character-count.njk +38 -38
  264. package/src/templates/components/character-count/params.character-count.yaml +77 -77
  265. package/src/templates/components/checkboxes/_examples.checkboxes.njk +835 -835
  266. package/src/templates/components/checkboxes/_macro.checkboxes.njk +3 -3
  267. package/src/templates/components/checkboxes/_styles.checkboxes.css +30 -30
  268. package/src/templates/components/checkboxes/_template.checkboxes.njk +137 -137
  269. package/src/templates/components/checkboxes/params.checkboxes.yaml +116 -116
  270. package/src/templates/components/collapsible/_examples.collapsible.njk +107 -107
  271. package/src/templates/components/collapsible/_macro.collapsible.njk +3 -3
  272. package/src/templates/components/collapsible/_styles.collapsible.css +35 -35
  273. package/src/templates/components/collapsible/_template.collapsible.njk +17 -17
  274. package/src/templates/components/collapsible/params.collapsible.yaml +48 -48
  275. package/src/templates/components/combobox/_examples.combobox.njk +440 -0
  276. package/src/templates/components/combobox/_macro.combobox.njk +3 -0
  277. package/src/templates/components/combobox/_styles.combobox.css +92 -0
  278. package/src/templates/components/combobox/_template.combobox.njk +97 -0
  279. package/src/templates/components/combobox/params.combobox.yaml +102 -0
  280. package/src/templates/components/date-input/_examples.date-input.njk +578 -578
  281. package/src/templates/components/date-input/_macro.date-input.njk +3 -3
  282. package/src/templates/components/date-input/_template.date-input.njk +125 -125
  283. package/src/templates/components/date-input/params.date-input.yaml +97 -97
  284. package/src/templates/components/datepicker/_examples.datepicker.njk +415 -415
  285. package/src/templates/components/datepicker/_macro.datepicker.njk +3 -3
  286. package/src/templates/components/datepicker/_styles.datepicker.css +87 -87
  287. package/src/templates/components/datepicker/_template.datepicker.njk +129 -129
  288. package/src/templates/components/datepicker/params.datepicker.yaml +104 -104
  289. package/src/templates/components/description-list/_examples.description-list.njk +502 -502
  290. package/src/templates/components/description-list/_macro.description-list.njk +3 -3
  291. package/src/templates/components/description-list/_template.description-list.njk +17 -17
  292. package/src/templates/components/description-list/params.description-list.yaml +61 -61
  293. package/src/templates/components/details/_examples.details.njk +68 -68
  294. package/src/templates/components/details/_macro.details.njk +3 -3
  295. package/src/templates/components/details/_template.details.njk +17 -17
  296. package/src/templates/components/details/params.details.yaml +40 -40
  297. package/src/templates/components/dialog/_examples.dialog.njk +156 -156
  298. package/src/templates/components/dialog/_macro.dialog.njk +3 -3
  299. package/src/templates/components/dialog/_styles.dialog.css +27 -27
  300. package/src/templates/components/dialog/_template.dialog.njk +12 -12
  301. package/src/templates/components/dialog/params.dialog.yaml +25 -25
  302. package/src/templates/components/dropdown/_examples.dropdown.njk +226 -226
  303. package/src/templates/components/dropdown/_macro.dropdown.njk +3 -3
  304. package/src/templates/components/dropdown/_styles.dropdown.css +191 -191
  305. package/src/templates/components/dropdown/_template.dropdown.njk +34 -34
  306. package/src/templates/components/dropdown/params.dropdown.yaml +32 -32
  307. package/src/templates/components/error-message/_examples.error-message.njk +28 -28
  308. package/src/templates/components/error-message/_macro.error-message.njk +5 -5
  309. package/src/templates/components/error-message/_template.error-message.njk +8 -8
  310. package/src/templates/components/error-message/params.error-message.yaml +23 -23
  311. package/src/templates/components/error-summary/_examples.error-summary.njk +111 -111
  312. package/src/templates/components/error-summary/_macro.error-summary.njk +5 -5
  313. package/src/templates/components/error-summary/_template.error-summary.njk +48 -48
  314. package/src/templates/components/error-summary/params.error-summary.yaml +50 -50
  315. package/src/templates/components/fieldset/_examples.fieldset.njk +79 -79
  316. package/src/templates/components/fieldset/_macro.fieldset.njk +2 -2
  317. package/src/templates/components/fieldset/_template.fieldset.njk +46 -46
  318. package/src/templates/components/fieldset/params.fieldset.yaml +49 -49
  319. package/src/templates/components/file-upload/_examples.file-upload.njk +103 -103
  320. package/src/templates/components/file-upload/_macro.file-upload.njk +2 -2
  321. package/src/templates/components/file-upload/_template.file-upload.njk +45 -45
  322. package/src/templates/components/file-upload/params.file-upload.yaml +48 -48
  323. package/src/templates/components/footer/_examples.footer.njk +558 -558
  324. package/src/templates/components/footer/_macro.footer.njk +3 -3
  325. package/src/templates/components/footer/_styles.footer.css +43 -43
  326. package/src/templates/components/footer/_template.footer.njk +130 -130
  327. package/src/templates/components/footer/params.footer.yaml +140 -140
  328. package/src/templates/components/header/_examples.header.njk +627 -627
  329. package/src/templates/components/header/_macro.header.header__dropdown.njk +3 -3
  330. package/src/templates/components/header/_macro.header.header__navigation.njk +3 -3
  331. package/src/templates/components/header/_macro.header.header__offcanvas.njk +3 -3
  332. package/src/templates/components/header/_macro.header.header__offcanvasButton.njk +3 -3
  333. package/src/templates/components/header/_macro.header.header__subnav.njk +3 -3
  334. package/src/templates/components/header/_macro.header.njk +3 -3
  335. package/src/templates/components/header/_styles.header.css +12 -12
  336. package/src/templates/components/header/_template.header.header__dropdown.njk +27 -27
  337. package/src/templates/components/header/_template.header.header__navigation.njk +25 -25
  338. package/src/templates/components/header/_template.header.header__offcanvas.njk +20 -20
  339. package/src/templates/components/header/_template.header.header__offcanvasButton.njk +10 -10
  340. package/src/templates/components/header/_template.header.header__subnav.njk +33 -33
  341. package/src/templates/components/header/_template.header.njk +155 -155
  342. package/src/templates/components/header/params.header.yaml +295 -295
  343. package/src/templates/components/header-advanced/_examples.header-advanced.njk +1110 -1110
  344. package/src/templates/components/header-advanced/_macro.header-advanced.njk +3 -3
  345. package/src/templates/components/header-advanced/_template.header-advanced.njk +171 -171
  346. package/src/templates/components/header-advanced/params.header-advanced.yaml +352 -350
  347. package/src/templates/components/header-mini/_examples.header-mini.njk +319 -316
  348. package/src/templates/components/header-mini/_macro.header-mini.njk +3 -3
  349. package/src/templates/components/header-mini/_template.header-mini.njk +51 -51
  350. package/src/templates/components/header-mini/params.header-mini.yaml +90 -90
  351. package/src/templates/components/hint/_examples.hint.njk +26 -26
  352. package/src/templates/components/hint/_macro.hint.njk +3 -3
  353. package/src/templates/components/hint/_template.hint.njk +3 -3
  354. package/src/templates/components/hint/params.hint.yaml +21 -21
  355. package/src/templates/components/input/_examples.input.njk +393 -393
  356. package/src/templates/components/input/_macro.input.njk +3 -3
  357. package/src/templates/components/input/_styles.input.css +17 -17
  358. package/src/templates/components/input/_template.input.njk +53 -53
  359. package/src/templates/components/input/params.input.yaml +80 -80
  360. package/src/templates/components/input-group/_examples.input-group.njk +558 -558
  361. package/src/templates/components/input-group/_macro.input-group.njk +3 -3
  362. package/src/templates/components/input-group/_template.input-group.njk +107 -107
  363. package/src/templates/components/input-group/params.input-group.yaml +123 -123
  364. package/src/templates/components/item/_examples.item.njk +370 -370
  365. package/src/templates/components/item/_macro.item.njk +3 -3
  366. package/src/templates/components/item/_template.item.njk +73 -73
  367. package/src/templates/components/item/params.item.yaml +101 -101
  368. package/src/templates/components/label/_examples.label.njk +58 -58
  369. package/src/templates/components/label/_macro.label.njk +5 -5
  370. package/src/templates/components/label/_template.label.njk +37 -37
  371. package/src/templates/components/label/params.label.yaml +28 -28
  372. package/src/templates/components/links-list/_examples.links-list.njk +761 -761
  373. package/src/templates/components/links-list/_macro.links-list.njk +3 -3
  374. package/src/templates/components/links-list/_template.links-list.njk +119 -119
  375. package/src/templates/components/links-list/params.links-list.yaml +101 -101
  376. package/src/templates/components/listbox/_examples.listbox.njk +813 -812
  377. package/src/templates/components/listbox/_macro.listbox.njk +3 -3
  378. package/src/templates/components/listbox/_styles.listbox.css +218 -218
  379. package/src/templates/components/listbox/_template.listbox.njk +90 -90
  380. package/src/templates/components/listbox/params.listbox.yaml +85 -85
  381. package/src/templates/components/media-object/_examples.media-object.njk +79 -79
  382. package/src/templates/components/media-object/_macro.media-object.njk +3 -3
  383. package/src/templates/components/media-object/_template.media-object.njk +21 -21
  384. package/src/templates/components/media-object/params.media-object.yaml +16 -16
  385. package/src/templates/components/menu-horizontal/_examples.menu-horizontal.njk +613 -613
  386. package/src/templates/components/menu-horizontal/_macro.menu-horizontal.njk +3 -3
  387. package/src/templates/components/menu-horizontal/_styles.menu-horizontal.css +167 -167
  388. package/src/templates/components/menu-horizontal/_template.menu-horizontal.njk +36 -36
  389. package/src/templates/components/menu-horizontal/params.menu-horizontal.yaml +53 -53
  390. package/src/templates/components/menu-navigation/_examples.menu-navigation.njk +1292 -1292
  391. package/src/templates/components/menu-navigation/_macro.menu-navigation.njk +3 -3
  392. package/src/templates/components/menu-navigation/_styles.menu-navigation.css +238 -238
  393. package/src/templates/components/menu-navigation/_template.menu-navigation.njk +87 -87
  394. package/src/templates/components/menu-navigation/params.menu-navigation.yaml +86 -86
  395. package/src/templates/components/menu-vertical/_examples.menu-vertical.njk +835 -835
  396. package/src/templates/components/menu-vertical/_macro.menu-vertical.njk +3 -3
  397. package/src/templates/components/menu-vertical/_template.menu-vertical.njk +92 -92
  398. package/src/templates/components/menu-vertical/params.menu-vertical.yaml +77 -77
  399. package/src/templates/components/menubar/_examples.menubar.njk +1843 -1843
  400. package/src/templates/components/menubar/_macro.menubar.njk +3 -3
  401. package/src/templates/components/menubar/_styles.menubar.css +259 -259
  402. package/src/templates/components/menubar/_template.menubar.njk +106 -106
  403. package/src/templates/components/menubar/params.menubar.yaml +130 -130
  404. package/src/templates/components/modal/_examples.modal.njk +432 -432
  405. package/src/templates/components/modal/_macro.modal.njk +3 -3
  406. package/src/templates/components/modal/_template.modal.njk +129 -129
  407. package/src/templates/components/modal/params.modal.yaml +77 -77
  408. package/src/templates/components/nav/_examples.nav.njk +468 -468
  409. package/src/templates/components/nav/_macro.nav.njk +3 -3
  410. package/src/templates/components/nav/_template.nav.njk +53 -53
  411. package/src/templates/components/nav/params.nav.yaml +61 -61
  412. package/src/templates/components/notification/_examples.notification.njk +277 -277
  413. package/src/templates/components/notification/_macro.notification.njk +3 -3
  414. package/src/templates/components/notification/_styles.notification.css +30 -30
  415. package/src/templates/components/notification/_template.notification.njk +86 -86
  416. package/src/templates/components/notification/params.notification.yaml +101 -101
  417. package/src/templates/components/pagination/_examples.pagination.njk +444 -444
  418. package/src/templates/components/pagination/_macro.pagination.njk +3 -3
  419. package/src/templates/components/pagination/_template.pagination.njk +162 -162
  420. package/src/templates/components/pagination/params.pagination.yaml +150 -150
  421. package/src/templates/components/pill/_examples.pill.njk +180 -180
  422. package/src/templates/components/pill/_macro.pill.njk +3 -3
  423. package/src/templates/components/pill/_styles.pill.css +59 -59
  424. package/src/templates/components/pill/_template.pill.njk +38 -38
  425. package/src/templates/components/pill/params.pill.yaml +28 -28
  426. package/src/templates/components/radios/_examples.radios.njk +698 -698
  427. package/src/templates/components/radios/_macro.radios.njk +5 -5
  428. package/src/templates/components/radios/_styles.radios.css +30 -30
  429. package/src/templates/components/radios/_template.radios.njk +133 -133
  430. package/src/templates/components/radios/params.radios.yaml +104 -104
  431. package/src/templates/components/searchbar/_examples.searchbar.njk +147 -147
  432. package/src/templates/components/searchbar/_macro.searchbar.njk +3 -3
  433. package/src/templates/components/searchbar/_template.searchbar.njk +45 -45
  434. package/src/templates/components/searchbar/params.searchbar.yaml +46 -46
  435. package/src/templates/components/select/_examples.select.njk +364 -364
  436. package/src/templates/components/select/_macro.select.njk +5 -5
  437. package/src/templates/components/select/_styles.select.css +45 -45
  438. package/src/templates/components/select/_template.select.njk +67 -67
  439. package/src/templates/components/select/params.select.yaml +93 -93
  440. package/src/templates/components/skip-link/_examples.skip-link.njk +31 -31
  441. package/src/templates/components/skip-link/_macro.skip-link.njk +3 -3
  442. package/src/templates/components/skip-link/_styles.skip-link.css +12 -12
  443. package/src/templates/components/skip-link/_template.skip-link.njk +4 -4
  444. package/src/templates/components/skip-link/params.skip-link.yaml +20 -20
  445. package/src/templates/components/spinner/_examples.spinner.njk +91 -101
  446. package/src/templates/components/spinner/_macro.spinner.njk +3 -3
  447. package/src/templates/components/spinner/_styles.spinner.css +33 -33
  448. package/src/templates/components/spinner/_template.spinner.njk +12 -12
  449. package/src/templates/components/spinner/params.spinner.yaml +12 -12
  450. package/src/templates/components/status/_examples.status.njk +80 -80
  451. package/src/templates/components/status/_macro.status.njk +3 -3
  452. package/src/templates/components/status/_template.status.njk +23 -23
  453. package/src/templates/components/status/params.status.yaml +24 -24
  454. package/src/templates/components/status-item/_examples.status-item.njk +319 -319
  455. package/src/templates/components/status-item/_macro.status-item.njk +3 -3
  456. package/src/templates/components/status-item/_template.status-item.njk +99 -99
  457. package/src/templates/components/status-item/params.status-item.yaml +106 -106
  458. package/src/templates/components/table/_examples.table.njk +705 -705
  459. package/src/templates/components/table/_macro.table.njk +3 -3
  460. package/src/templates/components/table/_styles.table.css +19 -19
  461. package/src/templates/components/table/_template.table.njk +68 -68
  462. package/src/templates/components/table/params.table.yaml +130 -130
  463. package/src/templates/components/table-advanced/_examples.table-advanced.njk +839 -839
  464. package/src/templates/components/table-advanced/_macro.table-advanced.njk +3 -3
  465. package/src/templates/components/table-advanced/_styles.table-advanced.css +63 -63
  466. package/src/templates/components/table-advanced/_template.table-advanced.njk +210 -210
  467. package/src/templates/components/table-advanced/params.table-advanced.yaml +160 -160
  468. package/src/templates/components/tabs/_examples.tabs.njk +588 -588
  469. package/src/templates/components/tabs/_macro.tabs.njk +3 -3
  470. package/src/templates/components/tabs/_styles.tabs.css +132 -132
  471. package/src/templates/components/tabs/_template.tabs.njk +70 -70
  472. package/src/templates/components/tabs/params.tabs.yaml +83 -83
  473. package/src/templates/components/textarea/_examples.textarea.njk +174 -174
  474. package/src/templates/components/textarea/_macro.textarea.njk +5 -5
  475. package/src/templates/components/textarea/_template.textarea.njk +47 -47
  476. package/src/templates/components/textarea/params.textarea.yaml +64 -64
  477. package/src/templates/components/toggle/_examples.toggle.njk +248 -248
  478. package/src/templates/components/toggle/_macro.toggle.njk +3 -3
  479. package/src/templates/components/toggle/_styles.toggle.css +31 -31
  480. package/src/templates/components/toggle/_template.toggle.njk +27 -27
  481. package/src/templates/components/toggle/params.toggle.yaml +69 -69
  482. package/src/templates/components/tooltip/_examples.tooltip.njk +134 -134
  483. package/src/templates/components/tooltip/_macro.tooltip.njk +3 -3
  484. package/src/templates/components/tooltip/_styles.tooltip.css +9 -9
  485. package/src/templates/components/tooltip/_template.tooltip.njk +40 -40
  486. package/src/templates/components/tooltip/params.tooltip.yaml +40 -40
  487. package/src/templates/components/tree/_examples.tree.njk +2671 -2671
  488. package/src/templates/components/tree/_macro.tree.njk +3 -3
  489. package/src/templates/components/tree/_styles.tree.css +54 -54
  490. package/src/templates/components/tree/_template.tree.njk +232 -232
  491. package/src/templates/components/tree/params.tree.yaml +173 -173
  492. package/src/templates/components/treegrid/_examples.treegrid.njk +1269 -1269
  493. package/src/templates/components/treegrid/_macro.treegrid.njk +3 -3
  494. package/src/templates/components/treegrid/_styles.treegrid.css +31 -31
  495. package/src/templates/components/treegrid/_template.treegrid.njk +90 -90
  496. package/src/templates/components/treegrid/params.treegrid.yaml +131 -131
  497. package/src/templates/includes/_abrir-notificaciones-extra.njk +2 -2
  498. package/src/templates/includes/_abrir-notificaciones.njk +2 -2
  499. package/src/templates/includes/_acciones-de-cabecera.njk +28 -28
  500. package/src/templates/includes/_ejemplo-titulo-h2-parrafo.njk +4 -4
  501. package/src/templates/includes/_ejemplo-titulo-parrafo.njk +4 -4
  502. package/src/templates/includes/_test-include.njk +13 -13
  503. package/src/templates/pages/_page.foot-headroom.njk +30 -30
  504. package/src/templates/pages/_page.foot.njk +5 -5
  505. package/src/templates/pages/_page.footer.njk +19 -19
  506. package/src/templates/pages/_page.head.njk +27 -27
  507. package/src/templates/pages/_page.notification-edit-inner-content.njk +31 -31
  508. package/src/templates/pages/_page.notification-edit.njk +30 -30
  509. package/src/templates/pages/_page.notification-footer.njk +32 -32
  510. package/src/templates/pages/_page.notification-header-fixed.njk +30 -30
  511. package/src/templates/pages/_page.notification-header.njk +31 -31
  512. package/src/templates/pages/_page.sidebar-content.njk +20 -20
  513. package/src/templates/pages/_page.spinner-block.njk +14 -14
  514. package/src/templates/pages/_page.spinner-show.njk +15 -15
  515. package/src/templates/pages/_page.spinner.njk +15 -15
  516. package/src/templates/pages/_template.edit-fixed-with-sticky-sidebar.njk +36 -36
  517. package/src/templates/pages/_template.edit-fixed.njk +74 -74
  518. package/src/templates/pages/_template.home.njk +111 -111
  519. package/src/templates/pages/_template.logged-out.njk +56 -56
  520. package/src/templates/pages/_template.logged-selector-fixed-headroom.njk +198 -198
  521. package/src/templates/pages/_template.logged-selector-fixed.njk +195 -195
  522. package/src/templates/pages/_template.logged-selector-subheader.njk +80 -80
  523. package/src/templates/pages/_template.logged-selector-with-sidebar.njk +28 -28
  524. package/src/templates/pages/_template.logged-selector.njk +195 -195
  525. package/src/templates/pages/_template.logged.njk +141 -141
  526. package/src/templates/pages/_template.mfe-iframe-content.njk +31 -31
  527. package/src/templates/pages/_template.mfe.njk +83 -83
  528. package/src/templates/pages/_template.test.njk +48 -48
  529. package/src/templates/pages/_template.with-header-advanced.njk +311 -311
  530. package/vite.config.js +982 -955
  531. package/public/images/logo-educa-aragon.svg +0 -1
package/vite.config.js CHANGED
@@ -1,955 +1,982 @@
1
- import tailwindcss from '@tailwindcss/vite';
2
- import fs from 'fs';
3
- import { glob } from 'glob';
4
- import hljs from 'highlight.js';
5
- import beautify from 'js-beautify';
6
- import yaml from 'js-yaml';
7
- import nunjucks from 'nunjucks';
8
- import { outdent } from 'outdent';
9
- import path from 'path';
10
- import sharp from 'sharp';
11
- import { defineConfig } from 'vite';
12
- import { brandingPlugin, loadBrandingConfig } from './branding/vite-branding-plugin.js';
13
-
14
- // Load branding configuration
15
- const brandingConfig = await loadBrandingConfig(
16
- process.env.BRANDING_CONFIG || 'default',
17
- process.cwd(),
18
- 'branding'
19
- );
20
-
21
- // Helper functions
22
- function kebabCaseToPascalCase(value) {
23
- return value
24
- .toLowerCase()
25
- .split('-')
26
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
27
- .join('');
28
- }
29
-
30
- function getBeautifyOptions() {
31
- const options = beautify.html.defaultOptions();
32
- return {
33
- indent_size: 2,
34
- inline: options.inline.filter((tag) => !['label'].includes(tag)),
35
- max_preserve_newlines: 0,
36
- wrap_attributes: 'preserve'
37
- };
38
- }
39
-
40
- // Function to configure Nunjucks environment
41
- function setupNunjucksEnvironment(paths, options = {}) {
42
- const defaultOptions = {
43
- autoescape: true,
44
- trimBlocks: true,
45
- lstripBlocks: true,
46
- throwOnUndefined: false,
47
- noCache: true
48
- };
49
-
50
- const env = nunjucks.configure(paths, { ...defaultOptions, ...options });
51
-
52
- // Global configuration
53
- env.addGlobal('assetsUrl', '/src');
54
- env.addGlobal('branding', brandingConfig);
55
-
56
- env.addGlobal('loadComponentParams', function (componentName) {
57
- try {
58
- const yamlPath = path.join(process.cwd(), 'src/templates/components', componentName, `params.${componentName}.yaml`);
59
-
60
- if (fs.existsSync(yamlPath)) {
61
- const content = fs.readFileSync(yamlPath, 'utf8');
62
- const parsedYaml = yaml.load(content);
63
- const dumpedYaml = yaml.dump(parsedYaml);
64
- return dumpedYaml;
65
- }
66
- console.error(`YAML file not found: ${yamlPath}`);
67
- return '';
68
- } catch (err) {
69
- console.error(`Error loading YAML for ${componentName}:`, err);
70
- return '';
71
- }
72
- });
73
-
74
- // Four globals for code blocks
75
- env.addGlobal('getHTMLCodeFromExample', function (componentName, params, pathName) {
76
- const templatePath = `${pathName}s/${componentName}/_template.${componentName}.njk`;
77
- const html = env.render(templatePath, { params }).trim();
78
- return beautify.html(html, getBeautifyOptions());
79
- });
80
-
81
- env.addGlobal('getHTMLCodeFromFile', function (templatePath) {
82
- const html = env.render(templatePath).trim();
83
- return beautify.html(html, getBeautifyOptions());
84
- });
85
-
86
- env.addGlobal('getNunjucksCodeFromExample', function (componentName, params, pathName) {
87
- const macroName = kebabCaseToPascalCase(componentName);
88
- return outdent`
89
- {% from "${pathName}s/${componentName}/_macro.${componentName}.njk" import ${pathName}${macroName} %}
90
- {{ ${pathName}${macroName}(${JSON.stringify(params, undefined, 2)
91
- }) }}
92
- `;
93
- });
94
-
95
- env.addGlobal('getNunjucksCodeFromFile', function (templatePath) {
96
- try {
97
- const codeFromFile = fs.readFileSync(
98
- path.join(process.cwd(), 'src/templates', templatePath),
99
- 'utf8'
100
- );
101
- return outdent`
102
- ${codeFromFile}
103
- `;
104
- } catch (err) {
105
- console.error(`Error reading file ${templatePath}:`, err);
106
- return '';
107
- }
108
- });
109
-
110
- // Filter configuration
111
- env.addFilter('filtercaller', function (params) {
112
- if (params && typeof params === 'object') {
113
- const filteredParams = { ...params };
114
- delete filteredParams.caller;
115
- return filteredParams;
116
- }
117
- return params;
118
- });
119
-
120
- env.addFilter('filterslugify', function (str) {
121
- if (typeof str !== 'string') {
122
- return '';
123
- }
124
- let slug = str.replace(/\s+/g, '-');
125
- slug = slug.replace(/[^\w-]+/g, '');
126
- slug = slug.toLowerCase();
127
- return slug;
128
- });
129
-
130
- env.addFilter('highlight', function (code, language) {
131
- try {
132
- return hljs.highlight(code.trim(), {
133
- language: language || 'plaintext'
134
- }).value;
135
- } catch (err) {
136
- console.error('Error highlighting code:', err);
137
- return code;
138
- }
139
- });
140
-
141
- env.addFilter('filterescapeltgt', function (str) {
142
- if (typeof str !== 'string' && typeof str !== 'object') return str;
143
- if (typeof str !== 'string') str = JSON.stringify(str);
144
- return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
145
- });
146
-
147
- env.addFilter('filterquotes', function (str) {
148
- if (typeof str !== 'string') return str;
149
- let inTag = false;
150
- let result = '';
151
- let currentQuote = null;
152
- for (let i = 0; i < str.length; i++) {
153
- const char = str[i];
154
- if (char === '<') {
155
- inTag = true;
156
- result += char;
157
- continue;
158
- }
159
- if (char === '>') {
160
- inTag = false;
161
- result += char;
162
- continue;
163
- }
164
- if (inTag) {
165
- if (char === "'" || char === '"') {
166
- if (currentQuote === null) {
167
- currentQuote = char;
168
- result += '"';
169
- } else if (currentQuote === char) {
170
- currentQuote = null;
171
- result += '"';
172
- } else {
173
- result += char;
174
- }
175
- } else {
176
- result += char;
177
- }
178
- } else {
179
- result += char;
180
- }
181
- }
182
- return result;
183
- });
184
-
185
- env.addFilter('filterversion', function () {
186
- const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
187
- return pkg.version;
188
- });
189
-
190
- return env;
191
- }
192
-
193
- // Improved function to process Nunjucks files during build
194
- async function processNunjucksFiles(env) {
195
- try {
196
- const docsPath = path.resolve(process.cwd(), 'docs');
197
- const htmlFiles = await glob('*.html', { cwd: docsPath });
198
-
199
-
200
- for (const htmlFile of htmlFiles) {
201
- try {
202
- const sourcePath = path.join(docsPath, htmlFile);
203
- const outputPath = path.join(process.cwd(), 'dist', htmlFile);
204
-
205
- const content = await fs.promises.readFile(sourcePath, 'utf8');
206
- let processedHtml = env.renderString(content);
207
-
208
- // Transformations for relative paths
209
- processedHtml = processedHtml.replace(/href=["']\/src\/css\/([^"']*)["']/g, 'href="css/$1"');
210
- processedHtml = processedHtml.replace(/href=["']src\/css\/([^"']*)["']/g, 'href="css/$1"');
211
-
212
- // Transform JS paths (ensure they are relative without leading slash)
213
- processedHtml = processedHtml.replace(/src=["']\/src\/js\/([^"']*)["']/g, 'src="js/$1"');
214
- processedHtml = processedHtml.replace(/src=["']src\/js\/([^"']*)["']/g, 'src="js/$1"');
215
-
216
- // Also process if any route with /js/ (absolute) remains
217
- processedHtml = processedHtml.replace(/src=["']\/js\/([^"']*)["']/g, 'src="js/$1"');
218
-
219
- // Transform image paths
220
- processedHtml = processedHtml.replace(/src=["']\/src\/images\/([^"']*)["']/g, 'src="images/$1"');
221
- processedHtml = processedHtml.replace(/src=["']src\/images\/([^"']*)["']/g, 'src="images/$1"');
222
-
223
- await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
224
- await fs.promises.writeFile(outputPath, processedHtml);
225
- } catch (fileError) {
226
- console.error(`Error processing file ${htmlFile}:`, fileError);
227
- }
228
- }
229
- } catch (error) {
230
- console.error('Error in processNunjucksFiles:', error);
231
- }
232
- }
233
-
234
- // NEW FUNCTION: Process JavaScript files to remove CSS imports and fix modules
235
- async function processJavaScriptFiles() {
236
- try {
237
- const srcJsDir = path.resolve(process.cwd(), 'src/js');
238
- const distJsDir = path.resolve(process.cwd(), 'dist/js');
239
-
240
- // Create dist/js directory if it doesn't exist
241
- await fs.promises.mkdir(distJsDir, { recursive: true });
242
-
243
- // 1. Copy npm packages to the vendor folder
244
- const vendorDir = path.join(distJsDir, 'vendor');
245
- await fs.promises.mkdir(vendorDir, { recursive: true });
246
-
247
- // 2. Process all JS files
248
- const jsFiles = await glob('**/*.js', { cwd: srcJsDir });
249
-
250
- for (const jsFile of jsFiles) {
251
- const sourcePath = path.join(srcJsDir, jsFile);
252
- const destPath = path.join(distJsDir, jsFile);
253
-
254
- // Create directory if necessary
255
- await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
256
-
257
- // Read file content
258
- const content = await fs.promises.readFile(sourcePath, 'utf8');
259
-
260
- // Transform the content
261
- let processedContent = content;
262
-
263
- // a) Remove CSS file imports
264
- processedContent = processedContent.replace(
265
- /import\s+['"](.+\.css)['"];?/g,
266
- '// CSS import removed: $1'
267
- );
268
-
269
- // c) Fix other relative import paths between JS files
270
- processedContent = processedContent.replace(
271
- /from\s+["']\/src\/js\/([^"']*)["']/g,
272
- (match, importPath) => {
273
- const depth = jsFile.split('/').length - 1;
274
- const prefix = depth > 0 ? '../'.repeat(depth) : './';
275
- return `from "${prefix}${importPath}"`;
276
- }
277
- );
278
-
279
- processedContent = processedContent.replace(
280
- /from\s+["']src\/js\/([^"']*)["']/g,
281
- (match, importPath) => {
282
- const depth = jsFile.split('/').length - 1;
283
- const prefix = depth > 0 ? '../'.repeat(depth) : './';
284
- return `from "${prefix}${importPath}"`;
285
- }
286
- );
287
-
288
- // Write the processed file
289
- await fs.promises.writeFile(destPath, processedContent);
290
- }
291
-
292
- } catch (error) {
293
- console.error('Error processing JavaScript:', error);
294
- }
295
- }
296
-
297
- // Optimize image with Sharp
298
- async function optimizeImage(sourcePath, destPath, fileExtension) {
299
- try {
300
- const image = sharp(sourcePath);
301
-
302
- switch (fileExtension.toLowerCase()) {
303
- case '.jpg':
304
- case '.jpeg':
305
- await image
306
- .jpeg({
307
- quality: 80,
308
- mozjpeg: true,
309
- progressive: true
310
- })
311
- .toFile(destPath);
312
- break;
313
-
314
- case '.png':
315
- await image
316
- .png({
317
- quality: 80,
318
- palette: true,
319
- compressionLevel: 9
320
- })
321
- .toFile(destPath);
322
- break;
323
-
324
- case '.webp':
325
- await image
326
- .webp({
327
- quality: 80,
328
- effort: 6
329
- })
330
- .toFile(destPath);
331
- break;
332
-
333
- case '.avif':
334
- await image
335
- .avif({
336
- quality: 80,
337
- effort: 4
338
- })
339
- .toFile(destPath);
340
- break;
341
-
342
- case '.svg':
343
- // SVGs are not processed with Sharp, they are copied directly
344
- await fs.promises.copyFile(sourcePath, destPath);
345
- break;
346
-
347
- default:
348
- // For other formats, copy without optimizing
349
- await fs.promises.copyFile(sourcePath, destPath);
350
- break;
351
- }
352
-
353
- return true;
354
- } catch (error) {
355
- console.error(`❌ Error optimizing ${sourcePath}:`, error.message);
356
- // If optimization fails, try direct copy as fallback
357
- try {
358
- await fs.promises.copyFile(sourcePath, destPath);
359
- return true;
360
- } catch (copyError) {
361
- console.error(`❌ Error copying ${sourcePath}:`, copyError.message);
362
- return false;
363
- }
364
- }
365
- }
366
-
367
- // UPDATED FUNCTION: Copy and optimize all images
368
- async function copyAndOptimizeImages() {
369
- try {
370
- const srcImagesDir = path.resolve(process.cwd(), 'public/images');
371
- const distImagesDir = path.resolve(process.cwd(), 'dist/images');
372
-
373
- // Check if public/images folder exists
374
- if (!fs.existsSync(srcImagesDir)) {
375
- console.log('📁 public/images does not exist, skipping image optimization');
376
- return;
377
- }
378
-
379
- // Create dist/images directory if it doesn't exist
380
- await fs.promises.mkdir(distImagesDir, { recursive: true });
381
-
382
- // Get all image files recursively
383
- const imageFiles = await glob('**/*.{png,jpg,jpeg,gif,svg,webp,avif,ico,bmp,tiff}', {
384
- cwd: srcImagesDir,
385
- nodir: true
386
- });
387
-
388
- console.log(`🖼️ Optimizing ${imageFiles.length} images with Sharp...`);
389
-
390
- let optimizedCount = 0;
391
- let copiedCount = 0;
392
- let totalSizeBefore = 0;
393
- let totalSizeAfter = 0;
394
-
395
- for (const imageFile of imageFiles) {
396
- const sourcePath = path.join(srcImagesDir, imageFile);
397
- const destPath = path.join(distImagesDir, imageFile);
398
- const fileExtension = path.extname(imageFile);
399
-
400
- // Create directory if necessary (for subdirectories)
401
- await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
402
-
403
- // Get original file size
404
- const statsBefore = await fs.promises.stat(sourcePath);
405
- totalSizeBefore += statsBefore.size;
406
-
407
- // Optimize or copy image
408
- const success = await optimizeImage(sourcePath, destPath, fileExtension);
409
-
410
- if (success) {
411
- // Get file size after optimization
412
- if (fs.existsSync(destPath)) {
413
- const statsAfter = await fs.promises.stat(destPath);
414
- totalSizeAfter += statsAfter.size;
415
-
416
- const reduction = ((statsBefore.size - statsAfter.size) / statsBefore.size * 100).toFixed(1);
417
-
418
- if (['.jpg', '.jpeg', '.png', '.webp', '.avif'].includes(fileExtension.toLowerCase())) {
419
- console.log(`✨ Optimized: ${imageFile} (${reduction}% reduced)`);
420
- optimizedCount++;
421
- } else {
422
- console.log(`📄 Copied: ${imageFile}`);
423
- copiedCount++;
424
- }
425
- }
426
- }
427
- }
428
-
429
- const totalReduction = ((totalSizeBefore - totalSizeAfter) / totalSizeBefore * 100).toFixed(1);
430
- const sizeBefore = (totalSizeBefore / 1024).toFixed(1);
431
- const sizeAfter = (totalSizeAfter / 1024).toFixed(1);
432
-
433
- console.log(`🎉 Processing completed:`);
434
- console.log(` ✨ ${optimizedCount} images optimized`);
435
- console.log(` 📄 ${copiedCount} files copied`);
436
- console.log(` 📊 Size: ${sizeBefore}KB → ${sizeAfter}KB (${totalReduction}% reduced)`);
437
-
438
- } catch (error) {
439
- console.error('❌ Error in copyAndOptimizeImages:', error);
440
- }
441
- }
442
-
443
- // Copy branding assets to dist/images
444
- async function copyBrandingAssets() {
445
- try {
446
- const brandingDir = path.resolve(process.cwd(), 'branding');
447
- const distImagesDir = path.resolve(process.cwd(), 'dist/images');
448
-
449
- if (!fs.existsSync(brandingDir)) {
450
- console.log('📁 branding/ does not exist, skipping branding assets');
451
- return;
452
- }
453
-
454
- await fs.promises.mkdir(distImagesDir, { recursive: true });
455
-
456
- // Copy logos (flattened to images/)
457
- const logosDir = path.join(brandingDir, 'logos');
458
- if (fs.existsSync(logosDir)) {
459
- const logoFiles = await glob('**/*.svg', { cwd: logosDir, nodir: true });
460
- for (const file of logoFiles) {
461
- const sourcePath = path.join(logosDir, file);
462
- const fileName = path.basename(file); // Get just the filename to flatten structure
463
- const destPath = path.join(distImagesDir, fileName);
464
- await fs.promises.copyFile(sourcePath, destPath);
465
- }
466
- }
467
-
468
- // Copy header background images
469
- const imagesDir = path.join(brandingDir, 'images');
470
- if (fs.existsSync(imagesDir)) {
471
- const imageFiles = await glob('*.svg', { cwd: imagesDir, nodir: true });
472
- for (const file of imageFiles) {
473
- const sourcePath = path.join(imagesDir, file);
474
- const destPath = path.join(distImagesDir, file);
475
- await fs.promises.copyFile(sourcePath, destPath);
476
- }
477
- }
478
-
479
- console.log('✅ Branding assets copied to dist/images/');
480
- } catch (error) {
481
- console.error(' Error in copyBrandingAssets:', error);
482
- }
483
- }
484
-
485
- // Function to copy assets (updated to use optimization)
486
- async function copyAssets() {
487
- try {
488
- // Copy and optimize images
489
- await copyAndOptimizeImages();
490
-
491
- // Copy branding assets
492
- await copyBrandingAssets();
493
-
494
- // Copy license if it exists
495
- const licenseFile = path.resolve(process.cwd(), 'EUPL-1.2.txt');
496
- if (fs.existsSync(licenseFile)) {
497
- await fs.promises.copyFile(licenseFile, path.join(process.cwd(), 'dist', 'EUPL-1.2.txt'));
498
- }
499
- } catch (error) {
500
- console.error('Error copying assets:', error);
501
- }
502
- }
503
-
504
- // Final verification to ensure there are no common errors
505
- async function validateBuild() {
506
- try {
507
- const distPath = path.resolve(process.cwd(), 'dist');
508
-
509
- // 1. Check that there are no src/ references in HTML files
510
- const htmlFiles = await glob('**/*.html', { cwd: distPath });
511
-
512
- for (const htmlFile of htmlFiles) {
513
- const filePath = path.join(distPath, htmlFile);
514
- const content = await fs.promises.readFile(filePath, 'utf8');
515
-
516
- // Search for /src/ or src/ references
517
- if (content.includes('/src/') || content.includes('src/')) {
518
-
519
- // Auto-correct
520
- let correctedContent = content
521
- .replace(/href=["']\/src\/([^"']*)["']/g, 'href="$1"')
522
- .replace(/href=["']src\/([^"']*)["']/g, 'href="$1"')
523
- .replace(/src=["']\/src\/([^"']*)["']/g, 'src="$1"')
524
- .replace(/src=["']src\/([^"']*)["']/g, 'src="$1"');
525
-
526
- await fs.promises.writeFile(filePath, correctedContent);
527
- }
528
- }
529
-
530
- // 2. Check that there are no absolute references in scripts
531
- for (const htmlFile of htmlFiles) {
532
- const filePath = path.join(distPath, htmlFile);
533
- const content = await fs.promises.readFile(filePath, 'utf8');
534
-
535
- // Search for script tags with src that starts with "/"
536
- const hasAbsoluteScripts = /<script[^>]*src=["']\/[^"']*["'][^>]*>/g.test(content);
537
-
538
- if (hasAbsoluteScripts) {
539
-
540
- // Auto-correct
541
- let correctedContent = content.replace(
542
- /<script([^>]*)src=["']\/([^"']*)["']([^>]*)>/g,
543
- '<script$1src="$2"$3>'
544
- );
545
-
546
- await fs.promises.writeFile(filePath, correctedContent);
547
- }
548
- }
549
-
550
- // 3. Check JS files
551
- const jsFiles = await glob('js/**/*.js', { cwd: distPath });
552
-
553
- for (const jsFile of jsFiles) {
554
- const filePath = path.join(distPath, jsFile);
555
- const content = await fs.promises.readFile(filePath, 'utf8');
556
-
557
- // Search for CSS imports
558
- if (content.includes('.css"') || content.includes(".css'")) {
559
-
560
- // Comment out these imports
561
- let correctedContent = content.replace(
562
- /import\s+['"](.+\.css)['"];?/g,
563
- '// CSS import removed: $1'
564
- );
565
-
566
- await fs.promises.writeFile(filePath, correctedContent);
567
- }
568
- }
569
-
570
- } catch (error) {
571
- console.error('Error validating build:', error);
572
- }
573
- }
574
-
575
- // Correct CSS image paths for production
576
- async function rewriteCssImagePaths() {
577
- const stylePath = path.resolve(process.cwd(), 'dist/css/styles.css');
578
- if (!fs.existsSync(stylePath)) return;
579
- let content = await fs.promises.readFile(stylePath, 'utf8');
580
- content = content.replace(/url\((['"]?)\/images\//g, 'url($1../images/');
581
- await fs.promises.writeFile(stylePath, content);
582
- }
583
-
584
- // Correct HTML image paths for production (from /images/, /branding/logos/, /branding/images/ to ../images/)
585
- async function rewriteHtmlImagePaths() {
586
- const distDir = path.resolve(process.cwd(), 'dist');
587
- if (!fs.existsSync(distDir)) return;
588
-
589
- const htmlFiles = await glob('**/*.html', { cwd: distDir, nodir: true });
590
-
591
- for (const htmlFile of htmlFiles) {
592
- const filePath = path.join(distDir, htmlFile);
593
- let content = await fs.promises.readFile(filePath, 'utf8');
594
-
595
- // Replace src="/images/ with src="./images/
596
- let modified = content.replace(/src="(\/images\/)/g, 'src="./images/');
597
-
598
- // Replace srcset="/images/ with srcset="./images/
599
- modified = modified.replace(/srcset="(\/images\/)/g, 'srcset="./images/');
600
-
601
- // Replace src="/branding/logos/ with src="./images/
602
- modified = modified.replace(/src="(\/branding\/logos\/)/g, 'src="./images/');
603
-
604
- // Replace srcset="/branding/logos/ with srcset="./images/
605
- modified = modified.replace(/srcset="(\/branding\/logos\/)/g, 'srcset="./images/');
606
-
607
- // Replace src="/branding/images/ with src="./images/
608
- modified = modified.replace(/src="(\/branding\/images\/)/g, 'src="./images/');
609
-
610
- // Replace srcset="/branding/images/ with srcset="./images/
611
- modified = modified.replace(/srcset="(\/branding\/images\/)/g, 'srcset="./images/');
612
-
613
- if (modified !== content) {
614
- await fs.promises.writeFile(filePath, modified);
615
- }
616
- }
617
- }
618
-
619
- // Custom Nunjucks plugin
620
- function customNunjucksPlugin() {
621
- let env;
622
-
623
- return {
624
- name: 'vite-plugin-custom-nunjucks',
625
-
626
- configureServer({ middlewares, watcher, ws }) {
627
-
628
- // Configure Nunjucks with development-specific options
629
- env = setupNunjucksEnvironment(['docs', 'src/templates'], {
630
- watch: true,
631
- throwOnUndefined: false
632
- });
633
-
634
- // Watch for CSS and NJK files
635
- watcher.add(['src/**/*.css', 'docs/**/*.njk', 'src/**/*.njk']);
636
- watcher.on('change', (path) => {
637
- ws.send({
638
- type: 'full-reload'
639
- });
640
- });
641
-
642
- // Middleware to serve branding assets in development
643
- middlewares.use(async (req, res, next) => {
644
- if (req.url.startsWith('/branding/')) {
645
- const filePath = path.join(process.cwd(), req.url);
646
- if (fs.existsSync(filePath)) {
647
- const ext = path.extname(filePath).toLowerCase();
648
- const mimeTypes = {
649
- '.svg': 'image/svg+xml',
650
- '.png': 'image/png',
651
- '.jpg': 'image/jpeg',
652
- '.jpeg': 'image/jpeg',
653
- '.webp': 'image/webp'
654
- };
655
- res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
656
- return res.end(fs.readFileSync(filePath));
657
- }
658
- }
659
- next();
660
- });
661
-
662
- // Helper function to find file recursively in directories
663
- function findFileRecursive(dir, fileName) {
664
- if (!fs.existsSync(dir)) return null;
665
- const files = fs.readdirSync(dir);
666
- for (const file of files) {
667
- const fullPath = path.join(dir, file);
668
- if (fs.statSync(fullPath).isDirectory()) {
669
- const found = findFileRecursive(fullPath, fileName);
670
- if (found) return found;
671
- } else if (file === fileName) {
672
- return fullPath;
673
- }
674
- }
675
- return null;
676
- }
677
-
678
- // Middleware to serve branding assets from /images/ in development
679
- middlewares.use(async (req, res, next) => {
680
- if (req.url.startsWith('/images/')) {
681
- let filePath = path.join(process.cwd(), 'public', req.url);
682
-
683
- if (!fs.existsSync(filePath)) {
684
- const fileName = path.basename(req.url);
685
-
686
- // Search in branding/logos (including subdirectories like eu/)
687
- const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
688
- const foundInLogos = findFileRecursive(brandingLogosPath, fileName);
689
-
690
- if (foundInLogos) {
691
- filePath = foundInLogos;
692
- } else {
693
- // Search in branding/images
694
- const brandingImagesPath = path.join(process.cwd(), 'branding/images');
695
- const foundInImages = findFileRecursive(brandingImagesPath, fileName);
696
- if (foundInImages) {
697
- filePath = foundInImages;
698
- }
699
- }
700
- }
701
-
702
- if (fs.existsSync(filePath)) {
703
- const ext = path.extname(filePath).toLowerCase();
704
- const mimeTypes = {
705
- '.svg': 'image/svg+xml',
706
- '.png': 'image/png',
707
- '.jpg': 'image/jpeg',
708
- '.jpeg': 'image/jpeg',
709
- '.webp': 'image/webp'
710
- };
711
- res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
712
- return res.end(fs.readFileSync(filePath));
713
- }
714
- }
715
- next();
716
- });
717
-
718
- // Middleware to serve branding assets from /branding/logos/ in development
719
- middlewares.use(async (req, res, next) => {
720
- if (req.url.startsWith('/branding/logos/')) {
721
- const fileName = path.basename(req.url);
722
- const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
723
- const filePath = findFileRecursive(brandingLogosPath, fileName);
724
-
725
- if (filePath && fs.existsSync(filePath)) {
726
- const ext = path.extname(filePath).toLowerCase();
727
- const mimeTypes = {
728
- '.svg': 'image/svg+xml',
729
- '.png': 'image/png',
730
- '.jpg': 'image/jpeg',
731
- '.jpeg': 'image/jpeg',
732
- '.webp': 'image/webp'
733
- };
734
- res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
735
- return res.end(fs.readFileSync(filePath));
736
- }
737
- }
738
- next();
739
- });
740
-
741
- // Middleware to serve branding assets from /branding/images/ in development
742
- middlewares.use(async (req, res, next) => {
743
- if (req.url.startsWith('/branding/images/')) {
744
- const fileName = path.basename(req.url);
745
- const brandingImagesPath = path.join(process.cwd(), 'branding/images');
746
- const filePath = findFileRecursive(brandingImagesPath, fileName);
747
-
748
- if (filePath && fs.existsSync(filePath)) {
749
- const ext = path.extname(filePath).toLowerCase();
750
- const mimeTypes = {
751
- '.svg': 'image/svg+xml',
752
- '.png': 'image/png',
753
- '.jpg': 'image/jpeg',
754
- '.jpeg': 'image/jpeg',
755
- '.webp': 'image/webp'
756
- };
757
- res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
758
- return res.end(fs.readFileSync(filePath));
759
- }
760
- }
761
- next();
762
- });
763
-
764
- // Middleware to transform paths in development (keep as is)
765
- middlewares.use(async (req, res, next) => {
766
- // Intercept requests to /css/ and redirect to /src/css/
767
- if (req.url.startsWith('/css/')) {
768
- req.url = req.url.replace('/css/', '/src/css/');
769
- }
770
-
771
- // Intercept requests to /js/ and redirect to /src/js/
772
- if (req.url.startsWith('/js/')) {
773
- req.url = req.url.replace('/js/', '/src/js/');
774
- }
775
-
776
- // Set correct MIME type for JS
777
- if (req.url.endsWith('.js')) {
778
- res.setHeader('Content-Type', 'application/javascript');
779
- }
780
-
781
- next();
782
- });
783
-
784
- // Middleware for CSS
785
- middlewares.use(async (req, res, next) => {
786
- if (req.url.startsWith('/src/css/')) {
787
- return next();
788
- }
789
- next();
790
- });
791
-
792
- // Middleware for .html
793
- middlewares.use(async (req, res, next) => {
794
- if (req.url === '/') {
795
- req.url = '/index.html';
796
- }
797
-
798
- if (req.url.endsWith('.html')) {
799
- try {
800
- const templatePath = req.url.slice(1);
801
- const html = env.render(templatePath);
802
- res.setHeader('Content-Type', 'text/html');
803
- return res.end(html);
804
- } catch (e) {
805
- console.error('Error rendering template:', e.message);
806
- return next(e);
807
- }
808
- }
809
- next();
810
- });
811
- },
812
-
813
- configurePreviewServer(server) {
814
- const { middlewares } = server;
815
-
816
-
817
- // Configure Nunjucks with preview-specific options
818
- env = setupNunjucksEnvironment(['docs', 'src/templates'], {
819
- watch: false,
820
- throwOnUndefined: false
821
- });
822
-
823
- // Configure the middlewares
824
- middlewares.use(async (req, res, next) => {
825
- if (req.url === '/') {
826
- req.url = '/index.html';
827
- }
828
-
829
- if (req.url.endsWith('.html')) {
830
- try {
831
- const templatePath = req.url.slice(1);
832
- const html = env.render(templatePath);
833
- res.setHeader('Content-Type', 'text/html');
834
- return res.end(html);
835
- } catch (e) {
836
- console.error('Error in preview:', e.message);
837
- return next(e);
838
- }
839
- }
840
- next();
841
- });
842
- },
843
-
844
- transform(content, id) {
845
- if (id.endsWith('.njk')) {
846
- const html = env.renderString(content);
847
- return {
848
- code: `export default ${JSON.stringify(html)}`,
849
- map: null
850
- };
851
- }
852
- },
853
-
854
- async buildStart() {
855
- // Configure Nunjucks for the build
856
- env = setupNunjucksEnvironment(['docs', 'docs/ds', 'src/templates'], {
857
- watch: false,
858
- throwOnUndefined: false
859
- });
860
- },
861
-
862
- async closeBundle() {
863
- console.log('🚀 Starting custom build process...');
864
-
865
- // Step 1: Copy and optimize assets (including images) first
866
- await copyAssets();
867
-
868
- // Step 2: Correct CSS image paths for production
869
- await rewriteCssImagePaths();
870
-
871
- // Step 3: Skip processing JavaScript files - let Vite handle bundling
872
- // (Vite will resolve npm dependencies like @floating-ui/dom correctly)
873
- // await processJavaScriptFiles();
874
-
875
- // Step 4: Process Nunjucks files (images are already in dist)
876
- await processNunjucksFiles(env);
877
-
878
- // Step 4b: Correct HTML image paths for production (after HTML files are generated)
879
- await rewriteHtmlImagePaths();
880
-
881
- // Step 5: Final validation to find and fix common errors
882
- await validateBuild();
883
-
884
- console.log('✅ Custom build completed');
885
- }
886
- };
887
- }
888
-
889
- // Main Vite configuration
890
- export default defineConfig({
891
- plugins: [
892
- brandingPlugin(brandingConfig),
893
- customNunjucksPlugin(),
894
- tailwindcss()
895
- ],
896
- build: {
897
- outDir: 'dist',
898
- minify: true,
899
- emptyOutDir: true,
900
- cssCodeSplit: false,
901
- manifest: true,
902
- rollupOptions: {
903
- input: {
904
- index: path.resolve(__dirname, 'src/js/index.js')
905
- },
906
- output: {
907
- entryFileNames: 'js/[name].js',
908
- chunkFileNames: 'js/[name]-[hash].js',
909
- assetFileNames: (assetInfo) => {
910
- // Rename style.css to styles.css
911
- if (assetInfo.name === 'style.css') {
912
- return 'css/styles.css';
913
- }
914
-
915
- // Handle other asset types
916
- const info = assetInfo.name.split('.');
917
- const extType = info[info.length - 1];
918
- if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
919
- return 'images/[name][extname]';
920
- }
921
- if (/css/i.test(extType)) {
922
- return 'css/[name][extname]';
923
- }
924
- return '[name][extname]';
925
- },
926
- format: 'es',
927
- }
928
- }
929
- },
930
- server: {
931
- open: '/',
932
- watch: {
933
- ignored: ['!**/docs/**', '!**/src/templates/**']
934
- },
935
- host: '0.0.0.0',
936
- allowedHosts: [
937
- '82c7d68c-1131-4db5-bf36-165d8f8b1d98-00-2b55tnaqky432.worf.replit.dev',
938
- 'localhost'
939
- ]
940
- },
941
- resolve: {
942
- alias: {
943
- '@': path.resolve(__dirname, 'src'),
944
- '/src': path.resolve(__dirname, 'src'),
945
- '@css': path.resolve(__dirname, 'src/css'),
946
- '@components': path.resolve(__dirname, 'src/templates/components'),
947
- '@ds': path.resolve(__dirname, 'docs/ds'),
948
- '@images': path.resolve(__dirname, 'public/images')
949
- }
950
- },
951
- publicDir: 'public',
952
- optimizeDeps: {
953
- include: ['tailwindcss']
954
- }
955
- });
1
+ import tailwindcss from '@tailwindcss/vite';
2
+ import fs from 'fs';
3
+ import { glob } from 'glob';
4
+ import hljs from 'highlight.js';
5
+ import beautify from 'js-beautify';
6
+ import yaml from 'js-yaml';
7
+ import nunjucks from 'nunjucks';
8
+ import { outdent } from 'outdent';
9
+ import path from 'path';
10
+ import sharp from 'sharp';
11
+ import { defineConfig } from 'vite';
12
+ import { brandingPlugin, loadBrandingConfig } from './branding/vite-branding-plugin.js';
13
+
14
+ // Load branding configuration
15
+ const brandingConfig = await loadBrandingConfig(
16
+ process.env.BRANDING_CONFIG || 'default',
17
+ process.cwd(),
18
+ 'branding'
19
+ );
20
+
21
+ // Helper para servir archivos con el MIME type correcto
22
+ function serveFileWithMimeType(filePath, res) {
23
+ if (!fs.existsSync(filePath)) return false;
24
+
25
+ const ext = path.extname(filePath).toLowerCase();
26
+ const mimeTypes = {
27
+ '.svg': 'image/svg+xml',
28
+ '.png': 'image/png',
29
+ '.jpg': 'image/jpeg',
30
+ '.jpeg': 'image/jpeg',
31
+ '.webp': 'image/webp',
32
+ '.ico': 'image/x-icon',
33
+ '.js': 'application/javascript',
34
+ '.css': 'text/css',
35
+ '.html': 'text/html'
36
+ };
37
+
38
+ res.setHeader('Content-Type', mimeTypes[ext] || 'application/octet-stream');
39
+ res.end(fs.readFileSync(filePath));
40
+ return true;
41
+ }
42
+
43
+ // Helper functions
44
+ function kebabCaseToPascalCase(value) {
45
+ return value
46
+ .toLowerCase()
47
+ .split('-')
48
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
49
+ .join('');
50
+ }
51
+
52
+ function getBeautifyOptions() {
53
+ const options = beautify.html.defaultOptions();
54
+ return {
55
+ indent_size: 2,
56
+ inline: options.inline.filter((tag) => !['label'].includes(tag)),
57
+ max_preserve_newlines: 0,
58
+ wrap_attributes: 'preserve'
59
+ };
60
+ }
61
+
62
+ // Function to configure Nunjucks environment
63
+ function setupNunjucksEnvironment(paths, options = {}) {
64
+ const defaultOptions = {
65
+ autoescape: true,
66
+ trimBlocks: true,
67
+ lstripBlocks: true,
68
+ throwOnUndefined: false,
69
+ noCache: true
70
+ };
71
+
72
+ const env = nunjucks.configure(paths, { ...defaultOptions, ...options });
73
+
74
+ // Global configuration
75
+ env.addGlobal('assetsUrl', '/src');
76
+ env.addGlobal('branding', brandingConfig);
77
+
78
+ env.addGlobal('loadComponentParams', function (componentName) {
79
+ try {
80
+ const yamlPath = path.join(process.cwd(), 'src/templates/components', componentName, `params.${componentName}.yaml`);
81
+
82
+ if (fs.existsSync(yamlPath)) {
83
+ const content = fs.readFileSync(yamlPath, 'utf8');
84
+ const parsedYaml = yaml.load(content);
85
+ const dumpedYaml = yaml.dump(parsedYaml);
86
+ return dumpedYaml;
87
+ }
88
+ console.error(`YAML file not found: ${yamlPath}`);
89
+ return '';
90
+ } catch (err) {
91
+ console.error(`Error loading YAML for ${componentName}:`, err);
92
+ return '';
93
+ }
94
+ });
95
+
96
+ // Four globals for code blocks
97
+ env.addGlobal('getHTMLCodeFromExample', function (componentName, params, pathName) {
98
+ const templatePath = `${pathName}s/${componentName}/_template.${componentName}.njk`;
99
+ const html = env.render(templatePath, { params }).trim();
100
+ return beautify.html(html, getBeautifyOptions());
101
+ });
102
+
103
+ env.addGlobal('getHTMLCodeFromFile', function (templatePath) {
104
+ const html = env.render(templatePath).trim();
105
+ return beautify.html(html, getBeautifyOptions());
106
+ });
107
+
108
+ env.addGlobal('getNunjucksCodeFromExample', function (componentName, params, pathName) {
109
+ const macroName = kebabCaseToPascalCase(componentName);
110
+ return outdent`
111
+ {% from "${pathName}s/${componentName}/_macro.${componentName}.njk" import ${pathName}${macroName} %}
112
+ {{ ${pathName}${macroName}(${JSON.stringify(params, undefined, 2)
113
+ }) }}
114
+ `;
115
+ });
116
+
117
+ env.addGlobal('getNunjucksCodeFromFile', function (templatePath) {
118
+ try {
119
+ const codeFromFile = fs.readFileSync(
120
+ path.join(process.cwd(), 'src/templates', templatePath),
121
+ 'utf8'
122
+ );
123
+ return outdent`
124
+ ${codeFromFile}
125
+ `;
126
+ } catch (err) {
127
+ console.error(`Error reading file ${templatePath}:`, err);
128
+ return '';
129
+ }
130
+ });
131
+
132
+ // Filter configuration
133
+ env.addFilter('filtercaller', function (params) {
134
+ if (params && typeof params === 'object') {
135
+ const filteredParams = { ...params };
136
+ delete filteredParams.caller;
137
+ return filteredParams;
138
+ }
139
+ return params;
140
+ });
141
+
142
+ env.addFilter('filterslugify', function (str) {
143
+ if (typeof str !== 'string') {
144
+ return '';
145
+ }
146
+ let slug = str.replace(/\s+/g, '-');
147
+ slug = slug.replace(/[^\w-]+/g, '');
148
+ slug = slug.toLowerCase();
149
+ return slug;
150
+ });
151
+
152
+ env.addFilter('highlight', function (code, language) {
153
+ try {
154
+ return hljs.highlight(code.trim(), {
155
+ language: language || 'plaintext'
156
+ }).value;
157
+ } catch (err) {
158
+ console.error('Error highlighting code:', err);
159
+ return code;
160
+ }
161
+ });
162
+
163
+ env.addFilter('filterescapeltgt', function (str) {
164
+ if (typeof str !== 'string' && typeof str !== 'object') return str;
165
+ if (typeof str !== 'string') str = JSON.stringify(str);
166
+ return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
167
+ });
168
+
169
+ env.addFilter('filterquotes', function (str) {
170
+ if (typeof str !== 'string') return str;
171
+ let inTag = false;
172
+ let result = '';
173
+ let currentQuote = null;
174
+ for (let i = 0; i < str.length; i++) {
175
+ const char = str[i];
176
+ if (char === '<') {
177
+ inTag = true;
178
+ result += char;
179
+ continue;
180
+ }
181
+ if (char === '>') {
182
+ inTag = false;
183
+ result += char;
184
+ continue;
185
+ }
186
+ if (inTag) {
187
+ if (char === "'" || char === '"') {
188
+ if (currentQuote === null) {
189
+ currentQuote = char;
190
+ result += '"';
191
+ } else if (currentQuote === char) {
192
+ currentQuote = null;
193
+ result += '"';
194
+ } else {
195
+ result += char;
196
+ }
197
+ } else {
198
+ result += char;
199
+ }
200
+ } else {
201
+ result += char;
202
+ }
203
+ }
204
+ return result;
205
+ });
206
+
207
+ env.addFilter('filterversion', function () {
208
+ const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
209
+ return pkg.version;
210
+ });
211
+
212
+ return env;
213
+ }
214
+
215
+ // Improved function to process Nunjucks files during build
216
+ async function processNunjucksFiles(env) {
217
+ try {
218
+ const docsPath = path.resolve(process.cwd(), 'docs');
219
+ const htmlFiles = await glob('*.html', { cwd: docsPath });
220
+
221
+
222
+ for (const htmlFile of htmlFiles) {
223
+ try {
224
+ const sourcePath = path.join(docsPath, htmlFile);
225
+ const outputPath = path.join(process.cwd(), 'dist', htmlFile);
226
+
227
+ const content = await fs.promises.readFile(sourcePath, 'utf8');
228
+ let processedHtml = env.renderString(content);
229
+
230
+ // Transformations for relative paths
231
+ processedHtml = processedHtml.replace(/href=["']\/src\/css\/([^"']*)["']/g, 'href="css/$1"');
232
+ processedHtml = processedHtml.replace(/href=["']src\/css\/([^"']*)["']/g, 'href="css/$1"');
233
+
234
+ // Transform JS paths (ensure they are relative without leading slash)
235
+ processedHtml = processedHtml.replace(/src=["']\/src\/js\/([^"']*)["']/g, 'src="js/$1"');
236
+ processedHtml = processedHtml.replace(/src=["']src\/js\/([^"']*)["']/g, 'src="js/$1"');
237
+
238
+ // Also process if any route with /js/ (absolute) remains
239
+ processedHtml = processedHtml.replace(/src=["']\/js\/([^"']*)["']/g, 'src="js/$1"');
240
+
241
+ // Transform image paths
242
+ processedHtml = processedHtml.replace(/src=["']\/src\/images\/([^"']*)["']/g, 'src="images/$1"');
243
+ processedHtml = processedHtml.replace(/src=["']src\/images\/([^"']*)["']/g, 'src="images/$1"');
244
+
245
+ await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
246
+ await fs.promises.writeFile(outputPath, processedHtml);
247
+ } catch (fileError) {
248
+ console.error(`Error processing file ${htmlFile}:`, fileError);
249
+ }
250
+ }
251
+ } catch (error) {
252
+ console.error('Error in processNunjucksFiles:', error);
253
+ }
254
+ }
255
+
256
+ // NEW FUNCTION: Process JavaScript files to remove CSS imports and fix modules
257
+ async function processJavaScriptFiles() {
258
+ try {
259
+ const srcJsDir = path.resolve(process.cwd(), 'src/js');
260
+ const distJsDir = path.resolve(process.cwd(), 'dist/js');
261
+
262
+ // Create dist/js directory if it doesn't exist
263
+ await fs.promises.mkdir(distJsDir, { recursive: true });
264
+
265
+ // 1. Copy npm packages to the vendor folder
266
+ const vendorDir = path.join(distJsDir, 'vendor');
267
+ await fs.promises.mkdir(vendorDir, { recursive: true });
268
+
269
+ // 2. Process all JS files
270
+ const jsFiles = await glob('**/*.js', { cwd: srcJsDir });
271
+
272
+ for (const jsFile of jsFiles) {
273
+ const sourcePath = path.join(srcJsDir, jsFile);
274
+ const destPath = path.join(distJsDir, jsFile);
275
+
276
+ // Create directory if necessary
277
+ await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
278
+
279
+ // Read file content
280
+ const content = await fs.promises.readFile(sourcePath, 'utf8');
281
+
282
+ // Transform the content
283
+ let processedContent = content;
284
+
285
+ // a) Remove CSS file imports
286
+ processedContent = processedContent.replace(
287
+ /import\s+['"](.+\.css)['"];?/g,
288
+ '// CSS import removed: $1'
289
+ );
290
+
291
+ // c) Fix other relative import paths between JS files
292
+ processedContent = processedContent.replace(
293
+ /from\s+["']\/src\/js\/([^"']*)["']/g,
294
+ (match, importPath) => {
295
+ const depth = jsFile.split('/').length - 1;
296
+ const prefix = depth > 0 ? '../'.repeat(depth) : './';
297
+ return `from "${prefix}${importPath}"`;
298
+ }
299
+ );
300
+
301
+ processedContent = processedContent.replace(
302
+ /from\s+["']src\/js\/([^"']*)["']/g,
303
+ (match, importPath) => {
304
+ const depth = jsFile.split('/').length - 1;
305
+ const prefix = depth > 0 ? '../'.repeat(depth) : './';
306
+ return `from "${prefix}${importPath}"`;
307
+ }
308
+ );
309
+
310
+ // Write the processed file
311
+ await fs.promises.writeFile(destPath, processedContent);
312
+ }
313
+
314
+ } catch (error) {
315
+ console.error('Error processing JavaScript:', error);
316
+ }
317
+ }
318
+
319
+ // Optimize image with Sharp
320
+ async function optimizeImage(sourcePath, destPath, fileExtension) {
321
+ try {
322
+ const image = sharp(sourcePath);
323
+
324
+ switch (fileExtension.toLowerCase()) {
325
+ case '.jpg':
326
+ case '.jpeg':
327
+ await image
328
+ .jpeg({
329
+ quality: 80,
330
+ mozjpeg: true,
331
+ progressive: true
332
+ })
333
+ .toFile(destPath);
334
+ break;
335
+
336
+ case '.png':
337
+ await image
338
+ .png({
339
+ quality: 80,
340
+ palette: true,
341
+ compressionLevel: 9
342
+ })
343
+ .toFile(destPath);
344
+ break;
345
+
346
+ case '.webp':
347
+ await image
348
+ .webp({
349
+ quality: 80,
350
+ effort: 6
351
+ })
352
+ .toFile(destPath);
353
+ break;
354
+
355
+ case '.avif':
356
+ await image
357
+ .avif({
358
+ quality: 80,
359
+ effort: 4
360
+ })
361
+ .toFile(destPath);
362
+ break;
363
+
364
+ case '.svg':
365
+ // SVGs are not processed with Sharp, they are copied directly
366
+ await fs.promises.copyFile(sourcePath, destPath);
367
+ break;
368
+
369
+ default:
370
+ // For other formats, copy without optimizing
371
+ await fs.promises.copyFile(sourcePath, destPath);
372
+ break;
373
+ }
374
+
375
+ return true;
376
+ } catch (error) {
377
+ console.error(`❌ Error optimizing ${sourcePath}:`, error.message);
378
+ // If optimization fails, try direct copy as fallback
379
+ try {
380
+ await fs.promises.copyFile(sourcePath, destPath);
381
+ return true;
382
+ } catch (copyError) {
383
+ console.error(`❌ Error copying ${sourcePath}:`, copyError.message);
384
+ return false;
385
+ }
386
+ }
387
+ }
388
+
389
+ // UPDATED FUNCTION: Copy and optimize all images
390
+ async function copyAndOptimizeImages() {
391
+ try {
392
+ const srcImagesDir = path.resolve(process.cwd(), 'public/images');
393
+ const distImagesDir = path.resolve(process.cwd(), 'dist/images');
394
+
395
+ // Check if public/images folder exists
396
+ if (!fs.existsSync(srcImagesDir)) {
397
+ console.log('📁 public/images does not exist, skipping image optimization');
398
+ return;
399
+ }
400
+
401
+ // Create dist/images directory if it doesn't exist
402
+ await fs.promises.mkdir(distImagesDir, { recursive: true });
403
+
404
+ // Get all image files recursively
405
+ const imageFiles = await glob('**/*.{png,jpg,jpeg,gif,svg,webp,avif,ico,bmp,tiff}', {
406
+ cwd: srcImagesDir,
407
+ nodir: true
408
+ });
409
+
410
+ console.log(`🖼️ Optimizing ${imageFiles.length} images with Sharp...`);
411
+
412
+ let optimizedCount = 0;
413
+ let copiedCount = 0;
414
+ let totalSizeBefore = 0;
415
+ let totalSizeAfter = 0;
416
+
417
+ for (const imageFile of imageFiles) {
418
+ const sourcePath = path.join(srcImagesDir, imageFile);
419
+ const destPath = path.join(distImagesDir, imageFile);
420
+ const fileExtension = path.extname(imageFile);
421
+
422
+ // Create directory if necessary (for subdirectories)
423
+ await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
424
+
425
+ // Get original file size
426
+ const statsBefore = await fs.promises.stat(sourcePath);
427
+ totalSizeBefore += statsBefore.size;
428
+
429
+ // Optimize or copy image
430
+ const success = await optimizeImage(sourcePath, destPath, fileExtension);
431
+
432
+ if (success) {
433
+ // Get file size after optimization
434
+ if (fs.existsSync(destPath)) {
435
+ const statsAfter = await fs.promises.stat(destPath);
436
+ totalSizeAfter += statsAfter.size;
437
+
438
+ const reduction = ((statsBefore.size - statsAfter.size) / statsBefore.size * 100).toFixed(1);
439
+
440
+ if (['.jpg', '.jpeg', '.png', '.webp', '.avif'].includes(fileExtension.toLowerCase())) {
441
+ console.log(`✨ Optimized: ${imageFile} (${reduction}% reduced)`);
442
+ optimizedCount++;
443
+ } else {
444
+ console.log(`📄 Copied: ${imageFile}`);
445
+ copiedCount++;
446
+ }
447
+ }
448
+ }
449
+ }
450
+
451
+ const totalReduction = ((totalSizeBefore - totalSizeAfter) / totalSizeBefore * 100).toFixed(1);
452
+ const sizeBefore = (totalSizeBefore / 1024).toFixed(1);
453
+ const sizeAfter = (totalSizeAfter / 1024).toFixed(1);
454
+
455
+ console.log(`🎉 Processing completed:`);
456
+ console.log(` ✨ ${optimizedCount} images optimized`);
457
+ console.log(` 📄 ${copiedCount} files copied`);
458
+ console.log(` 📊 Size: ${sizeBefore}KB → ${sizeAfter}KB (${totalReduction}% reduced)`);
459
+
460
+ } catch (error) {
461
+ console.error('❌ Error in copyAndOptimizeImages:', error);
462
+ }
463
+ }
464
+
465
+ // Copy branding assets to dist/images
466
+ async function copyBrandingAssets() {
467
+ try {
468
+ const brandingDir = path.resolve(process.cwd(), 'branding');
469
+ const distImagesDir = path.resolve(process.cwd(), 'dist/images');
470
+
471
+ if (!fs.existsSync(brandingDir)) {
472
+ console.log('📁 branding/ does not exist, skipping branding assets');
473
+ return;
474
+ }
475
+
476
+ await fs.promises.mkdir(distImagesDir, { recursive: true });
477
+
478
+ // Copy logos (flattened to images/)
479
+ const logosDir = path.join(brandingDir, 'logos');
480
+ if (fs.existsSync(logosDir)) {
481
+ const logoFiles = await glob('**/*.svg', { cwd: logosDir, nodir: true });
482
+ for (const file of logoFiles) {
483
+ const sourcePath = path.join(logosDir, file);
484
+ const fileName = path.basename(file); // Get just the filename to flatten structure
485
+ const destPath = path.join(distImagesDir, fileName);
486
+ await fs.promises.copyFile(sourcePath, destPath);
487
+ }
488
+ }
489
+
490
+ // Copy header background images
491
+ const imagesDir = path.join(brandingDir, 'images');
492
+ if (fs.existsSync(imagesDir)) {
493
+ const imageFiles = await glob('*.svg', { cwd: imagesDir, nodir: true });
494
+ for (const file of imageFiles) {
495
+ const sourcePath = path.join(imagesDir, file);
496
+ const destPath = path.join(distImagesDir, file);
497
+ await fs.promises.copyFile(sourcePath, destPath);
498
+ }
499
+ }
500
+
501
+ console.log('✅ Branding assets copied to dist/images/');
502
+ } catch (error) {
503
+ console.error('❌ Error in copyBrandingAssets:', error);
504
+ }
505
+ }
506
+
507
+ // Function to copy assets (updated to use optimization)
508
+ async function copyAssets() {
509
+ try {
510
+ // Copy and optimize images
511
+ await copyAndOptimizeImages();
512
+
513
+ // Copy branding assets
514
+ await copyBrandingAssets();
515
+
516
+ // Copy license if it exists
517
+ const licenseFile = path.resolve(process.cwd(), 'EUPL-1.2.txt');
518
+ if (fs.existsSync(licenseFile)) {
519
+ await fs.promises.copyFile(licenseFile, path.join(process.cwd(), 'dist', 'EUPL-1.2.txt'));
520
+ }
521
+ } catch (error) {
522
+ console.error('Error copying assets:', error);
523
+ }
524
+ }
525
+
526
+ // Final verification to ensure there are no common errors
527
+ async function validateBuild() {
528
+ try {
529
+ const distPath = path.resolve(process.cwd(), 'dist');
530
+
531
+ // 1. Check that there are no src/ references in HTML files
532
+ const htmlFiles = await glob('**/*.html', { cwd: distPath });
533
+
534
+ for (const htmlFile of htmlFiles) {
535
+ const filePath = path.join(distPath, htmlFile);
536
+ const content = await fs.promises.readFile(filePath, 'utf8');
537
+
538
+ // Search for /src/ or src/ references
539
+ if (content.includes('/src/') || content.includes('src/')) {
540
+
541
+ // Auto-correct
542
+ let correctedContent = content
543
+ .replace(/href=["']\/src\/([^"']*)["']/g, 'href="$1"')
544
+ .replace(/href=["']src\/([^"']*)["']/g, 'href="$1"')
545
+ .replace(/src=["']\/src\/([^"']*)["']/g, 'src="$1"')
546
+ .replace(/src=["']src\/([^"']*)["']/g, 'src="$1"');
547
+
548
+ await fs.promises.writeFile(filePath, correctedContent);
549
+ }
550
+ }
551
+
552
+ // 2. Check that there are no absolute references in scripts
553
+ for (const htmlFile of htmlFiles) {
554
+ const filePath = path.join(distPath, htmlFile);
555
+ const content = await fs.promises.readFile(filePath, 'utf8');
556
+
557
+ // Search for script tags with src that starts with "/"
558
+ const hasAbsoluteScripts = /<script[^>]*src=["']\/[^"']*["'][^>]*>/g.test(content);
559
+
560
+ if (hasAbsoluteScripts) {
561
+
562
+ // Auto-correct
563
+ let correctedContent = content.replace(
564
+ /<script([^>]*)src=["']\/([^"']*)["']([^>]*)>/g,
565
+ '<script$1src="$2"$3>'
566
+ );
567
+
568
+ await fs.promises.writeFile(filePath, correctedContent);
569
+ }
570
+ }
571
+
572
+ // 3. Check JS files
573
+ const jsFiles = await glob('js/**/*.js', { cwd: distPath });
574
+
575
+ for (const jsFile of jsFiles) {
576
+ const filePath = path.join(distPath, jsFile);
577
+ const content = await fs.promises.readFile(filePath, 'utf8');
578
+
579
+ // Search for CSS imports
580
+ if (content.includes('.css"') || content.includes(".css'")) {
581
+
582
+ // Comment out these imports
583
+ let correctedContent = content.replace(
584
+ /import\s+['"](.+\.css)['"];?/g,
585
+ '// CSS import removed: $1'
586
+ );
587
+
588
+ await fs.promises.writeFile(filePath, correctedContent);
589
+ }
590
+ }
591
+
592
+ } catch (error) {
593
+ console.error('Error validating build:', error);
594
+ }
595
+ }
596
+
597
+ // Correct CSS image paths for production
598
+ async function rewriteCssImagePaths() {
599
+ const stylePath = path.resolve(process.cwd(), 'dist/css/styles.css');
600
+ if (!fs.existsSync(stylePath)) return;
601
+ let content = await fs.promises.readFile(stylePath, 'utf8');
602
+ content = content.replace(/url\((['"]?)\/images\//g, 'url($1../images/');
603
+ await fs.promises.writeFile(stylePath, content);
604
+ }
605
+
606
+ // Correct HTML image paths for production (from /images/, /branding/logos/, /branding/images/ to ../images/)
607
+ async function rewriteHtmlImagePaths() {
608
+ const distDir = path.resolve(process.cwd(), 'dist');
609
+ if (!fs.existsSync(distDir)) return;
610
+
611
+ const htmlFiles = await glob('**/*.html', { cwd: distDir, nodir: true });
612
+
613
+ // Configuración centralizada de rewrites
614
+ const rewriteRules = [
615
+ // Imágenes en /images/
616
+ { pattern: /src="(\/images\/)/g, replacement: 'src="./images/' },
617
+ { pattern: /srcset="(\/images\/)/g, replacement: 'srcset="./images/' },
618
+ // Imágenes en /branding/logos/
619
+ { pattern: /src="(\/branding\/logos\/)/g, replacement: 'src="./images/' },
620
+ { pattern: /srcset="(\/branding\/logos\/)/g, replacement: 'srcset="./images/' },
621
+ // Imágenes en /branding/images/
622
+ { pattern: /src="(\/branding\/images\/)/g, replacement: 'src="./images/' },
623
+ { pattern: /srcset="(\/branding\/images\/)/g, replacement: 'srcset="./images/' },
624
+ ];
625
+
626
+ for (const htmlFile of htmlFiles) {
627
+ const filePath = path.join(distDir, htmlFile);
628
+ let content = await fs.promises.readFile(filePath, 'utf8');
629
+
630
+ // Aplicar todas las reglas
631
+ let modified = content;
632
+ for (const rule of rewriteRules) {
633
+ modified = modified.replace(rule.pattern, rule.replacement);
634
+ }
635
+
636
+ if (modified !== content) {
637
+ await fs.promises.writeFile(filePath, modified);
638
+ }
639
+ }
640
+ }
641
+
642
+ // Rewrite favicon hrefs from /branding/logos/ to ./images/ (after HTML files are generated)
643
+ async function rewriteFaviconPaths() {
644
+ const distDir = path.resolve(process.cwd(), 'dist');
645
+ const imagesDir = path.resolve(process.cwd(), 'dist/images');
646
+ if (!fs.existsSync(distDir)) return;
647
+
648
+ const htmlFiles = await glob('**/*.html', { cwd: distDir, nodir: true });
649
+
650
+ // Favicon files to rewrite
651
+ const faviconFiles = [
652
+ 'favicon.svg',
653
+ 'favicon.ico',
654
+ 'apple-touch-icon.png'
655
+ ];
656
+
657
+ for (const htmlFile of htmlFiles) {
658
+ const filePath = path.join(distDir, htmlFile);
659
+ let content = await fs.promises.readFile(filePath, 'utf8');
660
+ let modified = content;
661
+
662
+ for (const faviconFile of faviconFiles) {
663
+ const sourcePath = `/branding/logos/${faviconFile}`;
664
+ const destPath = `./images/${faviconFile}`;
665
+
666
+ // Only rewrite if the destination file exists in imagesDir
667
+ const destFilePath = path.join(imagesDir, faviconFile);
668
+ if (fs.existsSync(destFilePath)) {
669
+ const oldHref = `href="${sourcePath}"`;
670
+ const newHref = `href="${destPath}"`;
671
+ modified = modified.replaceAll(oldHref, newHref);
672
+ }
673
+ }
674
+
675
+ if (modified !== content) {
676
+ await fs.promises.writeFile(filePath, modified);
677
+ }
678
+ }
679
+
680
+ if (htmlFiles.length > 0) {
681
+ console.log(`✓ Rewrote favicon paths in ${htmlFiles.length} HTML file(s)`);
682
+ }
683
+ }
684
+
685
+ // Custom Nunjucks plugin
686
+ function customNunjucksPlugin() {
687
+ let env;
688
+
689
+ return {
690
+ name: 'vite-plugin-custom-nunjucks',
691
+
692
+ configureServer({ middlewares, watcher, ws }) {
693
+
694
+ // Configure Nunjucks with development-specific options
695
+ env = setupNunjucksEnvironment(['docs', 'src/templates'], {
696
+ watch: true,
697
+ throwOnUndefined: false
698
+ });
699
+
700
+ // Watch for CSS and NJK files
701
+ watcher.add(['src/**/*.css', 'docs/**/*.njk', 'src/**/*.njk']);
702
+ watcher.on('change', (path) => {
703
+ ws.send({
704
+ type: 'full-reload'
705
+ });
706
+ });
707
+
708
+ // Middleware to serve branding assets in development
709
+ middlewares.use(async (req, res, next) => {
710
+ if (req.url.startsWith('/branding/')) {
711
+ const filePath = path.join(process.cwd(), req.url);
712
+ if (serveFileWithMimeType(filePath, res)) return;
713
+ }
714
+ next();
715
+ });
716
+
717
+ // Helper function to find file recursively in directories
718
+ function findFileRecursive(dir, fileName) {
719
+ if (!fs.existsSync(dir)) return null;
720
+ const files = fs.readdirSync(dir);
721
+ for (const file of files) {
722
+ const fullPath = path.join(dir, file);
723
+ if (fs.statSync(fullPath).isDirectory()) {
724
+ const found = findFileRecursive(fullPath, fileName);
725
+ if (found) return found;
726
+ } else if (file === fileName) {
727
+ return fullPath;
728
+ }
729
+ }
730
+ return null;
731
+ }
732
+
733
+ // Middleware to serve branding assets from /images/ in development
734
+ middlewares.use(async (req, res, next) => {
735
+ if (req.url.startsWith('/images/')) {
736
+ let filePath = path.join(process.cwd(), 'public', req.url);
737
+
738
+ if (!fs.existsSync(filePath)) {
739
+ const fileName = path.basename(req.url);
740
+
741
+ // Search in branding/logos (including subdirectories like eu/)
742
+ const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
743
+ const foundInLogos = findFileRecursive(brandingLogosPath, fileName);
744
+
745
+ if (foundInLogos) {
746
+ filePath = foundInLogos;
747
+ } else {
748
+ // Search in branding/images
749
+ const brandingImagesPath = path.join(process.cwd(), 'branding/images');
750
+ const foundInImages = findFileRecursive(brandingImagesPath, fileName);
751
+ if (foundInImages) {
752
+ filePath = foundInImages;
753
+ }
754
+ }
755
+ }
756
+
757
+ if (fs.existsSync(filePath)) {
758
+ if (serveFileWithMimeType(filePath, res)) return;
759
+ }
760
+ }
761
+ next();
762
+ });
763
+
764
+ // Middleware to serve branding assets from /branding/logos/ in development
765
+ middlewares.use(async (req, res, next) => {
766
+ if (req.url.startsWith('/branding/logos/')) {
767
+ const fileName = path.basename(req.url);
768
+ const brandingLogosPath = path.join(process.cwd(), 'branding/logos');
769
+ const filePath = findFileRecursive(brandingLogosPath, fileName);
770
+
771
+ if (serveFileWithMimeType(filePath, res)) return;
772
+ }
773
+ next();
774
+ });
775
+
776
+ // Middleware to serve branding assets from /branding/images/ in development
777
+ middlewares.use(async (req, res, next) => {
778
+ if (req.url.startsWith('/branding/images/')) {
779
+ const fileName = path.basename(req.url);
780
+ const brandingImagesPath = path.join(process.cwd(), 'branding/images');
781
+ const filePath = findFileRecursive(brandingImagesPath, fileName);
782
+
783
+ if (serveFileWithMimeType(filePath, res)) return;
784
+ }
785
+ next();
786
+ });
787
+
788
+ // Middleware to transform paths in development (keep as is)
789
+ middlewares.use(async (req, res, next) => {
790
+ // Intercept requests to /css/ and redirect to /src/css/
791
+ if (req.url.startsWith('/css/')) {
792
+ req.url = req.url.replace('/css/', '/src/css/');
793
+ }
794
+
795
+ // Intercept requests to /js/ and redirect to /src/js/
796
+ if (req.url.startsWith('/js/')) {
797
+ req.url = req.url.replace('/js/', '/src/js/');
798
+ }
799
+
800
+ // Set correct MIME type for JS
801
+ if (req.url.endsWith('.js')) {
802
+ res.setHeader('Content-Type', 'application/javascript');
803
+ }
804
+
805
+ next();
806
+ });
807
+
808
+ // Middleware for CSS
809
+ middlewares.use(async (req, res, next) => {
810
+ if (req.url.startsWith('/src/css/')) {
811
+ return next();
812
+ }
813
+ next();
814
+ });
815
+
816
+ // Middleware for .html
817
+ middlewares.use(async (req, res, next) => {
818
+ if (req.url === '/') {
819
+ req.url = '/index.html';
820
+ }
821
+
822
+ if (req.url.endsWith('.html')) {
823
+ try {
824
+ const templatePath = req.url.slice(1);
825
+ const html = env.render(templatePath);
826
+ res.setHeader('Content-Type', 'text/html');
827
+ return res.end(html);
828
+ } catch (e) {
829
+ console.error('Error rendering template:', e.message);
830
+ return next(e);
831
+ }
832
+ }
833
+ next();
834
+ });
835
+ },
836
+
837
+ configurePreviewServer(server) {
838
+ const { middlewares } = server;
839
+
840
+
841
+ // Configure Nunjucks with preview-specific options
842
+ env = setupNunjucksEnvironment(['docs', 'src/templates'], {
843
+ watch: false,
844
+ throwOnUndefined: false
845
+ });
846
+
847
+ // Configure the middlewares
848
+ middlewares.use(async (req, res, next) => {
849
+ if (req.url === '/') {
850
+ req.url = '/index.html';
851
+ }
852
+
853
+ if (req.url.endsWith('.html')) {
854
+ try {
855
+ const templatePath = req.url.slice(1);
856
+ const html = env.render(templatePath);
857
+ res.setHeader('Content-Type', 'text/html');
858
+ return res.end(html);
859
+ } catch (e) {
860
+ console.error('Error in preview:', e.message);
861
+ return next(e);
862
+ }
863
+ }
864
+ next();
865
+ });
866
+ },
867
+
868
+ transform(content, id) {
869
+ if (id.endsWith('.njk')) {
870
+ const html = env.renderString(content);
871
+ return {
872
+ code: `export default ${JSON.stringify(html)}`,
873
+ map: null
874
+ };
875
+ }
876
+ },
877
+
878
+ async buildStart() {
879
+ // Configure Nunjucks for the build
880
+ env = setupNunjucksEnvironment(['docs', 'docs/ds', 'src/templates'], {
881
+ watch: false,
882
+ throwOnUndefined: false
883
+ });
884
+ },
885
+
886
+ async closeBundle() {
887
+ console.log('🚀 Starting custom build process...');
888
+
889
+ // Step 1: Copy and optimize assets (including images) first
890
+ await copyAssets();
891
+
892
+ // Step 2: Correct CSS image paths for production
893
+ await rewriteCssImagePaths();
894
+
895
+ // Step 3: Skip processing JavaScript files - let Vite handle bundling
896
+ // (Vite will resolve npm dependencies like @floating-ui/dom correctly)
897
+ // await processJavaScriptFiles();
898
+
899
+ // Step 4: Process Nunjucks files (images are already in dist)
900
+ await processNunjucksFiles(env);
901
+
902
+ // Step 4b: Correct HTML image paths for production (after HTML files are generated)
903
+ await rewriteHtmlImagePaths();
904
+
905
+ // Step 4c: Rewrite favicon paths from /branding/logos/ to ./images/
906
+ await rewriteFaviconPaths();
907
+
908
+ // Step 5: Final validation to find and fix common errors
909
+ await validateBuild();
910
+
911
+ console.log('✅ Custom build completed');
912
+ }
913
+ };
914
+ }
915
+
916
+ // Main Vite configuration
917
+ export default defineConfig({
918
+ plugins: [
919
+ brandingPlugin(brandingConfig),
920
+ customNunjucksPlugin(),
921
+ tailwindcss()
922
+ ],
923
+ build: {
924
+ outDir: 'dist',
925
+ minify: true,
926
+ emptyOutDir: true,
927
+ cssCodeSplit: false,
928
+ manifest: true,
929
+ rollupOptions: {
930
+ input: {
931
+ index: path.resolve(__dirname, 'src/js/index.js')
932
+ },
933
+ output: {
934
+ entryFileNames: 'js/[name].js',
935
+ chunkFileNames: 'js/[name]-[hash].js',
936
+ assetFileNames: (assetInfo) => {
937
+ // Rename style.css to styles.css
938
+ if (assetInfo.name === 'style.css') {
939
+ return 'css/styles.css';
940
+ }
941
+
942
+ // Handle other asset types
943
+ const info = assetInfo.name.split('.');
944
+ const extType = info[info.length - 1];
945
+ if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
946
+ return 'images/[name][extname]';
947
+ }
948
+ if (/css/i.test(extType)) {
949
+ return 'css/[name][extname]';
950
+ }
951
+ return '[name][extname]';
952
+ },
953
+ format: 'es',
954
+ }
955
+ }
956
+ },
957
+ server: {
958
+ open: '/',
959
+ watch: {
960
+ ignored: ['!**/docs/**', '!**/src/templates/**']
961
+ },
962
+ host: '0.0.0.0',
963
+ allowedHosts: [
964
+ '82c7d68c-1131-4db5-bf36-165d8f8b1d98-00-2b55tnaqky432.worf.replit.dev',
965
+ 'localhost'
966
+ ]
967
+ },
968
+ resolve: {
969
+ alias: {
970
+ '@': path.resolve(__dirname, 'src'),
971
+ '/src': path.resolve(__dirname, 'src'),
972
+ '@css': path.resolve(__dirname, 'src/css'),
973
+ '@components': path.resolve(__dirname, 'src/templates/components'),
974
+ '@ds': path.resolve(__dirname, 'docs/ds'),
975
+ '@images': path.resolve(__dirname, 'public/images')
976
+ }
977
+ },
978
+ publicDir: 'public',
979
+ optimizeDeps: {
980
+ include: ['tailwindcss']
981
+ }
982
+ });