@nutin/cli 1.3.1 → 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 (287) hide show
  1. package/CHANGELOG.md +89 -9
  2. package/README.md +20 -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 +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 -19
  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 +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 +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 +7 -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 +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 +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 +24 -33
  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/data-binding.helper.test.js.hbs +75 -0
  70. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  71. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  72. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  73. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  74. package/templates/base/src/core/config.ts.hbs +7 -0
  75. package/templates/base/src/core/index.ts.hbs +0 -3
  76. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  77. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  78. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  79. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  80. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  81. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  83. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  85. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  86. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  87. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  89. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  90. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  91. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
  92. package/templates/base/src/core/services/index.ts.hbs +5 -6
  93. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  95. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  96. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  97. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +40 -29
  99. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  100. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
  102. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  103. package/templates/base/src/core/services/router/router.ts.hbs +40 -8
  104. package/templates/base/src/index.html.hbs +2 -6
  105. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  106. package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
  107. package/templates/base/src/styles/main.scss.hbs +1 -18
  108. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  109. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  110. package/templates/base/tools/builder/builder.js.hbs +24 -26
  111. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  112. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  114. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  115. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  116. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  117. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  118. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  119. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  120. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  121. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  122. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  123. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  124. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  125. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  126. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  127. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  129. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  130. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  131. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  132. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  133. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  136. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  137. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  138. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  139. package/templates/base/tools/dev/serve.js.hbs +11 -1
  140. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  141. package/templates/base/tools/generator/generator.js.hbs +64 -47
  142. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  143. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  144. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  145. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  146. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  147. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  148. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  149. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  150. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  151. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  153. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  154. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  155. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  156. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  157. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  158. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  159. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  164. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  165. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  166. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  168. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  169. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  170. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  171. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  172. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  173. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  175. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  176. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  177. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  178. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  179. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  180. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  181. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  182. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  185. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  186. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  187. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  188. package/templates/base/tools/utils/format.js.hbs +10 -10
  189. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  190. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  191. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  192. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  193. package/templates/base/tools/utils/index.js.hbs +1 -3
  194. package/templates/base/tools/utils/languages.js.hbs +15 -0
  195. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  196. package/templates/base/tools/utils/print.js.hbs +19 -13
  197. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  198. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  199. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  200. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  201. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  202. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  203. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  204. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  205. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  206. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  207. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  208. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  211. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  213. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  214. package/lib/src/context-builder.mjs +0 -40
  215. package/lib/src/file-generator.mjs +0 -109
  216. package/lib/src/json-generator.mjs +0 -31
  217. package/lib/src/package-manager.mjs +0 -139
  218. package/lib/src/print.mjs +0 -32
  219. package/lib/src/utils.mjs +0 -18
  220. package/lib/src/version.mjs +0 -1
  221. package/templates/base/builder.config.js.hbs +0 -36
  222. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  223. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  224. package/templates/base/src/app/dom.test.js.hbs +0 -13
  225. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  226. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  227. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  228. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  229. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  230. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
  231. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  232. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
  233. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  234. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  235. package/templates/base/src/libs/index.ts.hbs +0 -4
  236. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
  237. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
  242. package/templates/base/src/styles/core/_index.scss.hbs +0 -6
  243. package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
  244. package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
  245. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  246. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  247. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  248. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  249. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  250. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  251. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  252. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  253. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  254. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  255. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  256. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  257. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  258. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  259. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  260. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  261. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  262. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  263. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
  264. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  265. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  266. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  267. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  268. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  269. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  270. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  271. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  272. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  273. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  274. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  275. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  276. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  277. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  278. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  279. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  280. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  281. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  282. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  283. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  284. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  285. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  286. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  287. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,100 @@
