@madojs/mado 0.11.1 → 0.13.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 (298) hide show
  1. package/AGENTS.md +116 -32
  2. package/CHANGELOG.md +216 -1
  3. package/README.md +158 -173
  4. package/dist/src/component.d.ts +6 -3
  5. package/dist/src/component.d.ts.map +1 -0
  6. package/dist/src/component.js +163 -36
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/context.d.ts +13 -32
  9. package/dist/src/context.d.ts.map +1 -0
  10. package/dist/src/context.js +73 -48
  11. package/dist/src/context.js.map +1 -1
  12. package/dist/src/css.d.ts +1 -0
  13. package/dist/src/css.d.ts.map +1 -0
  14. package/dist/src/css.js +34 -5
  15. package/dist/src/css.js.map +1 -1
  16. package/dist/src/devtools-hook.d.ts +14 -0
  17. package/dist/src/devtools-hook.d.ts.map +1 -0
  18. package/dist/src/devtools-hook.js +27 -0
  19. package/dist/src/devtools-hook.js.map +1 -0
  20. package/dist/src/devtools.d.ts +24 -22
  21. package/dist/src/devtools.d.ts.map +1 -0
  22. package/dist/src/devtools.js +230 -58
  23. package/dist/src/devtools.js.map +1 -1
  24. package/dist/src/diagnostics.d.ts +12 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +68 -8
  27. package/dist/src/diagnostics.js.map +1 -1
  28. package/dist/src/each.d.ts +1 -5
  29. package/dist/src/each.d.ts.map +1 -0
  30. package/dist/src/each.js +0 -8
  31. package/dist/src/each.js.map +1 -1
  32. package/dist/src/forms.d.ts +27 -96
  33. package/dist/src/forms.d.ts.map +1 -0
  34. package/dist/src/forms.js +184 -394
  35. package/dist/src/forms.js.map +1 -1
  36. package/dist/src/head.d.ts +7 -6
  37. package/dist/src/head.d.ts.map +1 -0
  38. package/dist/src/head.js +8 -7
  39. package/dist/src/head.js.map +1 -1
  40. package/dist/src/html/bindings.d.ts +1 -0
  41. package/dist/src/html/bindings.d.ts.map +1 -0
  42. package/dist/src/html/bindings.js.map +1 -1
  43. package/dist/src/html/parser.d.ts +4 -5
  44. package/dist/src/html/parser.d.ts.map +1 -0
  45. package/dist/src/html/parser.js +64 -15
  46. package/dist/src/html/parser.js.map +1 -1
  47. package/dist/src/html/template-types.d.ts +1 -0
  48. package/dist/src/html/template-types.d.ts.map +1 -0
  49. package/dist/src/html/template-types.js.map +1 -1
  50. package/dist/src/html/template.d.ts +5 -1
  51. package/dist/src/html/template.d.ts.map +1 -0
  52. package/dist/src/html/template.js +102 -13
  53. package/dist/src/html/template.js.map +1 -1
  54. package/dist/src/index.d.ts +15 -12
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +18 -10
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/json.d.ts +3 -0
  59. package/dist/src/json.d.ts.map +1 -0
  60. package/dist/src/json.js +14 -0
  61. package/dist/src/json.js.map +1 -0
  62. package/dist/src/lifecycle.d.ts +1 -0
  63. package/dist/src/lifecycle.d.ts.map +1 -0
  64. package/dist/src/lifecycle.js +3 -2
  65. package/dist/src/lifecycle.js.map +1 -1
  66. package/dist/src/page.d.ts +63 -28
  67. package/dist/src/page.d.ts.map +1 -0
  68. package/dist/src/page.js +5 -0
  69. package/dist/src/page.js.map +1 -1
  70. package/dist/src/persisted.d.ts +6 -1
  71. package/dist/src/persisted.d.ts.map +1 -0
  72. package/dist/src/persisted.js +17 -11
  73. package/dist/src/persisted.js.map +1 -1
  74. package/dist/src/resource.d.ts +2 -1
  75. package/dist/src/resource.d.ts.map +1 -0
  76. package/dist/src/resource.js +128 -50
  77. package/dist/src/resource.js.map +1 -1
  78. package/dist/src/router/base.d.ts +66 -0
  79. package/dist/src/router/base.d.ts.map +1 -0
  80. package/dist/src/router/base.js +168 -0
  81. package/dist/src/router/base.js.map +1 -0
  82. package/dist/src/router/manifest.d.ts +1 -0
  83. package/dist/src/router/manifest.d.ts.map +1 -0
  84. package/dist/src/router/manifest.js +156 -171
  85. package/dist/src/router/manifest.js.map +1 -1
  86. package/dist/src/router/match.d.ts +17 -2
  87. package/dist/src/router/match.d.ts.map +1 -0
  88. package/dist/src/router/match.js +60 -6
  89. package/dist/src/router/match.js.map +1 -1
  90. package/dist/src/router/navigation.d.ts +1 -0
  91. package/dist/src/router/navigation.d.ts.map +1 -0
  92. package/dist/src/router/navigation.js +58 -9
  93. package/dist/src/router/navigation.js.map +1 -1
  94. package/dist/src/signal.d.ts +1 -0
  95. package/dist/src/signal.d.ts.map +1 -0
  96. package/dist/src/signal.js +46 -17
  97. package/dist/src/signal.js.map +1 -1
  98. package/dist/src/static-runtime.d.ts +82 -0
  99. package/dist/src/static-runtime.d.ts.map +1 -0
  100. package/dist/src/static-runtime.js +209 -0
  101. package/dist/src/static-runtime.js.map +1 -0
  102. package/dist/src/vite/index.d.ts +32 -3
  103. package/dist/src/vite/index.d.ts.map +1 -0
  104. package/dist/src/vite/index.js +49 -17
  105. package/dist/src/vite/index.js.map +1 -1
  106. package/docs/README.md +5 -8
  107. package/docs/architecture/adr/0001-browser-static-snapshots.md +132 -0
  108. package/docs/architecture/v1-roadmap.md +19 -0
  109. package/docs/en/00-the-mado-way.md +1 -1
  110. package/docs/en/01-quickstart.md +183 -0
  111. package/docs/en/10-pages-and-components.md +279 -0
  112. package/docs/en/11-templates-and-signals.md +211 -0
  113. package/docs/en/12-routing.md +229 -0
  114. package/docs/en/13-data.md +227 -0
  115. package/docs/en/14-forms.md +91 -0
  116. package/docs/en/15-static-snapshots.md +181 -0
  117. package/docs/en/{10-app-architecture.md → 16-app-architecture.md} +56 -4
  118. package/docs/en/{13-deployment.md → 20-deployment.md} +79 -17
  119. package/docs/en/{15-error-handling.md → 21-error-handling.md} +9 -6
  120. package/docs/en/{14-testing.md → 22-testing.md} +6 -5
  121. package/docs/en/23-cookbook.md +292 -0
  122. package/docs/en/24-devtools-and-diagnostics.md +45 -0
  123. package/docs/en/{18-api-freeze-map.md → 30-api-freeze-map.md} +17 -9
  124. package/docs/en/{20-v1-stability.md → 32-v1-stability.md} +5 -5
  125. package/docs/en/33-migration-0.12-0.13.md +56 -0
  126. package/docs/en/40-llm-guide.md +774 -0
  127. package/docs/en/{06-for-backenders.md → 41-for-backenders.md} +28 -25
  128. package/docs/en/{05-why-mado.md → 42-why-mado.md} +3 -3
  129. package/docs/en/README.md +99 -27
  130. package/docs/recipes/nginx/Containerfile +26 -0
  131. package/docs/recipes/nginx/nginx.conf +42 -0
  132. package/llms.txt +263 -210
  133. package/package.json +51 -23
  134. package/scripts/clean.mjs +8 -0
  135. package/scripts/cli/generate.mjs +57 -10
  136. package/scripts/cli/help.mjs +13 -5
  137. package/scripts/cli/index.mjs +31 -7
  138. package/scripts/cli/init.mjs +19 -19
  139. package/scripts/cli/release.mjs +38 -83
  140. package/scripts/cli/run.mjs +5 -3
  141. package/scripts/logger.mjs +117 -0
  142. package/scripts/output-guard.mjs +128 -0
  143. package/scripts/preview.mjs +105 -24
  144. package/scripts/static/browser.mjs +695 -0
  145. package/scripts/static/discover.mjs +284 -0
  146. package/scripts/static/output.mjs +141 -0
  147. package/scripts/static/serialize.mjs +212 -0
  148. package/scripts/static/server.mjs +167 -0
  149. package/scripts/static.mjs +205 -0
  150. package/starters/default/README.md +57 -55
  151. package/starters/default/package.json +6 -12
  152. package/starters/default/src/app.routes.ts +20 -33
  153. package/starters/default/src/components/feature-card.component.ts +40 -0
  154. package/starters/default/src/components/live-counter.component.ts +57 -0
  155. package/starters/default/src/content/guides.ts +55 -0
  156. package/starters/default/src/main.ts +9 -13
  157. package/starters/default/src/pages/app.page.ts +41 -0
  158. package/starters/default/src/pages/guide.page.ts +40 -0
  159. package/starters/default/src/pages/home.page.ts +49 -0
  160. package/starters/default/src/pages/not-found.page.ts +18 -0
  161. package/starters/default/src/styles/document.css +39 -0
  162. package/starters/default/src/styles/reset.css +37 -0
  163. package/starters/default/src/styles/tokens.css +30 -0
  164. package/starters/default/src/styles.d.ts +3 -1
  165. package/starters/default/src/vite-env.d.ts +1 -1
  166. package/starters/default/tsconfig.node.json +13 -0
  167. package/starters/default/vite.config.ts +14 -4
  168. package/starters/modular/README.md +142 -0
  169. package/starters/modular/index.html +13 -0
  170. package/starters/modular/package.json +30 -0
  171. package/starters/modular/src/app.routes.ts +39 -0
  172. package/starters/{default → modular}/src/layouts/app-shell.layout.ts +9 -7
  173. package/starters/{default → modular}/src/layouts/auth-shell.layout.ts +3 -3
  174. package/starters/modular/src/main.ts +16 -0
  175. package/starters/{default → modular}/src/modules/auth/login.page.ts +5 -12
  176. package/starters/{default → modular}/src/modules/billing/pages/invoice-detail.page.ts +3 -4
  177. package/starters/{default → modular}/src/modules/billing/pages/invoices-list.page.ts +4 -4
  178. package/starters/{default → modular}/src/modules/home/home.page.ts +5 -7
  179. package/starters/modular/src/modules/home/not-found.page.ts +14 -0
  180. package/starters/{default → modular}/src/shared/styles/reset.css +21 -1
  181. package/starters/modular/src/styles.d.ts +1 -0
  182. package/starters/modular/src/vite-env.d.ts +1 -0
  183. package/starters/modular/tsconfig.json +33 -0
  184. package/starters/modular/tsconfig.node.json +13 -0
  185. package/starters/modular/vite.config.ts +132 -0
  186. package/TODO.md +0 -79
  187. package/dist/src/lazy.d.ts +0 -38
  188. package/dist/src/lazy.js +0 -73
  189. package/dist/src/lazy.js.map +0 -1
  190. package/docs/en/01-routing.md +0 -152
  191. package/docs/en/02-project-layout.md +0 -124
  192. package/docs/en/03-static-bake.md +0 -249
  193. package/docs/en/04-ide-setup.md +0 -162
  194. package/docs/en/07-llm-pitfalls.md +0 -724
  195. package/docs/en/08-llm-zero-history-test.md +0 -56
  196. package/docs/en/09-shadow-vs-light-dom.md +0 -174
  197. package/docs/en/11-layouts.md +0 -113
  198. package/docs/en/12-auth-and-api.md +0 -124
  199. package/docs/en/16-bake-cookbook.md +0 -100
  200. package/docs/en/17-shadow-dom-forms.md +0 -192
  201. package/docs/fr/00-the-mado-way.md +0 -85
  202. package/docs/fr/01-routing.md +0 -120
  203. package/docs/fr/02-project-layout.md +0 -84
  204. package/docs/fr/03-static-bake.md +0 -289
  205. package/docs/fr/04-ide-setup.md +0 -162
  206. package/docs/fr/05-why-mado.md +0 -193
  207. package/docs/fr/06-for-backenders.md +0 -438
  208. package/docs/fr/07-llm-pitfalls.md +0 -625
  209. package/docs/fr/08-llm-zero-history-test.md +0 -38
  210. package/docs/fr/09-shadow-vs-light-dom.md +0 -65
  211. package/docs/fr/10-app-architecture.md +0 -138
  212. package/docs/fr/11-layouts.md +0 -47
  213. package/docs/fr/12-auth-and-api.md +0 -76
  214. package/docs/fr/13-deployment.md +0 -57
  215. package/docs/fr/14-testing.md +0 -41
  216. package/docs/fr/15-error-handling.md +0 -50
  217. package/docs/fr/16-bake-cookbook.md +0 -88
  218. package/docs/fr/17-shadow-dom-forms.md +0 -196
  219. package/docs/fr/18-api-freeze-map.md +0 -63
  220. package/docs/fr/19-reactivity-ordering.md +0 -97
  221. package/docs/fr/20-v1-stability.md +0 -88
  222. package/docs/fr/README.md +0 -27
  223. package/docs/ru/00-the-mado-way.md +0 -84
  224. package/docs/ru/01-routing.md +0 -119
  225. package/docs/ru/02-project-layout.md +0 -84
  226. package/docs/ru/03-static-bake.md +0 -250
  227. package/docs/ru/04-ide-setup.md +0 -144
  228. package/docs/ru/05-why-mado.md +0 -193
  229. package/docs/ru/06-for-backenders.md +0 -428
  230. package/docs/ru/07-llm-pitfalls.md +0 -624
  231. package/docs/ru/08-llm-zero-history-test.md +0 -57
  232. package/docs/ru/09-shadow-vs-light-dom.md +0 -63
  233. package/docs/ru/10-app-architecture.md +0 -152
  234. package/docs/ru/11-layouts.md +0 -47
  235. package/docs/ru/12-auth-and-api.md +0 -75
  236. package/docs/ru/13-deployment.md +0 -66
  237. package/docs/ru/14-testing.md +0 -50
  238. package/docs/ru/15-error-handling.md +0 -56
  239. package/docs/ru/16-bake-cookbook.md +0 -95
  240. package/docs/ru/17-shadow-dom-forms.md +0 -193
  241. package/docs/ru/18-api-freeze-map.md +0 -64
  242. package/docs/ru/19-reactivity-ordering.md +0 -95
  243. package/docs/ru/20-v1-stability.md +0 -82
  244. package/docs/ru/README.md +0 -25
  245. package/docs/uk/00-the-mado-way.md +0 -82
  246. package/docs/uk/01-routing.md +0 -76
  247. package/docs/uk/02-project-layout.md +0 -73
  248. package/docs/uk/03-static-bake.md +0 -48
  249. package/docs/uk/04-ide-setup.md +0 -26
  250. package/docs/uk/05-why-mado.md +0 -34
  251. package/docs/uk/06-for-backenders.md +0 -55
  252. package/docs/uk/07-llm-pitfalls.md +0 -145
  253. package/docs/uk/08-llm-zero-history-test.md +0 -34
  254. package/docs/uk/09-shadow-vs-light-dom.md +0 -58
  255. package/docs/uk/10-app-architecture.md +0 -97
  256. package/docs/uk/11-layouts.md +0 -47
  257. package/docs/uk/12-auth-and-api.md +0 -70
  258. package/docs/uk/13-deployment.md +0 -40
  259. package/docs/uk/14-testing.md +0 -34
  260. package/docs/uk/15-error-handling.md +0 -32
  261. package/docs/uk/16-bake-cookbook.md +0 -36
  262. package/docs/uk/17-shadow-dom-forms.md +0 -193
  263. package/docs/uk/18-api-freeze-map.md +0 -61
  264. package/docs/uk/19-reactivity-ordering.md +0 -95
  265. package/docs/uk/20-v1-stability.md +0 -83
  266. package/docs/uk/README.md +0 -27
  267. package/scripts/bake.mjs +0 -570
  268. package/scripts/package-smoke.mjs +0 -77
  269. package/scripts/release-notes.mjs +0 -66
  270. package/scripts/size-budget.mjs +0 -91
  271. package/starters/default/src/modules/home/not-found.page.ts +0 -11
  272. package/starters/default/src/shared/ui/x-button.component.ts +0 -49
  273. /package/docs/en/{19-reactivity-ordering.md → 31-reactivity-ordering.md} +0 -0
  274. /package/starters/{default → modular}/.editorconfig +0 -0
  275. /package/starters/{default → modular}/eslint.config.mjs +0 -0
  276. /package/starters/{default → modular}/public/favicon.svg +0 -0
  277. /package/starters/{default → modular}/src/modules/auth/_contracts/auth-api.types.ts +0 -0
  278. /package/starters/{default → modular}/src/modules/auth/auth.connector.ts +0 -0
  279. /package/starters/{default → modular}/src/modules/auth/auth.guard.ts +0 -0
  280. /package/starters/{default → modular}/src/modules/auth/auth.public.ts +0 -0
  281. /package/starters/{default → modular}/src/modules/auth/auth.routes.ts +0 -0
  282. /package/starters/{default → modular}/src/modules/auth/auth.service.ts +0 -0
  283. /package/starters/{default → modular}/src/modules/auth/auth.types.ts +0 -0
  284. /package/starters/{default → modular}/src/modules/billing/_contracts/stripe.types.ts +0 -0
  285. /package/starters/{default → modular}/src/modules/billing/api/stripe.connector.ts +0 -0
  286. /package/starters/{default → modular}/src/modules/billing/billing.public.ts +0 -0
  287. /package/starters/{default → modular}/src/modules/billing/billing.routes.ts +0 -0
  288. /package/starters/{default → modular}/src/modules/billing/billing.types.ts +0 -0
  289. /package/starters/{default → modular}/src/modules/billing/components/invoice-status-badge.component.ts +0 -0
  290. /package/starters/{default → modular}/src/modules/billing/data/invoices.resource.ts +0 -0
  291. /package/starters/{default → modular}/src/shared/http/http-client.ts +0 -0
  292. /package/starters/{default → modular}/src/shared/http/http-error.ts +0 -0
  293. /package/starters/{default → modular}/src/shared/http/interceptors.ts +0 -0
  294. /package/starters/{default → modular}/src/shared/lib/format-date.ts +0 -0
  295. /package/starters/{default → modular}/src/shared/styles/content.css +0 -0
  296. /package/starters/{default → modular}/src/shared/styles/shell.css +0 -0
  297. /package/starters/{default → modular}/src/shared/styles/tokens.css +0 -0
  298. /package/starters/{default → modular}/src/shared/ui/x-spinner.component.ts +0 -0
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@madojs/mado",
3
- "version": "0.11.1",
4
- "description": "Mado — a calm browser-native SPA framework for internal tools, admin panels and business apps. Routing, forms, state and data fetching without frontend infrastructure overhead.",
3
+ "version": "0.13.0",
4
+ "description": "A calm native-first web framework for sites and apps. Web Components, signals, routing, data, forms and browser-rendered static snapshots. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "framework",
9
- "spa",
9
+ "native-first",
10
10
  "web-components",
