@madojs/mado 0.12.0 → 0.13.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 +15 -8
- package/CHANGELOG.md +66 -0
- package/README.md +25 -3
- package/dist/src/component.d.ts +3 -1
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +65 -30
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +2 -1
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +3 -4
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +63 -14
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +78 -3
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +7 -8
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +1 -0
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +126 -49
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +1 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +89 -148
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +1 -0
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +19 -5
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +1 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js.map +1 -1
- package/dist/src/vite/index.d.ts +1 -0
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +4 -3
- package/dist/src/vite/index.js.map +1 -1
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/10-pages-and-components.md +9 -1
- package/docs/en/13-data.md +8 -6
- package/docs/en/14-forms.md +66 -219
- package/docs/en/15-static-snapshots.md +4 -4
- package/docs/en/16-app-architecture.md +3 -2
- package/docs/en/20-deployment.md +21 -8
- package/docs/en/21-error-handling.md +9 -6
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/30-api-freeze-map.md +8 -9
- package/docs/en/32-v1-stability.md +2 -2
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +7 -9
- package/docs/en/41-for-backenders.md +13 -9
- package/docs/en/42-why-mado.md +2 -2
- package/docs/en/README.md +2 -0
- package/docs/recipes/nginx/nginx.conf +2 -2
- package/llms.txt +25 -7
- package/package.json +37 -16
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +11 -14
- package/scripts/cli/help.mjs +2 -0
- package/scripts/cli/index.mjs +14 -6
- package/scripts/cli/init.mjs +13 -18
- package/scripts/cli/release.mjs +22 -19
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +11 -19
- package/scripts/static/browser.mjs +58 -8
- package/scripts/static/discover.mjs +4 -1
- package/scripts/static.mjs +21 -7
- package/starters/default/package.json +4 -4
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +0 -3
- package/starters/modular/README.md +4 -4
- package/starters/modular/package.json +4 -4
- package/starters/modular/src/layouts/app-shell.layout.ts +1 -2
- package/starters/modular/src/modules/auth/login.page.ts +5 -12
- package/starters/modular/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/modular/src/modules/billing/pages/invoices-list.page.ts +1 -1
- package/starters/modular/src/shared/styles/reset.css +21 -1
- package/starters/modular/tsconfig.json +10 -1
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +6 -5
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/scripts/bake.mjs +0 -5
- package/scripts/docs-lint.mjs +0 -170
- package/scripts/package-smoke.mjs +0 -135
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -95
- package/starters/modular/src/shared/ui/x-button.component.ts +0 -49
package/docs/en/40-llm-guide.md
CHANGED
|
@@ -279,16 +279,15 @@ This means:
|
|
|
279
279
|
// ❌ No such API
|
|
280
280
|
const f = useForm({ resolver: zodResolver(schema) });
|
|
281
281
|
|
|
282
|
-
// ✅
|
|
282
|
+
// ✅ Native constraints in markup, reactive state in Mado
|
|
283
283
|
const f = useForm({
|
|
284
|
-
|
|
285
|
-
age: { required: true, type: "number", min: 18 },
|
|
284
|
+
initial: { email: "", age: "" as number | "" },
|
|
286
285
|
});
|
|
286
|
+
html`<input name="email" type="email" required @input=${f.onInput} />`;
|
|
287
287
|
|
|
288
288
|
// ✅ Or a custom function when HTML5 isn't enough
|
|
289
|
-
const f = useForm(
|
|
290
|
-
|
|
291
|
-
{
|
|
289
|
+
const f = useForm({
|
|
290
|
+
initial: { name: "" },
|
|
292
291
|
validate: (values) => {
|
|
293
292
|
const errors: Record<string, string> = {};
|
|
294
293
|
if (values.name && /\d/.test(values.name as string)) {
|
|
@@ -296,8 +295,7 @@ const f = useForm(
|
|
|
296
295
|
}
|
|
297
296
|
return Object.keys(errors).length ? errors : null;
|
|
298
297
|
},
|
|
299
|
-
|
|
300
|
-
);
|
|
298
|
+
});
|
|
301
299
|
```
|
|
302
300
|
|
|
303
301
|
---
|
|
@@ -705,7 +703,7 @@ automatically on navigation. Only raw browser APIs need explicit
|
|
|
705
703
|
| `useRouter().query.q` | `queryParam('q')` |
|
|
706
704
|
| `<input value={v} onChange={...}>` | `<input .value=${v} @input=${...}>` |
|
|
707
705
|
| `{items.map(x => ...)}` | `${() => each(items, x => x.id, x => ...)}` |
|
|
708
|
-
| `useForm({ resolver: zodResolver })` | `useForm({
|
|
706
|
+
| `useForm({ resolver: zodResolver })` | `useForm({ initial, validate })` + native constraints |
|
|
709
707
|
| `class extends HTMLElement` | `component('x-name', setup)` |
|
|
710
708
|
| `@customElement('x')` | `component('x-name', setup)` |
|
|
711
709
|
| `host.getAttribute('x')` in render | `ctx.attr('x', default)` (reactive) |
|
|
@@ -195,14 +195,13 @@ We register the `<x-counter>` tag in the browser — it becomes a "function" tha
|
|
|
195
195
|
|
|
196
196
|
## Forms — like `form.Validate()` on the backend
|
|
197
197
|
|
|
198
|
-
Mado uses **
|
|
198
|
+
Mado uses the browser's **constraint validation** and adds reactive state tracking.
|
|
199
199
|
|
|
200
200
|
```ts
|
|
201
201
|
import { useForm } from "@madojs/mado";
|
|
202
202
|
|
|
203
203
|
const f = useForm({
|
|
204
|
-
|
|
205
|
-
age: { required: true, type: "number", min: 18 },
|
|
204
|
+
initial: { email: "", age: "" as number | "" },
|
|
206
205
|
});
|
|
207
206
|
|
|
208
207
|
// in the template:
|
|
@@ -215,6 +214,8 @@ html`
|
|
|
215
214
|
>
|
|
216
215
|
<input
|
|
217
216
|
name="email"
|
|
217
|
+
type="email"
|
|
218
|
+
required
|
|
218
219
|
.value=${() => f.values().email ?? ""}
|
|
219
220
|
@input=${f.onInput}
|
|
220
221
|
@blur=${f.onBlur}
|
|
@@ -225,12 +226,13 @@ html`
|
|
|
225
226
|
? html`<small>${f.errors().email}</small>`
|
|
226
227
|
: null}
|
|
227
228
|
|
|
228
|
-
<
|
|
229
|
+
<input name="age" type="number" min="18" @input=${f.onInput} />
|
|
230
|
+
<button type="submit" ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
|
|
229
231
|
</form>
|
|
230
232
|
`;
|
|
231
233
|
```
|
|
232
234
|
|
|
233
|
-
Custom validation — `validate: (values) => errors | null`. No Yup schemas or dependencies.
|
|
235
|
+
Custom validation — `validate: (values, { signal }) => errors | null`. No Yup schemas or dependencies.
|
|
234
236
|
|
|
235
237
|
---
|
|
236
238
|
|
|
@@ -258,6 +260,8 @@ component("x-page", ({ host }) => {
|
|
|
258
260
|
```
|
|
259
261
|
|
|
260
262
|
This is like `context.WithValue` / `ctx.Value` in Go, but reactive.
|
|
263
|
+
The transport is the interoperable Web Components `context-request` event, so
|
|
264
|
+
providers and consumers can cross library boundaries.
|
|
261
265
|
|
|
262
266
|
---
|
|
263
267
|
|
|
@@ -335,7 +339,7 @@ const createUser = mutation<NewUser, User>(
|
|
|
335
339
|
);
|
|
336
340
|
|
|
337
341
|
// in page.view:
|
|
338
|
-
const f = useForm({
|
|
342
|
+
const f = useForm({ initial: { name: "" } });
|
|
339
343
|
|
|
340
344
|
html`
|
|
341
345
|
<form
|
|
@@ -344,8 +348,8 @@ html`
|
|
|
344
348
|
navigate("/users");
|
|
345
349
|
})}
|
|
346
350
|
>
|
|
347
|
-
<input name="name" @input=${f.onInput} />
|
|
348
|
-
<button>Create</button>
|
|
351
|
+
<input name="name" required @input=${f.onInput} />
|
|
352
|
+
<button type="submit">Create</button>
|
|
349
353
|
</form>
|
|
350
354
|
`;
|
|
351
355
|
```
|
|
@@ -426,7 +430,7 @@ Everything else — standard browser + TypeScript.
|
|
|
426
430
|
|
|
427
431
|
## What's missing (honestly)
|
|
428
432
|
|
|
429
|
-
-
|
|
433
|
+
- The built-in overlay covers runtime inspection; a browser extension remains post-v1 work.
|
|
430
434
|
- No StackBlitz starters (yet).
|
|
431
435
|
- No AI assistant that knows Mado as well as React. When in doubt — read `src/`, it's not scary.
|
|
432
436
|
|
package/docs/en/42-why-mado.md
CHANGED
|
@@ -138,7 +138,7 @@ I won't dwell on this for long, because React is in a **different weight class**
|
|
|
138
138
|
**Mado wins:**
|
|
139
139
|
|
|
140
140
|
- bundle size dozens of times smaller;
|
|
141
|
-
-
|
|
141
|
+
- one conventional Vite transport without a framework compiler or Babel layer;
|
|
142
142
|
- readable in an evening — if something breaks, open `src/`;
|
|
143
143
|
- signals instead of hooks (no "can't use in an if" rules, no stale-closure traps);
|
|
144
144
|
- no need to migrate between major versions.
|
|
@@ -194,7 +194,7 @@ This is sufficient for most applications. If you're building Excel in the browse
|
|
|
194
194
|
Mado is a **narrow** tool with honest positioning. It is strongest where:
|
|
195
195
|
|
|
196
196
|
1. You want to **own** the code and read it in its entirety.
|
|
197
|
-
2. **Infrastructure simplicity** is critical (no
|
|
197
|
+
2. **Infrastructure simplicity** is critical (one Vite transport, no custom compiler).
|
|
198
198
|
3. You need **batteries in one box** (router + data + forms + SEO).
|
|
199
199
|
4. You are not a junior and are not afraid of Web Components.
|
|
200
200
|
|
package/docs/en/README.md
CHANGED
|
@@ -47,6 +47,7 @@ section you need from the matrix below.
|
|
|
47
47
|
| Error handling | [21-error-handling.md](./21-error-handling.md) |
|
|
48
48
|
| Testing | [22-testing.md](./22-testing.md) |
|
|
49
49
|
| Cookbook | [23-cookbook.md](./23-cookbook.md) |
|
|
50
|
+
| Devtools and diagnostics | [24-devtools-and-diagnostics.md](./24-devtools-and-diagnostics.md) |
|
|
50
51
|
|
|
51
52
|
### Reference
|
|
52
53
|
|
|
@@ -55,6 +56,7 @@ section you need from the matrix below.
|
|
|
55
56
|
| API freeze map | [30-api-freeze-map.md](./30-api-freeze-map.md) |
|
|
56
57
|
| Reactivity ordering | [31-reactivity-ordering.md](./31-reactivity-ordering.md) |
|
|
57
58
|
| v1 stability contract | [32-v1-stability.md](./32-v1-stability.md) |
|
|
59
|
+
| Migration 0.12 → 0.13 | [33-migration-0.12-0.13.md](./33-migration-0.12-0.13.md) |
|
|
58
60
|
|
|
59
61
|
### Meta
|
|
60
62
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# - Vite hashed assets from /assets/ with immutable caching;
|
|
5
5
|
# - precompressed .gz files written by `mado release`;
|
|
6
6
|
# - baked route HTML when present;
|
|
7
|
-
# -
|
|
7
|
+
# - _mado/spa.html as the noindex SPA fallback for client-only routes.
|
|
8
8
|
|
|
9
9
|
server {
|
|
10
10
|
listen 80;
|
|
@@ -37,6 +37,6 @@ server {
|
|
|
37
37
|
|
|
38
38
|
location / {
|
|
39
39
|
add_header Cache-Control "no-cache, must-revalidate";
|
|
40
|
-
try_files $uri $uri/ /
|
|
40
|
+
try_files $uri $uri/ /_mado/spa.html;
|
|
41
41
|
}
|
|
42
42
|
}
|
package/llms.txt
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
// reactivity
|
|
85
85
|
signal, computed, effect, untracked, batch, flushSync,
|
|
86
86
|
// rendering + templates
|
|
87
|
-
html, render,
|
|
87
|
+
html, render, unmount, each, unsafeHTML, ref, classMap, styleMap,
|
|
88
88
|
// components
|
|
89
89
|
component, css, cssVars,
|
|
90
90
|
// routing
|
|
@@ -94,14 +94,14 @@ import {
|
|
|
94
94
|
page, layout, applyHead,
|
|
95
95
|
// data
|
|
96
96
|
resource, mutation, invalidate, jsonFetcher, HttpError,
|
|
97
|
-
// forms / context / persistence
|
|
97
|
+
// forms / context / persistence
|
|
98
98
|
useForm,
|
|
99
99
|
createContext, provide, inject,
|
|
100
|
-
persisted,
|
|
100
|
+
persisted,
|
|
101
101
|
} from "@madojs/mado";
|
|
102
102
|
|
|
103
|
-
//
|
|
104
|
-
import "@madojs/mado/devtools.js";
|
|
103
|
+
// Development overlay/controller:
|
|
104
|
+
import { devtools } from "@madojs/mado/devtools.js";
|
|
105
105
|
|
|
106
106
|
// Vite plugin:
|
|
107
107
|
import { mado } from "@madojs/mado/vite";
|
|
@@ -110,6 +110,10 @@ import { mado } from "@madojs/mado/vite";
|
|
|
110
110
|
Everything else under the package is internal and subject to change
|
|
111
111
|
without notice (see `docs/en/30-api-freeze-map.md`).
|
|
112
112
|
|
|
113
|
+
`page.load` is synchronous: return a plain value or `Resource`, never a
|
|
114
|
+
Promise. Async work belongs in `resource()` so navigation can commit or roll
|
|
115
|
+
back lifecycle ownership transactionally.
|
|
116
|
+
|
|
113
117
|
## Pages and the static snapshot pipeline
|
|
114
118
|
|
|
115
119
|
A page declares its participation in static capture through the
|
|
@@ -219,11 +223,25 @@ const create = mutation((u) => api.create(u), {
|
|
|
219
223
|
});
|
|
220
224
|
```
|
|
221
225
|
|
|
222
|
-
|
|
223
|
-
in-flight request. `mutation().run()` is concurrent by default;
|
|
226
|
+
Cache identity is the key plus fetcher. `staleTime: 0` only de-dupes an
|
|
227
|
+
in-flight request; positive values expire automatically. `mutation().run()` is concurrent by default;
|
|
224
228
|
`{ abortPrevious: true }` keeps only the latest in flight.
|
|
225
229
|
`invalidates` runs after a successful mutation and is best-effort.
|
|
226
230
|
|
|
231
|
+
## Forms
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const form = useForm({ initial: { email: "" } });
|
|
235
|
+
html`<form @submit=${form.onSubmit(save)}>
|
|
236
|
+
<input name="email" type="email" required @input=${form.onInput} />
|
|
237
|
+
<button type="submit">Save</button>
|
|
238
|
+
</form>`;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Native constraints and `ValidityState` are authoritative. Custom async
|
|
242
|
+
validation is `validate(values, { signal, form })`; reset aborts it. Use native
|
|
243
|
+
controls for checkbox/radio groups, multiple select, files and keyboard submit.
|
|
244
|
+
|
|
227
245
|
## Static snapshot constraints
|
|
228
246
|
|
|
229
247
|
- `paths()` and `initialData()` run during discovery AND ship in the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@madojs/mado",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "A calm native-first web framework for sites and apps. Web Components, signals, routing, data, forms and browser-rendered static snapshots. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"mado": "./scripts/cli.mjs"
|
|
30
30
|
},
|
|
31
31
|
"main": "./dist/src/index.js",
|
|
32
|
-
"module": "./dist/src/index.js",
|
|
33
32
|
"types": "./dist/src/index.d.ts",
|
|
34
33
|
"exports": {
|
|
35
34
|
".": {
|
|
@@ -47,8 +46,16 @@
|
|
|
47
46
|
},
|
|
48
47
|
"files": [
|
|
49
48
|
"dist/src",
|
|
50
|
-
"scripts",
|
|
51
|
-
"
|
|
49
|
+
"scripts/cli.mjs",
|
|
50
|
+
"scripts/_config.mjs",
|
|
51
|
+
"scripts/clean.mjs",
|
|
52
|
+
"scripts/logger.mjs",
|
|
53
|
+
"scripts/output-guard.mjs",
|
|
54
|
+
"scripts/preview.mjs",
|
|
55
|
+
"scripts/static.mjs",
|
|
56
|
+
"scripts/vite.default.mjs",
|
|
57
|
+
"scripts/cli",
|
|
58
|
+
"scripts/static",
|
|
52
59
|
"starters",
|
|
53
60
|
"README.md",
|
|
54
61
|
"LICENSE",
|
|
@@ -60,6 +67,14 @@
|
|
|
60
67
|
"docs/architecture/**/*.md",
|
|
61
68
|
"docs/recipes/**/*"
|
|
62
69
|
],
|
|
70
|
+
"sideEffects": [
|
|
71
|
+
"./dist/src/devtools.js"
|
|
72
|
+
],
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public",
|
|
75
|
+
"provenance": true
|
|
76
|
+
},
|
|
77
|
+
"packageManager": "npm@11.16.0",
|
|
63
78
|
"scripts": {
|
|
64
79
|
"build": "node scripts/cli.mjs build",
|
|
65
80
|
"watch": "node scripts/cli.mjs watch",
|
|
@@ -69,23 +84,26 @@
|
|
|
69
84
|
"new": "node scripts/cli.mjs new",
|
|
70
85
|
"size": "node scripts/size-budget.mjs",
|
|
71
86
|
"package:smoke": "node scripts/package-smoke.mjs",
|
|
72
|
-
"docs:lint": "node scripts/docs-lint.mjs",
|
|
87
|
+
"docs:lint": "node scripts/docs-lint.mjs && node node_modules/typescript/bin/tsc --noEmit -p tsconfig.docs.json",
|
|
88
|
+
"api:check": "node scripts/api-check.mjs",
|
|
89
|
+
"benchmark:static": "node scripts/benchmark-static.mjs",
|
|
73
90
|
"test": "node scripts/cli.mjs test",
|
|
74
91
|
"typecheck": "node scripts/cli.mjs typecheck",
|
|
75
|
-
"clean": "
|
|
76
|
-
"
|
|
92
|
+
"clean": "node scripts/clean.mjs",
|
|
93
|
+
"verify:release": "npm run clean && npm test && npm run docs:lint && npm run api:check && npm run size && npm run package:smoke && npm audit --audit-level=high",
|
|
94
|
+
"prepack": "node scripts/clean.mjs --dist-only && npm run build",
|
|
95
|
+
"prepublishOnly": "npm run verify:release"
|
|
77
96
|
},
|
|
78
97
|
"devDependencies": {
|
|
79
|
-
"@types/node": "^
|
|
80
|
-
"esbuild": "^0.28.
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"vite": "^8.0.16"
|
|
98
|
+
"@types/node": "^22.12.0",
|
|
99
|
+
"esbuild": "^0.28.1",
|
|
100
|
+
"linkedom": "^0.18.13",
|
|
101
|
+
"playwright-core": "^1.61.1",
|
|
102
|
+
"typescript": "^7.0.2",
|
|
103
|
+
"vite": "^8.1.4"
|
|
86
104
|
},
|
|
87
105
|
"peerDependencies": {
|
|
88
|
-
"playwright-core": "^1.
|
|
106
|
+
"playwright-core": "^1.61.1",
|
|
89
107
|
"vite": "^8.0.0"
|
|
90
108
|
},
|
|
91
109
|
"peerDependenciesMeta": {
|
|
@@ -97,6 +115,9 @@
|
|
|
97
115
|
}
|
|
98
116
|
},
|
|
99
117
|
"engines": {
|
|
100
|
-
"node": ">=
|
|
118
|
+
"node": ">=22.12"
|
|
119
|
+
},
|
|
120
|
+
"allowScripts": {
|
|
121
|
+
"esbuild@0.28.1": true
|
|
101
122
|
}
|
|
102
123
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const root = resolve(import.meta.dirname, "..");
|
|
5
|
+
const targets = process.argv.includes("--dist-only") ? ["dist"] : ["dist", "out"];
|
|
6
|
+
for (const target of targets) {
|
|
7
|
+
await rm(resolve(root, target), { recursive: true, force: true });
|
|
8
|
+
}
|
package/scripts/cli/generate.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, relative } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { logger } from "../logger.mjs";
|
|
6
|
+
|
|
5
7
|
const KINDS = [
|
|
6
8
|
"module",
|
|
7
9
|
"page",
|
|
@@ -52,15 +54,8 @@ export async function runNew(ctx, args) {
|
|
|
52
54
|
|
|
53
55
|
const shape = detectStarterShape(ctx);
|
|
54
56
|
if (shape === "universal" && MODULAR_ONLY.has(kind)) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"available in the universal starter.",
|
|
58
|
-
);
|
|
59
|
-
console.error(
|
|
60
|
-
"[mado] Either move this project to the modular layout " +
|
|
61
|
-
"(create src/modules/<name>/) or scaffold a fresh modular " +
|
|
62
|
-
"project with: mado init my-app --starter modular",
|
|
63
|
-
);
|
|
57
|
+
logger.error("mado", "generator-shape", `'${kind}' is not available in the universal starter`);
|
|
58
|
+
logger.info("mado", "generator-hint", "Create src/modules/<name>/ or run: mado init my-app --starter modular");
|
|
64
59
|
process.exit(1);
|
|
65
60
|
}
|
|
66
61
|
|
|
@@ -78,8 +73,8 @@ export async function runNew(ctx, args) {
|
|
|
78
73
|
|
|
79
74
|
const fn = generators[kind];
|
|
80
75
|
if (!fn) {
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
logger.error("mado", "unknown-generator", `unknown generator: ${kind}`);
|
|
77
|
+
logger.info("mado", "available-generators", `available generators: ${KINDS.join(", ")}`);
|
|
83
78
|
process.exit(1);
|
|
84
79
|
}
|
|
85
80
|
|
|
@@ -266,7 +261,9 @@ async function scaffoldForm(ctx, target) {
|
|
|
266
261
|
// Call inside a page view: const form = ${useName}();
|
|
267
262
|
export const ${useName} = () =>
|
|
268
263
|
useForm({
|
|
269
|
-
|
|
264
|
+
initial: {
|
|
265
|
+
// name: "",
|
|
266
|
+
},
|
|
270
267
|
});
|
|
271
268
|
`,
|
|
272
269
|
);
|
|
@@ -350,7 +347,7 @@ function moduleFile(ctx, target, suffix) {
|
|
|
350
347
|
|
|
351
348
|
async function writeOnce(path, content) {
|
|
352
349
|
if (await exists(path)) {
|
|
353
|
-
|
|
350
|
+
logger.error("mado", "file-exists", `file already exists: ${path}`);
|
|
354
351
|
process.exit(2);
|
|
355
352
|
}
|
|
356
353
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -383,7 +380,7 @@ function leafName(target) {
|
|
|
383
380
|
|
|
384
381
|
function assertSingleSegment(label, name) {
|
|
385
382
|
if (name.includes("/")) {
|
|
386
|
-
|
|
383
|
+
logger.error("mado", "invalid-name", `${label.toLowerCase()} name must be a single path segment`);
|
|
387
384
|
process.exit(1);
|
|
388
385
|
}
|
|
389
386
|
}
|
package/scripts/cli/help.mjs
CHANGED
|
@@ -28,6 +28,8 @@ ${buildLine}
|
|
|
28
28
|
Configuration:
|
|
29
29
|
Put app/dev/build settings in vite.config.ts.
|
|
30
30
|
Mado CLI flags are explicit per command.
|
|
31
|
+
--log-level debug|info|warn|error|silent
|
|
32
|
+
--log-format pretty|plain|json --no-color
|
|
31
33
|
|
|
32
34
|
Docs: README.md and docs/en/README.md.`);
|
|
33
35
|
}
|
package/scripts/cli/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
5
|
import { detectContext } from "../_config.mjs";
|
|
6
|
+
import { configureLogger, logger } from "../logger.mjs";
|
|
6
7
|
import { runInit } from "./init.mjs";
|
|
7
8
|
import { runNew } from "./generate.mjs";
|
|
8
9
|
import { printHelp } from "./help.mjs";
|
|
@@ -12,6 +13,7 @@ import { hasFlag, listTestFiles, run, runNodeBin, runNodeScript, runVite } from
|
|
|
12
13
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
13
14
|
|
|
14
15
|
export async function main(argv) {
|
|
16
|
+
argv = configureLogger(argv);
|
|
15
17
|
const projectRoot = resolve(process.cwd());
|
|
16
18
|
const context = detectContext(projectRoot);
|
|
17
19
|
const packageJson = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
@@ -37,7 +39,8 @@ export async function main(argv) {
|
|
|
37
39
|
if (ctx.isRepo) {
|
|
38
40
|
await runNodeBin(ctx, "typescript/bin/tsc", args);
|
|
39
41
|
} else {
|
|
40
|
-
|
|
42
|
+
const outputArgs = hasFlag(args, "--outDir") ? args : ["--outDir", "out", ...args];
|
|
43
|
+
await runVite(ctx, ["build", ...outputArgs], { defaultConfig: true });
|
|
41
44
|
}
|
|
42
45
|
break;
|
|
43
46
|
case "watch":
|
|
@@ -47,9 +50,14 @@ export async function main(argv) {
|
|
|
47
50
|
await runVite(ctx, ["build", "--watch", ...args], { defaultConfig: true });
|
|
48
51
|
}
|
|
49
52
|
break;
|
|
50
|
-
case "typecheck":
|
|
53
|
+
case "typecheck": {
|
|
51
54
|
await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit", ...args]);
|
|
55
|
+
const nodeConfig = join(ctx.projectRoot, "tsconfig.node.json");
|
|
56
|
+
if (existsSync(nodeConfig) && !hasFlag(args, "--project", "-p")) {
|
|
57
|
+
await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit", "-p", nodeConfig]);
|
|
58
|
+
}
|
|
52
59
|
break;
|
|
60
|
+
}
|
|
53
61
|
case "test": {
|
|
54
62
|
await runNodeBin(ctx, "typescript/bin/tsc", []);
|
|
55
63
|
const files = await listTestFiles(projectRoot);
|
|
@@ -66,8 +74,8 @@ export async function main(argv) {
|
|
|
66
74
|
);
|
|
67
75
|
break;
|
|
68
76
|
case "bake":
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
logger.error("mado", "command-removed", "`mado bake` was removed.");
|
|
78
|
+
logger.info("mado", "replacement", "Use `mado static`, or run the complete pipeline with `mado release`.");
|
|
71
79
|
process.exit(1);
|
|
72
80
|
break;
|
|
73
81
|
case "static":
|
|
@@ -88,7 +96,7 @@ export async function main(argv) {
|
|
|
88
96
|
printHelp(ctx);
|
|
89
97
|
break;
|
|
90
98
|
default:
|
|
91
|
-
|
|
99
|
+
logger.error("mado", "unknown-command", `unknown command: ${command}`);
|
|
92
100
|
printHelp(ctx);
|
|
93
101
|
process.exit(1);
|
|
94
102
|
}
|
package/scripts/cli/init.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { copyFile, cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { parseFlags } from "../_config.mjs";
|
|
6
|
+
import { logger } from "../logger.mjs";
|
|
6
7
|
|
|
7
8
|
// `default` is the universal starter: minimal, runnable, zero backend.
|
|
8
9
|
// `modular` is the long-lived business-app reference architecture:
|
|
@@ -15,30 +16,30 @@ export async function runInit(ctx, rawArgs) {
|
|
|
15
16
|
const { flags, positional } = parseFlags(rawArgs);
|
|
16
17
|
const targetArg = positional[0];
|
|
17
18
|
if (!targetArg) {
|
|
18
|
-
|
|
19
|
+
logger.error("mado", "usage", "usage: mado init <name> [--starter default] [--force]");
|
|
19
20
|
process.exit(1);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const starter = String(flags.starter ?? "default");
|
|
23
24
|
if (!STARTERS.includes(starter)) {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
logger.error("mado", "unknown-starter", `unknown starter: ${starter}`);
|
|
26
|
+
logger.info("mado", "available-starters", `available starters: ${STARTERS.join(", ")}`);
|
|
26
27
|
process.exit(1);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const target = resolve(ctx.projectRoot, targetArg);
|
|
30
31
|
const source = join(ctx.packageRoot, "starters", starter);
|
|
31
32
|
if (!existsSync(source)) {
|
|
32
|
-
|
|
33
|
+
logger.error("mado", "missing-starter", `missing starter template: ${starter}`);
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
35
36
|
if (existsSync(target) && statSync(target).isFile()) {
|
|
36
|
-
|
|
37
|
+
logger.error("mado", "target-is-file", `target exists and is a file: ${target}`);
|
|
37
38
|
process.exit(1);
|
|
38
39
|
}
|
|
39
40
|
if (existsSync(target) && readdirSync(target).length > 0 && !flags.force) {
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
logger.error("mado", "target-not-empty", `target directory is not empty: ${target}`);
|
|
42
|
+
logger.info("mado", "force-hint", "use --force to write into it");
|
|
42
43
|
process.exit(1);
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -50,7 +51,7 @@ export async function runInit(ctx, rawArgs) {
|
|
|
50
51
|
|
|
51
52
|
const packageName = packageNameFromDir(target);
|
|
52
53
|
if (!isValidPackageName(packageName)) {
|
|
53
|
-
|
|
54
|
+
logger.error("mado", "invalid-package-name", `invalid package name derived from target: ${packageName}`);
|
|
54
55
|
process.exit(1);
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -95,7 +96,10 @@ async function copyCanonicalLLMFiles(ctx, target) {
|
|
|
95
96
|
async function ensureStarterGitignore(target) {
|
|
96
97
|
const file = join(target, ".gitignore");
|
|
97
98
|
if (existsSync(file)) return;
|
|
98
|
-
await writeFile(
|
|
99
|
+
await writeFile(
|
|
100
|
+
file,
|
|
101
|
+
"node_modules\ndist\nout\n.cache\n.env\n.env.*\n!.env.example\n.DS_Store\n*.log\n",
|
|
102
|
+
);
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
async function ensureStarterPackageJson(ctx, target) {
|
|
@@ -110,15 +114,6 @@ async function ensureStarterPackageJson(ctx, target) {
|
|
|
110
114
|
process.env.MADOJS_PACKAGE_SPEC ||
|
|
111
115
|
`^${ctx.packageJson.version}`,
|
|
112
116
|
};
|
|
113
|
-
const rootDev = ctx.packageJson.devDependencies ?? {};
|
|
114
|
-
pkg.devDependencies = {
|
|
115
|
-
...(pkg.devDependencies ?? {}),
|
|
116
|
-
typescript: rootDev.typescript ?? "^6.0.3",
|
|
117
|
-
lightningcss: rootDev.lightningcss ?? "^1.32.0",
|
|
118
|
-
"playwright-core": rootDev["playwright-core"] ?? "^1.47.0",
|
|
119
|
-
vite: rootDev.vite ?? "^8.0.16",
|
|
120
|
-
};
|
|
121
|
-
|
|
122
117
|
await writeFile(file, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
123
118
|
}
|
|
124
119
|
|
package/scripts/cli/release.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readdir, readFile,
|
|
2
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { brotliCompressSync, constants as zlibConst, gzipSync } from "node:zlib";
|
|
5
5
|
|
|
6
6
|
import { parseFlags } from "../_config.mjs";
|
|
7
|
+
import { logger } from "../logger.mjs";
|
|
8
|
+
import { prepareOutputDirectory } from "../output-guard.mjs";
|
|
7
9
|
import { runNodeBin, runNodeScript, runVite, writeIfMissing } from "./run.mjs";
|
|
8
10
|
|
|
9
11
|
export async function runRelease(ctx, rawArgs) {
|
|
@@ -13,33 +15,35 @@ export async function runRelease(ctx, rawArgs) {
|
|
|
13
15
|
typeof releaseFlags.out === "string" ? releaseFlags.out : "out",
|
|
14
16
|
);
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log("");
|
|
18
|
+
logger.info("release", "context", `context: ${ctx.context}`);
|
|
19
|
+
logger.info("release", "artifact", `artifact: ${outDir}`);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
await prepareOutputDirectory({
|
|
22
|
+
projectRoot: ctx.projectRoot,
|
|
23
|
+
outDir,
|
|
24
|
+
clean: !releaseFlags["no-clean"],
|
|
25
|
+
force: releaseFlags["force-output"] === true,
|
|
26
|
+
});
|
|
27
|
+
if (releaseFlags["no-clean"]) {
|
|
28
|
+
logger.info("release", "clean-skip", "--no-clean: keeping existing out/");
|
|
25
29
|
} else {
|
|
26
|
-
|
|
30
|
+
logger.info("release", "clean", `prepared ${outDir}`);
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
logger.info("release", "step", "step 1/5 typecheck");
|
|
30
34
|
await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit"]);
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
logger.info("release", "step", "step 2/5 vite build");
|
|
33
37
|
await runVite(ctx, ["build", "--outDir", outDir], { defaultConfig: true });
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
logger.info("release", "step", "step 3/5 static snapshots");
|
|
36
40
|
await runNodeScript(ctx, "scripts/static.mjs", [
|
|
37
41
|
...rawArgs.filter((a) => a !== "--no-clean"),
|
|
38
42
|
"--out",
|
|
39
43
|
outDir,
|
|
40
44
|
]);
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
logger.info("release", "step", "step 4/5 deployment files");
|
|
43
47
|
// GitHub Pages / Netlify / Cloudflare Pages fallback. SPA fallback shell
|
|
44
48
|
// is written by `mado static`; here we only register the deployment
|
|
45
49
|
// bindings, and respect user-supplied files (writeIfMissing).
|
|
@@ -61,12 +65,11 @@ export async function runRelease(ctx, rawArgs) {
|
|
|
61
65
|
"[release] ",
|
|
62
66
|
);
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
logger.info("release", "step", "step 5/5 precompress assets");
|
|
65
69
|
await precompressOut(outDir);
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log("[release] try: mado preview");
|
|
71
|
+
logger.info("release", "done", `done. Deploy artifact: ${outDir}`);
|
|
72
|
+
logger.info("release", "next", "try: mado preview");
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
async function precompressOut(outDir) {
|
|
@@ -84,7 +87,7 @@ async function precompressOut(outDir) {
|
|
|
84
87
|
);
|
|
85
88
|
count++;
|
|
86
89
|
}
|
|
87
|
-
|
|
90
|
+
logger.info("release", "compress", `compressed ${count} file(s)`);
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
async function listCompressibleFiles(dir) {
|