@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
package/CHANGELOG.md CHANGED
@@ -1,6 +1,86 @@
1
1
  # Changelog
2
2
 
3
- ## V1.3.1
3
+ ## 2.0.0
4
+
5
+ Version 2.0.0 focuses on simplicity and clear APIs over feature accumulation: fewer abstractions, clearer responsibilities, and a more focused API.
6
+
7
+ ### Breaking Changes
8
+
9
+ - Renamed `BaseComponent`'s `childConfigs()` to `registerChildren()` and `catalogConfig` to `createCatalogComponents()`.
10
+ - Raised the required Node.js version from `>=18` to `>=22`.
11
+ - Removed all libraries except `Pipes`, as they did not fit Nutin's philosophy.
12
+ - Removed `Store`. Its responsibilities were unclear and largely duplicated what services already provide.
13
+ - Removed the `force` parameter from `listenToRenderEvents()`. It is no longer needed.
14
+
15
+ ### Architecture & API
16
+
17
+ - Clarified and separated the responsibilities of the base classes:
18
+ - **`BaseComponent`**: render lifecycle, hydration, DOM lifecycle, DOM and EventBus subscriptions, teardown, render guard, and composition orchestration.
19
+ - **`Component`**: props (`className`, `style`, `data-bindings`), configuration/defaults/normalization, and template generation through `templateFn`.
20
+ - **`View`**: route parameters, navigation hooks (`onEnter()` / `onExit()`), and view identity (`viewName`).
21
+
22
+ - Components and Views now inherit `listen()` and `listenToRenderEvents()` methods with automatic unsubscription.
23
+ - Added explicit lifecycle hooks:
24
+ - **Component:** `onBeforeRender()`, `onAfterRender()`, `onBeforeDestroy()`, `onAfterDestroy()`
25
+ - **View:** `onEnter()`, `onExit()`
26
+
27
+ - Simplified the EventBus API:
28
+ - Added `AppEventMap` for app-level events. Nutin's internal events are no longer declared in `globals.d.ts`.
29
+ - Event payload types are now objects.
30
+ - EventBus now exposes domain facades for `Navigation` and `Lifecycle` events.
31
+ - Event subscription functions (`on*`) now return a closure for unsubscription.
32
+ - `AppEventBus` can still be used directly when needed (`once()`, etc.), but subscriptions must then be unsubscribed manually through the `onBeforeDestroy()` hook.
33
+ ```ts
34
+ // emit
35
+ Navigation.navigateTo('path');
36
+ // closure
37
+ const unsub = Navigation.onNavigate(this.doStuff);
38
+ // onDestroy
39
+ unsub()
40
+ ```
41
+
42
+ ### Services
43
+
44
+ - Services now expose a single `getInstance()` method.
45
+ - Arguments can be passed to `getInstance()`, but are only used during the first instantiation.
46
+
47
+ ### Configuration & Build
48
+
49
+ - Added `nutin.config.js` as the central configuration file for Nutin options, builder, generator, and testing.
50
+ - Added optional Tailwind CSS v4 support as a utility layer alongside SASS.
51
+ - Added globally scoped stylesheets co-located with feature files. **Use unique class names.** Nutin's naming convention encourages prefixes such as `home__header`.
52
+ - HTML templates can now be either inline or external `.html` files. Defining both, or neither, fails the build.
53
+ - Added configurable Docker ports through `nutin.config.js`.
54
+ - Dockerfile now builds a Brotli-enabled image.
55
+
56
+ ### SEO & i18n
57
+
58
+ - Added optional generation of static SEO files from `config/seo.json`.
59
+ - Production builds generate static HTML for each route using the actual components.
60
+ - Only `title`, `description`, and `ogImage` remain hand-authored SEO content.
61
+ - Generates `robots.txt` and `sitemap.xml`.
62
+ - Supports per-route `disallow` and `disallowBots`.
63
+
64
+ - i18n now uses the URL, aligning with common practices and allowing static SEO HTML to be generated for each language.
65
+
66
+ ### CLI & Developer Experience
67
+
68
+ - Added `nutin-update`, which updates Nutin to the latest patch or minor version by diffing the project against the new templates and merging compatible changes. Unresolved conflicts are reported.
69
+ - Added `nutin-add docker`.
70
+ - Added `GETTING_STARTED.md`, providing the architectural context and conventions needed to start working with a Nutin project without immediately consulting the full documentation.
71
+ - Added `AGENTS.md`, providing LLMs with the minimal context required to work effectively in a Nutin project.
72
+
73
+ ### TestinNutin
74
+
75
+ - Added code coverage summaries for branches, functions, and lines.
76
+ - Added `it.todo`.
77
+ - Added clock mocking for `setTimeout` and `setInterval`.
78
+
79
+ ### Bug Fixes
80
+
81
+ - Numerous bug fixes and stability improvements.
82
+
83
+ ## 1.3.1
4
84
 