1
+ import path from 'path';
2
+ import {
3
+ registerTestGlobals, getTestFiles, runQueuedTests, setCurrentTestFile, print,
4
+ startCoverage, stopCoverage, buildCoverageReport, printCoverageReport, maybeWriteUncoveredReport,
5
+ writeSummaryReport, setupJsdom, teardownJsdom,
6
+ } from './core/index.js';
7
+ import config from '#root/nutin.config.js';
8
+
9
+ const args = process.argv.slice(2);
10
+ const coverageEnabled = args.includes('--coverage') || Boolean(config.testinNutin.coverage?.enabled);
11
+ const verboseEnabled = args.includes('--verbose');
12
+ const fileFilters = args.filter(arg => arg !== '--coverage' && arg !== '--verbose');
13
+
14
+ // Tests import and run the compiled build output (dist/src/)
15
+ // and has no sourcemap back to the .ts sources
16
+ const origins = [];
17
+ const coverageDirs = [];
18
+
19
+ if (config.testinNutin.includeFramework) {
20
+ const frameworkDir = path.join('src', 'core');
21
+ origins.push(frameworkDir);
22
+ if (coverageEnabled) coverageDirs.push(path.join('dist', frameworkDir));
23
+ }
24
+
25
+ if (config.testinNutin.includeApp) {
26
+ const appDir = path.join('src', 'app');
27
+ origins.push(appDir);
28
+ if (coverageEnabled) coverageDirs.push(path.join('dist', appDir));
29
+ }
30
+
31
+ if (config.testinNutin.includeTools) origins.push('tools');
32
+
33
+ registerTestGlobals();
34
+
35
+ let testFiles = origins.flatMap(
36
+ origin => getTestFiles(path.join('..', origin))
37
+ );
38
+
39
+ if (fileFilters.length) {
40
+ testFiles = testFiles.filter(file => fileFilters.some(f => file.includes(f)));
41
+ }
42
+
43
+ async function loadTestFile(file) {
44
+ setupJsdom();
45
+ setCurrentTestFile(file);
46
+ await import(file);
47
+ teardownJsdom();
48
+ }
49
+
50
+ async function runCoverage(testResults) {
51
+ let scopedResults = await stopCoverage(coverageDirs);
52
+ if (fileFilters.length) {
53
+ scopedResults = scopedResults.filter(({ filePath }) => fileFilters.some(f => filePath.includes(f)));
54
+ }
55
+ const report = buildCoverageReport(scopedResults);
56
+ const globals = printCoverageReport(report);
57
+ writeSummaryReport(report, globals, testResults);
58
+ await maybeWriteUncoveredReport(report);
59
+
60
+ const threshold = config.testinNutin.coverage?.threshold;
61
+ if (typeof threshold === 'number') {
62
+ const misses = Object.entries({
63
+ lines: globals.globalLinesPct,
64
+ functions: globals.globalFunctionsPct,
65
+ branches: globals.globalBranchesPct,
66
+ }).filter(([, pct]) => pct < threshold);
67
+
68
+ if (misses.length) {
69
+ print.boldError(`\n✗ Coverage below threshold (${threshold}%): ${misses.map(([k, pct]) => `${k} ${pct.toFixed(1)}%`).join(', ')}\n`);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ }
74
+
75
+ async function runTests() {
76
+ if (verboseEnabled) print.head('Test suites :');
77
+
78
+ if (coverageEnabled) await startCoverage();
79
+
80
+ for (const file of testFiles) {
81
+ try {
82
+ await loadTestFile(file);
83
+ } catch (error) {
84
+ print.boldError(`✗ ${file} failed to load:`);
85
+ print.grayError(error.stack);
86
+ }
87
+ }
88
+
89
+ const testResults = await runQueuedTests();
90
+
91
+ if (testResults.failed > 0) process.exitCode = 1;
92
+
93
+ if (coverageEnabled) await runCoverage(testResults);
94
+ }
95
+
96
+ runTests().catch((err) => {
97
+ print.boldError(`Unexpected error: ${err.message}`);
98
+ print.grayError(err.stack);
99
+ process.exitCode = 1;
100
+ });
@@ -0,0 +1,37 @@
1
+ import chokidar from 'chokidar';
2
+ import { exec } from 'child_process';
3
+ import path from 'path';
4
+ import { print } from './core/index.js'
5
+
6
+ const watcher = chokidar.watch(['src', 'test', 'unit', 'e2e'], {
7
+ ignored: /(^|[/\\])\../,
8
+ persistent: true,
9
+ });
10
+
11
+ print.boldHead('Watching for test changes...')
12
+
13
+ watcher.on('error', (err) => {
14
+ print.boldError(`\nWatcher error: ${err.message}`);
15
+ });
16
+
17
+ let isRunning = false;
18
+ let runTimeout = null;
19
+
20
+ watcher.on('change', (filePath) => {
21
+ if (runTimeout) clearTimeout(runTimeout);
22
+
23
+ runTimeout = setTimeout(() => {
24
+ if (isRunning) return;
25
+ isRunning = true;
26
+
27
+ console.clear();
28
+ print.boldInfo(`\n🔄 File changed: ${path.relative(process.cwd(), filePath)}\n`);
29
+
30
+ exec('node --experimental-modules testin-nutin/runner.js', { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
31
+ if (stdout) process.stdout.write(stdout);
32
+ if (stderr) process.stderr.write(stderr);
33
+ if (err) print.boldError(`\nTest run failed: ${err.message}`);
34
+ isRunning = false;
35
+ });
36
+ }, 100);
37
+ });
@@ -0,0 +1,8 @@
1
+ import { exit } from 'process';
2
+ import { print } from './print.js';
3
+
4
+ export function errorExit(err, origin = '') {
5
+ print.boldError(`\n${origin} - Error: ${ err.message || err }`);
6
+ if (err.stack) print.grayError(err.stack)
7
+ exit(1);
8
+ }
@@ -5,20 +5,20 @@ export function getLastWord(input) {
5
5
  }
6
6
 
7
7
  export function capitalized(input) {
8
- const formatted = input.replace(
8
+ const formatted = input.replace(
9
9
  /\w*/g,
10
10
  function(txt) {
11
11
  return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
12
12
  })
13
- return formatted.replace(/,/g, '');
13
+ return formatted.replace(/,/g, '');
14
14
  }
15
15
 
16
16
  export function kebabCased(input) {
17
- return input.replace(/[A-Z][a-z]*/g, str => str.toLowerCase() + ' ').trim().replace(/ /g, '-').replace(/,/g, '');
17
+ return input.replace(/[A-Z][a-z]*/g, str => str.toLowerCase() + ' ').trim().replace(/ /g, '-').replace(/,/g, '');
18
18
  }
19
19
 
20
20
  export function camelCased(input) {
21
- return input
21
+ return input
22
22
  .split('-')
23
23
  .map((word, index) =>
24
24
  index === 0
@@ -36,10 +36,10 @@ export function pascalCased(input) {
36
36
  }
37
37
 
38
38
  export function allFormats(input) {
39
- return {
40
- kebab: kebabCased(input),
41
- pascal: pascalCased(input),
42
- camel: camelCased(input),
43
- capitalized: capitalized(input)
44
- }
39
+ return {
40
+ kebab: kebabCased(input),
41
+ pascal: pascalCased(input),
42
+ camel: camelCased(input),
43
+ capitalized: capitalized(input)
44
+ }
45
45
  }
@@ -0,0 +1,45 @@
1
+ import { getLastWord, capitalized, kebabCased, camelCased, pascalCased, allFormats } from './format.js';
2
+
3
+ describe('format', () => {
4
+ it('getLastWord returns the last non-empty path segment', () => {
5
+ expect(getLastWord('a/b/c')).toBe('c');
6
+ expect(getLastWord('a/b/c/')).toBe('c');
7
+ expect(getLastWord('single')).toBe('single');
8
+ });
9
+
10
+ it('getLastWord returns an empty string for non-string input', () => {
11
+ expect(getLastWord(123)).toBe('');
12
+ expect(getLastWord(undefined)).toBe('');
13
+ });
14
+
15
+ it('capitalized title-cases each word and strips commas', () => {
16
+ expect(capitalized('hello world')).toBe('Hello World');
17
+ expect(capitalized('HELLO, WORLd')).toBe('Hello World');
18
+ });
19
+
20
+ it('kebabCased splits on capital letters', () => {
21
+ expect(kebabCased('HelloWorld')).toBe('hello-world');
22
+ // leading lowercase run isn't matched by the [A-Z][a-z]* regex, so it
23
+ // sticks to the next captured word instead of being split off on its own
24
+ expect(kebabCased('myComponentName')).toBe('mycomponent-name');
25
+ });
26
+
27
+ it('camelCased joins kebab-case segments with an initial lowercase', () => {
28
+ expect(camelCased('my-component-name')).toBe('myComponentName');
29
+ expect(camelCased('single')).toBe('single');
30
+ });
31
+
32
+ it('pascalCased joins kebab-case segments with every segment capitalized', () => {
33
+ expect(pascalCased('my-component-name')).toBe('MyComponentName');
34
+ expect(pascalCased('single')).toBe('Single');
35
+ });
36
+
37
+ it('allFormats returns all four casings for the same input', () => {
38
+ expect(allFormats('my-component')).toEqual({
39
+ kebab: 'my-component',
40
+ pascal: 'MyComponent',
41
+ camel: 'myComponent',
42
+ capitalized: 'My-Component',
43
+ });
44
+ });
45
+ });
@@ -16,7 +16,12 @@ export function getFilesRecursive(dir, extension) {
16
16
 
17
17
  function findFilesRecursively(currentDir) {
18
18
  const files = [];
19
- const items = fs.readdirSync(currentDir, { withFileTypes: true });
19
+ let items;
20
+ try {
21
+ items = fs.readdirSync(currentDir, { withFileTypes: true });
22
+ } catch (err) {
23
+ throw new Error(`Failed to read directory "${currentDir}": ${err.message}`, { cause: err });
24
+ }
20
25
 
21
26
  for (const item of items) {
22
27
  const fullPath = path.join(currentDir, item.name);
@@ -0,0 +1,49 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { getFilesRecursive } from './get-files-recursive.js';
5
+
6
+ describe('getFilesRecursive', () => {
7
+ let tmpDir;
8
+
9
+ beforeEach(() => {
10
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-get-files-recursive-'));
11
+ fs.mkdirSync(path.join(tmpDir, 'nested'), { recursive: true });
12
+ fs.writeFileSync(path.join(tmpDir, 'a.js'), '');
13
+ fs.writeFileSync(path.join(tmpDir, 'b.css'), '');
14
+ fs.writeFileSync(path.join(tmpDir, 'nested', 'c.js'), '');
15
+ fs.writeFileSync(path.join(tmpDir, 'nested', 'd.txt'), '');
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tmpDir, { recursive: true, force: true });
20
+ });
21
+
22
+ it('recursively collects files matching a single extension', () => {
23
+ const files = getFilesRecursive(tmpDir, '.js').sort();
24
+ expect(files).toEqual([
25
+ path.join(tmpDir, 'a.js'),
26
+ path.join(tmpDir, 'nested', 'c.js'),
27
+ ].sort());
28
+ });
29
+
30
+ it('accepts an extension without a leading dot', () => {
31
+ const files = getFilesRecursive(tmpDir, 'js').sort();
32
+ expect(files).toEqual([
33
+ path.join(tmpDir, 'a.js'),
34
+ path.join(tmpDir, 'nested', 'c.js'),
35
+ ].sort());
36
+ });
37
+
38
+ it('accepts an array of extensions', () => {
39
+ const files = getFilesRecursive(tmpDir, ['.css', '.txt']).sort();
40
+ expect(files).toEqual([
41
+ path.join(tmpDir, 'b.css'),
42
+ path.join(tmpDir, 'nested', 'd.txt'),
43
+ ].sort());
44
+ });
45
+
46
+ it('returns an empty array when the directory does not exist', () => {
47
+ expect(getFilesRecursive(path.join(tmpDir, 'does-not-exist'), '.js')).toEqual([]);
48
+ });
49
+ });
@@ -0,0 +1,18 @@
1
+ import path from 'path';
2
+ import { getRelToCore } from './get-rel-to-core.js';
3
+
4
+ describe('getRelToCore', () => {
5
+ it('adds one "../" per path segment before core/index.js', () => {
6
+ const targetPath = ['src', 'app', 'components', 'widget'].join(path.sep);
7
+ expect(getRelToCore(targetPath)).toBe('../../../core/index.js');
8
+ });
9
+
10
+ it('adds no "../" for a single-segment path', () => {
11
+ expect(getRelToCore('widget')).toBe('core/index.js');
12
+ });
13
+
14
+ it('scales with the number of segments', () => {
15
+ const targetPath = ['a', 'b'].join(path.sep);
16
+ expect(getRelToCore(targetPath)).toBe('../core/index.js');
17
+ });
18
+ });
@@ -3,8 +3,6 @@ export * from './format.js';
3
3
  export * from './get-files-recursive.js';
4
4
  export * from './run-command.js';
5
5
  export * from './run-script.js';
6
- export * from './export-flags.js';
7
6
  export * from './prompt-boolean.js';
8
- {{#if i18n}}
9
7
  export * from './languages.js';
10
- {{/if}}
8
+ export * from './error-exit.js';
@@ -0,0 +1,15 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { errorExit } from './error-exit.js';
4
+
5
+ const langsPath = path.join(process.cwd(), 'config', 'languages.json');
6
+
7
+ let langs;
8
+ try {
9
+ langs = JSON.parse(fs.readFileSync(langsPath, 'utf-8'));
10
+ } catch (err) {
11
+ errorExit(new Error(`config/languages.json is missing or malformed: ${err.message}`), 'languages');
12
+ }
13
+
14
+ export const LANGUAGES = langs.languages;
15
+ export const DEFAULT_LANGUAGE = langs.defaultLanguage;
@@ -0,0 +1,16 @@
1
+ import { LANGUAGES, DEFAULT_LANGUAGE } from './languages.js';
2
+
3
+ describe('languages', () => {
4
+ it('reads LANGUAGES from config/languages.json', () => {
5
+ expect(Array.isArray(LANGUAGES)).toBeTruthy();
6
+ expect(LANGUAGES.length).toBeGreaterThan(0);
7
+ });
8
+
9
+ it('reads DEFAULT_LANGUAGE from config/languages.json', () => {
10
+ expect(typeof DEFAULT_LANGUAGE).toBe('string');
11
+ });
12
+
13
+ it('includes the default language among the supported languages', () => {
14
+ expect(LANGUAGES).toContain(DEFAULT_LANGUAGE);
15
+ });
16
+ });
@@ -12,20 +12,26 @@ export const chalk = {
12
12
  boldYellow: (s) => `\x1b[1;33m${s}\x1b[0m`,
13
13
  boldCyan: (s) => `\x1b[1;36m${s}\x1b[0m`,
14
14
  boldPurple: (s) => `\x1b[1;35m${s}\x1b[0m`,
15
+ boldGray: (s) => `\x1b[1;90m${s}\x1b[0m`,
15
16
  };
16
17
 
17
18
  export const print = {
18
- head: (s) => console.log(`${chalk.purple(s)}`),
19
- boldHead : (s) => console.log(`${chalk.boldPurple(s)}`),
20
- section: (s) => console.log(`${chalk.yellow(s)}`),
21
- boldSection: (s) => console.log(`${chalk.boldYellow(s)}`),
22
- info: (s) => console.log(`${chalk.green(s)}`),
23
- boldInfo: (s) => console.log(`${chalk.boldGreen(s)}`),
24
- success: (s) => console.log(`${chalk.cyan(s)}`),
25
- boldSuccess: (s) => console.log(`${chalk.boldCyan(s)}`),
26
- error: (s) => console.error(`${chalk.red(s)}`),
27
- boldError: (s) => console.error(`${chalk.boldRed(s)}`),
28
- grayError: (s) => console.error(`${chalk.gray(s)}`),
29
- blue: (s) => console.log(`${chalk.blue(s)}`),
30
- boldBlue: (s) => console.log(`${chalk.boldBlue(s)}`),
19
+ head: (s) => console.info(`${chalk.purple(s)}`),
20
+ boldHead : (s) => console.info(`${chalk.boldPurple(s)}`),
21
+ section: (s) => console.info(`${chalk.yellow(s)}`),
22
+ boldSection: (s) => console.info(`${chalk.boldYellow(s)}`),
23
+ info: (s) => console.info(`${chalk.green(s)}`),
24
+ boldInfo: (s) => console.info(`${chalk.boldGreen(s)}`),
25
+ success: (s) => console.log(`${chalk.cyan(s)}`),
26
+ boldSuccess: (s) => console.log(`${chalk.boldCyan(s)}`),
27
+ gray: (s) => console.info(`${chalk.gray(s)}`),
28
+ boldGray: (s) => console.info(`${chalk.boldGray(s)}`),
29
+ blue: (s) => console.info(`${chalk.blue(s)}`),
30
+ boldBlue: (s) => console.info(`${chalk.boldBlue(s)}`),
31
+ error: (s) => console.error(`${chalk.red(s)}`),
32
+ boldError: (s) => console.error(`${chalk.boldRed(s)}`),
33
+ grayError: (s) => console.error(`${chalk.boldGray(s)}`),
34
+ warn: (s) => console.warn(`${chalk.yellow(s)}`),
35
+ boldWarn: (s) => console.warn(`${chalk.yellow(s)}`),
36
+ debug: (s) => console.debug(`${chalk.gray(s)}`),
31
37
  }
@@ -0,0 +1,60 @@
1
+ import { print, chalk } from './print.js';
2
+
3
+ describe('print', () => {
4
+ let infoSpy;
5
+ let logSpy;
6
+ let errorSpy;
7
+ let warnSpy;
8
+ let debugSpy;
9
+
10
+ beforeEach(() => {
11
+ infoSpy = spyOn(console, 'info').andCallFake(() => {});
12
+ logSpy = spyOn(console, 'log').andCallFake(() => {});
13
+ errorSpy = spyOn(console, 'error').andCallFake(() => {});
14
+ warnSpy = spyOn(console, 'warn').andCallFake(() => {});
15
+ debugSpy = spyOn(console, 'debug').andCallFake(() => {});
16
+ });
17
+
18
+ afterEach(() => {
19
+ infoSpy.restore();
20
+ logSpy.restore();
21
+ errorSpy.restore();
22
+ warnSpy.restore();
23
+ debugSpy.restore();
24
+ });
25
+
26
+ it('info() writes to console.info, colorized green', () => {
27
+ print.info('hello');
28
+ expect(infoSpy.callCount).toBe(1);
29
+ expect(infoSpy.lastCall).toEqual([chalk.green('hello')]);
30
+ });
31
+
32
+ it('success() writes to console.log, colorized cyan', () => {
33
+ print.success('done');
34
+ expect(logSpy.callCount).toBe(1);
35
+ expect(logSpy.lastCall).toEqual([chalk.cyan('done')]);
36
+ });
37
+
38
+ it('error() writes to console.error, colorized red', () => {
39
+ print.error('oops');
40
+ expect(errorSpy.callCount).toBe(1);
41
+ expect(errorSpy.lastCall).toEqual([chalk.red('oops')]);
42
+ });
43
+
44
+ it('warn() writes to console.warn, colorized yellow', () => {
45
+ print.warn('careful');
46
+ expect(warnSpy.callCount).toBe(1);
47
+ expect(warnSpy.lastCall).toEqual([chalk.yellow('careful')]);
48
+ });
49
+
50
+ it('debug() writes to console.debug, colorized gray', () => {
51
+ print.debug('trace');
52
+ expect(debugSpy.callCount).toBe(1);
53
+ expect(debugSpy.lastCall).toEqual([chalk.gray('trace')]);
54
+ });
55
+
56
+ it('chalk wraps text in the matching ANSI escape codes', () => {
57
+ expect(chalk.red('x')).toBe('\x1b[31mx\x1b[0m');
58
+ expect(chalk.boldGreen('x')).toBe('\x1b[1;32mx\x1b[0m');
59
+ });
60
+ });
@@ -7,9 +7,16 @@ export const promptBoolean = (question) => {
7
7
  });
8
8
 
9
9
  return new Promise((resolve) => {
10
+ let answered = false;
11
+
10
12
  rl.question(`${question} (y/n): `, (answer) => {
13
+ answered = true;
11
14
  rl.close();
12
15
  resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
13
16
  });
17
+
18
+ rl.on('close', () => {
19
+ if (!answered) resolve(false);
20
+ });
14
21
  });
15
22
  };
@@ -0,0 +1,54 @@
1
+ import readline from 'readline';
2
+ import { promptBoolean } from './prompt-boolean.js';
3
+
4
+ describe('promptBoolean', () => {
5
+ let createInterfaceSpy;
6
+ let answer;
7
+
8
+ beforeEach(() => {
9
+ createInterfaceSpy = spyOn(readline, 'createInterface').andCallFake(() => ({
10
+ question: (_question, callback) => callback(answer),
11
+ close: () => {},
12
+ on: () => {},
13
+ }));
14
+ });
15
+
16
+ afterEach(() => {
17
+ createInterfaceSpy.restore();
18
+ });
19
+
20
+ it('resolves true for "y"', async () => {
21
+ answer = 'y';
22
+ expect(await promptBoolean('continue?')).toBe(true);
23
+ });
24
+
25
+ it('resolves true for "yes", case-insensitively', async () => {
26
+ answer = 'YES';
27
+ expect(await promptBoolean('continue?')).toBe(true);
28
+ });
29
+
30
+ it('resolves false for "n"', async () => {
31
+ answer = 'n';
32
+ expect(await promptBoolean('continue?')).toBe(false);
33
+ });
34
+
35
+ it('resolves false for anything else', async () => {
36
+ answer = 'maybe';
37
+ expect(await promptBoolean('continue?')).toBe(false);
38
+ });
39
+
40
+ it('resolves false instead of hanging if stdin closes before an answer', async () => {
41
+ createInterfaceSpy.restore();
42
+ let closeHandler;
43
+ createInterfaceSpy = spyOn(readline, 'createInterface').andCallFake(() => ({
44
+ question: () => {},
45
+ close: () => {},
46
+ on: (event, handler) => { if (event === 'close') closeHandler = handler; },
47
+ }));
48
+
49
+ const resultPromise = promptBoolean('continue?');
50
+ closeHandler();
51
+
52
+ expect(await resultPromise).toBe(false);
53
+ });
54
+ });
@@ -1,8 +1,10 @@
1
1
  import { spawn } from 'child_process';
2
2
 
3
- export function runCommand(command, args = []) {
3
+ export function runCommand(command, args = [], options = {}) {
4
4
  return new Promise((resolve, reject) => {
5
- const child = spawn(command, args, { stdio: 'inherit', shell: true });
5
+ const child = spawn([command, ...args].join(' '), { stdio: 'inherit', shell: true, ...options });
6
+
7
+ child.on('error', (err) => reject(err));
6
8
 
7
9
  child.on('close', (code) => {
8
10
  if (code === 0) resolve();
@@ -0,0 +1,19 @@
1
+ import { runCommand } from './run-command.js';
2
+
3
+ describe('runCommand', () => {
4
+ it('resolves when the child process exits with code 0', async () => {
5
+ const result = await runCommand('node', ['-e', 'process.exitCode=0']);
6
+ expect(result).toBe(undefined);
7
+ });
8
+
9
+ it('rejects with the exit code when the child process exits non-zero', async () => {
10
+ let error;
11
+ try {
12
+ await runCommand('node', ['-e', 'process.exitCode=7']);
13
+ } catch (err) {
14
+ error = err;
15
+ }
16
+ expect(error).toBeDefined();
17
+ expect(error.message).toContain('7');
18
+ });
19
+ });
@@ -4,17 +4,28 @@ import process from 'process';
4
4
  import { print } from './index.js';
5
5
 
6
6
  export function runScript(path, message) {
7
- print.boldSection(message);
7
+ if (!message) print.boldSection(`Running ${path}...`);
8
+ else print.boldSection(`${message}`);
8
9
 
9
- const args = [resolve(path)];
10
+ const args = [resolve(path), '--silent'];
10
11
 
11
- const result = spawnSync('node', args,
12
+ const result = spawnSync('node', args,
12
13
  {
13
14
  stdio: 'inherit',
14
15
  env: { ...process.env },
15
16
  }
16
17
  );
17
18
 
19
+ if (result.error) {
20
+ print.boldError(`\nScript ${path} could not be started: ${result.error.message}`);
21
+ process.exit(1);
22
+ }
23
+
24
+ if (result.signal) {
25
+ print.boldError(`\nScript ${path} was terminated by signal ${result.signal}.`);
26
+ process.exit(1);
27
+ }
28
+
18
29
  if (result.status !== 0) {
19
30
  print.boldError(`\nScript ${path} failed.`);
20
31
  process.exit(result.status ?? 1);
@@ -0,0 +1,40 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { runScript } from './run-script.js';
5
+
6
+ describe('runScript', () => {
7
+ let tmpDir;
8
+ let exitSpy;
9
+
10
+ beforeEach(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-run-script-'));
12
+ exitSpy = spyOn(process, 'exit').andCallFake(() => {});
13
+ });
14
+
15
+ afterEach(() => {
16
+ exitSpy.restore();
17
+ fs.rmSync(tmpDir, { recursive: true, force: true });
18
+ });
19
+
20
+ it('does not call process.exit when the script exits 0', () => {
21
+ const scriptPath = path.join(tmpDir, 'ok.js');
22
+ fs.writeFileSync(scriptPath, '// no-op, exits 0 by default\n');
23
+
24
+ // runScript prints its status message via console.info as part of its real behavior
25
+ silenceConsole('info', () => runScript(scriptPath, 'running ok script'));
26
+
27
+ expect(exitSpy.callCount).toBe(0);
28
+ });
29
+
30
+ it('calls process.exit with the script status when it exits non-zero', () => {
31
+ const scriptPath = path.join(tmpDir, 'fail.js');
32
+ fs.writeFileSync(scriptPath, 'process.exitCode = 3;\n');
33
+
34
+ // runScript prints its status message and a failure message via console.info/console.error
35
+ silenceConsole(['info', 'error'], () => runScript(scriptPath, 'running failing script'));
36
+
37
+ expect(exitSpy.callCount).toBe(1);
38
+ expect(exitSpy.lastCall).toEqual([3]);
39
+ });
40
+ });
@@ -8,13 +8,13 @@ dist-build
8
8
  *.md
9
9
  .env
10
10
  *.txt
11
- {{#if testinNutin}}
12
11
  *.test.js
13
- testin-nutin
14
- {{/if}}
15
12
  *.log
16
13
 
17
- npm-debug.log
14
+ npm-debug.log*
15
+ yarn-debug.log*
16
+ yarn-error.log*
17
+ pnpm-debug.log*
18
18
  .nyc_output
19
19
  coverage
20
20
  .DS_Store