wu-framework 1.1.3 → 1.1.5

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.
@@ -4,25 +4,37 @@
4
4
  * Comparte automáticamente estilos de node_modules entre padre e hijos Shadow DOM
5
5
  * Soluciona el problema de aislamiento CSS en microfrontends
6
6
  *
7
- * 🚀 STYLE MODES:
8
- * - 'isolated': MFE solo tiene sus propios estilos (CSS variables SÍ se heredan)
9
- * - 'fully-isolated': Aislamiento TOTAL - resetea CSS variables del padre
10
- * - 'shared': Comparte todos los estilos del padre
11
- * - 'auto': Comparte solo librerías específicas (element-plus, etc.)
7
+ * MODOS DE INYECCIÓN DE ESTILOS:
8
+ * ================================
9
+ *
10
+ * 1. "shared" (default):
11
+ * - Inyecta TODOS los estilos del documento padre en el Shadow DOM
12
+ * - Incluye: librerías (Element Plus, Vue Flow), estilos globales, etc.
13
+ * - Ideal para: Apps que necesitan compartir un design system común
14
+ * - Riesgo de colisiones: ALTO
15
+ *
16
+ * 2. "isolated":
17
+ * - NO inyecta estilos externos
18
+ * - Usa el encapsulamiento NATIVO del Shadow DOM
19
+ * - La app debe incluir sus propios estilos (CSS-in-JS, scoped styles, etc.)
20
+ * - Ideal para: Apps con estilos completamente independientes
21
+ * - Riesgo de colisiones: NINGUNO
22
+ *
23
+ * 3. "fully-isolated":
24
+ * - Inyecta SOLO los estilos propios de la micro-app específica
25
+ * - Detecta estilos por patrón: packages/appName/src/
26
+ * - Usa MutationObserver para HMR de Vite
27
+ * - Ideal para: Apps que necesitan sus estilos pero no los globales
28
+ * - Riesgo de colisiones: NINGUNO
12
29
  */
13
30
 