5
85
  - Minor features:
6
86
  - New BaseComponent protected method.
@@ -34,7 +114,7 @@
34
114
  CI/CD: Minimal + deployment helpers
35
115
  - Remaining flags: `--i18n`, `--deploy-helper`, `--testin-nutin`, `--transition`
36
116
 
37
- ## V1.3.0
117
+ ## 1.3.0
38
118
 
39
119
  Version 1.3.0 marks a stability milestone with various improvements and refinements, making this the recommended version for new projects.
40
120
 
@@ -69,11 +149,11 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
69
149
  - TestinNutin
70
150
  - Now applies `setupJsdom()` beforeAll (was beforeEach) and `teardownJsdom()` afterAll (was afterEach) in `test-queue.js` (improved efficiency / speed)
71
151
 
72
- ## V1.2.3
152
+ ## 1.2.3
73
153
 
74
154
  - `nginx.conf` : fixed multi-line CSP map warning (single line map)
75
155
 
76
- ## V1.2.2
156
+ ## 1.2.2
77
157
 
78
158
  - Fixed Nginx security headers in child location blocks.
79
159
 
@@ -81,7 +161,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
81
161
 
82
162
  - Removed ghost files
83
163
 
84
- ## V1.2.1
164
+ ## 1.2.1
85
165
 
86
166
  - Builder
87
167
 
@@ -109,7 +189,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
109
189
 
110
190
  - Added explicit chokidar devDependency (sass & live-server transitive).
111
191
 
112
- ## V1.2.0
192
+ ## 1.2.0
113
193
 
114
194
  - Fixed `npm run dev` command. Added middleware to reload nested routes.
115
195
 
@@ -140,7 +220,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
140
220
  - stylin-nutin (generator)
141
221
  - When generating a component, prompts (boolean) to generate a `_component-name.scss` file in `styles/components` (forwarded by `styles/components/_index.scss`).
142
222
 
143
- ## V1.1.0
223
+ ## 1.1.0
144
224
 
145
225
  - Added index access in CatalogConfig. Use with `config.index`.
146
226
  ```typescript
@@ -159,10 +239,10 @@ type CatalogItemConfig<T = any> =
159
239
 
160
240
  **Notes:** *Primitive data arrays (string, number, etc) needs to be accessed with `config.value`.*
161
241
 
162
- ## V1.0.2
242
+ ## 1.0.2
163
243
 
164
244
  - Partially fixed `npm run dev` script when using internal templates. *Page still needs to be reload manually from time to time.*
165
245
 
166
- ## V1.0.1
246
+ ## 1.0.1
167
247
 
168
248
  - Fixed typo issue when using i18n feature
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
- # [nutin](https://www.nutin.org)
1
+ # [Nutin](https://www.nutin.org)
2
2
 
3
- **Build SPAs by understanding SPAs. A minimal frontend toolkit crafted for learning and lean applications.**
4
- Includes essential features out of the box.
5
- *Pure TypeScript, SASS, and DOM-driven components — zero runtime dependencies.*
3
+ `The structure of a framework. The freedom of vanilla.`
6
4
 
7
- - Required : Node >=18
8
- - **JSDOM requirement** (testin-nutin companion) : `{ node: '^20.19.0 || ^22.12.0 || >=24.0.0' }`
5
+ Nutin provides the structure and tooling you'd expect from a framework, while keeping the underlying web platform visible and giving you code ownership.
6
+
7
+ **Your app owns Nutin — not the other way around.** The source code lives alongside your application, so you can read it, modify it, and make it yours. Nutin's update system is designed to preserve those changes.
8
+
9
+ Nutin is deliberately pragmatic, lightweight, and dependency-free on runtime.
9
10
 
10
11
  ## Install
11
12
 
@@ -18,44 +19,28 @@ npm install -g @nutin/cli
18
19
 
19
20
  ```bash
