vite-plugin-sri4 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,29 +6,9 @@ var crypto = require('crypto');
6
6
  var path = require('path');
7
7
  var fetch = require('cross-fetch');
8
8
 
9
- // Constants definition
10
- const VITE_INTERNAL_ANALYSIS_PLUGIN = 'vite:build-import-analysis';
11
- const DEFAULT_TIMEOUT = 5000;
12
- const DEFAULT_HASH_ALGORITHM = 'sha384';
13
- const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
14
-
15
- // Optimized regex patterns for better readability and efficiency
16
- const HTML_PATTERNS = {
17
- script: {
18
- regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
19
- endOffset: 10
20
- },
21
- stylesheet: {
22
- regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
23
- endOffset: 1
24
- },
25
- modulepreload: {
26
- regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
27
- endOffset: 1
28
- }
29
- };
30
-
31
- // Extended caching mechanism with expiration time
9
+ /**
10
+ * Extended caching mechanism with expiration time
11
+ */
32
12
  class ResourceCache {
33
13
  constructor(ttl = 3600000) { // Default cache for 1 hour
34
14
  this.cache = new Map();
@@ -64,11 +44,35 @@ class ResourceCache {
64
44
  }
65
45
  }
66
46
 
67
- const urlSupportCache = new ResourceCache();
68
- const resourceCache = new ResourceCache();
47
+ /**
48
+ * Cache manager for plugin instances
49
+ */
50
+ class CacheManager {
51
+ constructor() {
52
+ this.urlSupportCache = new ResourceCache();
53
+ this.resourceCache = new ResourceCache();
54
+ }
69
55
 
70
- // Check if URL is from a bypass domain
71
- function isUrlFromBypassDomain(url, bypassDomains = []) {
56
+ getUrlSupportCache() {
57
+ return this.urlSupportCache
58
+ }
59
+
60
+ getResourceCache() {
61
+ return this.resourceCache
62
+ }
63
+
64
+ clearAll() {
65
+ this.urlSupportCache.clear();
66
+ this.resourceCache.clear();
67
+ }
68
+ }
69
+
70
+ const DEFAULT_TIMEOUT = 5000;
71
+
72
+ /**
73
+ * Check if URL is from a bypass domain
74
+ */
75
+ function isUrlFromBypassDomain(url, bypassDomains = [], logger = null) {
72
76
  if (!url || typeof url !== 'string' || !url.startsWith('http')) return false
73
77
 
74
78
  try {
@@ -77,13 +81,17 @@ function isUrlFromBypassDomain(url, bypassDomains = []) {
77
81
  urlObj.hostname === domain || urlObj.hostname.endsWith(`.${domain}`)
78
82
  )
79
83
  } catch (error) {
80
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Invalid URL: ${url}`, error);
84
+ if (logger) {
85
+ logger.warn(`Invalid URL: ${url}`, error);
86
+ }
81
87
  return false
82
88
  }
83
89
  }
84
90
 
85
- // Resource check with retry mechanism
86
- async function checkResourceSupport(url, retries = 2) {
91
+ /**
92
+ * Resource check with retry mechanism
93
+ */
94
+ async function checkResourceSupport(url, urlSupportCache, logger = null, retries = 2) {
87
95
  if (urlSupportCache.has(url)) {
88
96
  return urlSupportCache.get(url)
89
97
  }
@@ -108,7 +116,9 @@ async function checkResourceSupport(url, retries = 2) {
108
116
  } catch (error) {
109
117
  lastError = error;
110
118
  if (error.name === 'AbortError') {
111
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Resource check timed out: ${url}`);
119
+ if (logger) {
120
+ logger.warn(`Resource check timed out: ${url}`);
121
+ }
112
122
  break // Don't retry timeouts
113
123
  }
114
124
 
@@ -119,13 +129,17 @@ async function checkResourceSupport(url, retries = 2) {
119
129
  }
120
130
  }
121
131
 
122
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Failed to check resource support: ${url}`, lastError);
132
+ if (logger) {
133
+ logger.warn(`Failed to check resource support: ${url}`, lastError);
134
+ }
123
135
  urlSupportCache.set(url, false);
124
136
  return false
125
137
  }
126
138
 
127
- // Optimized resource fetching function with retry mechanism and caching
128
- async function fetchResource(url, retries = 1) {
139
+ /**
140
+ * Optimized resource fetching function with retry mechanism and caching
141
+ */
142
+ async function fetchResource(url, resourceCache, logger = null, retries = 1) {
129
143
  // Check cache
130
144
  if (resourceCache.has(url)) {
131
145
  return resourceCache.get(url)
@@ -150,7 +164,9 @@ async function fetchResource(url, retries = 1) {
150
164
  } catch (error) {
151
165
  lastError = error;
152
166
  if (error.name === 'AbortError') {
153
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Resource fetch timed out: ${url}`);
167
+ if (logger) {
168
+ logger.warn(`Resource fetch timed out: ${url}`);
169
+ }
154
170
  break // Don't retry timeouts
