@forinda/kickjs-devtools 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,110 +1,3 @@
1
- import { AppAdapter, Ref, ComputedRef, Container, AdapterMiddleware } from '@forinda/kickjs-core';
2
-
3
- /** Per-route latency stats */
4
- interface RouteStats {
5
- count: number;
6
- totalMs: number;
7
- minMs: number;
8
- maxMs: number;
9
- }
10
- interface DevToolsOptions {
11
- /** Base path for debug endpoints (default: '/_debug') */
12
- basePath?: string;
13
- /** Only enable when this is true (default: process.env.NODE_ENV !== 'production') */
14
- enabled?: boolean;
15
- /** Include environment variables (sanitized) at /_debug/config (default: false) */
16
- exposeConfig?: boolean;
17
- /** Env var prefixes to expose (default: ['APP_', 'NODE_ENV']). Others are redacted. */
18
- configPrefixes?: string[];
19
- /** Callback when error rate exceeds threshold */
20
- onErrorRateExceeded?: (rate: number) => void;
21
- /** Error rate threshold (default: 0.5 = 50%) */
22
- errorRateThreshold?: number;
23
- /** Other adapters to discover stats from (e.g., WsAdapter) */
24
- adapters?: any[];
25
- /**
26
- * Secret token to guard DevTools access. When set, all requests must
27
- * include this token as `x-devtools-token` header or `?token=` query param.
28
- *
29
- * Auto-generated on startup if not provided. The token is logged to the console.
30
- * Set to `false` to disable the guard entirely (not recommended).
31
- *
32
- * @example
33
- * ```ts
34
- * new DevToolsAdapter({ secret: process.env.DEVTOOLS_SECRET })
35
- * ```
36
- */
37
- secret?: string | false;
38
- }
39
- /**
40
- * DevToolsAdapter — Vue-style reactive introspection for KickJS applications.
41
- *
42
- * Exposes debug endpoints powered by reactive state (ref, computed, watch):
43
- * - `GET /_debug/routes` — all registered routes with middleware
44
- * - `GET /_debug/container` — DI registry with scopes and instantiation status
45
- * - `GET /_debug/metrics` — live request/error counts, error rate, uptime
46
- * - `GET /_debug/health` — deep health check with adapter status
47
- * - `GET /_debug/config` — sanitized environment variables (opt-in)
48
- * - `GET /_debug/state` — full reactive state snapshot
49
- *
50
- * @example
51
- * ```ts
52
- * import { DevToolsAdapter } from '@forinda/kickjs-devtools'
53
- *
54
- * bootstrap({
55
- * modules: [UserModule],
56
- * adapters: [
57
- * new DevToolsAdapter({
58
- * enabled: process.env.NODE_ENV !== 'production',
59
- * exposeConfig: true,
60
- * configPrefixes: ['APP_', 'DATABASE_'],
61
- * }),
62
- * ],
63
- * })
64
- * ```
65
- */
66
- declare class DevToolsAdapter implements AppAdapter {
67
- readonly name = "DevToolsAdapter";
68
- private basePath;
69
- private enabled;
70
- private exposeConfig;
71
- private configPrefixes;
72
- private errorRateThreshold;
73
- private secret;
74
- /** Total requests received */
75
- readonly requestCount: Ref<number>;
76
- /** Total responses with status >= 500 */
77
- readonly errorCount: Ref<number>;
78
- /** Total responses with status >= 400 and < 500 */
79
- readonly clientErrorCount: Ref<number>;
80
- /** Server start time */
81
- readonly startedAt: Ref<number>;
82
- /** Computed error rate (server errors / total requests) */
83
- readonly errorRate: ComputedRef<number>;
84
- /** Computed uptime in seconds */
85
- readonly uptimeSeconds: ComputedRef<number>;
86
- /** Per-route latency tracking */
87
- readonly routeLatency: Record<string, RouteStats>;
88
- private routes;
89
- private container;
90
- private appRef;
91
- private adapterStatuses;
92
- private stopErrorWatch;
93
- private peerAdapters;
94
- constructor(options?: DevToolsOptions);
95
- /**
96
- * Resolve peer adapters at request time. Prefers live adapters from the
97
- * Application registry (survives HMR rebuild) and falls back to the
98
- * constructor-provided refs.
99
- */
100
- private getPeerAdapters;
101
- beforeMount(app: any, container: Container): void;
102
- middleware(): AdapterMiddleware[];
103
- onRouteMount(controllerClass: any, mountPath: string): void;
104
- afterStart(_server: any, _container: Container): void;
105
- shutdown(): void;
106
- /** Find the public/devtools directory relative to the built dist or source */
107
- private resolvePublicDir;
108
- }
109
-
110
- export { DevToolsAdapter, type DevToolsOptions };
1
+ import 'reflect-metadata';
2
+ export { DevToolsAdapter, type DevToolsOptions } from './adapter';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AAEzB,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -1 +1,205 @@
1
- var R=Object.defineProperty;var p=(v,t)=>R(v,"name",{value:t,configurable:!0});import"reflect-metadata";import{Router as y,static as E}from"express";import{dirname as S,join as c}from"path";import{fileURLToPath as D}from"url";import{existsSync as C,readFileSync as M}from"fs";import{randomBytes as x}from"crypto";import{METADATA as m,ref as l,computed as b,reactive as P,watch as A,createLogger as w}from"@forinda/kickjs-core";var h=w("DevTools"),g=class{static{p(this,"DevToolsAdapter")}name="DevToolsAdapter";basePath;enabled;exposeConfig;configPrefixes;errorRateThreshold;secret;requestCount;errorCount;clientErrorCount;startedAt;errorRate;uptimeSeconds;routeLatency;routes=[];container=null;appRef=null;adapterStatuses={};stopErrorWatch=null;peerAdapters=[];constructor(t={}){if(this.basePath=t.basePath??"/_debug",this.enabled=t.enabled??process.env.NODE_ENV!=="production",this.exposeConfig=t.exposeConfig??!1,this.configPrefixes=t.configPrefixes??["APP_","NODE_ENV"],this.errorRateThreshold=t.errorRateThreshold??.5,this.peerAdapters=t.adapters??[],t.secret===!1?this.secret=!1:t.secret?this.secret=t.secret:this.secret=x(16).toString("hex"),this.requestCount=l(0),this.errorCount=l(0),this.clientErrorCount=l(0),this.startedAt=l(Date.now()),this.routeLatency=P({}),this.errorRate=b(()=>this.requestCount.value>0?this.errorCount.value/this.requestCount.value:0),this.uptimeSeconds=b(()=>Math.floor((Date.now()-this.startedAt.value)/1e3)),t.onErrorRateExceeded){let n=t.onErrorRateExceeded,s=this.errorRateThreshold;this.stopErrorWatch=A(this.errorRate,i=>{i>s&&n(i)})}else this.stopErrorWatch=A(this.errorRate,n=>{n>this.errorRateThreshold&&h.warn(`Error rate elevated: ${(n*100).toFixed(1)}%`)})}getPeerAdapters(){let t=this.appRef?.__kickApp;return t&&typeof t.getAdapters=="function"?t.getAdapters():this.peerAdapters}beforeMount(t,n){if(!this.enabled)return;this.appRef=t,this.container=n,this.startedAt.value=Date.now(),this.routes=[],this.adapterStatuses[this.name]="running";let s=y();if(this.secret!==!1){let o=this.secret;s.use((e,r,a)=>{if((e.headers["x-devtools-token"]??e.query?.token)===o||e.path==="/"&&e.method==="GET"&&!e.query?.token||e.path.endsWith(".js")||e.path.endsWith(".css"))return a();r.status(403).json({error:"Forbidden \u2014 invalid or missing devtools token"})})}s.get("/routes",(o,e)=>{e.json({routes:this.routes})}),s.get("/container",(o,e)=>{let r=this.container?.getRegistrations()??[];e.json({registrations:r,count:r.length})}),s.get("/metrics",(o,e)=>{e.json({requests:this.requestCount.value,serverErrors:this.errorCount.value,clientErrors:this.clientErrorCount.value,errorRate:this.errorRate.value,uptimeSeconds:this.uptimeSeconds.value,startedAt:new Date(this.startedAt.value).toISOString(),routeLatency:this.routeLatency})}),s.get("/health",(o,e)=>{let r=this.errorRate.value<this.errorRateThreshold,a=r?"healthy":"degraded";e.status(r?200:503).json({status:a,errorRate:this.errorRate.value,uptime:this.uptimeSeconds.value,adapters:this.adapterStatuses})}),s.get("/state",(o,e)=>{let r=this.getPeerAdapters().find(a=>a.name==="WsAdapter"&&typeof a.getStats=="function");e.json({reactive:{requestCount:this.requestCount.value,errorCount:this.errorCount.value,clientErrorCount:this.clientErrorCount.value,errorRate:this.errorRate.value,uptimeSeconds:this.uptimeSeconds.value,startedAt:new Date(this.startedAt.value).toISOString()},routes:this.routes.length,container:this.container?.getRegistrations().length??0,routeLatency:this.routeLatency,...r?{ws:r.getStats()}:{}})}),s.get("/ws",(o,e)=>{let r=this.getPeerAdapters().find(a=>a.name==="WsAdapter"&&typeof a.getStats=="function");if(!r){e.json({enabled:!1,message:"WsAdapter not found"});return}e.json({enabled:!0,...r.getStats()})}),s.get("/queues",async(o,e)=>{let r=this.getPeerAdapters().find(a=>a.name==="QueueAdapter"&&typeof a.getQueueNames=="function");if(!r){e.json({enabled:!1,message:"QueueAdapter not found"});return}try{let a=r.getQueueNames?.()??[],u=[];for(let d of a){let f=await r.getQueueStats?.(d);u.push({name:d,...f})}e.json({enabled:!0,queues:u})}catch{e.json({enabled:!0,queues:[],error:"Failed to fetch queue stats"})}}),this.exposeConfig&&s.get("/config",(o,e)=>{let r={};for(let[a,u]of Object.entries(process.env)){if(u===void 0)continue;let d=this.configPrefixes.some(f=>a.startsWith(f));r[a]=d?u:"[REDACTED]"}e.json({config:r})});let i=this.resolvePublicDir();if(i){s.use(E(i));let o=M(c(i,"index.html"),"utf-8");s.get("/",(e,r)=>{let a=o.replace("<body",`<body data-base="${this.basePath}"`);r.type("html").send(a)})}else s.get("/",(o,e)=>{e.type("html").send("<h1>DevTools: public directory not found</h1>")});t.use(this.basePath,s),this.secret?(h.info(`DevTools mounted at ${this.basePath} [token: ${this.secret}]`),h.info(`Access: ${this.basePath}?token=${this.secret}`)):h.info(`DevTools mounted at ${this.basePath} [no guard]`)}middleware(){return this.enabled?[{handler:p((t,n,s)=>{let i=Date.now();this.requestCount.value++,n.on("finish",()=>{n.statusCode>=500?this.errorCount.value++:n.statusCode>=400&&this.clientErrorCount.value++;let o=`${t.method} ${t.route?.path??t.path}`,e=Date.now()-i;this.routeLatency[o]||(this.routeLatency[o]={count:0,totalMs:0,minMs:1/0,maxMs:0});let r=this.routeLatency[o];r.count++,r.totalMs+=e,r.minMs=Math.min(r.minMs,e),r.maxMs=Math.max(r.maxMs,e)}),s()},"handler"),phase:"beforeGlobal"}]:[]}onRouteMount(t,n){if(!this.enabled)return;let s=Reflect.getMetadata(m.ROUTES,t)??[],i=Reflect.getMetadata(m.CLASS_MIDDLEWARES,t)??[];for(let o of s){let e=Reflect.getMetadata(m.METHOD_MIDDLEWARES,t.prototype,o.handlerName)??[];this.routes.push({method:o.method.toUpperCase(),path:`${n}${o.path==="/"?"":o.path}`,controller:t.name,handler:o.handlerName,middleware:[...i.map(r=>r.name||"anonymous"),...e.map(r=>r.name||"anonymous")]})}}afterStart(t,n){this.enabled&&h.info(`DevTools ready \u2014 ${this.routes.length} routes tracked, ${this.container?.getRegistrations().length??0} DI bindings`)}shutdown(){this.stopErrorWatch?.(),this.adapterStatuses[this.name]="stopped"}resolvePublicDir(){let t=S(D(import.meta.url)),n=[c(t,"..","public","devtools"),c(t,"..","..","public","devtools")];for(let s of n)if(C(c(s,"index.html")))return s;return null}};export{g as DevToolsAdapter};
1
+ import "reflect-metadata";
2
+ import { Router as v, static as A } from "express";
3
+ import { dirname as b, join as c } from "node:path";
4
+ import { fileURLToPath as R } from "node:url";
5
+ import { existsSync as y, readFileSync as E } from "node:fs";
6
+ import { randomBytes as D } from "node:crypto";
7
+ import { METADATA as f, computed as p, createLogger as S, reactive as C, ref as l, watch as m } from "@forinda/kickjs-core";
8
+ var h = S("DevTools"), j = class {
9
+ name = "DevToolsAdapter";
10
+ basePath;
11
+ enabled;
12
+ exposeConfig;
13
+ configPrefixes;
14
+ errorRateThreshold;
15
+ secret;
16
+ requestCount;
17
+ errorCount;
18
+ clientErrorCount;
19
+ startedAt;
20
+ errorRate;
21
+ uptimeSeconds;
22
+ routeLatency;
23
+ routes = [];
24
+ container = null;
25
+ appRef = null;
26
+ adapterStatuses = {};
27
+ stopErrorWatch = null;
28
+ peerAdapters = [];
29
+ constructor(r = {}) {
30
+ if (this.basePath = r.basePath ?? "/_debug", this.enabled = r.enabled ?? process.env.NODE_ENV !== "production", this.exposeConfig = r.exposeConfig ?? !1, this.configPrefixes = r.configPrefixes ?? ["APP_", "NODE_ENV"], this.errorRateThreshold = r.errorRateThreshold ?? 0.5, this.peerAdapters = r.adapters ?? [], r.secret === !1 ? this.secret = !1 : r.secret ? this.secret = r.secret : this.secret = D(16).toString("hex"), this.requestCount = l(0), this.errorCount = l(0), this.clientErrorCount = l(0), this.startedAt = l(Date.now()), this.routeLatency = C({}), this.errorRate = p(() => this.requestCount.value > 0 ? this.errorCount.value / this.requestCount.value : 0), this.uptimeSeconds = p(() => Math.floor((Date.now() - this.startedAt.value) / 1e3)), r.onErrorRateExceeded) {
31
+ const n = r.onErrorRateExceeded, s = this.errorRateThreshold;
32
+ this.stopErrorWatch = m(this.errorRate, (i) => {
33
+ i > s && n(i);
34
+ });
35
+ } else this.stopErrorWatch = m(this.errorRate, (n) => {
36
+ n > this.errorRateThreshold && h.warn(`Error rate elevated: ${(n * 100).toFixed(1)}%`);
37
+ });
38
+ }
39
+ getPeerAdapters() {
40
+ const r = this.appRef?.__kickApp;
41
+ return r && typeof r.getAdapters == "function" ? r.getAdapters() : this.peerAdapters;
42
+ }
43
+ beforeMount(r, n) {
44
+ if (!this.enabled) return;
45
+ this.appRef = r, this.container = n, this.startedAt.value = Date.now(), this.routes = [], this.adapterStatuses[this.name] = "running";
46
+ const s = v();
47
+ if (this.secret !== !1) {
48
+ const o = this.secret;
49
+ s.use((e, t, a) => {
50
+ if ((e.headers["x-devtools-token"] ?? e.query?.token) === o || e.path === "/" && e.method === "GET" && !e.query?.token || e.path.endsWith(".js") || e.path.endsWith(".css")) return a();
51
+ t.status(403).json({ error: "Forbidden — invalid or missing devtools token" });
52
+ });
53
+ }
54
+ s.get("/routes", (o, e) => {
55
+ e.json({ routes: this.routes });
56
+ }), s.get("/container", (o, e) => {
57
+ const t = this.container?.getRegistrations() ?? [];
58
+ e.json({
59
+ registrations: t,
60
+ count: t.length
61
+ });
62
+ }), s.get("/metrics", (o, e) => {
63
+ e.json({
64
+ requests: this.requestCount.value,
65
+ serverErrors: this.errorCount.value,
66
+ clientErrors: this.clientErrorCount.value,
67
+ errorRate: this.errorRate.value,
68
+ uptimeSeconds: this.uptimeSeconds.value,
69
+ startedAt: new Date(this.startedAt.value).toISOString(),
70
+ routeLatency: this.routeLatency
71
+ });
72
+ }), s.get("/health", (o, e) => {
73
+ const t = this.errorRate.value < this.errorRateThreshold, a = t ? "healthy" : "degraded";
74
+ e.status(t ? 200 : 503).json({
75
+ status: a,
76
+ errorRate: this.errorRate.value,
77
+ uptime: this.uptimeSeconds.value,
78
+ adapters: this.adapterStatuses
79
+ });
80
+ }), s.get("/state", (o, e) => {
81
+ const t = this.getPeerAdapters().find((a) => a.name === "WsAdapter" && typeof a.getStats == "function");
82
+ e.json({
83
+ reactive: {
84
+ requestCount: this.requestCount.value,
85
+ errorCount: this.errorCount.value,
86
+ clientErrorCount: this.clientErrorCount.value,
87
+ errorRate: this.errorRate.value,
88
+ uptimeSeconds: this.uptimeSeconds.value,
89
+ startedAt: new Date(this.startedAt.value).toISOString()
90
+ },
91
+ routes: this.routes.length,
92
+ container: this.container?.getRegistrations().length ?? 0,
93
+ routeLatency: this.routeLatency,
94
+ ...t ? { ws: t.getStats() } : {}
95
+ });
96
+ }), s.get("/ws", (o, e) => {
97
+ const t = this.getPeerAdapters().find((a) => a.name === "WsAdapter" && typeof a.getStats == "function");
98
+ if (!t) {
99
+ e.json({
100
+ enabled: !1,
101
+ message: "WsAdapter not found"
102
+ });
103
+ return;
104
+ }
105
+ e.json({
106
+ enabled: !0,
107
+ ...t.getStats()
108
+ });
109
+ }), s.get("/queues", async (o, e) => {
110
+ const t = this.getPeerAdapters().find((a) => a.name === "QueueAdapter" && typeof a.getQueueNames == "function");
111
+ if (!t) {
112
+ e.json({
113
+ enabled: !1,
114
+ message: "QueueAdapter not found"
115
+ });
116
+ return;
117
+ }
118
+ try {
119
+ const a = t.getQueueNames?.() ?? [], u = [];
120
+ for (const d of a) {
121
+ const g = await t.getQueueStats?.(d);
122
+ u.push({
123
+ name: d,
124
+ ...g
125
+ });
126
+ }
127
+ e.json({
128
+ enabled: !0,
129
+ queues: u
130
+ });
131
+ } catch {
132
+ e.json({
133
+ enabled: !0,
134
+ queues: [],
135
+ error: "Failed to fetch queue stats"
136
+ });
137
+ }
138
+ }), this.exposeConfig && s.get("/config", (o, e) => {
139
+ const t = {};
140
+ for (const [a, u] of Object.entries(process.env))
141
+ u !== void 0 && (t[a] = this.configPrefixes.some((d) => a.startsWith(d)) ? u : "[REDACTED]");
142
+ e.json({ config: t });
143
+ });
144
+ const i = this.resolvePublicDir();
145
+ if (i) {
146
+ s.use(A(i));
147
+ const o = E(c(i, "index.html"), "utf-8");
148
+ s.get("/", (e, t) => {
149
+ const a = o.replace("<body", `<body data-base="${this.basePath}"`);
150
+ t.type("html").send(a);
151
+ });
152
+ } else s.get("/", (o, e) => {
153
+ e.type("html").send("<h1>DevTools: public directory not found</h1>");
154
+ });
155
+ r.use(this.basePath, s), this.secret ? (h.info(`DevTools mounted at ${this.basePath} [token: ${this.secret}]`), h.info(`Access: ${this.basePath}?token=${this.secret}`)) : h.info(`DevTools mounted at ${this.basePath} [no guard]`);
156
+ }
157
+ middleware() {
158
+ return this.enabled ? [{
159
+ handler: (r, n, s) => {
160
+ const i = Date.now();
161
+ this.requestCount.value++, n.on("finish", () => {
162
+ n.statusCode >= 500 ? this.errorCount.value++ : n.statusCode >= 400 && this.clientErrorCount.value++;
163
+ const o = `${r.method} ${r.route?.path ?? r.path}`, e = Date.now() - i;
164
+ this.routeLatency[o] || (this.routeLatency[o] = {
165
+ count: 0,
166
+ totalMs: 0,
167
+ minMs: 1 / 0,
168
+ maxMs: 0
169
+ });
170
+ const t = this.routeLatency[o];
171
+ t.count++, t.totalMs += e, t.minMs = Math.min(t.minMs, e), t.maxMs = Math.max(t.maxMs, e);
172
+ }), s();
173
+ },
174
+ phase: "beforeGlobal"
175
+ }] : [];
176
+ }
177
+ onRouteMount(r, n) {
178
+ if (!this.enabled) return;
179
+ const s = Reflect.getMetadata(f.ROUTES, r) ?? [], i = Reflect.getMetadata(f.CLASS_MIDDLEWARES, r) ?? [];
180
+ for (const o of s) {
181
+ const e = Reflect.getMetadata(f.METHOD_MIDDLEWARES, r.prototype, o.handlerName) ?? [];
182
+ this.routes.push({
183
+ method: o.method.toUpperCase(),
184
+ path: `${n}${o.path === "/" ? "" : o.path}`,
185
+ controller: r.name,
186
+ handler: o.handlerName,
187
+ middleware: [...i.map((t) => t.name || "anonymous"), ...e.map((t) => t.name || "anonymous")]
188
+ });
189
+ }
190
+ }
191
+ afterStart(r, n) {
192
+ this.enabled && h.info(`DevTools ready — ${this.routes.length} routes tracked, ${this.container?.getRegistrations().length ?? 0} DI bindings`);
193
+ }
194
+ shutdown() {
195
+ this.stopErrorWatch?.(), this.adapterStatuses[this.name] = "stopped";
196
+ }
197
+ resolvePublicDir() {
198
+ const r = b(R(import.meta.url)), n = [c(r, "..", "public", "devtools"), c(r, "..", "..", "public", "devtools")];
199
+ for (const s of n) if (y(c(s, "index.html"))) return s;
200
+ return null;
201
+ }
202
+ };
203
+ export {
204
+ j as DevToolsAdapter
205
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-devtools",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Development introspection dashboard for KickJS — routes, DI container, metrics, health",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -35,7 +35,8 @@
35
35
  "@forinda/kickjs-mailer",
36
36
  "@forinda/kickjs-queue",
37
37
  "@forinda/kickjs-multi-tenant",
38
- "@forinda/kickjs-notifications"
38
+ "@forinda/kickjs-notifications",
39
+ "vite"
39
40
  ],
40
41
  "type": "module",
41
42
  "main": "dist/index.js",
@@ -52,7 +53,7 @@
52
53
  ],
53
54
  "dependencies": {
54
55
  "reflect-metadata": "^0.2.2",
55
- "@forinda/kickjs-core": "1.3.1"
56
+ "@forinda/kickjs-core": "1.4.0"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "express": "^5.1.0"
@@ -61,7 +62,6 @@
61
62
  "@types/express": "^5.0.6",
62
63
  "@types/node": "^24.5.2",
63
64
  "express": "^5.1.0",
64
- "tsup": "^8.5.0",
65
65
  "typescript": "^5.9.2",
66
66
  "vitest": "^3.2.4"
67
67
  },
@@ -83,8 +83,9 @@
83
83
  "url": "https://github.com/forinda/kick-js/issues"
84
84
  },
85
85
  "scripts": {
86
- "build": "tsup",
87
- "dev": "tsup --watch",
86
+ "build": "vite build && pnpm build:types",
87
+ "build:types": "tsc -p tsconfig.build.json",
88
+ "dev": "vite build --watch",
88
89
  "test": "vitest run --passWithNoTests",
89
90
  "typecheck": "tsc --noEmit",
90
91
  "clean": "rm -rf dist .turbo"