@nutin/cli 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +122 -8
  2. package/README.md +21 -37
  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 +81 -30
  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/create-app/new-app-prompt.mjs +51 -0
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
  23. package/lib/src/create-app/success-message.mjs +14 -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 +136 -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 +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -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 +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -15
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -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 +1 -56
  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 +4 -37
  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 +81 -31
  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 +38 -23
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
  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 +13 -14
  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 +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -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 +74 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  75. package/templates/base/src/core/config.ts.hbs +7 -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 +149 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -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 +43 -0
  91. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  92. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
  93. package/templates/base/src/core/services/index.ts.hbs +5 -6
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  96. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  97. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
  100. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
  103. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  104. package/templates/base/src/core/services/router/router.ts.hbs +40 -9
  105. package/templates/base/src/index.html.hbs +2 -6
  106. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  107. package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
  108. package/templates/base/src/styles/main.scss.hbs +1 -18
  109. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  110. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  111. package/templates/base/tools/builder/builder.js.hbs +24 -26
  112. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  115. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  116. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  117. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  118. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  119. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  120. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  121. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  122. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  123. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  124. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  125. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  126. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  127. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  130. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  131. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  132. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  133. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  137. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  138. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  139. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  140. package/templates/base/tools/dev/serve.js.hbs +11 -1
  141. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  142. package/templates/base/tools/generator/generator.js.hbs +64 -47
  143. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  144. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  145. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  146. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  147. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  148. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  149. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  150. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  151. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  154. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  155. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  156. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  157. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  158. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  159. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  165. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  166. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  169. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  170. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  171. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  173. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  176. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  177. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  178. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  179. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  180. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  181. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  182. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  186. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  187. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  188. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  189. package/templates/base/tools/utils/format.js.hbs +10 -10
  190. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  191. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  192. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  193. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  194. package/templates/base/tools/utils/index.js.hbs +1 -3
  195. package/templates/base/tools/utils/languages.js.hbs +15 -0
  196. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  197. package/templates/base/tools/utils/print.js.hbs +19 -13
  198. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  199. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  200. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  201. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  202. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  203. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  204. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  205. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  206. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  207. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  208. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  214. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  215. package/lib/src/context-builder.mjs +0 -35
  216. package/lib/src/file-generator.mjs +0 -109
  217. package/lib/src/json-generator.mjs +0 -31
  218. package/lib/src/package-manager.mjs +0 -139
  219. package/lib/src/print.mjs +0 -31
  220. package/lib/src/prompts.mjs +0 -123
  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 -35
  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 -85
  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 -108
  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/popover.ts.hbs +0 -145
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  242. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  243. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  244. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  245. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  246. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  247. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  248. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  249. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  250. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  251. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  252. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  253. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  254. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  255. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  256. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  257. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  258. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  259. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  260. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
  261. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  262. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  263. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  264. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  265. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  266. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  267. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  268. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  269. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  270. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  271. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  272. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  273. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  274. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  275. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  276. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  277. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  278. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  279. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  280. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  281. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  282. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  283. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  284. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,54 @@
