@nutin/cli 1.3.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/CHANGELOG.md +106 -9
  2. package/README.md +17 -35
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +76 -25
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
  23. package/lib/src/create-app/success-message.mjs +13 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +142 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +168 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +199 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +44 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -19
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +6 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -57
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +5 -38
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +68 -35
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +9 -8
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +96 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +6 -3
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +151 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +8 -2
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +2 -2
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +89 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +4 -1
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  65. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  66. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  67. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  68. package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  70. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  71. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  72. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  73. package/templates/base/src/core/base-classes/view/view.test.js.hbs +71 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +24 -49
  75. package/templates/base/src/core/config.ts.hbs +10 -0
  76. package/templates/base/src/core/index.ts.hbs +0 -3
  77. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  78. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  79. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  80. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  81. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  83. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  86. package/templates/base/src/core/services/globals/globals.test.js.hbs +187 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +104 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  90. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +73 -2
  91. package/templates/base/src/core/services/http-client/http-client.ts.hbs +4 -1
  92. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  93. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
  94. package/templates/base/src/core/services/index.ts.hbs +5 -6
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  96. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  97. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  98. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +257 -6
  100. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +56 -29
  101. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  103. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
  104. package/templates/base/src/core/services/router/router.test.js.hbs +304 -2
  105. package/templates/base/src/core/services/router/router.ts.hbs +47 -12
  106. package/templates/base/src/index.html.hbs +2 -6
  107. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  108. package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
  109. package/templates/base/src/styles/main.scss.hbs +1 -18
  110. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  111. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  112. package/templates/base/tools/builder/builder.js.hbs +24 -26
  113. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  115. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  116. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  117. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  118. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  119. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  120. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  121. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  122. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  123. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  124. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  125. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  126. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  127. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  128. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  130. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  131. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  132. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  133. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  134. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  137. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  138. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  139. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  140. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  141. package/templates/base/tools/dev/serve.js.hbs +11 -1
  142. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  143. package/templates/base/tools/generator/generator.js.hbs +44 -47
  144. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  145. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  146. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  147. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  148. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  149. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  150. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  151. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +8 -0
  152. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +17 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  154. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  155. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  156. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  157. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  158. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  159. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +4 -3
  160. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  165. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  166. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  167. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  169. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  170. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  171. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  172. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  173. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  174. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  176. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  177. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  178. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  179. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  180. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  181. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  182. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  183. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  186. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  187. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  188. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  189. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  190. package/templates/base/tools/utils/format.js.hbs +10 -10
  191. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  192. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  193. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  194. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  195. package/templates/base/tools/utils/index.js.hbs +1 -3
  196. package/templates/base/tools/utils/languages.js.hbs +15 -0
  197. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  198. package/templates/base/tools/utils/print.js.hbs +19 -13
  199. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  200. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  201. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  202. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  203. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  204. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  205. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  206. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  207. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  208. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  209. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  212. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  214. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  215. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  216. package/lib/src/context-builder.mjs +0 -40
  217. package/lib/src/file-generator.mjs +0 -109
  218. package/lib/src/json-generator.mjs +0 -31
  219. package/lib/src/package-manager.mjs +0 -139
  220. package/lib/src/print.mjs +0 -32
  221. package/lib/src/utils.mjs +0 -18
  222. package/lib/src/version.mjs +0 -1
  223. package/templates/base/builder.config.js.hbs +0 -36
  224. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  225. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  226. package/templates/base/src/app/dom.test.js.hbs +0 -13
  227. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  228. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  229. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  230. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  231. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  232. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
  233. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  234. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
  235. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  236. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  237. package/templates/base/src/libs/index.ts.hbs +0 -4
  238. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
  239. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
  240. package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
  241. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
  242. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  243. package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
  244. package/templates/base/src/styles/core/_index.scss.hbs +0 -6
  245. package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
  246. package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
  247. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  248. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  249. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  250. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  251. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  252. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  253. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  254. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  255. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  256. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  257. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  258. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  259. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  260. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  261. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  262. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  263. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  264. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  265. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
  266. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  267. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  268. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  269. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  270. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  271. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  272. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  273. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  274. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  275. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  276. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  277. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  278. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  279. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  280. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  281. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  282. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  283. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  284. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  285. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  286. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  287. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  288. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  289. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss/theme.css" layer(theme);
