Skip to content

Commit 3234ac9

Browse files
committed
feat(plugin-a11y): align with plugin convention and add tsnapi snapshots
Bring the a11y inspector in line with the other first-party plugins so it participates in the shared tsnapi API-snapshot guard instead of being skipped as a private package. - rename `@devframes/a11y` -> `@devframes/plugin-a11y` and publish it as a multi-entry library (`.`, `/node`, `/cli`, `/vite`, `/client`) built with tsdown, mirroring plugin-inspect / plugin-terminals - move the Solid panel SPA to `src/spa` so `/client` exposes a clean, node-free browser module while the panel keeps building to `dist/spa` - add the required devframe metadata fields and a `typecheck` script - wire the package into alias.ts, turbo.json, and the tsnapi suite, which now snapshots all five plugins
1 parent 6bbe9d2 commit 3234ac9

38 files changed

Lines changed: 392 additions & 62 deletions

‎alias.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export const alias = {
6868
'@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'),
6969
'@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'),
7070
'@devframes/plugin-inspect': p('inspect/src/index.ts'),
71+
'@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'),
72+
'@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'),
73+
'@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'),
74+
'@devframes/plugin-a11y/vite': p('a11y/src/vite.ts'),
75+
'@devframes/plugin-a11y': p('a11y/src/index.ts'),
7176
}
7277

7378
// update tsconfig.base.json

‎plugins/a11y/README.md‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# devframe-a11y-inspector
1+
# @devframes/plugin-a11y
22

