@nutin/cli 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/CHANGELOG.md +89 -9
  2. package/README.md +20 -35
  3. package/bin/add-feature.mjs +9 -0
  4. package/bin/cli.mjs +8 -0
  5. package/bin/create-app.mjs +5 -4
  6. package/bin/update-app.mjs +9 -0
  7. package/lib/index.mjs +76 -25
  8. package/lib/src/add-feature/feature-adder.mjs +83 -0
  9. package/lib/src/add-feature/feature-context-builder.mjs +25 -0
  10. package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
  11. package/lib/src/add-feature/package-json-updater.mjs +49 -0
  12. package/lib/src/common/feature-registry.mjs +11 -0
  13. package/lib/src/common/file-generator.mjs +159 -0
  14. package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
  15. package/lib/src/common/package-data.mjs +10 -0
  16. package/lib/src/common/package-json-helper.mjs +103 -0
  17. package/lib/src/common/project-meta.mjs +54 -0
  18. package/lib/src/create-app/context-builder.mjs +21 -0
  19. package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
  20. package/lib/src/create-app/json-manager.mjs +73 -0
  21. package/lib/src/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
  22. package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
  23. package/lib/src/create-app/success-message.mjs +14 -0
  24. package/lib/src/update-app/conflict-report-writer.mjs +31 -0
  25. package/lib/src/update-app/fetch-old-templates.mjs +61 -0
  26. package/lib/src/update-app/project-updater.mjs +136 -0
  27. package/lib/src/update-app/template-differ.mjs +124 -0
  28. package/lib/src/update-app/update-context-builder.mjs +25 -0
  29. package/lib/src/update-app/update-summary-printer.mjs +22 -0
  30. package/lib/src/update-app/version-compare.mjs +12 -0
  31. package/lib/src/utils/print.mjs +36 -0
  32. package/lib/src/utils/promise-exec-alias.mjs +4 -0
  33. package/package.json +17 -12
  34. package/templates/base/.gitignore.hbs +6 -1
  35. package/templates/base/AGENTS.md.hbs +169 -0
  36. package/templates/base/GETTING_STARTED.md.hbs +227 -0
  37. package/templates/base/README.md.hbs +8 -17
  38. package/templates/base/config/languages.json.hbs +6 -0
  39. package/templates/base/config/seo.json.hbs +12 -0
  40. package/templates/base/nutin.config.js.hbs +51 -0
  41. package/templates/base/src/app/components/index.ts.hbs +1 -2
  42. package/templates/base/src/app/globals.d.ts.hbs +3 -19
  43. package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
  44. package/templates/base/src/app/main.ts.hbs +7 -26
  45. package/templates/base/src/app/services/index.ts.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
  47. package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
  48. package/templates/base/src/app/views/index.ts.hbs +1 -0
  49. package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
  50. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
  51. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
  52. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +68 -35
  53. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
  54. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +9 -8
  55. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
  56. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
  57. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
  58. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +7 -1
  59. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +2 -2
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
  63. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
  64. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
  65. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
  66. package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
  67. package/templates/base/src/core/base-classes/component/component.ts.hbs +24 -33
  68. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
  69. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
  70. package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
  71. package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
  72. package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
  73. package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
  74. package/templates/base/src/core/config.ts.hbs +7 -0
  75. package/templates/base/src/core/index.ts.hbs +0 -3
  76. package/templates/base/src/core/internals.d.ts.hbs +27 -0
  77. package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
  78. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
  79. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
  80. package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
  81. package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
  82. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
  83. package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
  84. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
  85. package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
  86. package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
  87. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
  88. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
  89. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
  90. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
  91. package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +22 -4
  92. package/templates/base/src/core/services/index.ts.hbs +5 -6
  93. package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
  94. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
  95. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
  96. package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
  97. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
  98. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +40 -29
  99. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
  100. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
  101. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +14 -38
  102. package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
  103. package/templates/base/src/core/services/router/router.ts.hbs +40 -8
  104. package/templates/base/src/index.html.hbs +2 -6
  105. package/templates/base/src/styles/_styles.scss.hbs +102 -43
  106. package/templates/base/src/styles/base/_variables.scss.hbs +1 -86
  107. package/templates/base/src/styles/main.scss.hbs +1 -18
  108. package/templates/base/src/styles/tailwind.css.hbs +2 -0
  109. package/templates/base/tools/builder/builder.config.js.hbs +40 -0
  110. package/templates/base/tools/builder/builder.js.hbs +24 -26
  111. package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
  112. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
  113. package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
  114. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
  115. package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
  116. package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
  117. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
  118. package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
  119. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
  120. package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
  121. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
  122. package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
  123. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
  124. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
  125. package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
  126. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
  127. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
  128. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
  129. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
  130. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
  131. package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
  132. package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
  133. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
  134. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
  135. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
  136. package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
  137. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
  138. package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
  139. package/templates/base/tools/dev/serve.js.hbs +11 -1
  140. package/templates/base/tools/dev/watcher.js.hbs +14 -6
  141. package/templates/base/tools/generator/generator.js.hbs +64 -47
  142. package/templates/base/tools/generator/handle-file.js.hbs +17 -44
  143. package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
  144. package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
  145. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
  146. package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
  147. package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
  148. package/templates/base/tools/generator/templates/index.js.hbs +7 -12
  149. package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
  150. package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
  151. package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
  152. package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
  153. package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
  154. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
  155. package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
  156. package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
  157. package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
  158. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
  159. package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
  160. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
  161. package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
  162. package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
  163. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
  164. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
  165. package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
  166. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
  167. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
  168. package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
  169. package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
  170. package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
  171. package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
  172. package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
  173. package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
  174. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
  175. package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
  176. package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
  177. package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
  178. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
  179. package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
  180. package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
  181. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
  182. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
  183. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
  184. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
  185. package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
  186. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
  187. package/templates/base/tools/utils/error-exit.js.hbs +8 -0
  188. package/templates/base/tools/utils/format.js.hbs +10 -10
  189. package/templates/base/tools/utils/format.test.js.hbs +45 -0
  190. package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
  191. package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
  192. package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
  193. package/templates/base/tools/utils/index.js.hbs +1 -3
  194. package/templates/base/tools/utils/languages.js.hbs +15 -0
  195. package/templates/base/tools/utils/languages.test.js.hbs +16 -0
  196. package/templates/base/tools/utils/print.js.hbs +19 -13
  197. package/templates/base/tools/utils/print.test.js.hbs +60 -0
  198. package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
  199. package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
  200. package/templates/base/tools/utils/run-command.js.hbs +4 -2
  201. package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
  202. package/templates/base/tools/utils/run-script.js.hbs +14 -3
  203. package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
  204. package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
  205. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
  206. package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
  207. package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
  208. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
  209. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
  210. package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
  211. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
  212. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
  213. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
  214. package/lib/src/context-builder.mjs +0 -40
  215. package/lib/src/file-generator.mjs +0 -109
  216. package/lib/src/json-generator.mjs +0 -31
  217. package/lib/src/package-manager.mjs +0 -139
  218. package/lib/src/print.mjs +0 -32
  219. package/lib/src/utils.mjs +0 -18
  220. package/lib/src/version.mjs +0 -1
  221. package/templates/base/builder.config.js.hbs +0 -36
  222. package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
  223. package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
  224. package/templates/base/src/app/dom.test.js.hbs +0 -13
  225. package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
  226. package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
  227. package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
  228. package/templates/base/src/core/services/store/store.ts.hbs +0 -39
  229. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
  230. package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -113
  231. package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
  232. package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -131
  233. package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
  234. package/templates/base/src/core/utils/index.ts.hbs +0 -2
  235. package/templates/base/src/libs/index.ts.hbs +0 -4
  236. package/templates/base/src/libs/popover/helpers/focus-trap.helper.ts.hbs +0 -197
  237. package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
  238. package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
  239. package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
  240. package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
  241. package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
  242. package/templates/base/src/styles/core/_index.scss.hbs +0 -6
  243. package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
  244. package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
  245. package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
  246. package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
  247. package/templates/base/tools/builder/core/sass.js.hbs +0 -22
  248. package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
  249. package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
  250. package/templates/base/tools/utils/export-flags.js.hbs +0 -6
  251. package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
  252. package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
  253. package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
  254. package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
  255. package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
  256. package/templates/features/i18n/src/core/config.ts.hbs +0 -3
  257. package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
  258. package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
  259. package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
  260. package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
  261. package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
  262. package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
  263. package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -254
  264. package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
  265. package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
  266. package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
  267. package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
  268. package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
  269. package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
  270. package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
  271. package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
  272. package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
  273. package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
  274. package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
  275. package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
  276. package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
  277. package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
  278. /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
  279. /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
  280. /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
  281. /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
  282. /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
  283. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
  284. /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
  285. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
  286. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
  287. /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
