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