From a99284f835f5e0767beb0c973dfe542bb0508332 Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 25 Jun 2026 06:51:42 +0000 Subject: [PATCH] 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 --- alias.ts | 6 +- .../minimal-next-devframe-hub/package.json | 2 +- .../devframe/minimal-next-devframe-hub.ts | 2 +- .../minimal-vite-devframe-hub/package.json | 2 +- .../minimal-vite-devframe-hub/vite.config.ts | 2 +- plugins/a11y/README.md | 28 ++++---- plugins/a11y/bin.mjs | 5 +- plugins/a11y/demo/server.mjs | 6 +- plugins/a11y/package.json | 56 ++++++++++++++-- plugins/a11y/src/cli.ts | 13 ++++ plugins/a11y/src/client/index.ts | 16 +++++ plugins/a11y/src/devframe.ts | 23 ------- plugins/a11y/src/index.ts | 66 +++++++++++++++++++ plugins/a11y/src/node/index.ts | 14 ++++ plugins/a11y/src/{client => spa}/app.tsx | 0 .../src/{client => spa}/components/header.tsx | 0 .../src/{client => spa}/components/icons.tsx | 0 .../{client => spa}/components/violations.tsx | 0 plugins/a11y/src/{client => spa}/index.html | 0 .../a11y/src/{client => spa}/lib/channel.ts | 0 .../a11y/src/{client => spa}/lib/devframe.ts | 0 .../a11y/src/{client => spa}/lib/impact.ts | 0 plugins/a11y/src/{client => spa}/main.tsx | 0 plugins/a11y/src/{client => spa}/styles.css | 0 .../a11y/src/{client => spa}/vite.config.ts | 4 +- plugins/a11y/src/vite.ts | 16 +++++ plugins/a11y/tests/_utils.ts | 10 +-- plugins/a11y/tests/static-build.test.ts | 2 +- plugins/a11y/tsdown.config.ts | 54 +++++++++++++++ pnpm-lock.yaml | 21 +++--- .../@devframes/plugin-a11y/cli.snapshot.d.ts | 6 ++ .../@devframes/plugin-a11y/cli.snapshot.js | 6 ++ .../plugin-a11y/client.snapshot.d.ts | 14 ++++ .../@devframes/plugin-a11y/client.snapshot.js | 6 ++ .../plugin-a11y/index.snapshot.d.ts | 28 ++++++++ .../@devframes/plugin-a11y/index.snapshot.js | 10 +++ .../@devframes/plugin-a11y/node.snapshot.d.ts | 10 +++ .../@devframes/plugin-a11y/node.snapshot.js | 10 +++ .../@devframes/plugin-a11y/vite.snapshot.d.ts | 10 +++ .../@devframes/plugin-a11y/vite.snapshot.js | 6 ++ tsconfig.base.json | 16 ++++- turbo.json | 10 +-- 42 files changed, 403 insertions(+), 77 deletions(-) create mode 100644 plugins/a11y/src/cli.ts create mode 100644 plugins/a11y/src/client/index.ts delete mode 100644 plugins/a11y/src/devframe.ts create mode 100644 plugins/a11y/src/index.ts create mode 100644 plugins/a11y/src/node/index.ts rename plugins/a11y/src/{client => spa}/app.tsx (100%) rename plugins/a11y/src/{client => spa}/components/header.tsx (100%) rename plugins/a11y/src/{client => spa}/components/icons.tsx (100%) rename plugins/a11y/src/{client => spa}/components/violations.tsx (100%) rename plugins/a11y/src/{client => spa}/index.html (100%) rename plugins/a11y/src/{client => spa}/lib/channel.ts (100%) rename plugins/a11y/src/{client => spa}/lib/devframe.ts (100%) rename plugins/a11y/src/{client => spa}/lib/impact.ts (100%) rename plugins/a11y/src/{client => spa}/main.tsx (100%) rename plugins/a11y/src/{client => spa}/styles.css (100%) rename plugins/a11y/src/{client => spa}/vite.config.ts (79%) create mode 100644 plugins/a11y/src/vite.ts create mode 100644 plugins/a11y/tsdown.config.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.js create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.js create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.js create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.js create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.js diff --git a/alias.ts b/alias.ts index b9cd1f7..05744d1 100644 --- a/alias.ts +++ b/alias.ts @@ -74,7 +74,11 @@ export const alias = { '@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'), '@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'), '@devframes/plugin-inspect': p('inspect/src/index.ts'), - '@devframes/a11y': p('a11y/src/devframe.ts'), + '@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'), + '@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'), + '@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'), + '@devframes/plugin-a11y/vite': p('a11y/src/vite.ts'), + '@devframes/plugin-a11y': p('a11y/src/index.ts'), } // update tsconfig.base.json — CSS aliases exist for Vite resolution only; diff --git a/examples/minimal-next-devframe-hub/package.json b/examples/minimal-next-devframe-hub/package.json index 576ae15..56ef7a0 100644 --- a/examples/minimal-next-devframe-hub/package.json +++ b/examples/minimal-next-devframe-hub/package.json @@ -11,8 +11,8 @@ "test": "vitest run --config vitest.config.ts" }, "dependencies": { - "@devframes/a11y": "workspace:*", "@devframes/hub": "workspace:*", + "@devframes/plugin-a11y": "workspace:*", "@devframes/plugin-code-server": "workspace:*", "@devframes/plugin-git": "workspace:*", "@devframes/plugin-inspect": "workspace:*", diff --git a/examples/minimal-next-devframe-hub/src/client/devframe/minimal-next-devframe-hub.ts b/examples/minimal-next-devframe-hub/src/client/devframe/minimal-next-devframe-hub.ts index 028b8fc..0842b17 100644 --- a/examples/minimal-next-devframe-hub/src/client/devframe/minimal-next-devframe-hub.ts +++ b/examples/minimal-next-devframe-hub/src/client/devframe/minimal-next-devframe-hub.ts @@ -28,7 +28,7 @@ const BUILTIN_PLUGIN_PACKAGES = [ '@devframes/plugin-terminals', '@devframes/plugin-code-server', '@devframes/plugin-inspect', - '@devframes/a11y', + '@devframes/plugin-a11y', ] as const async function loadBuiltinPlugins(): Promise { diff --git a/examples/minimal-vite-devframe-hub/package.json b/examples/minimal-vite-devframe-hub/package.json index efd8771..50098b1 100644 --- a/examples/minimal-vite-devframe-hub/package.json +++ b/examples/minimal-vite-devframe-hub/package.json @@ -10,8 +10,8 @@ "build": "vite build" }, "dependencies": { - "@devframes/a11y": "workspace:*", "@devframes/hub": "workspace:*", + "@devframes/plugin-a11y": "workspace:*", "@devframes/plugin-code-server": "workspace:*", "@devframes/plugin-git": "workspace:*", "@devframes/plugin-inspect": "workspace:*", diff --git a/examples/minimal-vite-devframe-hub/vite.config.ts b/examples/minimal-vite-devframe-hub/vite.config.ts index e8eb642..53158fb 100644 --- a/examples/minimal-vite-devframe-hub/vite.config.ts +++ b/examples/minimal-vite-devframe-hub/vite.config.ts @@ -1,4 +1,4 @@ -import a11yDevframe from '@devframes/a11y' +import a11yDevframe from '@devframes/plugin-a11y' import codeServerDevframe from '@devframes/plugin-code-server' import gitDevframe from '@devframes/plugin-git' import inspectDevframe from '@devframes/plugin-inspect' diff --git a/plugins/a11y/README.md b/plugins/a11y/README.md index 6b89f09..0aa5a83 100644 --- a/plugins/a11y/README.md +++ b/plugins/a11y/README.md @@ -1,4 +1,4 @@ -# devframe-a11y-inspector +# @devframes/plugin-a11y An accessibility inspector built on [devframe](../../packages/devframe). It runs [axe-core](https://github.com/dequelabs/axe-core) against a host application, @@ -15,8 +15,8 @@ Three pieces, two of them browser-side: | Piece | Runs in | Role | |-------|---------|------| | **Agent** (`src/inject`) | the host app's page | runs axe-core, broadcasts the report, draws the highlight ring | -| **Panel** (`src/client`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover | -| **Node** (`src/devframe.ts`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build | +| **Panel** (`src/spa`) | the devtools iframe | Solid SPA: lists violations, fires highlight/clear on hover | +| **Node** (`src/index.ts`, `src/node`, `src/rpc`) | the devframe backend | `get-config` RPC (impact taxonomy) — live in dev, baked in a static build | The agent and panel talk over a same-origin [`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/ ## File map -| Path | Purpose | -|------|---------| -| `src/devframe.ts` | the `DevframeDefinition` consumed by every adapter | -| `src/rpc/` | `get-config` static RPC + the type-safe client registry | -| `src/shared/protocol.ts` | the agent ↔ panel `BroadcastChannel` contract | -| `src/inject/` | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` | -| `src/client/` | the Solid panel SPA → `dist/client` | -| `demo/` | same-origin host page + server (dev + static modes) | -| `tests/` | dev-server RPC + static-build dump | +| Path | Export | Purpose | +|------|--------|---------| +| `src/index.ts` | `.` | `createA11yDevframe()` + the default `DevframeDefinition` | +| `src/node/index.ts` | `/node` | `setupA11y(ctx)` — registers the RPC functions | +| `src/cli.ts` | `/cli` | `createA11yCli()` — backs the `devframe-a11y-inspector` bin | +| `src/vite.ts` | `/vite` | `a11yVitePlugin()` — mounts the panel into a Vite host | +| `src/client/index.ts` | `/client` | `connectA11y()` — typed browser RPC client wrapper | +| `src/rpc/` | — | `get-config` static RPC + the type-safe client registry | +| `src/shared/protocol.ts` | — | the agent ↔ panel `BroadcastChannel` contract | +| `src/inject/` | — | the host-page agent (axe scan, highlight overlay) → `dist/inject/inject.js` | +| `src/spa/` | — | the Solid panel SPA → `dist/spa` | +| `demo/` | — | same-origin host page + server (dev + static modes) | +| `tests/` | — | dev-server RPC + static-build dump | diff --git a/plugins/a11y/bin.mjs b/plugins/a11y/bin.mjs index e760d99..8e7267a 100755 --- a/plugins/a11y/bin.mjs +++ b/plugins/a11y/bin.mjs @@ -1,10 +1,9 @@ #!/usr/bin/env node import process from 'node:process' -import { createCli } from 'devframe/adapters/cli' -import devframe from './src/devframe.ts' +import { createA11yCli } from './dist/cli.mjs' async function main() { - const cli = createCli(devframe) + const cli = createA11yCli() await cli.parse() } diff --git a/plugins/a11y/demo/server.mjs b/plugins/a11y/demo/server.mjs index 00e7c17..49a4f40 100644 --- a/plugins/a11y/demo/server.mjs +++ b/plugins/a11y/demo/server.mjs @@ -11,7 +11,7 @@ * * Two modes prove the plugin works either way: * - * node demo/server.mjs dev — live WebSocket RPC (`dist/client`) + * node demo/server.mjs dev — live WebSocket RPC (`dist/spa`) * node demo/server.mjs build static — baked RPC dump, (`dist/static`) * * The scan/highlight loop is identical in both: it rides the BroadcastChannel, @@ -28,7 +28,7 @@ import { mountStaticHandler } from 'devframe/utils/serve-static' import { getPort } from 'get-port-please' import { H3, toNodeHandler } from 'h3' import { resolve } from 'pathe' -import devframe from '../src/devframe.ts' +import devframe from '../src/index.ts' const HERE = fileURLToPath(new URL('.', import.meta.url)) const ROOT = resolve(HERE, '..') @@ -36,7 +36,7 @@ const ROOT = resolve(HERE, '..') const mode = process.argv[2] === 'build' ? 'build' : 'dev' const basePath = devframe.basePath const injectDir = resolve(ROOT, 'dist/inject') -const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/client') +const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'dist/spa') function requireBuilt(file, hint) { if (!existsSync(file)) { diff --git a/plugins/a11y/package.json b/plugins/a11y/package.json index de02e08..6c4c534 100644 --- a/plugins/a11y/package.json +++ b/plugins/a11y/package.json @@ -1,32 +1,74 @@ { - "name": "@devframes/a11y", + "name": "@devframes/plugin-a11y", "type": "module", "version": "0.5.2", - "private": true, - "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.", - "main": "src/devframe.ts", + "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.", + "author": "Anthony Fu ", + "license": "MIT", + "homepage": "https://github.com/devframes/devframe#readme", + "repository": { + "directory": "plugins/a11y", + "type": "git", + "url": "git+https://github.com/devframes/devframe.git" + }, + "bugs": "https://github.com/devframes/devframe/issues", + "keywords": [ + "devframe", + "devframe-plugin", + "devtools", + "a11y", + "accessibility", + "axe-core" + ], + "sideEffects": false, + "exports": { + ".": "./dist/index.mjs", + "./client": "./dist/client/index.mjs", + "./cli": "./dist/cli.mjs", + "./node": "./dist/node/index.mjs", + "./vite": "./dist/vite.mjs", + "./package.json": "./package.json" + }, + "types": "./dist/index.d.mts", "bin": { "devframe-a11y-inspector": "./bin.mjs" }, + "files": [ + "bin.mjs", + "dist" + ], "scripts": { - "build": "vite build --config src/client/vite.config.ts && vite build --config src/inject/vite.config.ts", - "build:client": "vite build --config src/client/vite.config.ts", + "build": "tsdown && vite build --config src/spa/vite.config.ts && vite build --config src/inject/vite.config.ts", + "build:spa": "vite build --config src/spa/vite.config.ts", "build:inject": "vite build --config src/inject/vite.config.ts", + "watch": "tsdown --watch", "dev": "node bin.mjs", "cli:build": "node bin.mjs build --out-dir dist/static", "demo": "node demo/server.mjs", "demo:build": "node demo/server.mjs build", + "typecheck": "tsc --noEmit", + "prepack": "pnpm run build", "test": "vitest run" }, + "peerDependencies": { + "devframe": "workspace:*", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + }, "dependencies": { "axe-core": "catalog:frontend", - "devframe": "workspace:*", "solid-js": "catalog:frontend" }, "devDependencies": { "@internal/design": "workspace:*", + "devframe": "workspace:*", "get-port-please": "catalog:deps", "h3": "catalog:deps", + "tsdown": "catalog:build", "unocss": "catalog:frontend", "vite": "catalog:build", "vite-plugin-solid": "catalog:build", diff --git a/plugins/a11y/src/cli.ts b/plugins/a11y/src/cli.ts new file mode 100644 index 0000000..0e2351b --- /dev/null +++ b/plugins/a11y/src/cli.ts @@ -0,0 +1,13 @@ +import type { CliHandle } from 'devframe/adapters/cli' +import { createCli } from 'devframe/adapters/cli' +import a11yDevframe from './index.ts' + +/** + * Build the standalone CLI for the a11y inspector — backs the package `bin` + * (`devframe-a11y-inspector`) and `npx @devframes/plugin-a11y`. Wraps the + * default {@link createA11yDevframe} definition with devframe's + * `dev` / `build` / `spa` command shell. + */ +export function createA11yCli(): CliHandle { + return createCli(a11yDevframe) +} diff --git a/plugins/a11y/src/client/index.ts b/plugins/a11y/src/client/index.ts new file mode 100644 index 0000000..0de9134 --- /dev/null +++ b/plugins/a11y/src/client/index.ts @@ -0,0 +1,16 @@ +import type { DevframeRpcClient, DevframeRpcClientOptions } from 'devframe/client' +import { connectDevframe } from 'devframe/client' + +export type { DevframeRpcClient } +export type { Impact, ScanReport, Violation, ViolationNode } from '../shared/protocol.ts' + +/** + * Connect to the a11y inspector's devframe backend. A thin, typed wrapper + * around devframe's {@link connectDevframe}; the panel derives its base from + * `document.baseURI`, so no options are required in the common case. The + * live scan/highlight loop itself rides a same-origin BroadcastChannel, + * independent of this connection. + */ +export function connectA11y(options?: DevframeRpcClientOptions): Promise { + return connectDevframe(options) +} diff --git a/plugins/a11y/src/devframe.ts b/plugins/a11y/src/devframe.ts deleted file mode 100644 index 2eddea6..0000000 --- a/plugins/a11y/src/devframe.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { fileURLToPath } from 'node:url' -import { defineDevframe } from 'devframe/types' -import { serverFunctions } from './rpc/index.ts' - -const BASE_PATH = '/__devframe-a11y-inspector/' -const distDir = fileURLToPath(new URL('../dist/client', import.meta.url)) - -export default defineDevframe({ - id: 'devframe-a11y-inspector', - name: 'A11y Inspector', - icon: 'ph:wheelchair-duotone', - basePath: BASE_PATH, - cli: { - command: 'devframe-a11y-inspector', - port: 9899, - distDir, - }, - spa: { loader: 'none' }, - setup(ctx) { - for (const fn of serverFunctions) - ctx.rpc.register(fn) - }, -}) diff --git a/plugins/a11y/src/index.ts b/plugins/a11y/src/index.ts new file mode 100644 index 0000000..0070573 --- /dev/null +++ b/plugins/a11y/src/index.ts @@ -0,0 +1,66 @@ +import type { DevframeDefinition } from 'devframe/types' +import { fileURLToPath } from 'node:url' +import { defineDevframe } from 'devframe/types' +import pkg from '../package.json' with { type: 'json' } +import { setupA11y } from './node/index.ts' + +/** Default devframe id — drives the standalone CLI command and the hosted mount path `/__/`. */ +const DEFAULT_ID = 'devframe-a11y-inspector' +const BASE_PATH = '/__devframe-a11y-inspector/' + +// The Solid panel SPA is built (by Vite) into `dist/spa`. From both the +// source entry (`src/index.ts`, via the workspace alias) and the published +// entry (`dist/index.mjs`), `../dist/spa` resolves to `/dist/spa`. +const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url)) + +export interface A11yDevframeOptions { + /** Override the devframe id (and the default CLI command / mount path). */ + id?: string + /** Override the display name shown in a host dock. */ + name?: string + /** Override the dock icon. */ + icon?: string + /** + * Override the mount path. Defaults to `/__devframe-a11y-inspector/` so the + * panel iframe shares an origin with the host page it scans. + */ + basePath?: string + /** Preferred standalone CLI port. */ + port?: number +} + +/** + * Build a {@link DevframeDefinition} for the a11y inspector. The same + * definition runs standalone (`/cli`, `/build`) and mounts into a host + * (`/vite`, hub). The panel talks to the in-page agent over a same-origin + * BroadcastChannel, so the scan/highlight loop works identically in dev + * (live WebSocket RPC) and in a baked static build. + */ +export function createA11yDevframe(options: A11yDevframeOptions = {}): DevframeDefinition { + const id = options.id ?? DEFAULT_ID + return defineDevframe({ + id, + name: options.name ?? 'A11y Inspector', + version: pkg.version, + packageName: pkg.name, + homepage: pkg.homepage, + description: pkg.description, + icon: options.icon ?? 'ph:wheelchair-duotone', + basePath: options.basePath ?? BASE_PATH, + cli: { + command: id, + port: options.port ?? 9899, + distDir, + }, + spa: { loader: 'none' }, + setup(ctx) { + setupA11y(ctx) + }, + }) +} + +/** The default a11y inspector devframe definition. */ +const a11yDevframe: DevframeDefinition = createA11yDevframe() + +export default a11yDevframe +export type { Impact, ScanReport, Violation, ViolationNode } from './shared/protocol.ts' diff --git a/plugins/a11y/src/node/index.ts b/plugins/a11y/src/node/index.ts new file mode 100644 index 0000000..d369e26 --- /dev/null +++ b/plugins/a11y/src/node/index.ts @@ -0,0 +1,14 @@ +import type { DevframeNodeContext } from 'devframe/types' +import { serverFunctions } from '../rpc/index.ts' + +/** + * Register the a11y inspector's RPC functions on a devframe node context. + * Called from the definition's `setup(ctx)` and reusable by host adapters + * that wire their own context. + */ +export function setupA11y(ctx: DevframeNodeContext): void { + for (const fn of serverFunctions) + ctx.rpc.register(fn) +} + +export { serverFunctions } diff --git a/plugins/a11y/src/client/app.tsx b/plugins/a11y/src/spa/app.tsx similarity index 100% rename from plugins/a11y/src/client/app.tsx rename to plugins/a11y/src/spa/app.tsx diff --git a/plugins/a11y/src/client/components/header.tsx b/plugins/a11y/src/spa/components/header.tsx similarity index 100% rename from plugins/a11y/src/client/components/header.tsx rename to plugins/a11y/src/spa/components/header.tsx diff --git a/plugins/a11y/src/client/components/icons.tsx b/plugins/a11y/src/spa/components/icons.tsx similarity index 100% rename from plugins/a11y/src/client/components/icons.tsx rename to plugins/a11y/src/spa/components/icons.tsx diff --git a/plugins/a11y/src/client/components/violations.tsx b/plugins/a11y/src/spa/components/violations.tsx similarity index 100% rename from plugins/a11y/src/client/components/violations.tsx rename to plugins/a11y/src/spa/components/violations.tsx diff --git a/plugins/a11y/src/client/index.html b/plugins/a11y/src/spa/index.html similarity index 100% rename from plugins/a11y/src/client/index.html rename to plugins/a11y/src/spa/index.html diff --git a/plugins/a11y/src/client/lib/channel.ts b/plugins/a11y/src/spa/lib/channel.ts similarity index 100% rename from plugins/a11y/src/client/lib/channel.ts rename to plugins/a11y/src/spa/lib/channel.ts diff --git a/plugins/a11y/src/client/lib/devframe.ts b/plugins/a11y/src/spa/lib/devframe.ts similarity index 100% rename from plugins/a11y/src/client/lib/devframe.ts rename to plugins/a11y/src/spa/lib/devframe.ts diff --git a/plugins/a11y/src/client/lib/impact.ts b/plugins/a11y/src/spa/lib/impact.ts similarity index 100% rename from plugins/a11y/src/client/lib/impact.ts rename to plugins/a11y/src/spa/lib/impact.ts diff --git a/plugins/a11y/src/client/main.tsx b/plugins/a11y/src/spa/main.tsx similarity index 100% rename from plugins/a11y/src/client/main.tsx rename to plugins/a11y/src/spa/main.tsx diff --git a/plugins/a11y/src/client/styles.css b/plugins/a11y/src/spa/styles.css similarity index 100% rename from plugins/a11y/src/client/styles.css rename to plugins/a11y/src/spa/styles.css diff --git a/plugins/a11y/src/client/vite.config.ts b/plugins/a11y/src/spa/vite.config.ts similarity index 79% rename from plugins/a11y/src/client/vite.config.ts rename to plugins/a11y/src/spa/vite.config.ts index f1c700b..00bbed8 100644 --- a/plugins/a11y/src/client/vite.config.ts +++ b/plugins/a11y/src/spa/vite.config.ts @@ -5,7 +5,7 @@ import solid from 'vite-plugin-solid' import { alias } from '../../../../alias' // `base: './'` + `` keeps the bundle mount-path portable: -// the same `dist/client` works whether devframe serves it at `/` (standalone) +// the same `dist/spa` works whether devframe serves it at `/` (standalone) // or `/__devframe-a11y-inspector/` (mounted in a hub). `connectDevframe` // resolves its connection meta relative to `document.baseURI` to match. export default defineConfig({ @@ -14,7 +14,7 @@ export default defineConfig({ resolve: { alias }, plugins: [solid(), UnoCSS()], build: { - outDir: fileURLToPath(new URL('../../dist/client', import.meta.url)), + outDir: fileURLToPath(new URL('../../dist/spa', import.meta.url)), emptyOutDir: true, }, }) diff --git a/plugins/a11y/src/vite.ts b/plugins/a11y/src/vite.ts new file mode 100644 index 0000000..1819dd2 --- /dev/null +++ b/plugins/a11y/src/vite.ts @@ -0,0 +1,16 @@ +import type { DevframeVitePlugin, ViteDevBridgeOptions } from 'devframe/helpers/vite' +import { viteDevBridge } from 'devframe/helpers/vite' +import a11yDevframe from './index.ts' + +export type { ViteDevBridgeOptions } + +/** + * Mount the a11y inspector panel into an existing Vite dev server. In the + * default static-mount mode it serves the built panel at + * `/__devframe-a11y-inspector/`; pass `{ devMiddleware: true }` for the + * bridge mode where the host owns the SPA and devframe runs a side-car + * RPC + WS server. + */ +export function a11yVitePlugin(options?: ViteDevBridgeOptions): DevframeVitePlugin { + return viteDevBridge(a11yDevframe, options) +} diff --git a/plugins/a11y/tests/_utils.ts b/plugins/a11y/tests/_utils.ts index c1e063c..18a5500 100644 --- a/plugins/a11y/tests/_utils.ts +++ b/plugins/a11y/tests/_utils.ts @@ -9,16 +9,16 @@ import { mountStaticHandler } from 'devframe/utils/serve-static' import { getPort } from 'get-port-please' import { H3 } from 'h3' import { resolve } from 'pathe' -import devframe from '../src/devframe' +import devframe from '../src/index' const HERE = fileURLToPath(new URL('.', import.meta.url)) -export const CLIENT_DIST = resolve(HERE, '../dist/client') +export const SPA_DIST = resolve(HERE, '../dist/spa') -/** Loud failure if the Solid client hasn't been built — tests serve `dist/client`. */ +/** Loud failure if the Solid panel hasn't been built — tests serve `dist/spa`. */ export function assertClientBuilt(): void { - if (!existsSync(path.join(CLIENT_DIST, 'index.html'))) { + if (!existsSync(path.join(SPA_DIST, 'index.html'))) { throw new Error( - '[devframe-a11y-inspector] dist/client missing — run `pnpm -C plugins/a11y run build` first.', + '[devframe-a11y-inspector] dist/spa missing — run `pnpm -C plugins/a11y run build` first.', ) } } diff --git a/plugins/a11y/tests/static-build.test.ts b/plugins/a11y/tests/static-build.test.ts index 48efdab..9308eb1 100644 --- a/plugins/a11y/tests/static-build.test.ts +++ b/plugins/a11y/tests/static-build.test.ts @@ -8,7 +8,7 @@ import { DEVFRAME_RPC_DUMP_MANIFEST_FILENAME, } from 'devframe/constants' import { afterAll, beforeAll, describe, expect, it } from 'vitest' -import devframe from '../src/devframe' +import devframe from '../src/index' import { assertClientBuilt } from './_utils' interface DumpManifest { diff --git a/plugins/a11y/tsdown.config.ts b/plugins/a11y/tsdown.config.ts new file mode 100644 index 0000000..520ada4 --- /dev/null +++ b/plugins/a11y/tsdown.config.ts @@ -0,0 +1,54 @@ +import { defineConfig } from 'tsdown' + +const tsconfig = '../../tsconfig.base.json' + +// Browser-loaded entry. Kept in its own rolldown graph so node-only +// imports can never leak into the client bundle. +const clientEntries = { + 'client/index': 'src/client/index.ts', +} + +// Node-side entries — the devframe definition, the CLI/Vite host +// adapters, the setup module, and the RPC registry. +const serverEntries = { + 'index': 'src/index.ts', + 'cli': 'src/cli.ts', + 'vite': 'src/vite.ts', + 'node/index': 'src/node/index.ts', + 'rpc/index': 'src/rpc/index.ts', +} + +// Three configs mirror `packages/devframe`: +// 1. browser runtime build (`dts: false`, `clean: true`) — clears dist/ +// and emits the client bundle in an isolated graph; +// 2. node runtime build (`dts: false`, `clean: false`) — appends; +// 3. combined dts (`emitDtsOnly`) — one rolldown graph so the +// `declare module 'devframe'` RPC augmentation resolves once. +// +// The Solid panel SPA (`src/spa`) and the in-page agent (`src/inject`) +// build separately with Vite into `dist/spa` and `dist/inject`. +export default defineConfig([ + { + clean: true, + platform: 'browser', + tsconfig, + dts: false, + outExtensions: () => ({ js: '.mjs' }), + entry: clientEntries, + }, + { + clean: false, + platform: 'node', + tsconfig, + dts: false, + entry: serverEntries, + }, + { + clean: false, + platform: 'neutral', + tsconfig, + dts: { emitDtsOnly: true }, + outExtensions: () => ({ dts: '.d.mts' }), + entry: { ...clientEntries, ...serverEntries }, + }, +]) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2f84f6..914bb02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -384,12 +384,12 @@ importers: examples/minimal-next-devframe-hub: dependencies: - '@devframes/a11y': - specifier: workspace:* - version: link:../../plugins/a11y '@devframes/hub': specifier: workspace:* version: link:../../packages/hub + '@devframes/plugin-a11y': + specifier: workspace:* + version: link:../../plugins/a11y '@devframes/plugin-code-server': specifier: workspace:* version: link:../../plugins/code-server @@ -448,12 +448,12 @@ importers: examples/minimal-vite-devframe-hub: dependencies: - '@devframes/a11y': - specifier: workspace:* - version: link:../../plugins/a11y '@devframes/hub': specifier: workspace:* version: link:../../packages/hub + '@devframes/plugin-a11y': + specifier: workspace:* + version: link:../../plugins/a11y '@devframes/plugin-code-server': specifier: workspace:* version: link:../../plugins/code-server @@ -688,9 +688,6 @@ importers: axe-core: specifier: catalog:frontend version: 4.12.1 - devframe: - specifier: workspace:* - version: link:../../packages/devframe solid-js: specifier: catalog:frontend version: 1.9.13 @@ -698,12 +695,18 @@ importers: '@internal/design': specifier: workspace:* version: link:../../packages/design + devframe: + specifier: workspace:* + version: link:../../packages/devframe get-port-please: specifier: catalog:deps version: 3.2.0 h3: specifier: catalog:deps version: 2.0.1-rc.22(crossws@0.4.5(srvx@0.11.15)) + tsdown: + specifier: catalog:build + version: 0.22.0(tsx@4.22.3)(typescript@6.0.3) unocss: specifier: catalog:frontend version: 66.7.2(@unocss/postcss@66.7.2(postcss@8.5.15))(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.47.1)(tsx@4.22.3)(yaml@2.8.4)) diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.d.ts new file mode 100644 index 0000000..3fa0844 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.d.ts @@ -0,0 +1,6 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/cli` + */ +// #region Functions +export declare function createA11yCli(): CliHandle; +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.js new file mode 100644 index 0000000..9e376f4 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/cli.snapshot.js @@ -0,0 +1,6 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/cli` + */ +// #region Functions +export function createA11yCli() {} +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.d.ts new file mode 100644 index 0000000..a3d1541 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.d.ts @@ -0,0 +1,14 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/client` + */ +// #region Functions +export declare function connectA11y(_?: DevframeRpcClientOptions): Promise; +// #endregion + +// #region Other +export { DevframeRpcClient } +export { Impact } +export { ScanReport } +export { Violation } +export { ViolationNode } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.js new file mode 100644 index 0000000..bcacec4 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/client.snapshot.js @@ -0,0 +1,6 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/client` + */ +// #region Functions +export function connectA11y(_) {} +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.d.ts new file mode 100644 index 0000000..8588a52 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.d.ts @@ -0,0 +1,28 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y` + */ +// #region Interfaces +export interface A11yDevframeOptions { + id?: string; + name?: string; + icon?: string; + basePath?: string; + port?: number; +} +// #endregion + +// #region Functions +export declare function createA11yDevframe(_?: A11yDevframeOptions): DevframeDefinition; +// #endregion + +// #region Default Export +declare const _default: DevframeDefinition; +export default _default +// #endregion + +// #region Other +export { Impact } +export { ScanReport } +export { Violation } +export { ViolationNode } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.js new file mode 100644 index 0000000..e72fcdd --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/index.snapshot.js @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y` + */ +// #region Default Export +export default a11yDevframe +// #endregion + +// #region Other +export { createA11yDevframe } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.d.ts new file mode 100644 index 0000000..88f9527 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.d.ts @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/node` + */ +// #region Functions +export declare function setupA11y(_: DevframeNodeContext): void; +// #endregion + +// #region Other +export { serverFunctions } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.js new file mode 100644 index 0000000..be2d5f8 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/node.snapshot.js @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/node` + */ +// #region Functions +export function setupA11y(_) {} +// #endregion + +// #region Other +export { serverFunctions } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.d.ts b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.d.ts new file mode 100644 index 0000000..948f8b9 --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.d.ts @@ -0,0 +1,10 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/vite` + */ +// #region Functions +export declare function a11yVitePlugin(_?: ViteDevBridgeOptions): DevframeVitePlugin; +// #endregion + +// #region Other +export { ViteDevBridgeOptions } +// #endregion \ No newline at end of file diff --git a/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.js b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.js new file mode 100644 index 0000000..c9b1e5f --- /dev/null +++ b/tests/__snapshots__/tsnapi/@devframes/plugin-a11y/vite.snapshot.js @@ -0,0 +1,6 @@ +/** + * Generated by tsnapi — public API snapshot of `@devframes/plugin-a11y/vite` + */ +// #region Functions +export function a11yVitePlugin(_) {} +// #endregion \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index 165307b..776cdef 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -205,8 +205,20 @@ "@devframes/plugin-inspect": [ "./plugins/inspect/src/index.ts" ], - "@devframes/a11y": [ - "./plugins/a11y/src/devframe.ts" + "@devframes/plugin-a11y/client": [ + "./plugins/a11y/src/client/index.ts" + ], + "@devframes/plugin-a11y/node": [ + "./plugins/a11y/src/node/index.ts" + ], + "@devframes/plugin-a11y/cli": [ + "./plugins/a11y/src/cli.ts" + ], + "@devframes/plugin-a11y/vite": [ + "./plugins/a11y/src/vite.ts" + ], + "@devframes/plugin-a11y": [ + "./plugins/a11y/src/index.ts" ] }, "resolveJsonModule": true, diff --git a/turbo.json b/turbo.json index a910e9b..184caed 100644 --- a/turbo.json +++ b/turbo.json @@ -42,7 +42,7 @@ "@devframes/plugin-terminals#build", "@devframes/plugin-code-server#build", "@devframes/plugin-inspect#build", - "@devframes/a11y#build" + "@devframes/plugin-a11y#build" ], "outputs": ["dist/**"] }, @@ -55,7 +55,7 @@ "@devframes/plugin-terminals#build", "@devframes/plugin-code-server#build", "@devframes/plugin-inspect#build", - "@devframes/a11y#build" + "@devframes/plugin-a11y#build" ], "outputs": ["dist/**"] }, @@ -79,7 +79,7 @@ "dependsOn": ["devframe#build"], "outputs": ["dist/**"] }, - "@devframes/a11y#build": { + "@devframes/plugin-a11y#build": { "outputLogs": "new-only", "dependsOn": ["devframe#build"], "outputs": ["dist/**"] @@ -105,9 +105,9 @@ "dependsOn": ["@devframes/plugin-git#build"], "outputs": ["dist/static/**"] }, - "@devframes/a11y#cli:build": { + "@devframes/plugin-a11y#cli:build": { "outputLogs": "new-only", - "dependsOn": ["@devframes/a11y#build"], + "dependsOn": ["@devframes/plugin-a11y#build"], "outputs": ["dist/static/**"] } }