1
+ import esbuild from 'esbuild';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { pathToFileURL } from 'url';
5
+ import { PATHS } from '../../app/paths.js';
6
+ import { builderConfig } from '../../../builder.config.js';
7
+
8
+ const SSR_DIR = path.join(PATHS.temp, '.ssr');
9
+ const ENTRY_FILE = path.join(PATHS.tempApp, '__ssr-entry.ts');
10
+ const OUT_FILE = path.join(SSR_DIR, 'ssr-bundle.mjs');
11
+
12
+ const ENTRY_CONTENT = `export { appRoutes } from './routes.js';
13
+ export { I18nService, Service, registerPipes } from '../core/index.js';
14
+ export { RouteGuardsManager } from '../core/services/router/helpers/route-guard-manager.helper.js';
15
+ `;
16
+
17
+ /**
18
+ * Bundles a synthetic entry (re-exporting appRoutes + I18nService/Service) targeting Node,
19
+ * so generate-seo-html.js can import it and render real Views in a linkedom environment.
20
+ * Written outside dist-build/src so hash-files.js/compress-files.js never see it.
21
+ */
22
+ export async function buildSsrBundle() {
23
+ fs.mkdirSync(SSR_DIR, { recursive: true });
24
+ fs.writeFileSync(ENTRY_FILE, ENTRY_CONTENT, 'utf-8');
25
+
26
+ try {
27
+ await esbuild.build({
28
+ ...builderConfig.esbuild,
29
+ platform: 'node',
30
+ format: 'esm',
31
+ target: ['node22'],
32
+ minify: false,
33
+ drop: [],
34
+ legalComments: 'none',
35
+ loader: {
36
+ '.json': 'json',
37
+ },
38
+ define: {
39
+ 'process.env.NODE_ENV': '"production"',
40
+ },
41
+ entryPoints: [ENTRY_FILE],
42
+ outfile: OUT_FILE,
43
+ keepNames: true,
44
+ });
45
+ } finally {
46
+ fs.rmSync(ENTRY_FILE, { force: true });
47
+ }
48
+
49
+ return pathToFileURL(OUT_FILE).href;
50
+ }
51
+
52
+ export function cleanupSsrBundle() {
53
+ fs.rmSync(SSR_DIR, { recursive: true, force: true });
54
+ }
@@ -0,0 +1,132 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Installs the minimum browser globals the framework's own base classes touch
6
+ * unconditionally (Service's `beforeunload` listener, I18n's `navigator`/`window.location`
7
+ * reads), plus a couple of cheap defensive stubs (matchMedia) for common patterns
8
+ * downstream projects add (e.g. dark-mode toggles).
9
+ *
10
+ * Returns `trackedFetches`, the in-flight promises served by the fetch shim during this
11
+ * render — ssr-render.js awaits these before capturing markup, to let any resulting
12
+ * fetch-driven re-render (via `listenToRenderEvents`) complete first.
13
+ */
14
+ function setGlobal(target, key, value) {
15
+ try {
16
+ Object.defineProperty(target, key, { value, writable: true, configurable: true, enumerable: true });
17
+ } catch (err) {
18
+ // Object.defineProperty throws a TypeError for a genuinely non-configurable
19
+ // property — skip it and let whatever Node/linkedom already provides stand.
20
+ // Anything else is unexpected and shouldn't be silently swallowed.
21
+ if (!(err instanceof TypeError)) throw err;
22
+ }
23
+ }
24
+
25
+ const KNOWN_DOM_GLOBALS = [
26
+ 'document', 'customElements',
27
+ 'Node', 'Element', 'HTMLElement', 'HTMLDocument',
28
+ 'HTMLInputElement', 'HTMLTextAreaElement', 'HTMLImageElement',
29
+ 'HTMLMediaElement', 'HTMLSourceElement', 'HTMLAnchorElement',
30
+ 'HTMLButtonElement', 'HTMLFormElement', 'HTMLSelectElement',
31
+ 'HTMLOptionElement', 'HTMLTemplateElement', 'HTMLStyleElement',
32
+ 'HTMLScriptElement', 'HTMLLinkElement', 'HTMLMetaElement',
33
+ 'HTMLTitleElement', 'HTMLHeadElement', 'HTMLBodyElement',
34
+ 'HTMLHtmlElement', 'HTMLLabelElement', 'HTMLSpanElement',
35
+ 'HTMLDivElement', 'HTMLUListElement', 'HTMLOListElement',
36
+ 'HTMLLIElement', 'HTMLTableElement', 'HTMLParagraphElement',
37
+ 'HTMLHeadingElement', 'HTMLIFrameElement',
38
+ 'SVGElement', 'Text', 'Comment', 'DocumentFragment', 'DocumentType',
39
+ 'Event', 'CustomEvent', 'MutationObserver', 'NodeFilter', 'NodeList',
40
+ 'DOMParser', 'XMLSerializer', 'Attr',
41
+ ];
42
+
43
+ function createMemoryStorage() {
44
+ const store = new Map();
45
+ return {
46
+ getItem: (key) => (store.has(key) ? store.get(key) : null),
47
+ setItem: (key, value) => { store.set(key, String(value)); },
48
+ removeItem: (key) => { store.delete(key); },
49
+ clear: () => { store.clear(); },
50
+ key: (index) => Array.from(store.keys())[index] ?? null,
51
+ get length() { return store.size; },
52
+ };
53
+ }
54
+
55
+ export function installGlobals(window, { lang, mockFetch = {}, localesDir } = {}) {
56
+ for (const key of Object.getOwnPropertyNames(window)) {
57
+ setGlobal(globalThis, key, window[key]);
58
+ }
59
+ for (const key of KNOWN_DOM_GLOBALS) {
60
+ if (typeof window[key] !== 'undefined') setGlobal(globalThis, key, window[key]);
61
+ }
62
+ setGlobal(globalThis, 'window', window);
63
+
64
+ const navigatorStub = { language: lang, userAgent: 'nutin-ssr' };
65
+ setGlobal(globalThis, 'navigator', navigatorStub);
66
+ setGlobal(window, 'navigator', navigatorStub);
67
+
68
+ // linkedom doesn't implement localStorage/sessionStorage at all
69
+ // I18n's savePreferences()/getPreferences() need it.
70
+ const localStorageStub = createMemoryStorage();
71
+ setGlobal(globalThis, 'localStorage', localStorageStub);
72
+ setGlobal(window, 'localStorage', localStorageStub);
73
+ const sessionStorageStub = createMemoryStorage();
74
+ setGlobal(globalThis, 'sessionStorage', sessionStorageStub);
75
+ setGlobal(window, 'sessionStorage', sessionStorageStub);
76
+
77
+ const matchMediaStub = (query) => ({
78
+ matches: false,
79
+ media: query,
80
+ addEventListener() {},
81
+ removeEventListener() {},
82
+ addListener() {},
83
+ removeListener() {},
84
+ dispatchEvent: () => false,
85
+ });
86
+ setGlobal(globalThis, 'matchMedia', matchMediaStub);
87
+ setGlobal(window, 'matchMedia', matchMediaStub);
88
+
89
+ const trackedFetches = [];
90
+
91
+ const fetchShim = (input) => {
92
+ const url = typeof input === 'string' ? input : input?.url;
93
+ const promise = resolveFetch(url, { mockFetch, localesDir });
94
+ trackedFetches.push(promise);
95
+ return promise;
96
+ };
97
+ setGlobal(globalThis, 'fetch', fetchShim);
98
+ setGlobal(window, 'fetch', fetchShim);
99
+
100
+ return { trackedFetches };
101
+ }
102
+
103
+ async function resolveFetch(url, { mockFetch, localesDir }) {
104
+ if (Object.prototype.hasOwnProperty.call(mockFetch, url)) {
105
+ return jsonResponse(mockFetch[url]);
106
+ }
107
+
108
+ const localeMatch = /^\/locales\/([a-zA-Z-]+)\.json$/.exec(url ?? '');
109
+ if (localeMatch) {
110
+ const filePath = path.join(localesDir, `${localeMatch[1]}.json`);
111
+ if (fs.existsSync(filePath)) {
112
+ const body = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
113
+ return jsonResponse(body);
114
+ }
115
+ // No translations authored for this locale yet — a normal early-project state, not an error.
116
+ return jsonResponse({});
117
+ }
118
+
119
+ throw new Error(
120
+ `Unexpected fetch("${url}") during SSR — no "mockFetch" entry for this endpoint and it isn't a ` +
121
+ `/locales/*.json request. Add a "mockFetch" entry for this route in config/seo.json if this is intentional.`
122
+ );
123
+ }
124
+
125
+ function jsonResponse(body) {
126
+ return {
127
+ ok: true,
128
+ status: 200,
129
+ json: async () => body,
130
+ text: async () => JSON.stringify(body),
131
+ };
132
+ }
@@ -0,0 +1,95 @@
1
+ import { parseHTML } from 'linkedom';
2
+ import path from 'path';
3
+ import { PATHS } from '../../app/paths.js';
4
+ import { installGlobals } from './ssr-polyfills.js';
5
+ import { print } from '../../../../utils/index.js';
6
+
7
+ let renderIndex = 0;
8
+
9
+ /**
10
+ * Reads the real app's route paths (the keys of `appRoutes`) without rendering anything —
11
+ * used to warn about app routes that have no matching config/seo.json entry.
12
+ */
13
+ export async function getAppRoutePaths(bundleUrl, lang, pageUrl) {
14
+ // Same reasoning as renderRoute() below: I18n's getLocaleFromUrl() reads
15
+ // window.location.pathname, so a location-less window crashes as soon as the
16
+ // bundle's module-load side effects instantiate I18n.getInstance().
17
+ const { window } = parseHTML('<!doctype html><html><body></body></html>', { location: new URL(pageUrl) });
18
+ installGlobals(window, { lang });
19
+ const bundle = await import(`${bundleUrl}?ssr=routes-check`);
20
+ return Object.keys(bundle.appRoutes);
21
+ }
22
+
23
+ /**
24
+ * Renders one (route, lang) pair by instantiating the real View via appRoutes and calling
25
+ * .render(), returning the resulting real markup. Fresh linkedom window + cache-busted
26
+ * dynamic import per call, so no singleton state (I18nService, etc.) leaks between renders.
27
+ */
28
+ export async function renderRoute({ bundleUrl, appRoutesKey, mockParams, mockFetch, lang, pageUrl, i18nEnabled }) {
29
+ // Passing a real URL instance as `location` gives `.pathname`/`.href`
30
+ // which is what I18n's getLocaleFromUrl() actually reads.
31
+ const { window } = parseHTML('<!doctype html><html><body></body></html>', { location: new URL(pageUrl) });
32
+ const { trackedFetches } = installGlobals(window, {
33
+ lang,
34
+ mockFetch: mockFetch ?? {},
35
+ localesDir: path.join(PATHS.tempSource, 'locales'),
36
+ });
37
+
38
+ let Service;
39
+ let constructorName = appRoutesKey;
40
+
41
+ try {
42
+ const bundle = await import(`${bundleUrl}?ssr=${renderIndex++}`);
43
+ ({ Service } = bundle);
44
+ const { appRoutes, I18nService, RouteGuardsManager, registerPipes } = bundle;
45
+
46
+ const routeConfig = appRoutes[appRoutesKey];
47
+ if (!routeConfig) {
48
+ throw new Error(
49
+ `No appRoutes entry found for "${appRoutesKey}" — check that config/seo.json's route "path" ` +
50
+ `matches a real key in src/app/routes.ts.`
51
+ );
52
+ }
53
+
54
+ registerPipes();
55
+
56
+ if (i18nEnabled) await I18nService.setCurrentLanguage(lang);
57
+
58
+ const viewConstructor = RouteGuardsManager.getViewConstructor(routeConfig);
59
+ const view = viewConstructor();
60
+ constructorName = view.constructor.name;
61
+
62
+ if (mockParams) view.setRouteParams(mockParams);
63
+
64
+ const element = view.render();
65
+
66
+ await Promise.all(trackedFetches);
67
+ await Promise.resolve();
68
+
69
+ return element.outerHTML;
70
+ } catch (err) {
71
+ throw new Error(
72
+ `[ssr] Failed to render route "${appRoutesKey}" (lang "${lang}", view "${constructorName}"): ${err.message}${hintForError(err)}`,
73
+ { cause: err }
74
+ );
75
+ } finally {
76
+ if (Service) {
77
+ try {
78
+ await Service.destroyAll();
79
+ } catch (cleanupErr) {
80
+ print.grayError(`[ssr] destroyAll cleanup failed: ${cleanupErr.message}`);
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ function hintForError(err) {
87
+ if (err instanceof ReferenceError && /is not defined$/.test(err.message)) {
88
+ return (
89
+ `\n Hint: this usually means the component tree touches an unguarded browser global ` +
90
+ `(e.g. "window", "navigator", "matchMedia") that isn't polyfilled for SSR — see ` +
91
+ `tools/builder/core/ssr/ssr-polyfills.js.`
92
+ );
93
+ }
94
+ return '';
95
+ }
@@ -0,0 +1,40 @@
1
+ import * as sass from 'sass';
2
+ import * as fs from 'fs';
3
+ import path from 'path';
4
+ import { getFilesRecursive, print, errorExit } from '../../../utils/index.js';
5
+ import { PATHS } from '../app/paths.js';
6
+ import { builderConfig } from '../../builder.config.js';
7
+
8
+ const stylesOutput = path.join(PATHS.tempSource, 'main.css');
9
+
10
+ // global styles (partials)
11
+ const stylesPath = path.join(PATHS.source, 'styles');
12
+ const scssOrigins = builderConfig.sass.paths;
13
+ const scssPath = (origin) => path.join(stylesPath, origin);
14
+ const stylesInput = scssOrigins.map(origin => scssPath(origin));
15
+ const pathsToLoad = [ ...stylesInput ];
16
+
17
+ try {
18
+ const mainResult = await sass.compileAsync(path.join(stylesPath, 'main.scss'), {
19
+ loadPaths: [ ...pathsToLoad ],
20
+ style: 'compressed'
21
+ });
22
+ fs.writeFileSync(stylesOutput, mainResult.css);
23
+
24
+ // features styles
25
+ const appInput = path.join(PATHS.source, 'app');
26
+ const appStyles = [
27
+ ...getFilesRecursive(path.join(appInput, 'components'), 'scss'),
28
+ ...getFilesRecursive(path.join(appInput, 'views'), 'scss')
29
+ ];
30
+
31
+ for (const style of appStyles) {
32
+ const result = await sass.compileAsync(style, {
33
+ loadPaths: [ ...pathsToLoad ],
34
+ style: 'compressed'
35
+ })
36
+ fs.appendFileSync(stylesOutput, result.css);
37
+ }
38
+ } catch (err) {
39
+ errorExit(err, 'sass');
40
+ }
@@ -0,0 +1,84 @@
1
+ import { execFileSync } from 'child_process';
2
+ import * as fs from 'fs';
3
+ import path from 'path';
4
+ import { print, runCommand, promptBoolean, errorExit } from '../../../utils/index.js';
5
+ import { PATHS } from '../app/paths.js';
6
+ import { builderConfig } from '../../builder.config.js';
7
+
8
+ const REQUIRED_DEPS = [
9
+ { name: 'tailwindcss', version: '^4.3.0' },
10
+ { name: '@tailwindcss/cli', version: '^4.3.0' },
11
+ ];
12
+
13
+ const isInstalled = (depName) => fs.existsSync(path.join(process.cwd(), 'node_modules', depName, 'package.json'));
14
+
15
+ const getPackageManager = () => {
16
+ if (fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'))) return 'pnpm';
17
+ if (fs.existsSync(path.join(process.cwd(), 'yarn.lock'))) return 'yarn';
18
+ if (fs.existsSync(path.join(process.cwd(), 'bun.lock')) || fs.existsSync(path.join(process.cwd(), 'bun.lockb'))) return 'bun';
19
+ return 'npm';
20
+ };
21
+
22
+ const getInstallCommand = (packageManager, deps) => {
23
+ const pkgList = deps.map(dep => `${dep.name}@${dep.version}`).join(' ');
24
+ switch (packageManager) {
25
+ case 'yarn': return `yarn add -D ${pkgList}`;
26
+ case 'pnpm': return `pnpm add -D ${pkgList}`;
27
+ case 'bun': return `bun add -D ${pkgList}`;
28
+ default: return `npm install -D ${pkgList}`;
29
+ }
30
+ };
31
+
32
+ const missingDeps = REQUIRED_DEPS.filter(dep => !isInstalled(dep.name));
33
+
34
+ if (missingDeps.length) {
35
+ if (builderConfig.isProd) {
36
+ errorExit('Tailwind CSS is enabled (tailwind: true) but its dependencies are missing.\nRun "npm install" before rebuilding');
37
+ }
38
+ const packageManager = getPackageManager();
39
+ const installCommand = getInstallCommand(packageManager, missingDeps);
40
+
41
+ print.warn('Tailwind CSS is enabled (tailwind: true) but its dependencies are missing.');
42
+ print.gray('Required packages:');
43
+ missingDeps.forEach(dep => print.gray(` - ${dep.name}@${dep.version}`));
44
+
45
+ let shouldInstall = true;
46
+ if (process.stdin.isTTY) {
47
+ shouldInstall = await promptBoolean('Install them now?');
48
+ } else {
49
+ print.gray(`Non-interactive shell detected. Installing automatically: ${installCommand}`);
50
+ }
51
+
52
+ if (!shouldInstall) {
53
+ print.boldError(`Aborting. Run manually: ${installCommand}`);
54
+ process.exit(1);
55
+ }
56
+
57
+ const [command, ...args] = installCommand.split(' ');
58
+ try {
59
+ await runCommand(command, args);
60
+ } catch (err) {
61
+ errorExit(err, 'tailwind-install');
62
+ }
63
+ }
64
+
65
+ const twBin = path.join(process.cwd(), 'node_modules', '.bin', 'tailwindcss');
66
+ const input = path.join(PATHS.source, 'styles', 'tailwind.css');
67
+ const output = path.join(PATHS.tempSource, 'tw-out.css');
68
+ const mainCss = path.join(PATHS.tempSource, 'main.css');
69
+
70
+ const args = ['-i', input, '-o', output, '--silent'];
71
+ if (builderConfig.isProd) args.push('--minify');
72
+
73
+ try {
74
+ execFileSync(twBin, args, { stdio: 'inherit' });
75
+ } catch (err) {
76
+ errorExit(err, 'tailwind');
77
+ }
78
+
79
+ // Tailwind's @layer blocks always lose to unlayered SCSS in the cascade regardless
80
+ // of order, so prepending here is only for readable file ordering, not precedence.
81
+ const tailwindCss = fs.readFileSync(output, 'utf-8');
82
+ const existingCss = fs.readFileSync(mainCss, 'utf-8');
83
+ fs.writeFileSync(mainCss, tailwindCss + '\n' + existingCss);
84
+ fs.unlinkSync(output);
@@ -1,32 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { spawn } from 'child_process';
3
4
  import { print, runCommand } from '../utils/index.js';
4
5
 
5
6
  async function startDev() {
6
- try {
7
- console.clear();
8
- print.boldHead('🚀 Starting Dev Environment...');
9
-
10
- await runCommand('{{packageManager}}', ['run', 'build']);
11
-
12
- const { serve, watcher } = await Promise.all([
13
- runCommand('{{packageManager}}', ['run', 'serve:only']),
14
- runCommand('node', ['tools/dev/watcher.js'])
15
- ]);
7
+ console.clear();
8
+ print.blue('🚀 Starting Dev Environment...\n');
16
9
 
17
- serve.on('close', (code) => {
18
- print.error(`live-server exited with code ${code}`);
19
- process.exit(code);
20
- });
21
- watcher.on('close', (code) => {
22
- print.error(`watcher exited with code ${code}`);
23
- });
10
+ try {
11
+ await runCommand('{{packageManager}}', ['run', 'build', '--silent']);
24
12
  } catch (err) {
25
13
  print.boldError(`Dev startup failed: ${err.message}`);
14
+ process.exit(1);
26
15
  }
16
+
17
+ const serve = spawn(['{{packageManager}}', 'run', 'serve:only', '--silent'].join(' '), { stdio: 'inherit', shell: true });
18
+ const watcher = spawn(['node', 'tools/dev/watcher.js', '--silent'].join(' '), { stdio: 'inherit', shell: true });
19
+
20
+ serve.on('error', (err) => {
21
+ print.boldError(`live-server failed to start: ${err.message}`);
22
+ process.exit(1);
23
+ });
24
+ serve.on('close', (code) => {
25
+ print.error(`live-server exited with code ${code}`);
26
+ process.exit(code ?? 1);
27
+ });
28
+ watcher.on('error', (err) => {
29
+ print.boldError(`watcher failed to start: ${err.message}`);
30
+ process.exit(1);
31
+ });
32
+ watcher.on('close', (code) => {
33
+ print.error(`watcher exited with code ${code}`);
34
+ });
27
35
  }
28
36
 
29
37
  startDev().catch((err) => {
30
38
  print.boldError(`Unexpected error: ${err.message}`);
31
- exit(1);
39
+ process.exit(1);
32
40
  });
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import liveServer from 'live-server';
4
+ import { print } from '../utils/index.js';
4
5
 
5
6
  const params = {
6
7
  port: 9090,
@@ -20,4 +21,13 @@ const params = {
20
21
  ]
21
22
  };
22
23
 
23
- liveServer.start(params);
24
+ try {
25
+ liveServer.start(params);
26
+ } catch (err) {
27
+ if (err.code === 'EADDRINUSE') {
28
+ print.boldError(`Port ${params.port} is already in use — stop the process using it or change the dev port.`);
29
+ } else {
30
+ print.boldError(`live-server failed to start: ${err.message}`);
31
+ }
32
+ process.exit(1);
33
+ }
@@ -11,6 +11,10 @@ const watcher = chokidar.watch(['src'], {
11
11
  let isBuilding = false;
12
12
  let buildTimeout = null;
13
13
 
14
+ watcher.on('error', (err) => {
15
+ print.boldError(`\nWatcher error: ${err.message}`);
16
+ });
17
+
14
18
  watcher.on('change', (filePath) => {
15
19
  if (buildTimeout) {
16
20
  clearTimeout(buildTimeout);
@@ -24,15 +28,19 @@ watcher.on('change', (filePath) => {
24
28
  print.boldInfo(`\n🔄 File changed: ${path.relative(process.cwd(), filePath)}\n`);
25
29
  print.info('\nRebuilding...');
26
30
 
27
- const command = '{{packageManager}} run build';
28
-
29
- exec(command, (err, stdout, stderr) => {
31
+ const command = '{{packageManager}} run build --silent';
32
+
33
+ exec(command, { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
30
34
  if (stdout) process.stdout.write(stdout);
31
35
  if (stderr) process.stderr.write(stderr);
32
36
  if (err) {
33
- print.error(`\n❌ Build failed: ${err.message}`);
37
+ if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
38
+ print.boldError(`\nBuild output exceeded the buffer size — raise maxBuffer in tools/dev/watcher.js.`);
39
+ } else {
40
+ print.boldError(`\nBuild failed: ${err.message}`);
41
+ }
34
42
  } else {
35
- print.boldHead('Watching for changes...');
43
+ print.boldBlue('Watching for changes...');
36
44
  }
37
45
  isBuilding = false;
38
46
  });
@@ -40,5 +48,5 @@ watcher.on('change', (filePath) => {
40
48
  });
41
49
 
42
50
  watcher.on('ready', () => {
43
- print.boldHead('\nWatching for changes...\n');
51
+ print.boldBlue('Watching for changes...');
44
52
  });