@nutin/cli 2.1.0 → 3.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 (241) hide show
  1. package/CHANGELOG.md +322 -0
  2. package/README.md +14 -6
  3. package/bin/add-feature.mjs +1 -1
  4. package/bin/cli.mjs +1 -1
  5. package/bin/create-app.mjs +1 -1
  6. package/bin/update-app.mjs +1 -1
  7. package/lib/index.mjs +6 -2
  8. package/lib/src/add-feature/base-template-patcher.mjs +139 -0
  9. package/lib/src/add-feature/feature-adder.mjs +20 -9
  10. package/lib/src/add-feature/feature-context-builder.mjs +1 -0
  11. package/lib/src/add-feature/nutin-config-updater.mjs +34 -8
  12. package/lib/src/add-feature/package-json-updater.mjs +7 -9
  13. package/lib/src/common/feature-registry.mjs +4 -0
  14. package/lib/src/common/file-generator.mjs +25 -3
  15. package/lib/src/common/git.mjs +31 -0
  16. package/lib/src/common/meta-bootstrap-prompt.mjs +28 -2
  17. package/lib/src/common/package-json-helper.mjs +14 -4
  18. package/lib/src/common/project-meta.mjs +2 -2
  19. package/lib/src/common/template-compiler.mjs +2 -0
  20. package/lib/src/common/transpile-ts.mjs +94 -0
  21. package/lib/src/create-app/context-builder.mjs +1 -0
  22. package/lib/src/create-app/git-manager.mjs +12 -2
  23. package/lib/src/create-app/json-manager.mjs +21 -18
  24. package/lib/src/create-app/new-app-prompt.mjs +4 -2
  25. package/lib/src/create-app/success-message.mjs +1 -1
  26. package/lib/src/update-app/conflict-report-writer.mjs +33 -21
  27. package/lib/src/update-app/json-drift.mjs +54 -0
  28. package/lib/src/update-app/project-updater.mjs +123 -14
  29. package/lib/src/update-app/template-differ.mjs +5 -5
  30. package/lib/src/update-app/three-way-merge.mjs +42 -0
  31. package/lib/src/update-app/update-context-builder.mjs +1 -0
  32. package/lib/src/update-app/update-summary-printer.mjs +10 -2
  33. package/package.json +12 -11
  34. package/templates/base/.gitignore.hbs +1 -0
  35. package/templates/base/AGENTS.md.hbs +45 -43
  36. package/templates/base/GETTING_STARTED.md.hbs +32 -23
  37. package/templates/base/README.md.hbs +7 -4
  38. package/templates/base/nutin.config.js.hbs +1 -1
  39. package/templates/base/src/app/components/.gitkeep +0 -0
  40. package/templates/base/src/app/globals.d.ts.hbs +6 -3
  41. package/templates/base/src/app/guards.ts.hbs +1 -3
  42. package/templates/base/src/app/main.ts.hbs +2 -9
  43. package/templates/base/src/app/routes.ts.hbs +9 -3
  44. package/templates/base/src/app/services/.gitkeep +0 -0
  45. package/templates/base/src/app/views/home/home.view.html.hbs +1 -1
  46. package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
  47. package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +2 -2
  48. package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +461 -1
  49. package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +39 -48
  50. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +36 -1
  51. package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +47 -77
  52. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +307 -21
  53. package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +144 -26
  54. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +105 -4
  55. package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +136 -62
  56. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +85 -4
  57. package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +101 -51
  58. package/templates/base/src/core/base-classes/base-component/helpers/html.helper.test.js.hbs +404 -0
  59. package/templates/base/src/core/base-classes/base-component/helpers/html.helper.ts.hbs +242 -0
  60. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +70 -1
  61. package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +54 -15
  62. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +12 -1
  63. package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +39 -36
  64. package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.test.js.hbs +179 -0
  65. package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.ts.hbs +93 -0
  66. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +135 -17
  67. package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +199 -81
  68. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +40 -23
  69. package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +67 -65
  70. package/templates/base/src/core/base-classes/component/component.test.js.hbs +17 -17
  71. package/templates/base/src/core/base-classes/component/component.ts.hbs +8 -41
  72. package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +1 -1
  73. package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +37 -39
  74. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +14 -1
  75. package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +33 -31
  76. package/templates/base/src/core/base-classes/service/service.test.js.hbs +32 -0
  77. package/templates/base/src/core/base-classes/service/service.ts.hbs +21 -13
  78. package/templates/base/src/core/base-classes/view/view.test.js.hbs +4 -0
  79. package/templates/base/src/core/base-classes/view/view.ts.hbs +11 -8
  80. package/templates/base/src/core/index.ts.hbs +4 -2
  81. package/templates/base/src/core/internals.d.ts.hbs +199 -20
  82. package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +18 -0
  83. package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +26 -25
  84. package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +3 -3
  85. package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +3 -3
  86. package/templates/base/src/core/services/globals/globals.ts.hbs +7 -32
  87. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +133 -50
  88. package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +147 -39
  89. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +75 -19
  90. package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +73 -47
  91. package/templates/base/src/core/services/http-client/http-client.test.js.hbs +339 -232
  92. package/templates/base/src/core/services/http-client/http-client.ts.hbs +37 -25
  93. package/templates/base/src/core/services/i18n/i18n.test.js.hbs +57 -16
  94. package/templates/base/src/core/services/i18n/i18n.ts.hbs +42 -42
  95. package/templates/base/src/core/services/i18n/languages.ts.hbs +0 -2
  96. package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +4 -6
  97. package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +10 -0
  98. package/templates/base/src/core/services/pipe-registry/pipes.ts.hbs +4 -2
  99. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +217 -4
  100. package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +210 -65
  101. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +26 -27
  102. package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +38 -60
  103. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +4 -4
  104. package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +30 -30
  105. package/templates/base/src/core/services/router/router.test.js.hbs +321 -0
  106. package/templates/base/src/core/services/router/router.ts.hbs +122 -54
  107. package/templates/base/src/index.html.hbs +1 -0
  108. package/templates/base/src/styles/_styles.scss.hbs +1 -113
  109. package/templates/base/src/styles/main.scss.hbs +1 -1
  110. package/templates/base/tools/builder/builder.config.js.hbs +6 -0
  111. package/templates/base/tools/builder/builder.js.hbs +26 -8
  112. package/templates/base/tools/builder/core/app/compile-ts.js.hbs +81 -6
  113. package/templates/base/tools/builder/core/app/copy-static.js.hbs +12 -1
  114. package/templates/base/tools/builder/core/app/handler-refs.js.hbs +114 -0
  115. package/templates/base/tools/builder/core/app/handler-refs.test.js.hbs +70 -0
  116. package/templates/base/tools/builder/core/app/paths.js.hbs +17 -0
  117. package/templates/base/tools/builder/core/app/route-keys.js.hbs +47 -0
  118. package/templates/base/tools/builder/core/app/route-keys.test.js.hbs +97 -0
  119. package/templates/base/tools/builder/core/app/ts-errors.js.hbs +58 -0
  120. package/templates/base/tools/builder/core/app/ts-errors.test.js.hbs +84 -0
  121. package/templates/base/tools/builder/core/app/validate-routes.js.hbs +21 -55
  122. package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -0
  123. package/templates/base/tools/builder/core/html-index/add-tags.js.hbs +1 -4
  124. package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +3 -3
  125. package/templates/base/tools/builder/core/html-index/validate-html.js.hbs +10 -2
  126. package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +111 -23
  127. package/templates/base/tools/builder/core/html-templates/minify-templates.js.hbs +8 -0
  128. package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +5 -3
  129. package/templates/base/tools/builder/core/html-templates/template-literal.js.hbs +193 -0
  130. package/templates/base/tools/builder/core/html-templates/template-literal.test.js.hbs +69 -0
  131. package/templates/base/tools/builder/core/i18n/build-i18n.js.hbs +14 -6
  132. package/templates/base/tools/builder/core/prod-bundle/bundle-options.js.hbs +82 -0
  133. package/templates/base/tools/builder/core/prod-bundle/bundle-options.test.js.hbs +65 -0
  134. package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +6 -6
  135. package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +25 -8
  136. package/templates/base/tools/builder/core/prod-bundle/hash-files.js.hbs +4 -4
  137. package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +31 -13
  138. package/templates/base/tools/builder/core/seo/generate-robots-txt.test.js.hbs +48 -0
  139. package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +3 -3
  140. package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +61 -15
  141. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +22 -7
  142. package/templates/base/tools/builder/core/seo/generate-sitemap-xml.test.js.hbs +30 -0
  143. package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +48 -18
  144. package/templates/base/tools/builder/core/seo/html-substitutions.test.js.hbs +45 -0
  145. package/templates/base/tools/builder/core/seo/route-output.js.hbs +26 -8
  146. package/templates/base/tools/builder/core/seo/route-output.test.js.hbs +31 -0
  147. package/templates/base/tools/builder/core/seo/seo-routes.js.hbs +29 -0
  148. package/templates/base/tools/builder/core/seo/seo-routes.test.js.hbs +21 -0
  149. package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +6 -7
  150. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +34 -5
  151. package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.test.js.hbs +48 -0
  152. package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +26 -9
  153. package/templates/base/tools/builder/core/styles/sass.js.hbs +7 -5
  154. package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
  155. package/templates/base/tools/dev/dev-serve.js.hbs +71 -7
  156. package/templates/base/tools/dev/serve.js.hbs +152 -22
  157. package/templates/base/tools/dev/watcher.js.hbs +75 -28
  158. package/templates/base/tools/generator/generator.js.hbs +23 -18
  159. package/templates/base/tools/generator/handle-file.js.hbs +4 -24
  160. package/templates/base/tools/generator/handle-file.test.js.hbs +4 -23
  161. package/templates/base/tools/generator/templates/component/component.template.js.hbs +3 -3
  162. package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +3 -3
  163. package/templates/base/tools/generator/templates/service/service.template.js.hbs +1 -1
  164. package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +1 -1
  165. package/templates/base/tools/generator/templates/view/view.template.js.hbs +2 -2
  166. package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +2 -2
  167. package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +1 -1
  168. package/templates/base/tools/testin-nutin/core/globals/assertion-lib.js.hbs +2 -2
  169. package/templates/base/tools/testin-nutin/core/{tests/assertions.test.js.hbs → globals/assertion-lib.test.js.hbs} +6 -0
  170. package/templates/base/tools/testin-nutin/core/globals/register-test-globals.js.hbs +6 -7
  171. package/templates/base/tools/testin-nutin/core/{tests/globals-each-all.test.js.hbs → globals/register-test-globals.test.js.hbs} +12 -0
  172. package/templates/base/tools/testin-nutin/core/printer.js.hbs +10 -4
  173. package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +8 -8
  174. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.js.hbs +27 -25
  175. package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +30 -22
  176. package/templates/base/tools/testin-nutin/mocks/mock-http-client.js.hbs +7 -10
  177. package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +12 -0
  178. package/templates/base/tools/testin-nutin/mocks/mock-i18n.js.hbs +47 -22
  179. package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +47 -26
  180. package/templates/base/tools/testin-nutin/mocks/mock-router.js.hbs +19 -11
  181. package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +24 -7
  182. package/templates/base/tools/testin-nutin/runner.js.hbs +12 -3
  183. package/templates/base/tools/testin-nutin/watch-tests.js.hbs +45 -20
  184. package/templates/base/tools/utils/build-lock.js.hbs +97 -0
  185. package/templates/base/tools/utils/ensure-deps.js.hbs +72 -0
  186. package/templates/base/tools/utils/ensure-deps.test.js.hbs +17 -0
  187. package/templates/base/tools/utils/format.js.hbs +1 -1
  188. package/templates/base/tools/utils/index.js.hbs +3 -1
  189. package/templates/base/tools/utils/print.js.hbs +2 -2
  190. package/templates/base/tools/utils/resolve-port.js.hbs +30 -0
  191. package/templates/base/tools/utils/resolve-port.test.js.hbs +47 -0
  192. package/templates/base/tools/utils/run-command.js.hbs +30 -1
  193. package/templates/base/tools/utils/run-command.test.js.hbs +12 -1
  194. package/templates/base/tools/utils/run-script.js.hbs +2 -2
  195. package/templates/features/docker/tools/docker/Dockerfile.template.hbs +5 -2
  196. package/templates/features/docker/tools/docker/nginx.conf.template +19 -2
  197. package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +1 -1
  198. package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +2 -2
  199. package/templates/features/docker/tools/docker/scripts/locale-redirects.js.hbs +28 -0
  200. package/templates/features/docker/tools/docker/scripts/locale-redirects.test.js.hbs +34 -0
  201. package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.js.hbs +25 -0
  202. package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.test.js.hbs +34 -0
  203. package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +3 -1
  204. package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +12 -0
  205. package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +29 -0
  206. package/templates/features/markdown/markdown-content/getting-started.md.hbs +28 -0
  207. package/templates/features/markdown/markdown-content/writing-pages.md.hbs +19 -0
  208. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.scss.hbs +75 -0
  209. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.test.js.hbs +40 -0
  210. package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.ts.hbs +65 -0
  211. package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.test.js.hbs +67 -0
  212. package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.ts.hbs +87 -0
  213. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.scss.hbs +42 -0
  214. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.test.js.hbs +59 -0
  215. package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.ts.hbs +72 -0
  216. package/templates/features/markdown/src/app/markdown/locales/en.json.hbs +6 -0
  217. package/templates/features/markdown/src/app/markdown/markdown-guards.test.js.hbs +63 -0
  218. package/templates/features/markdown/src/app/markdown/markdown-guards.ts.hbs +31 -0
  219. package/templates/features/markdown/src/app/markdown/markdown-i18n.test.js.hbs +78 -0
  220. package/templates/features/markdown/src/app/markdown/markdown-i18n.ts.hbs +31 -0
  221. package/templates/features/markdown/src/app/markdown/markdown-manifest.test.js.hbs +98 -0
  222. package/templates/features/markdown/src/app/markdown/markdown-manifest.ts.hbs +157 -0
  223. package/templates/features/markdown/src/app/markdown/markdown-manifests.service.test.js.hbs +87 -0
  224. package/templates/features/markdown/src/app/markdown/markdown-manifests.service.ts.hbs +50 -0
  225. package/templates/features/markdown/src/app/markdown/markdown-routes.test.js.hbs +74 -0
  226. package/templates/features/markdown/src/app/markdown/markdown-routes.ts.hbs +26 -0
  227. package/templates/features/markdown/src/app/markdown/markdown-sources.test.js.hbs +22 -0
  228. package/templates/features/markdown/src/app/markdown/markdown-sources.ts.hbs +42 -0
  229. package/templates/features/markdown/src/app/markdown/view/markdown.view.html.hbs +4 -0
  230. package/templates/features/markdown/src/app/markdown/view/markdown.view.scss.hbs +19 -0
  231. package/templates/features/markdown/src/app/markdown/view/markdown.view.test.js.hbs +125 -0
  232. package/templates/features/markdown/src/app/markdown/view/markdown.view.ts.hbs +146 -0
  233. package/templates/features/markdown/tools/builder/core/markdown/generate-markdown-pages.js.hbs +71 -0
  234. package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.js.hbs +582 -0
  235. package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.test.js.hbs +390 -0
  236. package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.js.hbs +155 -0
  237. package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.test.js.hbs +112 -0
  238. package/templates/base/src/app/components/index.ts.hbs +0 -1
  239. package/templates/base/src/app/services/index.ts.hbs +0 -1
  240. package/templates/base/src/app/views/index.ts.hbs +0 -3
  241. /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