2
+ @import "tailwindcss/utilities.css" layer(utilities);
@@ -0,0 +1,40 @@
1
+ import process from 'process';
2
+ import nutinConfig from '../../nutin.config.js';
3
+ import { errorExit } from '../utils/index.js';
4
+
5
+ function validateConfig(config) {
6
+ if (!config || typeof config !== 'object') {
7
+ errorExit(new Error('nutin.config.js must export a default object.'), 'builder.config');
8
+ }
9
+ if (!config.builder || typeof config.builder !== 'object') {
10
+ errorExit(new Error('nutin.config.js is missing the required "builder" object.'), 'builder.config');
11
+ }
12
+ if (!Array.isArray(config.builder.sass?.paths)) {
13
+ errorExit(new Error('nutin.config.js is missing "builder.sass.paths" (expected an array).'), 'builder.config');
14
+ }
15
+ if (!config.builder.esbuild || typeof config.builder.esbuild !== 'object') {
16
+ errorExit(new Error('nutin.config.js is missing the required "builder.esbuild" object.'), 'builder.config');
17
+ }
18
+ }
19
+
20
+ validateConfig(nutinConfig);
21
+
22
+ const wellKnownNonSEORoutes = [
23
+ '/400',
24
+ '/401',
25
+ '/403',
26
+ '/404',
27
+ '/500',
28
+ '/502',
29
+ '/503',
30
+ '/504',
31
+ ]
32
+
33
+ export const builderConfig = {
34
+ ...nutinConfig.builder,
35
+ isProd: process.env.NODE_ENV === 'production',
36
+ i18n: nutinConfig.i18n,
37
+ tailwind: nutinConfig.tailwind,
38
+ generateSEO: nutinConfig.generateSEOFiles,
39
+ WELL_KNOWN_NON_SEO_ROUTES: wellKnownNonSEORoutes,
40
+ };
@@ -1,38 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import path from 'path';
4
- import { print, isProd, runScript } from '../utils/index.js';
4
+ import { print, runScript } from '../utils/index.js';
5
+ import { builderConfig } from './builder.config.js';
5
6
 
6
7
  const scriptsDir = path.join(process.cwd(), 'tools', 'builder', 'core');
7
8
 
