desy-html 16.0.4 → 17.0.1

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