diff --git a/alias.ts b/alias.ts index 08db230d3..6cefefa01 100644 --- a/alias.ts +++ b/alias.ts @@ -78,63 +78,52 @@ export const alias = { '@devframes/json-render-ui/spa': r('json-render-ui/src/spa.ts'), '@devframes/json-render-ui': r('json-render-ui/src/index.ts'), 'json-render/dashboard': fileURLToPath(new URL('./examples/json-render/src/dashboard.ts', import.meta.url)), - '@devframes/plugin-code-server/client': p('code-server/src/client/index.ts'), - '@devframes/plugin-code-server/node': p('code-server/src/node/index.ts'), - '@devframes/plugin-code-server/constants': p('code-server/src/constants.ts'), - '@devframes/plugin-code-server/types': p('code-server/src/types.ts'), - '@devframes/plugin-code-server/rpc': p('code-server/src/rpc/index.ts'), - '@devframes/plugin-code-server/cli': p('code-server/src/cli.ts'), - '@devframes/plugin-code-server/vite': p('code-server/src/vite.ts'), - '@devframes/plugin-code-server': p('code-server/src/index.ts'), - '@devframes/plugin-terminals/client': p('terminals/src/client/index.ts'), - '@devframes/plugin-terminals/node': p('terminals/src/node/index.ts'), - '@devframes/plugin-terminals/constants': p('terminals/src/constants.ts'), - '@devframes/plugin-terminals/types': p('terminals/src/types.ts'), - '@devframes/plugin-terminals/cli': p('terminals/src/cli.ts'), - '@devframes/plugin-terminals/vite': p('terminals/src/vite.ts'), - '@devframes/plugin-terminals': p('terminals/src/index.ts'), - '@devframes/plugin-git': p('git/src/index.ts'), + '@devframes/plugin-code-server/node': p('code-server/src/node/setup.ts'), + '@devframes/plugin-code-server/constants': p('code-server/src/node/constants.ts'), + '@devframes/plugin-code-server/types': p('code-server/src/node/types.ts'), + '@devframes/plugin-code-server/rpc': p('code-server/src/node/rpc/index.ts'), + '@devframes/plugin-code-server/cli': p('code-server/src/node/cli.ts'), + '@devframes/plugin-code-server/vite': p('code-server/src/node/vite.ts'), + '@devframes/plugin-code-server': p('code-server/src/node/index.ts'), + '@devframes/plugin-terminals/client': p('terminals/app/client/index.ts'), + '@devframes/plugin-terminals/node': p('terminals/src/node/setup.ts'), + '@devframes/plugin-terminals/constants': p('terminals/src/node/constants.ts'), + '@devframes/plugin-terminals/types': p('terminals/src/node/types.ts'), + '@devframes/plugin-terminals/cli': p('terminals/src/node/cli.ts'), + '@devframes/plugin-terminals/vite': p('terminals/src/node/vite.ts'), + '@devframes/plugin-terminals': p('terminals/src/node/index.ts'), + '@devframes/plugin-git': p('git/src/node/index.ts'), 'devframe/recipes/interactive-auth': r('devframe/src/recipes/interactive-auth.ts'), 'devframe/recipes/common-rpc-functions': r('devframe/src/recipes/common-rpc-functions.ts'), 'devframe/client': r('devframe/src/client/index.ts'), 'devframe': r('devframe/src'), - '@devframes/plugin-data-inspector/client': p('data-inspector/src/client/index.ts'), - '@devframes/plugin-data-inspector/node': p('data-inspector/src/node/index.ts'), - '@devframes/plugin-data-inspector/registry': p('data-inspector/src/registry/index.ts'), - '@devframes/plugin-data-inspector/engine': p('data-inspector/src/engine/index.ts'), '@devframes/plugin-data-inspector/inject': p('data-inspector/src/inject/index.ts'), - '@devframes/plugin-data-inspector/cli': p('data-inspector/src/cli.ts'), - '@devframes/plugin-data-inspector/vite': p('data-inspector/src/vite.ts'), - '@devframes/plugin-data-inspector': p('data-inspector/src/index.ts'), - '@devframes/plugin-inspect/client': p('inspect/src/client/index.ts'), - '@devframes/plugin-inspect/node': p('inspect/src/node/index.ts'), - '@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/plugin-og/client': p('og/src/client/index.ts'), - '@devframes/plugin-og/node': p('og/src/node/index.ts'), - '@devframes/plugin-og/rpc': p('og/src/rpc/index.ts'), - '@devframes/plugin-og/cli': p('og/src/cli.ts'), - '@devframes/plugin-og/vite': p('og/src/vite.ts'), - '@devframes/plugin-og': p('og/src/index.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'), - '@devframes/plugin-messages/client': p('messages/src/client/index.ts'), - '@devframes/plugin-messages/node': p('messages/src/node/index.ts'), - '@devframes/plugin-messages/constants': p('messages/src/constants.ts'), - '@devframes/plugin-messages/rpc': p('messages/src/rpc/index.ts'), - '@devframes/plugin-messages/cli': p('messages/src/cli.ts'), - '@devframes/plugin-messages/vite': p('messages/src/vite.ts'), - '@devframes/plugin-messages': p('messages/src/index.ts'), - '@devframes/plugin-assets/client': p('assets/src/client/index.ts'), - '@devframes/plugin-assets/node': p('assets/src/node/index.ts'), - '@devframes/plugin-assets/rpc': p('assets/src/rpc/index.ts'), - '@devframes/plugin-assets/cli': p('assets/src/cli.ts'), - '@devframes/plugin-assets/vite': p('assets/src/vite.ts'), - '@devframes/plugin-assets': p('assets/src/index.ts'), + '@devframes/plugin-data-inspector/node': p('data-inspector/src/node/setup.ts'), + '@devframes/plugin-data-inspector/registry': p('data-inspector/src/node/registry/index.ts'), + '@devframes/plugin-data-inspector/engine': p('data-inspector/src/node/engine/index.ts'), + '@devframes/plugin-data-inspector/cli': p('data-inspector/src/node/cli.ts'), + '@devframes/plugin-data-inspector': p('data-inspector/src/node/index.ts'), + '@devframes/plugin-inspect/node': p('inspect/src/node/setup.ts'), + '@devframes/plugin-inspect/cli': p('inspect/src/node/cli.ts'), + '@devframes/plugin-inspect': p('inspect/src/node/index.ts'), + '@devframes/plugin-og/node': p('og/src/node/setup.ts'), + '@devframes/plugin-og/rpc': p('og/src/node/rpc/index.ts'), + '@devframes/plugin-og/cli': p('og/src/node/cli.ts'), + '@devframes/plugin-og': p('og/src/node/index.ts'), + '@devframes/plugin-a11y/client-script': p('a11y/src/client-script/index.ts'), + '@devframes/plugin-a11y/node': p('a11y/src/node/setup.ts'), + '@devframes/plugin-a11y/cli': p('a11y/src/node/cli.ts'), + '@devframes/plugin-a11y': p('a11y/src/node/index.ts'), + '@devframes/plugin-messages/client': p('messages/app/client/index.ts'), + '@devframes/plugin-messages/node': p('messages/src/node/setup.ts'), + '@devframes/plugin-messages/constants': p('messages/src/node/constants.ts'), + '@devframes/plugin-messages/rpc': p('messages/src/node/rpc/index.ts'), + '@devframes/plugin-messages/cli': p('messages/src/node/cli.ts'), + '@devframes/plugin-messages': p('messages/src/node/index.ts'), + '@devframes/plugin-assets/node': p('assets/src/node/setup.ts'), + '@devframes/plugin-assets/rpc': p('assets/src/node/rpc/index.ts'), + '@devframes/plugin-assets/cli': p('assets/src/node/cli.ts'), + '@devframes/plugin-assets': p('assets/src/node/index.ts'), '@devframes/service-git': s('git/src/index.ts'), '@devframes/service-open': s('open/src/index.ts'), '@devframes/service-shiki': s('shiki/src/index.ts'), diff --git a/docs/content/1.guide/1.tutorial-server-data-inspector.md b/docs/content/1.guide/1.tutorial-server-data-inspector.md index 48cfbe90b..e31785259 100644 --- a/docs/content/1.guide/1.tutorial-server-data-inspector.md +++ b/docs/content/1.guide/1.tutorial-server-data-inspector.md @@ -13,6 +13,8 @@ You'll need [Node 24+](https://nodejs.org/) and a terminal. Every code block is A devframe is two halves talking over a typed connection: the **node side** exposes functions, and the **browser side** calls them and renders the results. Devframe is everything in between: the wire, the UI hosting, auth, builds, and a CLI. +The two halves live in their own folders: the node side under `src/node/`, the web app under `app/`. A `playgrounds/` folder holds hosts that boot the built tool. We'll fill these in as we go. + ## Step 1: Define the tool Everything starts with `defineDevframe`: your tool's name, plus a `setup` where you register what it can do. Create the project and the definition: @@ -23,7 +25,7 @@ npm init -y && npm pkg set type=module npm install devframe && npm install -D typescript ``` -```ts [src/data-inspector.ts] +```ts [src/node/data-inspector.ts] import { defineDevframe } from 'devframe' // Some example server-side data, whatever you want to peek at while your @@ -95,7 +97,7 @@ npm install react react-dom @devframes/vite npm install -D vite @vitejs/plugin-react @types/react @types/react-dom ``` -```html [client/index.html] +```html [app/index.html] @@ -109,14 +111,14 @@ npm install -D vite @vitejs/plugin-react @types/react @types/react-dom ``` -```tsx [client/main.tsx] +```tsx [app/main.tsx] import { createRoot } from 'react-dom/client' import { App } from './App' createRoot(document.getElementById('app')!).render() ``` -```tsx [client/App.tsx] +```tsx [app/App.tsx] import type { DevframeRpcClient } from 'devframe/client' import { connectDevframe } from 'devframe/client' import { useEffect, useState } from 'react' @@ -173,14 +175,13 @@ export function App() { To try what we've built, let Vite serve the UI and hand RPC traffic to devframe: -```ts [vite.client.config.ts] +```ts [app/vite.config.ts] import { devframeViteBridge } from '@devframes/vite/single' import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' -import dataInspectorFrame from './src/data-inspector.ts' +import dataInspectorFrame from '../src/node/data-inspector.ts' export default defineConfig({ - root: 'client', base: './', // relative asset URLs, so the built UI works under any mount path build: { outDir: '../dist/client', emptyOutDir: true }, plugins: [ @@ -193,7 +194,7 @@ export default defineConfig({ ``` ```sh -npx vite --config vite.client.config.ts +npx vite --config app/vite.config.ts ``` Open the printed URL. The three keys and their types show up, and typing `config.port` or `users.0.name` and hitting **Query** prints the value. Button → `call` → your `handler` → back to the page: that's the whole devframe working. @@ -201,19 +202,19 @@ Open the printed URL. The three keys and their types show up, and typing `config > [!WARNING] > `auth: false` trusts anything that can reach the port. It's off here to keep the tutorial simple; turn it on for anything you publish or expose beyond localhost. See [Security](/guide/security). -From here on we reuse this same `src/data-inspector.ts` and `client/` unchanged; all that changes is where they run. +From here on we reuse this same `src/node/data-inspector.ts` and `app/` unchanged; all that changes is where they run. ## Step 4: Dock it in a hub A [hub](/guide/hub) puts many devframes behind one interface, each a **dock entry** you switch between, the tool's own UI in an iframe. Since our SPA uses a bare `connectDevframe()`, it already works anywhere; the hub just needs the built UI, so point the definition at it: -```ts [src/data-inspector.ts] +```ts [src/node/data-inspector.ts] import { fileURLToPath } from 'node:url' // … const dataInspectorFrame = defineDevframe({ id: 'data-inspector', /** … */ - clientAssets: fileURLToPath(new URL('../dist/client', import.meta.url)), + clientAssets: fileURLToPath(new URL('../../dist/client', import.meta.url)), setup(ctx) { /* unchanged */ }, }) ``` @@ -222,14 +223,14 @@ Build the UI and stand up a one-devframe hub: ```sh npm install @devframes/hub @devframes/hub-ui -npx vite build --config vite.client.config.ts +npx vite build --config app/vite.config.ts ``` -```ts [vite.hub.config.ts] +```ts [playgrounds/hub.config.ts] import { createUi } from '@devframes/hub-ui' import { viteDevframeHub } from '@devframes/vite/hub' import { defineConfig } from 'vite' -import dataInspectorFrame from './src/data-inspector.ts' +import dataInspectorFrame from '../src/node/data-inspector.ts' export default defineConfig({ plugins: [ @@ -242,7 +243,7 @@ export default defineConfig({ ``` ```sh -npx vite --config vite.hub.config.ts +npx vite --config playgrounds/hub.config.ts ``` Your inspector now sits in the hub's dock rail as a dock entry. Add more to `devframes: [...]` (your own or the [built-in devframes](/add-ons)) and each gets its own. (The hub prints a code to authorize on first connect.) @@ -265,14 +266,14 @@ ctx.rpc.register({ ```js [scripts/build.mjs] import { createBuild } from 'devframe/adapters/build' -import dataInspectorFrame from '../src/data-inspector.ts' +import dataInspectorFrame from '../src/node/data-inspector.ts' await createBuild(dataInspectorFrame, { outDir: 'dist-static' }) ``` ```sh -npx vite build # refresh dist/client -node scripts/build.mjs # → dist-static/ +npx vite build --config app/vite.config.ts # refresh dist/client +node scripts/build.mjs # → dist-static/ ``` Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, no Node in sight. `query` takes an argument, so it still needs the live node side (next), or you can bake specific inputs ([Client Assets](/guide/client-assets)). @@ -281,16 +282,16 @@ Serve `dist-static/` anywhere and the meta list renders from the baked snapshot, The definition never depended on Vite. `createDevServer` runs the tool on its own, serving the UI from `clientAssets` and answering RPC live: -```js [scripts/serve.mjs] +```js [playgrounds/serve.mjs] import { createDevServer } from 'devframe/adapters/dev' -import dataInspectorFrame from '../src/data-inspector.ts' +import dataInspectorFrame from '../src/node/data-inspector.ts' await createDevServer(dataInspectorFrame, { openBrowser: true }) ``` ```sh -npx vite build -node scripts/serve.mjs +npx vite build --config app/vite.config.ts +node playgrounds/serve.mjs ``` Same UI, same live calls, no bundler in the loop: this is what you'd drop into your own Node program. @@ -302,7 +303,7 @@ Finally, wrap that dev server in a CLI. `devframe/adapters/cac` turns a devframe ```js [bin.mjs] #!/usr/bin/env node import { createCac } from 'devframe/adapters/cac' -import dataInspectorFrame from './src/data-inspector.ts' +import dataInspectorFrame from './src/node/data-inspector.ts' createCac(dataInspectorFrame).parse() ``` diff --git a/knip.jsonc b/knip.jsonc index 92d0782e2..24548d877 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -19,6 +19,7 @@ // the default export is the conventional single-devframe import. Service // packages (`createService`) follow the same rule. "ignoreIssues": { + "plugins/*/src/node/index.ts": ["duplicates"], "plugins/*/src/index.ts": ["duplicates"], "services/*/src/index.ts": ["duplicates"], // Deprecated rename aliases (old name re-exported next to its canonical @@ -209,10 +210,11 @@ // the monorepo. Repeat the `plugins/*` entry glob (a workspace config // replaces, not merges, it). "entry": [ - "src/{index,cli,vite,constants,types}.ts", - "src/{client,node,rpc,inject,engine,registry}/index.ts" + "src/node/{index,setup,cli}.ts", + "src/node/rpc/index.ts", + "app/**/*.stories.@(ts|tsx)" ], - "ignoreDependencies": ["@devframes/plugin-inspect--assets"] + "ignoreDependencies": ["@devframes/plugin-inspect--assets", "@devframes/vite"] }, "plugins/inspect/assets-pkg": { // Assets-only package: no source, just a prebuilt `dist` produced by @@ -225,32 +227,60 @@ // so knip can't see the dev-only workspace link. A workspace config // replaces (not merges) the `plugins/*` glob's `entry`, so repeat it. "plugins/og": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], - "ignoreDependencies": ["@devframes/plugin-og--assets"] + "entry": [ + "src/node/{index,setup,cli}.ts", + "src/node/rpc/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], + "ignoreDependencies": ["@devframes/plugin-og--assets", "@devframes/vite"] }, "plugins/og/assets-pkg": { "entry": [], "project": [] }, "plugins/data-inspector": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], - "ignoreDependencies": ["@devframes/plugin-data-inspector--assets"] + "entry": [ + "src/node/{index,setup,cli}.ts", + "src/node/rpc/index.ts", + "src/node/{engine,registry}/index.ts", + "src/inject/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], + "ignoreDependencies": ["@devframes/plugin-data-inspector--assets", "@devframes/vite"] }, "plugins/data-inspector/assets-pkg": { "entry": [], "project": [] }, "plugins/code-server": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], + "entry": [ + "src/node/{index,setup,cli,constants,types,vite}.ts", + "src/node/rpc/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], "ignoreDependencies": ["@devframes/plugin-code-server--assets"] }, "plugins/code-server/assets-pkg": { "entry": [], "project": [] }, "plugins/assets": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], - "ignoreDependencies": ["@devframes/plugin-assets--assets"] + "entry": [ + "src/node/{index,setup,cli,constants,types}.ts", + "src/node/rpc/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], + "ignoreDependencies": ["@devframes/plugin-assets--assets", "@devframes/vite"] }, "plugins/assets/assets-pkg": { "entry": [], "project": [] }, "plugins/messages": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], - "ignoreDependencies": ["@devframes/plugin-messages--assets"] + "entry": [ + "src/node/{index,setup,cli,constants}.ts", + "src/node/rpc/index.ts", + "app/client/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], + "ignoreDependencies": ["@devframes/plugin-messages--assets", "@devframes/vite"] }, "plugins/messages/assets-pkg": { "entry": [], "project": [] }, "plugins/terminals": { - "entry": ["src/{index,cli,vite,constants,types}.ts", "src/{client,node,rpc,inject,engine,registry}/index.ts"], + "entry": [ + "src/node/{index,setup,cli,constants,types,vite}.ts", + "src/node/rpc/index.ts", + "app/client/index.ts", + "app/**/*.stories.@(ts|tsx)" + ], "ignoreDependencies": ["@devframes/plugin-terminals--assets"] }, "plugins/terminals/assets-pkg": { "entry": [], "project": [] }, @@ -269,19 +299,24 @@ // `plugins/*` glob's `entry` above, so repeat that pattern here // alongside the stories. "entry": [ - "src/{index,cli,vite,constants,types}.ts", - "src/{client,node,rpc,inject,engine,registry}/index.ts", - "src/**/*.stories.tsx" + "src/node/{index,setup,cli}.ts", + "src/node/rpc/index.ts", + "src/client-script/index.ts", + "app/**/*.stories.tsx" ], - "ignoreDependencies": ["@devframes/plugin-a11y--assets"] + "ignoreDependencies": ["@devframes/plugin-a11y--assets", "@devframes/vite"] }, "plugins/git": { - // The React client build's root sits at `src/client` (`next dev - // src/client`), not the workspace root, so knip's default PostCSS - // config glob (workspace-root `postcss.config.*`) misses it. + // The React client build's root sits at `app/` (`next dev app`), not the + // workspace root, so knip's default PostCSS config glob (workspace-root + // `postcss.config.*`) misses it. "postcss": { - "config": ["src/client/postcss.config.mjs"] + "config": ["app/postcss.config.mjs"] }, + "entry": [ + "src/node/{index,cli}.ts", + "app/**/*.stories.@(ts|tsx)" + ], "ignoreDependencies": ["@devframes/plugin-git--assets"] } } diff --git a/plugins/a11y/.storybook/main.ts b/plugins/a11y/.storybook/main.ts index f3fb6eab1..a04d03afe 100644 --- a/plugins/a11y/.storybook/main.ts +++ b/plugins/a11y/.storybook/main.ts @@ -4,7 +4,7 @@ import { mergeConfig } from 'vite' import { alias } from '../../../alias' const config: StorybookConfig = { - stories: ['../src/**/*.stories.@(ts|tsx)'], + stories: ['../{src,app}/**/*.stories.@(ts|tsx)'], framework: { name: 'storybook-solidjs-vite', options: {}, diff --git a/plugins/a11y/.storybook/preview.tsx b/plugins/a11y/.storybook/preview.tsx index 4e9db35ef..0dabc3eb2 100644 --- a/plugins/a11y/.storybook/preview.tsx +++ b/plugins/a11y/.storybook/preview.tsx @@ -1,7 +1,7 @@ import type { Preview } from 'storybook-solidjs-vite' import 'virtual:uno.css' import '@antfu/design/styles.css' -import '../src/spa/styles.css' +import '../app/styles.css' // Drive the shared `@antfu/design` tokens off the toolbar theme toggle: dark mode // is the `.dark` class on ``, and the canvas takes the semantic diff --git a/plugins/a11y/src/spa/app.tsx b/plugins/a11y/app/app.tsx similarity index 99% rename from plugins/a11y/src/spa/app.tsx rename to plugins/a11y/app/app.tsx index 9226340cd..8cc919804 100644 --- a/plugins/a11y/src/spa/app.tsx +++ b/plugins/a11y/app/app.tsx @@ -1,8 +1,8 @@ -import type { Impact, PinTarget, Violation, ViolationNode } from '../shared/protocol.ts' +import type { Impact, PinTarget, Violation, ViolationNode } from '../src/shared/protocol.ts' import type { SelectedItem } from './lib/fix-prompt.ts' import type { RouteGroupModel, SelectionApi } from './lib/violation-view.ts' import { batch, createEffect, createMemo, createSignal, Match, on, Show, Switch } from 'solid-js' -import { emptyCounts } from '../shared/protocol.ts' +import { emptyCounts } from '../src/shared/protocol.ts' import { EmptyState } from './components/EmptyState.tsx' import { FixPromptsDialog } from './components/FixPromptsDialog.tsx' import { Header } from './components/Header.tsx' diff --git a/plugins/a11y/src/spa/components/EmptyState.stories.tsx b/plugins/a11y/app/components/EmptyState.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/EmptyState.stories.tsx rename to plugins/a11y/app/components/EmptyState.stories.tsx diff --git a/plugins/a11y/src/spa/components/EmptyState.tsx b/plugins/a11y/app/components/EmptyState.tsx similarity index 100% rename from plugins/a11y/src/spa/components/EmptyState.tsx rename to plugins/a11y/app/components/EmptyState.tsx diff --git a/plugins/a11y/src/spa/components/FixPromptsDialog.stories.tsx b/plugins/a11y/app/components/FixPromptsDialog.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/FixPromptsDialog.stories.tsx rename to plugins/a11y/app/components/FixPromptsDialog.stories.tsx diff --git a/plugins/a11y/src/spa/components/FixPromptsDialog.tsx b/plugins/a11y/app/components/FixPromptsDialog.tsx similarity index 100% rename from plugins/a11y/src/spa/components/FixPromptsDialog.tsx rename to plugins/a11y/app/components/FixPromptsDialog.tsx diff --git a/plugins/a11y/src/spa/components/Header.stories.tsx b/plugins/a11y/app/components/Header.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/Header.stories.tsx rename to plugins/a11y/app/components/Header.stories.tsx diff --git a/plugins/a11y/src/spa/components/Header.tsx b/plugins/a11y/app/components/Header.tsx similarity index 100% rename from plugins/a11y/src/spa/components/Header.tsx rename to plugins/a11y/app/components/Header.tsx diff --git a/plugins/a11y/src/spa/components/MetaLine.stories.tsx b/plugins/a11y/app/components/MetaLine.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/MetaLine.stories.tsx rename to plugins/a11y/app/components/MetaLine.stories.tsx diff --git a/plugins/a11y/src/spa/components/MetaLine.tsx b/plugins/a11y/app/components/MetaLine.tsx similarity index 100% rename from plugins/a11y/src/spa/components/MetaLine.tsx rename to plugins/a11y/app/components/MetaLine.tsx diff --git a/plugins/a11y/src/spa/components/RouteGroup.stories.tsx b/plugins/a11y/app/components/RouteGroup.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/RouteGroup.stories.tsx rename to plugins/a11y/app/components/RouteGroup.stories.tsx diff --git a/plugins/a11y/src/spa/components/RouteGroup.tsx b/plugins/a11y/app/components/RouteGroup.tsx similarity index 97% rename from plugins/a11y/src/spa/components/RouteGroup.tsx rename to plugins/a11y/app/components/RouteGroup.tsx index c91c57f4c..c3c5c74f1 100644 --- a/plugins/a11y/src/spa/components/RouteGroup.tsx +++ b/plugins/a11y/app/components/RouteGroup.tsx @@ -1,4 +1,4 @@ -import type { ScanReport, Violation } from '../../shared/protocol.ts' +import type { ScanReport, Violation } from '../../src/shared/protocol.ts' import type { A11yChannel } from '../lib/channel.ts' import type { SelectionApi } from '../lib/violation-view.ts' import { For, Show } from 'solid-js' diff --git a/plugins/a11y/src/spa/components/Summary.stories.tsx b/plugins/a11y/app/components/Summary.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/Summary.stories.tsx rename to plugins/a11y/app/components/Summary.stories.tsx diff --git a/plugins/a11y/src/spa/components/Summary.tsx b/plugins/a11y/app/components/Summary.tsx similarity index 97% rename from plugins/a11y/src/spa/components/Summary.tsx rename to plugins/a11y/app/components/Summary.tsx index a04f233eb..50f6ea9fa 100644 --- a/plugins/a11y/src/spa/components/Summary.tsx +++ b/plugins/a11y/app/components/Summary.tsx @@ -1,4 +1,4 @@ -import type { Impact } from '../../shared/protocol.ts' +import type { Impact } from '../../src/shared/protocol.ts' import { For } from 'solid-js' import { IMPACT_COLOR, IMPACT_LABEL } from '../lib/impact.ts' diff --git a/plugins/a11y/src/spa/components/SummaryBar.stories.tsx b/plugins/a11y/app/components/SummaryBar.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/SummaryBar.stories.tsx rename to plugins/a11y/app/components/SummaryBar.stories.tsx diff --git a/plugins/a11y/src/spa/components/SummaryBar.tsx b/plugins/a11y/app/components/SummaryBar.tsx similarity index 98% rename from plugins/a11y/src/spa/components/SummaryBar.tsx rename to plugins/a11y/app/components/SummaryBar.tsx index dc70547d6..81827c423 100644 --- a/plugins/a11y/src/spa/components/SummaryBar.tsx +++ b/plugins/a11y/app/components/SummaryBar.tsx @@ -1,4 +1,4 @@ -import type { Impact } from '../../shared/protocol.ts' +import type { Impact } from '../../src/shared/protocol.ts' import { Show } from 'solid-js' import { Summary } from './Summary.tsx' import { Switch } from './Switch.tsx' diff --git a/plugins/a11y/src/spa/components/Switch.stories.tsx b/plugins/a11y/app/components/Switch.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/Switch.stories.tsx rename to plugins/a11y/app/components/Switch.stories.tsx diff --git a/plugins/a11y/src/spa/components/Switch.tsx b/plugins/a11y/app/components/Switch.tsx similarity index 100% rename from plugins/a11y/src/spa/components/Switch.tsx rename to plugins/a11y/app/components/Switch.tsx diff --git a/plugins/a11y/src/spa/components/ViolationList.stories.tsx b/plugins/a11y/app/components/ViolationList.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/ViolationList.stories.tsx rename to plugins/a11y/app/components/ViolationList.stories.tsx diff --git a/plugins/a11y/src/spa/components/ViolationList.tsx b/plugins/a11y/app/components/ViolationList.tsx similarity index 100% rename from plugins/a11y/src/spa/components/ViolationList.tsx rename to plugins/a11y/app/components/ViolationList.tsx diff --git a/plugins/a11y/src/spa/components/ViolationRow.stories.tsx b/plugins/a11y/app/components/ViolationRow.stories.tsx similarity index 100% rename from plugins/a11y/src/spa/components/ViolationRow.stories.tsx rename to plugins/a11y/app/components/ViolationRow.stories.tsx diff --git a/plugins/a11y/src/spa/components/ViolationRow.tsx b/plugins/a11y/app/components/ViolationRow.tsx similarity index 98% rename from plugins/a11y/src/spa/components/ViolationRow.tsx rename to plugins/a11y/app/components/ViolationRow.tsx index 5524c6cf9..de00674fd 100644 --- a/plugins/a11y/src/spa/components/ViolationRow.tsx +++ b/plugins/a11y/app/components/ViolationRow.tsx @@ -1,4 +1,4 @@ -import type { Violation } from '../../shared/protocol.ts' +import type { Violation } from '../../src/shared/protocol.ts' import type { A11yChannel } from '../lib/channel.ts' import { For, Show } from 'solid-js' import { IMPACT_COLOR, IMPACT_LABEL } from '../lib/impact.ts' diff --git a/plugins/a11y/src/spa/components/_fixtures.ts b/plugins/a11y/app/components/_fixtures.ts similarity index 93% rename from plugins/a11y/src/spa/components/_fixtures.ts rename to plugins/a11y/app/components/_fixtures.ts index 3e36f67fd..54d30e7a7 100644 --- a/plugins/a11y/src/spa/components/_fixtures.ts +++ b/plugins/a11y/app/components/_fixtures.ts @@ -1,7 +1,7 @@ -import type { ScanReport, Violation } from '../../shared/protocol.ts' +import type { ScanReport, Violation } from '../../src/shared/protocol.ts' import type { A11yChannel } from '../lib/channel.ts' import type { RouteGroupModel, SelectionApi } from '../lib/violation-view.ts' -import { emptyCounts } from '../../shared/protocol.ts' +import { emptyCounts } from '../../src/shared/protocol.ts' function noop() {} diff --git a/plugins/assets/src/spa/app/design.ts b/plugins/a11y/app/design.ts similarity index 75% rename from plugins/assets/src/spa/app/design.ts rename to plugins/a11y/app/design.ts index af3b99e68..0bf9debcd 100644 --- a/plugins/assets/src/spa/app/design.ts +++ b/plugins/a11y/app/design.ts @@ -1,3 +1,3 @@ // Re-exports the shared devframe class-helper builders (see // `design/design.ts`) so this surface stays in lockstep with every other. -export * from '../../../../../design/design' +export * from '../../../design/design' diff --git a/plugins/a11y/src/spa/index.html b/plugins/a11y/app/index.html similarity index 100% rename from plugins/a11y/src/spa/index.html rename to plugins/a11y/app/index.html diff --git a/plugins/a11y/src/spa/lib/channel.ts b/plugins/a11y/app/lib/channel.ts similarity index 97% rename from plugins/a11y/src/spa/lib/channel.ts rename to plugins/a11y/app/lib/channel.ts index cb0e92c07..7b416fe89 100644 --- a/plugins/a11y/src/spa/lib/channel.ts +++ b/plugins/a11y/app/lib/channel.ts @@ -1,8 +1,8 @@ import type { Accessor } from 'solid-js' -import type { A11yChannelProtocol, A11yState, PageScriptConfig, PinTarget } from '../../shared/protocol.ts' +import type { A11yChannelProtocol, A11yState, PageScriptConfig, PinTarget } from '../../src/shared/protocol.ts' import { connectPanelChannel } from 'devframe/in-page-channel' import { createSignal, onCleanup } from 'solid-js' -import { A11Y_CHANNEL } from '../../shared/protocol.ts' +import { A11Y_CHANNEL } from '../../src/shared/protocol.ts' export interface A11yChannel { /** Latest full route → report aggregate, or `null` until the page script reports in. */ diff --git a/plugins/a11y/src/spa/lib/devframe.ts b/plugins/a11y/app/lib/devframe.ts similarity index 96% rename from plugins/a11y/src/spa/lib/devframe.ts rename to plugins/a11y/app/lib/devframe.ts index 3a528ea62..2beacf3ea 100644 --- a/plugins/a11y/src/spa/lib/devframe.ts +++ b/plugins/a11y/app/lib/devframe.ts @@ -1,9 +1,9 @@ import type { DevframeConnectionStatus } from 'devframe/client' import type { Accessor } from 'solid-js' -import type { Impact, PageScriptConfig } from '../../shared/protocol.ts' +import type { Impact, PageScriptConfig } from '../../src/shared/protocol.ts' import { connectDevframe } from 'devframe/client' import { createSignal } from 'solid-js' -import { A11Y_DOCKS_ACTIVE_KEY } from '../../shared/protocol.ts' +import { A11Y_DOCKS_ACTIVE_KEY } from '../../src/shared/protocol.ts' interface ImpactMeta { id: Impact diff --git a/plugins/a11y/src/spa/lib/fix-prompt.ts b/plugins/a11y/app/lib/fix-prompt.ts similarity index 97% rename from plugins/a11y/src/spa/lib/fix-prompt.ts rename to plugins/a11y/app/lib/fix-prompt.ts index 0e439baa6..779010608 100644 --- a/plugins/a11y/src/spa/lib/fix-prompt.ts +++ b/plugins/a11y/app/lib/fix-prompt.ts @@ -1,4 +1,4 @@ -import type { Violation } from '../../shared/protocol.ts' +import type { Violation } from '../../src/shared/protocol.ts' /** One selected violation, with the route context it was found on. */ export interface SelectedItem { diff --git a/plugins/a11y/src/spa/lib/impact.ts b/plugins/a11y/app/lib/impact.ts similarity index 71% rename from plugins/a11y/src/spa/lib/impact.ts rename to plugins/a11y/app/lib/impact.ts index bae18453f..6228d457c 100644 --- a/plugins/a11y/src/spa/lib/impact.ts +++ b/plugins/a11y/app/lib/impact.ts @@ -1,8 +1,8 @@ -import type { Impact } from '../../shared/protocol.ts' +import type { Impact } from '../../src/shared/protocol.ts' // The severity palette is shared with the page script, a single source in the // protocol module so the panel and the highlight ring never drift. -export { IMPACT_COLOR } from '../../shared/protocol.ts' +export { IMPACT_COLOR } from '../../src/shared/protocol.ts' export const IMPACT_LABEL: Record = { critical: 'Critical', diff --git a/plugins/a11y/src/spa/lib/violation-view.ts b/plugins/a11y/app/lib/violation-view.ts similarity index 90% rename from plugins/a11y/src/spa/lib/violation-view.ts rename to plugins/a11y/app/lib/violation-view.ts index ef13f482d..49ec1b8d6 100644 --- a/plugins/a11y/src/spa/lib/violation-view.ts +++ b/plugins/a11y/app/lib/violation-view.ts @@ -1,4 +1,4 @@ -import type { ScanReport, Violation } from '../../shared/protocol.ts' +import type { ScanReport, Violation } from '../../src/shared/protocol.ts' /** Controller the violation list uses to read/mutate the selection. */ export interface SelectionApi { diff --git a/plugins/a11y/src/spa/main.tsx b/plugins/a11y/app/main.tsx similarity index 100% rename from plugins/a11y/src/spa/main.tsx rename to plugins/a11y/app/main.tsx diff --git a/plugins/a11y/src/spa/styles.css b/plugins/a11y/app/styles.css similarity index 100% rename from plugins/a11y/src/spa/styles.css rename to plugins/a11y/app/styles.css diff --git a/plugins/a11y/app/vite.config.ts b/plugins/a11y/app/vite.config.ts new file mode 100644 index 000000000..e588e2bac --- /dev/null +++ b/plugins/a11y/app/vite.config.ts @@ -0,0 +1,54 @@ +import type { Server } from 'node:http' +import type { Plugin } from 'vite' +import { fileURLToPath } from 'node:url' +import createA11yDevframe from '@devframes/plugin-a11y' +import { initDevframe } from 'devframe/initiate' +import { resolveBasePath } from 'devframe/internal' +import UnoCSS from 'unocss/vite' +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' +import { alias } from '../../../alias' + +/** + * Serve-only plugin that bridges the a11y node side (RPC + WebSocket + + * `__connection.json`) onto Vite's own dev server under the devframe base + * path, mounted as a **post** middleware so Vite serves the HMR SPA first and + * the devframe host only answers the routes it owns. Inert during `vite build`. + */ +function a11yDevBridge(): Plugin { + const devframe = createA11yDevframe() + return { + name: 'a11y-dev-bridge', + apply: 'serve', + configureServer(server) { + const instance = initDevframe(devframe, { + base: resolveBasePath(devframe, 'hosted'), + distDir: false, + server: server.httpServer as Server, + auth: false, + }) + return () => server.middlewares.use(instance.nodeMiddleware) + }, + } +} + +/** + * `base: './'` for the build keeps the mount path portable: the same `app` + * output works whether devframe serves it at `/` (standalone) or + * `/__devframes_plugin_a11y/` (mounted in a hub). `connectDevframe` resolves + * its connection meta relative to `document.baseURI` to match. + * + * In `vite dev` the panel is served under the devframe base path so its + * `document.baseURI` matches production, and {@link a11yDevBridge} bridges the + * node side there with HMR. + */ +export default defineConfig(({ command }) => ({ + base: command === 'serve' ? resolveBasePath(createA11yDevframe(), 'hosted') : './', + root: fileURLToPath(new URL('.', import.meta.url)), + resolve: { alias }, + plugins: [solid(), UnoCSS(), a11yDevBridge()], + build: { + outDir: fileURLToPath(new URL('../assets-pkg/dist', import.meta.url)), + emptyOutDir: true, + }, +})) diff --git a/plugins/a11y/bin.mjs b/plugins/a11y/bin.mjs index 8e7267a04..fb3c9d931 100755 --- a/plugins/a11y/bin.mjs +++ b/plugins/a11y/bin.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node import process from 'node:process' -import { createA11yCli } from './dist/cli.mjs' +import { createA11yCli } from './dist/node/cli.mjs' async function main() { const cli = createA11yCli() diff --git a/plugins/a11y/package.json b/plugins/a11y/package.json index 582b3ff85..fa80c33d6 100644 --- a/plugins/a11y/package.json +++ b/plugins/a11y/package.json @@ -22,13 +22,13 @@ ], "sideEffects": false, "exports": { - ".": "./dist/index.mjs", - "./client": "./dist/client/index.mjs", - "./cli": "./dist/cli.mjs", - "./node": "./dist/node/index.mjs", + ".": "./dist/node/index.mjs", + "./client-script": "./dist/client-script/index.mjs", + "./cli": "./dist/node/cli.mjs", + "./node": "./dist/node/setup.mjs", "./package.json": "./package.json" }, - "types": "./dist/index.d.mts", + "types": "./dist/node/index.d.mts", "bin": { "devframes_plugin_a11y": "./bin.mjs" }, @@ -37,16 +37,15 @@ "dist" ], "scripts": { - "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", + "build": "tsdown && vite build --config app/vite.config.ts", + "build:app": "vite build --config app/vite.config.ts", "watch": "tsdown --watch", - "dev": "node bin.mjs", + "dev": "vite --config app/vite.config.ts --host", + "play": "pnpm run build && node playgrounds/server.mjs", + "play:build": "pnpm run build && pnpm run cli:build && node playgrounds/server.mjs build", "storybook": "storybook dev -p 6015", "build-storybook": "storybook build", "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": "turbo run build --filter=@devframes/plugin-a11y", "test": "vitest run" @@ -70,6 +69,7 @@ "devDependencies": { "@antfu/design": "catalog:frontend", "@devframes/plugin-a11y--assets": "workspace:*", + "@devframes/vite": "workspace:*", "axe-core": "catalog:frontend", "colorjs.io": "catalog:frontend", "devframe": "workspace:*", diff --git a/plugins/a11y/demo/index.html b/plugins/a11y/playgrounds/index.html similarity index 99% rename from plugins/a11y/demo/index.html rename to plugins/a11y/playgrounds/index.html index 5c0fda45d..8b05338db 100644 --- a/plugins/a11y/demo/index.html +++ b/plugins/a11y/playgrounds/index.html @@ -161,6 +161,6 @@

This week's roasts

- + diff --git a/plugins/a11y/demo/server.mjs b/plugins/a11y/playgrounds/server.mjs similarity index 86% rename from plugins/a11y/demo/server.mjs rename to plugins/a11y/playgrounds/server.mjs index 50d17da52..47d6b2863 100644 --- a/plugins/a11y/demo/server.mjs +++ b/plugins/a11y/playgrounds/server.mjs @@ -6,13 +6,13 @@ * panel (devtools iframe) can handshake their in-page channel: * * GET / → the demo page (intentional a11y bugs) - * GET /__df-inject/inject.js → the page script bundle + * GET /__df-inject/index.mjs → the page script bundle * GET /__devframes_plugin_a11y/** → the Solid panel SPA * * Two modes prove the plugin works either way: * - * node demo/server.mjs dev → live WebSocket RPC (`assets-pkg/dist`) - * node demo/server.mjs build static → baked RPC dump, (`dist/static`) + * node playgrounds/server.mjs dev → live WebSocket RPC (`assets-pkg/dist`) + * node playgrounds/server.mjs build static → baked RPC dump, (`dist/static`) * * The scan/highlight loop is identical in both: it rides the in-page channel, * not the devframe backend. @@ -27,7 +27,7 @@ import { getPort } from 'devframe/utils/get-port' import { mountStaticHandler } from 'devframe/utils/serve-static' import { H3, toNodeHandler } from 'h3' import { resolve } from 'pathe' -import createA11yDevframe from '../src/index.ts' +import createA11yDevframe from '../src/node/index.ts' const HERE = fileURLToPath(new URL('.', import.meta.url)) const ROOT = resolve(HERE, '..') @@ -35,12 +35,12 @@ const ROOT = resolve(HERE, '..') const devframe = createA11yDevframe() const mode = process.argv[2] === 'build' ? 'build' : 'dev' const basePath = devframe.basePath -const injectDir = resolve(ROOT, 'dist/inject') +const injectDir = resolve(ROOT, 'dist/client-script') const panelDir = mode === 'build' ? resolve(ROOT, 'dist/static') : resolve(ROOT, 'assets-pkg/dist') function requireBuilt(file, hint) { if (!existsSync(file)) { - console.error(`\n[a11y-inspector demo] missing ${file}\n → run \`${hint}\` first.\n`) + console.error(`\n[a11y-inspector playground] missing ${file}\n → run \`${hint}\` first.\n`) process.exit(1) } } @@ -56,7 +56,7 @@ function banner(origin) { } async function main() { - requireBuilt(resolve(injectDir, 'inject.js'), 'pnpm -C plugins/a11y build') + requireBuilt(resolve(injectDir, 'index.mjs'), 'pnpm -C plugins/a11y build') requireBuilt( resolve(panelDir, 'index.html'), mode === 'build' diff --git a/plugins/a11y/src/inject/index.ts b/plugins/a11y/src/client-script/index.ts similarity index 100% rename from plugins/a11y/src/inject/index.ts rename to plugins/a11y/src/client-script/index.ts diff --git a/plugins/a11y/src/inject/messages.ts b/plugins/a11y/src/client-script/messages.ts similarity index 100% rename from plugins/a11y/src/inject/messages.ts rename to plugins/a11y/src/client-script/messages.ts diff --git a/plugins/a11y/src/inject/overlay.ts b/plugins/a11y/src/client-script/overlay.ts similarity index 100% rename from plugins/a11y/src/inject/overlay.ts rename to plugins/a11y/src/client-script/overlay.ts diff --git a/plugins/a11y/src/inject/scanner.ts b/plugins/a11y/src/client-script/scanner.ts similarity index 100% rename from plugins/a11y/src/inject/scanner.ts rename to plugins/a11y/src/client-script/scanner.ts diff --git a/plugins/a11y/src/client/index.ts b/plugins/a11y/src/client/index.ts deleted file mode 100644 index 38e7883a7..000000000 --- a/plugins/a11y/src/client/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -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 the same-origin in-page channel, - * independent of this connection. - */ -export function connectA11y(options?: DevframeRpcClientOptions): Promise { - return connectDevframe(options) -} diff --git a/plugins/a11y/src/client/shims.d.ts b/plugins/a11y/src/client/shims.d.ts deleted file mode 100644 index 12545710f..000000000 --- a/plugins/a11y/src/client/shims.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Side-effect style imports used by the Solid SPA entry. -declare module '*.css' {} -declare module 'virtual:uno.css' {} diff --git a/plugins/a11y/src/index.ts b/plugins/a11y/src/index.ts deleted file mode 100644 index 38b1d4238..000000000 --- a/plugins/a11y/src/index.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { DevframeDefinition, RemoteAssets } from 'devframe' -import { fileURLToPath } from 'node:url' -import { defineDevframe } from 'devframe' -import pkg from '../package.json' with { type: 'json' } -import { setupA11y } from './node/index.ts' - -/** Default devframe id, driving the standalone CLI command and the hosted mount path `/__/`. */ -const DEFAULT_ID = 'devframes_plugin_a11y' -const BASE_PATH = '/__devframes_plugin_a11y/' - -// The Solid panel SPA ships in the lockstep `@devframes/plugin-a11y--assets` -// package, served on demand through devframe's remote-assets back-proxy. The -// definition's `importMetaUrl` (below) supplies the default `resolveFrom`, so a -// locally installed copy (a workspace link here) is served with zero network. -// The page-script bundle (`dist/inject`, below) stays here. -const distDir: RemoteAssets = { - package: `${pkg.name}--assets`, - version: pkg.version, -} - -/** - * Absolute path to the built **page script** module (`dist/inject/inject.js`), - * the dock **client script** the client runtime imports into the host page to - * scan it (its default export boots the page script; importing it does too). - * - * The definition already declares this as its dock `clientScript`, so a hub - * serves it with no host wiring. Exported for hosts that mount the module - * themselves (e.g. via `/@fs/` under Vite). Requires the built bundle - * (`pnpm -C plugins/a11y build`). - */ -export const a11yPageScriptBundlePath: string = fileURLToPath(new URL('../dist/inject/inject.js', import.meta.url)) - -/** @deprecated Renamed; use {@link a11yPageScriptBundlePath}. */ -export const a11yAgentBundlePath: string = a11yPageScriptBundlePath - -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 `/__devframes_plugin_a11y/` so the - * panel iframe shares an origin with the host page it scans. - */ - basePath?: string - /** Preferred standalone CLI port. */ - port?: number - /** - * Rescan on debounced user interaction (mouse/keyboard/touch), on top of the - * DOM MutationObserver. Default `true`. - */ - autoScan?: boolean - /** Log newly-appeared violations to the browser console. Default `true`. */ - logIssues?: boolean - /** - * Auto-pin all of a route's violations the first time it's scanned. - * Default `false`. - */ - defaultHighlight?: boolean - /** axe-core configuration. */ - axe?: { - /** Rule tags to run (defaults to the broadened WCAG 2.0–2.2 + best-practice set). */ - tags?: string[] - /** Extra axe `run` options merged over the defaults. */ - runOptions?: Record - } -} - -/** - * 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 page script over the in-page channel - * (`devframe/in-page-channel`), so the scan/highlight loop works identically in dev - * (live WebSocket RPC) and in a baked static build. - * - * @experimental This plugin is experimental and may change without a major - * version bump until it stabilizes. - */ -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, - importMetaUrl: import.meta.url, - homepage: pkg.homepage, - description: pkg.description, - icon: options.icon ?? 'ph:person-simple-circle-duotone', - basePath: options.basePath ?? BASE_PATH, - /** Declare the page script by path; the hub serves it with no host wiring. */ - dock: { - category: '~builtin', - clientScript: { importFrom: a11yPageScriptBundlePath }, - }, - cli: { - command: id, - port: options.port ?? 9899, - distDir, - }, - setup(ctx) { - setupA11y(ctx, { - dockId: id, - autoScan: options.autoScan, - logIssues: options.logIssues, - defaultHighlight: options.defaultHighlight, - axe: options.axe, - }) - }, - }) -} - -export default createA11yDevframe -export type { Impact, ScanReport, Violation, ViolationNode } from './shared/protocol.ts' diff --git a/plugins/a11y/src/inject/vite.config.ts b/plugins/a11y/src/inject/vite.config.ts deleted file mode 100644 index 93a1ac922..000000000 --- a/plugins/a11y/src/inject/vite.config.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { fileURLToPath } from 'node:url' -import { defineConfig } from 'vite' - -/** - * Builds the page script into a single self-contained ES module - * (`dist/inject/inject.js`) with axe-core bundled in. Loaded by the host app - * via `