8
- if (!isProd) print.boldInfo('For production, use build:prod.\n');
9
- print.boldHead(`Starting build...\n`);
10
-
11
- runScript(path.join(scriptsDir, 'copy-static.js'), 'Copying files...');
12
-
13
- if(!isProd) runScript(path.join(scriptsDir, 'tsc.js'), 'Running TypeScript compiler...');
14
-
15
- {{#if template}}
16
- runScript(path.join(scriptsDir, 'merge-templates.js'), 'Merging HTML templates in temp files...');
17
- {{else}}
18
- runScript(path.join(scriptsDir, 'minify-html.js'), 'Minifying inline templates...');
19
- {{/if}}
20
- {{#if i18n}}
21
- runScript(path.join(scriptsDir, 'build-i18n.js'), 'Combining locales for production...');
22
- {{/if}}
23
- runScript(path.join(scriptsDir, 'sass.js'), 'Compiling styles from main.scss...');
24
- runScript(path.join(scriptsDir, 'validate-html.js'), 'Adding and validating tags in index.html...');
25
-
26
- if (isProd) {
27
- runScript(path.join(scriptsDir, 'esbuild.js'), 'Running esbuild...');
28
- runScript(path.join(scriptsDir, 'hash-files.js'), 'Hashing files...');
29
- {{#if deployHelper}}
30
- runScript(path.join(scriptsDir, 'compress-files.js'), 'Compressing files...');
31
- {{/if}}
9
+ print.boldHead(`\nStarting build...`);
10
+
11
+ runScript(path.join(scriptsDir, 'app', 'copy-static.js'), 'Copying files...');
12
+
13
+ runScript(path.join(scriptsDir, 'html-index', 'validate-html.js'), 'Processing index.html...');
14
+ runScript(path.join(scriptsDir, 'app', 'validate-routes.js'), 'Validating app routes...');
15
+
16
+ runScript(path.join(scriptsDir, 'app', 'compile-ts.js'), 'Compiling TypeScript...');
17
+
18
+ runScript(path.join(scriptsDir, 'html-templates', 'process-html-templates.js'), 'Processing HTML templates...');
19
+
20
+ runScript(path.join(scriptsDir, 'styles', 'sass.js'), 'Compiling styles...');
21
+ if (builderConfig.tailwind) runScript(path.join(scriptsDir, 'styles', 'tailwind.js'), 'Compiling Tailwind CSS...');
22
+
23
+ if (builderConfig.i18n) runScript(path.join(scriptsDir, 'i18n', 'build-i18n.js'), 'Combining locales...');
24
+
25
+ if (builderConfig.isProd) {
26
+ runScript(path.join(scriptsDir, 'prod-bundle', 'esbuild.js'), 'Running esbuild...');
27
+ runScript(path.join(scriptsDir, 'prod-bundle', 'hash-files.js'), 'Hashing files...');
28
+ runScript(path.join(scriptsDir, 'prod-bundle', 'compress-files.js'), 'Compressing files...');
29
+ if (builderConfig.generateSEO) runScript(path.join(scriptsDir, 'seo', 'generate-seo-files.js'), 'Generating SEO Files...');
32
30
  }
33
31
 
34
32
  runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...')
35
33
 
36
34
  print.boldSuccess(`\nBuild successful!\n`);
37
35
 
38
- if (!isProd) print.boldInfo('For production, use build:prod.');
36
+ if (!builderConfig.isProd) print.info('For production, use "{{packageManager}} run build:prod"');
@@ -0,0 +1,16 @@
1
+ import { BINARY_EXTENSIONS } from './binary-extensions.js';
2
+
3
+ describe('BINARY_EXTENSIONS', () => {
4
+ it('is a Set containing common binary asset extensions', () => {
5
+ expect(BINARY_EXTENSIONS).toBeInstanceOf(Set);
6
+ expect(BINARY_EXTENSIONS.has('.png')).toBeTruthy();
7
+ expect(BINARY_EXTENSIONS.has('.woff2')).toBeTruthy();
8
+ expect(BINARY_EXTENSIONS.has('.gz')).toBeTruthy();
9
+ });
10
+
11
+ it('does not treat text/source extensions as binary', () => {
12
+ expect(BINARY_EXTENSIONS.has('.js')).toBeFalsy();
13
+ expect(BINARY_EXTENSIONS.has('.css')).toBeFalsy();
14
+ expect(BINARY_EXTENSIONS.has('.html')).toBeFalsy();
15
+ });
16
+ });
@@ -0,0 +1,16 @@
1
+ import { runCommand, print, errorExit } from "../../../utils/index.js";
2
+ import { builderConfig } from '../../builder.config.js';
3
+
4
+ async function compileTS() {
5
+ // Prod bundles straight from .ts source (see prod-bundle/esbuild.js), so tsc only
6
+ // needs to type-check there, not emit — emitting would leave a stale, unmerged .js
7
+ // sibling next to each merged .ts view/component's source.
8
+ const args = ['--project', 'tsconfig.json'];
9
+ if (builderConfig.isProd) args.push('--noEmit');
10
+
11
+ await runCommand('tsc', args);
12
+ }
13
+
14
+ compileTS().catch((err) => {
15
+ errorExit(err, 'compile-ts');
16
+ });
@@ -1,22 +1,23 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { exit } from 'process';
4
- import { getFilesRecursive, print, isVerbose, isProd } from '../../utils/index.js';
5
- import { BINARY_EXTENSIONS } from '../variables/binary-extensions.js';
3
+ import { getFilesRecursive, print, errorExit } from '../../../utils/index.js';
4
+ import { BINARY_EXTENSIONS } from './binary-extensions.js';
6
5
  import { PATHS } from './paths.js';
6
+ import { builderConfig } from '../../builder.config.js';
7
7
 
8
8
  async function copyStatic() {
9
- const extensions = [...BINARY_EXTENSIONS, 'html'];
10
- if (isProd) extensions.push('.ts');
11
-
12
- if (isVerbose) print.info(`File types that will be copied: ${Array.from(extensions)}`);
13
-
9
+ const extensions = [...BINARY_EXTENSIONS, '.html'];
10
+ if (builderConfig.isProd) extensions.push('.ts');
11
+
12
+ // Every build starts from a clean dist-build: a failed/interrupted prior
13
+ // build never gets renamed away by finalize-build.js, so a stale sibling
14
+ // (already merged, placeholder gone) can otherwise sit next to the next
15
+ // run's fresh output and trip merge-templates.js's per-sibling check.
16
+ await fs.rm(PATHS.temp, { recursive: true, force: true });
14
17
  await ensureDir(PATHS.tempSource);
15
18
 
16
19
  await copyFavicon();
17
- {{#if i18n}}
18
20
  await copyConfig();
19
- {{/if}}
20
21
 
21
22
  for (const extension of extensions) {
22
23
  const files = await getFilesRecursive(PATHS.source, extension);
@@ -27,17 +28,13 @@ async function copyStatic() {
27
28
 
28
29
  try {
29
30
  await copyFile(file, dest);
30
- if (isVerbose) print.info(`✅ Copied: ${file} → ${dest}`);
31
31
  } catch (err) {
32
- print.error(`❌ Failed to copy: ${file}. ${err.message}`);
33
- exit(1);
32
+ throw new Error(`Failed to copy: ${file}. ${err.message}`, { cause: err });
34
33
  }
35
34
  }
36
35
  }
37
36
 
38
- if (isProd) await copyJsonFiles(PATHS.source, PATHS.tempSource);
39
-
40
- if (isVerbose) print.boldInfo(`Copy complete.\n`);
37
+ if (builderConfig.isProd) await copyJsonFiles(PATHS.source, PATHS.tempSource);
41
38
  }
42
39
 
43
40
  async function ensureDir(dir) {
@@ -55,33 +52,31 @@ async function copyFavicon() {
55
52
 
56
53
  try {
57
54
  await copyFile(srcPath, destPath);
58
- if (isVerbose) print.info('Copied: favicon.ico');
59
- } catch {
60
- print.boldError('⚠️ No favicon found at public/favicon.ico');
55
+ } catch (err) {
56
+ if (err.code === 'ENOENT') print.warn('No favicon found at public/favicon.ico');
57
+ else errorExit(err, 'copy-static');
61
58
  }
62
59
  }
63
60
 
64
- {{#if i18n}}
65
61
  async function copyConfig() {
66
62
  const srcPath = path.resolve(path.join('config'));
67
- const temp = isProd ? PATHS.temp : PATHS.tempSource;
63
+ const temp = builderConfig.isProd ? PATHS.temp : PATHS.tempSource;
68
64
  const destPath = path.join(temp, 'config');
69
65
  try {
70
66
  await copyJsonFiles(srcPath, destPath);
71
- {{#if testinNutin}}
72
- // for tests to run
73
- if (!isProd) await copyJsonFiles(srcPath, path.join(PATHS.temp, 'config'))
74
- {{/if}}
75
- if (isVerbose) print.info('Config files copied');
76
- } catch {
77
- print.boldError('Issue when copying config files.');
78
- exit(1);
67
+ await copyFile(path.resolve('nutin.config.js'), path.join(temp, 'nutin.config.js'));
68
+
69
+ // for testin-nutin tests to run
70
+ if (!builderConfig.isProd) {
71
+ await copyJsonFiles(srcPath, path.join(PATHS.temp, 'config'));
72
+ await copyFile(path.resolve('nutin.config.js'), path.join(PATHS.temp, 'nutin.config.js'));
73
+ }
74
+ } catch (err) {
75
+ errorExit(err, 'copy-static');
79
76
  }
80
77
  }
81
78
 
82
- {{/if}}
83
79
  async function copyJsonFiles(sourceDir, destDir) {
84
-
85
80
  const items = await fs.readdir(sourceDir, { withFileTypes: true });
86
81
 
87
82
  for (const item of items) {
@@ -93,12 +88,10 @@ async function copyJsonFiles(sourceDir, destDir) {
93
88
  } else if (item.isFile() && path.extname(item.name) === '.json') {
94
89
  await fs.mkdir(path.dirname(destPath), { recursive: true });
95
90
  await fs.copyFile(sourcePath, destPath);
96
- if (isVerbose) print.info(`Copied JSON: ${sourcePath} -> ${destPath}`);
97
91
  }
98
92
  }
99
93
  }
100
94
 
101
95
  copyStatic().catch((err) => {
102
- print.boldError(`Unexpected error: ${err.message}`);
103
- exit(1);
96
+ errorExit(err, 'copy-static');
104
97
  });
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import ts from 'typescript';
4
+ import { print, errorExit } from '../../../utils/index.js';
5
+ import { PATHS } from './paths.js';
6
+
7
+ const ROUTES_FILE = path.join(PATHS.sourceApp, 'routes.ts');
8
+
9
+ function findAppRoutesObjectLiteral(sourceFile) {
10
+ let objectLiteral;
11
+
12
+ ts.forEachChild(sourceFile, function visit(node) {
13
+ if (
14
+ ts.isVariableDeclaration(node) &&
15
+ ts.isIdentifier(node.name) &&
16
+ node.name.text === 'appRoutes' &&
17
+ node.initializer &&
18
+ ts.isObjectLiteralExpression(node.initializer)
19
+ ) {
20
+ objectLiteral = node.initializer;
21
+ return;
22
+ }
23
+ ts.forEachChild(node, visit);
24
+ });
25
+
26
+ return objectLiteral;
27
+ }
28
+
29
+ function getRouteKeys(objectLiteral) {
30
+ return objectLiteral.properties
31
+ .filter((prop) => ts.isPropertyAssignment(prop) && ts.isStringLiteralLike(prop.name))
32
+ .map((prop) => prop.name.text);
33
+ }
34
+
35
+ function findDuplicates(keys) {
36
+ const seen = new Set();
37
+ const duplicates = new Set();
38
+
39
+ for (const key of keys) {
40
+ if (seen.has(key)) duplicates.add(key);
41
+ seen.add(key);
42
+ }
43
+
44
+ return [...duplicates];
45
+ }
46
+
47
+ function validateRoutes() {
48
+ let source;
49
+ try {
50
+ source = readFileSync(ROUTES_FILE, 'utf-8');
51
+ } catch {
52
+ errorExit(`Failed to read ${ROUTES_FILE}`);
53
+ }
54
+
55
+ const sourceFile = ts.createSourceFile(ROUTES_FILE, source, ts.ScriptTarget.Latest, true);
56
+ const objectLiteral = findAppRoutesObjectLiteral(sourceFile);
57
+
58
+ if (!objectLiteral) {
59
+ errorExit(`Could not find an "appRoutes" object literal in ${ROUTES_FILE}`);
60
+ }
61
+
62
+ const duplicates = findDuplicates(getRouteKeys(objectLiteral));
63
+
64
+ if (duplicates.length > 0) {
65
+ const list = duplicates.map((key) => `"${key}"`).join(', ');
66
+ errorExit(
67
+ `Duplicate route key(s) in ${ROUTES_FILE}: ${list} — an earlier route with this path is silently unreachable.`
68
+ );
69
+ }
70
+
71
+ }
72
+
73
+ try {
74
+ validateRoutes();
75
+ } catch(err) {
76
+ errorExit(err, 'validate-routes');
77
+ }
@@ -1,19 +1,24 @@
1
- import { print, isProd, isVerbose } from "../../utils/index.js";
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
- import { PATHS } from "./paths.js";
3
+ import { PATHS } from "./app/paths.js";
4
+ import { builderConfig } from '../builder.config.js';
5
+ import { print, errorExit } from "../../utils/index.js";
5
6
 
6
7
  function finalizeBuild() {
7
- if (isProd) removeFoldersAfterBundle();
8
+ if (builderConfig.isProd) removeFoldersAfterBundle();
9
+ if (builderConfig.isProd) removeNutinConfig();
8
10
  replaceDir(PATHS.temp, PATHS.build);
9
- if (isVerbose) print.boldInfo('Build finalized.\n');
10
11
  }
11
12
 
12
13
  function removeFoldersAfterBundle() {
13
- const foldersToRemove = ['core', 'libs', 'app'];
14
+ const foldersToRemove = [ 'core', 'app', path.join('..', 'config') ];
14
15
  const pathToFolder = (folder) => path.join(PATHS.tempSource, folder);
15
16
 
16
- foldersToRemove.forEach(folder => fs.rmSync(pathToFolder(folder), { recursive : true }));
17
+ foldersToRemove.forEach(folder => fs.rmSync(pathToFolder(folder), { recursive: true, force: true }));
18
+ }
19
+
20
+ function removeNutinConfig() {
21
+ fs.rmSync(path.join(PATHS.temp, 'nutin.config.js'));
17
22
  }
18
23
 
19
24
  function replaceDir(src, dest) {
@@ -21,4 +26,8 @@ function replaceDir(src, dest) {
21
26
  fs.renameSync(src, dest);
22
27
  }
23
28
 
24
- finalizeBuild();
29
+ try {
30
+ finalizeBuild();
31
+ } catch(err) {
32
+ errorExit(err, 'finalize-build')
33
+ }
@@ -1,8 +1,8 @@
1
- import { print, isProd } from '../../utils/index.js';
2
1
  import { writeFile } from 'fs/promises';
2
+ import { builderConfig } from '../../builder.config.js';
3
3
 
4
4
  export async function addTags(htmlContent, filePath) {
5
- const scriptSrc = isProd ? "bundle.js" : "app/main.js";
5
+ const scriptSrc = builderConfig.isProd ? "bundle.js" : "app/main.js";
6
6
 
7
7
  const tags = [
8
8
  { type: 'stylesheet', href: 'main.css', position: 'head' },
@@ -0,0 +1,56 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { addTags } from './add-tags.js';
5
+
6
+ describe('addTags', () => {
7
+ let tmpDir;
8
+ let filePath;
9
+
10
+ beforeEach(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-add-tags-'));
12
+ filePath = path.join(tmpDir, 'index.html');
13
+ });
14
+
15
+ afterEach(() => {
16
+ fs.rmSync(tmpDir, { recursive: true, force: true });
17
+ });
18
+
19
+ it('inserts the stylesheet before </head> and the script before </body>', async () => {
20
+ const html = '<html><head><title>T</title></head><body></body></html>';
21
+
22
+ const result = await addTags(html, filePath);
23
+
24
+ expect(result).toBe(
25
+ '<html><head><title>T</title><link rel="stylesheet" href="/main.css"></head><body><script type="module" src="/app/main.js"></script></body></html>'
26
+ );
27
+ });
28
+
29
+ it('falls back to inserting the stylesheet before <title> when there is no </head>', async () => {
30
+ const html = '<html><title>T</title><body></body></html>';
31
+
32
+ const result = await addTags(html, filePath);
33
+
34
+ expect(result).toBe(
35
+ '<html><link rel="stylesheet" href="/main.css"><title>T</title><body><script type="module" src="/app/main.js"></script></body></html>'
36
+ );
37
+ });
38
+
39
+ it('appends both tags to the end when neither anchor is present', async () => {
40
+ const html = '<html></html>';
41
+
42
+ const result = await addTags(html, filePath);
43
+
44
+ expect(result).toBe(
45
+ '<html></html><link rel="stylesheet" href="/main.css"><script type="module" src="/app/main.js"></script>'
46
+ );
47
+ });
48
+
49
+ it('writes the modified html to filePath', async () => {
50
+ const html = '<html><head></head><body></body></html>';
51
+
52
+ const result = await addTags(html, filePath);
53
+
54
+ expect(fs.readFileSync(filePath, 'utf8')).toBe(result);
55
+ });
56
+ });
@@ -1,8 +1,7 @@
1
1
  import { readFile } from 'fs/promises';
2
- import { exit } from 'process';
3
- import { print, isVerbose } from '../../utils/index.js';
4
- import { PATHS } from './paths.js';
5
2
  import path from 'path';
3
+ import { print, errorExit } from '../../../utils/index.js';
4
+ import { PATHS } from '../app/paths.js';
6
5
  import { addTags } from './add-tags.js';
7
6
 
8
7
  async function validateHtml() {
@@ -27,13 +26,8 @@ async function validateHtml() {
27
26
  if (!/<link[^>]*rel=["']stylesheet["']/.test(htmlContent)) {
28
27
  errorExit('No stylesheet found in HTML');
29
28
  }
30
-
31
- if (isVerbose) print.boldInfo('HTML validation passed\n');
32
- }
33
-
34
- function errorExit(message) {
35
- print.boldError(`[ERROR] ${message}`);
36
- exit(1);
37
29
  }
38
30
 
39
- validateHtml();
31
+ validateHtml().catch((err) => {
32
+ errorExit(err, 'validate-html');
33
+ });
@@ -0,0 +1,152 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { getFilesRecursive, print, pascalCased, capitalized } from '../../../utils/index.js';
4
+ import { PATHS } from '../app/paths.js';
5
+ import { minifyHTML } from './minify-html.js';
6
+
7
+ const PLACEHOLDER = '__TEMPLATE_PLACEHOLDER__';
8
+ const TARGET_EXTENSIONS = ['.ts', '.js'];
9
+ const TEMPLATE_REGEX = /const\s+(?:template|templateFn)\s*=?\s*(?:\(.*?\)\s*=>\s*)?`([\s\S]*?)`/;
10
+
11
+ // A component/view's template can live inline in its .ts/.js file or in a
12
+ // colocated .html sibling merged in via `__TEMPLATE_PLACEHOLDER__` — resolved
13
+ // automatically per file rather than through a global config flag:
14
+ // external + placeholder -> merge external in
15
+ // external + real inline markup -> conflict, fail
16
+ // no external + real inline markup -> minify inline
17
+ // no external + still the placeholder -> nothing to use, fail
18
+ export async function resolveTemplates() {
19
+ const htmlFiles = await getFilesRecursive(PATHS.tempApp, 'html');
20
+ const resolvedPaths = new Set();
21
+ const failures = [];
22
+
23
+ for (const htmlPath of htmlFiles) {
24
+ await resolveExternalTemplate(htmlPath, resolvedPaths, failures);
25
+ }
26
+
27
+ await resolveRemainingTemplates(resolvedPaths, failures);
28
+
29
+ if (failures.length) {
30
+ throw new Error(`Failed to resolve ${failures.length} template(s).`);
31
+ }
32
+ }
33
+
34
+ // Normally a view/component has exactly one sibling here — `.js` in dev (tsc
35
+ // output), `.ts` in prod (esbuild bundles straight from source, tsc runs
36
+ // --noEmit there). Checking whichever exist(s) rather than assuming one, so
37
+ // this can't silently regress again if that dev/prod split ever changes.
38
+ async function resolveExternalTemplate(htmlPath, resolvedPaths, failures) {
39
+ const htmlFilename = path.basename(htmlPath);
40
+
41
+ let htmlContent;
42
+ try {
43
+ htmlContent = await fs.readFile(htmlPath, 'utf-8');
44
+ } catch (err) {
45
+ print.warn(`Failed to read "${htmlFilename}". ${err.message}`);
46
+ failures.push(htmlFilename);
47
+ return;
48
+ }
49
+
50
+ const minifiedHtml = await minifyHTML(htmlContent);
51
+ let mergedCount = 0;
52
+ let conflictCount = 0;
53
+
54
+ for (const extension of TARGET_EXTENSIONS) {
55
+ const codePath = htmlPath.replace(/\.html$/, extension);
56
+
57
+ let content;
58
+ try {
59
+ content = await fs.readFile(codePath, 'utf-8');
60
+ } catch (err) {
61
+ if (err.code === 'ENOENT') continue;
62
+ print.warn(`Cannot update "${path.basename(codePath)}". ${err.message}`);
63
+ failures.push(path.basename(codePath));
64
+ resolvedPaths.add(codePath);
65
+ continue;
66
+ }
67
+
68
+ resolvedPaths.add(codePath);
69
+
70
+ if (content.includes(PLACEHOLDER)) {
71
+ await fs.writeFile(codePath, content.replace(PLACEHOLDER, minifiedHtml));
72
+ mergedCount++;
73
+ continue;
74
+ }
75
+
76
+ print.warn(describeConflict(codePath));
77
+ failures.push(path.basename(codePath));
78
+ conflictCount++;
79
+ }
80
+
81
+ if (mergedCount > 0) {
82
+ await fs.unlink(htmlPath);
83
+ } else if (mergedCount + conflictCount === 0) {
84
+ print.warn(describeOrphanHtml(htmlPath));
85
+ failures.push(htmlFilename);
86
+ }
87
+ }
88
+
89
+ async function resolveRemainingTemplates(resolvedPaths, failures) {
90
+ const codeFiles = await getFilesRecursive(PATHS.tempApp, TARGET_EXTENSIONS);
91
+
92
+ for (const codePath of codeFiles) {
93
+ if (resolvedPaths.has(codePath)) continue;
94
+
95
+ const content = await fs.readFile(codePath, 'utf-8');
96
+ const match = content.match(TEMPLATE_REGEX);
97
+ if (!match) continue;
98
+
99
+ const [full, body] = match;
100
+
101
+ if (body === PLACEHOLDER) {
102
+ print.warn(describeMissingTemplate(codePath));
103
+ failures.push(path.basename(codePath));
104
+ continue;
105
+ }
106
+
107
+ const minified = await minifyHTML(body);
108
+ await fs.writeFile(codePath, content.replace(full, full.replace(body, minified)));
109
+ }
110
+ }
111
+
112
+ function describeCodeFile(codePath) {
113
+ const filename = path.basename(codePath);
114
+ const basename = filename.replace(/\.(ts|js)$/, '');
115
+ const lastDot = basename.lastIndexOf('.');
116
+ const namePart = lastDot === -1 ? basename : basename.slice(0, lastDot);
117
+ const suffix = lastDot === -1 ? '' : basename.slice(lastDot + 1);
118
+ const kind = suffix ? capitalized(suffix) : 'Component';
119
+ const className = `${pascalCased(namePart)}${kind}`;
120
+
121
+ return { filename, basename, kind, className };
122
+ }
123
+
124
+ function describeConflict(codePath) {
125
+ const { filename, basename, kind, className } = describeCodeFile(codePath);
126
+
127
+ return `${kind} "${className}" has both an inline template and an external template.
128
+
129
+ Use either:
130
+ ${filename}
131
+ or:
132
+ ${filename} (template placeholder) + ${basename}.html
133
+ Remove one of the template definitions.`;
134
+ }
135
+
136
+ function describeMissingTemplate(codePath) {
137
+ const { filename, basename, kind, className } = describeCodeFile(codePath);
138
+
139
+ return `${kind} "${className}" has a template placeholder but no external template.
140
+
141
+ Expected:
142
+ ${filename} (template placeholder) + ${basename}.html
143
+ Add ${basename}.html, or replace the placeholder in ${filename} with inline markup.`;
144
+ }
145
+
146
+ function describeOrphanHtml(htmlPath) {
147
+ const htmlFilename = path.basename(htmlPath);
148
+ const basename = htmlFilename.replace(/\.html$/, '');
149
+
150
+ return `"${htmlFilename}" has no matching component or view (expected ${basename}.ts or ${basename}.js).
151
+ Remove ${htmlFilename}, or add ${basename}.ts with a template placeholder to merge it into.`;
152
+ }
@@ -0,0 +1,15 @@
1
+ import { minify } from 'html-minifier-terser';
2
+
3
+ export async function minifyHTML(html) {
4
+ return minify(html, {
5
+ collapseWhitespace: true,
6
+ removeComments: true,
7
+ removeRedundantAttributes: true,
8
+ removeScriptTypeAttributes: true,
9
+ removeStyleLinkTypeAttributes: true,
10
+ useShortDoctype: true,
11
+ keepClosingSlash: true,
12
+ minifyCSS: true,
13
+ minifyJS: true,
14
+ });
15
+ }
@@ -0,0 +1,8 @@
1
+ import { errorExit } from '../../../utils/index.js';
2
+ import { resolveTemplates } from './merge-templates.js';
3
+
4
+ try {
5
+ await resolveTemplates();
6
+ } catch (err) {
7
+ errorExit(err);
8
+ }