155
171
  }
156
172
 
@@ -160,144 +176,373 @@ async function fetchResource(url, retries = 1) {
160
176
  }
161
177
  }
162
178
 
163
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Failed to fetch external resource: ${url}`, lastError);
179
+ if (logger) {
180
+ logger.warn(`Failed to fetch external resource: ${url}`, lastError);
181
+ }
164
182
  return null
165
183
  }
166
184
 
167
- function createTransformer(options, config) {
168
- const {
169
- ignoreMissingAsset,
170
- bypassDomains,
171
- hashAlgorithm = DEFAULT_HASH_ALGORITHM
172
- } = options;
173
-
174
- // Improved method for getting bundle keys
175
- const getBundleKey = (htmlPath, url) => {
176
- // Handle absolute path URLs
177
- if (url.startsWith('/')) {
178
- // Remove leading slash to match keys in bundle
179
- return url.substring(1)
180
- }
181
-
182
- // Handle relative paths (when config.base is relative)
183
- if (config.base === './' || config.base === '') {
184
- return path.posix.resolve(path.posix.dirname(htmlPath), url)
185
- }
185
+ /**
186
+ * Improved method for getting bundle keys
187
+ */
188
+ function getBundleKey(htmlPath, url, config) {
189
+ // Handle absolute path URLs
190
+ if (url.startsWith('/')) {
191
+ // Remove leading slash to match keys in bundle
192
+ return url.substring(1)
193
+ }
186
194
 
187
- // Handle other cases, remove base prefix from URL
188
- return url.startsWith(config.base)
189
- ? url.substring(config.base.length)
190
- : url
191
- };
195
+ // Handle relative paths (when config.base is relative)
196
+ if (config.base === './' || config.base === '') {
197
+ return path.posix.resolve(path.posix.dirname(htmlPath), url)
198
+ }
192
199
 
193
- const calculateIntegrity = async (bundle, htmlPath, url) => {
194
- // Skip specified domains
195
- if (isUrlFromBypassDomain(url, bypassDomains)) {
196
- return null
197
- }
200
+ // Handle other cases, remove base prefix from URL
201
+ return url.startsWith(config.base)
202
+ ? url.substring(config.base.length)
203
+ : url
204
+ }
198
205
 
199
- let source;
200
- if (url.startsWith('http')) {
201
- const isSupported = await checkResourceSupport(url);
202
- if (!isSupported) return null
203
- source = await fetchResource(url);
204
- if (!source) return null
205
- } else {
206
- const bundleKey = getBundleKey(htmlPath, url);
207
- const bundleItem = bundle[bundleKey];
206
+ /**
207
+ * Calculate SRI integrity hash for a given resource
208
+ */
209
+ async function calculateIntegrity(
210
+ bundle,
211
+ htmlPath,
212
+ url,
213
+ options,
214
+ config,
215
+ cacheManager,
216
+ logger = null
217
+ ) {
218
+ const {
219
+ ignoreMissingAsset,
220
+ bypassDomains,
221
+ hashAlgorithm
222
+ } = options;
208
223
 
209
- if (!bundleItem) {
210
- // Try to find a matching item with more flexible matching
211
- const possibleMatch = Object.keys(bundle).find(key =>
212
- key.endsWith(bundleKey) || bundleKey.endsWith(key)
213
- );
224
+ // Skip specified domains
225
+ if (isUrlFromBypassDomain(url, bypassDomains, logger)) {
226
+ return null
227
+ }
214
228
 
215
- if (possibleMatch) {
216
- source = bundle[possibleMatch].type === 'chunk'
217
- ? bundle[possibleMatch].code
218
- : bundle[possibleMatch].source;
219
- } else if (ignoreMissingAsset) {
220
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
221
- return null
222
- } else {
223
- throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
229
+ let source;
230
+ if (url.startsWith('http')) {
231
+ const isSupported = await checkResourceSupport(url, cacheManager.getUrlSupportCache(), logger);
232
+ if (!isSupported) return null
233
+ source = await fetchResource(url, cacheManager.getResourceCache(), logger);
234
+ if (!source) return null
235
+ } else {
236
+ const bundleKey = getBundleKey(htmlPath, url, config);
237
+ const bundleItem = bundle[bundleKey];
238
+
239
+ if (!bundleItem) {
240
+ // Try to find a matching item with more flexible matching
241
+ const possibleMatch = Object.keys(bundle).find(key =>
242
+ key.endsWith(bundleKey) || bundleKey.endsWith(key)
243
+ );
244
+
245
+ if (possibleMatch) {
246
+ source = bundle[possibleMatch].type === 'chunk'
247
+ ? bundle[possibleMatch].code
248
+ : bundle[possibleMatch].source;
249
+ } else if (ignoreMissingAsset) {
250
+ if (logger) {
251
+ logger.warn(`Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
224
252
  }