20
21
  # create a new app
21
- create-nutin-app # or
22
- nutin-new
22
+ nutin-new # or create-nutin-app
23
23
 
24
24
  # without global installation
25
25
  npx @nutin/cli
26
26
  ```
27
27
 
28
- - Flags
28
+ ## Update Nutin
29
29
 
30
30
  ```bash
31
- # argument: project-name (prompt if not provided)
32
-
33
- # Help
34
- -h --help
35
-
36
- # Defaults settings - see below
37
- -d --defaults
38
-
39
- # Package manager
40
- -pm --package-manager <manager> # npm (Default), yarn, pnpm, bun
31
+ # update Nutin while preserving your changes
32
+ nutin-update
33
+ ```
41
34
 
42
- # Presets
43
- --preset <minimal|standard|full|cicd>
44
- # minimal: external templates
45
- # standard: minimal + i18n & built-in SCSS classes
46
- # full: standard + deployment helpers & built-in testing toolkit
47
- # cicd: minimal + deployment helpers
35
+ ## Add Docker
48
36
 
49
- # Features
50
- --i18n # Use i18n & json-based content
51
- --deploy-helper # Use Docker & deployment helpers
52
- --testin-nutin # Use testin-nutin toolkit
53
- --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
37
+ ```bash
38
+ # ready-to-use Dockerfile for Nutin apps
39
+ nutin-add docker
54
40
  ```
55
41
 
56
- ## [First app tutorial](https://www.nutin.org/tutorial)
57
-
58
- ## 📄 Docs
42
+ ## Useful links
59
43
 
60
- - Docs on [website](https://www.nutin.org).
61
- - Docs on [repository](https://github.com/LoisKOUNINEF/nutin/tree/main/docs).
44
+ - [Nutin website](https://www.nutin.org)
45
+ - [First app tutorial](https://www.nutin.org/tutorial)
46
+ - [Repository documentation](https://github.com/LoisKOUNINEF/nutin/tree/main/docs)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { addFeatureCommand } from '../lib/index.mjs';
5
+
6
+ addFeatureCommand(new Command()).parseAsync(process.argv).catch((err) => {
7
+ console.error(err instanceof Error ? err.stack : err);
8
+ process.exitCode = 1;
9
+ });
package/bin/cli.mjs ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { buildCli } from '../lib/index.mjs';
4
+
5
+ buildCli().parseAsync(process.argv).catch((err) => {
6
+ console.error(err instanceof Error ? err.stack : err);
7
+ process.exitCode = 1;
8
+ });
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createApp } from '../lib/index.mjs';
3
+ import { Command } from 'commander';
4
+ import { createAppCommand } from '../lib/index.mjs';
4
5
 
5
- createApp().catch((error) => {
6
- console.error('Error:', error.message);
7
- process.exit(1);
6
+ createAppCommand(new Command()).parseAsync(process.argv).catch((err) => {
7
+ console.error(err instanceof Error ? err.stack : err);
8
+ process.exitCode = 1;
8
9
  });
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { updateAppCommand } from '../lib/index.mjs';
5
+
6
+ updateAppCommand(new Command()).parseAsync(process.argv).catch((err) => {
7
+ console.error(err instanceof Error ? err.stack : err);
8
+ process.exitCode = 1;
9
+ });
package/lib/index.mjs CHANGED
@@ -1,33 +1,25 @@
1
- import { program } from 'commander';
2
- import { print, chalk } from './src/print.mjs';
3
- import { promptUser } from './src/prompts.mjs';
4
- import { createProject } from './src/project-generator.mjs';
5
- import { displaySuccessMessage } from './src/utils.mjs';
6
- import { packageVersion } from './src/version.mjs';
7
-
8
- export async function createApp() {
9
- program
10
- .version(packageVersion)
1
+ import { Command } from 'commander';
2
+ import { print, chalk } from './src/utils/print.mjs';
3
+ import { newAppPrompt } from './src/create-app/new-app-prompt.mjs';
4
+ import { createProject } from './src/create-app/project-generator.mjs';
5
+ import { displaySuccessMessage } from './src/create-app/success-message.mjs';
6
+ import { PACKAGE_VERSION } from './src/common/package-data.mjs';
7
+ import { FEATURES, findFeatureByCli } from './src/common/feature-registry.mjs';
8
+ import { addFeatureToProject } from './src/add-feature/feature-adder.mjs';
9
+ import { updateProject } from './src/update-app/project-updater.mjs';
10
+
11
+ export function createAppCommand(command) {
12
+ return command
13
+ .version(PACKAGE_VERSION)
11
14
  .configureOutput({
12
- helpWidth: 100
15
+ helpWidth: 100
13
16
  })
14
17
  .argument('[projectName]', 'Name of the project')
15
18
  .option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm, bun)')
16
- .option('--preset <preset>',
17
- `${chalk.yellow('Project preset configuration:')}
18
- ${chalk.cyan('• minimal')}${chalk.boldGray(' - external templates')}
19
- ${chalk.cyan('• standard')}${chalk.boldGray(' - minimal + i18n & built-in SCSS utilities')}
20
- ${chalk.cyan('• full')}${chalk.boldGray(' - standard + deployment helpers & built-in testing toolkit')}
21
- ${chalk.cyan('• cicd')}${chalk.boldGray(' - minimal + deployment helpers')}`
22
- )
23
- .option('--i18n', 'Use i18n & json-based content')
24
- .option('--deploy-helper', 'Use Docker & deployment helpers')
25
- .option('--testin-nutin', 'Use built-in testing toolkit')
26
- .option('--transition', 'Use animated view transitions\nNote: may interfere with CSS `position: fixed`, `z-index`…')
27
19
  .action(async (projectName, cliOptions) => {
28
- print.blue('🚀 Welcome to nutin !');
20
+ print.boldSuccess('\n🚀 Welcome to your new nutin app!\n');
29
21
  try {
30
- const preferences = await promptUser(projectName, cliOptions);
22
+ const preferences = await newAppPrompt(projectName, cliOptions);
31
23
  await createProject(preferences);
32
24
  displaySuccessMessage(preferences);
33
25
  } catch (error) {
@@ -35,7 +27,66 @@ export async function createApp() {
35
27
  process.exit(1);
36
28
  }
37
29
  });
30
+ }
31
+
32
+ export function addFeatureCommand(command) {
33
+ const featureChoices = [...FEATURES.map((feature) => feature.cli), 'all'];
34
+
35
+ return command
36
+ .version(PACKAGE_VERSION)
37
+ .configureOutput({
38
+ helpWidth: 100
39
+ })
40
+ .argument('<feature>', `Feature to add to the current project: ${featureChoices.join(', ')}`)
41
+ .action(async (featureArg) => {
42
+ if (!featureChoices.includes(featureArg)) {
43
+ print.boldError(`❌ Unknown feature "${featureArg}". Choose one of: ${featureChoices.join(', ')}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ try {
48
+ if (featureArg === 'all') {
49
+ for (const feature of FEATURES) {
50
+ await addFeatureToProject(feature.key);
51
+ }
52
+ } else {
53
+ const feature = findFeatureByCli(featureArg);
54
+ await addFeatureToProject(feature.key);
55
+ }
56
+ } catch (error) {
57
+ print.boldError(`❌ Error adding feature: ${error.stack}`);
58
+ process.exit(1);
59
+ }
60
+ });
61
+ }
38
62
 
