@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 +0 -1
- package/index.js +1 -4
- package/lib/event-mappers.d.ts +0 -1
- package/lib/event-mappers.js +2 -169
- package/lib/plugin.d.ts +0 -1
- package/lib/plugin.js +50 -304
- package/lib/types.d.ts +0 -1
- package/lib/types.js +1 -1
- package/package.json +6 -6
- package/index.d.ts.map +0 -1
- package/lib/event-mappers.d.ts.map +0 -1
- package/lib/plugin.d.ts.map +0 -1
- package/lib/types.d.ts.map +0 -1
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
export
|
|
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";
|
package/lib/event-mappers.d.ts
CHANGED
package/lib/event-mappers.js
CHANGED
|
@@ -1,169 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
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
package/lib/plugin.js
CHANGED
|
@@ -1,307 +1,16 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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
|
|
42
|
+
} catch (e) { /* strictly passive \u2014 never break the host page */ }
|
|
334
43
|
})();
|
|
335
44
|
|
|
336
|
-
const WS_URL = 'ws://${
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
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
|
+
"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.
|
|
19
|
-
"@lensmcp/core": "1.18.
|
|
20
|
-
"@lensmcp/protocol-types": "1.18.
|
|
21
|
-
"@lensmcp/react-instrumentation": "1.18.
|
|
22
|
-
"@lensmcp/valtio-instrumentation": "1.18.
|
|
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"}
|
package/lib/plugin.d.ts.map
DELETED
|
@@ -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"}
|
package/lib/types.d.ts.map
DELETED
|
@@ -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"}
|