11
+ "declarative-shadow-dom",
11
12
  "signals",
12
13
  "tagged-templates",
13
14
  "router",
14
15
  "vite",
15
- "tsc",
16
- "minimal",
17
- "lit-alternative",
18
- "solid-alternative"
16
+ "static-snapshot",
17
+ "spa",
18
+ "minimal"
19
19
  ],
20
20
  "homepage": "https://github.com/madojs/mado#readme",
21
21
  "repository": {
@@ -29,7 +29,6 @@
29
29
  "mado": "./scripts/cli.mjs"
30
30
  },
31
31
  "main": "./dist/src/index.js",
32
- "module": "./dist/src/index.js",
33
32
  "types": "./dist/src/index.d.ts",
34
33
  "exports": {
35
34
  ".": {
@@ -47,49 +46,78 @@
47
46
  },
48
47
  "files": [
49
48
  "dist/src",
50
- "scripts",
51
- "templates",
49
+ "scripts/cli.mjs",
50
+ "scripts/_config.mjs",
51
+ "scripts/clean.mjs",
52
+ "scripts/logger.mjs",
53
+ "scripts/output-guard.mjs",
54
+ "scripts/preview.mjs",
55
+ "scripts/static.mjs",
56
+ "scripts/vite.default.mjs",
57
+ "scripts/cli",
58
+ "scripts/static",
52
59
  "starters",
53
60
  "README.md",
54
61
  "LICENSE",
55
62
  "CHANGELOG.md",
56
- "TODO.md",
57
63
  "AGENTS.md",
58
64
  "llms.txt",
59
- "docs/**/*.md"
65
+ "docs/README.md",
66
+ "docs/en/**/*.md",
67
+ "docs/architecture/**/*.md",
68
+ "docs/recipes/**/*"
60
69
  ],
70
+ "sideEffects": [
71
+ "./dist/src/devtools.js"
72
+ ],
73
+ "publishConfig": {
74
+ "access": "public",
75
+ "provenance": true
76
+ },
77
+ "packageManager": "npm@11.16.0",
61
78
  "scripts": {
62
79
  "build": "node scripts/cli.mjs build",
63
80
  "watch": "node scripts/cli.mjs watch",
64
81
  "dev": "node scripts/cli.mjs dev",
65
- "bake": "node scripts/cli.mjs bake",
82
+ "static": "node scripts/cli.mjs static",
66
83
  "preview": "node scripts/cli.mjs preview",
67
84
  "new": "node scripts/cli.mjs new",
68
85
  "size": "node scripts/size-budget.mjs",
69
86
  "package:smoke": "node scripts/package-smoke.mjs",
87
+ "docs:lint": "node scripts/docs-lint.mjs && node node_modules/typescript/bin/tsc --noEmit -p tsconfig.docs.json",
88
+ "api:check": "node scripts/api-check.mjs",
89
+ "benchmark:static": "node scripts/benchmark-static.mjs",
70
90
  "test": "node scripts/cli.mjs test",
71
91
  "typecheck": "node scripts/cli.mjs typecheck",
72
- "clean": "rm -rf dist out",
73
- "prepublishOnly": "npm run clean && npm run build && npm test"
92
+ "clean": "node scripts/clean.mjs",
93
+ "verify:release": "npm run clean && npm test && npm run docs:lint && npm run api:check && npm run size && npm run package:smoke && npm audit --audit-level=high",
94
+ "prepack": "node scripts/clean.mjs --dist-only && npm run build",
95
+ "prepublishOnly": "npm run verify:release"
74
96
  },
75
97
  "devDependencies": {
76
- "@types/node": "^20.19.0",
77
- "esbuild": "^0.28.0",
78
- "lightningcss": "^1.32.0",
79
- "linkedom": "^0.18.12",
80
- "playwright-core": "^1.47.0",
81
- "typescript": "^6.0.3",
82
- "vite": "^8.0.16"
98
+ "@types/node": "^22.12.0",
99
+ "esbuild": "^0.28.1",
100
+ "linkedom": "^0.18.13",
101
+ "playwright-core": "^1.61.1",
102
+ "typescript": "^7.0.2",
103
+ "vite": "^8.1.4"
83
104
  },
84
105
  "peerDependencies": {
106
+ "playwright-core": "^1.61.1",
85
107
  "vite": "^8.0.0"
86
108
  },
87
109
  "peerDependenciesMeta": {
110
+ "playwright-core": {
111
+ "optional": true
112
+ },
88
113
  "vite": {
89
114
  "optional": true
90
115
  }
91
116
  },
92
117
  "engines": {
93
- "node": ">=20"
118
+ "node": ">=22.12"
119
+ },
120
+ "allowScripts": {
121
+ "esbuild@0.28.1": true
94
122
  }
95
123
  }
