Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ permissions:
jobs:
unit-test:
uses: sxzz/workflows/.github/workflows/unit-test.yml@main
with:
lint: pnpm run lint && pnpm run knip

e2e:
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pnpm dev # tsdown --watch
pnpm test # pnpm build && vitest (api snapshot guards against stale dist)
pnpm typecheck # turbo run typecheck (per-package tsc --noEmit)
pnpm lint --fix # ESLint via @antfu/eslint-config
pnpm knip # unused files/dependencies/exports across every workspace
pnpm start # tsx src/index.ts
```

Expand All @@ -33,6 +34,8 @@ The `pnpm test` script intentionally runs `build` first so `tsnapi` snapshots co

`pnpm typecheck` runs `scripts/verify-typecheck-coverage.ts` first — it fails the command (and CI, since CI just runs `pnpm typecheck`) if any workspace package has a `tsconfig.json` but no `typecheck` script, so a new package can't silently join the same blind spot. A package that genuinely can't typecheck yet needs a documented exception in that script, not a missing script.

`pnpm knip` finds unused files, dependencies, and exports across every workspace (config in `knip.jsonc`). It runs against source directly — no prior build needed. Most workspaces need no configuration; `knip.jsonc` only carries per-workspace overrides for cases knip's defaults can't infer on their own: a package's non-`index.ts` `exports` subpaths (knip's package.json→`dist`→`src` source mapping needs a workspace `tsconfig.json` `outDir`, which conflicts with this repo's cross-workspace `src/*.ts` imports, so multi-entry packages list their `exports`-mapped entry files explicitly instead — keep that list in sync with each `tsdown.config.ts`), config files knip's plugins don't discover in a nested location (a Next.js app rooted below the workspace root, `storybook-solidjs-vite` not matching the Storybook plugin trigger), and dependencies referenced dynamically outside its static import graph (icon collections consumed by UnoCSS at build time, plugin packages loaded via a runtime `import()` string). Prefer fixing the underlying gap or a scoped `ignoreDependencies`/`entry` override over a blanket `ignore`.

## Conventions

- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin:<slug>:<fn-name>` (matching the plugin's `@devframes/plugin-<slug>` package name).
Expand Down Expand Up @@ -145,7 +148,7 @@ Range allocation:
## Before PRs

```sh
pnpm lint && pnpm test && pnpm typecheck && pnpm build
pnpm lint && pnpm knip && pnpm test && pnpm typecheck && pnpm build
```

Follow conventional commits (`feat:`, `fix:`, etc.).
Expand Down
2 changes: 0 additions & 2 deletions design/uno.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,3 @@ export const designConfig = defineConfig({
'z-drawer-content': 'z-[90]',
},
})

export default designConfig
2 changes: 1 addition & 1 deletion examples/files-inspector/tests/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { resolve } from 'pathe'
import devframe from '../src/devframe'

const HERE = fileURLToPath(new URL('.', import.meta.url))
export const CLIENT_DIST = resolve(HERE, '../dist/client')
const CLIENT_DIST = resolve(HERE, '../dist/client')

/**
* Asserts the Preact client has been built. Tests boot a dev server
Expand Down
3 changes: 0 additions & 3 deletions examples/json-render/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,5 @@
"@devframes/json-render-ui": "workspace:*",
"cac": "catalog:deps",
"devframe": "workspace:*"
},
"devDependencies": {
"get-port-please": "catalog:deps"
}
}
1 change: 0 additions & 1 deletion examples/next-devframe-hub/src/client/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
'@unocss/postcss': {},
Expand Down
3 changes: 2 additions & 1 deletion examples/next-runtime-snapshot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"devframe": "workspace:*",
"next": "catalog:frontend",
"react": "catalog:frontend",
"react-dom": "catalog:frontend"
"react-dom": "catalog:frontend",
"valibot": "catalog:deps"
},
"devDependencies": {
"@types/react": "catalog:types",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useMemo } from 'react'
// bundle stays free of node-only server code.
const NAMESPACE = 'example:next-runtime-snapshot'

export type SnapshotCtx = DevframeScopedClientContext<typeof NAMESPACE>
type SnapshotCtx = DevframeScopedClientContext<typeof NAMESPACE>

interface ConnectionState {
ctx: SnapshotCtx | null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
'@unocss/postcss': {},
Expand Down
4 changes: 0 additions & 4 deletions examples/next-runtime-snapshot/tests/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { StartedServer } from 'devframe/node'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { DEVFRAME_CONNECTION_META_FILENAME } from 'devframe/constants'
import {
createH3DevframeHost,
Expand All @@ -13,9 +12,6 @@ import { H3 } from 'h3'
import { resolve } from 'pathe'
import devframe from '../src/devframe'

const HERE = fileURLToPath(new URL('.', import.meta.url))
export const CLIENT_DIST = resolve(HERE, '../dist/client')

export interface SnapshotServer extends StartedServer {
basePath: string
}
Expand Down
3 changes: 2 additions & 1 deletion examples/streaming-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"cac": "catalog:deps",
"colorjs.io": "catalog:frontend",
"devframe": "workspace:*",
"preact": "catalog:frontend"
"preact": "catalog:frontend",
"valibot": "catalog:deps"
},
"devDependencies": {
"@preact/preset-vite": "catalog:build",
Expand Down
4 changes: 0 additions & 4 deletions examples/streaming-chat/tests/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { DevframeNodeContext } from 'devframe/types'
import { existsSync } from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import {
DEVFRAME_CONNECTION_META_FILENAME,
} from 'devframe/constants'
Expand All @@ -18,9 +17,6 @@ import { H3 } from 'h3'
import { resolve } from 'pathe'
import devframe from '../src/devframe'

const HERE = fileURLToPath(new URL('.', import.meta.url))
export const CLIENT_DIST = resolve(HERE, '../dist/client')

/**
* Boot the streaming-chat server in-process for tests. Mirrors the
* cli adapter wiring so the WS+HTTP path is exercised end-to-end.
Expand Down
159 changes: 159 additions & 0 deletions knip.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
{
"$schema": "https://unpkg.com/knip@6/schema-jsonc.json",
// `tests/__snapshots__/tsnapi/**` is `tsnapi`'s generated API-snapshot
// fixture content (see `tests/exports.test.ts` and the root AGENTS.md
// "Development" section) — compared byte-for-byte against a fresh build,
// never imported. Not source to analyze.
"ignore": ["tests/__snapshots__/tsnapi/**"],
// `@iconify-json/*` icon collections and `@unocss/preset-icons` are
// resolved by UnoCSS's `presetIcons()` at build time by package presence
// (scanning `i-ph:*` classes against the installed collection), never
// imported in source — the same false-positive class knip's own docs
// describe for icon-collection dependencies.
"ignoreDependencies": ["@iconify-json/.+", "@unocss/preset-icons"],
"workspaces": {
".": {
// `design/*.ts` is the shared design system (AGENTS.md § "Design
// system") consumed by every plugin/example's own `uno.config.ts` and
// `design.ts` via relative imports (`../../design/uno.config`). Those
// are UnoCSS/plugin config files, not part of the reachability graph
// knip builds for "unused files", so the shared source is otherwise
// reported as unused from the root workspace.
//
// `skills/devframe/templates/*.ts` are the devframe-authoring skill's
// copy-and-run reference snippets (`skills/devframe/SKILL.md`) — each
// one a standalone, runnable example, not imported by anything else.
"entry": ["design/*.ts", "skills/devframe/templates/*.ts"],
// `spa-devframe.ts` imports `valibot` to illustrate schema validation
// for a consumer's own devframe — it's a dependency of the example a
// reader copies out, not of this repo's root tooling.
"ignoreDependencies": ["valibot"]
},
"docs": {
// `devframe` is referenced only inside fenced code samples in the
// guide markdown (e.g. `docs/guide/rpc.md`), which knip doesn't parse
// as source — not a real unused dependency.
"ignoreDependencies": ["devframe"]
},
"examples/next-devframe-hub": {
// 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.
"postcss": {
"config": ["src/client/postcss.config.mjs"]
},
// `BUILTIN_PLUGIN_PACKAGES` (`src/client/devframe/next-devframe-hub.ts`)
// lists these as string literals and loads them via a runtime
// `import()` carrying `webpackIgnore`/`turbopackIgnore` comments —
// deliberately invisible to bundlers (and knip) so Next never inlines
// their node-only code. They're genuinely used at runtime.
"ignoreDependencies": [
"@devframes/plugin-code-server",
"@devframes/plugin-git",
"@devframes/plugin-inspect",
"@devframes/plugin-messages",
"@devframes/plugin-terminals"
]
},
"examples/next-runtime-snapshot": {
// Same nested Next.js client root as `next-devframe-hub` above.
"postcss": {
"config": ["src/client/postcss.config.mjs"]
}
},
"packages/devframe": {
// Every `package.json#exports` subpath maps to one of these source
// files (see `tsdown.config.ts`'s `serverEntries`/`clientEntries`).
// Knip's package.json→dist→src source mapping needs `outDir` set on
// the workspace `tsconfig.json`, but this repo's packages resolve
// each other's `src/*.ts` directly across workspaces (via the `paths`
// aliases in `tsconfig.base.json`), and `outDir` breaks `tsc`'s
// `rootDir` inference for every consumer that does — so entries are
// listed explicitly instead. Keep this in sync with the `exports` map.
"entry": [
"src/{index,constants}.ts",
"src/helpers/vite.ts",
"src/adapters/{build,cac,cli,dev,embedded}.ts",
"src/adapters/mcp/index.ts",
"src/client/index.ts",
"src/node/index.ts",
"src/node/{auth,hub-internals}/index.ts",
"src/recipes/{common-rpc-functions,interactive-auth,open-helpers}.ts",
"src/rpc/{index,client,server}.ts",
"src/rpc/dump/index.ts",
"src/rpc/transports/{ws-client,ws-server}.ts",
"src/types/index.ts",
"src/utils/*.ts"
]
},
"packages/hub": {
"entry": ["src/{index,constants}.ts", "src/{client,node,types}/index.ts"]
},
"packages/json-render": {
// `src/node/index.ts` is already picked up via `tsdown.config.ts`
// (its literal `entry` object parses cleanly); only `core.ts`/`hub.ts`
// need to be declared explicitly.
"entry": ["src/{index,core,hub}.ts"]
},
"packages/nuxt": {
// `@nuxt/kit`'s own `defineNuxtModule` return type resolves through
// `@nuxt/schema`; tsdown's dts bundler inlines that as a lazy
// `import("@nuxt/schema")` type reference in the built
// `dist/module.d.mts` (visible only once this workspace has been
// built locally) — a transitive type-only leak from a dependency's
// own dependency, not a real gap in this package's own manifest.
"ignoreDependencies": ["@nuxt/schema"]
},
"packages/json-render-ui": {
// `src/components/index.ts` is already picked up via
// `tsdown.config.ts`; only `spa.ts` needs to be declared explicitly.
"entry": ["src/{index,spa}.ts"],
// The standalone SPA's own Vite config (`src/spa/vite.config.ts`)
// mounts `unocss/vite` with no explicit config path, so UnoCSS
// discovers this nested `uno.config.ts` by directory proximity to
// `root` — not an import knip's graph can see. Keep the package-root
// config the Storybook build uses alongside it.
"unocss": {
"config": ["uno.config.ts", "src/spa/uno.config.ts"]
}
},
// Every built-in plugin ships the same entry shape (see each
// `tsdown.config.ts`'s `serverEntries`/`clientEntries`): a root
// `index.ts`, an optional `cli.ts`/`vite.ts`/`constants.ts`/`types.ts`,
// and `client|node|rpc|inject|engine|registry/index.ts` subdirectories.
// One shared pattern covers them all — see `packages/devframe` above for
// why this is explicit instead of relying on source mapping.
"plugins/*": {
"entry": [
"src/{index,cli,vite,constants,types}.ts",
"src/{client,node,rpc,inject,engine,registry}/index.ts"
]
},
"plugins/a11y": {
// `storybook-solidjs-vite` (not an official `@storybook/*` framework
// package) doesn't match knip's Storybook plugin trigger, so it never
// auto-enables here. Mirror the plugin's own defaults by hand.
"storybook": {
"config": [".storybook/main.ts"],
"entry": [".storybook/preview.tsx"],
"project": [".storybook/**/*.{js,jsx,ts,tsx,mts}"]
},
// A workspace-specific `entry` replaces (rather than merges with) the
// `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"
]
},
"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.
"postcss": {
"config": ["src/client/postcss.config.mjs"]
}
}
}
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"storybook": "pnpm -C storybook dev",
"storybook:build": "pnpm --filter @devframes/storybook run storybook:build",
"lint": "eslint --cache",
"knip": "knip",
"test": "pnpm run build && vitest",
"test:e2e": "pnpm run build && playwright test",
"test:e2e:ui": "pnpm run build && playwright test --ui",
Expand All @@ -34,20 +35,21 @@
"postinstall": "npx simple-git-hooks && skills-npm"
},
"devDependencies": {
"@antfu/design": "catalog:frontend",
"@antfu/eslint-config": "catalog:tooling",
"@antfu/ni": "catalog:build",
"@antfu/utils": "catalog:inlined",
"@playwright/test": "catalog:testing",
"@types/node": "catalog:types",
"@types/prompts": "catalog:types",
"@types/ws": "catalog:types",
"bumpp": "catalog:tooling",
"eslint": "catalog:tooling",
"knip": "catalog:tooling",
"nano-staged": "catalog:tooling",
"pathe": "catalog:deps",
"prompts": "catalog:tooling",
"simple-git-hooks": "catalog:tooling",
"skills-npm": "catalog:tooling",
"tsdown": "catalog:build",
"tsnapi": "catalog:testing",
"tsx": "catalog:build",
"turbo": "catalog:build",
Expand Down
2 changes: 1 addition & 1 deletion packages/devframe/src/client/static-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface StaticRpcManifestQueryEntry {
serialization?: StaticRpcSerialization
}

export type StaticRpcManifestEntry
type StaticRpcManifestEntry
= | StaticRpcManifestStaticEntry
| StaticRpcManifestQueryEntry
| any
Expand Down
2 changes: 0 additions & 2 deletions packages/devframe/src/node/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { agentListResources } from './agent-list-resources'
import { agentListTools } from './agent-list-tools'
import { agentReadResource } from './agent-read-resource'

export { agentInvokeTool, agentListResources, agentListTools, agentReadResource }

/**
* Built-in agent introspection RPC functions. Registered automatically
* by `createHostContext`. Not themselves agent-exposed (no `agent`
Expand Down
1 change: 0 additions & 1 deletion packages/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"devframe": "workspace:*"
},
"dependencies": {
"birpc": "catalog:deps",
"destr": "catalog:deps",
"nostics": "catalog:deps",
"pathe": "catalog:deps",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-render-ui/src/components/_unsupported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { JrComponent } from './_shared'
import { h } from 'vue'

/** Format a list of prop keys as a compact gist, e.g. `{ foo, bar }`. */
export function formatPropKeys(keys: readonly string[]): string {
function formatPropKeys(keys: readonly string[]): string {
return keys.length ? `{ ${keys.join(', ')} }` : '{}'
}

Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/inject/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { A11Y_DEFAULT_DOCK_ID } from '../shared/protocol.ts'
* (deep-linking via `params`). Kept as a discriminated union so future action
* kinds can be added without reshaping the field.
*/
export interface HubMessageAction {
interface HubMessageAction {
id: string
label: string
kind: 'activate'
Expand Down
2 changes: 1 addition & 1 deletion plugins/a11y/src/inject/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IMPACT_COLOR } from '../shared/protocol.ts'
const PREFERS_REDUCED_MOTION
= typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches

export interface HighlightInfo {
interface HighlightInfo {
ruleId: string
impact: Impact
}
Expand Down
Loading
Loading