diff --git a/.opencode/skills/local-explorer/SKILL.md b/.opencode/skills/local-explorer/SKILL.md new file mode 100644 index 0000000000..b7f6bf5111 --- /dev/null +++ b/.opencode/skills/local-explorer/SKILL.md @@ -0,0 +1,74 @@ +--- +name: local-explorer +description: How to add products/resources to the local explorer or local API. Use when implementing new local APIs, or UI routes under packages/miniflare/src/workers/local-explorer or packages/local-explorer-ui. +--- + +# Cloudflare Local Explorer Products + +Use this skill when adding a new product or resource type to the local API and/or local explorer. + +## Start Here + +Read these files before editing: + +- `packages/miniflare/src/workers/local-explorer/explorer.worker.ts` +- `packages/miniflare/src/plugins/core/explorer.ts` +- `packages/miniflare/src/plugins/core/types.ts` +- One existing resource implementation in `packages/miniflare/src/workers/local-explorer/resources/`, preferably the product most similar to the new one +- One matching test in `packages/miniflare/test/plugins/local-explorer/` + +If there are UI changes, also read: + +- `packages/local-explorer-ui/src/components/Sidebar.tsx` +- Existing route files under `packages/local-explorer-ui/src/routes/` +- Existing product e2e tests under `packages/local-explorer-ui/src/__e2e__/` + +## Workflow + +1. Add the API surface to `packages/miniflare/scripts/openapi-filter-config.ts`. +2. Generate Miniflare's filtered spec and backend types from a full Cloudflare OpenAPI spec: + +```bash +OPENAPI_INPUT_PATH= pnpm --dir packages/miniflare generate:api +``` + +3. Inspect `packages/miniflare/src/workers/local-explorer/openapi.local.json` and generated types. If the generated schemas include fields local explorer will not support, add ignores in `openapi-filter-config.ts` and regenerate. +4. The explorer worker should have access to all user resource bindings. Ensure `proxyBindings` include bindings to the new product and that `getExplorerServices()` exposes any extra bindings the explorer worker needs. Wire product bindings through `constructExplorerBindingMap()` and `constructExplorerWorkerOpts()` in `packages/miniflare/src/plugins/core/explorer.ts`. +5. Add or extend resource binding metadata in `packages/miniflare/src/plugins/core/types.ts`. +6. Implement handlers in `packages/miniflare/src/workers/local-explorer/resources/.ts`. Make sure to account for cross-instance aggregation, if applicable. +7. Register Hono routes in `packages/miniflare/src/workers/local-explorer/explorer.worker.ts`. +8. Validate request bodies and query params with generated Zod schemas from `generated/zod.gen.ts` using `validateRequestBody()` and `validateQuery()`. +9. Return Cloudflare API envelope responses using `wrapResponse()` and `errorResponse()` from `common.ts` unless an existing endpoint for that product uses a different response shape. +10. Add Miniflare tests in `packages/miniflare/test/plugins/local-explorer/.spec.ts`. +11. Regenerate the UI API client: + +```bash +pnpm --dir packages/local-explorer-ui build +``` + +12. Add UI routes/components. Use Kumo components for new UI. See https://github.com/cloudflare/kumo/blob/main/AGENTS.md. +13. Add Playwright e2e tests under `packages/local-explorer-ui/src/__e2e__//` for new visible product flows. + +## OpenAPI Rules + +- Do not edit generated files like `packages/miniflare/src/workers/local-explorer/openapi.local.json` or `packages/miniflare/src/workers/local-explorer/generated/` directly. +- Prefer upstream Cloudflare API paths when a public API exists. +- Use `extensions.paths` in `openapi-filter-config.ts` only for local-only APIs or APIs that do not exist in the public Cloudflare API. +- Add ignores for unsupported params, headers, request body properties, and response fields rather than pretending to support them. + +## Backend Patterns + +- Local list endpoints such as listing KV namespaces should not implement pagination as this may require cross-instance aggregation. Pagination should be supported when targeting individual resources, such as listing KV keys within a specific namespace. +- For cross-worker aggregation, use `aggregateListResults()`, `getPeerUrlsIfAggregating()`, and `fetchFromPeer()` from `aggregation.ts`; do not hand-roll peer discovery. Add tests for both local-only behavior and aggregated behavior when the product can span multiple instances. +- If an API needs direct filesystem access, call through the loopback service (`c.env.MINIFLARE_LOOPBACK`) to a Node.js endpoint. The local explorer API runs inside workerd, so it cannot access the host filesystem directly. +- If an endpoint needs metadata that is not available on the runtime binding itself, put that metadata in `BindingIdMap` and pass it through `CoreBindings.JSON_LOCAL_EXPLORER_BINDING_MAP`. +- If a product should appear in `/api/local/workers`, add it to `WorkerResourceBindings` and populate it in `constructExplorerWorkerOpts()`. + +## UI Patterns + +- The UI API client is generated from `packages/miniflare/src/workers/local-explorer/openapi.local.json` into `packages/local-explorer-ui/src/api/generated/`. +- Sidebar resources come from `/api/local/workers`; update `LocalExplorerWorkerBindings` usage and `Sidebar.tsx` when the product should appear in navigation. +- Add route files under `packages/local-explorer-ui/src/routes/`. TanStack Router regenerates `src/routeTree.gen.ts` during UI build/dev. +- Preserve worker selection by carrying the `worker` search param through product links when following sidebar patterns. +- Use Kumo for new UI components wherever possible. Do not introduce a parallel component system. +- Do not use tailwindCSS color tokens, use Kumo color tokens instead. diff --git a/.opencode/skills/local-explorer/assets/local-explorer-diagram.md b/.opencode/skills/local-explorer/assets/local-explorer-diagram.md new file mode 100644 index 0000000000..d78d55028f --- /dev/null +++ b/.opencode/skills/local-explorer/assets/local-explorer-diagram.md @@ -0,0 +1,57 @@ +```mermaid +flowchart TB + Client["client"] --> Incoming["incoming request"] + Incoming --> EntryA["entry worker"] + EntryA -->|/cdn-cgi/explorer| ExplorerA["explorer worker
(Hono)"] + + subgraph Runtime["running Miniflare instances"] + direction LR + + subgraph A["miniflare A"] + direction TB + EntryA + ExplorerA + Frontend["frontend
(TanStack Router + React)"] + UserWorker["user worker"] + KV["KV, R2, D1"] + Wrapper["wrapped
DO/Workflow class"] + + EntryA --> UserWorker + ExplorerA -->|disk service to serve assets| Frontend + ExplorerA -->|binding| KV + ExplorerA -->|binding| Wrapper + UserWorker -->|binding| KV + UserWorker -->|binding| Wrapper + end + + subgraph B["miniflare B"] + direction TB + EntryB["entry worker"] + ExplorerB["explorer worker"] + Etc["etc."] + + EntryB --> ExplorerB + ExplorerB --> Etc + end + end + + Frontend -->|fetch /cdn-cgi/explorer/api| Incoming + ExplorerA -->|fetch /cdn-cgi/explorer/api/resource with NO_AGGREGATE_HEADER| EntryB + + subgraph FS["filesystem"] + Registry["dev registry"] + DOState[".wrangler/state/durable-objects
(list DOs, delete workflows, etc.)"] + end + + ExplorerA -->|node loopback binding| FS + + classDef miniflareA stroke:#f08c00,fill:#fff7ed,color:#1e1e1e; + classDef userResource stroke:#1971c2,fill:#eff6ff,color:#1e1e1e; + classDef entry stroke:#e03131,fill:#fff5f5,color:#1e1e1e; + classDef neutral stroke:#1e1e1e,fill:#ffffff,color:#1e1e1e; + + class EntryA entry; + class ExplorerA,Frontend,Wrapper miniflareA; + class UserWorker,KV userResource; + class Client,Incoming,Runtime,FS,Registry,DOState,EntryB,ExplorerB,Etc neutral; +``` diff --git a/packages/miniflare/scripts/check-generate-api.ts b/packages/miniflare/scripts/check-generate-api.ts index 124278dc40..3f75972d17 100644 --- a/packages/miniflare/scripts/check-generate-api.ts +++ b/packages/miniflare/scripts/check-generate-api.ts @@ -64,7 +64,7 @@ async function main(): Promise { "\n" + ` OPENAPI_INPUT_PATH= pnpm -F miniflare generate:api\n` + "\n" + - "See packages/miniflare/src/workers/local-explorer/README.md for details.\n" + + "See .opencode/skills/local-explorer/SKILL.md for details.\n" + `The CI check uses the spec pinned at commit ${OPENAPI_COMMIT}.\n` ); process.exit(1); diff --git a/packages/miniflare/src/workers/local-explorer/README.md b/packages/miniflare/src/workers/local-explorer/README.md deleted file mode 100644 index ff498022e5..0000000000 --- a/packages/miniflare/src/workers/local-explorer/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Adding new APIs to explorer worker - -1. Download the full Cloudflare OpenAPI Spec from https://github.com/cloudflare/api-schemas. -2. Add the new API to `miniflare/scripts/openapi-filter-config.ts`. -3. Run `OPENAPI_INPUT_PATH= pnpm generate:api` to filter and generate types. Confirm the filtered API is as expected, and add ignores to `openapi-filter-config.ts` if necessary. -4. The explorer should have access to all user resource bindings. This is done by adding `proxyBindings` to the explorer worker in `getGlobalServices()` in the core plugin. You may also have to add entries to `CoreBindings.JSON_LOCAL_EXPLORER_BINDING_MAP` if you need to access resource config such as IDs or database names which aren't available at runtime on the binding itself. -5. Implement the APIs in - `miniflare/src/workers/local-explorer/` using these bindings. You will have to register the routes in `explorer.worker.ts` and add handlers in `/local-explorer/resources/`. -6. Add tests for your API endpoint in `miniflare/tests/plugins/local-explorer/`. -7. Regenerate the UI's API client by running `pnpm build` in `packages/local-explorer-ui`. -8. Make any UI changes using your new API. The built output of the UI will be bundled into Miniflare when Miniflare is built.