@nutin/cli 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/CHANGELOG.md +122 -8
  2. package/README.md +21 -37
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +81 -30
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/create-app/new-app-prompt.mjs +51 -0
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
  23. package/lib/src/create-app/success-message.mjs +14 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +136 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -15
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +81 -31
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +38 -23
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +13 -14
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
  70. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  71. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  72. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  73. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  74. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  75. package/templates/base/src/core/config.ts.hbs +7 -0
  76. package/templates/base/src/core/index.ts.hbs +0 -3
  77. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  78. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  79. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  80. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  81. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  83. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  86. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  87. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  90. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  91. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  92. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
  93. package/templates/base/src/core/services/index.ts.hbs +5 -6
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  95. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  96. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  97. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
  100. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  102. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
  103. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  104. package/templates/base/src/core/services/router/router.ts.hbs +40 -9
  105. package/templates/base/src/index.html.hbs +2 -6
  106. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  107. package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
  108. package/templates/base/src/styles/main.scss.hbs +1 -18
  109. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  110. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  111. package/templates/base/tools/builder/builder.js.hbs +24 -26
  112. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  114. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  115. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  116. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  117. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  118. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  119. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  120. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  121. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  122. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  123. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  124. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  125. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  126. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  127. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  129. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  130. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  131. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  132. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  133. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  136. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  137. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  138. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  139. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  140. package/templates/base/tools/dev/serve.js.hbs +11 -1
  141. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  142. package/templates/base/tools/generator/generator.js.hbs +64 -47
  143. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  144. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  145. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  146. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  147. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  148. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  149. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  150. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  151. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  153. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  154. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  155. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  156. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  157. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  158. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  159. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  164. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  165. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  166. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  168. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  169. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  170. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  171. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  173. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  175. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  176. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  177. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  178. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  179. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  180. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  181. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  182. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  185. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  186. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  187. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  188. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  189. package/templates/base/tools/utils/format.js.hbs +10 -10
  190. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  191. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  192. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  193. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  194. package/templates/base/tools/utils/index.js.hbs +1 -3
  195. package/templates/base/tools/utils/languages.js.hbs +15 -0
  196. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  197. package/templates/base/tools/utils/print.js.hbs +19 -13
  198. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  199. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  200. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  201. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  202. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  203. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  204. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  205. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  206. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  207. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  208. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  211. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  213. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  214. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  215. package/lib/src/context-builder.mjs +0 -35
  216. package/lib/src/file-generator.mjs +0 -109
  217. package/lib/src/json-generator.mjs +0 -31
  218. package/lib/src/package-manager.mjs +0 -139
  219. package/lib/src/print.mjs +0 -31
  220. package/lib/src/prompts.mjs +0 -123
  221. package/lib/src/utils.mjs +0 -18
  222. package/lib/src/version.mjs +0 -1
  223. package/templates/base/builder.config.js.hbs +0 -35
  224. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  225. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  226. package/templates/base/src/app/dom.test.js.hbs +0 -13
  227. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  228. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  229. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  230. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  231. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  232. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -85
  233. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  234. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -108
  235. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  236. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  237. package/templates/base/src/libs/index.ts.hbs +0 -4
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -145
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
  242. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  243. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  244. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  245. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  246. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  247. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  248. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  249. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  250. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  251. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  252. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  253. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  254. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  255. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  256. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  257. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  258. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  259. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  260. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
  261. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  262. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  263. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  264. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  265. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  266. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  267. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  268. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  269. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  270. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  271. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  272. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  273. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  274. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  275. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  276. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  277. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  278. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  279. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  280. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  281. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  282. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  283. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  284. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,120 @@
1
1
  # Changelog
2
2
 
3
- ## V1.3.0
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
84
+
85
+ - Minor features:
86
+ - New BaseComponent protected method.
87
+ Register events that will trigger re-render. Call this in component's constructor.
88
+ ```ts
89
+ // force = true: calls forceRender() instead of render()
90
+ listenToRenderEvents(events: EventKey[], force: boolean = false): void
91
+ ```
92
+ - data-optional now supports JS values
93
+ ```html
94
+ data-optional="${myValue}"
95
+ ```
96
+ - PopoverView and AnchorComponents now have accessibility features
97
+ - ButtonManager now handles checkboxes as well
98
+ - i18nService now emits `language-changed` event and exposes related methods
99
+
100
+ - Fixes:
101
+ - CatalogConfig no longer renders multiple containers when render() or forceRender() are triggered by events
102
+ - Event Listeners are now destroyed properly before triggering a re-render
103
+ - Router now correctly redirects to 404 page when URL starts with two slashes `//`.
104
+ - Popover no longer flickers on close
105
+ - Snackbar messages are now properly sanitized
106
+
107
+ - CLI
108
+ - Rework: App creation flow
109
+ - No features enabled by default
110
+ - Presets `--preset <minimal|standard|full|cicd>`
111
+ Minimal: External templates
112
+ Standard: Minimal + i18n & built-in SCSS utilities.
113
+ Full: Standard + deployment helpers & built-in testing toolkit
114
+ CI/CD: Minimal + deployment helpers
115
+ - Remaining flags: `--i18n`, `--deploy-helper`, `--testin-nutin`, `--transition`
116
+
117
+ ## 1.3.0
4
118
 
5
119
  Version 1.3.0 marks a stability milestone with various improvements and refinements, making this the recommended version for new projects.
6
120
 
@@ -35,11 +149,11 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
35
149
  - TestinNutin
36
150
  - Now applies `setupJsdom()` beforeAll (was beforeEach) and `teardownJsdom()` afterAll (was afterEach) in `test-queue.js` (improved efficiency / speed)
37
151
 
38
- ## V1.2.3
152
+ ## 1.2.3
39
153
 
