@weft-js/cli 0.1.1 → 0.1.2
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/README.md +38 -14
- package/bin/weft.mjs +8 -1
- package/docs/async.md +16 -6
- package/docs/browser.md +22 -8
- package/docs/config.md +39 -9
- package/docs/core.md +30 -0
- package/docs/deploy.md +33 -8
- package/docs/design.md +12 -3
- package/docs/entrypoints.md +79 -0
- package/docs/fonts.md +12 -2
- package/docs/icons.md +13 -7
- package/docs/lists.md +3 -1
- package/docs/meta.md +13 -3
- package/docs/plugins.md +32 -9
- package/docs/props.md +13 -2
- package/docs/reactivity.md +26 -0
- package/docs/rules.md +19 -12
- package/docs/rust.md +47 -17
- package/docs/start-short.md +3 -2
- package/docs/start.md +226 -52
- package/docs/state.md +33 -7
- package/docs/style.md +31 -8
- package/docs/surface.md +61 -15
- package/docs/testing.md +15 -5
- package/docs/theme.md +182 -0
- package/docs/ui-state.md +19 -6
- package/docs/verify.md +13 -0
- package/package.json +13 -4
- package/src/add/config.mjs +93 -0
- package/src/add/errors.mjs +2 -0
- package/src/add/setup.mjs +88 -0
- package/src/add-rust.mjs +82 -27
- package/src/add.mjs +209 -181
- package/src/agents-md.mjs +144 -43
- package/src/alias.mjs +16 -5
- package/src/audit/page.mjs +313 -0
- package/src/audit/report.mjs +69 -0
- package/src/audit/utils.mjs +5 -0
- package/src/audit.mjs +159 -206
- package/src/browser/cli.mjs +24 -5
- package/src/browser/client.mjs +189 -25
- package/src/browser/commands.mjs +166 -51
- package/src/catalog/audit.mjs +129 -0
- package/src/catalog/auth.mjs +40 -0
- package/src/catalog/compiler.mjs +337 -0
- package/src/catalog/contracts.mjs +18 -0
- package/src/catalog/database.mjs +52 -0
- package/src/catalog/deploy.mjs +117 -0
- package/src/catalog/design.mjs +159 -0
- package/src/catalog/forms.mjs +25 -0
- package/src/catalog/hydration.mjs +29 -0
- package/src/catalog/icons.mjs +24 -0
- package/src/catalog/media.mjs +163 -0
- package/src/catalog/metadata.mjs +116 -0
- package/src/catalog/plugins.mjs +228 -0
- package/src/catalog/realtime.mjs +195 -0
- package/src/catalog/rust.mjs +75 -0
- package/src/catalog/server-functions.mjs +92 -0
- package/src/catalog/state.mjs +39 -0
- package/src/catalog/style-groups.mjs +54 -0
- package/src/catalog/tests.mjs +127 -0
- package/src/catalog/text.mjs +200 -0
- package/src/catalog/ui-state.mjs +42 -0
- package/src/catalog.mjs +67 -460
- package/src/check/runtime.mjs +280 -0
- package/src/check.mjs +198 -208
- package/src/cli/authoring.mjs +213 -0
- package/src/cli/project.mjs +302 -0
- package/src/cli.mjs +222 -250
- package/src/config.mjs +1 -1
- package/src/contributions.mjs +75 -25
- package/src/db-layer.mjs +13 -5
- package/src/deploy/cdn.mjs +94 -16
- package/src/deploy/files.mjs +100 -21
- package/src/deploy/fit.mjs +127 -23
- package/src/deploy/hosts.mjs +90 -20
- package/src/deploy/index/cli.mjs +20 -0
- package/src/deploy/index/environment.mjs +69 -0
- package/src/deploy/index/steps.mjs +87 -0
- package/src/deploy/index.mjs +228 -114
- package/src/deploy/needs.mjs +88 -31
- package/src/design-checks/accessible-names.mjs +62 -0
- package/src/design-checks/contrast.mjs +43 -0
- package/src/design-checks/csp.mjs +60 -0
- package/src/design-checks/files.mjs +59 -0
- package/src/design-checks/heading-links.mjs +61 -0
- package/src/design-checks/interaction.mjs +65 -0
- package/src/design-checks/jsx.mjs +67 -0
- package/src/design-checks/layout-values.mjs +68 -0
- package/src/design-checks/native-controls.mjs +48 -0
- package/src/design-checks/outline.mjs +39 -0
- package/src/design-checks/reporter.mjs +37 -0
- package/src/design-checks/rules.mjs +274 -0
- package/src/design-checks/values.mjs +262 -0
- package/src/design-checks.mjs +10 -400
- package/src/diag.mjs +32 -8
- package/src/docs.mjs +138 -43
- package/src/doctor.mjs +20 -8
- package/src/files.mjs +18 -5
- package/src/fix.mjs +10 -2
- package/src/gate/after.mjs +40 -0
- package/src/gate/before.mjs +54 -0
- package/src/gate/classify.mjs +72 -0
- package/src/gate/mutation.mjs +209 -0
- package/src/gate/resolve-cases.mjs +22 -0
- package/src/gate/result.mjs +63 -0
- package/src/gate/run.mjs +75 -0
- package/src/gate.mjs +104 -202
- package/src/infer/box-props.cjs +140 -0
- package/src/infer/core-boxes.cjs +46 -0
- package/src/infer/derived-writes.cjs +95 -0
- package/src/infer/engine/checks.cjs +193 -0
- package/src/infer/engine/flow.cjs +293 -0
- package/src/infer/engine/report.cjs +158 -0
- package/src/infer/engine/utils.cjs +15 -0
- package/src/infer/engine.cjs +108 -368
- package/src/infer/facts/sites.cjs +114 -0
- package/src/infer/facts.cjs +210 -117
- package/src/infer/hosts.cjs +69 -22
- package/src/infer/lsp.mjs +149 -40
- package/src/infer/session.cjs +73 -17
- package/src/infer/shadow.cjs +13 -5
- package/src/infer/worker.cjs +57 -9
- package/src/jsx-scan.mjs +63 -21
- package/src/load-config/schema.mjs +281 -0
- package/src/load-config.mjs +137 -138
- package/src/map.mjs +93 -26
- package/src/mcp/browser-tools.mjs +155 -0
- package/src/mcp.mjs +147 -69
- package/src/meta-table.mjs +43 -8
- package/src/mutate.mjs +120 -36
- package/src/plugin-checks.mjs +52 -23
- package/src/plugins.mjs +125 -39
- package/src/project.mjs +170 -79
- package/src/props-checks.mjs +24 -6
- package/src/props-listener.mjs +27 -9
- package/src/react-checks.mjs +35 -4
- package/src/server-state-checks.mjs +34 -0
- package/src/share-checks.mjs +121 -37
- package/src/site.mjs +33 -6
- package/src/slots.mjs +104 -19
- package/src/start/build.mjs +116 -0
- package/src/start/routes.mjs +38 -0
- package/src/start.mjs +193 -95
- package/src/state-checks/core-state.mjs +223 -0
- package/src/state-checks/modules.mjs +17 -0
- package/src/state-checks/scan.mjs +232 -0
- package/src/state-checks/server-graph.mjs +65 -0
- package/src/state-checks.mjs +109 -133
- package/src/style/find.mjs +121 -0
- package/src/style.mjs +133 -127
- package/src/surface-checks.mjs +212 -46
- package/src/tailwind.mjs +5 -4
- package/src/tasks.mjs +189 -27
- package/src/test-rules/context.mjs +109 -0
- package/src/test-rules/file-rules.mjs +279 -0
- package/src/test-rules/rule-helpers.mjs +244 -0
- package/src/test-rules/syntax.mjs +223 -0
- package/src/test-rules/test-rules.mjs +127 -0
- package/src/testdiff.mjs +94 -32
- package/src/tests.mjs +31 -483
- package/src/text-checks.mjs +229 -69
- package/src/tokens.mjs +12 -4
- package/src/trust/scan.mjs +95 -0
- package/src/trust.mjs +214 -102
- package/src/weft.mjs +41 -9
- package/src/workspace.mjs +96 -19
- package/ts-plugin/context.js +57 -0
- package/ts-plugin/index.js +4 -124
- package/ts-plugin/mapping.js +65 -0
- package/ts-plugin/proxy.js +144 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** deploy diagnostic entries in catalog order. */
|
|
2
|
+
export const deploy = [
|
|
3
|
+
// Deploy (research 46): what a host can run, the files and env a deploy needs, CDN and media setup. `weft check` reports
|
|
4
|
+
// them for `deploy.target` (or the host `--preset` names), `weft deploy` before it builds, `weft doctor` for every host.
|
|
5
|
+
{
|
|
6
|
+
code: 'E001',
|
|
7
|
+
severity: 'error',
|
|
8
|
+
by: 'cli',
|
|
9
|
+
catches:
|
|
10
|
+
"A `channel`, `sharedState` or `realtime({ share })` on a deploy target that can't hold a WebSocket (Netlify, Vercel, AWS Lambda): the page loads and never connects (at runtime: R024)",
|
|
11
|
+
fix: "`deploy: { target: 'fly' }` or `'render'`, `'cloudflare'` (Durable Objects), or the split: pages here, realtime on a server (`weft docs deploy`)",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: 'E002',
|
|
15
|
+
severity: 'error',
|
|
16
|
+
by: 'cli',
|
|
17
|
+
catches:
|
|
18
|
+
'Something that needs a process between requests on a target that keeps none: a `required` live query (R013), or a `remote()` sidecar',
|
|
19
|
+
fix: "A server host (`fly`, `render`); for a feed, `weft db listen` on one with `listener: 'external'`; for a sidecar, deploy it on its own with `rust.services.<name>.external: true`",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: 'E003',
|
|
23
|
+
severity: 'error',
|
|
24
|
+
by: 'cli',
|
|
25
|
+
catches:
|
|
26
|
+
'A SQLite file or media uploads on local disk, on a target whose disk is gone after a request or a deploy, or on a server host with no volume or disk configured (a warning in `weft deploy hosts`)',
|
|
27
|
+
fix: '`deploy.fly.volume` or `deploy.render.disk` (both billed; they mount at `/app/.data`), or a hosted database',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
code: 'E004',
|
|
31
|
+
severity: 'warning',
|
|
32
|
+
by: 'cli',
|
|
33
|
+
catches:
|
|
34
|
+
'A native npm addon (`better-sqlite3`, `sharp`, `bcrypt`…) built on this machine and deployed from here to a Linux function host',
|
|
35
|
+
fix: "Deploy from CI (`deploy.ci: 'github'` builds on Linux), or drop the addon",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
code: 'E005',
|
|
39
|
+
severity: 'info',
|
|
40
|
+
by: 'cli',
|
|
41
|
+
catches:
|
|
42
|
+
'No `deploy.target` (`weft doctor`, `weft deploy hosts`): which hosts can run this app, and the one weft suggests',
|
|
43
|
+
fix: '`weft deploy init <target>`',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: 'E006',
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
by: 'cli',
|
|
49
|
+
catches:
|
|
50
|
+
'A file `weft deploy init` writes for `deploy.target` (`fly.toml`, `Dockerfile`, the workflow…) is missing, or was written from an older config',
|
|
51
|
+
fix: '`weft deploy init` (a file edited by hand is left alone; `--force` overwrites it)',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
code: 'E007',
|
|
55
|
+
severity: 'error',
|
|
56
|
+
by: 'cli',
|
|
57
|
+
catches:
|
|
58
|
+
"A variable `weft deploy --yes` reads is unset (an error in CI or for an id the CLI can't find; a warning where the host CLI's own login stands in)",
|
|
59
|
+
fix: "Set it in the environment, or log in with the host's CLI here. Never in a file",
|
|
60
|
+
},
|
|
61
|
+
// oxlint-disable no-template-curly-in-string -- shows the GitHub Actions syntax
|
|
62
|
+
{
|
|
63
|
+
code: 'E008',
|
|
64
|
+
severity: 'error',
|
|
65
|
+
by: 'cli',
|
|
66
|
+
catches:
|
|
67
|
+
'A deploy credential in a committed file: a Netlify or Fly token, a Render deploy hook, a Cloudinary URL with its secret, or a long value for a key named token, secret, api key or password, in the config, a host file or a workflow',
|
|
68
|
+
fix: 'Move it to the environment (host settings, or GitHub secrets as `${{ secrets.NAME }}`), and rotate it' /* oxlint-enable no-template-curly-in-string */,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
code: 'E009',
|
|
72
|
+
severity: 'warning',
|
|
73
|
+
by: 'cli',
|
|
74
|
+
catches:
|
|
75
|
+
"The memory bus on a target that runs many instances: a message reaches one instance's clients (`weft build` fails on it as R002)",
|
|
76
|
+
fix: '`weft add redis` or `weft add nats`, `postgresBus({ url })`, or `durableObjectBus` on Cloudflare',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
code: 'E010',
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
by: 'cli',
|
|
82
|
+
catches:
|
|
83
|
+
"`deploy.cdn.proxy` is Cloudflare, and `site.url` is the host's own domain (`*.fly.dev`, `*.onrender.com`): nothing goes through the proxy",
|
|
84
|
+
fix: 'Set `site.url` to the domain Cloudflare proxies',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: 'E011',
|
|
88
|
+
severity: 'warning',
|
|
89
|
+
by: 'cli',
|
|
90
|
+
catches:
|
|
91
|
+
'`deploy.cdn.proxy` in front of a host that is a CDN already (Netlify, Vercel, Cloudflare): two caches, and a deploy purges only its own',
|
|
92
|
+
fix: 'Drop `cdn.proxy`, or deploy to `fly` or `render` behind it',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
code: 'E012',
|
|
96
|
+
severity: 'error',
|
|
97
|
+
by: 'cli',
|
|
98
|
+
catches: "`deploy.cdn.images` is Cloudinary, and `@weft-js/plugin-media` isn't in `plugins`",
|
|
99
|
+
fix: '`weft add media`, or drop `cdn.images`',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
code: 'E013',
|
|
103
|
+
severity: 'error',
|
|
104
|
+
by: 'cli',
|
|
105
|
+
catches:
|
|
106
|
+
'`deploy.cdn.images` with no `cloud` and no `CLOUDINARY_URL`, or the two naming different clouds (a warning when only the env var is unset, at a deploy)',
|
|
107
|
+
fix: "Set `cdn.images.cloud`, and `CLOUDINARY_URL` in the host's environment",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
code: 'E014',
|
|
111
|
+
severity: 'info',
|
|
112
|
+
by: 'cli',
|
|
113
|
+
catches:
|
|
114
|
+
"`deploy.cdn.images` is checked, and this weft doesn't send uploads to Cloudinary yet: they go to `media({ server: { dir } })`",
|
|
115
|
+
fix: 'Nothing yet (research 46 §9); on a function host, uploads need a server host with a disk (E003)',
|
|
116
|
+
},
|
|
117
|
+
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** design diagnostic entries in catalog order. */
|
|
2
|
+
export const design = [
|
|
3
|
+
// The design language (PLAN §6, research 37): tokens and layout where the app has warp's tokens, contrast and accessibility everywhere.
|
|
4
|
+
{
|
|
5
|
+
code: 'D001',
|
|
6
|
+
severity: 'error',
|
|
7
|
+
by: 'cli',
|
|
8
|
+
catches:
|
|
9
|
+
"A raw colour in `style`, app CSS, or a Tailwind arbitrary or palette class, in an app with warp's tokens: it won't follow the theme, the dials or dark mode",
|
|
10
|
+
fix: "Use a colour token or a `tone`. No autofix: a token follows the theme, the raw value didn't",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
code: 'D002',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
by: 'cli',
|
|
16
|
+
catches:
|
|
17
|
+
'Spacing off the scale, or a scale step written by hand (`padding: 16px`, `p-[13px]`, `gap-3`)',
|
|
18
|
+
fix: 'A step of `hair tight related group section page`; autofix when the value is exactly one',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: 'D003',
|
|
22
|
+
severity: 'warning',
|
|
23
|
+
by: 'cli',
|
|
24
|
+
catches: 'Raw type, radius or shadow (`font-size`, `text-lg`, `rounded-[7px]`, `box-shadow`)',
|
|
25
|
+
fix: '`<Heading role>` / `<Text role>`, the radius tokens, `--warp-elevation-*`; autofix for an exact radius',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
code: 'D004',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
by: 'cli',
|
|
31
|
+
catches:
|
|
32
|
+
'Layout spelled out on an element (`display: flex`, `flex flex-col gap-4`) where a primitive says it',
|
|
33
|
+
fix: '`<Stack>` or `<Row>`; a suggested edit when nothing else is on the element',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
code: 'D040',
|
|
37
|
+
severity: 'warning',
|
|
38
|
+
by: 'cli',
|
|
39
|
+
catches:
|
|
40
|
+
'The style gate (`data-weft="styled"`) on the main content or a page heading: the first paint hides what it should show',
|
|
41
|
+
fix: 'Mark the secondary part that needs its late styles, or drop the marker',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
code: 'D041',
|
|
45
|
+
severity: 'error',
|
|
46
|
+
by: 'cli',
|
|
47
|
+
catches:
|
|
48
|
+
'`aria-label` or `aria-labelledby` on a `<div>`, `<span>` or other element whose role may not be named: screen readers drop it (axe aria-prohibited-attr)',
|
|
49
|
+
fix: 'Put the words in the element (a visually hidden `<span data-part="sr-only">` keeps them off screen), or give it a role that takes a name: `role="img"` for a mark or icon that means something',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: 'D005',
|
|
53
|
+
severity: 'warning',
|
|
54
|
+
by: 'cli',
|
|
55
|
+
catches:
|
|
56
|
+
'A browser-painted control in a warp app: `<button>`, `<select>`, `<textarea>`, an input type like `date`, `number` or `range`, or a `title` tooltip',
|
|
57
|
+
fix: 'The warp component: focus ring, sweep, tones and touch target come with it',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
code: 'D006',
|
|
61
|
+
severity: 'warning',
|
|
62
|
+
by: 'cli',
|
|
63
|
+
catches:
|
|
64
|
+
'One heading level at two roles across an app (`h1` as both `title` and `display`), or a bare `<hN>` beside `<Heading>`',
|
|
65
|
+
fix: 'Drop `role`, or match the rest: `Heading` picks the role from `level`. A suggested edit at each site',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: 'D007',
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
by: 'cli',
|
|
71
|
+
catches: 'A bare `<a href>` in a warp app',
|
|
72
|
+
fix: "`<Link>`, or `Shell`'s `nav` for navigation (suggested edit)",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
code: 'D011',
|
|
76
|
+
severity: 'error',
|
|
77
|
+
by: 'cli',
|
|
78
|
+
catches: 'A literal text and background colour pair on one element below 4.5:1',
|
|
79
|
+
fix: 'Use tokens: they pass at every dial stop',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: 'D012',
|
|
83
|
+
severity: 'error',
|
|
84
|
+
by: 'cli',
|
|
85
|
+
catches:
|
|
86
|
+
'A Content-Security-Policy that blocks inline styles, or every script a page could switch its deferred sheets on with, in an app that inlines its CSS; as a warning, one that refuses only inline scripts',
|
|
87
|
+
fix: '`site: { inlineCss: false }` keeps plain links, or allow `unsafe-inline` in the directive that applies',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
code: 'D030',
|
|
91
|
+
severity: 'error',
|
|
92
|
+
by: 'cli',
|
|
93
|
+
catches: 'An `<input>`, `<select>` or `<textarea>` with no label',
|
|
94
|
+
fix: "A wrapping `<label>`, `<label for>`, `aria-label`, or warp's `<Input label>`",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
code: 'D031',
|
|
98
|
+
severity: 'error',
|
|
99
|
+
by: 'cli',
|
|
100
|
+
catches: 'A button or link with no accessible name (icon only, or empty)',
|
|
101
|
+
fix: "Text, or `aria-label`; warp's Button takes `label`",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
code: 'D032',
|
|
105
|
+
severity: 'error',
|
|
106
|
+
by: 'cli',
|
|
107
|
+
catches: '`<img>` with no `alt`',
|
|
108
|
+
fix: 'Describe the image; `alt=""` when it is decoration',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: 'D033',
|
|
112
|
+
severity: 'error',
|
|
113
|
+
by: 'cli',
|
|
114
|
+
catches: '`onClick` on a non-interactive element with no role: mouse only',
|
|
115
|
+
fix: 'A real `<button type="button">` (suggested edit)',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
code: 'D034',
|
|
119
|
+
severity: 'error',
|
|
120
|
+
by: 'cli',
|
|
121
|
+
catches: 'A control inside a control (`<a><button>`)',
|
|
122
|
+
fix: 'One control: move the other beside it',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
code: 'D035',
|
|
126
|
+
severity: 'warning',
|
|
127
|
+
by: 'cli',
|
|
128
|
+
catches: 'A heading level that jumps (h1 to h3)',
|
|
129
|
+
fix: 'Use the next level; `<Heading level role>` keeps the look apart from the outline',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
code: 'D036',
|
|
133
|
+
severity: 'error',
|
|
134
|
+
by: 'cli',
|
|
135
|
+
catches: 'The focus ring removed with nothing in its place (`outline: none`, `outline-none`)',
|
|
136
|
+
fix: "Remove it: warp's `:focus-visible` is the ring. Or add a `focus-visible:` style",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
code: 'D037',
|
|
140
|
+
severity: 'error',
|
|
141
|
+
by: 'cli',
|
|
142
|
+
catches: 'A positive `tabindex`',
|
|
143
|
+
fix: '`0`, and the order follows the DOM (autofix)',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
code: 'D038',
|
|
147
|
+
severity: 'warning',
|
|
148
|
+
by: 'cli',
|
|
149
|
+
catches: 'Two solid Buttons side by side',
|
|
150
|
+
fix: 'One primary action per region: make the others `soft` or `ghost`',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
code: 'D039',
|
|
154
|
+
severity: 'warning',
|
|
155
|
+
by: 'cli',
|
|
156
|
+
catches: '`<html>` with no `lang`',
|
|
157
|
+
fix: 'Set `lang`',
|
|
158
|
+
},
|
|
159
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** forms diagnostic entries in catalog order. */
|
|
2
|
+
export const forms = [
|
|
3
|
+
{
|
|
4
|
+
code: 'F001',
|
|
5
|
+
severity: 'error',
|
|
6
|
+
by: 'runtime',
|
|
7
|
+
catches: "`<Form of>` was given something that isn't a mutation",
|
|
8
|
+
fix: '`<Form of={addTodo}>`, with `addTodo` a `mutation()` export of a server module',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
code: 'F002',
|
|
12
|
+
severity: 'error',
|
|
13
|
+
by: 'start',
|
|
14
|
+
catches:
|
|
15
|
+
'A form POST without the same-origin proof (`Origin`, or `Sec-Fetch-Site: same-origin`): 403',
|
|
16
|
+
fix: 'Submit through a `<Form>` on the same origin; behind a proxy, forward the host so the origins match',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
code: 'F003',
|
|
20
|
+
severity: 'error',
|
|
21
|
+
by: 'start',
|
|
22
|
+
catches: 'A form POST naming no known mutation: the page is older than the server',
|
|
23
|
+
fix: 'Reload; keep `mutation` exports in server modules',
|
|
24
|
+
},
|
|
25
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** hydration diagnostic entries in catalog order. */
|
|
2
|
+
export const hydration = [
|
|
3
|
+
// Metadata and share cards (research 20 §6; H for head, M is media). `start`: reported by @weft-js/start at request or
|
|
4
|
+
// render time, and by `weft routes --meta` for every route.
|
|
5
|
+
{
|
|
6
|
+
code: 'H009',
|
|
7
|
+
severity: 'error',
|
|
8
|
+
by: 'runtime',
|
|
9
|
+
catches:
|
|
10
|
+
"A prop of an island is something JSON can't carry (a function, a Date, a class instance), found as the server renders",
|
|
11
|
+
fix: 'Pass plain data, or put `/* @weft client */` on the component that renders it',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: 'H010',
|
|
15
|
+
severity: 'info',
|
|
16
|
+
by: 'cli',
|
|
17
|
+
catches:
|
|
18
|
+
"An island has to load with the page for a reason its code could avoid: work that reaches past it, a module that runs as it loads, a box at module level, code the compiler can't see into. `.weft/hydration.json` says why for every island",
|
|
19
|
+
fix: '`/* @weft visible */` above the component when its work only matters on screen, `/* @weft interaction */` when it only matters to someone using it; otherwise the fix the diagnostic names',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: 'H017',
|
|
23
|
+
severity: 'info',
|
|
24
|
+
by: 'cli',
|
|
25
|
+
catches:
|
|
26
|
+
"A prop on a component holds a function over local names, which can't cross to an island, so the component around it hydrates whole",
|
|
27
|
+
fix: 'Nothing, when that component is mostly controls; otherwise move each control and what its function reads into a small component of its own, which becomes the island and leaves the rest static',
|
|
28
|
+
},
|
|
29
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** icons diagnostic entries in catalog order. */
|
|
2
|
+
export const icons = [
|
|
3
|
+
{
|
|
4
|
+
code: 'I001',
|
|
5
|
+
severity: 'error',
|
|
6
|
+
by: 'cli',
|
|
7
|
+
catches: '`<Icon name="as">`, or `Icons.As`, where no such icon exists',
|
|
8
|
+
fix: 'Use the name it suggests. `weft docs icons` has the set; an editor completes it from the type.',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
code: 'I002',
|
|
12
|
+
severity: 'info',
|
|
13
|
+
by: 'cli',
|
|
14
|
+
catches: 'An icon name only known at runtime, so that use loads the pack',
|
|
15
|
+
fix: 'Nothing to do if the name comes from data. To fetch single icons instead, set `icons({ source })`. A list of literals, or a map over one, ships only those.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
code: 'I003',
|
|
19
|
+
severity: 'info',
|
|
20
|
+
by: 'cli',
|
|
21
|
+
catches: 'The icon pack used as a value, so all of it ships with the page',
|
|
22
|
+
fix: 'Use `Icons.X`, or `Icons[name]`, which loads lazily.',
|
|
23
|
+
},
|
|
24
|
+
]
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** media diagnostic entries in catalog order. */
|
|
2
|
+
export const media = [
|
|
3
|
+
{
|
|
4
|
+
code: 'M001',
|
|
5
|
+
severity: 'error',
|
|
6
|
+
by: 'plugin',
|
|
7
|
+
owner: '@weft-js/plugin-media',
|
|
8
|
+
catches:
|
|
9
|
+
'Raw `<img>`/`<video>` without `width`/`height` or `aspect-ratio`, outside a `<Boundary>` (inside it is X030)',
|
|
10
|
+
fix: 'Insert the intrinsic `width`/`height` (autofix for an imported file), or use `<Media>`',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
code: 'M002',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
by: 'plugin',
|
|
16
|
+
owner: '@weft-js/plugin-media',
|
|
17
|
+
catches: 'An image rendered through a `?url` import or a string path, bypassing the optimizer',
|
|
18
|
+
fix: '`import x from \'./x.jpg?media\'` and `<Media src={x} alt="…" />`',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: 'M003',
|
|
22
|
+
severity: 'warning',
|
|
23
|
+
by: 'plugin',
|
|
24
|
+
owner: '@weft-js/plugin-media',
|
|
25
|
+
catches:
|
|
26
|
+
'A raw `<img>` whose file is wider than its `width` × 2 (DPR) × 1.25: bytes nobody sees',
|
|
27
|
+
fix: '`<Media>`, which resizes',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
code: 'M004',
|
|
31
|
+
severity: 'warning',
|
|
32
|
+
by: 'plugin',
|
|
33
|
+
owner: '@weft-js/plugin-media',
|
|
34
|
+
catches: 'A GIF over 256 KB rendered raw',
|
|
35
|
+
fix: '`<Media>` with `mediaFfmpeg()` in `plugins`, which turns GIFs into video when that saves 15%',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
code: 'M005',
|
|
39
|
+
severity: 'error',
|
|
40
|
+
by: 'plugin',
|
|
41
|
+
owner: '@weft-js/plugin-media',
|
|
42
|
+
catches:
|
|
43
|
+
'`<video autoplay>` without `muted`: browsers block it, and sound before a gesture is hostile',
|
|
44
|
+
fix: 'Add `muted` (autofix)',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: 'M006',
|
|
48
|
+
severity: 'warning',
|
|
49
|
+
by: 'plugin',
|
|
50
|
+
owner: '@weft-js/plugin-media',
|
|
51
|
+
catches: '`<video>` without `poster`: a blank box until the first frame decodes',
|
|
52
|
+
fix: 'Add a `poster` image',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
code: 'M007',
|
|
56
|
+
severity: 'error',
|
|
57
|
+
by: 'plugin',
|
|
58
|
+
owner: '@weft-js/plugin-media',
|
|
59
|
+
catches:
|
|
60
|
+
'Missing `alt` on `<Media>`/`<img>`, `<Media alt="">` without `decorative`, or `alt` equal to the file name',
|
|
61
|
+
fix: 'Describe what the image shows; `alt="" decorative` for purely visual media',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
code: 'M008',
|
|
65
|
+
severity: 'warning',
|
|
66
|
+
by: 'plugin',
|
|
67
|
+
owner: '@weft-js/plugin-media',
|
|
68
|
+
catches:
|
|
69
|
+
'A route\'s first media is `loading="lazy"`, or `priority` media is lazy: the likely LCP image loads late',
|
|
70
|
+
fix: 'Mark the hero `priority` and drop `loading="lazy"`',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
code: 'M009',
|
|
74
|
+
severity: 'warning',
|
|
75
|
+
by: 'plugin',
|
|
76
|
+
owner: '@weft-js/plugin-media',
|
|
77
|
+
catches: '`priority` on more than 2 media in one route: they compete for the first bytes',
|
|
78
|
+
fix: 'Keep `priority` on the one hero image',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
code: 'M010',
|
|
82
|
+
severity: 'warning',
|
|
83
|
+
by: 'plugin',
|
|
84
|
+
owner: '@weft-js/plugin-media',
|
|
85
|
+
catches:
|
|
86
|
+
'A `priority` `<Media>` without `width` or `sizes`: eager images can\'t use `sizes="auto"`, so the browser assumes 100vw',
|
|
87
|
+
fix: 'Pass `width="1/2"` (a viewport fraction or CSS length) or `sizes`',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
code: 'M011',
|
|
91
|
+
severity: 'warning',
|
|
92
|
+
by: 'plugin',
|
|
93
|
+
owner: '@weft-js/plugin-media',
|
|
94
|
+
catches:
|
|
95
|
+
'`<video autoplay loop>` without `controls`, or a GIF in a raw `<img>`: endless motion with no pause (WCAG 2.2.2)',
|
|
96
|
+
fix: '`<Media>` (pause control, poster under reduced motion), or add `controls`',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
code: 'M012',
|
|
100
|
+
severity: 'error',
|
|
101
|
+
by: 'plugin',
|
|
102
|
+
owner: '@weft-js/plugin-media',
|
|
103
|
+
catches:
|
|
104
|
+
'An SVG with `<script>`, `foreignObject`, `on*=` handlers or external references, used as an image',
|
|
105
|
+
fix: 'Sanitise the file; the build refuses to optimise it',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
code: 'M013',
|
|
109
|
+
severity: 'warning',
|
|
110
|
+
by: 'plugin',
|
|
111
|
+
owner: '@weft-js/plugin-media',
|
|
112
|
+
catches: 'A file in `public/` referenced by string: untracked, unoptimised, no dimensions',
|
|
113
|
+
fix: "Move it next to the code and `import x from './x.jpg?media'`",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
code: 'M014',
|
|
117
|
+
severity: 'info',
|
|
118
|
+
by: 'plugin',
|
|
119
|
+
owner: '@weft-js/plugin-media',
|
|
120
|
+
catches: "What `encoder: 'compatible'` adds to the build for compatible copies",
|
|
121
|
+
fix: 'Nothing: it says what the toggle costs',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
code: 'M015',
|
|
125
|
+
severity: 'warning',
|
|
126
|
+
by: 'plugin',
|
|
127
|
+
owner: '@weft-js/plugin-media',
|
|
128
|
+
catches: 'An HDR or over-8-bit video that comes out 8-bit SDR',
|
|
129
|
+
fix: 'Convert the source on purpose, or accept the flattening',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
code: 'M016',
|
|
133
|
+
severity: 'warning',
|
|
134
|
+
by: 'plugin',
|
|
135
|
+
owner: '@weft-js/plugin-media',
|
|
136
|
+
catches:
|
|
137
|
+
'`<Media src>` given a string: nothing knows its size before it loads, so the page shifts',
|
|
138
|
+
fix: 'Import it with `?media`, or store what `ctx.media.ingest` returns and type the field `z.media()`',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
code: 'M017',
|
|
142
|
+
severity: 'error',
|
|
143
|
+
by: 'start',
|
|
144
|
+
catches: '`ctx.media` or a `/_weft/m/` URL used with no `media.server` provider',
|
|
145
|
+
fix: 'Add `media()` from `@weft-js/plugin-media` to `plugins` in weft.config.ts',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
code: 'M019',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
by: 'plugin',
|
|
151
|
+
owner: '@weft-js/plugin-media-ffmpeg',
|
|
152
|
+
catches: 'ffmpeg did not run, is not the pinned binary, or has no encoder for a codec',
|
|
153
|
+
fix: 'Install ffmpeg, or give `mediaFfmpeg({ binary })` its path',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
code: 'M018',
|
|
157
|
+
severity: 'error',
|
|
158
|
+
by: 'start',
|
|
159
|
+
catches:
|
|
160
|
+
"An upload refused: it isn't an image weft can read, or it's over the size limit (422)",
|
|
161
|
+
fix: 'Raise `media({ server: { maxBytes } })`, or check the file on the client first',
|
|
162
|
+
},
|
|
163
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/** metadataHead diagnostic entries in catalog order. */
|
|
2
|
+
export const metadataHead = [
|
|
3
|
+
{
|
|
4
|
+
code: 'H001',
|
|
5
|
+
severity: 'warning',
|
|
6
|
+
by: 'cli',
|
|
7
|
+
catches:
|
|
8
|
+
"No `site.url`: a build has no absolute canonical, og:url or og:image, and a server takes them from the request's Host header",
|
|
9
|
+
fix: 'Set `site.url`, or `WEFT_SITE_URL` in the deploy environment',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
code: 'H002',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
by: 'cli',
|
|
15
|
+
catches:
|
|
16
|
+
'Param route with an `auto` image on a static-only preset and no `prerender` (`weft routes --meta --preset static`)',
|
|
17
|
+
fix: 'Export `prerender`, or set `og.image`',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: 'H003',
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
by: 'cli',
|
|
23
|
+
catches:
|
|
24
|
+
'No description after merging the config, layouts and route (`weft check`, per route and on `site`)',
|
|
25
|
+
fix: "Set `site.description` (or the route's `meta.description`)",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
code: 'H004',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
by: 'cli',
|
|
31
|
+
catches: 'Title over 60 characters or description over 160 (search results and cards cut them)',
|
|
32
|
+
fix: 'Shorten it',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: 'H011',
|
|
36
|
+
severity: 'warning',
|
|
37
|
+
by: 'cli',
|
|
38
|
+
catches:
|
|
39
|
+
'A page sets no `meta`, or leaves out its title or description: they come from what it renders (its `h1`, its first paragraph) or the site default',
|
|
40
|
+
fix: 'Add `meta` when you build the page, in words written for it; the diagnostic carries a starting point',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: 'H012',
|
|
44
|
+
severity: 'warning',
|
|
45
|
+
by: 'cli',
|
|
46
|
+
catches: 'Two routes have the same title',
|
|
47
|
+
fix: 'Give each page a title from what is on it',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
code: 'H013',
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
by: 'cli',
|
|
53
|
+
catches: 'Two routes have the same description, usually the site default',
|
|
54
|
+
fix: 'Give each page a description from what is on it',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
code: 'H014',
|
|
58
|
+
severity: 'warning',
|
|
59
|
+
by: 'cli',
|
|
60
|
+
catches:
|
|
61
|
+
'A param route whose `meta` ignores `data` and `params`, or whose real pages all have the same title or description',
|
|
62
|
+
fix: 'Build `meta` from `data` and `params`: `({ data }) => ({ title: data.name, description: data.summary })`',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
code: 'H015',
|
|
66
|
+
severity: 'warning',
|
|
67
|
+
by: 'cli',
|
|
68
|
+
catches:
|
|
69
|
+
'A written description that reads like code, a markdown fragment or a cut-off sentence',
|
|
70
|
+
fix: 'Write one plain sentence. Never slice raw markdown or code into `description`',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
code: 'H016',
|
|
74
|
+
severity: 'info',
|
|
75
|
+
by: 'cli',
|
|
76
|
+
catches:
|
|
77
|
+
"A route `weft check` couldn't fully check: it failed here, or a param route with no page to sample",
|
|
78
|
+
fix: 'Export `prerender`, or link to a page of it; `check: { routes: false }` turns the route pass off',
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
/** metadataCards diagnostic entries in catalog order. */
|
|
83
|
+
export const metadataCards = [
|
|
84
|
+
{
|
|
85
|
+
code: 'H005',
|
|
86
|
+
severity: 'error',
|
|
87
|
+
by: 'start',
|
|
88
|
+
catches:
|
|
89
|
+
'`.og.tsx` uses a style or element the card renderer does not support (an error in dev; production drops the declaration)',
|
|
90
|
+
fix: 'Use flexbox, text, images, borders, radius, gradients, shadows',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
code: 'H006',
|
|
94
|
+
severity: 'error',
|
|
95
|
+
by: 'cli',
|
|
96
|
+
catches:
|
|
97
|
+
'`meta` reads a box or calls a hook (metadata is plain data); found statically by `weft check`',
|
|
98
|
+
fix: 'Compute it from `params` and `data`',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
code: 'H007',
|
|
102
|
+
severity: 'error',
|
|
103
|
+
by: 'start',
|
|
104
|
+
catches:
|
|
105
|
+
"`meta` (or a card) reads a deferred (streamed) field of the loader's data: the request fails with 500 instead of a page with missing tags",
|
|
106
|
+
fix: 'Await the field in `load`, or leave it out of `meta`',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
code: 'H008',
|
|
110
|
+
severity: 'warning',
|
|
111
|
+
by: 'start',
|
|
112
|
+
catches:
|
|
113
|
+
"A card's `<img>` didn't load (timeout, HTTP error, not an image, too large, host not allowed); the card renders without it",
|
|
114
|
+
fix: 'Fix the URL, or embed the image with an import',
|
|
115
|
+
},
|
|
116
|
+
]
|