@@ -19,14 +19,19 @@ nutin.config.js build/feature config — see below
19
19
 
20
20
  - Favor small, composable components.
21
21
  - One `View` = one route.
22
- - Import everything from barrel files (`core/index.js`, `components/index.js`, `views/index.js`, `services/index.js`) — do not deep-import.
22
+ - Import framework code from the `core/index.js` barrel — do not deep-import into `core/`. App elements have no barrels: import them by file path.
23
+ {{#if (eq lang "ts")}}
24
+ - Framework types (`Nutin.ComponentConfig`, `Nutin.ComponentOptions`, `Nutin.CatalogConfig`, `Nutin.Template`, `Nutin.Routes`, `Nutin.RouteGuard`, `Nutin.RequestConfig`, …) live in the global `Nutin` namespace declared in `core/internals.d.ts` — use them without importing. App events extend it in `src/app/globals.d.ts` (`declare namespace Nutin { interface AppEventMap { … } }`).
25
+ {{/if}}
26
+ - `src/app/` layout is free; `components/`, `views/`, `services/` is the default suggestion.
23
27
 
24
28
  ## Commands
25
29
 
26
- - Serve: `{{packageManager}} run serve` (build + serve), `{{packageManager}} run serve:only` (no rebuild), `{{packageManager}} run dev` (watch mode).
27
- - Build: `{{packageManager}} run build` (dev) or `{{packageManager}} run build:prod` (production).
28
- - Generate: `{{packageManager}} run generate ELEMENT ELEMENT_NAME` (`ELEMENT` = `component`/`view`/`service`, supports nested paths, updates the nearest `index.ts` barrel).
29
- - Test: `{{packageManager}} run testin-nutin` (build + run), `{{packageManager}} run testin-nutin:only` (no rebuild), `{{packageManager}} run testin-nutin:watch`.
30
+ - Serve: `{{packageManager}} run serve` (build + serve), `{{packageManager}} run serve:prod` (prod build + serve), `{{packageManager}} run serve:only` (no rebuild), `{{packageManager}} run dev` (watch mode, reloads the page after each rebuild). Port defaults to 9090; override with `-- --port <n>` or `PORT=<n>`.
31
+ - Build: `{{packageManager}} run build` (dev) or `{{packageManager}} run build:prod` (production). Opt-in dependencies (Tailwind, `markdown`) are only installed automatically in an interactive terminal; elsewhere pass `-- -y` (or set `NUTIN_ASSUME_YES`).
32
+ - Generate: `{{packageManager}} run generate ELEMENT ELEMENT_NAME` (`ELEMENT` = `component`/`view`/`service`; `ELEMENT_NAME` may be a nested path, generated in `src/app/<path>/`; fails if that folder already exists and isn't empty).
33
+ - Test: `{{packageManager}} run testin-nutin` (build + run), `{{packageManager}} run testin-nutin:only` (no rebuild), `{{packageManager}} run testin-nutin:watch`, `{{packageManager}} run testin-nutin:coverage`, `{{packageManager}} run testin-nutin:verbose`.
34
+ - Update Nutin: `nutin-update` refuses to run unless the project is committed (no changes, no untracked files; `--allow-dirty` skips the check). Files edited here that also changed upstream are merged three-way; overlapping edits get `diff3` conflict markers, and the project won't build, nor will `nutin-update` run again, until they're resolved (listed in `.nutin-meta.json`'s `unmergedFiles` and `NUTIN-UPDATE-REPORT.md`).
30
35
 
31
36
  ## Base classes
32
37
 
@@ -37,15 +42,16 @@ BaseComponent<T> — render/destroy engine, data-* parsing, children
37
42
  └── View — one per route; mounted/unmounted by the Router
38
43
  ```
39
44
 
40
- - `render()`: `onBeforeRender()` → sanitize + set `innerHTML` → mount children (`data-component`/`data-catalog`) → parse `data-i18n`/`data-pipe` → bind `data-event` listeners → `onAfterRender()`. Runs in full on every `render()` call — no virtual DOM/diffing.
45
+ - `render()`: `onBeforeRender()` → sanitize the template and insert the resulting nodes (`SecurityHelper.sanitizeToFragment()`, never assigned to `innerHTML`) → mount children (`data-component`/`data-catalog`) → parse `data-i18n`/`data-pipe` → bind `data-event` listeners → `onAfterRender()`. Runs in full on every `render()` call — no virtual DOM/diffing; only children with `keep`/`keepBy` are kept (see "Children components").
41
46
  - `destroy()`: unbinds events, unsubscribes bus listeners, destroys children, removes the element.
42
- - `Service`: direct `new` throws — always use `MyService.getInstance()` (or the exported const). Auto-binds all methods to the instance. `registerCleanup(fn)` for teardown; `Service.destroyAll()` runs on `beforeunload`.
43
- - Overridable hooks (all no-op by default): `onBeforeRender`, `onAfterRender`, `onBeforeDestroy`, `onAfterDestroy`, `generateTemplate()`, `registerChildren()`. `View` adds router-only `onEnter()`/`onExit()`.
47
+ - `Service`: direct `new` throws — always use `MyService.getInstance()` (or the exported const). Auto-binds all methods to the instance. `registerCleanup(fn)` callbacks run on `dispose()`, `MyService.destroy()` and `Service.destroyAll()` (tests, SSR) — nothing is torn down on page unload.
48
+ - Overridable hooks (all no-op by default): `onBeforeRender`, `onAfterRender`, `onBeforeDestroy`, `onAfterDestroy`, `onKept`, `generateTemplate()`, `registerChildren()`. `View` adds router-only `onEnter()`/`onExit()`.
44
49
 
45
50
  ## Children components
46
51
 
47
- - Override `registerChildren(): ComponentConfig[]` to declare children — matched against `[data-component="selector"]` elements in the rendered template. ComponentConfig: `{ selector: 'my-selector', factory: (el) => new MyComponent(el, {...} )}`.
48
- - For repeated lists, use the inherited `createCatalogComponents({ items, elementName, selector, component })` inside `registerChildren()` instead of hand-writing one entry per item — targets `[data-catalog="selector"]`.
52
+ - Override `registerChildren(): Nutin.ComponentConfig[]` to declare children — matched against `[data-component="selector"]` elements in the rendered template. ComponentConfig: `{ selector: 'my-selector', factory: (el) => new MyComponent(el, {...} ), keep?: string | number }`.
53
+ - Children are destroyed and recreated on every parent render unless they have a `keep` value: with the same value on the next render, the child is kept as-is (factory not called, not re-rendered, DOM state preserved) and only its `onKept()` hook runs. Put in `keep` whatever should force a recreate.
54
+ - For repeated lists, use the inherited `createCatalogComponents({ items, elementName, selector, component })` inside `registerChildren()` instead of hand-writing one entry per item — targets `[data-catalog="selector"]`. Add `keepBy: (item, index) => id` to keep items whose id is unchanged and whose item is shallow-equal; without it the catalog is rebuilt every render.
49
55
 
50
56
  ## `data-*` attributes
51
57
 
@@ -62,7 +68,7 @@ BaseComponent<T> — render/destroy engine, data-* parsing, children
62
68
 
63
69
  ## DOM events binding
64
70
 
65
- Handlers are (re-)bound on every `render()`, not at build time. Always use private methods prefixed with underscore:
71
+ Handlers are (re-)bound on every `render()`, not at build time. Always use private methods prefixed with underscore{{#if (eq lang "ts")}} (`protected` for a handler a subclass's template uses){{/if}}:
66
72
 
67
73
  ```html
68
74
  <div data-event="event:_myFunction:@value,@id">
@@ -71,17 +77,21 @@ Handlers are (re-)bound on every `render()`, not at build time. Always use priva
71
77
  private _myFunction(value: string, id: string) { doStuff() }
72
78
  ```
73
79
 
74
- Tokens: `@value`, `@id`, `@class`, `@textContent`, `@event`, `@target`, `@attr:name`, `@dataset:key`, plus string/number literals.
80
+ {{#if (eq lang "ts")}}The build type-checks each handler name written in a template against the class: a missing one fails `tsc` at the template line, and handlers count as used for `noUnusedLocals` (on in this project). Names built with `${}` aren't checked. {{/if}}At runtime, a missing handler binds nothing and logs a `console.warn` in development.
81
+
82
+ Tokens: `@value`, `@id`, `@class`, `@textContent`, `@event`, `@target`, `@attr:name`, `@dataset:key`, plus string/number literals (quoted literals may contain commas). Token values are raw (not HTML-escaped): escaping happens when they are rendered through an `html` template.
83
+
84
+ Only defaults that leave the page are cancelled: link clicks (`<a href>`, unless modified, e.g. ctrl+click), form `submit` and submit-button clicks inside a form. Other events keep their default (typing, checking a box); take `@event` and call `preventDefault()` yourself if needed. A component only binds its own `data-event`/`data-bind`/`data-i18n` elements, not those inside nested child components.
75
85
 
76
86
  ## Core services
77
87
 
78
- - `AppEventBus` — `subscribe`/`once`/`emit`/`off`. Inside a component, prefer `this.listen(event, cb)` (auto-unsubscribes on `destroy()`) or `this.listenToRenderEvents([event1, event2, ...])` (re-runs `this.render()` whenever any of the given events fire) over calling the bus directly. Typed facades — each `on*` subscriber returns a closure you call directly to unsubscribe (`const off = Navigation.onNavigate(cb); ...; off();`), no need to keep the original callback reference around for `off()`:
88
+ - `AppEventBus` — `subscribe`/`once`/`emit`/`off`.{{#if (eq lang "ts")}} Declare app events in `src/app/globals.d.ts`'s `Nutin.AppEventMap`: `emit()` requires the declared payload, and an event declared `undefined` takes none.{{/if}} Inside a component, prefer `this.listen(event, cb)` (auto-unsubscribes on `destroy()`) or `this.listenToRenderEvents([event1, event2, ...])` (re-runs `this.render()` whenever any of the given events fire) over calling the bus directly. Typed facades — each `on*` subscriber returns a closure you call directly to unsubscribe (`const off = Navigation.onNavigate(cb); ...; off();`), no need to keep the original callback reference around for `off()`:
79
89
  - `Navigation` — `navigateTo(path)`/`onNavigate`, `reload()`/`onReload`.
80
90
  - `Lifecycle` — `viewMount(viewName)`/`onViewMount`, `viewUnmount(viewName)`/`onViewUnmount`, plus generic `beforeRender`/`afterRender`/`beforeDestroy`/`afterDestroy`.
81
- - `AppRouter(routes)` — call once in `main.ts`; routes support `:param`/`:param?` and per-route guards.
91
+ - `AppRouter(routes)` — call once in `main.ts` (optional: without it, mount components into any element with `new X(el).render()`; the build then skips the `routes` and `#app` checks, unless `generateSEOFiles` is on; a mount target that isn't found logs a `console.error` in development; testin-nutin's framework tests still mount into `#app`, see "Testing"); routes support `:param`/`:param?`, per-route guards, and lazy views: a factory returning `import('./views/x/x.view.js').then((m) => new m.XView())` loads the view on first visit (import it by its own file, not through a barrel{{#if (eq lang "ts")}}; factory type: global `Nutin.ViewFactory`{{/if}}). Keep `/` and `/404` eager. After each in-app navigation (not the first load or `Navigation.reload()`), focus moves to the view's `<h1>`, else the `<h1>` in `<main>`, else the view element, with `document.title` announced in a polite live region in that last case only (a focused heading is read by screen readers). Give each view an `<h1>`. `document.title` comes from `View.documentTitle()` if overridden, else `config/seo.json`'s title (with `generateSEOFiles`), the `<viewName>.title` translation, then `viewName`.
82
92
  - `registerGlobals({ before?, after? })` — each entry is `{ component, id }` (a bare class, constructed internally); mounts them outside `<main id="app">` (`before`/`after` control placement relative to it), stamping each root element with `id`. `hideGlobals(ids)`/`revealGlobals(ids)` (same module) toggle visibility by those ids from any component or view. Call once in `main.ts`.
83
- - `AppHttpClient` — `get/post/put/patch/delete<T>(endpoint, data?, config?)`; non-OK responses throw `HttpError`.
84
- - `I18nService` — `translate(key, fallback?)`, `setCurrentLanguage(lang)`; language list comes from `config/languages.json`.
93
+ - `AppHttpClient` — `get/post/put/patch/delete<T>(endpoint, data?, config?)`; non-OK responses throw `HttpError`. Relative endpoints resolve against the page origin; a subclass with a base URL rejects endpoints leaving it. Build paths from values with ``path`/users/${id}` `` (encoded `/` and `\` in paths are rejected unless the API is in the `trustedAPIs` option). FormData/Blob bodies are sent as-is; 204 → `undefined`.
94
+ - `I18nService` — `translate(key, fallback?)`, `setCurrentLanguage(lang)`; language list comes from `config/languages.json`. `initI18n()` (same module) loads the translations when `i18n` is enabled and does nothing otherwise; `main.ts` awaits it before starting the app. With `i18n: false`, prod builds drop the i18n code unless app code references `I18nService`.
85
95
  - `AppPipeRegistry` — `register(name, fn)` / `apply(name, value, args)`; backs `data-pipe`.
86
96
 
87
97
  ## Styling
@@ -91,40 +101,27 @@ Tokens: `@value`, `@id`, `@class`, `@textContent`, `@event`, `@target`, `@attr:n
91
101
 
92
102
  ## Pipes
93
103
 
94
- ```
95
- // currency
96
- AppPipeRegistry.register('currency');
97
-
98
- // date
99
- AppPipeRegistry.register('date');
100
-
101
- // number (toFixed)
102
- AppPipeRegistry.register('number');
103
-
104
- // string pipes
105
- AppPipeRegistry.register('uppercase');
106
- AppPipeRegistry.register('lowercase');
107
- AppPipeRegistry.register('capitalize');
108
- AppPipeRegistry.register('capitalizeAll');
109
- AppPipeRegistry.register('truncate');
110
-
111
- // utility pipes
112
- AppPipeRegistry.register('default');
113
- AppPipeRegistry.register('json');
114
- ```
104
+ Built-in pipes, registered by `registerPipes()` in `main.ts` (args in `data-pipe="name:arg1,arg2"`):
105
+
106
+ - `currency:currency,locale` (defaults `USD`, `en-US`), `date:locale,format,time` (defaults browser language, `long`), `number:decimals` (`toFixed`, default `0`)
107
+ - `uppercase`, `lowercase`, `capitalize`, `capitalizeAll`, `truncate:length,suffix` (defaults `50`, `...`)
108
+ - `default:fallback`, `json`
109
+
110
+ Add your own with `AppPipeRegistry.register(name, (value, ...args) => string)`; args arrive as strings.
115
111
 
116
112
  ## Configuration
117
113
 
118
114
  ### `nutin.config.js`
119
115
 
120
116
  Central options/tools config (see the inline comments for details):
121
- - `i18n`, `tailwind`, `generateSEO`, `dockerPorts` — top-level feature toggles (`dockerPorts` only applies once the docker feature is added).
117
+ - `i18n`, `tailwind`, `generateSEOFiles` — top-level feature toggles.
118
+ - `dockerPorts`, `markdownSources` — options of the `docker`/`markdown` features, only present once added with `nutin-add <feature>`.
122
119
  - `builder.{sass.paths, esbuild}` — build pipeline behavior.
123
120
  - `testinNutin.{includeFramework, includeTools, includeApp, jsdomOptions}` — test discovery/environment.
124
121
 
125
122
  ### `config/seo.json`
126
123
 
127
- Consumed by builder (production only) to generate static HTML per route. Unused if `nutin.config.js -> generateSEO` is disabled.
124
+ Consumed by builder (production only) to generate static HTML per route. Unused if `nutin.config.js -> generateSEOFiles` is disabled. Dynamic routes (`/blog/:slug`) are written at their real URL from `mockParams` (or `outputPath`). Route guards run first, as an anonymous visitor: a route they block or redirect isn't prerendered nor listed in `sitemap.xml`.
128
125
 
129
126
  ### `config/languages.json`
130
127
 
@@ -132,17 +129,22 @@ Consumed by i18nService and builder. Unused if i18n is disabled.
132
129
 
133
130
  ## Security
134
131
 
135
- `SecurityHelper.sanitizeTemplate()` runs on every `render()` before `innerHTML` is set — `trustLevel` (`'strict' | 'normal' | 'trusted'`, default `'normal'`) controls how much is stripped (`<script>`/inline handlers always; `<iframe>`/`javascript:`/`data:` under `strict` only). Use `SecurityHelper.escapeHtml()` when manually interpolating strings into templates.
132
+ Write every template with the `html` tag (`import { html, raw } from` core): `templateFn = (task) => html\`<li>${task.name}</li>\``. Every `${}` is HTML-escaped, whatever the data's source (input, HTTP, localStorage). Nested `html` results are inserted as-is, and arrays are inserted item by item (no `.join('')`). For lists, use `createCatalogComponents()` (see "Children components") rather than mapping items to markup in the template: each item becomes a child component with its own events and state, kept across renders with `keepBy`. Unquoted attribute values (`title=${x}`) are quoted automatically. A plain value in tag position (`<input ${x}>`) is filtered like `raw()` there: plain attributes stay, handlers, bindings and script URLs are dropped. Wrap HTML you want rendered in `raw()`: it's parsed in place at render (works inside `<svg>`/`<table>`) and loses Nutin's binding attributes (`data-event`, `data-component`, `data-catalog`, `data-bind`, `data-i18n`, `data-pipe*`). `trustedRaw()` keeps them, for markup you wrote yourself (e.g. compiled Markdown). An untagged template literal escapes nothing, and the build warns about each one containing `${}`.
133
+
134
+ Every `render()` also sanitizes the template (`SecurityHelper.sanitizeToFragment()`) and inserts the resulting nodes directly — never re-parsed from a string. `trustLevel` (`'strict' | 'normal' | 'trusted'`, default `'normal'`) controls how much is stripped (`<script>`/`<style>`/`<link>`/`<base>`/`<meta>`, inline handlers, `srcdoc`, `javascript:` URLs, `data:` frames and SVG `<animate>`/`<set>` targeting URL attributes from `normal` up; `<iframe>`/`<object>`/`<embed>` and all `data:` URLs under `strict` only). Don't interpolate data into `data-event` args or `style`: use `data-x="${v}"` + `@dataset:x`. URL attributes (`href`, `src`, `action`, …) whose interpolated value makes them a `javascript:` URL get `about:invalid#nutin-blocked` instead, at every trust level. It is a second layer for `raw()` content, not a substitute for `html`.
136
135
 
137
136
  ## Testing (testin-nutin)
138
137
 
139
- Nutin's own hand-built test runner + jsdom - not Jest. `describe`/`it` only (**no `test()` alias**), `beforeAll`/`beforeEach`/`afterEach`/`afterAll` (one of each per suite, no stacking - inside `describe` statement, not top-level). All globals — no imports needed in test files.
138
+ Nutin's own hand-built test runner + jsdom - not Jest. Tests are plain JavaScript (`*.test.js` importing the compiled `#root/dist/...` output){{#if (eq lang "ts")}}, so they aren't type-checked{{/if}}. `describe`/`it` only (**no `test()` alias**), `beforeAll`/`beforeEach`/`afterEach`/`afterAll` (one of each per suite, no stacking - anywhere inside the `describe` statement, not top-level). `it.todo(name)` lists a pending test. All globals — no imports needed in test files.
140
139
 
141
140
  - Matchers: `toBe`, `toEqual`, `toContain`, `toBeTruthy`/`toBeFalsy`, `toBeDefined`/`toBeUndefined`, `toBeInstanceOf`, `toHaveBeenCalled`/`toHaveBeenCalledWith`, `toBeLessThan`/`toBeGreaterThan`, `toThrow` — each has a `.not` counterpart.
142
141
  - `spyOn(obj, 'method')` returns `.calls`/`.callCount`/`.lastCall`, `.andCallFake(fn)`/`.andReturn(v)`, `.restore()` — unlike mocks, calls pass through by default.
143
142
  - `tools/testin-nutin/mocks/` has ready-made `MockEventBus`/`MockHttpClient`/`MockI18n`/`MockRouter` for constructor injection.
144
- - `useFakeTimers()`/`useRealTimers()`/`advanceTimersByTime(ms)`/`runAllTimers()` for time-dependent code.
143
+ - `useFakeTimers()`/`useRealTimers()`/`advanceTimersByTime(ms)`/`runAllTimers()` for time-dependent code; `flushPromises()` for pending promises.
144
+ - `silenceConsole(method | methods, fn)` runs `fn` with those `console` methods muted (sync or async). Use it in any test that triggers an expected warning/error.
145
+ - `$(selector)`/`$$(selector)` — `document.querySelector`/`querySelectorAll` (as an array).
145
146
  - Test discovery/scope controlled by `nutin.config.js`'s `testinNutin.includeApp`/`includeFramework`.
147
+ - Tests run against the built `dist/src/index.html`, and the framework tests (`includeFramework`) mount into its `#app` element. An app without the router that has no `#app` keeps an empty `<div id="app"></div>` in `src/index.html` (not `hidden`: focus tests need it focusable), or sets `includeFramework: false`.
146
148
 
147
149
  ## Nutin
148
150
 
@@ -165,4 +167,4 @@ When modifying Nutin `core/` files:
165
167
 
166
168
  ## Documentation
167
169
 
168
- [Nutin Documentation](https://github.com/LoisKOUNINEF/nutin/tree/main/docs)
170
+ [Nutin Documentation](https://www.nutin.dev/docs)
@@ -15,7 +15,7 @@
15
15
 
16
16
  ```
17
17
  src/
18
- app/
18
+ app/ -- suggested layout: organize src/app however you like
19
19
  components/
20
20
  services/
21
21
  views/
@@ -27,23 +27,33 @@ src/
27
27
 
28
28
  tools/ -- build, development, generator and testing
29
29
 
30
- nutin.config.js -- features and configuration
30
+ nutin.config.js -- options and configuration
31
31
  ```
32
32
 
33
+ * Views **orchestrate application flow**
34
+ * Components **encapsulate UI behavior**
35
+ * Services **handle application concerns** through shared instances
36
+
33
37
  ### Imports
34
38
 
35
- Imports use **relative paths** and end with `.js` extension.
39
+ - Imports use **relative paths** and end with `.js` extension.
36
40
 
37
41
  ```js
38
42
  import { Foo } from 'relative/path/to/foo.js';
39
43
  ```
40
44
 
45
+ - Use Nutin's code barrel file (`src/core/index.js`) to import Nutin's modules.
46
+ {{#if (eq lang "ts")}}
47
+ - Nutin's types (`Nutin.ComponentConfig`, `Nutin.Routes`, `Nutin.Template`, …) live in the global `Nutin` namespace declared in `src/core/internals.d.ts`: no import needed.
48
+ {{/if}}
49
+
41
50
  ## How do I run my app?
42
51
 
43
52
  ```bash
44
53
  # Development
45
54
  {{packageManager}} run serve # build and serve
46
55
  {{packageManager}} run dev # build and serve with live reload
56
+ {{packageManager}} run dev -- --port 3000 # serve on another port (default 9090; PORT=3000 also works)
47
57
 
48
58
  # Build
49
59
  {{packageManager}} run build # build the application
@@ -52,16 +62,14 @@ import { Foo } from 'relative/path/to/foo.js';
52
62
 
53
63
  ## How do I generate components, views and services?
54
64
 
55
- Generated elements are automatically exported from the corresponding barrel file:
56
- `src/app/<components|views|services>/index.ts`.
57
-
65
+ Elements are generated in `src/app/<path>/`, so you choose the project structure. The generator stops if the target folder already exists and isn't empty.
58
66
 
59
67
  ```bash
60
- {{packageManager}} run generate <component|view|service> ELEMENT_NAME
68
+ {{packageManager}} run generate <component|view|service> PATH/TO/ELEMENT_NAME
61
69
 
62
70
  # Examples
63
- {{packageManager}} run generate component my-component # Target dir: src/app/components/my-component/
64
- {{packageManager}} run generate component path/to/my-component/ # Target dir: src/app/components/path/to/my-component/
71
+ {{packageManager}} run generate component components/hello-world # generates src/app/components/hello-world/hello-world.component.ts|html|scss
72
+ {{packageManager}} run generate view user/dashboard # generates src/app/user/dashboard/dashboard.view.ts|html|scss
65
73
  ```
66
74
 
67
75
  ## What does a typical component look like?
@@ -75,8 +83,8 @@ components/
75
83
  ```
76
84
 
77
85
  ```ts
78
- // The builder injects the component's .html template here.
79
- const templateFn = (product: Product) => `__TEMPLATE_PLACEHOLDER__`;
86
+ // The component's .html template is injected here.
87
+ const templateFn = (product: Product) => html`__TEMPLATE_PLACEHOLDER__`;
80
88
 
81
89
  export class ProductCardComponent extends Component {
82
90
  private _product: Product;
@@ -87,7 +95,7 @@ export class ProductCardComponent extends Component {
87
95
  }
88
96
 
89
97
  // Declares children mounted into the data-component / data-catalog placeholders below.
90
- public registerChildren(): ComponentConfig[] {
98
+ public registerChildren(): Nutin.ComponentConfig[] {
91
99
  // Single child, matched to <div data-component="product-details">
92
100
  const detailsComponent = {
93
101
  selector: `product-details`,
@@ -120,7 +128,8 @@ export class ProductCardComponent extends Component {
120
128
 
121
129
  ```html
122
130
  <div
123
- data-event="click:_goToProductPage:${product.id}"
131
+ data-product-id="${product.id}"
132
+ data-event="click:_goToProductPage:@dataset:productId"
124
133
  class="product-card"
125
134
  >
126
135
  <h2 class="product-card__name">${product.name}</h2>
@@ -147,12 +156,12 @@ Defining both an inline template and an external `.html` file, or neither, fails
147
156
 
148
157
  ### Main file
149
158
 
150
- `nutin.config.js` - Main Nutin configuration and tool options. See the inline comments for details.
159
+ `nutin.config.js` - Main Nutin configuration and tool options. See the inline comments and [How do I configure Nutin?](https://www.nutin.dev/docs/options-and-features/configure-nutin) for details.
160
+
161
+ This is where you can:
151
162
 
152
- - `tailwind`, `i18n`, `generateSEOFiles` — top-level features, `false` by default.
153
- See [Options and features](https://github.com/LoisKOUNINEF/nutin/tree/main/docs/OPTIONS_AND_FEATURES.md).
154
- - `builder.*` — controls build behavior.
155
- - `testinNutin.*` — controls Nutin's built-in testing toolkit.
163
+ - Toggle options ([Tailwind CSS](https://www.nutin.dev/docs/options-and-features/use-tailwind-css), [i18n](https://www.nutin.dev/docs/options-and-features/use-i18n), [SEO file generation](https://www.nutin.dev/docs/options-and-features/use-seo-file-generation)).
164
+ - Configure the builder and the built-in testing toolkit.
156
165
 
157
166
  ### Specialized files
158
167
 
@@ -171,7 +180,7 @@ See [Options and features](https://github.com/LoisKOUNINEF/nutin/tree/main/docs/
171
180
 
172
181
  You can use any testing tool you prefer for application code.
173
182
 
174
- This project includes [testin-nutin](https://github.com/LoisKOUNINEF/nutin/tree/main/docs/TESTING.md), Nutin's lightweight testing toolkit. Nutin itself is tested exclusively with it, and the tests are included in the generated project so you can:
183
+ This project includes [testin-nutin](https://www.nutin.dev/docs/testing), Nutin's small testing toolkit. Nutin itself is tested exclusively with it, and the tests are included in the generated project so you can:
175
184
  - use them as executable documentation;
176
185
  - run and modify Nutin's tests when modifying its code.
177
186
 
@@ -187,13 +196,13 @@ testinNutin: {
187
196
  includeApp: true,
188
197
  }
189
198
  ```
190
- *Note:* TestinNutin files use root imports.
199
+ *Note:* TestinNutin files are plain JavaScript and use root imports{{#if (eq lang "ts")}}: they test the compiled output, so they aren't type-checked{{/if}}.
191
200
  ```js
192
201
  import { MyModule } from '#root/dist/src/path/to/my-module.js';
193
202
  ```
194
203
 
195
204
  ## Go further
196
205
 
197
- - [Repository documentation](https://github.com/LoisKOUNINEF/nutin/tree/main/docs/API.md)
198
- - [First app tutorial](https://www.nutin.org/tutorial)
199
- - [Nutin website](https://www.nutin.org)
206
+ - [Documentation](https://www.nutin.dev/docs)
207
+ - [First app tutorial](https://www.nutin.dev/tutorial)
208
+ - [Nutin website](https://www.nutin.dev)
@@ -2,13 +2,16 @@
2
2
 
3
3
  ## Development
4
4
 
5
- - `{{packageManager}} run serve` — build and serve
6
- - `{{packageManager}} run dev` — build and serve with live reload
5
+ Requires Node.js 24 or later.
6
+
7
+ - `{{packageManager}} run serve`: build and serve
8
+ - `{{packageManager}} run dev`: build and serve with live reload
9
+ - Both serve on port 9090 by default. Pass `-- --port 3000` (or set `PORT=3000`) to change it
7
10
 
8
11
  ## Build
9
12
 
10
- - `{{packageManager}} run build` — build the application
11
- - `{{packageManager}} run build:prod` — build the application for production
13
+ - `{{packageManager}} run build`: build the application
14
+ - `{{packageManager}} run build:prod`: build the application for production
12
15
 
13
16
  ## Getting started
14
17
 
@@ -16,7 +16,7 @@ export default {
16
16
  bundle: true,
17
17
  minify: true,
18
18
  sourcemap: false,
19
- target: ['es2015'],
19
+ target: ['es2020'],
20
20
  drop: ['console', 'debugger'],
21
21
  },
22
22
  },
File without changes
@@ -1,4 +1,7 @@
1
- declare interface AppEventMap {
1
+ declare namespace Nutin {
2
+ interface AppEventMap {
2
3
  // Add app-specific event types here
3
- // 'event': { /* payload? */ }
4
- };
4
+ // 'task-saved': { taskId: number }; // emit('task-saved', { taskId }) — payload required
5
+ // 'tasks-cleared': undefined; // emit('tasks-cleared') — no payload
6
+ }
7
+ }
@@ -1,10 +1,8 @@
1
- import { RouteGuard } from "../core/index.js";
2
-
3
1
  export const Guards = {
4
2
  /**
5
3
  * Basic example
6
4
  */
7
- requireAuth: (redirectTo: string = '/login'): RouteGuard => {
5
+ requireAuth: (redirectTo: string = '/login'): Nutin.RouteGuard => {
8
6
  return () => {
9
7
  const isAuthenticated = !!localStorage.getItem('user_token');
10
8
  return isAuthenticated || redirectTo;
@@ -1,5 +1,4 @@
1
- import nutinConfig from '../../nutin.config.js';
2
- import { AppRouter, Service, I18nService, registerPipes } from '../core/index.js';
1
+ import { AppRouter, initI18n, registerPipes } from '../core/index.js';
3
2
  import { appRoutes } from './routes.js';
4
3
 
5
4
  class App {
@@ -10,12 +9,6 @@ class App {
10
9
  }
11
10
 
12
11
  document.addEventListener('DOMContentLoaded', async () => {
13
- if (nutinConfig.i18n) {
14
- await I18nService.initTranslations();
15
- }
12
+ await initI18n();
16
13
  new App();
17
14
  });
18
-
19
- window.addEventListener('beforeunload', async () => {
20
- await Service.destroyAll();
21
- });
@@ -1,7 +1,13 @@
1
- import { Routes } from '../core/index.js';
2
- import { HomeView, NotFoundView } from './views/index.js';
1
+ import { HomeView } from './views/home/home.view.js';
2
+ import { NotFoundView } from './views/not-found/not-found.view.js';
3
+ {{#if markdown}}
4
+ import { markdownRoutes } from './markdown/markdown-routes.js';
5
+ {{/if}}
3
6
 
4
- export const appRoutes: Routes = {
7
+ export const appRoutes: Nutin.Routes = {
5
8
  '/': () => new HomeView(),
9
+ {{#if markdown}}
10
+ ...markdownRoutes(),
11
+ {{/if}}
6
12
  '/404': () => new NotFoundView(),
7
13
  }
File without changes
@@ -3,5 +3,5 @@
3
3
  <span style="font-style:italic;font-size:clamp(.6875rem,2vw,1rem);">nutin v{{version}}</span>
4
4
  <img src="/assets/images/logo-trans.avif" width=250 height=250 alt="nutin's logo. A frame opened on the top left through which a plant grows.">
5
5
  <div style="color:#C56648;font-size:clamp(1.125rem,4vw,1.5rem);">Happy Coding 🎉</div>
6
- <div style="font-size:clamp(.75rem,2vw,1rem);font-style:italic;position:fixed;bottom:0;">so lightweight it's <span style="font-weight:bold;">nutin</span>.</div>
6
+ <div style="font-size:clamp(.75rem,2vw,1rem);font-style:italic;position:fixed;bottom:0;padding-bottom:.2rem;">it's <span style="font-weight:bold;"><a style="text-decoration:none;color:#6C8359;" href="https://nutin.dev">nutin</a></span>.</div>
7
7
  </div>
@@ -1,6 +1,6 @@
1
- import { View } from '../../../core/index.js';
1
+ import { View, html } from '../../../core/index.js';
2
2
 
3
- const template = `__TEMPLATE_PLACEHOLDER__`;
3
+ const template = html`__TEMPLATE_PLACEHOLDER__`;
4
4
 
5
5
  export class HomeView extends View {
6
6
  constructor() {
@@ -1,6 +1,6 @@
1
- import { Navigation, View } from '../../../core/index.js';
1
+ import { Navigation, View, html } from '../../../core/index.js';
2
2
 
3
- const template = `__TEMPLATE_PLACEHOLDER__`;
3
+ const template = html`__TEMPLATE_PLACEHOLDER__`;
4
4
 
5
5
  export class NotFoundView extends View {
6
6
  constructor() {