@meddleware/frontend-config 0.0.0-stage → 0.0.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/CHANGELOG.md +32 -0
- package/LICENSE +14 -0
- package/README.md +124 -2
- package/SECURITY.md +30 -0
- package/dist/a11y.d.ts +41 -0
- package/dist/axe.d.ts +12 -0
- package/dist/bundle.d.ts +21 -0
- package/dist/check-prod-bundle.d.ts +2 -0
- package/dist/eslint.d.ts +33 -0
- package/dist/html-validate.d.ts +30 -0
- package/dist/index.d.ts +6 -0
- package/dist/lighthouserc.d.ts +54 -0
- package/dist/look.d.ts +35 -0
- package/dist/playwright.d.ts +21 -0
- package/dist/postcss.d.ts +5 -0
- package/dist/shots.d.ts +10 -0
- package/dist/stylelint.d.ts +105 -0
- package/dist/vite.d.ts +43 -0
- package/dist/vitest.d.ts +11 -0
- package/dist/wallet.d.ts +42 -0
- package/package.json +167 -4
- package/src/a11y.js +235 -0
- package/src/axe.js +43 -0
- package/src/browserslist-config.cjs +21 -0
- package/src/bundle.js +57 -0
- package/src/check-prod-bundle.js +45 -0
- package/src/eslint.js +88 -0
- package/src/html-validate.js +49 -0
- package/src/index.js +12 -0
- package/src/lighthouserc.js +39 -0
- package/src/look.js +59 -0
- package/src/playwright.js +62 -0
- package/src/postcss.js +7 -0
- package/src/shots.js +33 -0
- package/src/stylelint.js +107 -0
- package/src/vite.js +87 -0
- package/src/vitest.js +52 -0
- package/src/wallet.js +38 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.0.2] - 2026-11-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `check-prod-bundle` (and `scanBundle` at `/bundle`): the production-artefact scan for the e2e harness, moved here from token-issuance-ui so
|
|
13
|
+
every wallet app runs the same one (the mock-wallet hooks; `--also` / `--also-regex` add an app's own stand-in marker).
|
|
14
|
+
|
|
15
|
+
- `expectKeyboardFocusVisible` (and so `defineA11ySuite`) now also fails a stop that is entirely hidden behind sticky or fixed content when it
|
|
16
|
+
takes focus (WCAG 2.4.11 focus not obscured; axe has no rule for it), going forward and then backwards through the tab order; the message names the covering element, and the pointer is parked first so a stale hover tooltip cannot cover the walk; each stop is judged after the scroll has settled (WebKit animates the scroll that focus causes).
|
|
17
|
+
|
|
18
|
+
- `defineA11ySuite` takes `noKeyboard: [state names]` for states with nothing to operate (they skip the keyboard pass; every other state must
|
|
19
|
+
have a stop). `expectKeyboardFocusVisible` no longer reports "no keyboard stops" for a page reached with focus on its last control.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The keyboard walk follows element identity (a repeated link label no longer ends it early), stops at 300 stops and fails when it is capped (`maxStops` raises the cap); its test limit scales with the cap (WebKit needs about half a minute for a page of a hundred stops).
|
|
24
|
+
- `SECURITY.md` is part of the published files.
|
|
25
|
+
|
|
26
|
+
## [0.0.1] - 2026-10-31
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- First release. Shared browserslist config, ESLint (`vueApp`), Stylelint, html-validate, PostCSS, Vite target, Playwright config,
|
|
31
|
+
axe helper (WCAG 2.2 AA gate, AAA reported), screenshot matrix and Lighthouse CI config, extracted from the per-repo copies in
|
|
32
|
+
the frontend repos.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
BSD Zero Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MeddleWare
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
9
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
10
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
11
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
12
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
13
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
14
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,125 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @meddleware/frontend-config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared frontend tooling for MeddleWare apps and libraries, so a repo's config files are one-liners and every repo is held to the
|
|
4
|
+
same accessibility and browser-support bar.
|
|
5
|
+
|
|
6
|
+
| Subpath | What it gives you |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| `/browserslist-config` | The browser support promise (below), as a browserslist shared config. |
|
|
9
|
+
| `/eslint` | `vueApp({ name, sui?, ignores?, controlComponents?, labelComponents?, extra? })` — Vue + TypeScript, `eslint-plugin-vuejs-accessibility`, `eslint-plugin-compat`, optionally the Sui boundary from `@meddleware/eslint-config`. |
|
|
10
|
+
| `/stylelint` | `stylelintConfig({ tokens?, compat?, browsers?, rules? })` — standard config, no hex, token-only colour and `z-index`, `@double-great/stylelint-a11y`, `stylelint-no-unsupported-browser-features`. |
|
|
11
|
+
| `/html-validate` | `htmlValidateConfig({ elements?, rules? })` — recommended + `html-validate:a11y` + Vue, with `@meddleware/ui` components described as the native element they render. |
|
|
12
|
+
| `/postcss` | Autoprefixer for the repo's browserslist. |
|
|
13
|
+
| `/vite` | `viteTarget()` — Vite `build.target` derived from the browserslist; `themeInit({ default })` — a Vite plugin that loads ui's `theme-init.js` first in `<head>` (the saved or default colour mode and the season applied before first paint, no inline script, no CSP hash) and adds `color-scheme` and the browser-chrome `theme-color` from the tokens. |
|
|
14
|
+
| `/playwright` | `definePlaywrightConfig({ port, kind?, command?, browsers?, extra? })`. |
|
|
15
|
+
| `/axe` | `expectAccessible(page, options)` — WCAG 2.2 AA gate; AAA as `aaa: 'gate'`, `'report'` or `'off'`. |
|
|
16
|
+
| `/look` | `looks()`, `applyLook(page, theme, season)`, `offline(page)`, `VIEWPORTS`, `THEMES`, `SEASONS` — the viewport × colour mode × season matrix. |
|
|
17
|
+
| `/shots` | `defineShots(states)` — one screenshot per state × look, for looking at the UI. |
|
|
18
|
+
| `/a11y` | `defineA11ySuite(states, { aaa })` — the sweep every app runs, one call: each state in every colour mode × season at WCAG 2.2 AA and AAA, a 320 px reflow, 200% text size, forced colours, reduced motion and a keyboard pass. `expectNoHorizontalScroll`, `expectNoRunningAnimations` and `expectKeyboardFocusVisible` are exported on their own. |
|
|
19
|
+
| `/vitest` | `inlineMeddleware` — the packages a test must process through Vite (they import `.css`/`.vue`), which also gives them the test's single copy of Vue; `installDialogShim()` — jsdom's missing `<dialog>` modality. |
|
|
20
|
+
| `/bundle` + `check-prod-bundle` | `scanBundle(dist, { also })` and the `check-prod-bundle [dist] [--also TEXT]... [--also-regex PATTERN]...` command: fails when the e2e harness (the mock-wallet hooks, or an app's own stand-in marker) is found in a production build. Run it after the production build in CI and in the Dockerfile. |
|
|
21
|
+
| `/wallet` | `connectMockWallet(page)`, `exposeSigner(page, signer)`, `E2E_ADDRESS` — the Playwright side of the mock wallet. |
|
|
22
|
+
| `/lighthouserc` | `lighthouseConfig({ port, paths?, … })` — Lighthouse CI (accessibility must score 100). |
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
// package.json
|
|
28
|
+
"browserslist": ["extends @meddleware/frontend-config/browserslist-config"]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// eslint.config.ts
|
|
33
|
+
import { vueApp } from '@meddleware/frontend-config/eslint'
|
|
34
|
+
export default vueApp({ name: 'treasury-ui', sui: true })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// stylelint.config.js
|
|
39
|
+
import { stylelintConfig } from '@meddleware/frontend-config/stylelint'
|
|
40
|
+
export default stylelintConfig()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
// .htmlvalidate.mjs
|
|
45
|
+
import { htmlValidateConfig } from '@meddleware/frontend-config/html-validate'
|
|
46
|
+
export default htmlValidateConfig()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// postcss.config.js
|
|
51
|
+
export { default } from '@meddleware/frontend-config/postcss'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// playwright.config.ts
|
|
56
|
+
import { definePlaywrightConfig } from '@meddleware/frontend-config/playwright'
|
|
57
|
+
export default definePlaywrightConfig({ port: 4281 })
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// e2e/screens.spec.ts — run with `SHOTS=1 playwright test` (a screenshot run loads only the screens specs)
|
|
62
|
+
import { defineShots } from '@meddleware/frontend-config/shots'
|
|
63
|
+
defineShots({ overview: async (page) => { await page.goto('/') } })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// e2e/a11y.spec.ts
|
|
68
|
+
import { test } from '@playwright/test'
|
|
69
|
+
import { expectAccessible } from '@meddleware/frontend-config/axe'
|
|
70
|
+
import { applyLook, looks } from '@meddleware/frontend-config/look'
|
|
71
|
+
for (const { theme, season } of looks()) {
|
|
72
|
+
test(`home is accessible · ${theme} · ${season}`, async ({ page }) => {
|
|
73
|
+
await page.goto('/')
|
|
74
|
+
await applyLook(page, theme, season)
|
|
75
|
+
await expectAccessible(page)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## The support promise
|
|
81
|
+
|
|
82
|
+
`last 2` of Chrome, Chrome for Android, Edge, Firefox, Firefox for Android, Safari, iOS Safari and Samsung Internet, plus Firefox
|
|
83
|
+
ESR, minus dead browsers. It is deliberately **not** `last 2 versions`, which also selects Opera Mini, KaiOS, Baidu and QQ: every
|
|
84
|
+
app is a Vue SPA and the wallet apps need browser extensions, so those engines cannot run them, and fallbacks for them would cost
|
|
85
|
+
CSS and complexity for no working user. Features newer than the oldest browser in the list still need `@supports` or progressive
|
|
86
|
+
enhancement; the compat linters enforce that.
|
|
87
|
+
|
|
88
|
+
## Accessibility bar
|
|
89
|
+
|
|
90
|
+
WCAG 2.2 AA is always a hard gate: `expectAccessible` fails on any `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, `wcag22aa` or axe
|
|
91
|
+
best-practice violation. WCAG AAA (`wcag2aaa`, including enhanced contrast) is the target: `aaa: 'gate'` fails the run on it,
|
|
92
|
+
`'report'` (the default, while a repo still has findings) attaches it to the test as `aaa` annotations, `'off'` skips it. A repo
|
|
93
|
+
switches to `gate` in the release that clears its AAA findings. `@meddleware/design-tokens` already guarantees 7:1 for every
|
|
94
|
+
text and label role in every theme × season, so remaining AAA findings are local colours and sizes, not the palette.
|
|
95
|
+
|
|
96
|
+
## Browsers in tests
|
|
97
|
+
|
|
98
|
+
`definePlaywrightConfig` runs Chromium locally and five projects in CI: Chromium, Firefox, WebKit, a Pixel 7 and an iPhone 14
|
|
99
|
+
viewport. `E2E_BROWSERS=all` runs all five locally (install them first: `npx playwright install chromium firefox webkit`).
|
|
100
|
+
`SHOTS=1` keeps a screenshot run on Chromium and runs only `screens.spec.ts` (which a normal run ignores): use `SHOTS=1 playwright test` with no file filter, since the tests are attributed to `shots.js`.
|
|
101
|
+
|
|
102
|
+
## Mock wallet in Playwright (`/wallet`)
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { connectMockWallet, exposeSigner, E2E_ADDRESS } from '@meddleware/frontend-config/wallet'
|
|
106
|
+
await exposeSigner(page, { signTransaction, signPersonalMessage }) // optional: sign in Node (call before page.goto)
|
|
107
|
+
await page.goto('/')
|
|
108
|
+
await connectMockWallet(page) // the app's wallet dialog → "Test Wallet"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The page side is `@meddleware/wallet-adapter/testing` (`installE2eWallet`), registered by an app's e2e build.
|
|
112
|
+
|
|
113
|
+
## Keeping the e2e harness out of production
|
|
114
|
+
|
|
115
|
+
An app's end-to-end build (`vite build --mode e2e`) registers a mock wallet and a stub chain. Three things keep that out of every
|
|
116
|
+
other build: the app's entry loads it only when `import.meta.env.MODE === 'e2e' && import.meta.env.VITE_E2E === '1'` (both static, so
|
|
117
|
+
the block is removed); `assertE2eOnlyInE2eMode(mode)` from `/vite` fails a build with `VITE_E2E` set in any other mode; and
|
|
118
|
+
`check-prod-bundle dist` (`/bundle`) scans the built files for the harness's hooks and fails if any is present. An app whose e2e build also
|
|
119
|
+
swaps a module for a stand-in adds a marker of that stand-in: `check-prod-bundle dist --also 'a string only the stand-in contains'` (`--also-regex` takes a pattern).
|
|
120
|
+
|
|
121
|
+
## Peers
|
|
122
|
+
|
|
123
|
+
`eslint` ^10 (required for `/eslint`), and optional peers per subpath: `stylelint` ^17, `html-validate` ^11, `postcss` ^8,
|
|
124
|
+
`@playwright/test` ~1.64. The lint plugins are dependencies of this package and are resolved from it, so a consumer needs none of
|
|
125
|
+
them.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
This policy covers `@meddleware/frontend-config`: lint, build and test configuration for MeddleWare frontends. It runs only in a
|
|
6
|
+
consumer's development and CI processes (ESLint, Stylelint, html-validate, PostCSS, Vite, Playwright, Lighthouse); it ships nothing
|
|
7
|
+
to end users and makes no network requests of its own.
|
|
8
|
+
|
|
9
|
+
It does not cover a consuming application, or the tools and plugins it configures.
|
|
10
|
+
|
|
11
|
+
## What it guarantees
|
|
12
|
+
|
|
13
|
+
The rules are a **guard rail**: they catch accidents and shortcuts, not deliberate evasion (a `stylelint-disable` or
|
|
14
|
+
`eslint-disable` comment defeats them). Accessibility checks are automated and cover only what axe and the linters can detect;
|
|
15
|
+
they are not a substitute for manual testing with assistive technology.
|
|
16
|
+
|
|
17
|
+
## Supply chain
|
|
18
|
+
|
|
19
|
+
Releases are published from this repository's `npm-publish.yml` with npm trusted publishing (OIDC and provenance); tokens are not
|
|
20
|
+
used. A compromised release could weaken the lint, accessibility and browser-support gates in every consumer, or run code in their
|
|
21
|
+
build, so report anything that suggests tampering.
|
|
22
|
+
|
|
23
|
+
## Supported versions
|
|
24
|
+
|
|
25
|
+
Only the latest published npm version receives fixes.
|
|
26
|
+
|
|
27
|
+
## Reporting a vulnerability
|
|
28
|
+
|
|
29
|
+
Please **do not** open a public GitHub issue for security vulnerabilities. Use GitHub's private vulnerability reporting for this
|
|
30
|
+
repository, or email dev@meddleware.co.uk.
|
package/dist/a11y.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** @typedef {(page: import('@playwright/test').Page) => Promise<void>} Open */
|
|
2
|
+
/** @typedef {Open | { open: Open, disableRules?: string[] }} State A state is how to open it, or that and the axe rules that do not apply to it (a lone dialog has no landmarks). */
|
|
3
|
+
/**
|
|
4
|
+
* The page has no horizontal scroll: nothing is wider than the viewport (WCAG 1.4.10 reflow, and 1.4.4 at a larger text size).
|
|
5
|
+
* @param {import('@playwright/test').Page} page
|
|
6
|
+
*/
|
|
7
|
+
export function expectNoHorizontalScroll(page: import("@playwright/test").Page): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* No animation is running: with `prefers-reduced-motion: reduce` the page holds still (WCAG 2.3.3).
|
|
10
|
+
* @param {import('@playwright/test').Page} page
|
|
11
|
+
*/
|
|
12
|
+
export function expectNoRunningAnimations(page: import("@playwright/test").Page): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Tab through the page: every stop is visible, shows a focus indicator (an outline or a shadow) and is not hidden behind sticky or fixed
|
|
15
|
+
* content, and the tab order ends (no trap). Then Shift+Tab back through it, so a stop reached from below is checked as well (browsers
|
|
16
|
+
* scroll a stop into view from the side it arrives from, so a sticky header or a bottom bar can cover it in only one direction).
|
|
17
|
+
* @param {import('@playwright/test').Page} page
|
|
18
|
+
* @param {{ maxStops?: number }} [options]
|
|
19
|
+
*/
|
|
20
|
+
export function expectKeyboardFocusVisible(page: import("@playwright/test").Page, { maxStops }?: {
|
|
21
|
+
maxStops?: number;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* @param {Record<string, State>} states Opens each state of the app (navigates, mocks, opens the dialog).
|
|
25
|
+
* @param {{ aaa?: 'gate' | 'report' | 'off', noKeyboard?: string[], maxStops?: number }} [options] How WCAG AAA findings are treated (default `report`; a repo
|
|
26
|
+
* moves to `gate` in the release that clears them); `noKeyboard` names states with nothing to operate (a static summary, a modal that
|
|
27
|
+
* only reports progress), which skip the keyboard pass: every other state must have at least one keyboard stop; `maxStops` (default 300) caps the keyboard walk, and a page with more stops fails rather than passing unchecked.
|
|
28
|
+
*/
|
|
29
|
+
export function defineA11ySuite(states: Record<string, State>, { aaa, noKeyboard, maxStops }?: {
|
|
30
|
+
aaa?: "gate" | "report" | "off";
|
|
31
|
+
noKeyboard?: string[];
|
|
32
|
+
maxStops?: number;
|
|
33
|
+
}): void;
|
|
34
|
+
export type Open = (page: import("@playwright/test").Page) => Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* A state is how to open it, or that and the axe rules that do not apply to it (a lone dialog has no landmarks).
|
|
37
|
+
*/
|
|
38
|
+
export type State = Open | {
|
|
39
|
+
open: Open;
|
|
40
|
+
disableRules?: string[];
|
|
41
|
+
};
|
package/dist/axe.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail on any WCAG 2.2 A/AA (or best-practice) violation. AAA findings: `aaa: 'gate'` fails on them, `'report'` (default)
|
|
3
|
+
* attaches them to the test as `aaa` annotations, `'off'` skips the AAA run.
|
|
4
|
+
* @param {import('@playwright/test').Page} page
|
|
5
|
+
* @param {{ include?: string, exclude?: string[], disableRules?: string[], aaa?: 'gate' | 'report' | 'off' }} [options]
|
|
6
|
+
*/
|
|
7
|
+
export function expectAccessible(page: import("@playwright/test").Page, options?: {
|
|
8
|
+
include?: string;
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
disableRules?: string[];
|
|
11
|
+
aaa?: "gate" | "report" | "off";
|
|
12
|
+
}): Promise<void>;
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan the built files under `dist` for the harness.
|
|
3
|
+
* @param {string} dist The build output directory.
|
|
4
|
+
* @param {{ also?: (string | RegExp)[] }} [options] Extra markers: an app's own stand-in (a string is matched literally).
|
|
5
|
+
* @returns {{ scanned: number, hits: string[] }}
|
|
6
|
+
* @throws When `dist` cannot be read.
|
|
7
|
+
*/
|
|
8
|
+
export function scanBundle(dist: string, { also }?: {
|
|
9
|
+
also?: (string | RegExp)[];
|
|
10
|
+
}): {
|
|
11
|
+
scanned: number;
|
|
12
|
+
hits: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* What the shared harness (`@meddleware/wallet-adapter/testing` `installE2eWallet`, `@meddleware/frontend-config/wallet`) leaves in a
|
|
16
|
+
* bundle that includes it: the hooks it puts on `window`. Property names survive minification.
|
|
17
|
+
*/
|
|
18
|
+
export const E2E_MARKERS: readonly {
|
|
19
|
+
name: string;
|
|
20
|
+
re: RegExp;
|
|
21
|
+
}[];
|
package/dist/eslint.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} options
|
|
3
|
+
* @param {string} options.name Prefix for the config object names (usually the repo name).
|
|
4
|
+
* @param {boolean} [options.sui] Append the Sui chain-access boundary (apps and tool libraries; not ui, tokens or sites).
|
|
5
|
+
* @param {string[]} [options.ignores] Extra global ignores (for example `docs/api/**`).
|
|
6
|
+
* @param {string[]} [options.controlComponents] Extra components that render a native form control.
|
|
7
|
+
* @param {string[]} [options.labelComponents] Extra components that render a label.
|
|
8
|
+
* @param {FlatConfig[]} [options.extra] Extra config objects, placed before the boundary.
|
|
9
|
+
* @returns {FlatConfig[]}
|
|
10
|
+
*/
|
|
11
|
+
export function vueApp({ name, sui, ignores, controlComponents, labelComponents, extra }: {
|
|
12
|
+
name: string;
|
|
13
|
+
sui?: boolean | undefined;
|
|
14
|
+
ignores?: string[] | undefined;
|
|
15
|
+
controlComponents?: string[] | undefined;
|
|
16
|
+
labelComponents?: string[] | undefined;
|
|
17
|
+
extra?: FlatConfig[] | undefined;
|
|
18
|
+
}): FlatConfig[];
|
|
19
|
+
/** `@meddleware/ui` components that render a native control, so the label rules see through them. */
|
|
20
|
+
export const CONTROL_COMPONENTS: readonly string[];
|
|
21
|
+
/** `@meddleware/ui` components that render the `<label for>` for the control in their default slot. */
|
|
22
|
+
export const LABEL_COMPONENTS: readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* One flat-config object. Declared structurally (rather than as ESLint's own type) because ESLint's type refers to internal
|
|
25
|
+
* packages that TypeScript cannot name in a declaration file; ESLint accepts this shape.
|
|
26
|
+
*/
|
|
27
|
+
export type FlatConfig = {
|
|
28
|
+
name?: string;
|
|
29
|
+
files?: (string | string[])[];
|
|
30
|
+
ignores?: string[];
|
|
31
|
+
rules?: Record<string, unknown>;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} [options]
|
|
3
|
+
* @param {Record<string, unknown>} [options.elements] Extra element descriptions.
|
|
4
|
+
* @param {Record<string, unknown>} [options.rules] Rule overrides.
|
|
5
|
+
* @returns {import('html-validate').ConfigData}
|
|
6
|
+
*/
|
|
7
|
+
export function htmlValidateConfig({ elements, rules }?: {
|
|
8
|
+
elements?: Record<string, unknown> | undefined;
|
|
9
|
+
rules?: Record<string, unknown> | undefined;
|
|
10
|
+
}): import("html-validate").ConfigData;
|
|
11
|
+
/**
|
|
12
|
+
* `@meddleware/ui` components that render a single native element, described to the validator so content models and form
|
|
13
|
+
* rules see through them (keys are lowercased tag names).
|
|
14
|
+
*/
|
|
15
|
+
export const UI_ELEMENTS: Readonly<{
|
|
16
|
+
uibutton: {
|
|
17
|
+
inherit: string;
|
|
18
|
+
};
|
|
19
|
+
uitoolbarbutton: {
|
|
20
|
+
inherit: string;
|
|
21
|
+
};
|
|
22
|
+
uiselect: {
|
|
23
|
+
inherit: string;
|
|
24
|
+
};
|
|
25
|
+
uinotice: {
|
|
26
|
+
inherit: string;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
declare const _default: import("html-validate").ConfigData;
|
|
30
|
+
export default _default;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** axe tags that gate a build: WCAG 2.0, 2.1 and 2.2 level A and AA, plus axe's best practices. */
|
|
2
|
+
export const AXE_TAGS_AA: readonly string[];
|
|
3
|
+
/** axe tags reported but never gating: WCAG AAA (enhanced contrast and so on). */
|
|
4
|
+
export const AXE_TAGS_AAA: readonly string[];
|
|
5
|
+
/** Playwright projects for the browsers the support promise names (engines: Chromium, Gecko, WebKit; two mobile viewports). */
|
|
6
|
+
export const BROWSER_PROJECTS: readonly string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} options
|
|
3
|
+
* @param {number} options.port Port `vite preview` (or `vitepress preview`) serves on.
|
|
4
|
+
* @param {string[]} [options.paths] Routes to audit.
|
|
5
|
+
* @param {string} [options.startServerCommand] Command that serves the built site (default `npx vite preview`).
|
|
6
|
+
* @param {number} [options.maxScriptKb] Warn above this much JavaScript on a route.
|
|
7
|
+
* @param {number} [options.maxStylesheetKb] Warn above this much CSS on a route.
|
|
8
|
+
*/
|
|
9
|
+
export function lighthouseConfig({ port, paths, startServerCommand, maxScriptKb, maxStylesheetKb }: {
|
|
10
|
+
port: number;
|
|
11
|
+
paths?: string[] | undefined;
|
|
12
|
+
startServerCommand?: string | undefined;
|
|
13
|
+
maxScriptKb?: number | undefined;
|
|
14
|
+
maxStylesheetKb?: number | undefined;
|
|
15
|
+
}): {
|
|
16
|
+
ci: {
|
|
17
|
+
collect: {
|
|
18
|
+
url: string[];
|
|
19
|
+
startServerCommand: string;
|
|
20
|
+
startServerReadyPattern: string;
|
|
21
|
+
numberOfRuns: number;
|
|
22
|
+
settings: {
|
|
23
|
+
chromeFlags: string;
|
|
24
|
+
onlyCategories: string[];
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
assert: {
|
|
28
|
+
assertions: {
|
|
29
|
+
'categories:accessibility': (string | {
|
|
30
|
+
minScore: number;
|
|
31
|
+
})[];
|
|
32
|
+
'categories:best-practices': (string | {
|
|
33
|
+
minScore: number;
|
|
34
|
+
})[];
|
|
35
|
+
'categories:seo': (string | {
|
|
36
|
+
minScore: number;
|
|
37
|
+
})[];
|
|
38
|
+
'categories:performance': (string | {
|
|
39
|
+
minScore: number;
|
|
40
|
+
})[];
|
|
41
|
+
'resource-summary:script:size': (string | {
|
|
42
|
+
maxNumericValue: number;
|
|
43
|
+
})[];
|
|
44
|
+
'resource-summary:stylesheet:size': (string | {
|
|
45
|
+
maxNumericValue: number;
|
|
46
|
+
})[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
upload: {
|
|
50
|
+
target: string;
|
|
51
|
+
outputDir: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
package/dist/look.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Desktop covers every theme × season; tablet and phone cover each theme with no season. */
|
|
2
|
+
export function looks(): {
|
|
3
|
+
viewport: keyof typeof VIEWPORTS;
|
|
4
|
+
theme: string;
|
|
5
|
+
season: string;
|
|
6
|
+
}[];
|
|
7
|
+
/**
|
|
8
|
+
* Refuse every request that leaves localhost, so a run needs no network. Failed fetches are expected and are not page errors.
|
|
9
|
+
* @param {import('@playwright/test').Page} page
|
|
10
|
+
*/
|
|
11
|
+
export function offline(page: import("@playwright/test").Page): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Put the page in a look. Uses the real colour-mode control when the page has one and is not behind a modal dialog (as a visitor would, so
|
|
14
|
+
* the control's own state matches the page); otherwise sets the attribute (galleries; VitePress, which themes with a `.dark` class).
|
|
15
|
+
* @param {import('@playwright/test').Page} page
|
|
16
|
+
* @param {string} theme `light` | `dark`
|
|
17
|
+
* @param {string} season `none` | a season name
|
|
18
|
+
*/
|
|
19
|
+
export function applyLook(page: import("@playwright/test").Page, theme: string, season: string): Promise<void>;
|
|
20
|
+
export const VIEWPORTS: Readonly<{
|
|
21
|
+
phone: Readonly<{
|
|
22
|
+
width: 390;
|
|
23
|
+
height: 844;
|
|
24
|
+
}>;
|
|
25
|
+
tablet: Readonly<{
|
|
26
|
+
width: 768;
|
|
27
|
+
height: 1024;
|
|
28
|
+
}>;
|
|
29
|
+
desktop: Readonly<{
|
|
30
|
+
width: 1280;
|
|
31
|
+
height: 900;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
export const THEMES: readonly string[];
|
|
35
|
+
export const SEASONS: readonly string[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** @typedef {'chromium' | 'all'} BrowserSet */
|
|
2
|
+
/**
|
|
3
|
+
* @param {object} options
|
|
4
|
+
* @param {number} options.port Preview server port (unique per repo, so repos can run side by side).
|
|
5
|
+
* @param {'app' | 'gallery' | 'vitepress'} [options.kind] What is served. `app`: `npm run build` then `vite preview`.
|
|
6
|
+
* `gallery`: `npm run build:gallery` then `vite preview --config vite.gallery.config.ts`. `vitepress`: `vitepress preview docs`
|
|
7
|
+
* (the site must already be built, so the repo's `test:e2e` script builds first).
|
|
8
|
+
* @param {string} [options.command] Full server command; replaces the one `kind` implies.
|
|
9
|
+
* @param {BrowserSet} [options.browsers] Browser set; default from E2E_BROWSERS, else `all` in CI and `chromium` otherwise.
|
|
10
|
+
* @param {string} [options.testDir]
|
|
11
|
+
* @param {import('@playwright/test').PlaywrightTestConfig} [options.extra] Merged over the defaults.
|
|
12
|
+
*/
|
|
13
|
+
export function definePlaywrightConfig({ port, kind, command, browsers, testDir, extra }: {
|
|
14
|
+
port: number;
|
|
15
|
+
kind?: "app" | "gallery" | "vitepress" | undefined;
|
|
16
|
+
command?: string | undefined;
|
|
17
|
+
browsers?: BrowserSet | undefined;
|
|
18
|
+
testDir?: string | undefined;
|
|
19
|
+
extra?: import("playwright/test").PlaywrightTestConfig<{}, {}> | undefined;
|
|
20
|
+
}): import("playwright/test").PlaywrightTestConfig<{}, {}>;
|
|
21
|
+
export type BrowserSet = "chromium" | "all";
|
package/dist/shots.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @typedef {(page: import('@playwright/test').Page) => Promise<void>} State */
|
|
2
|
+
/**
|
|
3
|
+
* Register one test per state × look. Call once, at the top level of an `e2e/screens.spec.ts`.
|
|
4
|
+
* @param {Record<string, State>} states
|
|
5
|
+
* @param {{ out?: string }} [options]
|
|
6
|
+
*/
|
|
7
|
+
export function defineShots(states: Record<string, State>, options?: {
|
|
8
|
+
out?: string;
|
|
9
|
+
}): void;
|
|
10
|
+
export type State = (page: import("@playwright/test").Page) => Promise<void>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} [options]
|
|
3
|
+
* @param {'error' | 'warning' | false} [options.tokens] Severity of the token-only rule. Default `error`; a repo with
|
|
4
|
+
* literals still to fix starts at `warning` and moves to `error` in the same release as the fixes.
|
|
5
|
+
* @param {'error' | 'warning'} [options.compat] Severity of unsupported-browser-feature findings. Default `error`.
|
|
6
|
+
* @param {string[]} [options.browsers] Browsers to check against. Default: the repo's browserslist (the shared support promise).
|
|
7
|
+
* @param {Record<string, unknown>} [options.rules] Rule overrides.
|
|
8
|
+
*/
|
|
9
|
+
export function stylelintConfig({ tokens, compat, browsers, rules }?: {
|
|
10
|
+
tokens?: false | "error" | "warning" | undefined;
|
|
11
|
+
compat?: "error" | "warning" | undefined;
|
|
12
|
+
browsers?: string[] | undefined;
|
|
13
|
+
rules?: Record<string, unknown> | undefined;
|
|
14
|
+
}): {
|
|
15
|
+
extends: string[];
|
|
16
|
+
plugins: string[];
|
|
17
|
+
overrides: {
|
|
18
|
+
files: string[];
|
|
19
|
+
customSyntax: ((options?: Record<string, any>) => import("postcss").Syntax) & import("postcss").Syntax<import("postcss").Document | import("postcss").Root>;
|
|
20
|
+
}[];
|
|
21
|
+
rules: {
|
|
22
|
+
'plugin/no-unsupported-browser-features': (boolean | {
|
|
23
|
+
browsers?: string[] | undefined;
|
|
24
|
+
severity: "error" | "warning";
|
|
25
|
+
ignorePartialSupport: boolean;
|
|
26
|
+
ignore: string[];
|
|
27
|
+
})[];
|
|
28
|
+
'a11y/media-prefers-reduced-motion': null;
|
|
29
|
+
'a11y/no-outline-none': boolean;
|
|
30
|
+
'a11y/selector-pseudo-class-focus': null;
|
|
31
|
+
'a11y/content-property-no-static-value': boolean;
|
|
32
|
+
'a11y/font-size-is-readable': boolean;
|
|
33
|
+
'a11y/no-obsolete-attribute': boolean;
|
|
34
|
+
'a11y/no-obsolete-element': boolean;
|
|
35
|
+
'a11y/no-text-align-justify': boolean;
|
|
36
|
+
'scale-unlimited/declaration-strict-value'?: (string[] | {
|
|
37
|
+
ignoreValues: string[];
|
|
38
|
+
disableFix: boolean;
|
|
39
|
+
severity: "error" | "warning";
|
|
40
|
+
})[] | undefined;
|
|
41
|
+
'custom-property-empty-line-before': null;
|
|
42
|
+
'declaration-empty-line-before': null;
|
|
43
|
+
'comment-empty-line-before': null;
|
|
44
|
+
'rule-empty-line-before': null;
|
|
45
|
+
'at-rule-empty-line-before': null;
|
|
46
|
+
'no-descending-specificity': null;
|
|
47
|
+
'selector-class-pattern': null;
|
|
48
|
+
'declaration-block-single-line-max-declarations': null;
|
|
49
|
+
'value-keyword-case': null;
|
|
50
|
+
'selector-pseudo-class-no-unknown': (boolean | {
|
|
51
|
+
ignorePseudoClasses: string[];
|
|
52
|
+
})[];
|
|
53
|
+
'custom-property-pattern': null;
|
|
54
|
+
'property-no-vendor-prefix': null;
|
|
55
|
+
'value-no-vendor-prefix': null;
|
|
56
|
+
'color-no-hex': boolean;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
declare namespace _default {
|
|
60
|
+
let _extends: string[];
|
|
61
|
+
export { _extends as extends };
|
|
62
|
+
export let plugins: string[];
|
|
63
|
+
export let overrides: {
|
|
64
|
+
files: string[];
|
|
65
|
+
customSyntax: ((options?: Record<string, any>) => import("postcss").Syntax) & import("postcss").Syntax<import("postcss").Document | import("postcss").Root>;
|
|
66
|
+
}[];
|
|
67
|
+
export let rules: {
|
|
68
|
+
'plugin/no-unsupported-browser-features': (boolean | {
|
|
69
|
+
browsers?: string[] | undefined;
|
|
70
|
+
severity: "error" | "warning";
|
|
71
|
+
ignorePartialSupport: boolean;
|
|
72
|
+
ignore: string[];
|
|
73
|
+
})[];
|
|
74
|
+
'a11y/media-prefers-reduced-motion': null;
|
|
75
|
+
'a11y/no-outline-none': boolean;
|
|
76
|
+
'a11y/selector-pseudo-class-focus': null;
|
|
77
|
+
'a11y/content-property-no-static-value': boolean;
|
|
78
|
+
'a11y/font-size-is-readable': boolean;
|
|
79
|
+
'a11y/no-obsolete-attribute': boolean;
|
|
80
|
+
'a11y/no-obsolete-element': boolean;
|
|
81
|
+
'a11y/no-text-align-justify': boolean;
|
|
82
|
+
'scale-unlimited/declaration-strict-value'?: (string[] | {
|
|
83
|
+
ignoreValues: string[];
|
|
84
|
+
disableFix: boolean;
|
|
85
|
+
severity: "error" | "warning";
|
|
86
|
+
})[] | undefined;
|
|
87
|
+
'custom-property-empty-line-before': null;
|
|
88
|
+
'declaration-empty-line-before': null;
|
|
89
|
+
'comment-empty-line-before': null;
|
|
90
|
+
'rule-empty-line-before': null;
|
|
91
|
+
'at-rule-empty-line-before': null;
|
|
92
|
+
'no-descending-specificity': null;
|
|
93
|
+
'selector-class-pattern': null;
|
|
94
|
+
'declaration-block-single-line-max-declarations': null;
|
|
95
|
+
'value-keyword-case': null;
|
|
96
|
+
'selector-pseudo-class-no-unknown': (boolean | {
|
|
97
|
+
ignorePseudoClasses: string[];
|
|
98
|
+
})[];
|
|
99
|
+
'custom-property-pattern': null;
|
|
100
|
+
'property-no-vendor-prefix': null;
|
|
101
|
+
'value-no-vendor-prefix': null;
|
|
102
|
+
'color-no-hex': boolean;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export default _default;
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** @param {string | string[]} [queries] Browserslist queries; default: the repo's own configuration. */
|
|
2
|
+
export function viteTarget(queries?: string | string[]): string[];
|
|
3
|
+
/**
|
|
4
|
+
* The colour mode and the browser chrome, before the first paint: a Vite plugin for an app's `index.html`.
|
|
5
|
+
*
|
|
6
|
+
* - Serves `@meddleware/ui`'s `theme-init.js` at `/theme-init.js` (dev) and emits it into the build, and loads it as the first,
|
|
7
|
+
* render-blocking script in `<head>`. It applies the saved (or default) colour mode and the season to `<html>` before anything
|
|
8
|
+
* paints, so a page never flashes the wrong theme while its JavaScript loads. It is a same-origin file, so the CSP needs no hash
|
|
9
|
+
* and no `'unsafe-inline'`.
|
|
10
|
+
* - Adds `<meta name="color-scheme" content="light dark">` (native controls and scrollbars follow the theme) and the browser-chrome
|
|
11
|
+
* `theme-color` for each colour mode, from the design tokens' page background.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* plugins: [vue(), themeInit({ default: 'dark' })]
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @param {object} [options]
|
|
18
|
+
* @param {'light' | 'dark' | 'system'} [options.default] The mode a first visit gets (a saved choice always wins). Default `system`.
|
|
19
|
+
* @param {string} [options.scriptPath] The script to serve (default: `@meddleware/ui/theme-init.js` as installed in the app).
|
|
20
|
+
* @param {{ light: string, dark: string }} [options.colors] The `theme-color` values (default: the tokens' page backgrounds).
|
|
21
|
+
* @returns {import('vite').Plugin}
|
|
22
|
+
*/
|
|
23
|
+
export function themeInit({ default: mode, scriptPath, colors }?: {
|
|
24
|
+
default?: "light" | "dark" | "system" | undefined;
|
|
25
|
+
scriptPath?: string | undefined;
|
|
26
|
+
colors?: {
|
|
27
|
+
light: string;
|
|
28
|
+
dark: string;
|
|
29
|
+
} | undefined;
|
|
30
|
+
}): import("vite").Plugin;
|
|
31
|
+
/**
|
|
32
|
+
* The end-to-end build mode registers a mock wallet and a stub chain client in the app (`@meddleware/wallet-adapter/testing`), which must
|
|
33
|
+
* never reach a production build. The app's entry loads it only when `import.meta.env.MODE === 'e2e' && import.meta.env.VITE_E2E === '1'`
|
|
34
|
+
* (both static, so every other build drops the block); this makes `VITE_E2E=1` outside `--mode e2e` a build error instead of a silent no-op.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* export default defineConfig(({ mode }) => { assertE2eOnlyInE2eMode(mode, process.env); return { … } })
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param {string} mode Vite's mode.
|
|
41
|
+
* @param {Record<string, string | undefined>} env The environment (default: the process's).
|
|
42
|
+
*/
|
|
43
|
+
export function assertE2eOnlyInE2eMode(mode: string, env?: Record<string, string | undefined>): void;
|