@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.
- package/AGENTS.md +24 -26
- package/CHANGELOG.md +95 -0
- package/README.md +22 -47
- package/TODO.md +52 -48
- package/dist/src/component.d.ts +2 -1
- package/dist/src/component.js +5 -2
- package/dist/src/component.js.map +1 -1
- package/dist/src/each.d.ts +1 -1
- package/dist/src/each.js +1 -1
- package/dist/src/each.js.map +1 -1
- package/dist/src/html/bindings.js +3 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/index.d.ts +11 -6
- package/dist/src/index.js +5 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/lazy.d.ts +1 -1
- package/dist/src/lazy.js +1 -1
- package/dist/src/lazy.js.map +1 -1
- package/dist/src/page.d.ts +17 -21
- package/dist/src/page.js +7 -12
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -1
- package/dist/src/router/manifest.js +21 -13
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +2 -2
- package/dist/src/router/match.js +3 -3
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +1 -1
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/vite/index.d.ts +10 -0
- package/dist/src/vite/index.js +33 -0
- package/dist/src/vite/index.js.map +1 -0
- package/docs/en/00-the-mado-way.md +25 -12
- package/docs/en/01-routing.md +90 -142
- package/docs/en/02-project-layout.md +59 -53
- package/docs/en/03-static-bake.md +5 -6
- package/docs/en/05-why-mado.md +6 -6
- package/docs/en/06-for-backenders.md +18 -22
- package/docs/en/08-llm-zero-history-test.md +9 -14
- package/docs/en/09-shadow-vs-light-dom.md +28 -36
- package/docs/en/10-app-architecture.md +158 -96
- package/docs/en/11-layouts.md +22 -24
- package/docs/en/12-auth-and-api.md +89 -182
- package/docs/en/13-deployment.md +18 -22
- package/docs/en/14-testing.md +4 -4
- package/docs/en/16-bake-cookbook.md +11 -12
- package/docs/en/18-api-freeze-map.md +6 -4
- package/docs/en/20-v1-stability.md +1 -1
- package/docs/fr/00-the-mado-way.md +55 -90
- package/docs/fr/01-routing.md +70 -152
- package/docs/fr/02-project-layout.md +61 -42
- package/docs/fr/03-static-bake.md +1 -1
- package/docs/fr/05-why-mado.md +6 -6
- package/docs/fr/06-for-backenders.md +7 -7
- package/docs/fr/08-llm-zero-history-test.md +21 -48
- package/docs/fr/09-shadow-vs-light-dom.md +43 -162
- package/docs/fr/10-app-architecture.md +110 -33
- package/docs/fr/11-layouts.md +24 -12
- package/docs/fr/12-auth-and-api.md +63 -22
- package/docs/fr/13-deployment.md +7 -10
- package/docs/fr/14-testing.md +1 -1
- package/docs/fr/16-bake-cookbook.md +2 -2
- package/docs/fr/18-api-freeze-map.md +1 -1
- package/docs/fr/20-v1-stability.md +1 -1
- package/docs/recipes/nginx/README.md +13 -0
- package/docs/ru/00-the-mado-way.md +53 -75
- package/docs/ru/01-routing.md +68 -143
- package/docs/ru/02-project-layout.md +61 -41
- package/docs/ru/03-static-bake.md +2 -2
- package/docs/ru/05-why-mado.md +6 -6
- package/docs/ru/06-for-backenders.md +7 -7
- package/docs/ru/08-llm-zero-history-test.md +9 -14
- package/docs/ru/09-shadow-vs-light-dom.md +43 -178
- package/docs/ru/10-app-architecture.md +115 -63
- package/docs/ru/11-layouts.md +24 -24
- package/docs/ru/12-auth-and-api.md +57 -35
- package/docs/ru/13-deployment.md +7 -11
- package/docs/ru/14-testing.md +1 -1
- package/docs/ru/16-bake-cookbook.md +12 -6
- package/docs/ru/18-api-freeze-map.md +5 -3
- package/docs/ru/20-v1-stability.md +1 -1
- package/docs/uk/00-the-mado-way.md +70 -44
- package/docs/uk/01-routing.md +41 -47
- package/docs/uk/02-project-layout.md +68 -41
- package/docs/uk/03-static-bake.md +1 -2
- package/docs/uk/06-for-backenders.md +3 -3
- package/docs/uk/08-llm-zero-history-test.md +22 -24
- package/docs/uk/09-shadow-vs-light-dom.md +37 -86
- package/docs/uk/10-app-architecture.md +72 -31
- package/docs/uk/11-layouts.md +25 -12
- package/docs/uk/12-auth-and-api.md +58 -22
- package/docs/uk/13-deployment.md +4 -3
- package/docs/uk/14-testing.md +1 -1
- package/docs/uk/18-api-freeze-map.md +1 -1
- package/docs/uk/20-v1-stability.md +1 -1
- package/llms.txt +14 -15
- package/package.json +18 -11
- package/scripts/_config.mjs +15 -161
- package/scripts/bake.mjs +74 -63
- package/scripts/cli/generate.mjs +348 -0
- package/scripts/cli/help.mjs +27 -0
- package/scripts/cli/index.mjs +79 -0
- package/scripts/cli/init.mjs +153 -0
- package/scripts/cli/release.mjs +152 -0
- package/scripts/cli/run.mjs +96 -0
- package/scripts/cli.mjs +2 -621
- package/scripts/package-smoke.mjs +4 -1
- package/scripts/preview.mjs +13 -37
- package/scripts/size-budget.mjs +5 -2
- package/scripts/vite.default.mjs +11 -0
- package/starters/default/.editorconfig +12 -0
- package/starters/default/README.md +74 -0
- package/starters/default/eslint.config.mjs +256 -0
- package/starters/default/index.html +13 -0
- package/starters/default/package.json +30 -0
- package/starters/default/public/favicon.svg +4 -0
- package/starters/default/src/app.routes.ts +39 -0
- package/starters/default/src/layouts/app-shell.layout.ts +35 -0
- package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
- package/starters/default/src/main.ts +16 -0
- package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
- package/starters/default/src/modules/auth/auth.connector.ts +45 -0
- package/starters/default/src/modules/auth/auth.guard.ts +22 -0
- package/starters/default/src/modules/auth/auth.public.ts +9 -0
- package/starters/default/src/modules/auth/auth.routes.ts +8 -0
- package/starters/default/src/modules/auth/auth.service.ts +71 -0
- package/starters/default/src/modules/auth/auth.types.ts +15 -0
- package/starters/default/src/modules/auth/login.page.ts +62 -0
- package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
- package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
- package/starters/default/src/modules/billing/billing.public.ts +5 -0
- package/starters/default/src/modules/billing/billing.routes.ts +9 -0
- package/starters/default/src/modules/billing/billing.types.ts +15 -0
- package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
- package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
- package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
- package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
- package/starters/default/src/modules/home/home.page.ts +34 -0
- package/starters/default/src/modules/home/not-found.page.ts +11 -0
- package/starters/default/src/shared/http/http-client.ts +86 -0
- package/starters/default/src/shared/http/http-error.ts +37 -0
- package/starters/default/src/shared/http/interceptors.ts +59 -0
- package/starters/default/src/shared/lib/format-date.ts +19 -0
- package/starters/default/src/shared/styles/content.css +70 -0
- package/starters/default/src/shared/styles/reset.css +32 -0
- package/starters/default/src/shared/styles/shell.css +57 -0
- package/starters/default/src/shared/styles/tokens.css +44 -0
- package/starters/default/src/shared/ui/x-button.component.ts +49 -0
- package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
- package/starters/default/src/styles.d.ts +1 -0
- package/starters/default/src/vite-env.d.ts +1 -0
- package/starters/default/tsconfig.json +24 -0
- package/starters/default/vite.config.ts +9 -0
- package/MADO_V1_PLAN.md +0 -179
- package/ROADMAP.md +0 -178
- package/dist/src/html.d.ts +0 -18
- package/dist/src/html.js +0 -17
- package/dist/src/html.js.map +0 -1
- package/dist/src/router.d.ts +0 -13
- package/dist/src/router.js +0 -13
- package/dist/src/router.js.map +0 -1
- package/scripts/bundle.mjs +0 -212
- package/scripts/llm-zero-history-smoke.mjs +0 -93
- package/scripts/new.mjs +0 -80
- package/scripts/showcase-regression.mjs +0 -392
- package/server/serve.mjs +0 -455
- package/starters/admin/README.md +0 -63
- package/starters/admin/index.html +0 -28
- package/starters/admin/mado.config.json +0 -22
- package/starters/admin/package.json +0 -24
- package/starters/admin/public/favicon.svg +0 -4
- package/starters/admin/src/components/x-button.ts +0 -82
- package/starters/admin/src/components/x-input.ts +0 -105
- package/starters/admin/src/layouts/app.ts +0 -101
- package/starters/admin/src/layouts/auth.ts +0 -41
- package/starters/admin/src/lib/api.ts +0 -184
- package/starters/admin/src/lib/auth.ts +0 -83
- package/starters/admin/src/main.ts +0 -15
- package/starters/admin/src/pages/admin/dashboard.ts +0 -48
- package/starters/admin/src/pages/admin/order-detail.ts +0 -80
- package/starters/admin/src/pages/admin/orders.ts +0 -117
- package/starters/admin/src/pages/home.ts +0 -34
- package/starters/admin/src/pages/login.ts +0 -70
- package/starters/admin/src/pages/not-found.ts +0 -12
- package/starters/admin/src/routes.ts +0 -40
- package/starters/admin/src/styles/global.ts +0 -86
- package/starters/admin/tsconfig.json +0 -15
- package/starters/crud/README.md +0 -33
- package/starters/crud/index.html +0 -28
- package/starters/crud/mado.config.json +0 -20
- package/starters/crud/package.json +0 -24
- package/starters/crud/src/components/app-shell.ts +0 -56
- package/starters/crud/src/components/ticket-detail.ts +0 -33
- package/starters/crud/src/components/ticket-form.ts +0 -69
- package/starters/crud/src/components/ticket-list.ts +0 -66
- package/starters/crud/src/lib/api.ts +0 -76
- package/starters/crud/src/main.ts +0 -9
- package/starters/crud/src/pages/home.ts +0 -34
- package/starters/crud/src/pages/not-found.ts +0 -12
- package/starters/crud/src/pages/ticket-detail.ts +0 -7
- package/starters/crud/src/pages/ticket-new.ts +0 -7
- package/starters/crud/src/pages/tickets.ts +0 -7
- package/starters/crud/src/routes.ts +0 -11
- package/starters/crud/src/styles/global.ts +0 -155
- package/starters/crud/tsconfig.json +0 -15
- package/starters/minimal/README.md +0 -21
- package/starters/minimal/index.html +0 -28
- package/starters/minimal/mado.config.json +0 -20
- package/starters/minimal/package.json +0 -24
- package/starters/minimal/src/components/app-counter.ts +0 -31
- package/starters/minimal/src/main.ts +0 -9
- package/starters/minimal/src/pages/home.ts +0 -35
- package/starters/minimal/src/pages/not-found.ts +0 -14
- package/starters/minimal/src/routes.ts +0 -8
- package/starters/minimal/src/styles/global.ts +0 -60
- package/starters/minimal/tsconfig.json +0 -15
- package/templates/page-detail.ts +0 -63
- package/templates/page-form.ts +0 -94
- 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 {
|
|
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
|
-
|
|
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"
|
|
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)}`);
|