desy-html 16.0.4 → 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 (528) hide show
  1. package/.node-version +1 -1
  2. package/.nvmrc +1 -1
  3. package/AGENTS.md +207 -207
  4. package/README.md +203 -203
  5. package/TESTING_PLAN.md +379 -379
  6. package/branding/BRANDING.md +398 -398
  7. package/branding/branding.config.js +76 -76
  8. package/branding/branding.config.yourorganization.js +72 -72
  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-favicon.svg +13 -13
  17. package/branding/vite-branding-plugin.js +221 -221
  18. package/docs/_global.content-placeholder.njk +7 -7
  19. package/docs/_global.foot.njk +11 -11
  20. package/docs/_global.head.njk +42 -42
  21. package/docs/_include.template-header.njk +391 -386
  22. package/docs/_macro.component-example.njk +6 -6
  23. package/docs/_macro.example-render.njk +350 -346
  24. package/docs/_macro.load-component-params.njk +2 -2
  25. package/docs/_macro.load-component-template.njk +3 -3
  26. package/docs/_macro.render-caller.njk +3 -3
  27. package/docs/_macro.show-code-from-file.njk +57 -57
  28. package/docs/_macro.show-html-from-file.njk +42 -42
  29. package/docs/_template.default.njk +8 -8
  30. package/docs/_template.examples.njk +13 -13
  31. package/docs/catalogo.html +27 -27
  32. package/docs/componentes.html +187 -184
  33. package/docs/ds/_ds.example.accordion.njk +109 -109
  34. package/docs/ds/_ds.example.border.njk +39 -39
  35. package/docs/ds/_ds.example.botones-alert-sm.njk +264 -264
  36. package/docs/ds/_ds.example.botones-alert.njk +264 -264
  37. package/docs/ds/_ds.example.botones-default-sm.njk +264 -264
  38. package/docs/ds/_ds.example.botones-default.njk +258 -258
  39. package/docs/ds/_ds.example.botones-primary-lg.njk +264 -264
  40. package/docs/ds/_ds.example.botones-primary-sm.njk +264 -264
  41. package/docs/ds/_ds.example.botones-primary.njk +264 -264
  42. package/docs/ds/_ds.example.botones-transparent-sm.njk +264 -264
  43. package/docs/ds/_ds.example.botones-transparent.njk +264 -264
  44. package/docs/ds/_ds.example.breadcrumbs.njk +65 -65
  45. package/docs/ds/_ds.example.card.njk +198 -198
  46. package/docs/ds/_ds.example.checkboxes.njk +377 -377
  47. package/docs/ds/_ds.example.collapsible.njk +18 -18
  48. package/docs/ds/_ds.example.color-de-interaccion.njk +16 -16
  49. package/docs/ds/_ds.example.colores-cabecera.njk +15 -15
  50. package/docs/ds/_ds.example.colores-de-soporte.njk +82 -82
  51. package/docs/ds/_ds.example.colores-neutros.njk +26 -26
  52. package/docs/ds/_ds.example.combobox.njk +91 -0
  53. package/docs/ds/_ds.example.date-input.njk +363 -363
  54. package/docs/ds/_ds.example.description-list.njk +289 -289
  55. package/docs/ds/_ds.example.details.njk +23 -23
  56. package/docs/ds/_ds.example.dropdowns-default.njk +186 -186
  57. package/docs/ds/_ds.example.dropdowns-en-uso.njk +105 -105
  58. package/docs/ds/_ds.example.dropdowns-variaciones.njk +224 -224
  59. package/docs/ds/_ds.example.error-summary.njk +34 -34
  60. package/docs/ds/_ds.example.file-upload.njk +54 -54
  61. package/docs/ds/_ds.example.footer.njk +28 -28
  62. package/docs/ds/_ds.example.header-advanced.njk +70 -70
  63. package/docs/ds/_ds.example.header.njk +211 -211
  64. package/docs/ds/_ds.example.input-group.njk +242 -242
  65. package/docs/ds/_ds.example.input.njk +164 -164
  66. package/docs/ds/_ds.example.item.njk +176 -176
  67. package/docs/ds/_ds.example.layout-escritorio.njk +38 -38
  68. package/docs/ds/_ds.example.layout-movil.njk +14 -14
  69. package/docs/ds/_ds.example.layout-sidebar.njk +11 -11
  70. package/docs/ds/_ds.example.links-list.njk +170 -170
  71. package/docs/ds/_ds.example.listbox-default.njk +677 -677
  72. package/docs/ds/_ds.example.listbox-variaciones.njk +824 -824
  73. package/docs/ds/_ds.example.menu-horizontal.njk +66 -66
  74. package/docs/ds/_ds.example.menu-navigation.njk +493 -493
  75. package/docs/ds/_ds.example.menu-vertical.njk +129 -129
  76. package/docs/ds/_ds.example.menubar-en-uso.njk +244 -244
  77. package/docs/ds/_ds.example.menubar-variaciones.njk +785 -785
  78. package/docs/ds/_ds.example.modal.njk +210 -210
  79. package/docs/ds/_ds.example.nav.njk +158 -158
  80. package/docs/ds/_ds.example.notification.njk +122 -122
  81. package/docs/ds/_ds.example.pagination.njk +190 -190
  82. package/docs/ds/_ds.example.pills.njk +215 -215
  83. package/docs/ds/_ds.example.radios.njk +419 -419
  84. package/docs/ds/_ds.example.searchbar.njk +87 -87
  85. package/docs/ds/_ds.example.select.njk +390 -390
  86. package/docs/ds/_ds.example.status-item.njk +239 -239
  87. package/docs/ds/_ds.example.status.njk +47 -47
  88. package/docs/ds/_ds.example.table.njk +740 -740
  89. package/docs/ds/_ds.example.tabs.njk +136 -136
  90. package/docs/ds/_ds.example.textarea.njk +237 -237
  91. package/docs/ds/_ds.example.textos.njk +32 -32
  92. package/docs/ds/_ds.example.toggle.njk +75 -75
  93. package/docs/ds/_ds.example.tree.njk +456 -456
  94. package/docs/ds/_ds.example.treegrid.njk +810 -810
  95. package/docs/ds/_ds.example.typography.njk +225 -225
  96. package/docs/ds/_ds.macro.code-snippet.njk +32 -32
  97. package/docs/ds/_ds.macro.section-title.njk +2 -2
  98. package/docs/ds/_ds.macro.subsection-title.njk +2 -2
  99. package/docs/ds/_ds.section.avisos.njk +22 -22
  100. package/docs/ds/_ds.section.botones.njk +53 -53
  101. package/docs/ds/_ds.section.campo-y-area-de-texto.njk +19 -19
  102. package/docs/ds/_ds.section.color.njk +147 -147
  103. package/docs/ds/_ds.section.datos.njk +42 -42
  104. package/docs/ds/_ds.section.dropdowns.njk +23 -23
  105. package/docs/ds/_ds.section.espaciado.njk +296 -296
  106. package/docs/ds/_ds.section.forms.njk +73 -68
  107. package/docs/ds/_ds.section.informacion.njk +12 -12
  108. package/docs/ds/_ds.section.layout.njk +63 -63
  109. package/docs/ds/_ds.section.menubar.njk +18 -18
  110. package/docs/ds/_ds.section.mostrar-ocultar.njk +33 -33
  111. package/docs/ds/_ds.section.navigation.njk +57 -57
  112. package/docs/ds/_ds.section.textos.njk +287 -287
  113. package/docs/ds/_ds.section.typography.njk +28 -28
  114. package/docs/estilos.html +37 -37
  115. package/docs/examples-accordion-history.html +8 -8
  116. package/docs/examples-accordion.html +8 -8
  117. package/docs/examples-alert.html +8 -8
  118. package/docs/examples-breadcrumbs.html +8 -8
  119. package/docs/examples-button-loader.html +8 -8
  120. package/docs/examples-button.html +8 -8
  121. package/docs/examples-card.html +8 -8
  122. package/docs/examples-character-count.html +8 -8
  123. package/docs/examples-checkboxes.html +8 -8
  124. package/docs/examples-collapsible.html +8 -8
  125. package/docs/examples-combobox.html +8 -0
  126. package/docs/examples-date-input.html +8 -8
  127. package/docs/examples-datepicker.html +8 -8
  128. package/docs/examples-description-list.html +8 -8
  129. package/docs/examples-details.html +8 -8
  130. package/docs/examples-dialog.html +8 -8
  131. package/docs/examples-dropdown.html +8 -8
  132. package/docs/examples-error-message.html +8 -8
  133. package/docs/examples-error-summary.html +8 -8
  134. package/docs/examples-fieldset.html +8 -8
  135. package/docs/examples-file-upload.html +8 -8
  136. package/docs/examples-footer.html +8 -8
  137. package/docs/examples-header-advanced.html +8 -8
  138. package/docs/examples-header-mini.html +8 -8
  139. package/docs/examples-header.html +8 -8
  140. package/docs/examples-hint.html +8 -8
  141. package/docs/examples-input-group.html +8 -8
  142. package/docs/examples-input.html +8 -8
  143. package/docs/examples-item.html +8 -8
  144. package/docs/examples-label.html +8 -8
  145. package/docs/examples-links-list.html +8 -8
  146. package/docs/examples-listbox.html +8 -8
  147. package/docs/examples-media-object.html +8 -8
  148. package/docs/examples-menu-horizontal.html +8 -8
  149. package/docs/examples-menu-navigation.html +8 -8
  150. package/docs/examples-menu-vertical.html +8 -8
  151. package/docs/examples-menubar.html +8 -8
  152. package/docs/examples-modal.html +8 -8
  153. package/docs/examples-nav.html +8 -8
  154. package/docs/examples-notification.html +8 -8
  155. package/docs/examples-pagination.html +8 -8
  156. package/docs/examples-pill.html +8 -8
  157. package/docs/examples-radios.html +8 -8
  158. package/docs/examples-searchbar.html +8 -8
  159. package/docs/examples-select.html +8 -8
  160. package/docs/examples-skip-link.html +8 -8
  161. package/docs/examples-spinner.html +8 -8
  162. package/docs/examples-status-item.html +8 -8
  163. package/docs/examples-status.html +8 -8
  164. package/docs/examples-table-advanced.html +8 -8
  165. package/docs/examples-table.html +8 -8
  166. package/docs/examples-tabs.html +8 -8
  167. package/docs/examples-textarea.html +8 -8
  168. package/docs/examples-toggle.html +8 -8
  169. package/docs/examples-tooltip.html +8 -8
  170. package/docs/examples-tree.html +8 -8
  171. package/docs/examples-treegrid.html +8 -8
  172. package/docs/index.html +914 -908
  173. package/docs/pagina-accesibilidad.html +110 -110
  174. package/docs/pagina-mapa-web.html +137 -137
  175. package/docs/pagina-prueba.html +95 -95
  176. package/docs/plantilla-con-header-advanced.html +14 -14
  177. package/docs/plantilla-editar-con-cabecera-fija-y-sidebar-sticky.html +154 -154
  178. package/docs/plantilla-editar-con-cabecera-fija.html +17 -17
  179. package/docs/plantilla-logueado-con-cabecera-fija-headroom.html +15 -15
  180. package/docs/plantilla-logueado-con-cabecera-fija.html +15 -15
  181. package/docs/plantilla-logueado-con-selector-de-app-y-sidebar.html +164 -164
  182. package/docs/plantilla-logueado-con-selector-de-app-y-subheader.html +16 -16
  183. package/docs/plantilla-logueado-con-selector-de-app.html +14 -14
  184. package/docs/plantilla-logueado-con-titulo-de-app.html +14 -14
  185. package/docs/plantilla-sin-loguear.html +14 -14
  186. package/docs/plantillas.html +89 -89
  187. package/docs/spinner-plantilla-con-header-advanced.html +2 -2
  188. package/docs/spinner-plantilla-editar-con-cabecera-fija.html +4 -4
  189. package/docs/spinner-plantilla-logueado-con-cabecera-fija.html +2 -2
  190. package/docs/spinner-plantilla-logueado-con-selector-de-app-y-subheader.html +2 -2
  191. package/docs/spinner-plantilla-logueado-con-titulo-de-app.html +2 -2
  192. package/docs/spinner-plantilla-sin-loguear.html +18 -18
  193. package/docs/sprints/01-combobox-template.md +433 -0
  194. package/docs/sprints/02-combobox-styles.md +291 -0
  195. package/docs/sprints/03-combobox-javascript.md +589 -0
  196. package/docs/sprints/04-combobox-integration.md +386 -0
  197. package/docs/sprints/05-combobox-refinamiento-w3c.md +55 -0
  198. package/package.json +61 -61
  199. package/public/images/demo-icon.svg +9 -9
  200. package/replit.md +74 -74
  201. package/src/EUPL-1.2.txt +287 -287
  202. package/src/css/base.css +44 -44
  203. package/src/css/component.form-group.css +24 -24
  204. package/src/css/component.headroom.css +32 -32
  205. package/src/css/component.text.css +169 -169
  206. package/src/css/styles.css +247 -246
  207. package/src/js/aria/MenuHorizontal.js +65 -65
  208. package/src/js/aria/MenuNavigation.js +34 -2
  209. package/src/js/aria/MenuVertical.js +60 -60
  210. package/src/js/aria/Nav.js +60 -60
  211. package/src/js/aria/PopupMenuAction.js +30 -12
  212. package/src/js/aria/accordion.js +264 -264
  213. package/src/js/aria/checkBoxes.js +52 -52
  214. package/src/js/aria/collapsible.js +44 -44
  215. package/src/js/aria/combobox.js +584 -0
  216. package/src/js/aria/dataGrid.js +950 -950
  217. package/src/js/aria/linksList.js +42 -42
  218. package/src/js/aria/notification.js +56 -56
  219. package/src/js/aria/radioButton.js +50 -50
  220. package/src/js/aria/toggle.js +61 -61
  221. package/src/js/aria/treegrid.js +507 -507
  222. package/src/js/aria/utils.js +219 -219
  223. package/src/js/desy-html.js +860 -813
  224. package/src/js/headroom.min.js +6 -6
  225. package/src/js/index.js +74 -72
  226. package/src/main.js +2 -2
  227. package/src/templates/components/accordion/_examples.accordion.njk +482 -482
  228. package/src/templates/components/accordion/_macro.accordion.njk +3 -3
  229. package/src/templates/components/accordion/_template.accordion.njk +131 -131
  230. package/src/templates/components/accordion/params.accordion.yaml +125 -125
  231. package/src/templates/components/accordion-history/_examples.accordion-history.njk +521 -521
  232. package/src/templates/components/accordion-history/_macro.accordion-history.njk +3 -3
  233. package/src/templates/components/accordion-history/_template.accordion-history.njk +193 -193
  234. package/src/templates/components/accordion-history/params.accordion-history.yaml +129 -129
  235. package/src/templates/components/alert/_examples.alert.njk +91 -91
  236. package/src/templates/components/alert/_macro.alert.njk +3 -3
  237. package/src/templates/components/alert/_styles.alert.css +9 -9
  238. package/src/templates/components/alert/_template.alert.njk +16 -16
  239. package/src/templates/components/alert/params.alert.yaml +25 -25
  240. package/src/templates/components/breadcrumbs/_examples.breadcrumbs.njk +464 -464
  241. package/src/templates/components/breadcrumbs/_macro.breadcrumbs.njk +3 -3
  242. package/src/templates/components/breadcrumbs/_styles.breadcrumbs.css +81 -81
  243. package/src/templates/components/breadcrumbs/_template.breadcrumbs.njk +77 -77
  244. package/src/templates/components/breadcrumbs/params.breadcrumbs.yaml +44 -44
  245. package/src/templates/components/button/_examples.button.njk +518 -511
  246. package/src/templates/components/button/_macro.button.njk +3 -3
  247. package/src/templates/components/button/_styles.button.css +180 -180
  248. package/src/templates/components/button/_template.button.njk +49 -49
  249. package/src/templates/components/button/params.button.yaml +48 -48
  250. package/src/templates/components/button-loader/_examples.button-loader.njk +499 -492
  251. package/src/templates/components/button-loader/_macro.button-loader.njk +3 -3
  252. package/src/templates/components/button-loader/_styles.button-loader.css +194 -194
  253. package/src/templates/components/button-loader/_template.button-loader.njk +84 -84
  254. package/src/templates/components/button-loader/params.button-loader.yaml +74 -74
  255. package/src/templates/components/card/_examples.card.njk +381 -381
  256. package/src/templates/components/card/_macro.card.njk +3 -3
  257. package/src/templates/components/card/_template.card.njk +34 -34
  258. package/src/templates/components/card/params.card.yaml +112 -112
  259. package/src/templates/components/character-count/_examples.character-count.njk +195 -195
  260. package/src/templates/components/character-count/_macro.character-count.njk +5 -5
  261. package/src/templates/components/character-count/_template.character-count.njk +38 -38
  262. package/src/templates/components/character-count/params.character-count.yaml +77 -77
  263. package/src/templates/components/checkboxes/_examples.checkboxes.njk +835 -835
  264. package/src/templates/components/checkboxes/_macro.checkboxes.njk +3 -3
  265. package/src/templates/components/checkboxes/_styles.checkboxes.css +30 -30
  266. package/src/templates/components/checkboxes/_template.checkboxes.njk +137 -137
  267. package/src/templates/components/checkboxes/params.checkboxes.yaml +116 -116
  268. package/src/templates/components/collapsible/_examples.collapsible.njk +107 -107
  269. package/src/templates/components/collapsible/_macro.collapsible.njk +3 -3
  270. package/src/templates/components/collapsible/_styles.collapsible.css +35 -35
  271. package/src/templates/components/collapsible/_template.collapsible.njk +17 -17
  272. package/src/templates/components/collapsible/params.collapsible.yaml +48 -48
  273. package/src/templates/components/combobox/_examples.combobox.njk +440 -0
  274. package/src/templates/components/combobox/_macro.combobox.njk +3 -0
  275. package/src/templates/components/combobox/_styles.combobox.css +92 -0
  276. package/src/templates/components/combobox/_template.combobox.njk +97 -0
  277. package/src/templates/components/combobox/params.combobox.yaml +102 -0
  278. package/src/templates/components/date-input/_examples.date-input.njk +578 -578
  279. package/src/templates/components/date-input/_macro.date-input.njk +3 -3
  280. package/src/templates/components/date-input/_template.date-input.njk +125 -125
  281. package/src/templates/components/date-input/params.date-input.yaml +97 -97
  282. package/src/templates/components/datepicker/_examples.datepicker.njk +415 -415
  283. package/src/templates/components/datepicker/_macro.datepicker.njk +3 -3
  284. package/src/templates/components/datepicker/_styles.datepicker.css +87 -87
  285. package/src/templates/components/datepicker/_template.datepicker.njk +129 -129
  286. package/src/templates/components/datepicker/params.datepicker.yaml +104 -104
  287. package/src/templates/components/description-list/_examples.description-list.njk +502 -502
  288. package/src/templates/components/description-list/_macro.description-list.njk +3 -3
  289. package/src/templates/components/description-list/_template.description-list.njk +17 -17
  290. package/src/templates/components/description-list/params.description-list.yaml +61 -61
  291. package/src/templates/components/details/_examples.details.njk +68 -68
  292. package/src/templates/components/details/_macro.details.njk +3 -3
  293. package/src/templates/components/details/_template.details.njk +17 -17
  294. package/src/templates/components/details/params.details.yaml +40 -40
  295. package/src/templates/components/dialog/_examples.dialog.njk +156 -156
  296. package/src/templates/components/dialog/_macro.dialog.njk +3 -3
  297. package/src/templates/components/dialog/_styles.dialog.css +27 -27
  298. package/src/templates/components/dialog/_template.dialog.njk +12 -12
  299. package/src/templates/components/dialog/params.dialog.yaml +25 -25
  300. package/src/templates/components/dropdown/_examples.dropdown.njk +226 -226
  301. package/src/templates/components/dropdown/_macro.dropdown.njk +3 -3
  302. package/src/templates/components/dropdown/_styles.dropdown.css +191 -191
  303. package/src/templates/components/dropdown/_template.dropdown.njk +34 -34
  304. package/src/templates/components/dropdown/params.dropdown.yaml +32 -32
  305. package/src/templates/components/error-message/_examples.error-message.njk +28 -28
  306. package/src/templates/components/error-message/_macro.error-message.njk +5 -5
  307. package/src/templates/components/error-message/_template.error-message.njk +8 -8
  308. package/src/templates/components/error-message/params.error-message.yaml +23 -23
  309. package/src/templates/components/error-summary/_examples.error-summary.njk +111 -111
  310. package/src/templates/components/error-summary/_macro.error-summary.njk +5 -5
  311. package/src/templates/components/error-summary/_template.error-summary.njk +48 -48
  312. package/src/templates/components/error-summary/params.error-summary.yaml +50 -50
  313. package/src/templates/components/fieldset/_examples.fieldset.njk +79 -79
  314. package/src/templates/components/fieldset/_macro.fieldset.njk +2 -2
  315. package/src/templates/components/fieldset/_template.fieldset.njk +46 -46
  316. package/src/templates/components/fieldset/params.fieldset.yaml +49 -49
  317. package/src/templates/components/file-upload/_examples.file-upload.njk +103 -103
  318. package/src/templates/components/file-upload/_macro.file-upload.njk +2 -2
  319. package/src/templates/components/file-upload/_template.file-upload.njk +45 -45
  320. package/src/templates/components/file-upload/params.file-upload.yaml +48 -48
  321. package/src/templates/components/footer/_examples.footer.njk +558 -558
  322. package/src/templates/components/footer/_macro.footer.njk +3 -3
  323. package/src/templates/components/footer/_styles.footer.css +43 -43
  324. package/src/templates/components/footer/_template.footer.njk +130 -130
  325. package/src/templates/components/footer/params.footer.yaml +140 -140
  326. package/src/templates/components/header/_examples.header.njk +627 -627
  327. package/src/templates/components/header/_macro.header.header__dropdown.njk +3 -3
  328. package/src/templates/components/header/_macro.header.header__navigation.njk +3 -3
  329. package/src/templates/components/header/_macro.header.header__offcanvas.njk +3 -3
  330. package/src/templates/components/header/_macro.header.header__offcanvasButton.njk +3 -3
  331. package/src/templates/components/header/_macro.header.header__subnav.njk +3 -3
  332. package/src/templates/components/header/_macro.header.njk +3 -3
  333. package/src/templates/components/header/_styles.header.css +12 -12
  334. package/src/templates/components/header/_template.header.header__dropdown.njk +27 -27
  335. package/src/templates/components/header/_template.header.header__navigation.njk +25 -25
  336. package/src/templates/components/header/_template.header.header__offcanvas.njk +20 -20
  337. package/src/templates/components/header/_template.header.header__offcanvasButton.njk +10 -10
  338. package/src/templates/components/header/_template.header.header__subnav.njk +33 -33
  339. package/src/templates/components/header/_template.header.njk +155 -155
  340. package/src/templates/components/header/params.header.yaml +295 -295
  341. package/src/templates/components/header-advanced/_examples.header-advanced.njk +1110 -1110
  342. package/src/templates/components/header-advanced/_macro.header-advanced.njk +3 -3
  343. package/src/templates/components/header-advanced/_template.header-advanced.njk +171 -171
  344. package/src/templates/components/header-advanced/params.header-advanced.yaml +352 -352
  345. package/src/templates/components/header-mini/_examples.header-mini.njk +319 -316
  346. package/src/templates/components/header-mini/_macro.header-mini.njk +3 -3
  347. package/src/templates/components/header-mini/_template.header-mini.njk +51 -51
  348. package/src/templates/components/header-mini/params.header-mini.yaml +90 -90
  349. package/src/templates/components/hint/_examples.hint.njk +26 -26
  350. package/src/templates/components/hint/_macro.hint.njk +3 -3
  351. package/src/templates/components/hint/_template.hint.njk +3 -3
  352. package/src/templates/components/hint/params.hint.yaml +21 -21
  353. package/src/templates/components/input/_examples.input.njk +393 -393
  354. package/src/templates/components/input/_macro.input.njk +3 -3
  355. package/src/templates/components/input/_styles.input.css +17 -17
  356. package/src/templates/components/input/_template.input.njk +53 -53
  357. package/src/templates/components/input/params.input.yaml +80 -80
  358. package/src/templates/components/input-group/_examples.input-group.njk +558 -558
  359. package/src/templates/components/input-group/_macro.input-group.njk +3 -3
  360. package/src/templates/components/input-group/_template.input-group.njk +107 -107
  361. package/src/templates/components/input-group/params.input-group.yaml +123 -123
  362. package/src/templates/components/item/_examples.item.njk +370 -370
  363. package/src/templates/components/item/_macro.item.njk +3 -3
  364. package/src/templates/components/item/_template.item.njk +73 -73
  365. package/src/templates/components/item/params.item.yaml +101 -101
  366. package/src/templates/components/label/_examples.label.njk +58 -58
  367. package/src/templates/components/label/_macro.label.njk +5 -5
  368. package/src/templates/components/label/_template.label.njk +37 -37
  369. package/src/templates/components/label/params.label.yaml +28 -28
  370. package/src/templates/components/links-list/_examples.links-list.njk +761 -761
  371. package/src/templates/components/links-list/_macro.links-list.njk +3 -3
  372. package/src/templates/components/links-list/_template.links-list.njk +119 -119
  373. package/src/templates/components/links-list/params.links-list.yaml +101 -101
  374. package/src/templates/components/listbox/_examples.listbox.njk +813 -812
  375. package/src/templates/components/listbox/_macro.listbox.njk +3 -3
  376. package/src/templates/components/listbox/_styles.listbox.css +218 -218
  377. package/src/templates/components/listbox/_template.listbox.njk +90 -90
  378. package/src/templates/components/listbox/params.listbox.yaml +85 -85
  379. package/src/templates/components/media-object/_examples.media-object.njk +79 -79
  380. package/src/templates/components/media-object/_macro.media-object.njk +3 -3
  381. package/src/templates/components/media-object/_template.media-object.njk +21 -21
  382. package/src/templates/components/media-object/params.media-object.yaml +16 -16
  383. package/src/templates/components/menu-horizontal/_examples.menu-horizontal.njk +613 -613
  384. package/src/templates/components/menu-horizontal/_macro.menu-horizontal.njk +3 -3
  385. package/src/templates/components/menu-horizontal/_styles.menu-horizontal.css +167 -167
  386. package/src/templates/components/menu-horizontal/_template.menu-horizontal.njk +36 -36
  387. package/src/templates/components/menu-horizontal/params.menu-horizontal.yaml +53 -53
  388. package/src/templates/components/menu-navigation/_examples.menu-navigation.njk +1292 -1292
  389. package/src/templates/components/menu-navigation/_macro.menu-navigation.njk +3 -3
  390. package/src/templates/components/menu-navigation/_styles.menu-navigation.css +238 -238
  391. package/src/templates/components/menu-navigation/_template.menu-navigation.njk +87 -87
  392. package/src/templates/components/menu-navigation/params.menu-navigation.yaml +86 -86
  393. package/src/templates/components/menu-vertical/_examples.menu-vertical.njk +835 -835
  394. package/src/templates/components/menu-vertical/_macro.menu-vertical.njk +3 -3
  395. package/src/templates/components/menu-vertical/_template.menu-vertical.njk +92 -92
  396. package/src/templates/components/menu-vertical/params.menu-vertical.yaml +77 -77
  397. package/src/templates/components/menubar/_examples.menubar.njk +1843 -1843
  398. package/src/templates/components/menubar/_macro.menubar.njk +3 -3
  399. package/src/templates/components/menubar/_styles.menubar.css +259 -259
  400. package/src/templates/components/menubar/_template.menubar.njk +106 -106
  401. package/src/templates/components/menubar/params.menubar.yaml +130 -130
  402. package/src/templates/components/modal/_examples.modal.njk +432 -432
  403. package/src/templates/components/modal/_macro.modal.njk +3 -3
  404. package/src/templates/components/modal/_template.modal.njk +129 -129
  405. package/src/templates/components/modal/params.modal.yaml +77 -77
  406. package/src/templates/components/nav/_examples.nav.njk +468 -468
  407. package/src/templates/components/nav/_macro.nav.njk +3 -3
  408. package/src/templates/components/nav/_template.nav.njk +53 -53
  409. package/src/templates/components/nav/params.nav.yaml +61 -61
  410. package/src/templates/components/notification/_examples.notification.njk +277 -277
  411. package/src/templates/components/notification/_macro.notification.njk +3 -3
  412. package/src/templates/components/notification/_styles.notification.css +30 -30
  413. package/src/templates/components/notification/_template.notification.njk +86 -86
  414. package/src/templates/components/notification/params.notification.yaml +101 -101
  415. package/src/templates/components/pagination/_examples.pagination.njk +444 -444
  416. package/src/templates/components/pagination/_macro.pagination.njk +3 -3
  417. package/src/templates/components/pagination/_template.pagination.njk +162 -162
  418. package/src/templates/components/pagination/params.pagination.yaml +150 -150
  419. package/src/templates/components/pill/_examples.pill.njk +180 -180
  420. package/src/templates/components/pill/_macro.pill.njk +3 -3
  421. package/src/templates/components/pill/_styles.pill.css +59 -59
  422. package/src/templates/components/pill/_template.pill.njk +38 -38
  423. package/src/templates/components/pill/params.pill.yaml +28 -28
  424. package/src/templates/components/radios/_examples.radios.njk +698 -698
  425. package/src/templates/components/radios/_macro.radios.njk +5 -5
  426. package/src/templates/components/radios/_styles.radios.css +30 -30
  427. package/src/templates/components/radios/_template.radios.njk +133 -133
  428. package/src/templates/components/radios/params.radios.yaml +104 -104
  429. package/src/templates/components/searchbar/_examples.searchbar.njk +147 -147
  430. package/src/templates/components/searchbar/_macro.searchbar.njk +3 -3
  431. package/src/templates/components/searchbar/_template.searchbar.njk +45 -45
  432. package/src/templates/components/searchbar/params.searchbar.yaml +46 -46
  433. package/src/templates/components/select/_examples.select.njk +364 -364
  434. package/src/templates/components/select/_macro.select.njk +5 -5
  435. package/src/templates/components/select/_styles.select.css +45 -45
  436. package/src/templates/components/select/_template.select.njk +67 -67
  437. package/src/templates/components/select/params.select.yaml +93 -93
  438. package/src/templates/components/skip-link/_examples.skip-link.njk +31 -31
  439. package/src/templates/components/skip-link/_macro.skip-link.njk +3 -3
  440. package/src/templates/components/skip-link/_styles.skip-link.css +12 -12
  441. package/src/templates/components/skip-link/_template.skip-link.njk +4 -4
  442. package/src/templates/components/skip-link/params.skip-link.yaml +20 -20
  443. package/src/templates/components/spinner/_examples.spinner.njk +91 -101
  444. package/src/templates/components/spinner/_macro.spinner.njk +3 -3
  445. package/src/templates/components/spinner/_styles.spinner.css +33 -33
  446. package/src/templates/components/spinner/_template.spinner.njk +12 -12
  447. package/src/templates/components/spinner/params.spinner.yaml +12 -12
  448. package/src/templates/components/status/_examples.status.njk +80 -80
  449. package/src/templates/components/status/_macro.status.njk +3 -3
  450. package/src/templates/components/status/_template.status.njk +23 -23
  451. package/src/templates/components/status/params.status.yaml +24 -24
  452. package/src/templates/components/status-item/_examples.status-item.njk +319 -319
  453. package/src/templates/components/status-item/_macro.status-item.njk +3 -3
  454. package/src/templates/components/status-item/_template.status-item.njk +99 -99
  455. package/src/templates/components/status-item/params.status-item.yaml +106 -106
  456. package/src/templates/components/table/_examples.table.njk +705 -705
  457. package/src/templates/components/table/_macro.table.njk +3 -3
  458. package/src/templates/components/table/_styles.table.css +19 -19
  459. package/src/templates/components/table/_template.table.njk +68 -68
  460. package/src/templates/components/table/params.table.yaml +130 -130
  461. package/src/templates/components/table-advanced/_examples.table-advanced.njk +839 -839
  462. package/src/templates/components/table-advanced/_macro.table-advanced.njk +3 -3
  463. package/src/templates/components/table-advanced/_styles.table-advanced.css +63 -63
  464. package/src/templates/components/table-advanced/_template.table-advanced.njk +210 -210
  465. package/src/templates/components/table-advanced/params.table-advanced.yaml +160 -160
  466. package/src/templates/components/tabs/_examples.tabs.njk +588 -588
  467. package/src/templates/components/tabs/_macro.tabs.njk +3 -3
  468. package/src/templates/components/tabs/_styles.tabs.css +132 -132
  469. package/src/templates/components/tabs/_template.tabs.njk +70 -70
  470. package/src/templates/components/tabs/params.tabs.yaml +83 -83
  471. package/src/templates/components/textarea/_examples.textarea.njk +174 -174
  472. package/src/templates/components/textarea/_macro.textarea.njk +5 -5
  473. package/src/templates/components/textarea/_template.textarea.njk +47 -47
  474. package/src/templates/components/textarea/params.textarea.yaml +64 -64
  475. package/src/templates/components/toggle/_examples.toggle.njk +248 -248
  476. package/src/templates/components/toggle/_macro.toggle.njk +3 -3
  477. package/src/templates/components/toggle/_styles.toggle.css +31 -31
  478. package/src/templates/components/toggle/_template.toggle.njk +27 -27
  479. package/src/templates/components/toggle/params.toggle.yaml +69 -69
  480. package/src/templates/components/tooltip/_examples.tooltip.njk +134 -134
  481. package/src/templates/components/tooltip/_macro.tooltip.njk +3 -3
  482. package/src/templates/components/tooltip/_styles.tooltip.css +9 -9
  483. package/src/templates/components/tooltip/_template.tooltip.njk +40 -40
  484. package/src/templates/components/tooltip/params.tooltip.yaml +40 -40
  485. package/src/templates/components/tree/_examples.tree.njk +2671 -2671
  486. package/src/templates/components/tree/_macro.tree.njk +3 -3
  487. package/src/templates/components/tree/_styles.tree.css +54 -54
  488. package/src/templates/components/tree/_template.tree.njk +232 -232
  489. package/src/templates/components/tree/params.tree.yaml +173 -173
  490. package/src/templates/components/treegrid/_examples.treegrid.njk +1269 -1269
  491. package/src/templates/components/treegrid/_macro.treegrid.njk +3 -3
  492. package/src/templates/components/treegrid/_styles.treegrid.css +31 -31
  493. package/src/templates/components/treegrid/_template.treegrid.njk +90 -90
  494. package/src/templates/components/treegrid/params.treegrid.yaml +131 -131
  495. package/src/templates/includes/_abrir-notificaciones-extra.njk +2 -2
  496. package/src/templates/includes/_abrir-notificaciones.njk +2 -2
  497. package/src/templates/includes/_acciones-de-cabecera.njk +28 -28
  498. package/src/templates/includes/_ejemplo-titulo-h2-parrafo.njk +4 -4
  499. package/src/templates/includes/_ejemplo-titulo-parrafo.njk +4 -4
  500. package/src/templates/includes/_test-include.njk +13 -13
  501. package/src/templates/pages/_page.foot-headroom.njk +30 -30
  502. package/src/templates/pages/_page.foot.njk +5 -5
  503. package/src/templates/pages/_page.footer.njk +19 -19
  504. package/src/templates/pages/_page.head.njk +27 -27
  505. package/src/templates/pages/_page.notification-edit-inner-content.njk +31 -31
  506. package/src/templates/pages/_page.notification-edit.njk +30 -30
  507. package/src/templates/pages/_page.notification-footer.njk +32 -32
  508. package/src/templates/pages/_page.notification-header-fixed.njk +30 -30
  509. package/src/templates/pages/_page.notification-header.njk +31 -31
  510. package/src/templates/pages/_page.sidebar-content.njk +20 -20
  511. package/src/templates/pages/_page.spinner-block.njk +14 -14
  512. package/src/templates/pages/_page.spinner-show.njk +15 -15
  513. package/src/templates/pages/_page.spinner.njk +15 -15
  514. package/src/templates/pages/_template.edit-fixed-with-sticky-sidebar.njk +36 -36
  515. package/src/templates/pages/_template.edit-fixed.njk +74 -74
  516. package/src/templates/pages/_template.home.njk +111 -111
  517. package/src/templates/pages/_template.logged-out.njk +56 -56
  518. package/src/templates/pages/_template.logged-selector-fixed-headroom.njk +198 -198
  519. package/src/templates/pages/_template.logged-selector-fixed.njk +195 -195
  520. package/src/templates/pages/_template.logged-selector-subheader.njk +80 -80
  521. package/src/templates/pages/_template.logged-selector-with-sidebar.njk +28 -28
  522. package/src/templates/pages/_template.logged-selector.njk +195 -195
  523. package/src/templates/pages/_template.logged.njk +141 -141
  524. package/src/templates/pages/_template.mfe-iframe-content.njk +31 -31
  525. package/src/templates/pages/_template.mfe.njk +83 -83
  526. package/src/templates/pages/_template.test.njk +48 -48
  527. package/src/templates/pages/_template.with-header-advanced.njk +311 -311
  528. package/vite.config.js +982 -982
package/vite.config.js CHANGED
@@ -1,982 +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 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
- });
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
+ });