253
+ return null
225
254
  } else {
226
- source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
255
+ throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
227
256
  }
257
+ } else {
258
+ source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
228
259
  }
260
+ }
229
261
 
230
- // Ensure source is a Uint8Array or string
231
- if (!source) return null
262
+ // Ensure source is a Uint8Array or string
263
+ if (!source) return null
264
+
265
+ if (typeof source === 'string') {
266
+ return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
267
+ }
268
+
269
+ return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
270
+ }
271
+
272
+ // Optimized regex patterns for better readability and efficiency
273
+ const HTML_PATTERNS = {
274
+ script: {
275
+ regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
276
+ endOffset: 10
277
+ },
278
+ stylesheet: {
279
+ regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
280
+ endOffset: 1
281
+ },
282
+ modulepreload: {
283
+ regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
284
+ endOffset: 1
285
+ }
286
+ };
232
287
 
233
- if (typeof source === 'string') {
234
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
288
+ /**
289
+ * Validate HTML input
290
+ */
291
+ function validateHtmlInput(html, htmlPath, logger) {
292
+ if (!html || typeof html !== 'string') {
293
+ logger.warn(`Invalid HTML content for ${htmlPath}`);
294
+ return false
295
+ }
296
+ return true
297
+ }
298
+
299
+ /**
300
+ * Process a single match to create an integrity change object
301
+ */
302
+ async function processMatch(
303
+ match,
304
+ endOffset,
305
+ bundle,
306
+ htmlPath,
307
+ options,
308
+ config,
309
+ cacheManager,
310
+ logger
311
+ ) {
312
+ const [, url] = match;
313
+ if (!url) return null
314
+
315
+ const end = match.index + match[0].length;
316
+ const integrity = await calculateIntegrity(
317
+ bundle,
318
+ htmlPath,
319
+ url,
320
+ options,
321
+ config,
322
+ cacheManager,
323
+ logger
324
+ );
325
+
326
+ if (integrity) {
327
+ return {
328
+ integrity,
329
+ position: end - endOffset,
330
+ url // For logging
235
331
  }
332
+ }
333
+ return null
334
+ }
335
+
336
+ /**
337
+ * Process matches for a specific HTML pattern
338
+ */
339
+ async function processPatternMatches(
340
+ html,
341
+ pattern,
342
+ bundle,
343
+ htmlPath,
344
+ options,
345
+ config,
346
+ cacheManager,
347
+ logger
348
+ ) {
349
+ const { regex, endOffset } = pattern;
350
+ const matches = [...html.matchAll(regex)];
351
+
352
+ // Process each match in parallel
353
+ const matchResults = await Promise.all(
354
+ matches.map(match =>
355
+ processMatch(match, endOffset, bundle, htmlPath, options, config, cacheManager, logger)
356
+ )
357
+ );
236
358
 
237
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
238
- };
359
+ // Filter out null results
360
+ return matchResults.filter(Boolean)
361
+ }
239
362
 
