@meddleware/frontend-config 0.0.7 → 0.0.9
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 +14 -0
- package/README.md +1 -1
- package/dist/vite.d.ts +16 -1
- package/package.json +1 -1
- package/src/csp.js +1 -1
- package/src/sync.js +12 -1
- package/src/vite.js +72 -2
- package/templates/audit-allowlist.lhci.json +1 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.9] - 2026-11-13
|
|
9
|
+
|
|
10
|
+
The Lighthouse audit allowlist template is empty: @lhci/cli 0.16 no longer installs extract-zip, so the two dated extract-zip entries are gone (mw-sync --write adopts it).
|
|
11
|
+
|
|
12
|
+
## [0.0.8] - 2026-11-08
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `brandIcons` (`/vite`, added by `meddlewareApp`): serves and emits the company mark's favicon set from `@meddleware/ui/brand/*` with a web manifest and the `<link>` tags. `mw-sync` fails an app that keeps its own `public/favicon.*` (`--write` removes it).
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- The `docs` CSP profile no longer allows `https:` images (the documentation sites load none).
|
|
21
|
+
|
|
8
22
|
## [0.0.7] - 2026-11-07
|
|
9
23
|
|
|
10
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ same accessibility and browser-support bar.
|
|
|
10
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
11
|
| `/html-validate` | `htmlValidateConfig({ elements?, rules? })` — recommended + `html-validate:a11y` + Vue, with `@meddleware/ui` components described as the native element they render. |
|
|
12
12
|
| `/postcss` | Autoprefixer for the repo's browserslist. |
|
|
13
|
-
| `/vite` | `viteTarget()` — Vite `build.target` derived from the browserslist; `meddlewareApp({ plugins, theme?, wallet?, config? })` — the whole Vite configuration of a tool app (target, theme-init, e2e guard, wallet-adapter kept out of pre-bundling); `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. |
|
|
13
|
+
| `/vite` | `viteTarget()` — Vite `build.target` derived from the browserslist; `meddlewareApp({ plugins, theme?, wallet?, name?, config? })` — the whole Vite configuration of a tool app (target, theme-init, brand icons, e2e guard, wallet-adapter kept out of pre-bundling); `brandIcons({ name? })` — the company mark's favicon set from `@meddleware/ui/brand/*` (served in dev, emitted at the site root with a web manifest, `<link>` tags injected; an app keeps no icon of its own and `mw-sync --check` fails on a `public/favicon.*`); `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
14
|
| `/playwright` | `definePlaywrightConfig({ port, kind?, command?, browsers?, extra? })`. |
|
|
15
15
|
| `/axe` | `expectAccessible(page, options)` — WCAG 2.2 AA gate; AAA as `aaa: 'gate'`, `'report'` or `'off'`. |
|
|
16
16
|
| `/look` | `looks()`, `applyLook(page, theme, season)`, `offline(page)`, `VIEWPORTS`, `THEMES`, `SEASONS` — the viewport × colour mode × season matrix. |
|
package/dist/vite.d.ts
CHANGED
|
@@ -57,12 +57,27 @@ export function assertE2eOnlyInE2eMode(mode: string, env?: Record<string, string
|
|
|
57
57
|
* @param {import('vite').PluginOption[]} options.plugins The app's plugins (`vue()` …); `themeInit` is added after them.
|
|
58
58
|
* @param {'light' | 'dark' | 'system'} [options.theme] The mode a first visit gets (default `dark`, the brand default).
|
|
59
59
|
* @param {boolean} [options.wallet] The app uses `@meddleware/wallet-adapter` (default true): keep it out of pre-bundling.
|
|
60
|
+
* @param {string} [options.name] The app's name for the web manifest (default `Meddleware`).
|
|
60
61
|
* @param {import('vite').UserConfig} [options.config] Anything else (`base`, `build.rollupOptions`, `server` …); `build` is merged.
|
|
61
62
|
* @returns {import('vite').UserConfigFnObject}
|
|
62
63
|
*/
|
|
63
|
-
export function meddlewareApp({ plugins, theme, wallet, config }: {
|
|
64
|
+
export function meddlewareApp({ plugins, theme, wallet, name, config }: {
|
|
64
65
|
plugins: import("vite").PluginOption[];
|
|
65
66
|
theme?: "light" | "dark" | "system" | undefined;
|
|
66
67
|
wallet?: boolean | undefined;
|
|
68
|
+
name?: string | undefined;
|
|
67
69
|
config?: import("vite").UserConfig | undefined;
|
|
68
70
|
}): import("vite").UserConfigFnObject;
|
|
71
|
+
/**
|
|
72
|
+
* The favicon set of the company mark (the Kopimi "K"), for an app's `index.html`: serves the files from `@meddleware/ui` in dev, emits them
|
|
73
|
+
* into the build at the site root (`/favicon.svg`, `/favicon.ico`, `/apple-touch-icon.png`, `/icon-192.png`, `/icon-512.png`, `/site.webmanifest`)
|
|
74
|
+
* and adds the `<link>` tags. An app keeps no icon of its own (`mw-sync --check` fails on a `public/favicon.*`), and its `index.html` has no
|
|
75
|
+
* `<link rel="icon">`.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} [options]
|
|
78
|
+
* @param {string} [options.name] The app's name, for the web manifest. Default `Meddleware`.
|
|
79
|
+
* @returns {import('vite').Plugin}
|
|
80
|
+
*/
|
|
81
|
+
export function brandIcons({ name }?: {
|
|
82
|
+
name?: string | undefined;
|
|
83
|
+
}): import("vite").Plugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/frontend-config",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared frontend tooling for MeddleWare apps: browser support, ESLint, Stylelint, html-validate, PostCSS, Playwright, axe, screenshots, Lighthouse and the Vite target.",
|
|
6
6
|
"license": "0BSD",
|
package/src/csp.js
CHANGED
|
@@ -36,7 +36,7 @@ export const PROFILES = {
|
|
|
36
36
|
export function buildCsp(options = {}) {
|
|
37
37
|
const { wasm = false, chain = false, worker = false, strict = false, frameAncestors = 'self', scriptHashes = [] } = options
|
|
38
38
|
const script = ["'self'", ...(wasm ? ["'wasm-unsafe-eval'"] : []), ...scriptHashes.map((h) => `'${h.replace(/^'|'$/g, '')}'`)]
|
|
39
|
-
const richImages = chain
|
|
39
|
+
const richImages = chain // only an app that shows other people's images (a chain app: wallet icons, blob previews) may load https images
|
|
40
40
|
const directives = [
|
|
41
41
|
["default-src", "'self'"],
|
|
42
42
|
['script-src', script.join(' ')],
|
package/src/sync.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// kind "app" → .github/dependabot.yml, .dockerignore, postcss.config.js, stylelint.config.js are the templates
|
|
12
12
|
// csp a profile of ./csp.js → the Dockerfile's `ARG CSP="…"` is that profile's policy (`docs` keeps the script hashes already there)
|
|
13
13
|
// lhci true → .github/audit-allowlist.json is the template (the dated allowlist for the Lighthouse CI tool chain; one expiry, reviewed together)
|
|
14
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
|
15
15
|
import { dirname, join } from 'node:path'
|
|
16
16
|
import { fileURLToPath } from 'node:url'
|
|
17
17
|
import { cspForProfile } from './csp.js'
|
|
@@ -48,6 +48,17 @@ export function sync(root, options = {}) {
|
|
|
48
48
|
expectFile('postcss.config.js', tpl('postcss.config.js'))
|
|
49
49
|
expectFile('stylelint.config.js', tpl('stylelint.config.js'))
|
|
50
50
|
}
|
|
51
|
+
if (cfg.kind === 'app') {
|
|
52
|
+
// The favicon set comes from `@meddleware/ui` through `brandIcons` (an app keeps none of its own).
|
|
53
|
+
for (const f of ['public/favicon.svg', 'public/favicon.ico', 'public/apple-touch-icon.png']) {
|
|
54
|
+
const p = join(root, f)
|
|
55
|
+
if (!existsSync(p)) continue
|
|
56
|
+
if (options.write) {
|
|
57
|
+
rmSync(p)
|
|
58
|
+
out.push({ file: f, ok: true, note: 'removed (served by brandIcons from @meddleware/ui)' })
|
|
59
|
+
} else out.push({ file: f, ok: false, note: 'an app serves the brand icons through brandIcons; delete this file (mw-sync --write does)' })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
51
62
|
if (cfg.lhci === true) expectFile('.github/audit-allowlist.json', tpl('audit-allowlist.lhci.json'))
|
|
52
63
|
if (cfg.csp) {
|
|
53
64
|
const df = join(root, 'Dockerfile')
|
package/src/vite.js
CHANGED
|
@@ -102,17 +102,87 @@ export function assertE2eOnlyInE2eMode(mode, env = process.env) {
|
|
|
102
102
|
* @param {import('vite').PluginOption[]} options.plugins The app's plugins (`vue()` …); `themeInit` is added after them.
|
|
103
103
|
* @param {'light' | 'dark' | 'system'} [options.theme] The mode a first visit gets (default `dark`, the brand default).
|
|
104
104
|
* @param {boolean} [options.wallet] The app uses `@meddleware/wallet-adapter` (default true): keep it out of pre-bundling.
|
|
105
|
+
* @param {string} [options.name] The app's name for the web manifest (default `Meddleware`).
|
|
105
106
|
* @param {import('vite').UserConfig} [options.config] Anything else (`base`, `build.rollupOptions`, `server` …); `build` is merged.
|
|
106
107
|
* @returns {import('vite').UserConfigFnObject}
|
|
107
108
|
*/
|
|
108
|
-
export function meddlewareApp({ plugins, theme = 'dark', wallet = true, config = {} }) {
|
|
109
|
+
export function meddlewareApp({ plugins, theme = 'dark', wallet = true, name, config = {} }) {
|
|
109
110
|
return ({ mode }) => {
|
|
110
111
|
assertE2eOnlyInE2eMode(mode)
|
|
111
112
|
return {
|
|
112
113
|
...config,
|
|
113
114
|
build: { target: viteTarget(), ...config.build },
|
|
114
|
-
plugins: [...plugins, themeInit({ default: theme })],
|
|
115
|
+
plugins: [...plugins, themeInit({ default: theme }), brandIcons({ name })],
|
|
115
116
|
...(wallet ? { optimizeDeps: { ...config.optimizeDeps, exclude: ['@meddleware/wallet-adapter', ...(config.optimizeDeps?.exclude ?? [])] } } : {}),
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
|
|
121
|
+
/** The brand files `@meddleware/ui` publishes (`@meddleware/ui/brand/*`), by served name. */
|
|
122
|
+
const BRAND_FILES = /** @type {const} */ ({
|
|
123
|
+
'favicon.svg': 'image/svg+xml',
|
|
124
|
+
'favicon.ico': 'image/x-icon',
|
|
125
|
+
'apple-touch-icon.png': 'image/png',
|
|
126
|
+
'icon-192.png': 'image/png',
|
|
127
|
+
'icon-512.png': 'image/png',
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The favicon set of the company mark (the Kopimi "K"), for an app's `index.html`: serves the files from `@meddleware/ui` in dev, emits them
|
|
132
|
+
* into the build at the site root (`/favicon.svg`, `/favicon.ico`, `/apple-touch-icon.png`, `/icon-192.png`, `/icon-512.png`, `/site.webmanifest`)
|
|
133
|
+
* and adds the `<link>` tags. An app keeps no icon of its own (`mw-sync --check` fails on a `public/favicon.*`), and its `index.html` has no
|
|
134
|
+
* `<link rel="icon">`.
|
|
135
|
+
*
|
|
136
|
+
* @param {object} [options]
|
|
137
|
+
* @param {string} [options.name] The app's name, for the web manifest. Default `Meddleware`.
|
|
138
|
+
* @returns {import('vite').Plugin}
|
|
139
|
+
*/
|
|
140
|
+
export function brandIcons({ name = 'Meddleware' } = {}) {
|
|
141
|
+
const read = (/** @type {string} */ file) => readFileSync(resolveFromApp(`@meddleware/ui/brand/${file}`))
|
|
142
|
+
const manifest = () => {
|
|
143
|
+
const { semantic } = JSON.parse(readFileSync(resolveFromApp('@meddleware/design-tokens/tokens.json'), 'utf8'))
|
|
144
|
+
return JSON.stringify(
|
|
145
|
+
{
|
|
146
|
+
name,
|
|
147
|
+
short_name: name,
|
|
148
|
+
icons: [
|
|
149
|
+
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
|
|
150
|
+
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' },
|
|
151
|
+
{ src: '/favicon.svg', sizes: 'any', type: 'image/svg+xml', purpose: 'any' },
|
|
152
|
+
],
|
|
153
|
+
theme_color: semantic.dark.bg,
|
|
154
|
+
background_color: semantic.dark.bg,
|
|
155
|
+
display: 'browser',
|
|
156
|
+
},
|
|
157
|
+
null,
|
|
158
|
+
2,
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
name: 'meddleware:brand-icons',
|
|
163
|
+
configureServer(server) {
|
|
164
|
+
server.middlewares.use((req, res, next) => {
|
|
165
|
+
const file = (req.url ?? '').split('?')[0]?.slice(1) ?? ''
|
|
166
|
+
if (file in BRAND_FILES) {
|
|
167
|
+
res.setHeader('content-type', BRAND_FILES[/** @type {keyof typeof BRAND_FILES} */ (file)])
|
|
168
|
+
res.end(read(file))
|
|
169
|
+
} else if (file === 'site.webmanifest') {
|
|
170
|
+
res.setHeader('content-type', 'application/manifest+json')
|
|
171
|
+
res.end(manifest())
|
|
172
|
+
} else next()
|
|
173
|
+
})
|
|
174
|
+
},
|
|
175
|
+
generateBundle() {
|
|
176
|
+
for (const file of Object.keys(BRAND_FILES)) this.emitFile({ type: 'asset', fileName: file, source: read(file) })
|
|
177
|
+
this.emitFile({ type: 'asset', fileName: 'site.webmanifest', source: manifest() })
|
|
178
|
+
},
|
|
179
|
+
transformIndexHtml() {
|
|
180
|
+
return [
|
|
181
|
+
{ tag: 'link', attrs: { rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml', sizes: 'any' }, injectTo: 'head' },
|
|
182
|
+
{ tag: 'link', attrs: { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }, injectTo: 'head' },
|
|
183
|
+
{ tag: 'link', attrs: { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }, injectTo: 'head' },
|
|
184
|
+
{ tag: 'link', attrs: { rel: 'manifest', href: '/site.webmanifest' }, injectTo: 'head' },
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "GHSA-jmr9-qjv8-65gv",
|
|
4
|
-
"reason": "extract-zip has no patched release. It is a dev-only transitive of @lhci/cli (lighthouse > puppeteer-core > @puppeteer/browsers) and only unpacks browser archives that Lighthouse CI downloads itself; nothing from it ships in the image.",
|
|
5
|
-
"expires": "2027-01-31"
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
"id": "GHSA-7pqw-9j4j-h8q3",
|
|
9
|
-
"reason": "extract-zip has no patched release. It is a dev-only transitive of @lhci/cli (lighthouse > puppeteer-core > @puppeteer/browsers) and only unpacks browser archives that Lighthouse CI downloads itself; nothing from it ships in the image.",
|
|
10
|
-
"expires": "2027-01-31"
|
|
11
|
-
}
|
|
12
|
-
]
|
|
1
|
+
[]
|