From f0d13f9897ee0dcd9083d855ef4e0ea0249ac850 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:18:35 +0100 Subject: [PATCH] feat(devtools): add devtools --- pnpm-lock.yaml | 41 ++++++++++ .../packages/devtools/README.md | 28 +++++++ .../packages/devtools/package.json | 40 +++++++++ .../packages/devtools/src/globals.d.ts | 9 +++ .../packages/devtools/src/icon.svg | 1 + .../packages/devtools/src/mod.ts | 68 ++++++++++++++++ .../packages/devtools/src/styles.css | 81 +++++++++++++++++++ .../packages/devtools/tsconfig.json | 24 ++++++ .../packages/devtools/tsup.config.ts | 10 +++ .../packages/devtools/turbo.json | 15 ++++ .../packages/rivetkit/src/client/config.ts | 9 +++ .../packages/rivetkit/src/client/mod.ts | 6 ++ .../packages/rivetkit/src/devtools/mod.ts | 31 +++++++ .../packages/rivetkit/src/registry/mod.ts | 1 + .../packages/rivetkit/tsup.config.ts | 8 ++ .../packages/rivetkit/turbo.json | 2 +- 16 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 rivetkit-typescript/packages/devtools/README.md create mode 100644 rivetkit-typescript/packages/devtools/package.json create mode 100644 rivetkit-typescript/packages/devtools/src/globals.d.ts create mode 100644 rivetkit-typescript/packages/devtools/src/icon.svg create mode 100644 rivetkit-typescript/packages/devtools/src/mod.ts create mode 100644 rivetkit-typescript/packages/devtools/src/styles.css create mode 100644 rivetkit-typescript/packages/devtools/tsconfig.json create mode 100644 rivetkit-typescript/packages/devtools/tsup.config.ts create mode 100644 rivetkit-typescript/packages/devtools/turbo.json create mode 100644 rivetkit-typescript/packages/rivetkit/src/devtools/mod.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 099dc54305..08b39fc9bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2184,6 +2184,18 @@ importers: specifier: ^3.1.1 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.1)(less@4.4.1)(lightningcss@1.30.2)(sass@1.93.2)(stylus@0.62.0)(terser@5.44.0) + rivetkit-typescript/packages/devtools: + devDependencies: + rivetkit: + specifier: workspace:* + version: link:../rivetkit + tsup: + specifier: ^8.4.0 + version: 8.5.0(@microsoft/api-extractor@7.53.2(@types/node@24.10.1))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1) + typescript: + specifier: ^5.5.2 + version: 5.9.3 + rivetkit-typescript/packages/framework-base: dependencies: '@tanstack/store': @@ -27203,6 +27215,35 @@ snapshots: - tsx - yaml + tsup@8.5.0(@microsoft/api-extractor@7.53.2(@types/node@24.10.1))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.9) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.1 + esbuild: 0.25.9 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.1) + resolve-from: 5.0.0 + rollup: 4.50.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + optionalDependencies: + '@microsoft/api-extractor': 7.53.2(@types/node@24.10.1) + postcss: 8.5.6 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsup@8.5.0(@microsoft/api-extractor@7.53.2(@types/node@24.7.1))(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(typescript@5.9.2)(yaml@2.8.1): dependencies: bundle-require: 5.1.0(esbuild@0.25.9) diff --git a/rivetkit-typescript/packages/devtools/README.md b/rivetkit-typescript/packages/devtools/README.md new file mode 100644 index 0000000000..da73e2d45c --- /dev/null +++ b/rivetkit-typescript/packages/devtools/README.md @@ -0,0 +1,28 @@ +# RivetKit DevTools + + +## Contributing + +To contribute to the RivetKit DevTools package, please follow these steps: + +1. Set up assets server for the `dist` folder: + ```bash + pnpm dlx serve dist + ``` + +2. Set your `CUSTOM_RIVETKIT_DEVTOOLS_URL` environment variable to point to the assets server (default is `http://localhost:3000`): + ```bash + export CUSTOM_RIVETKIT_DEVTOOLS_URL=http://localhost:5000 + ``` + + This will ensure that the RivetKit will use local devtool assets instead of fetching them from the CDN. + +3. In another terminal, run the development build: + ```bash + pnpm dev + ``` + + or run the production build: + ```bash + pnpm build + ``` diff --git a/rivetkit-typescript/packages/devtools/package.json b/rivetkit-typescript/packages/devtools/package.json new file mode 100644 index 0000000000..11fbe69829 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/package.json @@ -0,0 +1,40 @@ +{ + "name": "@rivetkit/devtools", + "private": true, + "version": "2.0.24-rc.1", + "description": "RivetKit DevTools - A set of development tools for RivetKit", + "license": "Apache-2.0", + "keywords": [ + "rivetkit" + ], + "sideEffects": [ + "./dist/chunk-*.js", + "./dist/chunk-*.cjs" + ], + "files": [ + "dist", + "package.json" + ], + "exports": { + ".": { + "import": { + "types": "./dist/mod.d.mts", + "default": "./dist/mod.mjs" + }, + "require": { + "types": "./dist/mod.d.ts", + "default": "./dist/mod.js" + } + } + }, + "scripts": { + "build": "tsup src/mod.ts", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "tsup": "^8.4.0", + "typescript": "^5.5.2", + "rivetkit": "workspace:*" + }, + "stableVersion": "0.8.0" +} diff --git a/rivetkit-typescript/packages/devtools/src/globals.d.ts b/rivetkit-typescript/packages/devtools/src/globals.d.ts new file mode 100644 index 0000000000..84210ac866 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/src/globals.d.ts @@ -0,0 +1,9 @@ +declare module "*.css" { + const content: string; + export default content; +} + +declare module "*.svg" { + const content: string; + export default content; +} diff --git a/rivetkit-typescript/packages/devtools/src/icon.svg b/rivetkit-typescript/packages/devtools/src/icon.svg new file mode 100644 index 0000000000..287c425791 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/src/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rivetkit-typescript/packages/devtools/src/mod.ts b/rivetkit-typescript/packages/devtools/src/mod.ts new file mode 100644 index 0000000000..381c6c30ab --- /dev/null +++ b/rivetkit-typescript/packages/devtools/src/mod.ts @@ -0,0 +1,68 @@ +import svg from "./icon.svg"; +import styles from "./styles.css"; + +declare global { + interface Window { + _rivetkit_devtools_configs?: Array< + Parameters[0] + >; + } +} + +const root = document.createElement("div"); + +root.id = "rivetkit-devtools"; +const shadow = root.attachShadow({ mode: "open" }); + +const div = document.createElement("div"); + +const img = document.createElement("img"); +img.src = svg; +div.appendChild(img); + +const btn = document.createElement("button"); +btn.appendChild(div); + +const tooltip = document.createElement("div"); +tooltip.className = "tooltip"; +tooltip.textContent = "Open Inspector"; + +const style = document.createElement("style"); +style.textContent = styles; +shadow.appendChild(style); +shadow.appendChild(btn); +shadow.appendChild(tooltip); + +btn.addEventListener("mouseenter", () => { + tooltip.classList.add("visible"); +}); + +btn.addEventListener("mouseleave", () => { + tooltip.classList.remove("visible"); +}); + +btn.addEventListener("click", () => { + const config = window._rivetkit_devtools_configs?.[0]; + if (!config || typeof config !== "object") { + console.error("RivetKit Devtools: No client config found"); + return; + } + const url = new URL("https://inspect.rivet.dev/"); + if (!config.endpoint) { + console.error("RivetKit Devtools: No endpoint found in client config"); + return; + } + url.searchParams.set("u", config.endpoint); + if (config.token) { + url.searchParams.set("t", config.token); + } + if (config.namespace) { + url.searchParams.set("ns", config.namespace); + } + if (config.runnerName) { + url.searchParams.set("r", config.runnerName); + } + window.open(url.toString(), "_blank"); +}); + +document.body.appendChild(root); diff --git a/rivetkit-typescript/packages/devtools/src/styles.css b/rivetkit-typescript/packages/devtools/src/styles.css new file mode 100644 index 0000000000..a4a703a8e2 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/src/styles.css @@ -0,0 +1,81 @@ +:host { + all: initial; + position: fixed; + bottom: 12px; + right: 25px; + bottom: 25px; + z-index: 2147483647; + width: 48px; + height: 48px; +} + +:host * { + box-sizing: border-box; +} + +button { + all: unset; + width: 100%; + height: 100%; + cursor: pointer; + border-radius: 50%; + border: 1px solid #171717; + background-color: #09090b; + transition: background-color .3s ease; +} + +button:hover { + background-color: #0e0e11; +} + +button div { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + border: 1px solid #292525; +} + +img { + width: 28px; + height: 28px; + object-fit: contain; +} + +.tooltip { + position: absolute; + right: 60px; + bottom: 50%; + transform: translateY(50%); + background-color: #09090b; + color: #fafafa; + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity .3s ease; + border: 1px solid #27272a; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); +} + +.tooltip::after { + content: ""; + position: absolute; + right: -5px; + top: 50%; + transform: translateY(-50%) rotate(45deg); + width: 8px; + height: 8px; + background-color: #09090b; + border-right: 1px solid #27272a; + border-top: 1px solid #27272a; +} + +.tooltip.visible { + opacity: 1; +} \ No newline at end of file diff --git a/rivetkit-typescript/packages/devtools/tsconfig.json b/rivetkit-typescript/packages/devtools/tsconfig.json new file mode 100644 index 0000000000..e1804bad2b --- /dev/null +++ b/rivetkit-typescript/packages/devtools/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "lib"] +} diff --git a/rivetkit-typescript/packages/devtools/tsup.config.ts b/rivetkit-typescript/packages/devtools/tsup.config.ts new file mode 100644 index 0000000000..b469e5af65 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsup"; +import defaultConfig from "../../../tsup.base.ts"; + +export default defineConfig({ + ...defaultConfig, + loader: { + ".svg": "dataurl", + ".css": "text", + }, +}); diff --git a/rivetkit-typescript/packages/devtools/turbo.json b/rivetkit-typescript/packages/devtools/turbo.json new file mode 100644 index 0000000000..ba682d5df2 --- /dev/null +++ b/rivetkit-typescript/packages/devtools/turbo.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "inputs": [ + "src/**", + "tsconfig.json", + "tsup.config.ts", + "package.json" + ], + "outputs": ["dist/**"] + } + } +} diff --git a/rivetkit-typescript/packages/rivetkit/src/client/config.ts b/rivetkit-typescript/packages/rivetkit/src/client/config.ts index 97f9f8d269..149db9efde 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/config.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/config.ts @@ -39,6 +39,15 @@ export const ClientConfigSchema = z.object({ /** Whether to automatically perform health checks when the client is created. */ disableMetadataLookup: z.boolean().optional().default(false), + + /** Whether to enable RivetKit Devtools integration. */ + devtools: z + .boolean() + .default( + () => + typeof globalThis.window !== "undefined" && + window?.location?.hostname === "localhost", + ), }); export type ClientConfig = z.infer; diff --git a/rivetkit-typescript/packages/rivetkit/src/client/mod.ts b/rivetkit-typescript/packages/rivetkit/src/client/mod.ts index d4b8d27766..e44c25ca0f 100644 --- a/rivetkit-typescript/packages/rivetkit/src/client/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/client/mod.ts @@ -1,3 +1,4 @@ +import { injectDevtools } from "@/devtools/mod"; import type { Registry } from "@/registry/mod"; import { RemoteManagerDriver } from "@/remote-manager-driver/mod"; import { @@ -55,5 +56,10 @@ export function createClient>( // Create client const driver = new RemoteManagerDriver(config); + + if (config.devtools) { + injectDevtools(config); + } + return createClientWithDriver(driver, config); } diff --git a/rivetkit-typescript/packages/rivetkit/src/devtools/mod.ts b/rivetkit-typescript/packages/rivetkit/src/devtools/mod.ts new file mode 100644 index 0000000000..13244ac12d --- /dev/null +++ b/rivetkit-typescript/packages/rivetkit/src/devtools/mod.ts @@ -0,0 +1,31 @@ +/// + +declare global { + interface Window { + _rivetkit_devtools_configs?: ClientConfigInput[]; + } + // injected via tsup config + var CUSTOM_RIVETKIT_DEVTOOLS_URL: string | undefined; +} + +import type { ClientConfigInput } from "@/client/client"; +import { VERSION } from "@/utils"; + +const DEVTOOLS_URL = (version = VERSION) => + `https://releases.rivet.gg/devtools/${version}/rivetkit-devtools.js`; + +const scriptId = "rivetkit-devtools-script"; + +export function injectDevtools(config: ClientConfigInput) { + if (!document.getElementById(scriptId)) { + const script = document.createElement("script"); + script.id = scriptId; + script.src = globalThis.CUSTOM_RIVETKIT_DEVTOOLS_URL || DEVTOOLS_URL(); + script.async = true; + document.head.appendChild(script); + } + + window._rivetkit_devtools_configs = window._rivetkit_devtools_configs || []; + window._rivetkit_devtools_configs.push(config); + return; +} diff --git a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts index 24622a5518..3725caab8a 100644 --- a/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts +++ b/rivetkit-typescript/packages/rivetkit/src/registry/mod.ts @@ -291,6 +291,7 @@ async function configureServerlessRunner(config: RunnerConfig): Promise { headers: config.headers, getUpgradeWebSocket: config.getUpgradeWebSocket, disableMetadataLookup: true, // We don't need health check for this operation + devtools: false, }; // Fetch all datacenters diff --git a/rivetkit-typescript/packages/rivetkit/tsup.config.ts b/rivetkit-typescript/packages/rivetkit/tsup.config.ts index d8652c0151..e8e9cec121 100644 --- a/rivetkit-typescript/packages/rivetkit/tsup.config.ts +++ b/rivetkit-typescript/packages/rivetkit/tsup.config.ts @@ -1,7 +1,15 @@ +/// + import { defineConfig } from "tsup"; import defaultConfig from "../../../tsup.base.ts"; export default defineConfig({ ...defaultConfig, outDir: "dist/tsup/", + define: { + "globalThis.CUSTOM_RIVETKIT_DEVTOOLS_URL": process.env + .CUSTOM_RIVETKIT_DEVTOOLS_URL + ? `"${process.env.CUSTOM_RIVETKIT_DEVTOOLS_URL}"` + : "false", + }, }); diff --git a/rivetkit-typescript/packages/rivetkit/turbo.json b/rivetkit-typescript/packages/rivetkit/turbo.json index 8b13573eb9..8f63714485 100644 --- a/rivetkit-typescript/packages/rivetkit/turbo.json +++ b/rivetkit-typescript/packages/rivetkit/turbo.json @@ -35,7 +35,7 @@ "package.json" ], "outputs": ["dist/**"], - "env": ["FAST_BUILD"] + "env": ["FAST_BUILD", "CUSTOM_RIVETKIT_DEVTOOLS_URL"] }, "test": { "dependsOn": ["^test", "build"],