240
- const transformHTML = async (bundle, htmlPath, html) => {
241
- if (!html || typeof html !== 'string') {
242
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Invalid HTML content for ${htmlPath}`);
243
- return html
363
+ /**
364
+ * Collect all integrity changes from HTML patterns
365
+ */
366
+ async function collectIntegrityChanges(
367
+ html,
368
+ bundle,
369
+ htmlPath,
370
+ options,
371
+ config,
372
+ cacheManager,
373
+ logger
374
+ ) {
375
+ const changes = [];
376
+
377
+ // Collect changes from all patterns in parallel
378
+ await Promise.all(
379
+ Object.values(HTML_PATTERNS).map(async pattern => {
380
+ const patternChanges = await processPatternMatches(
381
+ html,
382
+ pattern,
383
+ bundle,
384
+ htmlPath,
385
+ options,
386
+ config,
387
+ cacheManager,
388
+ logger
389
+ );
390
+ changes.push(...patternChanges);
391
+ })
392
+ );
393
+
394
+ return changes
395
+ }
396
+
397
+ /**
398
+ * Check if integrity attribute already exists in HTML segment
399
+ */
400
+ function hasExistingIntegrity(html, position, integrity) {
401
+ const segment = html.slice(Math.max(0, position - 100), position + 100);
402
+ return segment.includes(`integrity="${integrity}"`)
403
+ }
404
+
405
+ /**
406
+ * Apply integrity changes to HTML content
407
+ */
408
+ function applyIntegrityChanges(html, changes, logger) {
409
+ // Sort by position in descending order to insert from back to front
410
+ changes.sort((a, b) => b.position - a.position);
411
+
412
+ for (const { integrity, position, url } of changes) {
413
+ // Skip if integrity attribute already exists
414
+ if (hasExistingIntegrity(html, position, integrity)) {
415
+ continue
244
416
  }
245
417
 
246
- const changes = [];
418
+ const insertText = ` integrity="${integrity}"`;
419
+ html = html.slice(0, position) + insertText + html.slice(position);
420
+ logger.debug(`Added integrity for: ${url}`);
421
+ }
247
422
 
248
- // Collect changes from all patterns in parallel
249
- await Promise.all(
250
- Object.values(HTML_PATTERNS).map(async ({ regex, endOffset }) => {
251
- const matches = [...html.matchAll(regex)];
423
+ return html
424
+ }
252
425
 
253
- // Process each match in parallel
254
- const matchResults = await Promise.all(
255
- matches.map(async match => {
256
- const [, url] = match;
257
- if (!url) return null
426
+ /**
427
+ * Transform HTML by adding SRI integrity attributes
428
+ */
429
+ async function transformHTML(
430
+ bundle,
431
+ htmlPath,
432
+ html,
433
+ options,
434
+ config,
435
+ cacheManager,
436
+ logger
437
+ ) {
438
+ if (!validateHtmlInput(html, htmlPath, logger)) {
439
+ return html
440
+ }
258
441
 
259
- const end = match.index + match[0].length;
260
- const integrity = await calculateIntegrity(bundle, htmlPath, url);
442
+ const changes = await collectIntegrityChanges(
443
+ html,
444
+ bundle,
445
+ htmlPath,
446
+ options,
447
+ config,
448
+ cacheManager,
449
+ logger
450
+ );
451
+
452
+ return applyIntegrityChanges(html, changes, logger)
453
+ }
261
454
 
262
- if (integrity) {
263
- return {
264
- integrity,
265
- position: end - endOffset,
266
- url // For logging
267
- }
268
- }
269
- return null
270
- })
271
- );
455
+ /**
456
+ * Create HTML transformer with given options and config
457
+ */
458
+ function createTransformer(options, config, cacheManager, logger) {
459
+ return {
460
+ transformHTML: (bundle, htmlPath, html) =>
461
+ transformHTML(bundle, htmlPath, html, options, config, cacheManager, logger),
462
+ calculateIntegrity: (bundle, htmlPath, url) =>
463
+ calculateIntegrity(bundle, htmlPath, url, options, config, cacheManager, logger)
464
+ }
465
+ }
272
466
 
273
- // Filter out null results
274
- matchResults.filter(Boolean).forEach(result => changes.push(result));
275
- })
276
- );
467
+ const DEFAULT_PLUGIN_NAME$1 = 'vite-plugin-sri4';
468
+
469
+ /**
470
+ * Logger class to handle different log levels without hijacking global console
471
+ */
472
+ class Logger {
473
+ constructor(logLevel = 'warn', pluginName = DEFAULT_PLUGIN_NAME$1) {
474
+ this.logLevel = logLevel;
475
+ this.pluginName = pluginName;
476
+ this.levels = {
477
+ silent: 0,
478
+ error: 1,
479
+ warn: 2,
480
+ info: 3,
481
+ debug: 4
482
+ };
483
+ this.currentLevel = this.levels[logLevel] || this.levels.warn;
484
+ }
277
485
 
278
- // Sort by position in descending order to insert from back to front (won't affect insertion points ahead)
279
- changes.sort((a, b) => b.position - a.position);
486
+ /**
487
+ * Format message with plugin name prefix
488
+ */
489
+ formatMessage(message, ...args) {
490
+ const prefix = `[${this.pluginName}]`;
491
+ if (typeof message === 'string') {
492
+ return [prefix + ' ' + message, ...args]
493
+ }
494
+ return [prefix, message, ...args]
495
+ }
280
496
 
281
- // Check if identical integrity attributes already exist to avoid duplicates
282
- for (const { integrity, position, url } of changes) {
283
- const insertText = ` integrity="${integrity}"`;
497
+ /**
498
+ * Log error messages
499
+ */
500
+ error(message, ...args) {
501
+ if (this.currentLevel >= this.levels.error) {
502
+ console.error(...this.formatMessage(message, ...args));
503
+ }
504
+ }
284
505
 
285
- // Check if integrity attribute already exists
286
- const segment = html.slice(Math.max(0, position - 100), position + 100);
287
- if (segment.includes(`integrity="${integrity}"`)) {
288
- continue // Skip elements that already have the same integrity
289
- }
506
+ /**
507
+ * Log warning messages
508
+ */
509
+ warn(message, ...args) {
510
+ if (this.currentLevel >= this.levels.warn) {
511
+ console.warn(...this.formatMessage(message, ...args));
512
+ }
513
+ }
290
514
 
291
- html = html.slice(0, position) + insertText + html.slice(position);
292
- console.debug(`[${DEFAULT_PLUGIN_NAME}] Added integrity for: ${url}`);
515
+ /**
516
+ * Log info messages
517
+ */
518
+ info(message, ...args) {
519
+ if (this.currentLevel >= this.levels.info) {
520
+ console.info(...this.formatMessage(message, ...args));
293
521
  }
522
+ }
294
523
 
295
- return html
296
- };
524
+ /**
525
+ * Log debug messages
526
+ */
527
+ debug(message, ...args) {
528
+ if (this.currentLevel >= this.levels.debug) {
529
+ console.debug(...this.formatMessage(message, ...args));
530
+ }
531
+ }
297
532
 
298
- return { transformHTML, calculateIntegrity }
533
+ /**
534
+ * Create a child logger with the same configuration
535
+ */
536
+ child(name) {
537
+ return new Logger(this.logLevel, `${this.pluginName}:${name}`)
538
+ }
299
539
  }
300
540
 
541
+ // Constants definition
542
+ const VITE_INTERNAL_ANALYSIS_PLUGIN = 'vite:build-import-analysis';
543
+ const DEFAULT_HASH_ALGORITHM = 'sha384';
544
+ const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
545
+
301
546
  function sri(options = {}) {
302
547
  const {
303
548
  ignoreMissingAsset = false,
@@ -306,16 +551,9 @@ function sri(options = {}) {
306
551
  logLevel = 'warn'
307
552
  } = options;
308
553
 
309
- // Adjust log level
310
- const originalConsoleWarn = console.warn;
311
- const originalConsoleDebug = console.debug;
312
-
313
- if (logLevel === 'error') {
314
- console.warn = () => {};
315
- console.debug = () => {};
316
- } else if (logLevel === 'warn') {
317
- console.debug = () => {};
318
- }
554
+ // Create cache manager and logger instances for this plugin instance
555
+ const cacheManager = new CacheManager();
556
+ const logger = new Logger(logLevel, DEFAULT_PLUGIN_NAME);
319
557
 
320
558
  return {
321
559
  name: DEFAULT_PLUGIN_NAME,
@@ -324,13 +562,8 @@ function sri(options = {}) {
324
562
 
325
563
  // Cleanup work
326
564
  buildEnd() {
327
- // Restore console functions
328
- console.warn = originalConsoleWarn;
329
- console.debug = originalConsoleDebug;
330
-
331
565
  // Clear caches
332
- urlSupportCache.clear();
333
- resourceCache.clear();
566
+ cacheManager.clearAll();
334
567
  },
335
568
 
336
569
  configResolved(config) {
@@ -338,7 +571,7 @@ function sri(options = {}) {
338
571
  ignoreMissingAsset,
339
572
  bypassDomains,
340
573
  hashAlgorithm
341
- }, config);
574
+ }, config, cacheManager, logger);
342
575
 
343
576
  const generateBundle = async function(_, bundle) {
344
577
  const htmlFiles = Object.entries(bundle).filter(
@@ -348,7 +581,7 @@ function sri(options = {}) {
348
581
  );
349
582
 
350
583
  if (htmlFiles.length === 0) {
351
- console.debug(`[${DEFAULT_PLUGIN_NAME}] No HTML files found in bundle`);
584
+ logger.debug('No HTML files found in bundle');
352
585
  return
353
586
  }
354
587
 
@@ -360,10 +593,10 @@ function sri(options = {}) {
360
593
  chunk.source = await transformer.transformHTML(bundle, name, originalContent);
361
594
 
362
595
  if (originalContent !== chunk.source) {
363
- console.debug(`[${DEFAULT_PLUGIN_NAME}] SRI attributes added to ${name}`);
596
+ logger.debug(`SRI attributes added to ${name}`);
364
597
  }
365
598
  } catch (error) {
366
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Error processing ${name}:`, error);
599
+ logger.warn(`Error processing ${name}:`, error);
367
600
  // Keep original content on error
368
601
  }
369
602
  })