@meddleware/frontend-config 0.0.6 → 0.0.7
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 +6 -0
- package/README.md +1 -1
- package/dist/network-status.d.ts +26 -5
- package/package.json +1 -1
- package/src/network-status.js +56 -22
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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.7] - 2026-11-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `mw-network-status` failed on a real install: the client packages ship TypeScript source, which Node does not load from `node_modules`. It now reads each package's generated `deployments.ts` as text.
|
|
13
|
+
|
|
8
14
|
## [0.0.6] - 2026-11-07
|
|
9
15
|
|
|
10
16
|
### Fixed
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ same accessibility and browser-support bar.
|
|
|
23
23
|
| `/tsconfig/vue-app.json` | The compiler options of a Vue app (`"extends": "@meddleware/frontend-config/tsconfig/vue-app.json"`); the repository keeps only its `include`. |
|
|
24
24
|
| `/csp` | `buildCsp(options)`, `cspForProfile(name, current?)`, `PROFILES`, `scriptHashesOf(csp)` — the Content-Security-Policy every app image serves, as six named profiles (`static`, `static-strict`, `console`, `chain`, `chain-wasm`, `docs`), so the policy is written once. |
|
|
25
25
|
| `mw-sync` | `mw-sync --check` / `--write` in a repository root: the files every frontend repository copies (dependabot.yml, .dockerignore, postcss/stylelint configs, the Lighthouse allowlist) equal this package's `templates/`, and the Dockerfile's `ARG CSP` equals its profile. Declared in package.json: `"meddleware": { "kind": "app", "csp": "chain-wasm", "lhci": true }`. |
|
|
26
|
-
| `mw-network-status` | `mw-network-status --out src/network.json` (or `--check src/network.json` in CI): which networks MeddleWare is deployed on, from the installed clients' deployment records
|
|
26
|
+
| `mw-network-status` | `mw-network-status --out src/network.json` (or `--check src/network.json` in CI): which networks MeddleWare is deployed on, from the installed clients' deployment records (read as text: the clients ship TypeScript source). The file is committed and CI checks it, so "mainnet is coming" turns into "live" by the records alone. |
|
|
27
27
|
| `mw-audit-gate` | CI dependency gate: `npm audit` at high/critical minus the dated, reasoned entries of `.github/audit-allowlist.json` (an entry with no reason or an expired date fails). |
|
|
28
28
|
| `mw-third-party-licenses` | Writes `dist/THIRD_PARTY_LICENSES` from the lockfile's production packages and their installed licence texts; `--check` fails when none can be written. |
|
|
29
29
|
| `mw-check-csp-inline` | `mw-check-csp-inline <policy or @Dockerfile> [dist]`: every inline script in the built pages is allowed by a `sha256` source in the policy, and the policy never allows `'unsafe-inline'` scripts. |
|
package/dist/network-status.d.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* @param {
|
|
3
|
+
* The network keys of `export const <constant> = { … }` in generated source: the keys at the object's top level (two-space indent).
|
|
4
|
+
* @param {string} source
|
|
5
|
+
* @param {string} constant
|
|
6
|
+
* @returns {string[]}
|
|
5
7
|
*/
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
export function networksOf(source: string, constant: string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Which networks are live: `live` when every product has a record there, `partial` when some do, `coming` when none does. (The same rule as
|
|
11
|
+
* `networkAvailability` in `@meddleware/web-utils`, which the dashboard uses at run time.)
|
|
12
|
+
* @param {Record<string, string[]>} products product name → the networks it has a record for
|
|
13
|
+
*/
|
|
14
|
+
export function availability(products: Record<string, string[]>): {
|
|
15
|
+
testnet: {
|
|
16
|
+
status: string;
|
|
17
|
+
live: string[];
|
|
18
|
+
missing: string[];
|
|
19
|
+
};
|
|
20
|
+
mainnet: {
|
|
21
|
+
status: string;
|
|
22
|
+
live: string[];
|
|
23
|
+
missing: string[];
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** @param {string} root */
|
|
27
|
+
export function readRecords(root: string): Record<string, string[]>;
|
|
28
|
+
/** The products and the constant that holds each one's records. */
|
|
29
|
+
export const PRODUCTS: readonly [readonly ["access gate", "@meddleware/access-gate-client", "ACCESS_GATE_DEPLOYMENTS"], readonly ["sealed storage", "@meddleware/sealed-storage-client", "SEAL_POLICIES_DEPLOYMENTS"]];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meddleware/frontend-config",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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/network-status.js
CHANGED
|
@@ -3,44 +3,78 @@
|
|
|
3
3
|
// mw-network-status — write which networks MeddleWare is deployed on, from the deployment records of the installed client packages, so a static
|
|
4
4
|
// site (landing page, docs, developer docs) says "mainnet is coming" until the records have a `mainnet` key and says "live" the moment they do.
|
|
5
5
|
//
|
|
6
|
-
// mw-network-status
|
|
7
|
-
// mw-network-status --check src/network.json
|
|
6
|
+
// mw-network-status --out src/network.json write the file
|
|
7
|
+
// mw-network-status --check src/network.json fail (exit 1) when the committed file differs from the records
|
|
8
|
+
// mw-network-status print to stdout
|
|
8
9
|
//
|
|
9
10
|
// The file is committed, so the page and its tests never depend on a hidden build step; CI runs `--check`, and bumping a client package
|
|
10
|
-
// (Dependabot, or a release that adds a mainnet record) fails the check until the file is regenerated with `--out`.
|
|
11
|
-
//
|
|
11
|
+
// (Dependabot, or a release that adds a mainnet record) fails the check until the file is regenerated with `--out`.
|
|
12
|
+
//
|
|
13
|
+
// The client packages ship TypeScript source, which Node does not load from node_modules, so the records are read as text: each package's
|
|
14
|
+
// generated `deployments.ts` has the shape `export const X_DEPLOYMENTS = { testnet: {…}, mainnet: {…} } as const`, and only the network keys
|
|
15
|
+
// are needed. The repository needs `@meddleware/access-gate-client` and `@meddleware/sealed-storage-client` installed; a missing one is an error.
|
|
12
16
|
import { readFileSync, realpathSync, writeFileSync } from 'node:fs'
|
|
13
17
|
import { createRequire } from 'node:module'
|
|
14
|
-
import { join } from 'node:path'
|
|
18
|
+
import { dirname, join } from 'node:path'
|
|
15
19
|
import { pathToFileURL } from 'node:url'
|
|
16
20
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
/** The products and the constant that holds each one's records. */
|
|
22
|
+
export const PRODUCTS = /** @type {const} */ ([
|
|
23
|
+
['access gate', '@meddleware/access-gate-client', 'ACCESS_GATE_DEPLOYMENTS'],
|
|
24
|
+
['sealed storage', '@meddleware/sealed-storage-client', 'SEAL_POLICIES_DEPLOYMENTS'],
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The network keys of `export const <constant> = { … }` in generated source: the keys at the object's top level (two-space indent).
|
|
29
|
+
* @param {string} source
|
|
30
|
+
* @param {string} constant
|
|
31
|
+
* @returns {string[]}
|
|
32
|
+
*/
|
|
33
|
+
export function networksOf(source, constant) {
|
|
34
|
+
const start = source.indexOf(`export const ${constant} =`)
|
|
35
|
+
if (start < 0) throw new Error(`${constant} not found`)
|
|
36
|
+
const open = source.indexOf('{', start)
|
|
37
|
+
const end = source.indexOf('\n} as const', open)
|
|
38
|
+
if (open < 0 || end < 0) throw new Error(`${constant} has an unexpected shape`)
|
|
39
|
+
return [...source.slice(open, end).matchAll(/^ {2}([a-z]+): \{/gm)].map((m) => /** @type {string} */ (m[1]))
|
|
21
40
|
}
|
|
22
41
|
|
|
23
42
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
43
|
+
* Which networks are live: `live` when every product has a record there, `partial` when some do, `coming` when none does. (The same rule as
|
|
44
|
+
* `networkAvailability` in `@meddleware/web-utils`, which the dashboard uses at run time.)
|
|
45
|
+
* @param {Record<string, string[]>} products product name → the networks it has a record for
|
|
26
46
|
*/
|
|
27
|
-
export function
|
|
28
|
-
|
|
47
|
+
export function availability(products) {
|
|
48
|
+
const names = Object.keys(products)
|
|
49
|
+
/** @param {string} network */
|
|
50
|
+
const report = (network) => {
|
|
51
|
+
const live = names.filter((p) => products[p]?.includes(network))
|
|
52
|
+
const missing = names.filter((p) => !products[p]?.includes(network))
|
|
53
|
+
return { status: names.length > 0 && missing.length === 0 ? 'live' : live.length > 0 ? 'partial' : 'coming', live, missing }
|
|
54
|
+
}
|
|
55
|
+
return { testnet: report('testnet'), mainnet: report('mainnet') }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {string} root */
|
|
59
|
+
export function readRecords(root) {
|
|
60
|
+
const require = createRequire(join(root, 'noop.js'))
|
|
61
|
+
/** @type {Record<string, string[]>} */
|
|
62
|
+
const out = {}
|
|
63
|
+
for (const [name, pkg, constant] of PRODUCTS) {
|
|
64
|
+
const entry = require.resolve(pkg)
|
|
65
|
+
out[name] = networksOf(readFileSync(join(dirname(entry), 'deployments.ts'), 'utf8'), constant)
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
29
68
|
}
|
|
30
69
|
|
|
31
70
|
// `process.argv[1]` is the symlink in node_modules/.bin when run as a bin: compare real paths.
|
|
32
71
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
33
72
|
const args = process.argv.slice(2)
|
|
34
|
-
const
|
|
35
|
-
const out =
|
|
36
|
-
const
|
|
37
|
-
const check = checkIndex >= 0 ? args[checkIndex + 1] : undefined
|
|
73
|
+
const value = (/** @type {string} */ flag) => (args.includes(flag) ? args[args.indexOf(flag) + 1] : undefined)
|
|
74
|
+
const out = value('--out')
|
|
75
|
+
const check = value('--check')
|
|
38
76
|
try {
|
|
39
|
-
const
|
|
40
|
-
const gate = await load('@meddleware/access-gate-client')
|
|
41
|
-
const sealed = await load('@meddleware/sealed-storage-client')
|
|
42
|
-
const status = networkStatus(utils, { 'access gate': gate.ACCESS_GATE_DEPLOYMENTS, 'sealed storage': sealed.SEAL_POLICIES_DEPLOYMENTS })
|
|
43
|
-
const json = JSON.stringify(status, null, 2) + '\n'
|
|
77
|
+
const json = JSON.stringify(availability(readRecords(process.cwd())), null, 2) + '\n'
|
|
44
78
|
if (check) {
|
|
45
79
|
let have = ''
|
|
46
80
|
try {
|