40
154
  - `nginx.conf` : fixed multi-line CSP map warning (single line map)
41
155
 
42
- ## V1.2.2
156
+ ## 1.2.2
43
157
 
44
158
  - Fixed Nginx security headers in child location blocks.
45
159
 
@@ -47,7 +161,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
47
161
 
48
162
  - Removed ghost files
49
163
 
50
- ## V1.2.1
164
+ ## 1.2.1
51
165
 
52
166
  - Builder
53
167
 
@@ -75,7 +189,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
75
189
 
76
190
  - Added explicit chokidar devDependency (sass & live-server transitive).
77
191
 
78
- ## V1.2.0
192
+ ## 1.2.0
79
193
 
80
194
  - Fixed `npm run dev` command. Added middleware to reload nested routes.
81
195
 
@@ -106,7 +220,7 @@ Version 1.3.0 marks a stability milestone with various improvements and refineme
106
220
  - stylin-nutin (generator)
107
221
  - When generating a component, prompts (boolean) to generate a `_component-name.scss` file in `styles/components` (forwarded by `styles/components/_index.scss`).
108
222
 
109
- ## V1.1.0
223
+ ## 1.1.0
110
224
 
111
225
  - Added index access in CatalogConfig. Use with `config.index`.
112
226
  ```typescript
@@ -125,10 +239,10 @@ type CatalogItemConfig<T = any> =
125
239
 
126
240
  **Notes:** *Primitive data arrays (string, number, etc) needs to be accessed with `config.value`.*
127
241
 
128
- ## V1.0.2
242
+ ## 1.0.2
129
243
 
130
244
  - Partially fixed `npm run dev` script when using internal templates. *Page still needs to be reload manually from time to time.*
131
245
 
132
- ## V1.0.1
246
+ ## 1.0.1
133
247
 
134
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,45 +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
41
-
42
- # Features
43
- --template # (Default) Use external templates
44
- --no-template # Use inline templates
45
- --stylin-nutin # (Default) Use built-in SCSS utility classes & mixins
46
- --no-stylin-nutin # Do not use built-in SCSS utility
47
- --testin-nutin # Use testin-nutin toolkit
48
- --no-testin-nutin # (Default) Do not use testin-nutin toolkit
49
- --i18n # Use i18n & json-based content
50
- --no-i18n # (Default) Do not use i18n & json-based content
51
- --deploy-helper # Use Docker & deployment helpers
52
- --no-deploy-helper, # (Default) Do not use Docker & deployment helpers
53
- --transition # Use animated view transitions. Note: May interfere with CSS `position: fixed`, `z-index`...
54
- --no-transition # (Default) Do not use animated view transitions
31
+ # update Nutin while preserving your changes
32
+ nutin-update
55
33
  ```
56
34
 
57
- ## [First app tutorial](https://www.nutin.org/tutorial)
35
+ ## Add Docker
36
+
37
+ ```bash
38
+ # ready-to-use Dockerfile for Nutin apps
39
+ nutin-add docker
40
+ ```
58
41
 
59
- ## 📄 Docs
42
+ ## Useful links
60
43
 
61
- - Docs on [website](https://www.nutin.org).
62
- - 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,41 +1,92 @@
1
- import { program } from 'commander';
2
- import { print } 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)
14
+ .configureOutput({
15
+ helpWidth: 100
16
+ })
11
17
  .argument('[projectName]', 'Name of the project')
12
- .option('-d, --defaults', 'Use default options')
13
18
  .option('-pm, --package-manager <manager>', 'Specify package manager (npm, yarn, pnpm, bun)')
14
- .option('--template', '(Default) Use external templates')
15
- .option('--no-template', 'Do not use external templates')
16
- .option('--stylin-nutin', '(Default) Use built-in SCSS utility classes & mixins')
17
- .option('--no-stylin-nutin', 'Do not use built-in SCSS utility')
18
- .option('--i18n', 'Use i18n & json-based content')
19
- .option('--no-i18n', '(Default) Do not use i18n & json-based content')
20
- .option('--deploy-helper', 'Use Docker & deployment helpers')
21
- .option('--no-deploy-helper', '(Default) Do not use Docker & deployment helpers')
22
- .option('--testin-nutin', 'Use testin-nutin toolkit')
23
- .option('--no-testin-nutin', '(Default) Do not use testin-nutin toolkit')
24
- .option('--transition', 'Use animated view transitions\nNote: May interfere with CSS `position: fixed`, `z-index`...')
25
- .option('--no-transition', '(Default) Do not use animated view transitions')
26
- .addHelpText('afterAll', '\nChaining options :\nExample: -d -pm npm --i18n --testinNutin')
27
19
  .action(async (projectName, cliOptions) => {
28
- print.blue('🚀 Welcome to nutin !');
20
+ print.boldSuccess('\n🚀 Welcome to your new nutin app!\n');
21
+ try {
22
+ const preferences = await newAppPrompt(projectName, cliOptions);
23
+ await createProject(preferences);
24
+ displaySuccessMessage(preferences);
25
+ } catch (error) {
26
+ print.boldError(`❌ Error creating project: ${error.stack}`);
27
+ process.exit(1);
28
+ }
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
+ }
29
46
 
30
47
  try {
31
- const answers = await promptUser(projectName, cliOptions);
32
- await createProject(answers);
33
- displaySuccessMessage(answers);
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
+ }
34
56
  } catch (error) {
35
- print.boldError(`❌ Error creating project: ${error.message}`);
57
+ print.boldError(`❌ Error adding feature: ${error.stack}`);
36
58
  process.exit(1);
37
59
  }
38
60
  });
61
+ }
62
+
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
+ });
81
+ }
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'));
39
90
 
40
- program.parse();
91
+ return program;
41
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
+ }