@madojs/mado 0.10.1 → 0.11.1

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 (219) hide show
  1. package/AGENTS.md +24 -26
  2. package/CHANGELOG.md +95 -0
  3. package/README.md +22 -47
  4. package/TODO.md +52 -48
  5. package/dist/src/component.d.ts +2 -1
  6. package/dist/src/component.js +5 -2
  7. package/dist/src/component.js.map +1 -1
  8. package/dist/src/each.d.ts +1 -1
  9. package/dist/src/each.js +1 -1
  10. package/dist/src/each.js.map +1 -1
  11. package/dist/src/html/bindings.js +3 -3
  12. package/dist/src/html/bindings.js.map +1 -1
  13. package/dist/src/index.d.ts +11 -6
  14. package/dist/src/index.js +5 -3
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/lazy.d.ts +1 -1
  17. package/dist/src/lazy.js +1 -1
  18. package/dist/src/lazy.js.map +1 -1
  19. package/dist/src/page.d.ts +17 -21
  20. package/dist/src/page.js +7 -12
  21. package/dist/src/page.js.map +1 -1
  22. package/dist/src/router/manifest.d.ts +1 -1
  23. package/dist/src/router/manifest.js +21 -13
  24. package/dist/src/router/manifest.js.map +1 -1
  25. package/dist/src/router/match.d.ts +2 -2
  26. package/dist/src/router/match.js +3 -3
  27. package/dist/src/router/match.js.map +1 -1
  28. package/dist/src/router/navigation.js +1 -1
  29. package/dist/src/router/navigation.js.map +1 -1
  30. package/dist/src/vite/index.d.ts +10 -0
  31. package/dist/src/vite/index.js +33 -0
  32. package/dist/src/vite/index.js.map +1 -0
  33. package/docs/en/00-the-mado-way.md +25 -12
  34. package/docs/en/01-routing.md +90 -142
  35. package/docs/en/02-project-layout.md +59 -53
  36. package/docs/en/03-static-bake.md +5 -6
  37. package/docs/en/05-why-mado.md +6 -6
  38. package/docs/en/06-for-backenders.md +18 -22
  39. package/docs/en/08-llm-zero-history-test.md +9 -14
  40. package/docs/en/09-shadow-vs-light-dom.md +28 -36
  41. package/docs/en/10-app-architecture.md +158 -96
  42. package/docs/en/11-layouts.md +22 -24
  43. package/docs/en/12-auth-and-api.md +89 -182
  44. package/docs/en/13-deployment.md +18 -22
  45. package/docs/en/14-testing.md +4 -4
  46. package/docs/en/16-bake-cookbook.md +11 -12
  47. package/docs/en/18-api-freeze-map.md +6 -4
  48. package/docs/en/20-v1-stability.md +1 -1
  49. package/docs/fr/00-the-mado-way.md +55 -90
  50. package/docs/fr/01-routing.md +70 -152
  51. package/docs/fr/02-project-layout.md +61 -42
  52. package/docs/fr/03-static-bake.md +1 -1
  53. package/docs/fr/05-why-mado.md +6 -6
  54. package/docs/fr/06-for-backenders.md +7 -7
  55. package/docs/fr/08-llm-zero-history-test.md +21 -48
  56. package/docs/fr/09-shadow-vs-light-dom.md +43 -162
  57. package/docs/fr/10-app-architecture.md +110 -33
  58. package/docs/fr/11-layouts.md +24 -12
  59. package/docs/fr/12-auth-and-api.md +63 -22
  60. package/docs/fr/13-deployment.md +7 -10
  61. package/docs/fr/14-testing.md +1 -1
  62. package/docs/fr/16-bake-cookbook.md +2 -2
  63. package/docs/fr/18-api-freeze-map.md +1 -1
  64. package/docs/fr/20-v1-stability.md +1 -1
  65. package/docs/recipes/nginx/README.md +13 -0
  66. package/docs/ru/00-the-mado-way.md +53 -75
  67. package/docs/ru/01-routing.md +68 -143
  68. package/docs/ru/02-project-layout.md +61 -41
  69. package/docs/ru/03-static-bake.md +2 -2
  70. package/docs/ru/05-why-mado.md +6 -6
  71. package/docs/ru/06-for-backenders.md +7 -7
  72. package/docs/ru/08-llm-zero-history-test.md +9 -14
  73. package/docs/ru/09-shadow-vs-light-dom.md +43 -178
  74. package/docs/ru/10-app-architecture.md +115 -63
  75. package/docs/ru/11-layouts.md +24 -24
  76. package/docs/ru/12-auth-and-api.md +57 -35
  77. package/docs/ru/13-deployment.md +7 -11
  78. package/docs/ru/14-testing.md +1 -1
  79. package/docs/ru/16-bake-cookbook.md +12 -6
  80. package/docs/ru/18-api-freeze-map.md +5 -3
  81. package/docs/ru/20-v1-stability.md +1 -1
  82. package/docs/uk/00-the-mado-way.md +70 -44
  83. package/docs/uk/01-routing.md +41 -47
  84. package/docs/uk/02-project-layout.md +68 -41
  85. package/docs/uk/03-static-bake.md +1 -2
  86. package/docs/uk/06-for-backenders.md +3 -3
  87. package/docs/uk/08-llm-zero-history-test.md +22 -24
  88. package/docs/uk/09-shadow-vs-light-dom.md +37 -86
  89. package/docs/uk/10-app-architecture.md +72 -31
  90. package/docs/uk/11-layouts.md +25 -12
  91. package/docs/uk/12-auth-and-api.md +58 -22
  92. package/docs/uk/13-deployment.md +4 -3
  93. package/docs/uk/14-testing.md +1 -1
  94. package/docs/uk/18-api-freeze-map.md +1 -1
  95. package/docs/uk/20-v1-stability.md +1 -1
  96. package/llms.txt +14 -15
  97. package/package.json +18 -11
  98. package/scripts/_config.mjs +15 -161
  99. package/scripts/bake.mjs +74 -63
  100. package/scripts/cli/generate.mjs +348 -0
  101. package/scripts/cli/help.mjs +27 -0
  102. package/scripts/cli/index.mjs +79 -0
  103. package/scripts/cli/init.mjs +153 -0
  104. package/scripts/cli/release.mjs +152 -0
  105. package/scripts/cli/run.mjs +96 -0
  106. package/scripts/cli.mjs +2 -621
  107. package/scripts/package-smoke.mjs +4 -1
  108. package/scripts/preview.mjs +13 -37
  109. package/scripts/size-budget.mjs +5 -2
  110. package/scripts/vite.default.mjs +11 -0
  111. package/starters/default/.editorconfig +12 -0
  112. package/starters/default/README.md +74 -0
  113. package/starters/default/eslint.config.mjs +256 -0
  114. package/starters/default/index.html +13 -0
  115. package/starters/default/package.json +30 -0
  116. package/starters/default/public/favicon.svg +4 -0
  117. package/starters/default/src/app.routes.ts +39 -0
  118. package/starters/default/src/layouts/app-shell.layout.ts +35 -0
  119. package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
  120. package/starters/default/src/main.ts +16 -0
  121. package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
  122. package/starters/default/src/modules/auth/auth.connector.ts +45 -0
  123. package/starters/default/src/modules/auth/auth.guard.ts +22 -0
  124. package/starters/default/src/modules/auth/auth.public.ts +9 -0
  125. package/starters/default/src/modules/auth/auth.routes.ts +8 -0
  126. package/starters/default/src/modules/auth/auth.service.ts +71 -0
  127. package/starters/default/src/modules/auth/auth.types.ts +15 -0
  128. package/starters/default/src/modules/auth/login.page.ts +62 -0
  129. package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
  130. package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
  131. package/starters/default/src/modules/billing/billing.public.ts +5 -0
  132. package/starters/default/src/modules/billing/billing.routes.ts +9 -0
  133. package/starters/default/src/modules/billing/billing.types.ts +15 -0
  134. package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
  135. package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
  136. package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
  137. package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
  138. package/starters/default/src/modules/home/home.page.ts +34 -0
  139. package/starters/default/src/modules/home/not-found.page.ts +11 -0
  140. package/starters/default/src/shared/http/http-client.ts +86 -0
  141. package/starters/default/src/shared/http/http-error.ts +37 -0
  142. package/starters/default/src/shared/http/interceptors.ts +59 -0
  143. package/starters/default/src/shared/lib/format-date.ts +19 -0
  144. package/starters/default/src/shared/styles/content.css +70 -0
  145. package/starters/default/src/shared/styles/reset.css +32 -0
  146. package/starters/default/src/shared/styles/shell.css +57 -0
  147. package/starters/default/src/shared/styles/tokens.css +44 -0
  148. package/starters/default/src/shared/ui/x-button.component.ts +49 -0
  149. package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
  150. package/starters/default/src/styles.d.ts +1 -0
  151. package/starters/default/src/vite-env.d.ts +1 -0
  152. package/starters/default/tsconfig.json +24 -0
  153. package/starters/default/vite.config.ts +9 -0
  154. package/MADO_V1_PLAN.md +0 -179
  155. package/ROADMAP.md +0 -178
  156. package/dist/src/html.d.ts +0 -18
  157. package/dist/src/html.js +0 -17
  158. package/dist/src/html.js.map +0 -1
  159. package/dist/src/router.d.ts +0 -13
  160. package/dist/src/router.js +0 -13
  161. package/dist/src/router.js.map +0 -1
  162. package/scripts/bundle.mjs +0 -212
  163. package/scripts/llm-zero-history-smoke.mjs +0 -93
  164. package/scripts/new.mjs +0 -80
  165. package/scripts/showcase-regression.mjs +0 -392
  166. package/server/serve.mjs +0 -455
  167. package/starters/admin/README.md +0 -63
  168. package/starters/admin/index.html +0 -28
  169. package/starters/admin/mado.config.json +0 -22
  170. package/starters/admin/package.json +0 -24
  171. package/starters/admin/public/favicon.svg +0 -4
  172. package/starters/admin/src/components/x-button.ts +0 -82
  173. package/starters/admin/src/components/x-input.ts +0 -105
  174. package/starters/admin/src/layouts/app.ts +0 -101
  175. package/starters/admin/src/layouts/auth.ts +0 -41
  176. package/starters/admin/src/lib/api.ts +0 -184
  177. package/starters/admin/src/lib/auth.ts +0 -83
  178. package/starters/admin/src/main.ts +0 -15
  179. package/starters/admin/src/pages/admin/dashboard.ts +0 -48
  180. package/starters/admin/src/pages/admin/order-detail.ts +0 -80
  181. package/starters/admin/src/pages/admin/orders.ts +0 -117
  182. package/starters/admin/src/pages/home.ts +0 -34
  183. package/starters/admin/src/pages/login.ts +0 -70
  184. package/starters/admin/src/pages/not-found.ts +0 -12
  185. package/starters/admin/src/routes.ts +0 -40
  186. package/starters/admin/src/styles/global.ts +0 -86
  187. package/starters/admin/tsconfig.json +0 -15
  188. package/starters/crud/README.md +0 -33
  189. package/starters/crud/index.html +0 -28
  190. package/starters/crud/mado.config.json +0 -20
  191. package/starters/crud/package.json +0 -24
  192. package/starters/crud/src/components/app-shell.ts +0 -56
  193. package/starters/crud/src/components/ticket-detail.ts +0 -33
  194. package/starters/crud/src/components/ticket-form.ts +0 -69
  195. package/starters/crud/src/components/ticket-list.ts +0 -66
  196. package/starters/crud/src/lib/api.ts +0 -76
  197. package/starters/crud/src/main.ts +0 -9
  198. package/starters/crud/src/pages/home.ts +0 -34
  199. package/starters/crud/src/pages/not-found.ts +0 -12
  200. package/starters/crud/src/pages/ticket-detail.ts +0 -7
  201. package/starters/crud/src/pages/ticket-new.ts +0 -7
  202. package/starters/crud/src/pages/tickets.ts +0 -7
  203. package/starters/crud/src/routes.ts +0 -11
  204. package/starters/crud/src/styles/global.ts +0 -155
  205. package/starters/crud/tsconfig.json +0 -15
  206. package/starters/minimal/README.md +0 -21
  207. package/starters/minimal/index.html +0 -28
  208. package/starters/minimal/mado.config.json +0 -20
  209. package/starters/minimal/package.json +0 -24
  210. package/starters/minimal/src/components/app-counter.ts +0 -31
  211. package/starters/minimal/src/main.ts +0 -9
  212. package/starters/minimal/src/pages/home.ts +0 -35
  213. package/starters/minimal/src/pages/not-found.ts +0 -14
  214. package/starters/minimal/src/routes.ts +0 -8
  215. package/starters/minimal/src/styles/global.ts +0 -60
  216. package/starters/minimal/tsconfig.json +0 -15
  217. package/templates/page-detail.ts +0 -63
  218. package/templates/page-form.ts +0 -94
  219. package/templates/page-list.ts +0 -79