@@ -0,0 +1,159 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import * as fsExtra from 'fs-extra';
4
+ import { TemplateCompiler } from './template-compiler.mjs';
5
+ import { print } from '../utils/print.mjs';
6
+ import { FEATURES } from './feature-registry.mjs';
7
+
8
+ const fs = fsExtra.default;
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const BINARY_EXTENSIONS = new Set([
13
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg',
14
+ '.ico', '.woff', '.woff2', '.ttf', '.otf', '.eot',
15
+ '.mp4', '.webm', '.mp3', '.wav', '.ogg',
16
+ '.pdf', '.zip', '.tar', '.gz'
17
+ ]);
18
+
19
+ export class FileGenerator {
20
+ constructor() {
21
+ this.compiler = new TemplateCompiler();
22
+ }
23
+
24
+ async generateProjectFromTemplates(projectPath, context, templatesRoot = this.getTemplatesRoot()) {
25
+ const templateDir = path.join(templatesRoot, 'base');
26
+
27
+ print.section('📝 Processing templates...');
28
+ await this.processTemplateDirectory(templateDir, projectPath, context);
29
+
30
+ await this.processFeatureTemplates(projectPath, context, templatesRoot);
31
+ }
32
+
33
+ getTemplatesRoot() {
34
+ return path.join(__dirname, '..', '..', '..', 'templates');
35
+ }
36
+
37
+ async processTemplateDirectory(templateDir, outputDir, context, options = {}) {
38
+ if (!(await fs.pathExists(templateDir))) {
39
+ throw new Error(`Template directory not found: ${templateDir}`);
40
+ }
41
+
42
+ const entries = await fs.readdir(templateDir, { withFileTypes: true });
43
+
44
+ for (const entry of entries) {
45
+ if (entry.name === '.DS_Store') continue;
46
+
47
+ const templatePath = path.join(templateDir, entry.name);
48
+
49
+ if (entry.isDirectory()) {
50
+ const outputPath = path.join(outputDir, entry.name);
51
+ await fs.ensureDir(outputPath);
52
+ await this.processTemplateDirectory(templatePath, outputPath, context, options);
53
+ } else {
54
+ await this.processTemplateFile(templatePath, outputDir, entry.name, context, options);
55
+ }
56
+ }
57
+ }
58
+
59
+ async processFeatureTemplates(projectPath, context, templatesRoot = this.getTemplatesRoot()) {
60
+ const featuresDir = path.join(templatesRoot, 'features');
61
+
62
+ if (!(await fs.pathExists(featuresDir))) {
63
+ return;
64
+ }
65
+
66
+ for (const feature of FEATURES) {
67
+ if (context[feature.key]) {
68
+ const featureTemplateDir = path.join(featuresDir, feature.key);
69
+
70
+ if (await fs.pathExists(featureTemplateDir)) {
71
+ print.info(`Adding ${feature.key} feature...`);
72
+ await this.processTemplateDirectory(featureTemplateDir, projectPath, context);
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ async isRenderTemplateFile(templatePath, fileName, context) {
79
+ const fileExt = path.extname(fileName).toLowerCase();
80
+ const outputFileName = fileName.endsWith('.hbs') ? fileName.replace('.hbs', '') : fileName;
81
+ const isBinary = BINARY_EXTENSIONS.has(fileExt);
82
+
83
+ let content;
84
+ if (isBinary) {
85
+ content = await fs.readFile(templatePath);
86
+ } else if (fileName.endsWith('.hbs')) {
87
+ content = await this.compiler.compileFile(templatePath, context);
88
+ } else {
89
+ content = await fs.readFile(templatePath, 'utf8');
90
+ }
91
+
92
+ return { outputFileName, isBinary, content };
93
+ }
94
+
95
+ async processTemplateFile(templatePath, outputDir, fileName, context, options = {}) {
96
+ let rendered;
97
+ try {
98
+ rendered = await this.isRenderTemplateFile(templatePath, fileName, context);
99
+ } catch (error) {
100
+ print.boldError(`❌ Failed to process template: ${fileName}`);
101
+ throw error;
102
+ }
103
+
104
+ if (!rendered) {
105
+ return;
106
+ }
107
+
108
+ const outputPath = path.join(outputDir, rendered.outputFileName);
109
+
110
+ if (options.skipExisting && await fs.pathExists(outputPath)) {
111
+ print.gray(`Skipped (already exists): ${path.relative(outputDir, outputPath)}`);
112
+ return;
113
+ }
114
+
115
+ await fs.writeFile(outputPath, rendered.content);
116
+ }
117
+
118
+ async collectTemplateTree(templatesRoot, context) {
119
+ const result = new Map();
120
+
121
+ const baseDir = path.join(templatesRoot, 'base');
122
+ if (await fs.pathExists(baseDir)) {
123
+ await this.collectTemplateDirectory(baseDir, baseDir, context, result);
124
+ }
125
+
126
+ const featuresDir = path.join(templatesRoot, 'features');
127
+ for (const feature of FEATURES) {
128
+ if (!context[feature.key]) continue;
129
+
130
+ const featureDir = path.join(featuresDir, feature.key);
131
+ if (await fs.pathExists(featureDir)) {
132
+ await this.collectTemplateDirectory(featureDir, featureDir, context, result);
133
+ }
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ async collectTemplateDirectory(dir, baseDir, context, result) {
140
+ const entries = await fs.readdir(dir, { withFileTypes: true });
141
+
142
+ for (const entry of entries) {
143
+ if (entry.name === '.DS_Store') continue;
144
+
145
+ const entryPath = path.join(dir, entry.name);
146
+
147
+ if (entry.isDirectory()) {
148
+ await this.collectTemplateDirectory(entryPath, baseDir, context, result);
149
+ } else {
150
+ const rendered = await this.isRenderTemplateFile(entryPath, entry.name, context);
151
+ if (!rendered) continue;
152
+
153
+ const relativeDir = path.relative(baseDir, dir);
154
+ const relativeOutputPath = path.join(relativeDir, rendered.outputFileName);
155
+ result.set(relativeOutputPath, { content: rendered.content, isBinary: rendered.isBinary });
156
+ }
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,33 @@
1
+ import inquirer from 'inquirer';
2
+ import { print } from '../utils/print.mjs';
3
+ import { FEATURES } from './feature-registry.mjs';
4
+ import { detectPackageManager } from './package-json-helper.mjs';
5
+ import { writeProjectMeta } from './project-meta.mjs';
6
+
7
+ export async function bootstrapProjectMeta(projectPath) {
8
+ print.warn('⚠️ No .nutin-meta.json found for this project.');
9
+ print.section('Answer a couple of questions so a baseline can be reconstructed:\n');
10
+
11
+ const { version } = await inquirer.prompt([
12
+ {
13
+ type: 'input',
14
+ name: 'version',
15
+ message: 'Which nutin version was this project originally created with (e.g. 1.3.1)?',
16
+ validate: (input) => /^\d+\.\d+\.\d+$/.test(input.trim()) || 'Enter a version like 1.3.1',
17
+ },
18
+ ]);
19
+
20
+ const { features } = await inquirer.prompt([
21
+ {
22
+ type: 'checkbox',
23
+ name: 'features',
24
+ message: 'Which optional features does this project have?',
25
+ choices: FEATURES.map((feature) => ({ name: feature.key, value: feature.key })),
26
+ },
27
+ ]);
28
+
29
+ const packageManager = await detectPackageManager(projectPath);
30
+ const flags = Object.fromEntries(FEATURES.map((f) => [f.key, features.includes(f.key)]));
31
+
32
+ return writeProjectMeta(projectPath, { version: version.trim(), packageManager, ...flags });
33
+ }
@@ -0,0 +1,10 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const packageJson = require('../../../package.json');
5
+
6
+ export const PACKAGE_VERSION = packageJson.version;
7
+ export const PACKAGE_NAME = packageJson.name;
8
+ export const PACKAGE_HOMEPAGE = packageJson.homepage;
9
+
10
+ export const META_FILE_NAME = '.nutin-meta.json';
@@ -0,0 +1,103 @@
1
+ import { promiseExec } from '../utils/promise-exec-alias.mjs';
2
+ import { print } from '../utils/print.mjs';
3
+ import * as fsExtra from 'fs-extra';
4
+ import * as path from 'path';
5
+
6
+ const fs = fsExtra.default;
7
+
8
+ export async function detectPackageManager(projectPath) {
9
+ if (await fs.pathExists(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';
10
+ if (await fs.pathExists(path.join(projectPath, 'yarn.lock'))) return 'yarn';
11
+ // bun 1.x switched its default lockfile from the binary bun.lockb to a text-based bun.lock —
12
+ // check both since either can be present depending on the bun version that created the project.
13
+ if (await fs.pathExists(path.join(projectPath, 'bun.lock'))) return 'bun';
14
+ if (await fs.pathExists(path.join(projectPath, 'bun.lockb'))) return 'bun';
15
+ return 'npm';
16
+ }
17
+
18
+ export async function installDependencies(projectPath, packageManager) {
19
+ print.section(`📦 Installing dependencies with ${packageManager}...`);
20
+
21
+ const installCommand = getInstallCommand(packageManager);
22
+ try {
23
+ await promiseExec(installCommand, { cwd: projectPath, maxBuffer: 1024 * 1024 * 10 });
24
+ } catch (err) {
25
+ const output = `${err.stdout ?? ''}\n${err.stderr ?? ''}`;
26
+
27
+ // pnpm >=10 refuses to run devDependency postinstall/build scripts (e.g. esbuild's,
28
+ // chokidar's optional native watcher backends) by default and exits non-zero even though
29
+ // every package installed successfully. Recover by approving them here (writes pnpm-workspace.yaml,
30
+ // which also stops every later `pnpm run <script>` from re-triggering the same check).
31
+ if (packageManager === 'pnpm' && /ERR_PNPM_IGNORED_BUILDS/.test(output)) {
32
+ print.warn('⚠️ pnpm flagged some build scripts as needing approval — approving automatically...');
33
+ try {
34
+ await promiseExec('pnpm approve-builds --all', { cwd: projectPath, maxBuffer: 1024 * 1024 * 10 });
35
+ return;
36
+ } catch (approveErr) {
37
+ print.error(`pnpm approve-builds --all also failed: ${approveErr.message}`);
38
+ }
39
+ }
40
+
41
+ if (err.stdout) print.gray(err.stdout);
42
+ if (err.stderr) print.error(err.stderr);
43
+ throw new Error(`${installCommand} failed (see output above for the real cause).`);
44
+ }
45
+ }
46
+
47
+ export function getCiCommand(packageManager) {
48
+ switch (packageManager) {
49
+ case 'yarn':
50
+ return 'yarn install --frozen-lockfile';
51
+ case 'pnpm':
52
+ return 'pnpm install --frozen-lockfile';
53
+ case 'bun':
54
+ return 'bun install --frozen-lockfile'
55
+ default:
56
+ return 'npm ci';
57
+ }
58
+ }
59
+
60
+ export function getInstallCommand(packageManager) {
61
+ switch (packageManager) {
62
+ case 'yarn':
63
+ return 'yarn install';
64
+ case 'pnpm':
65
+ return 'pnpm install';
66
+ case 'bun':
67
+ return 'bun install'
68
+ default:
69
+ return 'npm install';
70
+ }
71
+ }
72
+
73
+ export function getDockerScripts() {
74
+ const dockerScriptsDir = 'node tools/docker/scripts'
75
+ return {
76
+ "docker:build": `${dockerScriptsDir}/docker-build.js`,
77
+ "docker:run": `${dockerScriptsDir}/docker-run.js`
78
+ };
79
+ }
80
+
81
+ export function getAllScripts(context) {
82
+ const { packageManager, docker } = context;
83
+
84
+ const baseScripts = {
85
+ "build": "node tools/builder/builder.js",
86
+ "build:prod": "NODE_ENV=production node tools/builder/builder.js",
87
+ "serve": `${packageManager} run build && ${packageManager} run serve:only`,
88
+ "serve:only": "node tools/dev/serve.js",
89
+ "serve:prod": `${packageManager} run build:prod && ${packageManager} run serve:only`,
90
+ "dev": "node tools/dev/dev-serve.js",
91
+ "generate": "node tools/generator/generator.js",
92
+ "testin-nutin": `${packageManager} run build && node tools/testin-nutin/runner.js`,
93
+ "testin-nutin:watch": `${packageManager} run build && node tools/testin-nutin/watch-tests.js`,
94
+ "testin-nutin:coverage": `${packageManager} run build && node tools/testin-nutin/runner.js --coverage`,
95
+ "testin-nutin:verbose": `${packageManager} run build && node tools/testin-nutin/runner.js --verbose`
96
+ };
97
+
98
+ let scripts = { ...baseScripts };
99
+
100
+ if (docker) scripts = { ...scripts, ...getDockerScripts() };
101
+
102
+ return scripts;
103
+ }
@@ -0,0 +1,54 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { FEATURES } from './feature-registry.mjs';
4
+
5
+ const fs = fsExtra.default;
6
+
7
+ const META_FILE_NAME = '.nutin-meta.json';
8
+
9
+ function metaPath(projectPath) {
10
+ return path.join(projectPath, META_FILE_NAME);
11
+ }
12
+
13
+ function extractFeatures(context) {
14
+ const features = {};
15
+ for (const feature of FEATURES) {
16
+ features[feature.key] = Boolean(context[feature.key]);
17
+ }
18
+ return features;
19
+ }
20
+
21
+ export async function writeProjectMeta(projectPath, context) {
22
+ const meta = {
23
+ version: context.version,
24
+ packageManager: context.packageManager,
25
+ features: extractFeatures(context),
26
+ };
27
+
28
+ await fs.writeJSON(metaPath(projectPath), meta, { spaces: 2 });
29
+ return meta;
30
+ }
31
+
32
+ export async function readProjectMeta(projectPath) {
33
+ const file = metaPath(projectPath);
34
+ if (!(await fs.pathExists(file))) {
35
+ return null;
36
+ }
37
+ return fs.readJSON(file);
38
+ }
39
+
40
+ export async function updateProjectMeta(projectPath, patch) {
41
+ const existing = (await readProjectMeta(projectPath)) ?? {};
42
+
43
+ const merged = {
44
+ ...existing,
45
+ ...patch,
46
+ features: {
47
+ ...(existing.features ?? {}),
48
+ ...(patch.features ?? {}),
49
+ },
50
+ };
51
+
52
+ await fs.writeJSON(metaPath(projectPath), merged, { spaces: 2 });
53
+ return merged;
54
+ }
@@ -0,0 +1,21 @@
1
+ import { getCiCommand } from '../common/package-json-helper.mjs';
2
+ import { PACKAGE_VERSION } from '../common/package-data.mjs';
3
+
4
+ export const defaults = {
5
+ projectName: 'my-nutin-app',
6
+ packageManager: 'npm',
7
+ };
8
+
9
+ export class ContextBuilder {
10
+ buildContext(preferences) {
11
+ const version = PACKAGE_VERSION;
12
+ const ciCommand = getCiCommand(preferences.packageManager);
13
+
14
+ return {
15
+ projectName: preferences.projectName,
16
+ packageManager: preferences.packageManager,
17
+ ciCommand: ciCommand,
18
+ version: version
19
+ };
20
+ }
21
+ }
@@ -1,5 +1,5 @@
1
- import { print } from './print.mjs';
2
- import { promiseExec } from './utils.mjs';
1
+ import { print } from '../utils/print.mjs';
2
+ import { promiseExec } from '../utils/promise-exec-alias.mjs';
3
3
 
4
4
  export async function initializeGit(projectPath) {
5
5
  print.section('⚙️ Initializing Git repository...');
@@ -0,0 +1,73 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import { getAllScripts } from '../common/package-json-helper.mjs';
4
+
5
+ const fs = fsExtra.default;
6
+
7
+ export class JsonManager {
8
+ async generateJsonFiles(projectPath, context) {
9
+ await this.generatePackageJson(projectPath, context);
10
+ await this.generateTsconfigJson(projectPath, context);
11
+ }
12
+
13
+ async generatePackageJson(projectPath, context) {
14
+ const { testinNutin, projectName } = context;
15
+
16
+ const devDependencies = {
17
+ "chokidar": "^4.0.3",
18
+ "esbuild": "^0.25.12",
19
+ "html-minifier-terser": "^7.2.0",
20
+ "jsdom": "^26.1.0",
21
+ "linkedom": "^0.18.12",
22
+ "live-server": "^1.2.2",
23
+ "sass": "^1.89.0",
24
+ "typescript": "^5.8.3"
25
+ };
26
+
27
+ const scripts = getAllScripts(context);
28
+
29
+ const packageJson = {
30
+ "name": projectName,
31
+ "version": "0.1.0",
32
+ "type": "module",
33
+ "imports": {
34
+ "#root/*.js": "./*.js"
35
+ },
36
+ scripts,
37
+ devDependencies,
38
+ "engines": {
39
+ "node": ">=22"
40
+ }
41
+ };
42
+
43
+ await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
44
+ }
45
+
46
+ async generateTsconfigJson(projectPath) {
47
+ const tsconfig = {
48
+ "compilerOptions": {
49
+ "baseUrl": "./",
50
+ "paths": {},
51
+ "target": "ESNext",
52
+ "module": "NodeNext",
53
+ "moduleResolution": "NodeNext",
54
+ "rootDir": "src",
55
+ "outDir": "dist-build/src",
56
+ "strict": true,
57
+ "skipLibCheck": true,
58
+ "forceConsistentCasingInFileNames": true,
59
+ "noUncheckedIndexedAccess": true,
60
+ "esModuleInterop": true,
61
+ "allowSyntheticDefaultImports": true,
62
+ "lib": ["es2022", "DOM"],
63
+ "removeComments": true,
64
+ "resolveJsonModule": true,
65
+ "typeRoots": ["src/types", "node_modules/@types"]
66
+ },
67
+ "include": ["src/app", "src/core"],
68
+ "exclude": ["node_modules"]
69
+ };
70
+
71
+ await fs.writeJSON(path.join(projectPath, 'tsconfig.json'), tsconfig, { spaces: 2 });
72
+ }
73
+ }
@@ -1,7 +1,7 @@
1
1
  import inquirer from 'inquirer';
2
2
  import { defaults } from './context-builder.mjs';
3
3
 
4
- export async function promptUser(initialName, cliOptions = {}) {
4
+ export async function newAppPrompt(initialName, cliOptions = {}) {
5
5
 
6
6
  let projectName = initialName;
7
7
  if (!projectName) {
@@ -15,6 +15,11 @@ export async function promptUser(initialName, cliOptions = {}) {
15
15
  },
16
16
  ]);
17
17
  projectName = nameInput.projectName;
18
+ } else {
19
+ const validationResult = validateProjectName(projectName);
20
+ if (validationResult !== true) {
21
+ throw new Error(validationResult);
22
+ }
18
23
  }
19
24
 
20
25
  let packageManager = cliOptions.packageManager;
@@ -34,11 +39,6 @@ export async function promptUser(initialName, cliOptions = {}) {
34
39
  return {
35
40
  projectName,
36
41
  packageManager,
37
- preset: cliOptions.preset,
38
- i18n: cliOptions.i18n,
39
- deployHelper: cliOptions.deployHelper,
40
- testinNutin: cliOptions.testinNutin,
41
- transition: cliOptions.transition,
42
42
  };
43
43
  }
44
44
 
@@ -1,18 +1,19 @@
1
1
  import path from 'path';
2
2
  import * as fsExtra from 'fs-extra';
3
- import { print } from './print.mjs';
3
+ import { print } from '../utils/print.mjs';
4
4
  import { initializeGit } from './git-manager.mjs';
5
- import { installDependencies } from './package-manager.mjs';
6
- import { FileGenerator } from './file-generator.mjs';
7
- import { JsonGenerator } from './json-generator.mjs';
5
+ import { installDependencies } from '../common/package-json-helper.mjs';
6
+ import { FileGenerator } from '../common/file-generator.mjs';
7
+ import { JsonManager } from './json-manager.mjs';
8
8
  import { ContextBuilder } from './context-builder.mjs';
9
+ import { writeProjectMeta } from '../common/project-meta.mjs';
9
10
 
10
11
  const fs = fsExtra.default;
11
12
 
12
13
  export class ProjectGenerator {
13
14
  constructor() {
14
15
  this.fileGenerator = new FileGenerator();
15
- this.jsonGenerator = new JsonGenerator();
16
+ this.jsonManager = new JsonManager();
16
17
  this.builder = new ContextBuilder();
17
18
  }
18
19
 
@@ -40,8 +41,9 @@ export class ProjectGenerator {
40
41
  }
41
42
 
42
43
  async runPostSetupTasks(projectPath, context) {
43
- await this.jsonGenerator.generateJsonFiles(projectPath, context);
44
- await initializeGit(projectPath);
44
+ await this.jsonManager.generateJsonFiles(projectPath, context);
45
+ await writeProjectMeta(projectPath, context);
46
+ await initializeGit(projectPath);
45
47
  await installDependencies(projectPath, context.packageManager);
46
48
  }
47
49
  }
@@ -50,4 +52,4 @@ export const projectGenerator = new ProjectGenerator();
50
52
 
51
53
  export async function createProject(preferences) {
52
54
  return projectGenerator.createProject(preferences);
53
- }
55
+ }
@@ -0,0 +1,14 @@
1
+ import { print } from '../utils/print.mjs';
2
+ import { PACKAGE_HOMEPAGE } from '../common/package-data.mjs';
3
+
4
+ export function displaySuccessMessage(answers) {
5
+ const { projectName, packageManager, deployHelper } = answers;
6
+ print.boldSuccess('\n🎉 Your project is ready!\n');
7
+
8
+ print.boldInfo('Next steps:');
9
+ print.info(`cd ${projectName}`);
10
+ print.info(`${packageManager} run serve\n`);
11
+
12
+ print.boldInfo('Documentation:');
13
+ print.info(`https://${PACKAGE_HOMEPAGE}\n`);
14
+ }
@@ -0,0 +1,31 @@
1
+ import path from 'path';
2
+ import * as fsExtra from 'fs-extra';
3
+ import * as Diff from 'diff';
4
+
5
+ const fs = fsExtra.default;
6
+ const REPORT_FILE_NAME = 'NUTIN-UPDATE-REPORT.md';
7
+
8
+ export async function writeConflictReport(projectPath, conflicts, oldVersion, newVersion) {
9
+ const sections = conflicts.map(({ relPath, actualContent, newEntry }) => {
10
+ if (newEntry.isBinary || Buffer.isBuffer(actualContent)) {
11
+ return `## ${relPath}\n\nBinary file — differs from both your recorded baseline and the new nutin version. Compare manually.\n`;
12
+ }
13
+
14
+ const patch = Diff.createTwoFilesPatch(
15
+ relPath,
16
+ relPath,
17
+ actualContent,
18
+ newEntry.content,
19
+ 'yours',
20
+ `nutin v${newVersion}`,
21
+ );
22
+ return `## ${relPath}\n\n\`\`\`diff\n${patch}\`\`\`\n`;
23
+ });
24
+
25
+ const header = '# nutin-update report\n\n' +
26
+ `These files changed between nutin v${oldVersion} and v${newVersion}, but you've customized them ` +
27
+ 'since generation, so they were left untouched. Review each diff below (yours vs. the new nutin ' +
28
+ 'version) and merge by hand.\n\n';
29
+
30
+ await fs.writeFile(path.join(projectPath, REPORT_FILE_NAME), header + sections.join('\n'));
31
+ }
@@ -0,0 +1,61 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import * as fsExtra from 'fs-extra';
4
+ import * as tar from 'tar';
5
+ import { promiseExec } from '../utils/promise-exec-alias.mjs';
6
+ import { PACKAGE_NAME } from '../common/package-data.mjs';
7
+
8
+ const fs = fsExtra.default;
9
+
10
+ export async function fetchOldTemplates(version, { from } = {}) {
11
+ if (from) {
12
+ const templatesRoot = path.resolve(from);
13
+ if (!(await fs.pathExists(templatesRoot))) {
14
+ throw new Error(`--from path not found: ${templatesRoot}`);
15
+ }
16
+ return { templatesRoot, cleanup: async () => {} };
17
+ }
18
+
19
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'nutin-update-'));
20
+
21
+ let tarballName;
22
+ try {
23
+ const { stdout } = await promiseExec(
24
+ `npm pack ${PACKAGE_NAME}@${version} --pack-destination "${tmpDir}" --json`,
25
+ { maxBuffer: 1024 * 1024 * 10 },
26
+ );
27
+ const parsed = JSON.parse(stdout);
28
+ const packResult = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0];
29
+ tarballName = packResult?.filename;
30
+ } catch (error) {
31
+ await fs.remove(tmpDir);
32
+ throw new Error(
33
+ `Couldn't fetch ${PACKAGE_NAME}@${version} from the npm registry — (${error.message})`,
34
+ );
35
+ }
36
+
37
+ if (!tarballName) {
38
+ await fs.remove(tmpDir);
39
+ throw new Error(`npm pack did not report a tarball for ${PACKAGE_NAME}@${version}.`);
40
+ }
41
+
42
+ try {
43
+ await tar.x({ file: path.join(tmpDir, tarballName), cwd: tmpDir });
44
+ } catch (error) {
45
+ await fs.remove(tmpDir);
46
+ throw new Error(`Couldn't extract ${tarballName} for ${PACKAGE_NAME}@${version} — (${error.message})`);
47
+ }
48
+
49
+ await fs.remove(path.join(tmpDir, tarballName));
50
+
51
+ const templatesRoot = path.join(tmpDir, 'package', 'templates');
52
+ if (!(await fs.pathExists(templatesRoot))) {
53
+ await fs.remove(tmpDir);
54
+ throw new Error(`${PACKAGE_NAME}@${version}'s package didn't contain a templates/ directory.`);
55
+ }
56
+
57
+ return {
58
+ templatesRoot,
59
+ cleanup: async () => fs.remove(tmpDir),
60
+ };
61
+ }