@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/scripts/bake.mjs DELETED
@@ -1,570 +0,0 @@
1
- // Smart Static: bake HTML for pages whose `page({ bake })` is set.
2
- //
3
- // Usage:
4
- // mado bake
5
- // mado bake --entry src/routes.ts --template index.html --out out
6
- // mado bake --base-url https://example.com
7
- //
8
- // Defaults:
9
- // entry: src/app.routes.ts, then src/routes.ts
10
- // template: index.html
11
- // out: out/
12
- //
13
- // What it does:
14
- // 1. Loads `entry` (routes module) through Vite SSR/module loading.
15
- // 2. For every route whose page has `bake`:
16
- // a) gets params via `bake.paths()`,
17
- // b) gets data per params via `bake.data(params)`,
18
- // c) renders the TemplateResult to an HTML string (no browser),
19
- // d) materializes head() into <meta>/<link>/<script type=json-ld>,
20
- // e) inlines baked data in <script id="bake" type="application/json">,
21
- // f) writes <out>/<path>/index.html.
22
- // 3. Generates <out>/sitemap.xml.
23
- //
24
- // Context awareness:
25
- // - In app-mode (default outside the framework repository) the bundler
26
- // resolves `@madojs/mado` from node_modules normally.
27
- // - In repo-mode (the framework repository itself) it aliases
28
- // `@madojs/mado` → ./src/index.ts so the framework can dogfood itself.
29
- //
30
- // Required dev deps: linkedom, vite. We print a clear error if missing.
31
-
32
- import { readFile, writeFile, mkdir } from "node:fs/promises";
33
- import { existsSync, writeSync } from "node:fs";
34
- import { join, dirname, resolve } from "node:path";
35
-
36
- import { detectContext, parseFlags, resolveProjectPath } from "./_config.mjs";
37
-
38
- // ---------- Resolve options from config + flags + env ----------
39
-
40
- const { flags } = parseFlags(process.argv.slice(2));
41
- const PROJECT_ROOT = resolve(process.cwd());
42
- const CONTEXT = detectContext(PROJECT_ROOT);
43
- const ENTRY = resolveProjectPath(
44
- PROJECT_ROOT,
45
- typeof flags.entry === "string" ? flags.entry : pickDefaultEntry(PROJECT_ROOT),
46
- );
47
- const TEMPLATE = resolveProjectPath(
48
- PROJECT_ROOT,
49
- typeof flags.template === "string" ? flags.template : "index.html",
50
- );
51
- const BASE_URL = typeof flags["base-url"] === "string" ? flags["base-url"] : "https://example.com";
52
- const OUT_DIR = resolveProjectPath(
53
- PROJECT_ROOT,
54
- typeof flags.out === "string" ? flags.out : "out",
55
- );
56
-
57
- /** Write message to stderr and exit. Sync write keeps CI/execFile output reliable. */
58
- function fatal(...msgs) {
59
- writeSync(2, msgs.join("\n") + "\n");
60
- process.exit(1);
61
- }
62
-
63
- function error(...msgs) {
64
- writeSync(2, msgs.join(" ") + "\n");
65
- }
66
-
67
- if (!existsSync(ENTRY)) {
68
- fatal(
69
- `[bake] entry not found: ${ENTRY}`,
70
- `[bake] expected src/app.routes.ts or src/routes.ts; pass --entry <file> to override`,
71
- );
72
- }
73
- if (!existsSync(TEMPLATE)) {
74
- fatal(
75
- `[bake] template not found: ${TEMPLATE}`,
76
- `[bake] expected index.html; pass --template <file> to override`,
77
- );
78
- }
79
-
80
- // ---------- Optional dependencies ----------
81
-
82
- let parseHTML;
83
- try {
84
- ({ parseHTML } = await import("linkedom"));
85
- } catch {
86
- fatal(
87
- "[bake] package 'linkedom' is required.",
88
- "[bake] Install it as a dev dependency in this project:",
89
- "[bake] npm i -D linkedom vite",
90
- "[bake] (vite is also required, see next check).",
91
- "[bake] These are not bundled into @madojs/mado on purpose: bake is an",
92
- "[bake] optional build step and we don't want to add transitive deps to",
93
- "[bake] every Mado install.",
94
- );
95
- }
96
-
97
- let createViteServer;
98
- try {
99
- ({ createServer: createViteServer } = await import("vite"));
100
- } catch {
101
- fatal(
102
- "[bake] package 'vite' is required.",
103
- "[bake] Install it as a dev dependency in this project:",
104
- "[bake] npm i -D vite linkedom",
105
- );
106
- }
107
-
108
- // ---------- DOM polyfills for Node ----------
109
- //
110
- // router.ts / component.ts / css.ts touch window/document/location/customElements
111
- // at module top-level. Node has none, so install linkedom stubs before importing
112
- // the app graph.
113
-
114
- const baseHtml = await readFile(TEMPLATE, "utf8");
115
- const { window: linkedomWindow } = parseHTML(baseHtml);
116
-
117
- globalThis.window = linkedomWindow;
118
- globalThis.document = linkedomWindow.document;
119
- globalThis.location = new URL("http://localhost/");
120
- globalThis.history = { pushState: () => { }, replaceState: () => { } };
121
- globalThis.customElements = {
122
- define: () => { },
123
- get: () => undefined,
124
- whenDefined: () => Promise.resolve(),
125
- };
126
- globalThis.HTMLElement = linkedomWindow.HTMLElement ?? class { };
127
- globalThis.CSSStyleSheet = globalThis.CSSStyleSheet ?? class {
128
- cssRules = [];
129
- replaceSync() { }
130
- };
131
- globalThis.matchMedia = () => ({
132
- matches: false,
133
- addEventListener: () => { },
134
- removeEventListener: () => { },
135
- });
136
- if (!globalThis.queueMicrotask) {
137
- globalThis.queueMicrotask = (fn) => Promise.resolve().then(fn);
138
- }
139
-
140
- // ---------- Load the routes module through Vite ----------
141
- //
142
- // In repo-mode the framework dogfoods its own source; alias `@madojs/mado`
143
- // to ./src/index.ts. In app-mode the package is resolved from node_modules.
144
-
145
- const aliases = CONTEXT === "repo"
146
- ? { "@madojs/mado": resolve(PROJECT_ROOT, "src/index.ts") }
147
- : {};
148
-
149
- const viteServer = await createViteServer({
150
- root: PROJECT_ROOT,
151
- logLevel: "error",
152
- server: { middlewareMode: true },
153
- appType: "custom",
154
- resolve: { alias: aliases },
155
- });
156
-
157
- let routesModule;
158
- try {
159
- routesModule = await viteServer.ssrLoadModule(toViteId(ENTRY));
160
- } catch (err) {
161
- await closeAndFatal(`[bake] failed to load ${ENTRY}: ${err.message}`);
162
- }
163
- const routeApi = routesModule.default;
164
-
165
- if (!routeApi) {
166
- await closeAndFatal(`[bake] ${ENTRY} must default-export routes({...})`);
167
- }
168
-
169
- // Bake needs the source manifest (not the runtime RouterApi).
170
- // routes.ts must therefore also `export const manifest = {...}`.
171
- const manifest = routesModule.manifest;
172
- if (!manifest) {
173
- await closeAndFatal(
174
- `[bake] ${ENTRY} must also \`export const manifest = {...}\` ` +
175
- "(the same object passed to routes()).",
176
- );
177
- }
178
-
179
- // ---------- Main loop ----------
180
-
181
- await mkdir(OUT_DIR, { recursive: true });
182
-
183
- // Re-read template (already loaded for DOM polyfills, but we want a fresh copy
184
- // per generated page so meta/link tags don't accumulate across iterations).
185
- const TEMPLATE_HTML = baseHtml;
186
-
187
- const sitemapEntries = [];
188
- let total = 0;
189
- let bakedErrors = 0;
190
- let bakeablePages = 0;
191
- const skippedNoBake = [];
192
-
193
- for (const [pattern, entry] of Object.entries(manifest)) {
194
- if (pattern === "*") continue;
195
-
196
- const pg = await resolvePage(entry);
197
- if (!pg) continue;
198
- if (!pg.bake) {
199
- skippedNoBake.push(pattern);
200
- continue;
201
- }
202
- bakeablePages++;
203
-
204
- console.log(`[bake] ${pattern}`);
205
-
206
- let allParams;
207
- try {
208
- allParams = await pg.bake.paths();
209
- } catch (err) {
210
- error(`[bake] ${pattern}: bake.paths() failed:`, err.message);
211
- bakedErrors++;
212
- continue;
213
- }
214
-
215
- for (const params of allParams) {
216
- const pathname = applyParams(pattern, params);
217
- let data;
218
- try {
219
- data = await pg.bake.data(params);
220
- } catch (err) {
221
- error(`[bake] ${pathname}: bake.data() failed:`, err.message);
222
- bakedErrors++;
223
- continue;
224
- }
225
- const headMeta = pg.head ? pg.head(params, data) : {};
226
-
227
- // Ensure <title> is always set in baked HTML. page.title is the primary
228
- // source (string or function of params); head().title overrides it.
229
- if (!headMeta.title && pg.title) {
230
- headMeta.title = typeof pg.title === "function" ? pg.title(params) : pg.title;
231
- }
232
-
233
- const tpl = pg.view({
234
- params,
235
- data,
236
- path: () => pathname,
237
- child: null,
238
- });
239
-
240
- let bodyHtml;
241
- try {
242
- bodyHtml = renderTemplate(tpl, { route: pattern });
243
- } catch (err) {
244
- error(`[bake] ${pathname}: render failed:`, err.message);
245
- bakedErrors++;
246
- continue;
247
- }
248
-
249
- const finalHtml = buildHtml({
250
- template: TEMPLATE_HTML,
251
- bodyHtml,
252
- head: headMeta,
253
- bakedData: data,
254
- revalidate: pg.bake.revalidate,
255
- canonical: headMeta.canonical ?? `${BASE_URL}${pathname}`,
256
- });
257
-
258
- const file = join(
259
- OUT_DIR,
260
- pathname === "/" ? "/index.html" : `${pathname}/index.html`,
261
- );
262
- await mkdir(dirname(file), { recursive: true });
263
- await writeFile(file, finalHtml);
264
- total++;
265
- sitemapEntries.push({ loc: `${BASE_URL}${pathname}`, changefreq: "weekly" });
266
- }
267
- }
268
-
269
- // ---------- Sitemap ----------
270
-
271
- const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
272
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
273
- ${sitemapEntries
274
- .map(
275
- (e) =>
276
- ` <url><loc>${escapeXml(e.loc)}</loc><changefreq>${e.changefreq}</changefreq></url>`,
277
- )
278
- .join("\n")}
279
- </urlset>
280
- `;
281
- await writeFile(join(OUT_DIR, "sitemap.xml"), sitemap);
282
-
283
- console.log(`[bake] done: ${total} pages + sitemap.xml → ${OUT_DIR}`);
284
- if (bakedErrors > 0) {
285
- await closeAndFatal(`[bake] ${bakedErrors} route(s) failed; see errors above.`);
286
- }
287
- // Loud diagnostic when the manifest exists but no page declares `bake`.
288
- // Previously bake silently produced 0 pages + an empty sitemap and exited
289
- // 0, which made `mado release` look successful while shipping no static
290
- // HTML for crawlers. Fail loudly so the user notices.
291
- if (bakeablePages === 0) {
292
- error("");
293
- error(
294
- `[bake] WARNING: no page in ${ENTRY} declares \`bake: { paths, data }\`.`,
295
- );
296
- error(
297
- `[bake] ${skippedNoBake.length} route(s) skipped: ${skippedNoBake
298
- .slice(0, 6)
299
- .join(", ")}${skippedNoBake.length > 6 ? ", …" : ""}`,
300
- );
301
- error("[bake] Add `bake` to at least one page (e.g. your landing route):");
302
- error("[bake] export default page({");
303
- error("[bake] view: …,");
304
- error("[bake] bake: { paths: () => [{}], data: () => ({}) },");
305
- error("[bake] });");
306
- error(
307
- "[bake] Without bake the build ships only the SPA shell — search engines",
308
- );
309
- error("[bake] and link previews see an empty <body>.");
310
- // Exit non-zero so `mado release` halts and the user is forced to address
311
- // it. If you intentionally have an SPA-only deploy, drop `mado bake` from
312
- // the release pipeline (or set MADO_BAKE_ALLOW_EMPTY=1).
313
- if (process.env.MADO_BAKE_ALLOW_EMPTY !== "1") {
314
- await viteServer.close();
315
- process.exit(1);
316
- }
317
- }
318
-
319
- await viteServer.close();
320
-
321
- // ---------- Helpers ----------
322
-
323
- async function resolvePage(entry) {
324
- if (entry && entry._page === true) return entry;
325
- if (typeof entry === "function") {
326
- try {
327
- const mod = await entry();
328
- return mod?.default;
329
- } catch (e) {
330
- console.warn("[bake] failed to load route:", e.message);
331
- return null;
332
- }
333
- }
334
- return null;
335
- }
336
-
337
- function toViteId(path) {
338
- return path.split("\\").join("/");
339
- }
340
-
341
- function pickDefaultEntry(projectRoot) {
342
- const appRoutes = "src/app.routes.ts";
343
- if (existsSync(resolve(projectRoot, appRoutes))) return appRoutes;
344
- return "src/routes.ts";
345
- }
346
-
347
- async function closeAndFatal(...msgs) {
348
- await viteServer.close().catch(() => { });
349
- fatal(...msgs);
350
- }
351
-
352
- function applyParams(pattern, params) {
353
- return pattern.replace(/:([\w]+)/g, (_, k) => {
354
- const v = params[k];
355
- if (v == null) throw new Error(`[bake] missing param :${k} for ${pattern}`);
356
- return encodeURIComponent(String(v));
357
- });
358
- }
359
-
360
- // ---------- Render TemplateResult → HTML string ----------
361
- //
362
- // Tiny SSR for TemplateResult. Supports the same shapes as html.ts but
363
- // without events (@click is stripped) and without live signals (function
364
- // values are called once).
365
-
366
- function renderTemplate(tpl, ctx) {
367
- if (tpl == null || tpl === false || tpl === true) return "";
368
- if (typeof tpl === "string") return escapeHtml(tpl);
369
- if (typeof tpl === "number") return String(tpl);
370
- if (Array.isArray(tpl)) return tpl.map((x) => renderTemplate(x, ctx)).join("");
371
- if (typeof tpl === "object") {
372
- if (tpl._mado === true) return renderMadoTemplate(tpl, ctx);
373
-
374
- // Unknown shapes (e.g. each() directive results) must NOT silently render
375
- // as "[object Object]". Either each() unwraps to an array here, or we
376
- // throw with a meaningful location.
377
- throw new Error(
378
- `bake cannot render value of type "${tpl._type ?? tpl.constructor?.name ?? "object"}" ` +
379
- `in route ${ctx?.route ?? "?"}. ` +
380
- "Hint: each() and other directives are not yet supported in bake. " +
381
- "Use a plain array (items.map(render)) in baked views, or render this " +
382
- "section only on the client.",
383
- );
384
- }
385
- return "";
386
- }
387
-
388
- function renderMadoTemplate(tpl, ctx) {
389
- const { strings, values } = tpl;
390
- let html = "";
391
- for (let i = 0; i < strings.length; i++) {
392
- html += strings[i];
393
- if (i < strings.length - 1) {
394
- html += renderValue(values[i], inAttributeContext(html), ctx);
395
- }
396
- }
397
- // Strip event markers and normalize property / boolean-attribute forms
398
- // (mirrors the runtime bindings in src/html/bindings.ts).
399
- return html
400
- .replace(/\s+@[\w-]+="[^"]*"/g, "")
401
- .replace(/\s+\.([\w-]+)="([^"]*)"/g, ' $1="$2"')
402
- .replace(/\s+\?([\w-]+)="(true|on|1)"/g, ' $1=""')
403
- .replace(/\s+\?[\w-]+="(false|off|0|)"/g, "");
404
- }
405
-
406
- function inAttributeContext(html) {
407
- const lastOpen = html.lastIndexOf("<");
408
- const lastClose = html.lastIndexOf(">");
409
- return lastOpen > lastClose;
410
- }
411
-
412
- function renderValue(v, inAttr, ctx) {
413
- if (v == null || v === false) return "";
414
- if (v === true) return "";
415
- if (typeof v === "function") {
416
- try {
417
- return renderValue(v(), inAttr, ctx);
418
- } catch {
419
- return "";
420
- }
421
- }
422
- if (Array.isArray(v)) return v.map((x) => renderValue(x, inAttr, ctx)).join("");
423
- if (v && v._mado === true) return renderMadoTemplate(v, ctx);
424
- if (v && typeof v === "object" && typeof v._madoDirective === "string") {
425
- return renderDirective(v, inAttr, ctx);
426
- }
427
- if (v && typeof v === "object") {
428
- // Same defense as renderTemplate(): never silently coerce to "[object Object]".
429
- throw new Error(
430
- `bake cannot serialize value of type "${v?._type ?? v?.constructor?.name ?? "object"}" ` +
431
- `in route ${ctx?.route ?? "?"}. ` +
432
- "Hint: each() is not yet supported in bake. Use a plain array in baked views.",
433
- );
434
- }
435
- if (inAttr) return escapeAttr(String(v));
436
- return escapeHtml(String(v));
437
- }
438
-
439
- function renderDirective(v, inAttr, ctx) {
440
- switch (v._madoDirective) {
441
- case "unsafeHTML": {
442
- if (inAttr) {
443
- throw new Error(
444
- `bake cannot render unsafeHTML() inside an attribute in route ${ctx?.route ?? "?"}.`,
445
- );
446
- }
447
- return String(v.value ?? "");
448
- }
449
- case "classMap": {
450
- const value = Object.entries(v.value ?? {})
451
- .filter(([, enabled]) => !!enabled)
452
- .map(([className]) => className)
453
- .join(" ");
454
- return inAttr ? escapeAttr(value) : escapeHtml(value);
455
- }
456
- case "styleMap": {
457
- const value = Object.entries(v.value ?? {})
458
- .filter(([, raw]) => raw != null && raw !== false)
459
- .map(([name, raw]) => `${toCssPropertyName(name)}:${String(raw)}`)
460
- .join(";");
461
- return inAttr ? escapeAttr(value) : escapeHtml(value);
462
- }
463
- case "ref":
464
- return "";
465
- default:
466
- throw new Error(
467
- `bake cannot render directive "${v._madoDirective}" in route ${ctx?.route ?? "?"}.`,
468
- );
469
- }
470
- }
471
-
472
- function toCssPropertyName(name) {
473
- if (name.startsWith("--")) return name;
474
- return name.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
475
- }
476
-
477
- function escapeHtml(s) {
478
- return s
479
- .replace(/&/g, "&amp;")
480
- .replace(/</g, "&lt;")
481
- .replace(/>/g, "&gt;");
482
- }
483
- function escapeAttr(s) {
484
- return escapeHtml(s).replace(/"/g, "&quot;");
485
- }
486
- function escapeXml(s) {
487
- return s
488
- .replace(/&/g, "&amp;")
489
- .replace(/</g, "&lt;")
490
- .replace(/>/g, "&gt;")
491
- .replace(/"/g, "&quot;");
492
- }
493
-
494
- // ---------- Final HTML with head + baked data ----------
495
-
496
- function buildHtml({ template, bodyHtml, head, bakedData, revalidate, canonical }) {
497
- const { document } = parseHTML(template);
498
-
499
- if (head.title) document.title = head.title;
500
- if (head.description) {
501
- setMeta(document, { name: "description", content: head.description });
502
- }
503
- if (canonical) {
504
- setLink(document, { rel: "canonical", href: canonical });
505
- }
506
- if (head.og) {
507
- for (const [k, v] of Object.entries(head.og)) {
508
- if (!v) continue;
509
- setMeta(document, { property: `og:${k}`, content: String(v) });
510
- }
511
- }
512
- if (head.twitter || head.og) {
513
- const tw = head.twitter ?? {};
514
- const og = head.og ?? {};
515
- setMeta(document, { name: "twitter:card", content: tw.card ?? "summary" });
516
- if (tw.title ?? og.title)
517
- setMeta(document, { name: "twitter:title", content: tw.title ?? og.title });
518
- if (tw.description ?? og.description)
519
- setMeta(document, { name: "twitter:description", content: tw.description ?? og.description });
520
- if (tw.image ?? og.image)
521
- setMeta(document, { name: "twitter:image", content: tw.image ?? og.image });
522
- }
523
- for (const m of head.meta ?? []) setMeta(document, m);
524
- for (const l of head.link ?? []) setLink(document, l);
525
-
526
- if (head.jsonLd != null) {
527
- const s = document.createElement("script");
528
- s.setAttribute("type", "application/ld+json");
529
- s.setAttribute("data-mado-head", "baked");
530
- s.textContent = JSON.stringify(head.jsonLd);
531
- document.head.appendChild(s);
532
- }
533
-
534
- if (revalidate) {
535
- setMeta(document, { name: "bake-revalidate", content: String(revalidate) });
536
- }
537
-
538
- if (bakedData !== undefined) {
539
- const s = document.createElement("script");
540
- s.setAttribute("type", "application/json");
541
- s.id = "bake";
542
- s.textContent = JSON.stringify(bakedData);
543
- document.body.appendChild(s);
544
- }
545
-
546
- const app = document.getElementById("app");
547
- if (app) {
548
- app.setAttribute("data-mado-baked", "");
549
- app.innerHTML = bodyHtml;
550
- }
551
-
552
- return "<!doctype html>\n" + document.documentElement.outerHTML;
553
- }
554
-
555
- function setMeta(doc, attrs) {
556
- const m = doc.createElement("meta");
557
- if (attrs.name) m.setAttribute("name", attrs.name);
558
- if (attrs.property) m.setAttribute("property", attrs.property);
559
- m.setAttribute("content", attrs.content);
560
- m.setAttribute("data-mado-head", "baked");
561
- doc.head.appendChild(m);
562
- }
563
- function setLink(doc, attrs) {
564
- const l = doc.createElement("link");
565
- l.setAttribute("rel", attrs.rel);
566
- l.setAttribute("href", attrs.href);
567
- if (attrs.hreflang) l.setAttribute("hreflang", attrs.hreflang);
568
- l.setAttribute("data-mado-head", "baked");
569
- doc.head.appendChild(l);
570
- }
@@ -1,77 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { execFile } from "node:child_process";
4
- import { mkdir, mkdtemp, rm } from "node:fs/promises";
5
- import { tmpdir } from "node:os";
6
- import { basename, join, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import { promisify } from "node:util";
9
-
10
- const exec = promisify(execFile);
11
- const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
12
- const tempRoot = await mkdtemp(join(tmpdir(), "mado-package-smoke-"));
13
- let tarball = "";
14
-
15
- try {
16
- const packed = await exec("npm", ["pack", "--silent"], { cwd: repoRoot });
17
- tarball = resolve(repoRoot, packed.stdout.trim().split(/\s+/).at(-1) ?? "");
18
- if (!tarball) throw new Error("[package-smoke] npm pack did not return a tarball");
19
-
20
- const installRoot = join(tempRoot, "installed");
21
- await mkdir(installRoot, { recursive: true });
22
- await run("npm", ["install", tarball], { cwd: installRoot });
23
-
24
- await run(
25
- process.execPath,
26
- [
27
- "--input-type=module",
28
- "--eval",
29
- `
30
- import { html, signal } from "@madojs/mado";
31
- import "@madojs/mado/devtools.js";
32
- import { mado } from "@madojs/mado/vite";
33
- if (typeof html !== "function" || typeof signal !== "function") {
34
- throw new Error("public root import failed");
35
- }
36
- if (typeof mado !== "function") throw new Error("vite plugin import failed");
37
- try {
38
- await import("@madojs/mado/lifecycle.js");
39
- throw new Error("internal lifecycle subpath unexpectedly resolved");
40
- } catch (err) {
41
- if (err?.code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw err;
42
- }
43
- `,
44
- ],
45
- { cwd: installRoot },
46
- );
47
-
48
- await run("npx", ["mado", "init", "smoke-app"], {
49
- cwd: installRoot,
50
- env: { ...process.env, MADO_PACKAGE_SPEC: tarball },
51
- });
52
-
53
- const appRoot = join(installRoot, "smoke-app");
54
- await run("npm", ["install"], { cwd: appRoot });
55
- await run("npm", ["run", "new", "--", "module", "smoke"], { cwd: appRoot });
56
- await run("npm", ["run", "release"], { cwd: appRoot });
57
-
58
- console.log(`[package-smoke] ok ${basename(tarball)}`);
59
- } finally {
60
- await rm(tempRoot, { recursive: true, force: true });
61
- if (tarball) await rm(tarball, { force: true });
62
- }
63
-
64
- async function run(cmd, args, options) {
65
- console.log(`[package-smoke] ${cmd} ${args.join(" ")}`);
66
- try {
67
- await exec(cmd, args, {
68
- cwd: options.cwd,
69
- env: options.env ?? process.env,
70
- maxBuffer: 20 * 1024 * 1024,
71
- });
72
- } catch (err) {
73
- if (err.stdout) process.stdout.write(err.stdout);
74
- if (err.stderr) process.stderr.write(err.stderr);
75
- throw err;
76
- }
77
- }