14
31
  export class WuStyleBridge {
15
32
  constructor() {
16
33
  this.sharedStyles = new Map();
17
34
  this.styleObserver = null;
18
- this.appStyleModes = new Map(); // Per-app style isolation mode
35
+ this.fullyIsolatedApps = new Map(); // Mapa de appName -> appUrl para apps con fully-isolated
19
36
  this.config = {
20
- // 🛡️ DEFAULT MODE: 'isolated' = cada MFE tiene sus propios estilos
21
- // 'fully-isolated' = aislamiento total incluyendo CSS variables
22
- // 'shared' = compartir estilos del padre
23
- // 'auto' = compartir solo librerías específicas
24
- defaultMode: 'isolated',
25
- // Librerías que se deben compartir automáticamente (solo en modo 'auto')
37
+ // Librerías que se deben compartir automáticamente
26
38
  autoShareLibraries: [
27
39
  'element-plus',
28
40
  'vue-flow',
@@ -32,56 +44,94 @@ export class WuStyleBridge {
32
44
  'normalize.css',
33
45
  'reset.css'
34
46
  ],
35
- // Patrones de URLs a compartir (solo en modo 'auto' o 'shared')
47
+ // Patrones de URLs a compartir
36
48
  sharePatterns: [
37
49
  /\/node_modules\//,
38
50
  /\/@vite\/client/,
39
51
  /\/dist\/index\.css$/,
40
52
  /\/dist\/style\.css$/
41
53
  ],
54
+ // Modo de compartición
55
+ mode: 'auto', // 'auto' | 'manual' | 'all'
42
56
  // Caché de estilos
43
57
  cacheEnabled: true
44
58
  };
45
59
 
46
- console.log('[WuStyleBridge] 🎨 Style sharing system initialized (default: isolated)');
60
+ console.log('[WuStyleBridge] 🎨 Style sharing system initialized');
47
61
  }
48
62
 
49
63
  /**
50
- * 🛡️ SET APP STYLE MODE: Configura el modo de estilos para una app específica
64
+ * 🛡️ REGISTRAR APP FULLY-ISOLATED: Registra una app con fully-isolated para filtrar sus estilos
51
65
  * @param {string} appName - Nombre de la app
52
- * @param {'isolated' | 'shared' | 'auto'} mode - Modo de estilos
53
- * @param {string} appUrl - URL base del MFE (opcional)
66
+ * @param {string} appUrl - URL base de la app
54
67
  */
55
- setAppStyleMode(appName, mode, appUrl = null) {
56
- this.appStyleModes.set(appName, mode);
57
- if (appUrl) {
58
- this.appUrls = this.appUrls || new Map();
59
- this.appUrls.set(appName, appUrl);
60
- }
61
- console.log(`[WuStyleBridge] 🎨 Style mode for ${appName}: ${mode}${appUrl ? ` (url: ${appUrl})` : ''}`);
68
+ registerFullyIsolatedApp(appName, appUrl) {
69
+ this.fullyIsolatedApps.set(appName, appUrl);
70
+ console.log(`[WuStyleBridge] 🛡️ Registered fully-isolated app: ${appName} (${appUrl})`);
62
71
  }
63
72
 
64
73
  /**
65
- * 🔗 GET APP URL: Obtiene la URL de una app
66
- * @param {string} appName - Nombre de la app
67
- * @returns {string|null}
74
+ * 🔍 VERIFICAR SI ESTILO ES DE APP FULLY-ISOLATED: Verifica si un estilo proviene de una app con fully-isolated
75
+ * @param {string|Object|HTMLElement} styleUrlOrElement - URL del estilo, objeto de estilo, o elemento DOM
76
+ * @returns {boolean}
68
77
  */
69
- getAppUrl(appName) {
70
- return this.appUrls?.get(appName) || null;
71
- }
78
+ isStyleFromFullyIsolatedApp(styleUrlOrElement) {
79
+ let url = '';
80
+
81
+ // Si es un string, usar directamente
82
+ if (typeof styleUrlOrElement === 'string') {
83
+ url = styleUrlOrElement;
84
+ }
85
+ // Si es un elemento DOM (HTMLElement) - verificar si tiene getAttribute (método común de elementos DOM)
86
+ else if (styleUrlOrElement && typeof styleUrlOrElement.getAttribute === 'function') {
87
+ // Obtener data-vite-dev-id o href del elemento
88
+ url = styleUrlOrElement.getAttribute('data-vite-dev-id') || styleUrlOrElement.href || '';
89
+ }
90
+ // Si es un objeto con propiedades
91
+ else if (styleUrlOrElement) {
92
+ if (styleUrlOrElement.href) {
93
+ url = styleUrlOrElement.href;
94
+ } else if (styleUrlOrElement.viteId) {
95
+ url = styleUrlOrElement.viteId;
96
+ } else if (styleUrlOrElement.element) {
97
+ if (typeof styleUrlOrElement.element.getAttribute === 'function') {
98
+ url = styleUrlOrElement.element.getAttribute('data-vite-dev-id') || styleUrlOrElement.element.href || '';
99
+ } else if (styleUrlOrElement.element.href) {
100
+ url = styleUrlOrElement.element.href;
101
+ }
102
+ }
103
+ }
72
104
 
73
- /**
74
- * 🔍 GET APP STYLE MODE: Obtiene el modo de estilos de una app
75
- * @param {string} appName - Nombre de la app
76
- * @returns {'isolated' | 'shared' | 'auto'}
77
- */
78
- getAppStyleMode(appName) {
79
- return this.appStyleModes.get(appName) || this.config.defaultMode;
105
+ if (!url || url.trim() === '') return false;
106
+
107
+ // Normalizar la URL para comparación (convertir backslashes a forward slashes)
108
+ const normalizedUrl = url.replace(/\\/g, '/').toLowerCase();
109
+
110
+ // Verificar si la URL pertenece a alguna app con fully-isolated
111
+ for (const [appName, appUrl] of this.fullyIsolatedApps.entries()) {
112
+ const normalizedAppUrl = appUrl.replace(/\\/g, '/').toLowerCase();
113
+ const normalizedAppName = appName.toLowerCase();
114
+
115
+ // Verificar si la URL contiene la URL base de la app (ej: http://localhost:4001)
116
+ if (normalizedAppUrl && normalizedUrl.includes(normalizedAppUrl)) {
117
+ return true;
118
+ }
119
+
120
+ // Verificar si la URL contiene rutas del app en el sistema de archivos
121
+ // Ej: C:/Users/.../header/src/... o /header/src/...
122
+ // Patrón: cualquier ruta que contenga /header/ o \header\
123
+ const appPathPattern = new RegExp(`[/\\\\]${normalizedAppName}[/\\\\]`, 'i');
124
+ if (appPathPattern.test(normalizedUrl)) {
125
+ return true;
126
+ }
127
+ }
128
+
129
+ return false;
80
130
  }
81
131
 
82
132
  /**
83
133
  * 🔍 DETECTAR ESTILOS: Escanea todos los estilos del documento
84
- * @returns {Array} Lista de estilos detectados
134
+ * @returns {Array} Lista de estilos detectados (filtrados para excluir apps con fully-isolated)
85
135
  */
86
136
  detectDocumentStyles() {
87
137
  const styles = [];
@@ -89,6 +139,11 @@ export class WuStyleBridge {
89
139
  // 1. Detectar TODOS los <link> tags de CSS
90
140
  const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
91
141
  linkTags.forEach((link) => {
142
+ // Filtrar estilos de apps con fully-isolated
143
+ if (this.isStyleFromFullyIsolatedApp(link)) {
144
+ return;
145
+ }
146
+
92
147
  styles.push({
93
148
  type: 'link',
94
149
  href: link.href,
@@ -108,6 +163,12 @@ export class WuStyleBridge {
108
163
  const viteId = style.getAttribute('data-vite-dev-id');
109
164
  const content = style.textContent;
110
165
 
166
+ // Filtrar estilos de apps con fully-isolated (después de obtener viteId para mejor detección)
167
+ if (this.isStyleFromFullyIsolatedApp(style) || (viteId && this.isStyleFromFullyIsolatedApp(viteId))) {
168
+ console.log(`[WuStyleBridge] 🛡️ Filtered out style from fully-isolated app: ${viteId || 'unknown'}`);
169
+ return;
170
+ }
171
+
111
172
  // Incluir todos los estilos con contenido
112
173
  if (content && content.trim().length > 0) {
113
174
  styles.push({
@@ -180,61 +241,42 @@ export class WuStyleBridge {
180
241
  }
181
242
 
182
243
  /**
183
- * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM según el modo
244
+ * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM
184
245
  * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
185
246
  * @param {string} appName - Nombre de la app
186
- * @param {string} appUrl - URL base del MFE (opcional, para detectar sus propios estilos)
247
+ * @param {string} styleMode - Modo de estilos: 'shared', 'isolated', 'fully-isolated'
187
248
  * @returns {Promise<number>}
188
249
  */
189
- async injectStylesIntoShadow(shadowRoot, appName, appUrl = null) {
250
+ async injectStylesIntoShadow(shadowRoot, appName, styleMode) {
190
251
  if (!shadowRoot) {
191
252
  console.warn('[WuStyleBridge] ⚠️ No shadow root provided');
192
253
  return 0;
193
254
  }
194
255
 
195
- // 🛡️ CHECK STYLE MODE
196
- const styleMode = this.getAppStyleMode(appName);
197
- // Use stored URL if not provided
198
- const resolvedAppUrl = appUrl || this.getAppUrl(appName);
199
- console.log(`[WuStyleBridge] 🌉 Injecting styles into ${appName} (mode: ${styleMode}, url: ${resolvedAppUrl || 'none'})...`);
200
-
201
- // 🔒 FULLY-ISOLATED MODE: Inject CSS variables reset FIRST
256
+ // 🛡️ MODO FULLY-ISOLATED: No inyectar ningún estilo compartido
257
+ // Los estilos propios se manejan en wu-sandbox.js con injectOwnStylesToShadow
202
258
  if (styleMode === 'fully-isolated') {
203
- this.injectCSSVariablesReset(shadowRoot, appName);
259
+ console.log(`[WuStyleBridge] 🛡️ Style mode "fully-isolated" for ${appName}, skipping shared style injection`);
260
+ return 0;
204
261
  }
205
262
 
263
+ // 🔒 MODO ISOLATED: No inyectar estilos externos - usar encapsulamiento nativo de Shadow DOM
264
+ // La app debe manejar sus propios estilos (CSS-in-JS, scoped styles, imports directos)
265
+ if (styleMode === 'isolated') {
266
+ console.log(`[WuStyleBridge] 🔒 Style mode "isolated" for ${appName}, using native Shadow DOM encapsulation (no external styles)`);
267
+ return 0;
268
+ }
269
+
270
+ // 🌐 MODO SHARED (default): Inyectar todos los estilos compartidos del documento
271
+ console.log(`[WuStyleBridge] 🌐 Style mode "shared" for ${appName}, injecting all shared styles...`);
272
+
206
273
  // Detectar estilos del documento
207
274
  const styles = this.detectDocumentStyles();
208
275
  let injectedCount = 0;
209
276
 
210
- // Inyectar cada estilo según el modo
277
+ // Inyectar cada estilo
211
278
  for (const style of styles) {
212
279
  try {
213
- const styleSource = style.href || style.viteId || '';
214
-
215
- // 🎯 DETECTAR SI EL ESTILO PERTENECE AL MFE
216
- const isOwnStyle = this.isStyleFromApp(styleSource, appName, resolvedAppUrl);
217
-
218
- // 🛡️ En modo 'isolated' o 'fully-isolated': SOLO inyectar estilos propios del MFE
219
- if (styleMode === 'isolated' || styleMode === 'fully-isolated') {
220
- if (!isOwnStyle) {
221
- const shortSource = styleSource.split(/[/\\]/).slice(-2).join('/');
222
- console.log(`[WuStyleBridge] 🚫 ${appName} skipping foreign style: ${shortSource}`);
223
- continue; // Skip parent/shell styles
224
- }
225
- console.log(`[WuStyleBridge] 🎨 ${appName} own style: ${styleSource.split(/[/\\]/).slice(-2).join('/')}`);
226
- }
227
-
228
- // 🎯 En modo 'auto': compartir librerías + estilos propios
229
- if (styleMode === 'auto') {
230
- const shouldShare = this.shouldShareStyle(styleSource);
231
- if (!shouldShare && !isOwnStyle) {
232
- continue; // Skip this style
233
- }
234
- }
235
-
236
- // 'shared' mode: inject everything
237
-
238
280
  switch (style.type) {
239
281
  case 'link':
240
282
  await this.injectLinkStyle(shadowRoot, style);
@@ -256,246 +298,10 @@ export class WuStyleBridge {
256
298
  }
257
299
  }
258
300
 
259
- console.log(`[WuStyleBridge] ✅ Injected ${injectedCount} styles into ${appName}`);
301
+ console.log(`[WuStyleBridge] ✅ Injected ${injectedCount} shared styles into ${appName}`);
260
302
  return injectedCount;
261
303
  }
262
304
 
263
- /**
264
- * 🔒 INJECT CSS VARIABLES RESET: Resetea las CSS variables heredadas del padre
265
- * Este método detecta todas las CSS variables definidas en el documento padre
266
- * y las resetea a 'initial' dentro del Shadow DOM para lograr aislamiento total.
267
- *
268
- * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar el reset
269
- * @param {string} appName - Nombre de la app para logging
270
- */
271
- injectCSSVariablesReset(shadowRoot, appName) {
272
- // Verificar si ya existe el reset
273
- const existingReset = shadowRoot.querySelector('style[data-wu-css-reset="true"]');
274
- if (existingReset) {
275
- console.log(`[WuStyleBridge] ⏭️ CSS variables reset already exists for ${appName}`);
276
- return;
277
- }
278
-
279
- // 🔍 Detectar todas las CSS variables del documento padre
280
- const parentVariables = this.detectParentCSSVariables();
281
-
282
- if (parentVariables.length === 0) {
283
- console.log(`[WuStyleBridge] 🔒 No parent CSS variables to reset for ${appName}`);
284
- return;
285
- }
286
-
287
- // 🎨 Generar CSS reset
288
- const resetCSS = this.generateCSSVariablesReset(parentVariables);
289
-
290
- // Crear style tag con el reset
291
- const resetStyle = document.createElement('style');
292
- resetStyle.setAttribute('data-wu-css-reset', 'true');
293
- resetStyle.setAttribute('data-wu-app', appName);
294
- resetStyle.textContent = resetCSS;
295
-
296
- // Insertar al PRINCIPIO del shadow root (antes de otros estilos)
297
- shadowRoot.insertBefore(resetStyle, shadowRoot.firstChild);
298
-
299
- console.log(`[WuStyleBridge] 🔒 CSS variables reset injected for ${appName} (${parentVariables.length} variables blocked)`);
300
- }
301
-
302
- /**
303
- * 🔍 DETECT PARENT CSS VARIABLES: Detecta todas las CSS variables del documento padre
304
- * @returns {Array<{name: string, value: string}>} Lista de variables detectadas
305
- */
306
- detectParentCSSVariables() {
307
- const variables = [];
308
- const seen = new Set();
309
-
310
- try {
311
- // 1. Buscar en :root y html
312
- const rootStyles = getComputedStyle(document.documentElement);
313
-
314
- // 2. Buscar en todos los stylesheets del documento
315
- for (const sheet of document.styleSheets) {
316
- try {
317
- // Algunos stylesheets (cross-origin) no permiten acceso
318
- const rules = sheet.cssRules || sheet.rules;
319
- if (!rules) continue;
320
-
321
- for (const rule of rules) {
322
- // Solo procesar reglas de estilo
323
- if (rule.type !== CSSRule.STYLE_RULE) continue;
324
-
325
- // Buscar reglas que definen variables (:root, html, body, *)
326
- const selector = rule.selectorText?.toLowerCase() || '';
327
- if (selector === ':root' || selector === 'html' || selector === 'body' || selector === '*') {
328
- const style = rule.style;
329
- for (let i = 0; i < style.length; i++) {
330
- const prop = style[i];
331
- // Las CSS variables empiezan con --
332
- if (prop.startsWith('--') && !seen.has(prop)) {
333
- seen.add(prop);
334
- variables.push({
335
- name: prop,
336
- value: style.getPropertyValue(prop).trim()
337
- });
338
- }
339
- }
340
- }
341
- }
342
- } catch (e) {
343
- // Ignorar errores de CORS en stylesheets externos
344
- }
345
- }
346
-
347
- // 3. También detectar variables inline en el html element
348
- const htmlStyle = document.documentElement.getAttribute('style') || '';
349
- const inlineVarMatches = htmlStyle.matchAll(/--([\w-]+)\s*:\s*([^;]+)/g);
350
- for (const match of inlineVarMatches) {
351
- const name = `--${match[1]}`;
352
- if (!seen.has(name)) {
353
- seen.add(name);
354
- variables.push({
355
- name,
356
- value: match[2].trim()
357
- });
358
- }
359
- }
360
-
361
- } catch (error) {
362
- console.warn('[WuStyleBridge] ⚠️ Error detecting CSS variables:', error);
363
- }
364
-
365
- return variables;
366
- }
367
-
368
- /**
369
- * 🎨 GENERATE CSS VARIABLES RESET: Genera el CSS que resetea las variables
370
- * @param {Array<{name: string, value: string}>} variables - Variables a resetear
371
- * @returns {string} CSS con el reset
372
- */
373
- generateCSSVariablesReset(variables) {
374
- // 🔒 TÉCNICA: Redefinir cada variable como "unset" dentro del :host
375
- // Esto rompe la cadena de herencia de CSS custom properties
376
- //
377
- // Problema: CSS variables se heredan a través de Shadow DOM por spec
378
- // Solución: Redefinirlas explícitamente para "sobrescribir" el valor heredado
379
- //
380
- // Usamos valores por defecto del navegador para colores comunes
381
- // y cadena vacía para otros
382
-
383
- const resetRules = variables.map(v => {
384
- const name = v.name;
385
- // Detectar tipo de variable por su nombre y asignar valor neutro
386
- if (name.includes('color') || name.includes('bg')) {
387
- return ` ${name}: transparent;`;
388
- } else if (name.includes('size') || name.includes('width') || name.includes('height')) {
389
- return ` ${name}: auto;`;
390
- } else if (name.includes('font')) {
391
- return ` ${name}: inherit;`;
392
- } else {
393
- // Para otras variables, usar valor vacío que las invalida
394
- return ` ${name}: initial;`;
395
- }
396
- }).join('\n');
397
-
398
- return `
399
- /* 🔒 WU-FRAMEWORK: CSS Variables Reset (fully-isolated mode)
400
- * Este estilo BLOQUEA las CSS variables heredadas del padre
401
- * para lograr un aislamiento visual completo.
402
- * Variables bloqueadas: ${variables.length}
403
- *
404
- * El MFE debe definir sus propias variables CSS si las necesita.
405
- * Las variables del padre/shell NO afectarán este componente.
406
- */
407
-
408
- :host {
409
- /* 🛡️ Bloquear herencia de propiedades CSS del padre */
410
- all: initial;
411
- display: block;
412
-
413
- /* 🔒 Redefinir variables del padre como valores neutros */
414
- ${resetRules}
415
- }
416
-
417
- /* Restaurar herencia normal DENTRO del shadow DOM */
418
- :host * {
419
- all: revert;
420
- }
421
- `.trim();
422
- }
423
-
424
- /**
425
- * 🔗 SET APP FOLDER: Asocia una carpeta del sistema de archivos con un MFE
426
- * @param {string} appName - Nombre de la app
427
- * @param {string} folderPath - Ruta de la carpeta (extraída de data-vite-dev-id)
428
- */
429
- setAppFolder(appName, folderPath) {
430
- this.appFolders = this.appFolders || new Map();
431
- this.appFolders.set(appName, folderPath.toLowerCase());
432
- console.log(`[WuStyleBridge] 📁 Folder for ${appName}: ${folderPath}`);
433
- }
434
-
435
- /**
436
- * 🔍 DETECTAR SI UN ESTILO PERTENECE A UN MFE
437
- * @param {string} styleSource - URL o viteId del estilo
438
- * @param {string} appName - Nombre de la app
439
- * @param {string} appUrl - URL base del MFE
440
- * @returns {boolean}
441
- */
442
- isStyleFromApp(styleSource, appName, appUrl) {
443
- if (!styleSource) return false;
444
-
445
- const source = styleSource.toLowerCase();
446
-
447
- // 🎯 PRIORITY 1: Verificar puerto del MFE en localhost URL
448
- // Ej: "http://localhost:3001/src/style.css" pertenece a MFE en puerto 3001
449
- if (appUrl) {
450
- try {
451
- const appPort = new URL(appUrl).port;
452
- if (appPort) {
453
- // Si el estilo viene de localhost con el puerto del MFE
454
- if (source.includes(`localhost:${appPort}`) ||
455
- source.includes(`127.0.0.1:${appPort}`)) {
456
- console.log(`[WuStyleBridge] ✅ Port match: ${appPort} for ${appName}`);
457
- return true;
458
- }
459
- // Si el estilo viene de localhost pero con OTRO puerto, NO es de este MFE
460
- const portMatch = source.match(/localhost:(\d+)|127\.0\.0\.1:(\d+)/);
461
- if (portMatch) {
462
- const stylePort = portMatch[1] || portMatch[2];
463
- if (stylePort && stylePort !== appPort) {
464
- return false;
465
- }
466
- }
467
- }
468
- } catch (e) {
469
- // Ignore URL parse errors
470
- }
471
- }
472
-
473
- // 🎯 PRIORITY 2: Usar la carpeta registrada del MFE
474
- // Cuando el MFE carga, detectamos su carpeta y la guardamos
475
- const appFolder = this.appFolders?.get(appName);
476
- if (appFolder && source.includes(appFolder)) {
477
- console.log(`[WuStyleBridge] ✅ Folder match for ${appName}`);
478
- return true;
479
- }
480
-
481
- // 🎯 PRIORITY 3: Para file paths de Vite (data-vite-dev-id)
482
- // Verificar si el path contiene la carpeta registrada del MFE
483
- // La carpeta ya fue registrada desde wu-core.js con el valor del manifest
484
- // Solo comparar, NO auto-detectar ni sobrescribir
485
- if (appFolder) {
486
- // El path debe contener la carpeta del MFE (e.g., /container/ o /header/)
487
- // Normalizar la búsqueda para Windows y Unix paths
488
- const folderPattern = appFolder.replace(/\//g, '[/\\\\]');
489
- const folderRegex = new RegExp(folderPattern, 'i');
490
- if (folderRegex.test(source)) {
491
- console.log(`[WuStyleBridge] ✅ Folder regex match for ${appName}: ${appFolder}`);
492
- return true;
493
- }
494
- }
495
-
496
- return false;
497
- }
498
-
499
305
  /**
500
306
  * 🔗 INYECTAR LINK STYLE: Clona <link> tag al Shadow DOM
501
307
  * @param {ShadowRoot} shadowRoot