@lensmcp/vite-plugin 1.24.0 → 1.24.2
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/README.md +2 -0
- package/lib/event-mappers.js +2 -2
- package/lib/plugin.d.ts +11 -0
- package/lib/plugin.js +49 -4
- package/lib/types.d.ts +9 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ lensmcpVitePlugin({ applyOnBuild: true }); // or LENSMCP_VITE_APPLY_ON_BUILD=1
|
|
|
48
48
|
## What it does
|
|
49
49
|
|
|
50
50
|
- **`valtio` → `@lensmcp/valtio-instrumentation` alias.** A `config()` hook rewrites bare `import … from 'valtio'` to the instrumented drop-in, so existing Valtio stores become observable without edits. The alias uses an exact `/^valtio$/` match so the drop-in's own `valtio/vanilla` and `valtio/react` imports are left untouched. (Toggle with `aliasValtio`.)
|
|
51
|
+
- **Its own dependency cache in a worktree.** When the checkout's `node_modules` is a symlink to another checkout's (how agent worktrees are often made), the `config()` hook moves Vite's `cacheDir` to the checkout's own `.lensmcp/cache/vite/…`, so two checkouts' Vites never optimize into one folder. A checkout that owns `node_modules` is untouched.
|
|
51
52
|
- **`@lensmcp/react-instrumentation` Babel transform.** A `transform()` hook runs the LensMCP Babel plugin (`lensmcpBabelPlugin`) over every `.jsx`/`.tsx` file outside `node_modules`, wrapping the app root, instrumenting hooks, and stamping JSX identity props. The output is plain JSX/TSX that downstream React compilers preserve. (Toggle with `transformSource`.)
|
|
52
53
|
- **Browser event bridge / client runtime injection.** `transformIndexHtml()` injects `<script type="module" src="/@lensmcp/client-runtime">` into the page head; the plugin resolves that virtual module to a small client runtime. The runtime opens a WebSocket back to the plugin and forwards browser events — runtime errors, unhandled rejections, `console.*`, `fetch`, History/route changes, plus the pre-formed `publish` envelopes from the in-page React and Valtio instrumentation (via `window.__LENSMCP_PUBLISH__`). Each tab gets a stable per-tab id so multiple tabs stay distinct.
|
|
53
54
|
- **Build + HMR events.** `handleHotUpdate()` emits an HMR event with the affected files, and `generateBundle()` emits a bundle-size report (per-chunk and per-module byte sizes). Vite's native `vite:error` stream is forwarded too.
|
|
@@ -68,6 +69,7 @@ Incoming browser envelopes and Vite/Rollup events are normalised into LensMCP's
|
|
|
68
69
|
| `injectClient` | `boolean` | `true` | Inject the client runtime via a `<script>` tag. |
|
|
69
70
|
| `aliasValtio` | `boolean` | `true` | Alias `valtio` to the instrumented drop-in in dev. |
|
|
70
71
|
| `transformSource` | `boolean` | `true` | Run the LensMCP source transform (root-wrap + hooks + JSX ids). |
|
|
72
|
+
| `hmrRecovery` | `boolean` | `true` | When Vite gives up on its HMR socket at page load, the runtime always reports it (`hmr` `ws-failed`); with this on it also reloads the page once — only in a tab whose socket worked earlier, at most once per two minutes. `false` = report only. |
|
|
71
73
|
|
|
72
74
|
The returned plugin also exposes a `wsPort(): number \| undefined` accessor (`LensmcpViteRuntime`), available after Vite's `configureServer` hook fires.
|
|
73
75
|
|
package/lib/event-mappers.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`).slice(0,2);for(const i of n){const a=i.match(/at\s+(\S+)/);if(a&&a[1])return a[1]}return"unknown"}
|
|
1
|
+
"use strict";var pe=Object.defineProperty;var u=(e,n)=>pe(e,"name",{value:n,configurable:!0});var de=Object.defineProperty,v=u((e,n)=>de(e,"name",{value:n,configurable:!0}),"u"),be=Object.defineProperty,s=v((e,n)=>be(e,"name",{value:n,configurable:!0}),"u"),me=Object.defineProperty,f=s((e,n)=>me(e,"name",{value:n,configurable:!0}),"c"),ge=Object.defineProperty,p=f((e,n)=>ge(e,"name",{value:n,configurable:!0}),"c"),ye=Object.defineProperty,d=p((e,n)=>ye(e,"name",{value:n,configurable:!0}),"c"),je=Object.defineProperty,b=d((e,n)=>je(e,"name",{value:n,configurable:!0}),"u"),Te=Object.defineProperty,m=b((e,n)=>Te(e,"name",{value:n,configurable:!0}),"u"),Ee=Object.defineProperty,g=m((e,n)=>Ee(e,"name",{value:n,configurable:!0}),"u"),Oe=Object.defineProperty,y=g((e,n)=>Oe(e,"name",{value:n,configurable:!0}),"c"),Pe=Object.defineProperty,j=y((e,n)=>Pe(e,"name",{value:n,configurable:!0}),"c"),Ie=Object.defineProperty,T=j((e,n)=>Ie(e,"name",{value:n,configurable:!0}),"c"),we=Object.defineProperty,E=T((e,n)=>we(e,"name",{value:n,configurable:!0}),"u"),ke=Object.defineProperty,O=E((e,n)=>ke(e,"name",{value:n,configurable:!0}),"u"),$e=Object.defineProperty,P=O((e,n)=>$e(e,"name",{value:n,configurable:!0}),"u"),he=Object.defineProperty,I=P((e,n)=>he(e,"name",{value:n,configurable:!0}),"c"),xe=Object.defineProperty,w=I((e,n)=>xe(e,"name",{value:n,configurable:!0}),"c"),Se=Object.defineProperty,k=w((e,n)=>Se(e,"name",{value:n,configurable:!0}),"c"),Ue=Object.defineProperty,$=k((e,n)=>Ue(e,"name",{value:n,configurable:!0}),"u"),Re=Object.defineProperty,h=$((e,n)=>Re(e,"name",{value:n,configurable:!0}),"u"),Ae=Object.defineProperty,x=h((e,n)=>Ae(e,"name",{value:n,configurable:!0}),"u"),Ce=Object.defineProperty,S=x((e,n)=>Ce(e,"name",{value:n,configurable:!0}),"u"),Fe=Object.defineProperty,U=S((e,n)=>Fe(e,"name",{value:n,configurable:!0}),"u"),Je=Object.defineProperty,R=U((e,n)=>Je(e,"name",{value:n,configurable:!0}),"u"),Le=Object.defineProperty,A=R((e,n)=>Le(e,"name",{value:n,configurable:!0}),"u"),Ne=Object.defineProperty,C=A((e,n)=>Ne(e,"name",{value:n,configurable:!0}),"u"),qe=Object.defineProperty,F=C((e,n)=>qe(e,"name",{value:n,configurable:!0}),"u"),ze=Object.defineProperty,J=F((e,n)=>ze(e,"name",{value:n,configurable:!0}),"s"),Be=Object.defineProperty,L=J((e,n)=>Be(e,"name",{value:n,configurable:!0}),"s"),De=Object.defineProperty,N=L((e,n)=>De(e,"name",{value:n,configurable:!0}),"s"),Ge=Object.defineProperty,q=N((e,n)=>Ge(e,"name",{value:n,configurable:!0}),"c"),He=Object.defineProperty,z=q((e,n)=>He(e,"name",{value:n,configurable:!0}),"c"),Ke=Object.defineProperty,B=z((e,n)=>Ke(e,"name",{value:n,configurable:!0}),"c"),Me=Object.defineProperty,D=B((e,n)=>Me(e,"name",{value:n,configurable:!0}),"c"),Qe=Object.defineProperty,G=D((e,n)=>Qe(e,"name",{value:n,configurable:!0}),"c"),Ve=Object.defineProperty,H=G((e,n)=>Ve(e,"name",{value:n,configurable:!0}),"c"),We=Object.defineProperty,K=H((e,n)=>We(e,"name",{value:n,configurable:!0}),"l"),Xe=Object.defineProperty,M=K((e,n)=>Xe(e,"name",{value:n,configurable:!0}),"l"),Ye=Object.defineProperty,Q=M((e,n)=>Ye(e,"name",{value:n,configurable:!0}),"c"),Ze=Object.defineProperty,V=Q((e,n)=>Ze(e,"name",{value:n,configurable:!0}),"c"),_e=Object.defineProperty,W=V((e,n)=>_e(e,"name",{value:n,configurable:!0}),"c"),en=Object.defineProperty,X=W((e,n)=>en(e,"name",{value:n,configurable:!0}),"u"),nn=Object.defineProperty,Y=X((e,n)=>nn(e,"name",{value:n,configurable:!0}),"u"),tn=Object.defineProperty,Z=Y((e,n)=>tn(e,"name",{value:n,configurable:!0}),"u"),rn=Object.defineProperty,_=Z((e,n)=>rn(e,"name",{value:n,configurable:!0}),"c"),on=Object.defineProperty,ee=_((e,n)=>on(e,"name",{value:n,configurable:!0}),"c"),an=Object.defineProperty,ne=ee((e,n)=>an(e,"name",{value:n,configurable:!0}),"c"),ln=Object.defineProperty,te=ne((e,n)=>ln(e,"name",{value:n,configurable:!0}),"u"),cn=Object.defineProperty,re=te((e,n)=>cn(e,"name",{value:n,configurable:!0}),"u"),un=Object.defineProperty,oe=re((e,n)=>un(e,"name",{value:n,configurable:!0}),"u"),vn=Object.defineProperty,ie=oe((e,n)=>vn(e,"name",{value:n,configurable:!0}),"u"),sn=Object.defineProperty,ae=ie((e,n)=>sn(e,"name",{value:n,configurable:!0}),"o"),fn=Object.defineProperty,le=ae((e,n)=>fn(e,"name",{value:n,configurable:!0}),"o"),pn=Object.defineProperty,ce=le((e,n)=>pn(e,"name",{value:n,configurable:!0}),"o"),dn=Object.defineProperty,ue=ce((e,n)=>dn(e,"name",{value:n,configurable:!0}),"o"),bn=Object.defineProperty,ve=ue((e,n)=>bn(e,"name",{value:n,configurable:!0}),"o"),mn=Object.defineProperty,se=ve((e,n)=>mn(e,"name",{value:n,configurable:!0}),"o");import{fingerprint as l,ulid as c}from"@lensmcp/core";const gn="pending";export function envelopeToEvent(e,n=gn){const i=e.tabId?{tabId:e.tabId}:{};switch(e.kind){case"error":return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`error:${t(e.stack)}`,file:e.source,detail:e.message}),title:e.message,message:e.stack??e.message,location:e.source?{file:e.source,line:e.lineno,column:e.colno}:void 0,raw:e};case"unhandledrejection":return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"error",context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`unhandledrejection:${e.reason.slice(0,80)}`}),title:"Unhandled promise rejection",message:e.reason,raw:e};case"console":{const a=e.level==="error"?"error":e.level==="warn"?"warning":"info",fe=e.args.map(o).join(" ");return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:a,context:{sessionId:n,...i},fingerprint:l({kind:"runtime",identity:`console:${e.level}:${fe.slice(0,80)}`}),title:`console.${e.level}`,message:fe,raw:e}}case"fetch":{const a=e.error!==void 0||e.status!==void 0&&e.status>=400;return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"network",severity:a?"error":"info",context:{sessionId:n,...i},fingerprint:l({kind:"network",identity:`${e.method}:${r(e.url)}:${e.status??"err"}`}),title:`${e.method} ${e.url}${e.status!==void 0?` \u2192 ${e.status}`:""}`,message:e.error,relatedUrls:[e.url],raw:e}}case"route":return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...i},fingerprint:l({kind:"runtime",identity:`route:${e.url}`}),title:`Route \u2192 ${e.url}`,raw:e};case"ready":return{id:c(),sessionId:n,timestamp:e.at,source:"client-runtime",category:"runtime",severity:"info",context:{sessionId:n,url:e.url,...i},fingerprint:l({kind:"runtime",identity:"client-ready"}),title:"Client runtime ready",message:e.userAgent,raw:e};case"publish":return{id:c(),sessionId:n,timestamp:e.at,source:e.source,category:e.category,severity:e.severity??"info",context:{sessionId:n,...i,...e.context??{}},fingerprint:e.fingerprint??l({kind:"publish",identity:e.title}),title:e.title,message:e.message,raw:e.raw}}}u(envelopeToEvent,"envelopeToEvent"),v(envelopeToEvent,"envelopeToEvent"),s(envelopeToEvent,"envelopeToEvent"),f(envelopeToEvent,"envelopeToEvent"),p(envelopeToEvent,"envelopeToEvent"),d(envelopeToEvent,"envelopeToEvent"),b(envelopeToEvent,"envelopeToEvent"),m(envelopeToEvent,"envelopeToEvent"),g(envelopeToEvent,"envelopeToEvent"),y(envelopeToEvent,"envelopeToEvent"),j(envelopeToEvent,"envelopeToEvent"),T(envelopeToEvent,"envelopeToEvent"),E(envelopeToEvent,"envelopeToEvent"),O(envelopeToEvent,"envelopeToEvent"),P(envelopeToEvent,"envelopeToEvent"),I(envelopeToEvent,"envelopeToEvent"),w(envelopeToEvent,"envelopeToEvent"),k(envelopeToEvent,"envelopeToEvent"),$(envelopeToEvent,"envelopeToEvent"),h(envelopeToEvent,"envelopeToEvent"),x(envelopeToEvent,"envelopeToEvent"),S(envelopeToEvent,"envelopeToEvent"),U(envelopeToEvent,"envelopeToEvent"),R(envelopeToEvent,"envelopeToEvent"),A(envelopeToEvent,"envelopeToEvent"),C(envelopeToEvent,"envelopeToEvent"),F(envelopeToEvent,"envelopeToEvent"),J(envelopeToEvent,"envelopeToEvent"),L(envelopeToEvent,"envelopeToEvent"),N(envelopeToEvent,"envelopeToEvent"),q(envelopeToEvent,"envelopeToEvent"),z(envelopeToEvent,"envelopeToEvent"),B(envelopeToEvent,"envelopeToEvent"),D(envelopeToEvent,"envelopeToEvent"),G(envelopeToEvent,"envelopeToEvent"),H(envelopeToEvent,"envelopeToEvent"),K(envelopeToEvent,"envelopeToEvent"),M(envelopeToEvent,"envelopeToEvent"),Q(envelopeToEvent,"envelopeToEvent"),V(envelopeToEvent,"envelopeToEvent"),W(envelopeToEvent,"envelopeToEvent"),X(envelopeToEvent,"envelopeToEvent"),Y(envelopeToEvent,"envelopeToEvent"),Z(envelopeToEvent,"envelopeToEvent"),_(envelopeToEvent,"envelopeToEvent"),ee(envelopeToEvent,"envelopeToEvent"),ne(envelopeToEvent,"envelopeToEvent"),te(envelopeToEvent,"envelopeToEvent"),re(envelopeToEvent,"envelopeToEvent"),oe(envelopeToEvent,"envelopeToEvent"),ie(envelopeToEvent,"envelopeToEvent"),ae(envelopeToEvent,"envelopeToEvent"),le(envelopeToEvent,"envelopeToEvent"),ce(envelopeToEvent,"envelopeToEvent"),ue(envelopeToEvent,"envelopeToEvent"),ve(envelopeToEvent,"envelopeToEvent"),se(envelopeToEvent,"envelopeToEvent");function t(e){if(!e)return"unknown";const n=e.split(`
|
|
2
|
+
`).slice(0,2);for(const i of n){const a=i.match(/at\s+(\S+)/);if(a&&a[1])return a[1]}return"unknown"}u(t,"t"),v(t,"t"),s(t,"t"),f(t,"t"),p(t,"t"),d(t,"t"),b(t,"t"),m(t,"t"),g(t,"t"),y(t,"t"),j(t,"t"),T(t,"t"),E(t,"t"),O(t,"t"),P(t,"t"),I(t,"t"),w(t,"t"),k(t,"t"),$(t,"t"),h(t,"t"),x(t,"t"),S(t,"t"),U(t,"t"),R(t,"t"),A(t,"t"),C(t,"t"),F(t,"t"),J(t,"t"),L(t,"t"),N(t,"t"),q(t,"t"),z(t,"t"),B(t,"t"),D(t,"n"),G(t,"t"),H(t,"n"),K(t,"t"),M(t,"n"),Q(t,"t"),V(t,"n"),W(t,"t"),X(t,"r"),Y(t,"t"),Z(t,"r"),_(t,"t"),ee(t,"n"),ne(t,"n"),te(t,"r"),re(t,"n"),oe(t,"o"),ie(t,"a"),ae(t,"l"),le(t,"l"),ce(t,"c"),ue(t,"c"),ve(t,"s"),se(t,"normaliseStackTopFrame");function r(e){try{return`${new URL(e,"http://x").pathname}`}catch{return e}}u(r,"r"),v(r,"r"),s(r,"r"),f(r,"r"),p(r,"r"),d(r,"r"),b(r,"r"),m(r,"r"),g(r,"r"),y(r,"r"),j(r,"r"),T(r,"r"),E(r,"r"),O(r,"r"),P(r,"r"),I(r,"r"),w(r,"r"),k(r,"r"),$(r,"r"),h(r,"r"),x(r,"r"),S(r,"r"),U(r,"r"),R(r,"r"),A(r,"r"),C(r,"r"),F(r,"r"),J(r,"r"),L(r,"r"),N(r,"r"),q(r,"r"),z(r,"r"),B(r,"r"),D(r,"r"),G(r,"r"),H(r,"r"),K(r,"r"),M(r,"r"),Q(r,"r"),V(r,"r"),W(r,"r"),X(r,"n"),Y(r,"r"),Z(r,"n"),_(r,"r"),ee(r,"r"),ne(r,"r"),te(r,"i"),re(r,"i"),oe(r,"a"),ie(r,"c"),ae(r,"d"),le(r,"d"),ce(r,"l"),ue(r,"l"),ve(r,"m"),se(r,"normaliseUrl");function o(e){if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}u(o,"o"),v(o,"o"),s(o,"o"),f(o,"o"),p(o,"o"),d(o,"o"),b(o,"o"),m(o,"o"),g(o,"o"),y(o,"o"),j(o,"o"),T(o,"o"),E(o,"o"),O(o,"o"),P(o,"o"),I(o,"o"),w(o,"o"),k(o,"o"),$(o,"o"),h(o,"o"),x(o,"o"),S(o,"o"),U(o,"o"),R(o,"i"),A(o,"o"),C(o,"i"),F(o,"i"),J(o,"i"),L(o,"i"),N(o,"i"),q(o,"i"),z(o,"i"),B(o,"i"),D(o,"i"),G(o,"i"),H(o,"i"),K(o,"i"),M(o,"i"),Q(o,"i"),V(o,"i"),W(o,"i"),X(o,"i"),Y(o,"i"),Z(o,"i"),_(o,"i"),ee(o,"i"),ne(o,"i"),te(o,"o"),re(o,"o"),oe(o,"c"),ie(o,"u"),ae(o,"m"),le(o,"m"),ce(o,"d"),ue(o,"d"),ve(o,"d"),se(o,"stringify");
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
import { type EventBus } from '@lensmcp/core';
|
|
3
3
|
import type { LensmcpViteOptions, LensmcpViteRuntime } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The dependency cache this app's Vite should use when the checkout's `node_modules` is NOT its own — a symlink to
|
|
6
|
+
* another checkout's, as agent worktrees are made — or `undefined` to leave Vite's choice alone.
|
|
7
|
+
*
|
|
8
|
+
* Vite keeps its pre-bundled dependencies under `node_modules/.vite` (Nx apps pin `cacheDir` there). Through a
|
|
9
|
+
* symlinked `node_modules` two checkouts' Vites then optimize into ONE folder, and each rewrite is a "new
|
|
10
|
+
* dependencies optimized, reloading" for the other's open tabs. So the cache moves to the checkout's own
|
|
11
|
+
* `.lensmcp/cache/vite/…` (git-ignored with the rest of `.lensmcp/`), keeping the path below `node_modules/`.
|
|
12
|
+
* A checkout that owns its `node_modules` is untouched. Mirrors `@lensmcp/cluster`'s checkout-cache.ts.
|
|
13
|
+
*/
|
|
14
|
+
export declare function checkoutViteCacheDir(root: string | undefined, cacheDir: string | undefined, cwd?: string): string | undefined;
|
|
4
15
|
/**
|
|
5
16
|
* PURE (fs reads only): the extra roots to add to Vite's watcher for a source SCOPE the gateway handed over
|
|
6
17
|
* (`LENSMCP_WATCH_DIRS`, `path.delimiter`-joined absolute dirs — e.g. `<ws>/web:<ws>/shared:<ws>/ai`).
|
package/lib/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var $=Object.defineProperty;var g=(e,t)=>$(e,"name",{value:t,configurable:!0});var G=Object.defineProperty,h=g((e,t)=>G(e,"name",{value:t,configurable:!0}),"u");import{randomBytes as J}from"node:crypto";import{existsSync as O,readdirSync as q,realpathSync as N,statSync as z}from"node:fs";import{delimiter as Z,dirname as L,join as _,relative as I,resolve as k,sep as A}from"node:path";import{fingerprint as C,ulid as F}from"@lensmcp/core";import{BRIDGE_TOKEN_PARAM as Q,createBridgeServer as ee,makePublisher as x,handleClientEnvelope as te}from"@lensmcp/bridge";import{CLIENT_VIRTUAL_ID as H,createRuntimeModuleGuard as ne,RESOLVED_CLIENT_VIRTUAL_ID as j}from"./runtime-guard.js";const se="/__lensmcp/watcher-health",V=256,oe=64,re=new Set(["node_modules",".git","dist","tmp","coverage","build","out","target",".nx",".lensmcp"]);export function checkoutViteCacheDir(e,t,r=process.cwd()){const p=k(r,e??".");let i;if(t)i=k(p,t);else{let m=p;for(;!O(_(m,"package.json"))&&L(m)!==m;)m=L(m);i=_(O(_(m,"package.json"))?m:p,"node_modules",".vite")}const f=i.split(A),c=f.indexOf("node_modules");if(c<1)return;const s=f.slice(0,c).join(A)||A;try{if(N(_(s,"node_modules"))===_(N(s),"node_modules"))return}catch{return}const l=f.slice(c+1);return _(s,".lensmcp","cache","vite",...l[0]===".vite"?l.slice(1):l)}g(checkoutViteCacheDir,"checkoutViteCacheDir"),h(checkoutViteCacheDir,"checkoutViteCacheDir");export function resolveExtraWatchRoots(e,t){if(!e)return[];const r=k(t),p=h((c,s)=>{const l=I(s,c);return l!==""&&!l.startsWith("..")&&!l.startsWith(A)&&!/^[A-Za-z]:/.test(l)},"inside"),i=[],f=h(c=>{i.length<oe&&!i.includes(c)&&i.push(c)},"push");for(const c of e.split(Z)){const s=c.trim();if(!s)continue;const l=k(s);let m;try{m=O(l)&&z(l).isDirectory()}catch{m=!1}if(m&&!(l===r||p(l,r))){if(p(r,l)){const S=I(l,r).split(A).filter(Boolean);let w=l;for(const n of S){let a;try{a=q(w,{withFileTypes:!0})}catch{a=[]}for(const o of a)!o.isDirectory()||o.name===n||o.name.startsWith(".")||re.has(o.name)||f(_(w,o.name));w=_(w,n)}continue}f(l)}}return i}g(resolveExtraWatchRoots,"resolveExtraWatchRoots"),h(resolveExtraWatchRoots,"resolveExtraWatchRoots");export function lensmcpVitePlugin(e={},t={}){const r=e.enabled??!0,p=e.wsHost??"127.0.0.1",i=e.injectClient??!0;let f,c,s;const l=x(t),m=e.aliasValtio??!0,S=e.transformSource??!0,w=e.applyOnBuild??process.env.LENSMCP_VITE_APPLY_ON_BUILD==="1";return{name:"@lensmcp/vite-plugin",apply:h((n,a)=>r&&(a.command==="serve"||w),"apply"),enforce:"pre",wsPort:h(()=>f,"wsPort"),config(n){if(!r)return;const a=checkoutViteCacheDir(n?.root,n?.cacheDir);a&&console.log(`[lensmcp] node_modules is shared with another checkout \u2014 Vite's dependency cache moves to ${a}`);const o=process.env.LENSMCP_FREEZE==="1";o&&console.log("[lensmcp] frozen worktree instance \u2014 Vite watches no files");const u={...a?{cacheDir:a}:{},...o?{server:{watch:null}}:{}};return m?{...u,resolve:{alias:[{find:/^valtio$/,replacement:"@lensmcp/valtio-instrumentation"}]}}:Object.keys(u).length?u:void 0},async configureServer(n){if(!r)return;s=J(24).toString("base64url"),c=await ee({host:p,port:e.wsPort??0,bus:t.bus,sessionId:t.sessionId,token:s,...e.wsAllowedOrigins?{allowedOrigins:e.wsAllowedOrigins}:{}}),f=c.port,n.middlewares.use(ne(c)),n.ws.on("vite:error",d=>{l(X(d,t.sessionId))});const a=resolveExtraWatchRoots(process.env.LENSMCP_WATCH_DIRS,n.config?.root??process.cwd());if(a.length>0&&typeof n.watcher?.add=="function")try{n.watcher.add(a),console.log(`[lensmcp] watching ${a.length} extra source root(s) for set changes: ${a.map(d=>I(n.config?.root??process.cwd(),d)||d).join(", ")}`)}catch(d){console.warn(`[lensmcp] could not widen the watcher: ${d?.message??String(d)}`)}let o=0,u=0,R=0;const E=[],v=h((d,y)=>{o=Date.now(),u+=1,R+=1,E.push({sequence:R,kind:d,path:k(y),at:o}),E.length>V&&E.splice(0,E.length-V)},"stampFsSetEvent");n.watcher.on("add",d=>v("add",d)),n.watcher.on("addDir",d=>v("addDir",d)),n.watcher.on("unlink",d=>v("unlink",d)),n.watcher.on("unlinkDir",d=>v("unlinkDir",d)),n.middlewares.use(se,(d,y)=>{y.setHeader("content-type","application/json"),y.setHeader("cache-control","no-store"),y.end(JSON.stringify({schemaVersion:2,ready:!0,sequence:R,lastFsSetEventAt:o,fsSetEvents:u,recentEvents:E,extraWatchRoots:a,now:Date.now()}))})},resolveId(n){return n===H?j:null},load(n){if(n!==j)return null;const a=f??e.wsPort??0;return Y({host:p,port:a,token:s,hmrRecovery:e.hmrRecovery!==!1})},async transform(n,a){if(!r||!S)return null;const o=a.split("?")[0]??a;if(o.includes("/node_modules/")||!/\.[jt]sx$/.test(o))return null;const u=await P(n,o);return u?{code:u.code,map:u.map}:null},transformIndexHtml(){if(!(!i||!r))return[{tag:"script",attrs:{type:"module",src:H},injectTo:"head-prepend"}]},handleHotUpdate(n){if(!r)return;const a=n.modules.map(u=>u.file??u.id??null).filter(u=>!!u),o=hmrReloadCause(n.modules);l({id:F(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:o.willFullReload?"warning":"info",context:{sessionId:t.sessionId??"pending"},fingerprint:C({kind:"hmr",identity:n.file}),title:o.willFullReload?`HMR ${n.file} \u2014 FULL RELOAD (no boundary above ${o.deadEnd??"it"})`:`HMR ${n.file}`,location:{file:n.file},relatedFiles:a,raw:{kind:"hmr",file:n.file,willFullReload:o.willFullReload,deadEnd:o.deadEnd,chain:o.chain,acceptedBy:o.boundaries}})},generateBundle(n,a){if(!r)return;const o=[];let u=0;for(const E of Object.values(a)){const v=E,d=v.fileName;let y;if(v.type==="chunk")y=(v.code??"").length;else{const b=v.source;y=typeof b=="string"?b.length:b instanceof Uint8Array?b.byteLength:0}u+=y;const M=[];if(v.type==="chunk"&&v.modules){for(const[b,T]of Object.entries(v.modules)){const K=T.renderedLength??T.originalLength??0;M.push({id:b,sizeBytes:K})}M.sort((b,T)=>T.sizeBytes-b.sizeBytes)}o.push({name:d,sizeBytes:y,modules:M})}const R={timestamp:Date.now(),totalBytes:u,chunks:o};l({id:F(),sessionId:t.sessionId??"pending",timestamp:Date.now(),source:"rollup",category:"build",severity:"info",context:{sessionId:t.sessionId??"pending"},fingerprint:C({kind:"bundle",identity:"report"}),title:`Bundle report: ${o.length} chunks, ${u} bytes`,raw:{kind:"bundle-report",report:R}})},async closeBundle(){c&&(await c.close(),c=void 0,s=void 0)}}}g(lensmcpVitePlugin,"lensmcpVitePlugin"),h(lensmcpVitePlugin,"lensmcpVitePlugin");const D=12;export function hmrReloadCause(e){const t=[],r=[],p=new Set;let i=null;const f=h(s=>s.file??s.id??"(anonymous module)","nameOf"),c=h((s,l)=>{if(p.has(s))return!1;if(p.add(s),s.isSelfAccepting===!0)return r.length<D&&r.push(f(s)),!1;const m=[...s.importers??[]];if(m.length===0)return i===null&&(i=f(s),t.push(...l.slice(-D),f(s))),!0;let S=!1;for(const w of m){if(w.acceptedHmrDeps?.has(s)){r.length<D&&r.push(f(w));continue}c(w,[...l,f(s)])&&(S=!0)}return S},"walk");return{willFullReload:e.map(s=>c(s,[])).some(Boolean),deadEnd:i,chain:t,boundaries:r}}g(hmrReloadCause,"hmrReloadCause"),h(hmrReloadCause,"hmrReloadCause");async function P(e,t){try{const[{transformSync:r},{lensmcpBabelPlugin:p}]=await Promise.all([import("@babel/core"),import("@lensmcp/react-instrumentation")]),i=r(e,{filename:t,babelrc:!1,configFile:!1,sourceMaps:!0,parserOpts:{plugins:["jsx","typescript"]},plugins:[[p,{rootDir:process.cwd()}]],code:!0});return i?.code?{code:i.code,map:i.map}:null}catch(r){return U(t,r),null}}g(P,"Q"),h(P,"runLensmcpBabel");const B=new Set;function U(e,t){B.has(e)||(B.add(e),console.warn(`[lensmcp] could not instrument ${e}: ${t?.message??t}. The file still builds, but its renders, effects and hooks will be MISSING from react://renders/*, react://effects/recent and react://hooks/recent.`))}g(U,"ee"),h(U,"warnTransformFailed");export function resetTransformFailuresForTests(){B.clear()}g(resetTransformFailuresForTests,"resetTransformFailuresForTests"),h(resetTransformFailuresForTests,"resetTransformFailuresForTests");export function handleClientEnvelope(e,t){te(e,x(t),t.sessionId)}g(handleClientEnvelope,"handleClientEnvelope"),h(handleClientEnvelope,"handleClientEnvelope");function W(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}:{}}}g(W,"te"),h(W,"errorLocation");function X(e,t){const r=e.err?.message??"Vite error",p=e.err?.stack,i=W(e.err);return{id:F(),sessionId:t??"pending",timestamp:Date.now(),source:"vite",category:"build",severity:"error",context:{sessionId:t??"pending"},fingerprint:C({kind:"vite",identity:e.type??r,...i?{file:i.file}:{}}),title:r,message:p,...i?{location:i}:{},raw:{kind:"build-diagnostic",...i?{file:i.file}:{},...e.err?.plugin?{plugin:e.err.plugin}:{}}}}g(X,"ne"),h(X,"viteErrorToEvent");function Y(e){const t=`ws://${e.host}:${e.port}${e.token?`/?${Q}=${encodeURIComponent(e.token)}`:""}`;return`// @lensmcp/client-runtime (inlined by @lensmcp/vite-plugin)
|
|
2
2
|
(() => {
|
|
3
3
|
if (typeof window === 'undefined') return;
|
|
4
4
|
if (window.__LENSMCP_CLIENT__) return;
|
|
@@ -208,10 +208,50 @@
|
|
|
208
208
|
});
|
|
209
209
|
hot.on('vite:invalidate', (p) => send({ kind: 'hmr', event: 'invalidate', path: p && p.path, message: p && p.message }));
|
|
210
210
|
hot.on('vite:ws:disconnect', () => send({ kind: 'hmr', event: 'ws-disconnect' }));
|
|
211
|
-
hot.on('vite:ws:connect', () =>
|
|
211
|
+
hot.on('vite:ws:connect', () => {
|
|
212
|
+
try { sessionStorage.setItem(HMR_OK_KEY, '1'); } catch (e) {}
|
|
213
|
+
send({ kind: 'hmr', event: 'ws-connect' });
|
|
214
|
+
});
|
|
212
215
|
}
|
|
213
216
|
} catch (e) { /* strictly passive */ }
|
|
214
217
|
|
|
218
|
+
// HMR SOCKET WATCHDOG (issues/closed/foodguard-49). Vite opens its HMR socket once, at page load; when that
|
|
219
|
+
// socket and its direct fallback both close before opening, it prints "[vite] failed to connect to
|
|
220
|
+
// websocket" and NEVER tries again. The page still works \u2014 it just runs the code it loaded, gets no hot
|
|
221
|
+
// update and no reload notice (not even the next dev-server handoff's), and says so nowhere. A tab
|
|
222
|
+
// reloaded by a lens handoff into a still-cold Vite came back exactly like that on FoodGuard.
|
|
223
|
+
// So: always report it, and \u2014 in a tab whose socket WORKED earlier in this session (the handoff case),
|
|
224
|
+
// at most once per HMR_RECOVERY_COOLDOWN_MS \u2014 reload once, which opens a fresh socket against whichever
|
|
225
|
+
// Vite serves the page now. A tab whose socket never connected (HMR off, a proxy that drops WebSockets)
|
|
226
|
+
// is reported and left alone: reloading it would only loop.
|
|
227
|
+
const HMR_OK_KEY = 'lensmcp:hmrOk';
|
|
228
|
+
const HMR_RECOVERY_KEY = 'lensmcp:hmrRecoveryAt';
|
|
229
|
+
const HMR_RECOVERY = ${JSON.stringify(e.hmrRecovery!==!1)};
|
|
230
|
+
const HMR_RECOVERY_COOLDOWN_MS = 120000;
|
|
231
|
+
let hmrGaveUp = false;
|
|
232
|
+
function onViteGaveUp(text) {
|
|
233
|
+
if (hmrGaveUp) return;
|
|
234
|
+
hmrGaveUp = true;
|
|
235
|
+
let workedBefore = false;
|
|
236
|
+
let lastRecovery = 0;
|
|
237
|
+
try {
|
|
238
|
+
workedBefore = sessionStorage.getItem(HMR_OK_KEY) === '1';
|
|
239
|
+
lastRecovery = Number(sessionStorage.getItem(HMR_RECOVERY_KEY)) || 0;
|
|
240
|
+
} catch (e) {}
|
|
241
|
+
const recovering = HMR_RECOVERY && workedBefore && Date.now() - lastRecovery > HMR_RECOVERY_COOLDOWN_MS;
|
|
242
|
+
send({ kind: 'hmr', event: 'ws-failed', recovering, message: String(text).slice(0, 2000) });
|
|
243
|
+
if (recovering) {
|
|
244
|
+
try {
|
|
245
|
+
sessionStorage.setItem(HMR_RECOVERY_KEY, String(Date.now()));
|
|
246
|
+
// The next document's \`ready\` says why it was reloaded (the same channel a Vite full reload uses).
|
|
247
|
+
sessionStorage.setItem('lensmcp:reloadCause', JSON.stringify({ triggeredBy: 'lensmcp: the Vite HMR socket failed to connect', at: Date.now() }));
|
|
248
|
+
} catch (e) {}
|
|
249
|
+
}
|
|
250
|
+
flushBatch(); // before a reload unloads the page, or so the report is not left in the macrotask queue
|
|
251
|
+
if (recovering) setTimeout(() => { try { location.reload(); } catch (e) {} }, 0);
|
|
252
|
+
}
|
|
253
|
+
const VITE_GAVE_UP = '[vite] failed to connect to websocket';
|
|
254
|
+
|
|
215
255
|
// Bridge for in-page instrumentation (@lensmcp/react-instrumentation,
|
|
216
256
|
// @lensmcp/valtio-instrumentation). Their publish() calls land here and
|
|
217
257
|
// ride the same WS as a 'publish' envelope, so React renders + Valtio
|
|
@@ -293,7 +333,12 @@
|
|
|
293
333
|
const original = console[level];
|
|
294
334
|
console[level] = function(...args) {
|
|
295
335
|
try { send({ kind: 'console', level, args: args.map((a) => safe(a)) }); } catch {}
|
|
296
|
-
|
|
336
|
+
const result = original.apply(this, args);
|
|
337
|
+
// Vite's only signal that it gave up on the HMR socket is this line (see the HMR watchdog above).
|
|
338
|
+
if (level === 'error' && typeof args[0] === 'string' && args[0].indexOf(VITE_GAVE_UP) === 0) {
|
|
339
|
+
try { onViteGaveUp(args[0]); } catch (e) {}
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
297
342
|
};
|
|
298
343
|
}
|
|
299
344
|
|
|
@@ -370,4 +415,4 @@
|
|
|
370
415
|
try { return safeWalk(v, 0, { left: SAFE_MAX_BYTES }); } catch (e) { return String(v); }
|
|
371
416
|
}
|
|
372
417
|
})();
|
|
373
|
-
`}g(
|
|
418
|
+
`}g(Y,"se"),h(Y,"buildInlineClientRuntime");
|
package/lib/types.d.ts
CHANGED
|
@@ -28,6 +28,15 @@ export interface LensmcpViteOptions {
|
|
|
28
28
|
aliasValtio?: boolean;
|
|
29
29
|
/** Run the LensMCP source transform (root-wrap + hooks + JSX ids). Default true. */
|
|
30
30
|
transformSource?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* When Vite gives up opening its HMR socket at page load ("[vite] failed to connect to websocket"), the
|
|
33
|
+
* page still works but never gets another hot update or reload notice — it silently runs the code it
|
|
34
|
+
* loaded. The injected runtime always reports that (`hmr` `ws-failed`). With this on (the default) it
|
|
35
|
+
* also RELOADS the page once, but only in a tab whose HMR socket worked earlier in its session (the
|
|
36
|
+
* case a dev-server handoff leaves behind — issues/closed/foodguard-49), and at most once per two
|
|
37
|
+
* minutes, so a setup where the socket never connects is reported, never looped. `false` = report only.
|
|
38
|
+
*/
|
|
39
|
+
hmrRecovery?: boolean;
|
|
31
40
|
/**
|
|
32
41
|
* Also attach during `vite build`. Default **false** — everything this plugin does (the WS
|
|
33
42
|
* bridge, the injected client runtime, `data-agent-source` on every element, source paths in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/vite-plugin",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.2",
|
|
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.24.
|
|
19
|
-
"@lensmcp/core": "1.24.
|
|
20
|
-
"@lensmcp/protocol-types": "1.24.
|
|
21
|
-
"@lensmcp/react-instrumentation": "1.24.
|
|
22
|
-
"@lensmcp/valtio-instrumentation": "1.24.
|
|
18
|
+
"@lensmcp/bridge": "1.24.2",
|
|
19
|
+
"@lensmcp/core": "1.24.2",
|
|
20
|
+
"@lensmcp/protocol-types": "1.24.2",
|
|
21
|
+
"@lensmcp/react-instrumentation": "1.24.2",
|
|
22
|
+
"@lensmcp/valtio-instrumentation": "1.24.2",
|
|
23
23
|
"tslib": "^2.3.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|