package/scripts/cli.mjs CHANGED
@@ -1,624 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from "node:child_process";
4
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5
- import { copyFile, cp, mkdir, readdir, readFile, writeFile, rm } from "node:fs/promises";
6
- import http from "node:http";
7
- import { dirname, join, resolve } from "node:path";
8
- import { fileURLToPath } from "node:url";
3
+ import { main } from "./cli/index.mjs";
9
4
 
10
- import { detectContext, loadConfig } from "./_config.mjs";
11
-
12
- const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
13
- const PROJECT_ROOT = resolve(process.cwd());
14
- const PACKAGE_JSON = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
15
- const [, , rawCommand, ...args] = process.argv;
16
-
17
- // Context detection lives in _config.mjs so every script agrees on what
18
- // "repo" vs "app" means. CLI uses it to pick safer defaults.
19
- const CONTEXT = detectContext(PROJECT_ROOT);
20
- const IS_REPO = CONTEXT === "repo";
21
-
22
- const EXAMPLES = [
23
- ["basic", "minimal API tour"],
24
- ["tickets", "LLM zero-history CRUD validation"],
25
- ["showcase", "flagship SaaS CRM pressure app"],
26
- ["cloudflare", "Cloudflare Workers edge example"],
27
- ];
28
- const STARTERS = ["minimal", "crud", "admin"];
29
-
30
- const command = rawCommand ?? "help";
31
-
32
- switch (command) {
33
- case "init":
34
- await runInit(args);
35
- break;
36
- case "build":
37
- await runNodeBin("typescript/bin/tsc", args);
38
- break;
39
- case "watch":
40
- await runNodeBin("typescript/bin/tsc", ["-w", ...args]);
41
- break;
42
- case "typecheck":
43
- await runNodeBin("typescript/bin/tsc", ["--noEmit", ...args]);
44
- break;
45
- case "test":
46
- if (args[0] === "browser") {
47
- await runNodeScript("scripts/showcase-regression.mjs", args.slice(1));
48
- } else {
49
- // Ensure dist/ is fresh so tests that import from ../dist/ work.
50
- await runNodeBin("typescript/bin/tsc", []);
51
- const files = await listTestFiles();
52
- await run(process.execPath, ["--test", "--test-timeout=20000", ...files, ...args]);
53
- }
54
- break;
55
- case "serve":
56
- await runServe(args);
57
- break;
58
- case "dev":
59
- await runDev(args);
60
- break;
61
- case "bake":
62
- await runNodeScript("scripts/bake.mjs", args);
63
- break;
64
- case "bundle":
65
- await runNodeScript("scripts/bundle.mjs", args);
66
- break;
67
- case "preview":
68
- await runNodeScript("scripts/preview.mjs", args);
69
- break;
70
- case "release":
71
- await runRelease(args);
72
- break;
73
- case "new":
74
- await runNodeScript("scripts/new.mjs", args);
75
- break;
76
- case "examples":
77
- printExamples();
78
- break;
79
- case "help":
80
- case "--help":
81
- case "-h":
82
- printHelp();
83
- break;
84
- default:
85
- console.error(`[mado] unknown command: ${command}`);
86
- printHelp();
87
- process.exit(1);
88
- }
89
-
90
- async function runServe(rawArgs) {
91
- // Split args into [example?, ...flags]. The first non-flag positional is the
92
- // example name; everything else (including `--host`, `--port`, etc.) is
93
- // forwarded verbatim to server/serve.mjs.
94
- const { example, forwarded } = splitDevArgs(rawArgs);
95
- if (example) assertExample(example, { serveable: true });
96
-
97
- // In app-mode (generated project, no example argument) we also go through
98
- // server/serve.mjs to get config support (--host, --port, mado.config.json
99
- // dev.proxy, HMR, etc.) — previously this fell back to serveStaticProject()
100
- // which only read PORT from env and had no proxy/config/HMR.
101
- await run(
102
- process.execPath,
103
- [join(PACKAGE_ROOT, "server/serve.mjs"), example, ...forwarded].filter(
104
- Boolean,
105
- ),
106
- {
107
- env: { ...process.env, EXAMPLE: example || process.env.EXAMPLE || "" },
108
- },
109
- );
110
- }
111
-
112
- async function runInit(rawArgs) {
113
- const { flags, positional } = parseFlags(rawArgs);
114
- const targetArg = positional[0];
115
- if (!targetArg) {
116
- console.error("[mado] usage: mado init <name> [--starter minimal|crud] [--force]");
117
- process.exit(1);
118
- }
119
-
120
- const starter = String(flags.starter ?? "minimal");
121
- if (!STARTERS.includes(starter)) {
122
- console.error(`[mado] unknown starter: ${starter}`);
123
- console.error(`[mado] available starters: ${STARTERS.join(", ")}`);
124
- process.exit(1);
125
- }
126
-
127
- const target = resolve(PROJECT_ROOT, targetArg);
128
- const source = join(PACKAGE_ROOT, "starters", starter);
129
- if (!existsSync(source)) {
130
- console.error(`[mado] missing starter template: ${starter}`);
131
- process.exit(1);
132
- }
133
- if (existsSync(target) && statSync(target).isFile()) {
134
- console.error(`[mado] target exists and is a file: ${target}`);
135
- process.exit(1);
136
- }
137
- if (existsSync(target) && readdirSync(target).length > 0 && !flags.force) {
138
- console.error(`[mado] target directory is not empty: ${target}`);
139
- console.error("[mado] use --force to write into it");
140
- process.exit(1);
141
- }
142
-
143
- await mkdir(target, { recursive: true });
144
- await cp(source, target, { recursive: true, force: true });
145
- await copyCanonicalAgentFiles(target);
146
- await ensureStarterGitignore(target);
147
- await ensureStarterPackageJson(target);
148
-
149
- const packageName = packageNameFromDir(target);
150
- if (!isValidPackageName(packageName)) {
151
- console.error(`[mado] invalid package name derived from target: ${packageName}`);
152
- process.exit(1);
153
- }
154
-
155
- const replacements = {
156
- __APP_NAME__: packageName,
157
- __PACKAGE_NAME__: packageName,
158
- __MADOJS_VERSION__: process.env.MADO_PACKAGE_SPEC || process.env.MADOJS_PACKAGE_SPEC || `^${PACKAGE_JSON.version}`,
159
- __MADO_VERSION__: PACKAGE_JSON.version,
160
- };
161
-
162
- for (const file of await walkFiles(target)) {
163
- const text = await readFile(file, "utf8").catch(() => null);
164
- if (text === null) continue;
165
- let next = text;
166
- for (const [key, value] of Object.entries(replacements)) {
167
- next = next.split(key).join(value);
168
- }
169
- if (next !== text) await writeFile(file, next);
170
- }
171
-
172
- console.log("");
173
- console.log(`Created ${packageName} with the ${starter} starter.`);
174
- console.log("");
175
- console.log("Next steps:");
176
- console.log(` cd ${relativePath(PROJECT_ROOT, target)}`);
177
- console.log(" npm install");
178
- console.log(" npm run build");
179
- console.log(" npm run serve");
180
- console.log("");
181
- }
182
-
183
- async function runDev(rawArgs) {
184
- // Forward unknown flags (e.g. --host, --port) to server/serve.mjs so callers
185
- // can write `mado dev --host 127.0.0.1` without the CLI mistaking `--host`
186
- // for an example name.
187
- const { example, forwarded } = splitDevArgs(rawArgs);
188
- if (example) assertExample(example, { serveable: true });
189
-
190
- const env = { ...process.env, EXAMPLE: example || process.env.EXAMPLE || "" };
191
- const server = spawn(
192
- process.execPath,
193
- [join(PACKAGE_ROOT, "server/serve.mjs"), example, ...forwarded].filter(
194
- Boolean,
195
- ),
196
- {
197
- cwd: PROJECT_ROOT,
198
- env,
199
- stdio: "inherit",
200
- },
201
- );
202
- const tsc = spawn(process.execPath, [resolveBin("typescript/bin/tsc"), "-w"], {
203
- cwd: PROJECT_ROOT,
204
- stdio: "inherit",
205
- });
206
-
207
- const children = [server, tsc];
208
- let shuttingDown = false;
209
-
210
- const shutdown = (code = 0) => {
211
- if (shuttingDown) return;
212
- shuttingDown = true;
213
- for (const child of children) {
214
- if (!child.killed) child.kill("SIGTERM");
215
- }
216
- setTimeout(() => process.exit(code), 80);
217
- };
218
-
219
- process.on("SIGINT", () => shutdown(0));
220
- process.on("SIGTERM", () => shutdown(0));
221
-
222
- await Promise.race(
223
- children.map(
224
- (child) =>
225
- new Promise((resolveExit) => {
226
- child.on("exit", (code, signal) => resolveExit({ code, signal }));
227
- }),
228
- ),
229
- ).then(({ code, signal }) => {
230
- if (signal) shutdown(1);
231
- else shutdown(code ?? 0);
232
- });
233
- }
234
-
235
- async function runRelease(rawArgs) {
236
- // Single "ship it" command. Composes the smaller steps so the user does not
237
- // have to remember the order, and so the deploy artifact (out/) is always
238
- // assembled the same way.
239
- //
240
- // mado release [--no-clean]
241
- // → rm -rf out/ (unless --no-clean)
242
- // → mado typecheck
243
- // → mado build (tsc → dist/)
244
- // → mado bundle (esbuild → out/assets/, also writes out/index.html)
245
- // → mado bake (HTML → out/baked/, using bundled out/index.html)
246
- // → copy public/* → out/
247
- // → promote baked HTML + sitemap into out/ route paths
248
- //
249
- // Flags are forwarded to bake/bundle.
250
- const { flags: releaseFlags } = parseFlags(rawArgs);
251
- const cfg = loadConfig({ projectRoot: PROJECT_ROOT });
252
- const outDir = resolve(
253
- cfg.projectRoot,
254
- typeof releaseFlags.out === "string" ? releaseFlags.out : cfg.build.out ?? "out",
255
- );
256
- const publicDir = resolve(cfg.projectRoot, cfg.build.publicDir ?? "public");
257
- const bundledHtml = join(outDir, "index.html");
258
- const bakedDir = resolve(
259
- cfg.projectRoot,
260
- cfg.bake.outDir ??
261
- join(
262
- typeof releaseFlags.out === "string" ? releaseFlags.out : cfg.build.out ?? "out",
263
- "baked",
264
- ),
265
- );
266
-
267
- console.log(`[release] context: ${cfg.context}`);
268
- console.log(`[release] artifact: ${outDir}`);
269
- console.log("");
270
-
271
- // Deterministic builds: remove the entire output directory so stale assets,
272
- // removed bake routes, and deleted public files don't linger in the deploy
273
- // artifact. Use --no-clean to opt out (e.g. incremental CI workflows).
274
- if (!releaseFlags["no-clean"]) {
275
- if (existsSync(outDir)) {
276
- await rm(outDir, { recursive: true, force: true });
277
- console.log(`[release] cleaned ${outDir}`);
278
- }
279
- } else {
280
- console.log("[release] --no-clean: keeping existing out/");
281
- }
282
-
283
- console.log("[release] step 1/5 typecheck");
284
- await runNodeBin("typescript/bin/tsc", ["--noEmit"]);
285
-
286
- console.log("[release] step 2/5 build (tsc → dist/)");
287
- await runNodeBin("typescript/bin/tsc", []);
288
-
289
- console.log("[release] step 3/5 bundle (esbuild → out/assets/)");
290
- await runNodeScript("scripts/bundle.mjs", rawArgs);
291
-
292
- console.log("[release] step 4/5 bake (out/baked/, bundled shell)");
293
- await runNodeScript("scripts/bake.mjs", [
294
- ...rawArgs,
295
- "--template",
296
- bundledHtml,
297
- "--out",
298
- bakedDir,
299
- ]);
300
-
301
- console.log("[release] step 5/5 copy public/ → out/");
302
- if (existsSync(publicDir)) {
303
- await mkdir(outDir, { recursive: true });
304
- await cp(publicDir, outDir, { recursive: true });
305
- console.log(`[release] copied ${publicDir} → ${outDir}`);
306
- } else {
307
- console.log(`[release] no ${publicDir}, skipping`);
308
- }
309
-
310
- const promoted = await promoteBakedHtml(bakedDir, outDir);
311
- if (promoted.html > 0) {
312
- console.log(`[release] promoted ${promoted.html} baked HTML page(s) into out/`);
313
- }
314
- if (promoted.sitemap) {
315
- console.log(`[release] copied sitemap.xml → ${join(outDir, "sitemap.xml")}`);
316
- }
317
-
318
- // Optional CDN config files. Generated only when not already provided.
319
- await writeIfMissing(
320
- join(outDir, "_redirects"),
321
- // Cloudflare Pages / Netlify: SPA fallback so deep links work after a
322
- // hard refresh. Baked HTML files are matched first because of
323
- // `force: false` / static-priority rules on these hosts.
324
- "/* /index.html 200\n",
325
- );
326
- await writeIfMissing(
327
- join(outDir, "_headers"),
328
- [
329
- "/assets/*",
330
- " Cache-Control: public, max-age=31536000, immutable",
331
- "",
332
- "/*.html",
333
- " Cache-Control: no-cache, must-revalidate",
334
- "",
335
- ].join("\n"),
336
- );
337
-
338
- console.log("");
339
- console.log(`[release] done. Deploy artifact: ${outDir}`);
340
- console.log("[release] try: mado preview");
341
- }
342
-
343
- async function writeIfMissing(path, content) {
344
- if (existsSync(path)) return;
345
- await writeFile(path, content);
346
- console.log(`[release] wrote ${path}`);
347
- }
348
-
349
- async function promoteBakedHtml(bakedDir, outDir) {
350
- if (!existsSync(bakedDir)) return { html: 0, sitemap: false };
351
-
352
- let html = 0;
353
-
354
- async function walk(dir, rel = "") {
355
- for (const entry of await readdir(dir, { withFileTypes: true })) {
356
- const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
357
- const source = join(dir, entry.name);
358
- if (entry.isDirectory()) {
359
- await walk(source, nextRel);
360
- continue;
361
- }
362
- if (!entry.isFile() || !entry.name.endsWith(".html")) continue;
363
- const target = join(outDir, nextRel);
364
- await mkdir(dirname(target), { recursive: true });
365
- await copyFile(source, target);
366
- html++;
367
- }
368
- }
369
-
370
- await walk(bakedDir);
371
-
372
- const bakedSitemap = join(bakedDir, "sitemap.xml");
373
- const rootSitemap = join(outDir, "sitemap.xml");
374
- let sitemap = false;
375
- if (existsSync(bakedSitemap)) {
376
- await copyFile(bakedSitemap, rootSitemap);
377
- sitemap = true;
378
- }
379
-
380
- return { html, sitemap };
381
- }
382
-
383
- async function copyCanonicalAgentFiles(target) {
384
- for (const file of ["AGENTS.md", "llms.txt"]) {
385
- const source = join(PACKAGE_ROOT, file);
386
- if (existsSync(source)) await copyFile(source, join(target, file));
387
- }
388
- }
389
-
390
- async function ensureStarterGitignore(target) {
391
- const file = join(target, ".gitignore");
392
- if (existsSync(file)) return;
393
- await writeFile(file, "node_modules\ndist\nout\n.DS_Store\n*.log\n");
394
- }
395
-
396
- async function ensureStarterPackageJson(target) {
397
- const file = join(target, "package.json");
398
- if (!existsSync(file)) return;
399
-
400
- const pkg = JSON.parse(await readFile(file, "utf8"));
401
- const rootDev = PACKAGE_JSON.devDependencies ?? {};
402
- pkg.devDependencies = {
403
- ...(pkg.devDependencies ?? {}),
404
- typescript: rootDev.typescript ?? "^6.0.3",
405
- esbuild: rootDev.esbuild ?? "^0.28.0",
406
- linkedom: rootDev.linkedom ?? "^0.18.12",
407
- };
408
-
409
- await writeFile(file, `${JSON.stringify(pkg, null, 2)}\n`);
410
- }
411
-
412
- async function runNodeBin(bin, args) {
413
- await run(process.execPath, [resolveBin(bin), ...args]);
414
- }
415
-
416
- async function runNodeScript(script, args) {
417
- await run(process.execPath, [join(PACKAGE_ROOT, script), ...args]);
418
- }
419
-
420
- async function run(cmd, args, options = {}) {
421
- const child = spawn(cmd, args, {
422
- cwd: PROJECT_ROOT,
423
- stdio: "inherit",
424
- env: options.env ?? process.env,
425
- shell: options.shell ?? false,
426
- });
427
- const code = await new Promise((resolveExit) => {
428
- child.on("exit", (status) => resolveExit(status ?? 1));
429
- });
430
- if (code !== 0) process.exit(code);
431
- }
432
-
433
- function resolveBin(bin) {
434
- const projectPath = join(PROJECT_ROOT, "node_modules", bin);
435
- if (existsSync(projectPath)) return projectPath;
436
- const path = join(PACKAGE_ROOT, "node_modules", bin);
437
- if (!existsSync(path)) {
438
- console.error(`[mado] missing ${bin}. Run npm install first.`);
439
- process.exit(1);
440
- }
441
- return path;
442
- }
443
-
444
- function assertExample(name, { serveable }) {
445
- const dir = join(PROJECT_ROOT, "examples", name);
446
- if (!existsSync(dir)) {
447
- console.error(`[mado] unknown example: ${name}`);
448
- printExamples();
449
- process.exit(1);
450
- }
451
- if (serveable && !existsSync(join(dir, "index.html"))) {
452
- console.error(`[mado] example "${name}" is not a browser SPA example.`);
453
- process.exit(1);
454
- }
455
- }
456
-
457
- function printExamples() {
458
- console.log("Available examples:");
459
- for (const [name, description] of EXAMPLES) {
460
- const marker = existsSync(join(PROJECT_ROOT, "examples", name, "index.html")) ? "serve" : "docs";
461
- console.log(` ${name.padEnd(10)} ${marker.padEnd(5)} ${description}`);
462
- }
463
- }
464
-
465
- async function listTestFiles() {
466
- const dir = join(PROJECT_ROOT, "test");
467
- const files = await readdir(dir);
468
- return files
469
- .filter((file) => file.endsWith(".test.mjs"))
470
- .sort()
471
- .map((file) => join("test", file));
472
- }
473
-
474
- function printHelp() {
475
- const ctx = IS_REPO ? "repo-mode (framework repository)" : "app-mode";
476
- console.log(`mado commands (${ctx}):
477
-
478
- Project lifecycle:
479
- mado init <name> [--starter minimal|crud|admin] [--force]
480
- scaffold a new app
481
- mado dev tsc -w + dev server with HMR
482
- mado build tsc (writes dist/)
483
- mado typecheck tsc --noEmit
484
- mado test [browser] run unit tests (or browser regression)
485
-
486
- Production:
487
- mado bundle esbuild → out/assets/ (hashed bundles)
488
- mado bake [--entry <file>] [--template <html>] [--out <dir>] [--base-url <url>]
489
- prerender baked routes → out/baked/
490
- mado release typecheck + build + bundle + bake + copy public/ → out/
491
- ← the one command for "ship it"
492
- mado preview serve exactly out/ locally (production rehearsal)
493
- mado serve [example] simple static server (also runs in repo-mode for examples)
494
-
495
- Generators:
496
- mado new <list|form|detail> <name>
497
-
498
- Misc:
499
- mado examples list bundled examples
500
- mado help this screen
501
-
502
- Configuration:
503
- mado reads ./mado.config.json (dev.proxy, build.out, bake.entry/template/baseUrl, …)
504
- CLI flags > mado.config.json > built-in defaults.
505
-
506
- See MADO_V1_PLAN.md for the road to v1.`);
507
- }
508
-
509
- function parseFlags(raw) {
510
- const flags = {};
511
- const positional = [];
512
- for (let i = 0; i < raw.length; i++) {
513
- const arg = raw[i];
514
- if (arg === "--") continue;
515
- if (arg.startsWith("--")) {
516
- const [name, inline] = arg.slice(2).split("=");
517
- if (inline !== undefined) flags[name] = inline;
518
- else if (raw[i + 1] && !raw[i + 1].startsWith("-")) flags[name] = raw[++i];
519
- else flags[name] = true;
520
- } else {
521
- positional.push(arg);
522
- }
523
- }
524
- return { flags, positional };
525
- }
526
-
527
- /**
528
- * Split args for `mado dev` / `mado serve` into:
529
- * - example: the first non-flag positional (or undefined)
530
- * - forwarded: every remaining token (flags, their values, leftover
531
- * positionals), preserved in order so server/serve.mjs sees them
532
- * unchanged.
533
- *
534
- * This is what lets `mado dev -- --host 127.0.0.1` and
535
- * `mado dev showcase --port 6000` both work without the CLI confusing
536
- * `--host` for an example name.
537
- */
538
- function splitDevArgs(raw) {
539
- if (!Array.isArray(raw) || raw.length === 0) {
540
- return { example: "", forwarded: [] };
541
- }
542
- let example = "";
543
- const forwarded = [];
544
- let pickedExample = false;
545
- for (let i = 0; i < raw.length; i++) {
546
- const a = raw[i];
547
- if (a === "--") {
548
- forwarded.push(...raw.slice(i + 1));
549
- break;
550
- }
551
- if (a.startsWith("-")) {
552
- forwarded.push(a);
553
- // Lookahead: if the next token is the flag's VALUE (does not start with
554
- // "-"), forward it too — but only when the flag is in inline form
555
- // (--flag value), not --flag=value.
556
- if (!a.includes("=") && raw[i + 1] !== undefined && !raw[i + 1].startsWith("-")) {
557
- forwarded.push(raw[++i]);
558
- }
559
- continue;
560
- }
561
- if (!pickedExample) {
562
- example = a;
563
- pickedExample = true;
564
- } else {
565
- forwarded.push(a);
566
- }
567
- }
568
- return { example, forwarded };
569
- }
570
-
571
- function packageNameFromDir(target) {
572
- return target
573
- .split(/[\\/]/)
574
- .filter(Boolean)
575
- .at(-1)
576
- ?.toLowerCase()
577
- .replace(/[^a-z0-9._-]+/g, "-")
578
- .replace(/^-+|-+$/g, "") || "mado-app";
579
- }
580
-
581
- function isValidPackageName(name) {
582
- return /^(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(name)
583
- && !name.includes("..")
584
- && !name.startsWith(".")
585
- && name.length <= 214;
586
- }
587
-
588
- async function walkFiles(dir) {
589
- const out = [];
590
- for (const entry of readdirSync(dir)) {
591
- if (["node_modules", "dist", ".git"].includes(entry)) continue;
592
- if (entry === "package-lock.json") continue;
593
- const file = join(dir, entry);
594
- const stat = statSync(file);
595
- if (stat.isDirectory()) out.push(...await walkFiles(file));
596
- else out.push(file);
597
- }
598
- return out;
599
- }
600
-
601
- function relativePath(from, to) {
602
- const rel = to.startsWith(from) ? to.slice(from.length).replace(/^[/\\]/, "") : to;
603
- return rel || ".";
604
- }
605
-
606
- function contentType(file) {
607
- const ext = file.slice(file.lastIndexOf("."));
608
- return {
609
- ".html": "text/html; charset=utf-8",
610
- ".js": "text/javascript; charset=utf-8",
611
- ".mjs": "text/javascript; charset=utf-8",
612
- ".css": "text/css; charset=utf-8",
613
- ".json": "application/json; charset=utf-8",
614
- ".svg": "image/svg+xml",
615
- ".png": "image/png",
616
- ".jpg": "image/jpeg",
617
- ".jpeg": "image/jpeg",
618
- ".webp": "image/webp",
619
- ".ico": "image/x-icon",
620
- }[ext] ?? "application/octet-stream";
621
- }
622
-
623
- // serveStaticProject removed in v0.7 — mado serve now always goes through
624
- // server/serve.mjs to get --host, --port, dev.proxy, and HMR support.
5
+ await main(process.argv.slice(2));
@@ -29,9 +29,11 @@ try {
29
29
  `
30
30
  import { html, signal } from "@madojs/mado";
31
31
  import "@madojs/mado/devtools.js";
32
+ import { mado } from "@madojs/mado/vite";
32
33
  if (typeof html !== "function" || typeof signal !== "function") {
33
34
  throw new Error("public root import failed");
34
35
  }
36
+ if (typeof mado !== "function") throw new Error("vite plugin import failed");
35
37
  try {
36
38
  await import("@madojs/mado/lifecycle.js");
37
39
  throw new Error("internal lifecycle subpath unexpectedly resolved");
@@ -43,13 +45,14 @@ try {
43
45
  { cwd: installRoot },
44
46
  );
45
47
 
46
- await run("npx", ["mado", "init", "smoke-app", "--starter", "minimal"], {
48
+ await run("npx", ["mado", "init", "smoke-app"], {
47
49
  cwd: installRoot,
48
50
  env: { ...process.env, MADO_PACKAGE_SPEC: tarball },
49
51
  });
50
52
 
51
53
  const appRoot = join(installRoot, "smoke-app");
52
54
  await run("npm", ["install"], { cwd: appRoot });
55
+ await run("npm", ["run", "new", "--", "module", "smoke"], { cwd: appRoot });
53
56
  await run("npm", ["run", "release"], { cwd: appRoot });
54
57
 
55
58
  console.log(`[package-smoke] ok ${basename(tarball)}`);