@@ -0,0 +1,8 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+
4
+ const root = resolve(import.meta.dirname, "..");
5
+ const targets = process.argv.includes("--dist-only") ? ["dist"] : ["dist", "out"];
6
+ for (const target of targets) {
7
+ await rm(resolve(root, target), { recursive: true, force: true });
8
+ }
@@ -1,6 +1,9 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { access, mkdir, writeFile } from "node:fs/promises";
2
3
  import { dirname, join, relative } from "node:path";
3
4
 
5
+ import { logger } from "../logger.mjs";
6
+
4
7
  const KINDS = [
5
8
  "module",
6
9
  "page",
@@ -13,6 +16,35 @@ const KINDS = [
13
16
  "layout",
14
17
  ];
15
18
 
19
+ /**
20
+ * Generators support two starter shapes:
21
+ *
22
+ * modular — src/modules/<name>/<...> (default behaviour)
23
+ * universal— src/pages, src/components, src/content (the default starter)
24
+ *
25
+ * Detection is structural: a project with `src/modules/` is treated as
26
+ * modular; otherwise the universal layout is assumed. This lets users of
27
+ * either starter run `mado new` without configuring anything, and keeps
28
+ * the modular contract intact for projects that opt into it.
29
+ */
30
+ function detectStarterShape(ctx) {
31
+ if (existsSync(join(ctx.projectRoot, "src/modules"))) return "modular";
32
+ if (existsSync(join(ctx.projectRoot, "src/pages"))) return "universal";
33
+ // No explicit hint — fall back to modular, which matches the
34
+ // historical behaviour and is documented in the generator help.
35
+ return "modular";
36
+ }
37
+
38
+ /** Generators that only make sense inside the modular starter. */
39
+ const MODULAR_ONLY = new Set([
40
+ "module",
41
+ "connector",
42
+ "resource",
43
+ "service",
44
+ "form",
45
+ "guard",
46
+ ]);
47
+
16
48
  export async function runNew(ctx, args) {
17
49
  const [kind, target] = args;
18
50
  if (!kind || !target) {
@@ -20,6 +52,13 @@ export async function runNew(ctx, args) {
20
52
  process.exit(1);
21
53
  }
22
54
 
55
+ const shape = detectStarterShape(ctx);
56
+ if (shape === "universal" && MODULAR_ONLY.has(kind)) {
57
+ logger.error("mado", "generator-shape", `'${kind}' is not available in the universal starter`);
58
+ logger.info("mado", "generator-hint", "Create src/modules/<name>/ or run: mado init my-app --starter modular");
59
+ process.exit(1);
60
+ }
61
+
23
62
  const generators = {
24
63
  module: scaffoldModule,
25
64
  page: scaffoldPage,
@@ -34,12 +73,12 @@ export async function runNew(ctx, args) {
34
73
 
35
74
  const fn = generators[kind];
36
75
  if (!fn) {
37
- console.error(`[mado] unknown generator: ${kind}`);
38
- console.error(`[mado] available generators: ${KINDS.join(", ")}`);
76
+ logger.error("mado", "unknown-generator", `unknown generator: ${kind}`);
77
+ logger.info("mado", "available-generators", `available generators: ${KINDS.join(", ")}`);
39
78
  process.exit(1);
40
79
  }
41
80
 
42
- await fn(ctx, normalizeTarget(target));
81
+ await fn(ctx, normalizeTarget(target), shape);
43
82
  }
44
83
 
45
84
  function printUsage() {
@@ -86,8 +125,11 @@ async function scaffoldModule(ctx, name) {
86
125
  );
87
126
  }
88
127
 
89
- async function scaffoldPage(ctx, target) {
90
- const file = moduleFile(ctx, target, ".page.ts");
128
+ async function scaffoldPage(ctx, target, shape) {
129
+ const file =
130
+ shape === "universal"
131
+ ? join(srcDir(ctx), "pages", `${leafName(target)}.page.ts`)
132
+ : moduleFile(ctx, target, ".page.ts");
91
133
  const name = leafName(target);
92
134
  await writeOnce(
93
135
  file,
@@ -219,14 +261,19 @@ async function scaffoldForm(ctx, target) {
219
261
  // Call inside a page view: const form = ${useName}();
220
262
  export const ${useName} = () =>
221
263
  useForm({
222
- // name: { required: true },
264
+ initial: {
265
+ // name: "",
266
+ },
223
267
  });
224
268
  `,
225
269
  );
226
270
  }
227
271
 
228
- async function scaffoldComponent(ctx, target) {
229
- const file = moduleFile(ctx, target, ".component.ts");
272
+ async function scaffoldComponent(ctx, target, shape) {
273
+ const file =
274
+ shape === "universal"
275
+ ? join(srcDir(ctx), "components", `${leafName(target)}.component.ts`)
276
+ : moduleFile(ctx, target, ".component.ts");
230
277
  const name = leafName(target);
231
278
  const tag = name.includes("-") ? name : `x-${name}`;
232
279
  await writeOnce(
@@ -300,7 +347,7 @@ function moduleFile(ctx, target, suffix) {
300
347
 
301
348
  async function writeOnce(path, content) {
302
349
  if (await exists(path)) {
303
- console.error(`[mado] file already exists: ${path}`);
350
+ logger.error("mado", "file-exists", `file already exists: ${path}`);
304
351
  process.exit(2);
305
352
  }
306
353
  await mkdir(dirname(path), { recursive: true });
@@ -333,7 +380,7 @@ function leafName(target) {
333
380
 
334
381
  function assertSingleSegment(label, name) {
335
382
  if (name.includes("/")) {
336
- console.error(`[mado] ${label.toLowerCase()} name must be a single path segment`);
383
+ logger.error("mado", "invalid-name", `${label.toLowerCase()} name must be a single path segment`);
337
384
  process.exit(1);
338
385
  }
339
386
  }
@@ -1,20 +1,26 @@
1
1
  export function printHelp(ctx) {
2
2
  const mode = ctx.isRepo ? "repo-mode (framework repository)" : "app-mode";
3
+ const buildLine = ctx.isRepo
4
+ ? " mado build tsc package compile (writes internal dist/)"
5
+ : " mado build Vite production SPA build -> out/";
3
6
  console.log(`mado commands (${mode}):
4
7
 
5
8
  Project lifecycle:
6
9
  mado init <name> [--starter default] [--force]
7
10
  scaffold a new app
8
11
  mado dev Vite dev server
9
- mado build tsc package compile (writes internal dist/)
12
+ ${buildLine}
10
13
  mado typecheck tsc --noEmit
11
14
  mado test run unit tests
12
15
 
13
16
  Production:
14
- mado bake [--entry <file>] [--template <html>] [--out <dir>] [--base-url <url>]
15
- prerender baked routes -> out/
16
- mado release typecheck + vite build + bake -> out/
17
- mado preview serve exactly out/ locally
17
+ mado static [--entry <file>] [--out <dir>] [--base-url <url>]
18
+ [--browser-channel chrome | --browser-path <path>]
19
+ browser-snapshot static routes -> out/
20
+ (low-level; prefer \`mado release\`)
21
+ mado release typecheck + vite build + static snapshots
22
+ + deployment files -> out/
23
+ mado preview serve exactly out/ locally (production-like)
18
24
 
19
25
  Generators:
20
26
  mado new <module|page|connector|resource|service|form|component|guard|layout> <path>
@@ -22,6 +28,8 @@ export function printHelp(ctx) {
22
28
  Configuration:
23
29
  Put app/dev/build settings in vite.config.ts.
24
30
  Mado CLI flags are explicit per command.
31
+ --log-level debug|info|warn|error|silent
32
+ --log-format pretty|plain|json --no-color
25
33
 
26
34
  Docs: README.md and docs/en/README.md.`);
27
35
  }
@@ -1,8 +1,9 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import { detectContext } from "../_config.mjs";
6
+ import { configureLogger, logger } from "../logger.mjs";
6
7
  import { runInit } from "./init.mjs";
7
8
  import { runNew } from "./generate.mjs";
8
9
  import { printHelp } from "./help.mjs";
@@ -12,6 +13,7 @@ import { hasFlag, listTestFiles, run, runNodeBin, runNodeScript, runVite } from
12
13
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
13
14
 
14
15
  export async function main(argv) {
16
+ argv = configureLogger(argv);
15
17
  const projectRoot = resolve(process.cwd());
16
18
  const context = detectContext(projectRoot);
17
19
  const packageJson = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
@@ -31,18 +33,35 @@ export async function main(argv) {
31
33
  await runInit(ctx, args);
32
34
  break;
33
35
  case "build":
34
- await runNodeBin(ctx, "typescript/bin/tsc", args);
36
+ // Framework repo bootstraps via `tsc` (so the published package
37
+ // ships compiled .js). Every other context — user apps, the
38
+ // starter — gets a production Vite build of the deployable SPA.
39
+ if (ctx.isRepo) {
40
+ await runNodeBin(ctx, "typescript/bin/tsc", args);
41
+ } else {
42
+ const outputArgs = hasFlag(args, "--outDir") ? args : ["--outDir", "out", ...args];
43
+ await runVite(ctx, ["build", ...outputArgs], { defaultConfig: true });
44
+ }
35
45
  break;
36
46
  case "watch":
37
- await runNodeBin(ctx, "typescript/bin/tsc", ["-w", ...args]);
47
+ if (ctx.isRepo) {
48
+ await runNodeBin(ctx, "typescript/bin/tsc", ["-w", ...args]);
49
+ } else {
50
+ await runVite(ctx, ["build", "--watch", ...args], { defaultConfig: true });
51
+ }
38
52
  break;
39
- case "typecheck":
53
+ case "typecheck": {
40
54
  await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit", ...args]);
55
+ const nodeConfig = join(ctx.projectRoot, "tsconfig.node.json");
56
+ if (existsSync(nodeConfig) && !hasFlag(args, "--project", "-p")) {
57
+ await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit", "-p", nodeConfig]);
58
+ }
41
59
  break;
60
+ }
42
61
  case "test": {
43
62
  await runNodeBin(ctx, "typescript/bin/tsc", []);
44
63
  const files = await listTestFiles(projectRoot);
45
- await run(process.execPath, ["--test", "--test-timeout=30000", ...files, ...args], {
64
+ await run(process.execPath, ["--test", "--test-timeout=120000", ...files, ...args], {
46
65
  cwd: projectRoot,
47
66
  });
48
67
  break;
@@ -55,7 +74,12 @@ export async function main(argv) {
55
74
  );
56
75
  break;
57
76
  case "bake":
58
- await runNodeScript(ctx, "scripts/bake.mjs", args);
77
+ logger.error("mado", "command-removed", "`mado bake` was removed.");
78
+ logger.info("mado", "replacement", "Use `mado static`, or run the complete pipeline with `mado release`.");
79
+ process.exit(1);
80
+ break;
81
+ case "static":
82
+ await runNodeScript(ctx, "scripts/static.mjs", args);
59
83
  break;
60
84
  case "preview":
61
85
  await runNodeScript(ctx, "scripts/preview.mjs", args);
@@ -72,7 +96,7 @@ export async function main(argv) {
72
96
  printHelp(ctx);
73
97
  break;
74
98
  default:
75
- console.error(`[mado] unknown command: ${command}`);
99
+ logger.error("mado", "unknown-command", `unknown command: ${command}`);
76
100
  printHelp(ctx);
77
101
  process.exit(1);
78
102
  }
@@ -3,37 +3,43 @@ import { copyFile, cp, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { join, resolve } from "node:path";
4
4
 
5
5
  import { parseFlags } from "../_config.mjs";
6
+ import { logger } from "../logger.mjs";
6
7
 
7
- const STARTERS = ["default"];
8
+ // `default` is the universal starter: minimal, runnable, zero backend.
9
+ // `modular` is the long-lived business-app reference architecture:
10
+ // auth, billing, guards, layouts, services, connectors, contracts.
11
+ // Both target the same Mado runtime — they only differ in how much
12
+ // structure they pre-create.
13
+ const STARTERS = ["default", "modular"];
8
14
 
9
15
  export async function runInit(ctx, rawArgs) {
10
16
  const { flags, positional } = parseFlags(rawArgs);
11
17
  const targetArg = positional[0];
12
18
  if (!targetArg) {
13
- console.error("[mado] usage: mado init <name> [--starter default] [--force]");
19
+ logger.error("mado", "usage", "usage: mado init <name> [--starter default] [--force]");
14
20
  process.exit(1);
15
21
  }
16
22
 
17
23
  const starter = String(flags.starter ?? "default");
18
24
  if (!STARTERS.includes(starter)) {
19
- console.error(`[mado] unknown starter: ${starter}`);
20
- console.error(`[mado] available starters: ${STARTERS.join(", ")}`);
25
+ logger.error("mado", "unknown-starter", `unknown starter: ${starter}`);
26
+ logger.info("mado", "available-starters", `available starters: ${STARTERS.join(", ")}`);
21
27
  process.exit(1);
22
28
  }
23
29
 
24
30
  const target = resolve(ctx.projectRoot, targetArg);
25
31
  const source = join(ctx.packageRoot, "starters", starter);
26
32
  if (!existsSync(source)) {
27
- console.error(`[mado] missing starter template: ${starter}`);
33
+ logger.error("mado", "missing-starter", `missing starter template: ${starter}`);
28
34
  process.exit(1);
29
35
  }
30
36
  if (existsSync(target) && statSync(target).isFile()) {
31
- console.error(`[mado] target exists and is a file: ${target}`);
37
+ logger.error("mado", "target-is-file", `target exists and is a file: ${target}`);
32
38
  process.exit(1);
33
39
  }
34
40
  if (existsSync(target) && readdirSync(target).length > 0 && !flags.force) {
35
- console.error(`[mado] target directory is not empty: ${target}`);
36
- console.error("[mado] use --force to write into it");
41
+ logger.error("mado", "target-not-empty", `target directory is not empty: ${target}`);
42
+ logger.info("mado", "force-hint", "use --force to write into it");
37
43
  process.exit(1);
38
44
  }
39
45
 
@@ -45,7 +51,7 @@ export async function runInit(ctx, rawArgs) {
45
51
 
46
52
  const packageName = packageNameFromDir(target);
47
53
  if (!isValidPackageName(packageName)) {
48
- console.error(`[mado] invalid package name derived from target: ${packageName}`);
54
+ logger.error("mado", "invalid-package-name", `invalid package name derived from target: ${packageName}`);
49
55
  process.exit(1);
50
56
  }
51
57
 
@@ -90,7 +96,10 @@ async function copyCanonicalLLMFiles(ctx, target) {
90
96
  async function ensureStarterGitignore(target) {
91
97
  const file = join(target, ".gitignore");
92
98
  if (existsSync(file)) return;
93
- await writeFile(file, "node_modules\nout\n.DS_Store\n*.log\n");
99
+ await writeFile(
100
+ file,
101
+ "node_modules\ndist\nout\n.cache\n.env\n.env.*\n!.env.example\n.DS_Store\n*.log\n",
102
+ );
94
103
  }
95
104
 
96
105
  async function ensureStarterPackageJson(ctx, target) {
@@ -105,15 +114,6 @@ async function ensureStarterPackageJson(ctx, target) {
105
114
  process.env.MADOJS_PACKAGE_SPEC ||
106
115
  `^${ctx.packageJson.version}`,
107
116
  };
108
- const rootDev = ctx.packageJson.devDependencies ?? {};
109
- pkg.devDependencies = {
110
- ...(pkg.devDependencies ?? {}),
111
- typescript: rootDev.typescript ?? "^6.0.3",
112
- linkedom: rootDev.linkedom ?? "^0.18.12",
113
- lightningcss: rootDev.lightningcss ?? "^1.32.0",
114
- vite: rootDev.vite ?? "^8.0.16",
115
- };
116
-
117
117
  await writeFile(file, `${JSON.stringify(pkg, null, 2)}\n`);
118
118
  }
119
119
 
@@ -1,9 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
- import { copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import { dirname, join, resolve } from "node:path";
2
+ import { readdir, readFile, writeFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
4
  import { brotliCompressSync, constants as zlibConst, gzipSync } from "node:zlib";
5
5
 
6
6
  import { parseFlags } from "../_config.mjs";
7
+ import { logger } from "../logger.mjs";
8
+ import { prepareOutputDirectory } from "../output-guard.mjs";
7
9
  import { runNodeBin, runNodeScript, runVite, writeIfMissing } from "./run.mjs";
8
10
 
9
11
  export async function runRelease(ctx, rawArgs) {
@@ -12,59 +14,44 @@ export async function runRelease(ctx, rawArgs) {
12
14
  ctx.projectRoot,
13
15
  typeof releaseFlags.out === "string" ? releaseFlags.out : "out",
14
16
  );
15
- const bundledHtml = join(outDir, "index.html");
16
- const bakedDir = join(outDir, "baked");
17
17
 
18
- console.log(`[release] context: ${ctx.context}`);
19
- console.log(`[release] artifact: ${outDir}`);
20
- console.log("");
21
-
22
- if (!releaseFlags["no-clean"]) {
23
- if (existsSync(outDir)) {
24
- await rm(outDir, { recursive: true, force: true });
25
- console.log(`[release] cleaned ${outDir}`);
26
- }
18
+ logger.info("release", "context", `context: ${ctx.context}`);
19
+ logger.info("release", "artifact", `artifact: ${outDir}`);
20
+
21
+ await prepareOutputDirectory({
22
+ projectRoot: ctx.projectRoot,
23
+ outDir,
24
+ clean: !releaseFlags["no-clean"],
25
+ force: releaseFlags["force-output"] === true,
26
+ });
27
+ if (releaseFlags["no-clean"]) {
28
+ logger.info("release", "clean-skip", "--no-clean: keeping existing out/");
27
29
  } else {
28
- console.log("[release] --no-clean: keeping existing out/");
30
+ logger.info("release", "clean", `prepared ${outDir}`);
29
31
  }
30
32
 
31
- console.log("[release] step 1/5 typecheck");
33
+ logger.info("release", "step", "step 1/5 typecheck");
32
34
  await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit"]);
33
35
 
34
- console.log("[release] step 2/5 vite build");
36
+ logger.info("release", "step", "step 2/5 vite build");
35
37
  await runVite(ctx, ["build", "--outDir", outDir], { defaultConfig: true });
36
38
 
37
- console.log("[release] step 3/5 bake");
38
- if (releaseFlags["keep-bake-dir"]) {
39
- await runNodeScript(ctx, "scripts/bake.mjs", [
40
- ...rawArgs.filter((a) => a !== "--keep-bake-dir"),
41
- "--template",
42
- bundledHtml,
43
- "--out",
44
- bakedDir,
45
- ]);
46
- const promoted = await promoteBakedHtml(bakedDir, outDir);
47
- if (promoted.html > 0) {
48
- console.log(`[release] promoted ${promoted.html} baked HTML page(s) into out/`);
49
- }
50
- if (promoted.sitemap) {
51
- console.log(`[release] copied sitemap.xml -> ${join(outDir, "sitemap.xml")}`);
52
- }
53
- } else {
54
- await runNodeScript(ctx, "scripts/bake.mjs", [
55
- ...rawArgs,
56
- "--template",
57
- bundledHtml,
58
- "--out",
59
- outDir,
60
- ]);
39
+ logger.info("release", "step", "step 3/5 static snapshots");
40
+ await runNodeScript(ctx, "scripts/static.mjs", [
41
+ ...rawArgs.filter((a) => a !== "--no-clean"),
42
+ "--out",
43
+ outDir,
44
+ ]);
45
+
46
+ logger.info("release", "step", "step 4/5 deployment files");
47
+ // GitHub Pages / Netlify / Cloudflare Pages fallback. SPA fallback shell
48
+ // is written by `mado static`; here we only register the deployment
49
+ // bindings, and respect user-supplied files (writeIfMissing).
50
+ const spaShell = join(outDir, "_mado/spa.html");
51
+ if (existsSync(spaShell)) {
52
+ await writeIfMissing(join(outDir, "404.html"), await readFile(spaShell, "utf8"), "[release] ");
61
53
  }
62
-
63
- console.log("[release] step 4/5 precompress assets");
64
- await precompressOut(outDir);
65
-
66
- console.log("[release] step 5/5 CDN config");
67
- await writeIfMissing(join(outDir, "_redirects"), "/* /index.html 200\n", "[release] ");
54
+ await writeIfMissing(join(outDir, "_redirects"), "/* /_mado/spa.html 200\n", "[release] ");
68
55
  await writeIfMissing(
69
56
  join(outDir, "_headers"),
70
57
  [
@@ -78,43 +65,11 @@ export async function runRelease(ctx, rawArgs) {
78
65
  "[release] ",
79
66
  );
80
67
 
81
- console.log("");
82
- console.log(`[release] done. Deploy artifact: ${outDir}`);
83
- console.log("[release] try: mado preview");
84
- }
85
-
86
- async function promoteBakedHtml(bakedDir, outDir) {
87
- if (!existsSync(bakedDir)) return { html: 0, sitemap: false };
88
-
89
- let html = 0;
90
-
91
- async function walk(dir, rel = "") {
92
- for (const entry of await readdir(dir, { withFileTypes: true })) {
93
- const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
94
- const source = join(dir, entry.name);
95
- if (entry.isDirectory()) {
96
- await walk(source, nextRel);
97
- continue;
98
- }
99
- if (!entry.isFile() || !entry.name.endsWith(".html")) continue;
100
- const target = join(outDir, nextRel);
101
- await mkdir(dirname(target), { recursive: true });
102
- await copyFile(source, target);
103
- html++;
104
- }
105
- }
106
-
107
- await walk(bakedDir);
108
-
109
- const bakedSitemap = join(bakedDir, "sitemap.xml");
110
- const rootSitemap = join(outDir, "sitemap.xml");
111
- let sitemap = false;
112
- if (existsSync(bakedSitemap)) {
113
- await copyFile(bakedSitemap, rootSitemap);
114
- sitemap = true;
115
- }
68
+ logger.info("release", "step", "step 5/5 precompress assets");
69
+ await precompressOut(outDir);
116
70
 
117
- return { html, sitemap };
71
+ logger.info("release", "done", `done. Deploy artifact: ${outDir}`);
72
+ logger.info("release", "next", "try: mado preview");
118
73
  }
119
74
 
120
75
  async function precompressOut(outDir) {
@@ -132,7 +87,7 @@ async function precompressOut(outDir) {
132
87
  );
133
88
  count++;
134
89
  }
135
- console.log(`[release] compressed ${count} file(s)`);
90
+ logger.info("release", "compress", `compressed ${count} file(s)`);
136
91
  }
137
92
 
138
93
  async function listCompressibleFiles(dir) {