@lensmcp/vite-plugin 1.18.3 → 1.18.6

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/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export { lensmcpVitePlugin, handleClientEnvelope } from './lib/plugin.js';
2
2
  export type { LensmcpViteOptions, LensmcpViteRuntime, ClientEnvelope } from './lib/types.js';
3
3
  export { envelopeToEvent } from '@lensmcp/bridge';
4
- //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,4 +1 @@
1
- export { lensmcpVitePlugin, handleClientEnvelope } from './lib/plugin.js';
2
- // `envelopeToEvent` moved to @lensmcp/bridge when the bridge was decoupled
3
- // from Vite; re-exported here for backwards compatibility.
4
- export { envelopeToEvent } from '@lensmcp/bridge';
1
+ "use strict";export{lensmcpVitePlugin,handleClientEnvelope}from"./lib/plugin.js";export{envelopeToEvent}from"@lensmcp/bridge";
@@ -7,4 +7,3 @@ import type { ClientEnvelope } from './types.js';
7
7
  * `runtime://browser/*` resources.
8
8
  */
9
9
  export declare function envelopeToEvent(env: ClientEnvelope, sessionId?: string): BaseEvent;
10
- //# sourceMappingURL=event-mappers.d.ts.map
@@ -1,169 +1,2 @@
1
- import { fingerprint, ulid } from '@lensmcp/core';
2
- const SESSION_ID_PLACEHOLDER = 'pending';
3
- /**
4
- * Translate a single client-runtime envelope into the normalised
5
- * `BaseEvent` consumed by the LensMCP event bus. The Vite plugin
6
- * publishes these; reducers in `@lensmcp/<app>` materialise them into
7
- * `runtime://browser/*` resources.
8
- */
9
- export function envelopeToEvent(env, sessionId = SESSION_ID_PLACEHOLDER) {
10
- // Per-tab identity stamped by the client runtime — keys browser://tabs.
11
- const tab = env.tabId ? { tabId: env.tabId } : {};
12
- switch (env.kind) {
13
- case 'error':
14
- return {
15
- id: ulid(),
16
- sessionId,
17
- timestamp: env.at,
18
- source: 'client-runtime',
19
- category: 'runtime',
20
- severity: 'error',
21
- context: { sessionId, ...tab },
22
- fingerprint: fingerprint({
23
- kind: 'runtime',
24
- identity: `error:${normaliseStackTopFrame(env.stack)}`,
25
- file: env.source,
26
- detail: env.message,
27
- }),
28
- title: env.message,
29
- message: env.stack ?? env.message,
30
- location: env.source
31
- ? { file: env.source, line: env.lineno, column: env.colno }
32
- : undefined,
33
- raw: env,
34
- };
35
- case 'unhandledrejection':
36
- return {
37
- id: ulid(),
38
- sessionId,
39
- timestamp: env.at,
40
- source: 'client-runtime',
41
- category: 'runtime',
42
- severity: 'error',
43
- context: { sessionId, ...tab },
44
- fingerprint: fingerprint({
45
- kind: 'runtime',
46
- identity: `unhandledrejection:${env.reason.slice(0, 80)}`,
47
- }),
48
- title: 'Unhandled promise rejection',
49
- message: env.reason,
50
- raw: env,
51
- };
52
- case 'console': {
53
- const severity = env.level === 'error' ? 'error' : env.level === 'warn' ? 'warning' : 'info';
54
- const text = env.args.map(stringify).join(' ');
55
- return {
56
- id: ulid(),
57
- sessionId,
58
- timestamp: env.at,
59
- source: 'client-runtime',
60
- category: 'runtime',
61
- severity,
62
- context: { sessionId, ...tab },
63
- fingerprint: fingerprint({
64
- kind: 'runtime',
65
- identity: `console:${env.level}:${text.slice(0, 80)}`,
66
- }),
67
- title: `console.${env.level}`,
68
- message: text,
69
- raw: env,
70
- };
71
- }
72
- case 'fetch': {
73
- const failed = env.error !== undefined || (env.status !== undefined && env.status >= 400);
74
- return {
75
- id: ulid(),
76
- sessionId,
77
- timestamp: env.at,
78
- source: 'client-runtime',
79
- category: 'network',
80
- severity: failed ? 'error' : 'info',
81
- context: { sessionId, ...tab },
82
- fingerprint: fingerprint({
83
- kind: 'network',
84
- identity: `${env.method}:${normaliseUrl(env.url)}:${env.status ?? 'err'}`,
85
- }),
86
- title: `${env.method} ${env.url}${env.status !== undefined ? ` → ${env.status}` : ''}`,
87
- message: env.error,
88
- relatedUrls: [env.url],
89
- raw: env,
90
- };
91
- }
92
- case 'route':
93
- return {
94
- id: ulid(),
95
- sessionId,
96
- timestamp: env.at,
97
- source: 'client-runtime',
98
- category: 'runtime',
99
- severity: 'info',
100
- context: { sessionId, url: env.url, ...tab },
101
- fingerprint: fingerprint({ kind: 'runtime', identity: `route:${env.url}` }),
102
- title: `Route → ${env.url}`,
103
- raw: env,
104
- };
105
- case 'ready':
106
- return {
107
- id: ulid(),
108
- sessionId,
109
- timestamp: env.at,
110
- source: 'client-runtime',
111
- category: 'runtime',
112
- severity: 'info',
113
- context: { sessionId, url: env.url, ...tab },
114
- fingerprint: fingerprint({ kind: 'runtime', identity: 'client-ready' }),
115
- title: 'Client runtime ready',
116
- message: env.userAgent,
117
- raw: env,
118
- };
119
- case 'publish':
120
- // Pre-formed publish envelope from in-page instrumentation. Trust
121
- // its source/category (the producer is our own injected code); the
122
- // reducers gate on those, and the context carries the flow id so
123
- // React renders correlate with the originating user action.
124
- return {
125
- id: ulid(),
126
- sessionId,
127
- timestamp: env.at,
128
- source: env.source,
129
- category: env.category,
130
- severity: (env.severity ?? 'info'),
131
- context: { sessionId, ...tab, ...(env.context ?? {}) },
132
- fingerprint: env.fingerprint ?? fingerprint({ kind: 'publish', identity: env.title }),
133
- title: env.title,
134
- message: env.message,
135
- raw: env.raw,
136
- };
137
- }
138
- }
139
- // --- helpers ---
140
- function normaliseStackTopFrame(stack) {
141
- if (!stack)
142
- return 'unknown';
143
- const lines = stack.split('\n').slice(0, 2);
144
- for (const line of lines) {
145
- const m = line.match(/at\s+(\S+)/);
146
- if (m && m[1])
147
- return m[1];
148
- }
149
- return 'unknown';
150
- }
151
- function normaliseUrl(url) {
152
- try {
153
- const u = new URL(url, 'http://x');
154
- return `${u.pathname}`;
155
- }
156
- catch {
157
- return url;
158
- }
159
- }
160
- function stringify(v) {
161
- if (typeof v === 'string')
162
- return v;
163
- try {
164
- return JSON.stringify(v);
165
- }
166
- catch {
167
- return String(v);
168
- }
169
- }
1
+ "use strict";var g=Object.defineProperty;var o=(t,e)=>g(t,"name",{value:e,configurable:!0});var f=Object.defineProperty,a=o((t,e)=>f(t,"name",{value:e,configurable:!0}),"o"),p=Object.defineProperty,u=a((t,e)=>p(t,"name",{value:e,configurable:!0}),"o");import{fingerprint as n,ulid as s}from"@lensmcp/core";const y="pending";export function envelopeToEvent(t,e=y){const r=t.tabId?{tabId:t.tabId}:{};switch(t.kind){case"error":return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:e,...r},fingerprint:n({kind:"runtime",identity:`error:${c(t.stack)}`,file:t.source,detail:t.message}),title:t.message,message:t.stack??t.message,location:t.source?{file:t.source,line:t.lineno,column:t.colno}:void 0,raw:t};case"unhandledrejection":return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:e,...r},fingerprint:n({kind:"runtime",identity:`unhandledrejection:${t.reason.slice(0,80)}`}),title:"Unhandled promise rejection",message:t.reason,raw:t};case"console":{const i=t.level==="error"?"error":t.level==="warn"?"warning":"info",m=t.args.map(d).join(" ");return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"runtime",severity:i,context:{sessionId:e,...r},fingerprint:n({kind:"runtime",identity:`console:${t.level}:${m.slice(0,80)}`}),title:`console.${t.level}`,message:m,raw:t}}case"fetch":{const i=t.error!==void 0||t.status!==void 0&&t.status>=400;return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"network",severity:i?"error":"info",context:{sessionId:e,...r},fingerprint:n({kind:"network",identity:`${t.method}:${l(t.url)}:${t.status??"err"}`}),title:`${t.method} ${t.url}${t.status!==void 0?` \u2192 ${t.status}`:""}`,message:t.error,relatedUrls:[t.url],raw:t}}case"route":return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:e,url:t.url,...r},fingerprint:n({kind:"runtime",identity:`route:${t.url}`}),title:`Route \u2192 ${t.url}`,raw:t};case"ready":return{id:s(),sessionId:e,timestamp:t.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:e,url:t.url,...r},fingerprint:n({kind:"runtime",identity:"client-ready"}),title:"Client runtime ready",message:t.userAgent,raw:t};case"publish":return{id:s(),sessionId:e,timestamp:t.at,source:t.source,category:t.category,severity:t.severity??"info",context:{sessionId:e,...r,...t.context??{}},fingerprint:t.fingerprint??n({kind:"publish",identity:t.title}),title:t.title,message:t.message,raw:t.raw}}}o(envelopeToEvent,"envelopeToEvent"),a(envelopeToEvent,"envelopeToEvent"),u(envelopeToEvent,"envelopeToEvent");function c(t){if(!t)return"unknown";const e=t.split(`
2
+ `).slice(0,2);for(const r of e){const i=r.match(/at\s+(\S+)/);if(i&&i[1])return i[1]}return"unknown"}o(c,"c"),a(c,"s"),u(c,"normaliseStackTopFrame");function l(t){try{return`${new URL(t,"http://x").pathname}`}catch{return t}}o(l,"l"),a(l,"m"),u(l,"normaliseUrl");function d(t){if(typeof t=="string")return t;try{return JSON.stringify(t)}catch{return String(t)}}o(d,"d"),a(d,"d"),u(d,"stringify");
package/lib/plugin.d.ts CHANGED
@@ -39,4 +39,3 @@ export declare function lensmcpVitePlugin(options?: LensmcpViteOptions, deps?: B
39
39
  */
40
40
  export declare function handleClientEnvelope(raw: string, deps: BoundDeps): void;
41
41
  export {};
42
- //# sourceMappingURL=plugin.d.ts.map
package/lib/plugin.js CHANGED
@@ -1,307 +1,16 @@
1
- import { fingerprint, ulid } from '@lensmcp/core';
2
- import { createBridgeServer, makePublisher, handleClientEnvelope as forwardClientEnvelope, } from '@lensmcp/bridge';
3
- const CLIENT_VIRTUAL_ID = '/@lensmcp/client-runtime';
4
- const RESOLVED_CLIENT_VIRTUAL_ID = '\0lensmcp-client-runtime';
5
- /**
6
- * Vite plugin that:
7
- * 1. Stands up the LensMCP browser-event bridge (`@lensmcp/bridge`) on an
8
- * ephemeral WebSocket port so the injected @lensmcp/client-runtime can
9
- * post browser events back into the LensMCP event bus.
10
- * 2. Injects a `<script type="module" src="/@lensmcp/client-runtime">`
11
- * into served HTML.
12
- * 3. Resolves that virtual module to the bundled client runtime
13
- * (during Phase 1 — a minimal inline implementation; the real
14
- * package import lands in Phase 2/3).
15
- * 4. Emits HMR + module-graph events for the build reducer.
16
- *
17
- * Since the bridge was decoupled, this plugin is just ONE host adapter for
18
- * it — webpack / Next.js / no-build hosts run the same bridge as a
19
- * standalone sidecar (`lensmcp bridge`) on a fixed port instead.
20
- *
21
- * The plugin is *passive*: it never mutates app state or behaviour.
22
- */
23
- export function lensmcpVitePlugin(options = {}, deps = {}) {
24
- const enabled = options.enabled ?? true;
25
- const wsHost = options.wsHost ?? '127.0.0.1';
26
- const injectClient = options.injectClient ?? true;
27
- let wssPort;
28
- let bridge;
29
- // Bus → env-sink → console publisher, shared by every hook below.
30
- const publishEvent = makePublisher(deps);
31
- const aliasValtio = options.aliasValtio ?? true;
32
- const transformSource = options.transformSource ?? true;
33
- return {
34
- name: '@lensmcp/vite-plugin',
35
- apply: enabled ? undefined : () => false,
36
- enforce: 'pre',
37
- wsPort: () => wssPort,
38
- config() {
39
- if (!enabled || !aliasValtio)
40
- return;
41
- // Zero-config (Phase 8): make `import { proxy } from 'valtio'`
42
- // resolve to the instrumented drop-in, so existing Valtio code
43
- // becomes observable without edits. Dev-only.
44
- //
45
- // EXACT match (`/^valtio$/`) only — a bare-string alias does prefix
46
- // matching, which would also rewrite the drop-in's own
47
- // `valtio/vanilla` + `valtio/react` imports and self-loop.
48
- return {
49
- resolve: {
50
- alias: [
51
- { find: /^valtio$/, replacement: '@lensmcp/valtio-instrumentation' },
52
- ],
53
- },
54
- };
55
- },
56
- async configureServer(server) {
57
- if (!enabled)
58
- return;
59
- // The bridge owns the WebSocket + envelope→event mapping; we just
60
- // host it on an ephemeral port (or `options.wsPort` if pinned) and
61
- // bake that port into the injected runtime at `load()` time.
62
- bridge = await createBridgeServer({
63
- host: wsHost,
64
- port: options.wsPort ?? 0,
65
- bus: deps.bus,
66
- sessionId: deps.sessionId,
67
- });
68
- wssPort = bridge.port;
69
- // Vite native error stream → our bus.
70
- server.ws.on('vite:error', (data) => {
71
- publishEvent(viteErrorToEvent(data, deps.sessionId));
72
- });
73
- },
74
- resolveId(id) {
75
- if (id === CLIENT_VIRTUAL_ID)
76
- return RESOLVED_CLIENT_VIRTUAL_ID;
77
- return null;
78
- },
79
- load(id) {
80
- if (id !== RESOLVED_CLIENT_VIRTUAL_ID)
81
- return null;
82
- const port = wssPort ?? options.wsPort ?? 0;
83
- return buildInlineClientRuntime({ host: wsHost, port });
84
- },
85
- // Zero-config source transform. Runs in our OWN transform hook at
86
- // enforce:'pre', so it's independent of whichever compiler the React
87
- // plugin uses — it runs on the raw JSX/TSX BEFORE @vitejs/plugin-react
88
- // (Babel), @vitejs/plugin-react@6 (oxc, which dropped the `babel` hook)
89
- // or @vitejs/plugin-react-swc (SWC) turn JSX into calls. The LensMCP
90
- // output is plain JSX/TSX (LensmcpRoot/withFlow/traced* + data-* props),
91
- // which every downstream compiler preserves (see swc-coexistence smoke).
92
- // A native SWC/oxc rewrite would only be a perf optimisation.
93
- async transform(code, id) {
94
- if (!enabled || !transformSource)
95
- return null;
96
- const file = id.split('?')[0] ?? id;
97
- if (file.includes('/node_modules/'))
98
- return null;
99
- if (!/\.[jt]sx$/.test(file))
100
- return null;
101
- const result = await runLensmcpBabel(code, file);
102
- if (!result)
103
- return null;
104
- // Babel's source map is a valid Rollup map object at runtime; the
105
- // cast just bridges the nominal type gap.
106
- return { code: result.code, map: result.map };
107
- },
108
- transformIndexHtml() {
109
- if (!injectClient || !enabled)
110
- return;
111
- return [
112
- {
113
- tag: 'script',
114
- attrs: { type: 'module', src: CLIENT_VIRTUAL_ID },
115
- injectTo: 'head-prepend',
116
- },
117
- ];
118
- },
119
- handleHotUpdate(ctx) {
120
- if (!enabled)
121
- return;
122
- const affected = ctx.modules
123
- .map((m) => m.file ?? m.id ?? null)
124
- .filter((f) => !!f);
125
- publishEvent({
126
- id: ulid(),
127
- sessionId: deps.sessionId ?? 'pending',
128
- timestamp: Date.now(),
129
- source: 'vite',
130
- category: 'build',
131
- severity: 'info',
132
- context: { sessionId: deps.sessionId ?? 'pending' },
133
- fingerprint: fingerprint({
134
- kind: 'hmr',
135
- identity: ctx.file,
136
- }),
137
- title: `HMR ${ctx.file}`,
138
- // The changed file, structured. The build reducer retracts bundler
139
- // failures for exactly the files an update touched, so it needs them
140
- // as data — it previously had to scrape the `HMR ` title prefix.
141
- location: { file: ctx.file },
142
- relatedFiles: affected,
143
- raw: { kind: 'hmr', file: ctx.file },
144
- });
145
- },
146
- generateBundle(_opts, bundle) {
147
- if (!enabled)
148
- return;
149
- const chunks = [];
150
- let totalBytes = 0;
151
- for (const file of Object.values(bundle)) {
152
- const item = file;
153
- const name = item.fileName;
154
- let sizeBytes;
155
- if (item.type === 'chunk') {
156
- sizeBytes = (item.code ?? '').length;
157
- }
158
- else {
159
- const src = item.source;
160
- sizeBytes =
161
- typeof src === 'string' ? src.length : src instanceof Uint8Array ? src.byteLength : 0;
162
- }
163
- totalBytes += sizeBytes;
164
- const modules = [];
165
- if (item.type === 'chunk' && item.modules) {
166
- for (const [id, mod] of Object.entries(item.modules)) {
167
- const ml = mod.renderedLength ?? mod.originalLength ?? 0;
168
- modules.push({ id, sizeBytes: ml });
169
- }
170
- modules.sort((a, b) => b.sizeBytes - a.sizeBytes);
171
- }
172
- chunks.push({ name, sizeBytes, modules });
173
- }
174
- const report = { timestamp: Date.now(), totalBytes, chunks };
175
- publishEvent({
176
- id: ulid(),
177
- sessionId: deps.sessionId ?? 'pending',
178
- timestamp: Date.now(),
179
- source: 'rollup',
180
- category: 'build',
181
- severity: 'info',
182
- context: { sessionId: deps.sessionId ?? 'pending' },
183
- fingerprint: fingerprint({ kind: 'bundle', identity: 'report' }),
184
- title: `Bundle report: ${chunks.length} chunks, ${totalBytes} bytes`,
185
- raw: { kind: 'bundle-report', report },
186
- });
187
- },
188
- async closeBundle() {
189
- if (bridge) {
190
- await bridge.close();
191
- bridge = undefined;
192
- }
193
- },
194
- };
195
- }
196
- // --- helpers ---
197
- /**
198
- * Run the LensMCP Babel transform on one module's source. Lazily loads
199
- * `@babel/core` + the plugin so they're only touched in dev when a
200
- * `.jsx`/`.tsx` file is transformed. Returns null on parse error (let
201
- * Vite proceed with the untransformed source).
202
- */
203
- async function runLensmcpBabel(code, filename) {
204
- try {
205
- const [{ transformSync }, { lensmcpBabelPlugin }] = await Promise.all([
206
- import('@babel/core'),
207
- import('@lensmcp/react-instrumentation'),
208
- ]);
209
- const result = transformSync(code, {
210
- filename,
211
- babelrc: false,
212
- configFile: false,
213
- sourceMaps: true,
214
- parserOpts: { plugins: ['jsx', 'typescript'] },
215
- plugins: [[lensmcpBabelPlugin, { rootDir: process.cwd(), production: false }]],
216
- code: true,
217
- });
218
- if (!result?.code)
219
- return null;
220
- return { code: result.code, map: result.map };
221
- }
222
- catch {
223
- return null;
224
- }
225
- }
226
- /**
227
- * Parse one raw WebSocket message from the injected client runtime and
228
- * publish the resulting event. Kept as a public export for backwards
229
- * compatibility — it now delegates to `@lensmcp/bridge`. Exercised over a
230
- * real WebSocket without booting Vite (see the react-bridge smoke).
231
- */
232
- export function handleClientEnvelope(raw, deps) {
233
- forwardClientEnvelope(raw, makePublisher(deps), deps.sessionId);
234
- }
235
- /**
236
- * Vite's `ErrorPayload.err` carries the offending module as `loc.file` (when
237
- * the plugin reported a position) or `id` (the resolved module id, which may
238
- * carry a `?t=` cache-buster). Both are stripped to a bare path.
239
- */
240
- function errorLocation(err) {
241
- const raw = err?.loc?.file ?? err?.id;
242
- if (typeof raw !== 'string' || raw.length === 0)
243
- return undefined;
244
- const file = raw.split('?')[0] || raw;
245
- return {
246
- file,
247
- ...(typeof err?.loc?.line === 'number' ? { line: err.loc.line } : {}),
248
- ...(typeof err?.loc?.column === 'number' ? { column: err.loc.column } : {}),
249
- };
250
- }
251
- function viteErrorToEvent(data, sessionId) {
252
- const message = data.err?.message ?? 'Vite error';
253
- const stack = data.err?.stack;
254
- const loc = errorLocation(data.err);
255
- return {
256
- id: ulid(),
257
- sessionId: sessionId ?? 'pending',
258
- timestamp: Date.now(),
259
- source: 'vite',
260
- category: 'build',
261
- severity: 'error',
262
- context: { sessionId: sessionId ?? 'pending' },
263
- // The FILE is part of the identity. Without it two different modules
264
- // failing with the same message ("Failed to resolve import") collapsed
265
- // into ONE entry, so retracting either would have silenced both — and a
266
- // file-scoped clear could not be safe. It is also what lets a later HMR
267
- // update for that file retract this error at all: until now nothing in
268
- // this repo ever removed a build failure, so a fixed Vite error was
269
- // reported for the life of the session.
270
- fingerprint: fingerprint({
271
- kind: 'vite',
272
- identity: data.type ?? message,
273
- ...(loc ? { file: loc.file } : {}),
274
- }),
275
- title: message,
276
- message: stack,
277
- ...(loc ? { location: loc } : {}),
278
- raw: {
279
- kind: 'build-diagnostic',
280
- ...(loc ? { file: loc.file } : {}),
281
- ...(data.err?.plugin ? { plugin: data.err.plugin } : {}),
282
- },
283
- };
284
- }
285
- /**
286
- * Minimal browser-side runtime that mirrors what `@lensmcp/client-runtime`
287
- * will eventually own. Inlined here so Phase 1 has no cross-build
288
- * coupling. Future: replace with a real import of the published
289
- * package's browser bundle.
290
- */
291
- function buildInlineClientRuntime(opts) {
292
- return `// @lensmcp/client-runtime (inlined by @lensmcp/vite-plugin)
1
+ "use strict";var x=Object.defineProperty;var l=(e,t)=>x(e,"name",{value:t,configurable:!0});var P=Object.defineProperty,u=l((e,t)=>P(e,"name",{value:t,configurable:!0}),"l");import{fingerprint as v,ulid as b}from"@lensmcp/core";import{createBridgeServer as D,makePublisher as S,handleClientEnvelope as N}from"@lensmcp/bridge";const _="/@lensmcp/client-runtime",E="\0lensmcp-client-runtime";export function lensmcpVitePlugin(e={},t={}){const r=e.enabled??!0,p=e.wsHost??"127.0.0.1",o=e.injectClient??!0;let g,f;const y=S(t),L=e.aliasValtio??!0,T=e.transformSource??!0;return{name:"@lensmcp/vite-plugin",apply:r?void 0:()=>!1,enforce:"pre",wsPort:u(()=>g,"wsPort"),config(){if(!(!r||!L))return{resolve:{alias:[{find:/^valtio$/,replacement:"@lensmcp/valtio-instrumentation"}]}}},async configureServer(n){r&&(f=await D({host:p,port:e.wsPort??0,bus:t.bus,sessionId:t.sessionId}),g=f.port,n.ws.on("vite:error",s=>{y(C(s,t.sessionId))}))},resolveId(n){return n===_?E:null},load(n){if(n!==E)return null;const s=g??e.wsPort??0;return R({host:p,port:s})},async transform(n,s){if(!r||!T)return null;const i=s.split("?")[0]??s;if(i.includes("/node_modules/")||!/\.[jt]sx$/.test(i))return null;const c=await k(n,i);return c?{code:c.code,map:c.map}:null},transformIndexHtml(){if(!(!o||!r))return[{tag:"script",attrs:{type:"module",src:_},injectTo:"head-prepend"}]},handleHotUpdate(n){if(!r)return;const s=n.modules.map(i=>i.file??i.id??null).filter(i=>!!i);y({id:b(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:"info",context:{sessionId:t.sessionId??"pending"},fingerprint:v({kind:"hmr",identity:n.file}),title:`HMR ${n.file}`,location:{file:n.file},relatedFiles:s,raw:{kind:"hmr",file:n.file}})},generateBundle(n,s){if(!r)return;const i=[];let c=0;for(const A of Object.values(s)){const d=A,B=d.fileName;let m;if(d.type==="chunk")m=(d.code??"").length;else{const a=d.source;m=typeof a=="string"?a.length:a instanceof Uint8Array?a.byteLength:0}c+=m;const w=[];if(d.type==="chunk"&&d.modules){for(const[a,h]of Object.entries(d.modules)){const F=h.renderedLength??h.originalLength??0;w.push({id:a,sizeBytes:F})}w.sort((a,h)=>h.sizeBytes-a.sizeBytes)}i.push({name:B,sizeBytes:m,modules:w})}const O={timestamp:Date.now(),totalBytes:c,chunks:i};y({id:b(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"rollup",category:"build",severity:"info",context:{sessionId:t.sessionId??"pending"},fingerprint:v({kind:"bundle",identity:"report"}),title:`Bundle report: ${i.length} chunks, ${c} bytes`,raw:{kind:"bundle-report",report:O}})},async closeBundle(){f&&(await f.close(),f=void 0)}}}l(lensmcpVitePlugin,"lensmcpVitePlugin"),u(lensmcpVitePlugin,"lensmcpVitePlugin");async function k(e,t){try{const[{transformSync:r},{lensmcpBabelPlugin:p}]=await Promise.all([import("@babel/core"),import("@lensmcp/react-instrumentation")]),o=r(e,{filename:t,babelrc:!1,configFile:!1,sourceMaps:!0,parserOpts:{plugins:["jsx","typescript"]},plugins:[[p,{rootDir:process.cwd(),production:!1}]],code:!0});return o?.code?{code:o.code,map:o.map}:null}catch{return null}}l(k,"B"),u(k,"runLensmcpBabel");export function handleClientEnvelope(e,t){N(e,S(t),t.sessionId)}l(handleClientEnvelope,"handleClientEnvelope"),u(handleClientEnvelope,"handleClientEnvelope");function I(e){const t=e?.loc?.file??e?.id;return typeof t!="string"||t.length===0?void 0:{file:t.split("?")[0]||t,...typeof e?.loc?.line=="number"?{line:e.loc.line}:{},...typeof e?.loc?.column=="number"?{column:e.loc.column}:{}}}l(I,"F"),u(I,"errorLocation");function C(e,t){const r=e.err?.message??"Vite error",p=e.err?.stack,o=I(e.err);return{id:b(),sessionId:t??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:"error",context:{sessionId:t??"pending"},fingerprint:v({kind:"vite",identity:e.type??r,...o?{file:o.file}:{}}),title:r,message:p,...o?{location:o}:{},raw:{kind:"build-diagnostic",...o?{file:o.file}:{},...e.err?.plugin?{plugin:e.err.plugin}:{}}}}l(C,"D"),u(C,"viteErrorToEvent");function R(e){return`// @lensmcp/client-runtime (inlined by @lensmcp/vite-plugin)
293
2
  (() => {
294
3
  if (typeof window === 'undefined') return;
295
4
  if (window.__LENSMCP_CLIENT__) return;
296
5
  window.__LENSMCP_CLIENT__ = true;
297
6
 
298
- // React DevTools hook shim/patch this script is the FIRST module script in
7
+ // React DevTools hook shim/patch \u2014 this script is the FIRST module script in
299
8
  // the document, so it runs BEFORE react-dom evaluates. react-dom only calls
300
9
  // onCommitFiberRoot when a hook exists at its module init, so installing (or
301
10
  // patching) it here is what gives @lensmcp/react-instrumentation per-commit
302
11
  // fiber roots for per-component render attribution. When another hook is
303
12
  // already present (React DevTools extension, react-refresh preamble) we
304
- // CHAIN its onCommitFiberRoot never replace it.
13
+ // CHAIN its onCommitFiberRoot \u2014 never replace it.
305
14
  (() => {
306
15
  try {
307
16
  const subs = new Set();
@@ -330,16 +39,16 @@ function buildInlineClientRuntime(opts) {
330
39
  setStrictMode() {},
331
40
  };
332
41
  }
333
- } catch (e) { /* strictly passive never break the host page */ }
42
+ } catch (e) { /* strictly passive \u2014 never break the host page */ }
334
43
  })();
335
44
 
336
- const WS_URL = 'ws://${opts.host}:${opts.port}';
45
+ const WS_URL = 'ws://${e.host}:${e.port}';
337
46
  let ws = null;
338
47
  let queue = [];
339
48
  let openOnce = false;
340
49
 
341
50
  // Per-TAB identity: sessionStorage is scoped to the tab (and survives
342
- // reloads in it), so each browser tab gets a stable id this is what
51
+ // reloads in it), so each browser tab gets a stable id \u2014 this is what
343
52
  // lets browser://tabs show every open tab instead of one 'default'.
344
53
  const TAB_ID = (() => {
345
54
  try {
@@ -363,7 +72,7 @@ function buildInlineClientRuntime(opts) {
363
72
  send({ kind: 'ready', userAgent: navigator.userAgent, url: location.href });
364
73
  });
365
74
  ws.addEventListener('close', () => { if (openOnce) setTimeout(open, 1000); });
366
- ws.addEventListener('error', () => { /* ignore close handler reconnects */ });
75
+ ws.addEventListener('error', () => { /* ignore \u2014 close handler reconnects */ });
367
76
  }
368
77
  open();
369
78
 
@@ -388,20 +97,58 @@ function buildInlineClientRuntime(opts) {
388
97
  };
389
98
 
390
99
  // Errors
100
+ //
101
+ // Bounded at capture so a deep React stack can't push a multi-hundred-KB
102
+ // frame down the socket; the bridge bounds again for anything hand-rolled.
103
+ const MAX_STACK = 4096;
104
+ function clipStr(s, n) {
105
+ if (typeof s !== 'string' || s.length <= n) return s;
106
+ return s.slice(0, n) + '\u2026[lensmcp truncated: ' + (s.length - n) + ' more chars]';
107
+ }
108
+ // A rejected non-Error ({ status: 401 }, a Response) used to be flattened by
109
+ // String(reason) into "[object Object]" \u2014 the reason was captured and told
110
+ // you nothing.
111
+ function describeReason(r) {
112
+ if (r === null || r === undefined) return String(r);
113
+ if (typeof r === 'string') return r;
114
+ if (typeof r.message === 'string' && r.message) return r.message;
115
+ try { const j = JSON.stringify(r); if (j && j !== '{}' && j !== 'null') return j; } catch (err) {}
116
+ return String(r);
117
+ }
391
118
  window.addEventListener('error', (e) => {
119
+ // This listener is registered in the CAPTURE phase, which means it also
120
+ // receives subresource load failures (<img>, <script>, <link>, <video>).
121
+ // Those arrive as a plain Event, NOT an ErrorEvent: no message, no
122
+ // filename, no lineno \u2014 the only identity is the target element and its
123
+ // URL. Reading only the ErrorEvent fields (as this did) serialised every
124
+ // one of them to a bare { kind:'error', at, tabId } and lost the lot.
125
+ const t = e.target;
126
+ if (t && t !== window && t.tagName && !e.message) {
127
+ send({
128
+ kind: 'resource-error',
129
+ tagName: String(t.tagName).toLowerCase(),
130
+ url: t.currentSrc || t.src || t.href || undefined,
131
+ });
132
+ return;
133
+ }
134
+ const err = e.error;
392
135
  send({
393
136
  kind: 'error',
394
137
  message: e.message,
395
- stack: e.error && e.error.stack,
138
+ name: err && err.name,
139
+ stack: clipStr(err && err.stack, MAX_STACK),
396
140
  source: e.filename,
397
141
  lineno: e.lineno,
398
142
  colno: e.colno,
399
143
  });
400
144
  }, true);
401
145
  window.addEventListener('unhandledrejection', (e) => {
146
+ const r = e.reason;
402
147
  send({
403
148
  kind: 'unhandledrejection',
404
- reason: e.reason && (e.reason.stack || e.reason.message || String(e.reason)),
149
+ reason: clipStr(describeReason(r), MAX_STACK),
150
+ name: r && r.name,
151
+ stack: clipStr(r && r.stack, MAX_STACK),
405
152
  });
406
153
  });
407
154
 
@@ -422,11 +169,11 @@ function buildInlineClientRuntime(opts) {
422
169
  const start = Date.now();
423
170
  // flow-fetch (@lensmcp/react-instrumentation) wraps fetch OUTSIDE this
424
171
  // wrapper and publishes flow-correlated network events for the same
425
- // request sending ours too double-counts every fetch in the timeline.
172
+ // request \u2014 sending ours too double-counts every fetch in the timeline.
426
173
  const flowFetchActive = !!window.__LENSMCP_FLOW_FETCH__;
427
174
  try {
428
175
  const res = await origFetch(input, init);
429
- if (!flowFetchActive) send({ kind: 'fetch', method, url, status: res.status, ok: res.ok, durationMs: Date.now() - start });
176
+ if (!flowFetchActive) send({ kind: 'fetch', method, url, status: res.status, statusText: res.statusText, ok: res.ok, durationMs: Date.now() - start });
430
177
  return res;
431
178
  } catch (err) {
432
179
  if (!flowFetchActive) send({ kind: 'fetch', method, url, error: err && (err.message || String(err)), durationMs: Date.now() - start });
@@ -449,5 +196,4 @@ function buildInlineClientRuntime(opts) {
449
196
  try { return JSON.parse(JSON.stringify(v)); } catch { return String(v); }
450
197
  }
451
198
  })();
452
- `;
453
- }
199
+ `}l(R,"N"),u(R,"buildInlineClientRuntime");
package/lib/types.d.ts CHANGED
@@ -26,4 +26,3 @@ export interface LensmcpViteRuntime {
26
26
  */
27
27
  wsPort(): number | undefined;
28
28
  }
29
- //# sourceMappingURL=types.d.ts.map
package/lib/types.js CHANGED
@@ -1 +1 @@
1
- export {};
1
+ "use strict";export{};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/vite-plugin",
3
- "version": "1.18.3",
3
+ "version": "1.18.6",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -15,11 +15,11 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@babel/core": "^7.0.0",
18
- "@lensmcp/bridge": "1.18.3",
19
- "@lensmcp/core": "1.18.3",
20
- "@lensmcp/protocol-types": "1.18.3",
21
- "@lensmcp/react-instrumentation": "1.18.3",
22
- "@lensmcp/valtio-instrumentation": "1.18.3",
18
+ "@lensmcp/bridge": "1.18.6",
19
+ "@lensmcp/core": "1.18.6",
20
+ "@lensmcp/protocol-types": "1.18.6",
21
+ "@lensmcp/react-instrumentation": "1.18.6",
22
+ "@lensmcp/valtio-instrumentation": "1.18.6",
23
23
  "tslib": "^2.3.0"
24
24
  },
25
25
  "peerDependencies": {
package/index.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAG7F,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"event-mappers.d.ts","sourceRoot":"","sources":["../../src/lib/event-mappers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAIjD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,SAAyB,GAAG,SAAS,CAuIlG"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/lib/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAElD,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAOjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAKzE,UAAU,SAAS;IACjB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,kBAAuB,EAChC,IAAI,GAAE,SAAc,GACnB,MAAM,GAAG,kBAAkB,CAwL7B;AAqCD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAEvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oFAAoF;IACpF,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,IAAI,MAAM,GAAG,SAAS,CAAC;CAC9B"}