39
- program.parse();
63
+ export function updateAppCommand(command) {
64
+ return command
65
+ .version(PACKAGE_VERSION)
66
+ .configureOutput({
67
+ helpWidth: 100
68
+ })
69
+ .option('-y, --yes', 'Skip the confirmation prompt and apply safe updates immediately')
70
+ .option('--from <path>', 'Use a local templates/ directory as the old baseline instead of fetching from npm')
71
+ .action(async (cliOptions) => {
72
+ print.boldSuccess('🚀 nutin — update');
73
+
74
+ try {
75
+ await updateProject(process.cwd(), cliOptions);
76
+ } catch (error) {
77
+ print.boldError(`❌ Error updating project: ${error.stack}`);
78
+ process.exit(1);
79
+ }
80
+ });
40
81
  }
41
82
 
83
+ export function buildCli() {
84
+ const program = new Command();
85
+ program.name('nutin').version(PACKAGE_VERSION);
86
+
87
+ createAppCommand(program.command('new', { isDefault: true }).description('Create a new nutin app'));
88
+ addFeatureCommand(program.command('add').description('Add a feature to the current project'));
89
+ updateAppCommand(program.command('update').description('Update nutin while preserving your changes'));
90
+
91
+ return program;
92
+ }
@@ -0,0 +1,83 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import * as fsExtra from 'fs-extra';
4
+ import { print } from '../utils/print.mjs';
5
+ import { FileGenerator } from '../common/file-generator.mjs';
6
+ import { FEATURES } from '../common/feature-registry.mjs';
7
+ import { PACKAGE_VERSION } from '../common/package-data.mjs';
8
+ import { installDependencies } from '../common/package-json-helper.mjs';
9
+ import { readProjectMeta, updateProjectMeta } from '../common/project-meta.mjs';
10
+ import { FeatureContextBuilder } from './feature-context-builder.mjs';
11
+ import { updatePackageJson } from './package-json-updater.mjs';
12
+ import { updateNutinConfig } from './nutin-config-updater.mjs';
13
+
14
+ const fs = fsExtra.default;
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ export class FeatureAdder {
19
+ constructor() {
20
+ this.fileGenerator = new FileGenerator();
21
+ this.builder = new FeatureContextBuilder();
22
+ }
23
+
24
+ async addFeatureToProject(featureKey) {
25
+ const feature = FEATURES.find((candidate) => candidate.key === featureKey);
26
+ if (!feature) {
27
+ throw new Error(`Unknown feature: ${featureKey}`);
28
+ }
29
+
30
+ const projectPath = process.cwd();
31
+ await this.validateProject(projectPath, feature);
32
+
33
+ const meta = await readProjectMeta(projectPath);
34
+ if (meta?.features?.[feature.key]) {
35
+ print.gray(`${feature.key} is already enabled in this project — skipping.`);
36
+ return;
37
+ }
38
+
39
+ print.boldInfo(`\nAdding ${feature.key} to ${projectPath}...`);
40
+
41
+ const context = await this.builder.buildContext(projectPath, feature);
42
+
43
+ await this.applyFeatureTemplate(projectPath, feature, context);
44
+ await this.runPostAddTasks(projectPath, feature, context);
45
+
46
+ print.boldInfo(`${feature.key} added.`);
47
+ }
48
+
49
+ async validateProject(projectPath, feature) {
50
+ const packageJsonPath = path.join(projectPath, 'package.json');
51
+ if (!(await fs.pathExists(packageJsonPath))) {
52
+ throw new Error(`No package.json found in ${projectPath} — run this from a nutin project's root.`);
53
+ }
54
+ if (!(await fs.pathExists(path.join(projectPath, 'nutin.config.js')))) {
55
+ throw new Error(`No nutin.config.js file found in ${projectPath} — is this a nutin project?`);
56
+ }
57
+ }
58
+
59
+ async applyFeatureTemplate(projectPath, feature, context) {
60
+ const featureTemplateDir = path.join(__dirname, '..', '..', '..', 'templates', 'features', feature.key);
61
+
62
+ await this.fileGenerator.processTemplateDirectory(featureTemplateDir, projectPath, context, { skipExisting: true });
63
+
64
+ await updatePackageJson(projectPath, feature);
65
+ await updateNutinConfig(projectPath, feature);
66
+ }
67
+
68
+ async runPostAddTasks(projectPath, feature, context) {
69
+ await updateProjectMeta(projectPath, {
70
+ version: PACKAGE_VERSION,
71
+ packageManager: context.packageManager,
72
+ features: { [feature.key]: true },
73
+ });
74
+ if (feature.key === 'docker') return;
75
+ await installDependencies(projectPath, context.packageManager);
76
+ }
77
+ }
78
+
79
+ export const featureAdder = new FeatureAdder();
80
+
81
+ export async function addFeatureToProject(featureKey) {
82
+ return featureAdder.addFeatureToProject(featureKey);
83
+ }
@@ -0,0 +1,25 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { getCiCommand, detectPackageManager } from '../common/package-json-helper.mjs';
4
+ import { PACKAGE_VERSION } from '../common/package-data.mjs';
5
+ import { readProjectMeta } from '../common/project-meta.mjs';
6
+ import { bootstrapProjectMeta } from '../common/meta-bootstrap-prompt.mjs';
7
+
8
+ const fs = fsExtra.default;
9
+
10
+ export class FeatureContextBuilder {
11
+ async buildContext(projectPath, feature) {
12
+ const packageJson = await fs.readJSON(path.join(projectPath, 'package.json'));
13
+ const packageManager = await detectPackageManager(projectPath);
14
+ const meta = (await readProjectMeta(projectPath)) ?? (await bootstrapProjectMeta(projectPath));
15
+
16
+ return {
17
+ projectName: packageJson.name,
18
+ packageManager,
19
+ ciCommand: getCiCommand(packageManager),
20
+ version: PACKAGE_VERSION,
21
+ ...(meta.features ?? {}),
22
+ [feature.key]: true,
23
+ };
24
+ }
25
+ }
@@ -0,0 +1,29 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { print } from '../utils/print.mjs';
4
+
5
+ const fs = fsExtra.default;
6
+
7
+ const GENERATE_SEO_LINE_RE = /^([ \t]*generateSEOFiles:[^\n]*\n)/m;
8
+ const DOCKER_PORTS_LINE = '\n// Nutin features\n dockerPorts: [9090], // Ports the Docker container exposes/listens on — edit as needed\n';
9
+
10
+ export async function updateNutinConfig(projectPath, feature) {
11
+ if (feature.key !== 'docker') return;
12
+
13
+ const configPath = path.join(projectPath, 'nutin.config.js');
14
+ const content = await fs.readFile(configPath, 'utf-8');
15
+
16
+ if (/dockerPorts\s*:/.test(content)) {
17
+ print.info('Kept existing "dockerPorts" value in nutin.config.js');
18
+ return;
19
+ }
20
+
21
+ if (!GENERATE_SEO_LINE_RE.test(content)) {
22
+ print.warn('Could not find a "generateSEOFiles:" line in nutin.config.js — add "dockerPorts: [9090]" to it manually before building.');
23
+ return;
24
+ }
25
+
26
+ const updated = content.replace(GENERATE_SEO_LINE_RE, `$1${DOCKER_PORTS_LINE}`);
27
+ await fs.writeFile(configPath, updated);
28
+ print.warn('Added "dockerPorts" to nutin.config.js — edit it if you need different port(s).');
29
+ }
@@ -0,0 +1,49 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { print } from '../utils/print.mjs';
4
+ import { getDockerScripts } from '../common/package-json-helper.mjs';
5
+
6
+ const fs = fsExtra.default;
7
+
8
+ function mergeAdditive(target, additions) {
9
+ const merged = { ...target };
10
+ const added = [];
11
+ const skipped = [];
12
+
13
+ for (const [key, value] of Object.entries(additions)) {
14
+ if (Object.prototype.hasOwnProperty.call(merged, key)) {
15
+ skipped.push(key);
16
+ } else {
17
+ merged[key] = value;
18
+ added.push(key);
19
+ }
20
+ }
21
+
22
+ return { merged, added, skipped };
23
+ }
24
+
25
+ export async function updatePackageJson(projectPath, feature) {
26
+ const packageJsonPath = path.join(projectPath, 'package.json');
27
+ const packageJson = await fs.readJSON(packageJsonPath);
28
+
29
+ const isDocker = feature.key === 'docker';
30
+
31
+ let scripts = {};
32
+
33
+ if (isDocker) {
34
+ scripts = getDockerScripts();
35
+ } else {
36
+ return;
37
+ }
38
+
39
+ const scriptsResult = mergeAdditive(packageJson.scripts ?? {}, scripts);
40
+ packageJson.scripts = scriptsResult.merged;
41
+
42
+ await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
43
+
44
+ const skipped = scriptsResult.skipped;
45
+ if (skipped.length > 0) {
46
+ print.info(`Kept existing package.json values for: ${skipped.join(', ')}`);
47
+ }
48
+ print.info('Updated package.json');
49
+ }
@@ -0,0 +1,11 @@
1
+ // Single source of truth for the optional, additive features under templates/features
2
+ export const FEATURES = [
3
+ {
4
+ key: 'docker',
5
+ cli: 'docker',
6
+ },
7
+ ];
8
+
9
+ export function findFeatureByCli(cliName) {
10
+ return FEATURES.find((feature) => feature.cli === cliName);
11
+ }