33
An accessibility inspector built on [devframe](../../packages/devframe). It runs
44
[axe-core](https://github.com/dequelabs/axe-core) against a host application,
@@ -15,8 +15,8 @@ Three pieces, two of them browser-side:
1515
| Piece | Runs in | Role |
1616
|-------|---------|------|
1717
| **Agent** (`src/inject`) | the host app's page | runs axe-core, broadcasts the report, draws the highlight ring |
18-
| **Panel** (`src/client`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover |
19-
| **Node** (`src/devframe.ts`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build |
18+
| **Panel** (`src/spa`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover |
19+
| **Node** (`src/index.ts`, `src/node`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build |
2020

2121
The agent and panel talk over a same-origin
2222
[`BroadcastChannel`](src/shared/protocol.ts), not the devframe RPC backend. That
@@ -55,12 +55,16 @@ pnpm -C plugins/a11y dev # panel only, at /__devframe-a11y-inspector/
5555

5656
## File map
5757

58-
| Path | Purpose |
59-
|------|---------|
60-
| `src/devframe.ts` | the `DevframeDefinition` consumed by every adapter |
61-
| `src/rpc/` | `get-config` static RPC + the type-safe client registry |
62-
| `src/shared/protocol.ts` | the agent ↔ panel `BroadcastChannel` contract |
63-
| `src/inject/` | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` |
64-
| `src/client/` | the Solid panel SPA → `dist/client` |
65-
| `demo/` | same-origin host page + server (dev + static modes) |
66-
| `tests/` | dev-server RPC + static-build dump |
58+
| Path | Export | Purpose |
59+
|------|--------|---------|
60+
| `src/index.ts` | `.` | `createA11yDevframe()` + the default `DevframeDefinition` |
61+
| `src/node/index.ts` | `/node` | `setupA11y(ctx)` — registers the RPC functions |
62+
| `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframe-a11y-inspector` bin |
63+
| `src/vite.ts` | `/vite` | `a11yVitePlugin()` — mounts the panel into a Vite host |
64+
| `src/client/index.ts` | `/client` | `connectA11y()` — typed browser RPC client wrapper |
65+
| `src/rpc/` | — | `get-config` static RPC + the type-safe client registry |
66+
| `src/shared/protocol.ts` | — | the agent ↔ panel `BroadcastChannel` contract |
67+
| `src/inject/` | — | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` |
68+
| `src/spa/` | — | the Solid panel SPA → `dist/spa` |
69+
| `demo/` | — | same-origin host page + server (dev + static modes) |
70+
| `tests/` | — | dev-server RPC + static-build dump |

‎plugins/a11y/bin.mjs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
#!/usr/bin/env node
22
import process from 'node:process'
3-
import { createCli } from 'devframe/adapters/cli'
4-
import devframe from './src/devframe.ts'
3+
import { createA11yCli } from './dist/cli.mjs'
54

65
async function main() {
7-
const cli = createCli(devframe)
6+
const cli = createA11yCli()
87
await cli.parse()
98
}
109

‎plugins/a11y/demo/server.mjs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*
1212
* Two modes prove the plugin works either way:
1313
*
14-
* node demo/server.mjs dev — live WebSocket RPC (`dist/client`)
14+
* node demo/server.mjs dev — live WebSocket RPC (`dist/spa`)
1515
* node demo/server.mjs build static — baked RPC dump, (`dist/static`)
1616
*
1717
* The scan/highlight loop is identical in both: it rides the BroadcastChannel,
@@ -28,15 +28,15 @@ import { mountStaticHandler } from 'devframe/utils/serve-static'
2828
import { getPort } from 'get-port-please'
2929
import { H3, toNodeHandler } from 'h3'
3030
import { resolve } from 'pathe'
31-
import devframe from '../src/devframe.ts'
31+
import devframe from '../src/index.ts'
3232

3333
const HERE = fileURLToPath(new URL('.', import.meta.url))
3434
const ROOT = resolve(HERE, '..')
3535

3636
const mode = process.argv[2] === 'build' ? 'build' : 'dev'
3737
const basePath = devframe.basePath
3838
const injectDir = resolve(ROOT, 'dist/inject')
39-
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/client')
39+
const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/spa')
4040

4141
function requireBuilt(file, hint) {
4242
if (!existsSync(file)) {

‎plugins/a11y/package.json‎

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,73 @@
11
{
2-
"name": "@devframes/a11y",
2+
"name": "@devframes/plugin-a11y",
33
"type": "module",
44
"version": "0.5.2",
5-
"private": true,
6-
"description": "Devframe a11y inspector — runs axe-core against the host app, surfaces the violations through a Solid SPA, and highlights the offending element in the page on hover. Works in dev (WebSocket) and static build modes.",
7-
"main": "src/devframe.ts",
5+
"description": "Devframe a11y inspector — runs axe-core against the host app, surfaces the violations through a Solid panel, and highlights the offending element in the page on hover. Works in dev (WebSocket) and static build modes.",
6+
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
7+
"license": "MIT",
8+
"homepage": "https://github.com/devframes/devframe#readme",
9+
"repository": {
10+
"directory": "plugins/a11y",
11+
"type": "git",
12+
"url": "git+https://github.com/devframes/devframe.git"
13+
},
14+
"bugs": "https://github.com/devframes/devframe/issues",
15+
"keywords": [
16+
"devframe",
17+
"devframe-plugin",
18+
"devtools",
19+
"a11y",
20+
"accessibility",
21+
"axe-core"
22+
],
23+
"sideEffects": false,
24+
"exports": {
25+
".": "./dist/index.mjs",
26+
"./client": "./dist/client/index.mjs",
27+
"./cli": "./dist/cli.mjs",
28+
"./node": "./dist/node/index.mjs",
29+
"./vite": "./dist/vite.mjs",
30+
"./package.json": "./package.json"
31+
},
32+
"types": "./dist/index.d.mts",
833
"bin": {
934
"devframe-a11y-inspector": "./bin.mjs"
1035
},
36+
"files": [
37+
"bin.mjs",
38+
"dist"
39+
],
1140
"scripts": {
12-
"build": "vite build --config src/client/vite.config.ts && vite build --config src/inject/vite.config.ts",
13-
"build:client": "vite build --config src/client/vite.config.ts",
41+
"build": "tsdown && vite build --config src/spa/vite.config.ts && vite build --config src/inject/vite.config.ts",
42+
"build:spa": "vite build --config src/spa/vite.config.ts",
1443
"build:inject": "vite build --config src/inject/vite.config.ts",
44+
"watch": "tsdown --watch",
1545
"dev": "node bin.mjs",
1646
"cli:build": "node bin.mjs build --out-dir dist/static",
1747
"demo": "node demo/server.mjs",
1848
"demo:build": "node demo/server.mjs build",
49+
"typecheck": "tsc --noEmit",
50+
"prepack": "pnpm run build",
1951
"test": "vitest run"
2052
},
53+
"peerDependencies": {
54+
"devframe": "workspace:*",
55+
"vite": "^8.0.0"
56+
},
57+
"peerDependenciesMeta": {
58+
"vite": {
59+
"optional": true
60+
}
61+
},
2162
"dependencies": {
2263
"axe-core": "catalog:frontend",
23-
"devframe": "workspace:*",
2464
"solid-js": "catalog:frontend"
2565
},
2666
"devDependencies": {
67+
"devframe": "workspace:*",
2768
"get-port-please": "catalog:deps",
2869
"h3": "catalog:deps",
70+
"tsdown": "catalog:build",
2971
"vite": "catalog:build",
3072
"vite-plugin-solid": "catalog:build",
3173
"vitest": "catalog:testing",

‎plugins/a11y/src/cli.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { CliHandle } from 'devframe/adapters/cli'
2+
import { createCli } from 'devframe/adapters/cli'
3+
import a11yDevframe from './index.ts'
4+
5+
/**
6+
* Build the standalone CLI for the a11y inspector — backs the package `bin`
7+
* (`devframe-a11y-inspector`) and `npx @devframes/plugin-a11y`. Wraps the
8+
* default {@link createA11yDevframe} definition with devframe's
9+
* `dev` / `build` / `spa` command shell.
10+
*/
11+
export function createA11yCli(): CliHandle {
12+
return createCli(a11yDevframe)
13+
}

‎plugins/a11y/src/client/index.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { DevframeRpcClient, DevframeRpcClientOptions } from 'devframe/client'
2+
import { connectDevframe } from 'devframe/client'
3+
4+
export type { DevframeRpcClient }
5+
export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts'
6+
7+
/**
8+
* Connect to the a11y inspector's devframe backend. A thin, typed wrapper
9+
* around devframe's {@link connectDevframe}; the panel derives its base from
10+
* `document.baseURI`, so no options are required in the common case. The
11+
* live scan/highlight loop itself rides a same-origin BroadcastChannel,
12+
* independent of this connection.
13+
*/
14+
export function connectA11y(options?: DevframeRpcClientOptions): Promise<DevframeRpcClient> {
15+
return connectDevframe(options)
16+
}

‎plugins/a11y/src/devframe.ts‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎plugins/a11y/src/index.ts‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { DevframeDefinition } from 'devframe/types'
2+
import { fileURLToPath } from 'node:url'
3+
import { defineDevframe } from 'devframe/types'
4+
import pkg from '../package.json' with { type: 'json' }
5+
import { setupA11y } from './node/index.ts'
6+
7+
/** Default devframe id — drives the standalone CLI command and the hosted mount path `/__<id>/`. */
8+
const DEFAULT_ID = 'devframe-a11y-inspector'
9+
const BASE_PATH = '/__devframe-a11y-inspector/'
10+
11+
// The Solid panel SPA is built (by Vite) into `dist/spa`. From both the
12+
// source entry (`src/index.ts`, via the workspace alias) and the published
13+
// entry (`dist/index.mjs`), `../dist/spa` resolves to `<pkg>/dist/spa`.
14+
const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url))
15+
16+
export interface A11yDevframeOptions {
17+
/** Override the devframe id (and the default CLI command / mount path). */
18+
id?: string
19+
/** Override the display name shown in a host dock. */
20+
name?: string
21+
/** Override the dock icon. */
22+
icon?: string
23+
/**
24+
* Override the mount path. Defaults to `/__devframe-a11y-inspector/` so the
25+
* panel iframe shares an origin with the host page it scans.
26+
*/
27+
basePath?: string
28+
/** Preferred standalone CLI port. */
29+
port?: number
30+
}
31+
32+
/**
33+
* Build a {@link DevframeDefinition} for the a11y inspector. The same
34+
* definition runs standalone (`/cli`, `/build`) and mounts into a host
35+
* (`/vite`, hub). The panel talks to the in-page agent over a same-origin
36+
* BroadcastChannel, so the scan/highlight loop works identically in dev
37+
* (live WebSocket RPC) and in a baked static build.
38+
*/
39+
export function createA11yDevframe(options: A11yDevframeOptions = {}): DevframeDefinition {
40+
const id = options.id ?? DEFAULT_ID
41+
return defineDevframe({
42+
id,
43+
name: options.name ?? 'A11y Inspector',
44+
version: pkg.version,
45+
packageName: pkg.name,
46+
homepage: pkg.homepage,
47+
description: pkg.description,
48+
icon: options.icon ?? 'ph:wheelchair-duotone',
49+
basePath: options.basePath ?? BASE_PATH,
50+
cli: {
51+
command: id,
52+
port: options.port ?? 9899,
53+
distDir,
54+
},
55+
spa: { loader: 'none' },
56+
setup(ctx) {
57+
setupA11y(ctx)
58+
},
59+
})
60+
}
61+
62+
/** The default a11y inspector devframe definition. */
63+
const a11yDevframe: DevframeDefinition = createA11yDevframe()
64+
65+
export default a11yDevframe
66+
export type { Impact, ScanReport, Violation, ViolationNode } from './shared/protocol.ts'

‎plugins/a11y/src/node/index.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { DevframeNodeContext } from 'devframe/types'
2+
import { serverFunctions } from '../rpc/index.ts'
3+
4+
/**
5+
* Register the a11y inspector's RPC functions on a devframe node context.
6+
* Called from the definition's `setup(ctx)` and reusable by host adapters
7+
* that wire their own context.
8+
*/
9+
export function setupA11y(ctx: DevframeNodeContext): void {
10+
for (const fn of serverFunctions)
11+
ctx.rpc.register(fn)
12+
}
13+
14+
export { serverFunctions }

0 commit comments

Comments
 (0)