@emasoft/svg-matrix 1.0.30 → 1.0.31
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/bin/svg-matrix.js +310 -61
- package/bin/svglinter.cjs +102 -3
- package/bin/svgm.js +236 -27
- package/package.json +1 -1
- package/src/animation-optimization.js +137 -17
- package/src/animation-references.js +123 -6
- package/src/arc-length.js +213 -4
- package/src/bezier-analysis.js +217 -21
- package/src/bezier-intersections.js +275 -12
- package/src/browser-verify.js +237 -4
- package/src/clip-path-resolver.js +168 -0
- package/src/convert-path-data.js +479 -28
- package/src/css-specificity.js +73 -10
- package/src/douglas-peucker.js +219 -2
- package/src/flatten-pipeline.js +284 -26
- package/src/geometry-to-path.js +250 -25
- package/src/gjk-collision.js +236 -33
- package/src/index.js +261 -3
- package/src/inkscape-support.js +86 -28
- package/src/logger.js +48 -3
- package/src/marker-resolver.js +278 -74
- package/src/mask-resolver.js +265 -66
- package/src/matrix.js +44 -5
- package/src/mesh-gradient.js +352 -102
- package/src/off-canvas-detection.js +382 -13
- package/src/path-analysis.js +192 -18
- package/src/path-data-plugins.js +309 -5
- package/src/path-optimization.js +129 -5
- package/src/path-simplification.js +188 -32
- package/src/pattern-resolver.js +454 -106
- package/src/polygon-clip.js +324 -1
- package/src/svg-boolean-ops.js +226 -9
- package/src/svg-collections.js +7 -5
- package/src/svg-flatten.js +386 -62
- package/src/svg-parser.js +179 -8
- package/src/svg-rendering-context.js +235 -6
- package/src/svg-toolbox.js +45 -8
- package/src/svg2-polyfills.js +40 -10
- package/src/transform-decomposition.js +258 -32
- package/src/transform-optimization.js +259 -13
- package/src/transforms2d.js +82 -9
- package/src/transforms3d.js +62 -10
- package/src/use-symbol-resolver.js +286 -42
- package/src/vector.js +64 -8
- package/src/verification.js +392 -1
package/src/inkscape-support.js
CHANGED
|
@@ -55,11 +55,14 @@ export function findLayers(doc) {
|
|
|
55
55
|
layers.push({
|
|
56
56
|
element: el,
|
|
57
57
|
label: getLayerLabel(el),
|
|
58
|
-
id: el.getAttribute('id')
|
|
58
|
+
id: (typeof el.getAttribute === 'function' ? el.getAttribute('id') : null)
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
// Safety check: ensure children is an array before iteration
|
|
62
|
+
if (Array.isArray(el.children)) {
|
|
63
|
+
for (const child of el.children) {
|
|
64
|
+
walk(child);
|
|
65
|
+
}
|
|
63
66
|
}
|
|
64
67
|
};
|
|
65
68
|
|
|
@@ -75,6 +78,9 @@ export function findLayers(doc) {
|
|
|
75
78
|
* @returns {Object|null} Named view settings or null if not found
|
|
76
79
|
*/
|
|
77
80
|
export function getNamedViewSettings(doc) {
|
|
81
|
+
// Validate doc parameter
|
|
82
|
+
if (!doc) return null;
|
|
83
|
+
|
|
78
84
|
// Find namedview element - may be direct child or nested
|
|
79
85
|
let namedview = null;
|
|
80
86
|
|
|
@@ -84,7 +90,7 @@ export function getNamedViewSettings(doc) {
|
|
|
84
90
|
namedview = el;
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
|
-
if (el.children) {
|
|
93
|
+
if (el.children && Array.isArray(el.children)) {
|
|
88
94
|
for (const child of el.children) {
|
|
89
95
|
findNamedview(child);
|
|
90
96
|
if (namedview) return;
|
|
@@ -93,7 +99,7 @@ export function getNamedViewSettings(doc) {
|
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
findNamedview(doc);
|
|
96
|
-
if (!namedview) return null;
|
|
102
|
+
if (!namedview || typeof namedview.getAttribute !== 'function') return null;
|
|
97
103
|
|
|
98
104
|
return {
|
|
99
105
|
pagecolor: namedview.getAttribute('pagecolor'),
|
|
@@ -118,21 +124,27 @@ export function getNamedViewSettings(doc) {
|
|
|
118
124
|
* @returns {Array<{position: string, orientation: string, id: string|null}>} Array of guide info
|
|
119
125
|
*/
|
|
120
126
|
export function findGuides(doc) {
|
|
127
|
+
// Validate doc parameter
|
|
128
|
+
if (!doc) return [];
|
|
129
|
+
|
|
121
130
|
const guides = [];
|
|
122
131
|
|
|
123
132
|
const walk = (el) => {
|
|
124
133
|
if (!el || !el.children) return;
|
|
125
134
|
if (el.tagName === 'sodipodi:guide') {
|
|
126
135
|
guides.push({
|
|
127
|
-
position: el.getAttribute('position'),
|
|
128
|
-
orientation: el.getAttribute('orientation'),
|
|
129
|
-
id: el.getAttribute('id'),
|
|
130
|
-
inkscapeColor: el.getAttribute('inkscape:color'),
|
|
131
|
-
inkscapeLabel: el.getAttribute('inkscape:label')
|
|
136
|
+
position: el.getAttribute?.('position') || null,
|
|
137
|
+
orientation: el.getAttribute?.('orientation') || null,
|
|
138
|
+
id: el.getAttribute?.('id') || null,
|
|
139
|
+
inkscapeColor: el.getAttribute?.('inkscape:color') || null,
|
|
140
|
+
inkscapeLabel: el.getAttribute?.('inkscape:label') || null
|
|
132
141
|
});
|
|
133
142
|
}
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
// Safety check: ensure children is an array before iteration
|
|
144
|
+
if (Array.isArray(el.children)) {
|
|
145
|
+
for (const child of el.children) {
|
|
146
|
+
walk(child);
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
};
|
|
138
150
|
|
|
@@ -148,7 +160,7 @@ export function findGuides(doc) {
|
|
|
148
160
|
* @returns {Object|null} Arc parameters or null if not an arc
|
|
149
161
|
*/
|
|
150
162
|
export function getArcParameters(element) {
|
|
151
|
-
if (!element) return null;
|
|
163
|
+
if (!element || typeof element.getAttribute !== 'function') return null;
|
|
152
164
|
|
|
153
165
|
const type = element.getAttribute('sodipodi:type');
|
|
154
166
|
if (type !== 'arc') return null;
|
|
@@ -183,7 +195,7 @@ export function getNodeTypes(element) {
|
|
|
183
195
|
* @returns {Object|null} Export settings or null if not set
|
|
184
196
|
*/
|
|
185
197
|
export function getExportSettings(element) {
|
|
186
|
-
if (!element) return null;
|
|
198
|
+
if (!element || typeof element.getAttribute !== 'function') return null;
|
|
187
199
|
|
|
188
200
|
const filename = element.getAttribute('inkscape:export-filename');
|
|
189
201
|
const xdpi = element.getAttribute('inkscape:export-xdpi');
|
|
@@ -191,10 +203,14 @@ export function getExportSettings(element) {
|
|
|
191
203
|
|
|
192
204
|
if (!filename && !xdpi && !ydpi) return null;
|
|
193
205
|
|
|
206
|
+
// Parse DPI values and handle NaN by returning null
|
|
207
|
+
const parsedXdpi = xdpi ? parseFloat(xdpi) : null;
|
|
208
|
+
const parsedYdpi = ydpi ? parseFloat(ydpi) : null;
|
|
209
|
+
|
|
194
210
|
return {
|
|
195
211
|
filename,
|
|
196
|
-
xdpi:
|
|
197
|
-
ydpi:
|
|
212
|
+
xdpi: (parsedXdpi !== null && !isNaN(parsedXdpi)) ? parsedXdpi : null,
|
|
213
|
+
ydpi: (parsedYdpi !== null && !isNaN(parsedYdpi)) ? parsedYdpi : null
|
|
198
214
|
};
|
|
199
215
|
}
|
|
200
216
|
|
|
@@ -205,7 +221,8 @@ export function getExportSettings(element) {
|
|
|
205
221
|
* @returns {boolean} True if element is a tiled clone
|
|
206
222
|
*/
|
|
207
223
|
export function isTiledClone(element) {
|
|
208
|
-
|
|
224
|
+
if (!element || typeof element.hasAttribute !== 'function') return false;
|
|
225
|
+
return element.hasAttribute('inkscape:tiled-clone-of');
|
|
209
226
|
}
|
|
210
227
|
|
|
211
228
|
/**
|
|
@@ -241,6 +258,9 @@ export function hasInkscapeNamespaces(doc) {
|
|
|
241
258
|
* @returns {Object} The document (modified in place)
|
|
242
259
|
*/
|
|
243
260
|
export function ensureInkscapeNamespaces(doc) {
|
|
261
|
+
// Validate doc parameter
|
|
262
|
+
if (!doc) return doc;
|
|
263
|
+
|
|
244
264
|
const svg = doc.documentElement || doc;
|
|
245
265
|
|
|
246
266
|
// Safety check: ensure getAttribute and setAttribute methods exist
|
|
@@ -271,6 +291,9 @@ export function ensureInkscapeNamespaces(doc) {
|
|
|
271
291
|
* @returns {Set<string>} Set of referenced IDs
|
|
272
292
|
*/
|
|
273
293
|
export function findReferencedIds(element) {
|
|
294
|
+
// Validate element parameter
|
|
295
|
+
if (!element) return new Set();
|
|
296
|
+
|
|
274
297
|
const ids = new Set();
|
|
275
298
|
|
|
276
299
|
// Attributes that can contain url(#id) references
|
|
@@ -284,14 +307,14 @@ export function findReferencedIds(element) {
|
|
|
284
307
|
const hrefAttrs = ['href', 'xlink:href'];
|
|
285
308
|
|
|
286
309
|
const extractUrlId = (value) => {
|
|
287
|
-
if (!value) return null;
|
|
310
|
+
if (!value || typeof value !== 'string') return null;
|
|
288
311
|
// Match url(#id) or url("#id")
|
|
289
312
|
const match = value.match(/url\(["']?#([^"')]+)["']?\)/);
|
|
290
313
|
return match ? match[1] : null;
|
|
291
314
|
};
|
|
292
315
|
|
|
293
316
|
const extractHrefId = (value) => {
|
|
294
|
-
if (!value) return null;
|
|
317
|
+
if (!value || typeof value !== 'string') return null;
|
|
295
318
|
// Match #id references
|
|
296
319
|
if (value.startsWith('#')) {
|
|
297
320
|
return value.slice(1);
|
|
@@ -324,7 +347,7 @@ export function findReferencedIds(element) {
|
|
|
324
347
|
}
|
|
325
348
|
|
|
326
349
|
// Recurse into children
|
|
327
|
-
if (el.children) {
|
|
350
|
+
if (el.children && Array.isArray(el.children)) {
|
|
328
351
|
for (const child of el.children) {
|
|
329
352
|
walk(child);
|
|
330
353
|
}
|
|
@@ -342,6 +365,9 @@ export function findReferencedIds(element) {
|
|
|
342
365
|
* @returns {Map<string, Object>} Map of ID to element
|
|
343
366
|
*/
|
|
344
367
|
export function buildDefsMapFromDefs(doc) {
|
|
368
|
+
// Validate doc parameter
|
|
369
|
+
if (!doc) return new Map();
|
|
370
|
+
|
|
345
371
|
const defsMap = new Map();
|
|
346
372
|
|
|
347
373
|
const walk = (el) => {
|
|
@@ -354,7 +380,7 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
354
380
|
}
|
|
355
381
|
|
|
356
382
|
// Recurse
|
|
357
|
-
if (el.children) {
|
|
383
|
+
if (el.children && Array.isArray(el.children)) {
|
|
358
384
|
for (const child of el.children) {
|
|
359
385
|
walk(child);
|
|
360
386
|
}
|
|
@@ -367,7 +393,7 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
367
393
|
if (el.tagName === 'defs') {
|
|
368
394
|
walk(el);
|
|
369
395
|
}
|
|
370
|
-
if (el.children) {
|
|
396
|
+
if (el.children && Array.isArray(el.children)) {
|
|
371
397
|
for (const child of el.children) {
|
|
372
398
|
findDefs(child);
|
|
373
399
|
}
|
|
@@ -387,6 +413,14 @@ export function buildDefsMapFromDefs(doc) {
|
|
|
387
413
|
* @returns {Set<string>} Complete set of IDs including all nested dependencies
|
|
388
414
|
*/
|
|
389
415
|
export function resolveDefsDependencies(initialIds, defsMap) {
|
|
416
|
+
// Validate parameters
|
|
417
|
+
if (!initialIds || !(initialIds instanceof Set)) {
|
|
418
|
+
throw new Error('initialIds must be a Set');
|
|
419
|
+
}
|
|
420
|
+
if (!defsMap || !(defsMap instanceof Map)) {
|
|
421
|
+
throw new Error('defsMap must be a Map');
|
|
422
|
+
}
|
|
423
|
+
|
|
390
424
|
const resolved = new Set();
|
|
391
425
|
const toProcess = [...initialIds];
|
|
392
426
|
|
|
@@ -424,7 +458,7 @@ export function cloneElement(element) {
|
|
|
424
458
|
const attrs = {};
|
|
425
459
|
if (element._attributes) {
|
|
426
460
|
Object.assign(attrs, element._attributes);
|
|
427
|
-
} else if (element.getAttributeNames) {
|
|
461
|
+
} else if (typeof element.getAttributeNames === 'function') {
|
|
428
462
|
for (const name of element.getAttributeNames()) {
|
|
429
463
|
attrs[name] = element.getAttribute(name);
|
|
430
464
|
}
|
|
@@ -432,7 +466,7 @@ export function cloneElement(element) {
|
|
|
432
466
|
|
|
433
467
|
// Clone children recursively
|
|
434
468
|
const clonedChildren = [];
|
|
435
|
-
if (element.children) {
|
|
469
|
+
if (element.children && Array.isArray(element.children)) {
|
|
436
470
|
for (const child of element.children) {
|
|
437
471
|
const clonedChild = cloneElement(child);
|
|
438
472
|
if (clonedChild) {
|
|
@@ -464,6 +498,11 @@ export function cloneElement(element) {
|
|
|
464
498
|
* @returns {{svg: SVGElement, layerInfo: {id: string, label: string}}} Extracted SVG and layer info
|
|
465
499
|
*/
|
|
466
500
|
export function extractLayer(doc, layerOrId, options = {}) {
|
|
501
|
+
// Validate doc parameter
|
|
502
|
+
if (!doc) {
|
|
503
|
+
throw new Error('doc parameter is required');
|
|
504
|
+
}
|
|
505
|
+
|
|
467
506
|
const { preserveTransform = true } = options;
|
|
468
507
|
|
|
469
508
|
// Find the layer element
|
|
@@ -471,11 +510,17 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
471
510
|
if (typeof layerOrId === 'string') {
|
|
472
511
|
const layers = findLayers(doc);
|
|
473
512
|
const found = layers.find(l => l.id === layerOrId || l.label === layerOrId);
|
|
474
|
-
if (!found
|
|
475
|
-
throw new Error(`Layer not found
|
|
513
|
+
if (!found) {
|
|
514
|
+
throw new Error(`Layer not found: ${layerOrId}`);
|
|
515
|
+
}
|
|
516
|
+
if (!found.element) {
|
|
517
|
+
throw new Error(`Layer element is invalid for: ${layerOrId}`);
|
|
476
518
|
}
|
|
477
519
|
layer = found.element;
|
|
478
520
|
} else {
|
|
521
|
+
if (!layerOrId) {
|
|
522
|
+
throw new Error('layerOrId parameter is required');
|
|
523
|
+
}
|
|
479
524
|
layer = layerOrId;
|
|
480
525
|
}
|
|
481
526
|
|
|
@@ -499,7 +544,7 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
499
544
|
const svgAttrs = {};
|
|
500
545
|
if (svgRoot._attributes) {
|
|
501
546
|
Object.assign(svgAttrs, svgRoot._attributes);
|
|
502
|
-
} else if (svgRoot.getAttributeNames) {
|
|
547
|
+
} else if (typeof svgRoot.getAttributeNames === 'function') {
|
|
503
548
|
for (const name of svgRoot.getAttributeNames()) {
|
|
504
549
|
svgAttrs[name] = svgRoot.getAttribute(name);
|
|
505
550
|
}
|
|
@@ -538,7 +583,7 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
538
583
|
|
|
539
584
|
// Get layer info
|
|
540
585
|
const layerInfo = {
|
|
541
|
-
id: layer.getAttribute('id'),
|
|
586
|
+
id: (typeof layer.getAttribute === 'function' ? layer.getAttribute('id') : null),
|
|
542
587
|
label: getLayerLabel(layer)
|
|
543
588
|
};
|
|
544
589
|
|
|
@@ -555,6 +600,11 @@ export function extractLayer(doc, layerOrId, options = {}) {
|
|
|
555
600
|
* @returns {Array<{svg: Object, layerInfo: {id: string, label: string}}>} Array of extracted SVGs
|
|
556
601
|
*/
|
|
557
602
|
export function extractAllLayers(doc, options = {}) {
|
|
603
|
+
// Validate doc parameter
|
|
604
|
+
if (!doc) {
|
|
605
|
+
throw new Error('doc parameter is required');
|
|
606
|
+
}
|
|
607
|
+
|
|
558
608
|
const { includeHidden = false } = options;
|
|
559
609
|
const layers = findLayers(doc);
|
|
560
610
|
const results = [];
|
|
@@ -564,6 +614,9 @@ export function extractAllLayers(doc, options = {}) {
|
|
|
564
614
|
|
|
565
615
|
// Skip hidden layers unless requested
|
|
566
616
|
if (!includeHidden) {
|
|
617
|
+
// Validate getAttribute method exists
|
|
618
|
+
if (typeof layer.getAttribute !== 'function') continue;
|
|
619
|
+
|
|
567
620
|
const style = layer.getAttribute('style') || '';
|
|
568
621
|
const display = layer.getAttribute('display');
|
|
569
622
|
const visibility = layer.getAttribute('visibility');
|
|
@@ -596,6 +649,11 @@ export function extractAllLayers(doc, options = {}) {
|
|
|
596
649
|
* @returns {Object} Summary of shared resources
|
|
597
650
|
*/
|
|
598
651
|
export function analyzeLayerDependencies(doc) {
|
|
652
|
+
// Validate doc parameter
|
|
653
|
+
if (!doc) {
|
|
654
|
+
throw new Error('doc parameter is required');
|
|
655
|
+
}
|
|
656
|
+
|
|
599
657
|
const layers = findLayers(doc);
|
|
600
658
|
const defsMap = buildDefsMapFromDefs(doc);
|
|
601
659
|
const layerRefs = new Map(); // layer ID -> Set of referenced def IDs
|
package/src/logger.js
CHANGED
|
@@ -126,11 +126,21 @@ export const Logger = {
|
|
|
126
126
|
* @private
|
|
127
127
|
*/
|
|
128
128
|
_format(level, message) {
|
|
129
|
+
// Why: Validate parameters to prevent crashes from invalid inputs
|
|
130
|
+
if (typeof level !== 'string' || !level) {
|
|
131
|
+
throw new Error('Logger._format: level must be a non-empty string');
|
|
132
|
+
}
|
|
133
|
+
if (message === null || message === undefined) {
|
|
134
|
+
throw new Error('Logger._format: message cannot be null or undefined');
|
|
135
|
+
}
|
|
136
|
+
// Why: Convert message to string to handle non-string inputs gracefully
|
|
137
|
+
const messageStr = String(message);
|
|
138
|
+
|
|
129
139
|
if (this.timestamps) {
|
|
130
140
|
const ts = new Date().toISOString();
|
|
131
|
-
return `[${ts}] [${level}] ${
|
|
141
|
+
return `[${ts}] [${level}] ${messageStr}`;
|
|
132
142
|
}
|
|
133
|
-
return `[${level}] ${
|
|
143
|
+
return `[${level}] ${messageStr}`;
|
|
134
144
|
},
|
|
135
145
|
|
|
136
146
|
/**
|
|
@@ -144,7 +154,14 @@ export const Logger = {
|
|
|
144
154
|
_bufferWrite(message) {
|
|
145
155
|
if (!this.logToFile) return;
|
|
146
156
|
|
|
147
|
-
|
|
157
|
+
// Why: Validate message parameter to prevent buffer corruption
|
|
158
|
+
if (message === null || message === undefined) {
|
|
159
|
+
throw new Error('Logger._bufferWrite: message cannot be null or undefined');
|
|
160
|
+
}
|
|
161
|
+
// Why: Convert to string to handle non-string inputs gracefully
|
|
162
|
+
const messageStr = String(message);
|
|
163
|
+
|
|
164
|
+
this._buffer.push(messageStr);
|
|
148
165
|
|
|
149
166
|
// Why: Flush when buffer is full to prevent unbounded memory growth
|
|
150
167
|
if (this._buffer.length >= LOG_BUFFER_SIZE) {
|
|
@@ -188,6 +205,10 @@ export const Logger = {
|
|
|
188
205
|
* @param {...any} args - Additional arguments
|
|
189
206
|
*/
|
|
190
207
|
error(message, ...args) {
|
|
208
|
+
// Why: Validate message parameter to prevent crashes
|
|
209
|
+
if (message === null || message === undefined) {
|
|
210
|
+
throw new Error('Logger.error: message cannot be null or undefined');
|
|
211
|
+
}
|
|
191
212
|
if (this.level >= LogLevel.ERROR) {
|
|
192
213
|
const formatted = this._format('ERROR', message);
|
|
193
214
|
console.error(formatted, ...args);
|
|
@@ -203,6 +224,10 @@ export const Logger = {
|
|
|
203
224
|
* @param {...any} args - Additional arguments
|
|
204
225
|
*/
|
|
205
226
|
warn(message, ...args) {
|
|
227
|
+
// Why: Validate message parameter to prevent crashes
|
|
228
|
+
if (message === null || message === undefined) {
|
|
229
|
+
throw new Error('Logger.warn: message cannot be null or undefined');
|
|
230
|
+
}
|
|
206
231
|
if (this.level >= LogLevel.WARN) {
|
|
207
232
|
const formatted = this._format('WARN', message);
|
|
208
233
|
console.warn(formatted, ...args);
|
|
@@ -216,6 +241,10 @@ export const Logger = {
|
|
|
216
241
|
* @param {...any} args - Additional arguments
|
|
217
242
|
*/
|
|
218
243
|
info(message, ...args) {
|
|
244
|
+
// Why: Validate message parameter to prevent crashes
|
|
245
|
+
if (message === null || message === undefined) {
|
|
246
|
+
throw new Error('Logger.info: message cannot be null or undefined');
|
|
247
|
+
}
|
|
219
248
|
if (this.level >= LogLevel.INFO) {
|
|
220
249
|
const formatted = this._format('INFO', message);
|
|
221
250
|
console.log(formatted, ...args);
|
|
@@ -229,6 +258,10 @@ export const Logger = {
|
|
|
229
258
|
* @param {...any} args - Additional arguments
|
|
230
259
|
*/
|
|
231
260
|
debug(message, ...args) {
|
|
261
|
+
// Why: Validate message parameter to prevent crashes
|
|
262
|
+
if (message === null || message === undefined) {
|
|
263
|
+
throw new Error('Logger.debug: message cannot be null or undefined');
|
|
264
|
+
}
|
|
232
265
|
if (this.level >= LogLevel.DEBUG) {
|
|
233
266
|
const formatted = this._format('DEBUG', message);
|
|
234
267
|
console.log(formatted, ...args);
|
|
@@ -257,6 +290,10 @@ export const Logger = {
|
|
|
257
290
|
* @param {number} level - Log level from LogLevel enum
|
|
258
291
|
*/
|
|
259
292
|
export function setLogLevel(level) {
|
|
293
|
+
// Why: Validate level is a number to catch type errors
|
|
294
|
+
if (typeof level !== 'number' || !Number.isFinite(level)) {
|
|
295
|
+
throw new Error(`setLogLevel: level must be a finite number, got ${typeof level}`);
|
|
296
|
+
}
|
|
260
297
|
// Why: Validate level is within valid range to catch typos
|
|
261
298
|
if (level < LogLevel.SILENT || level > LogLevel.DEBUG) {
|
|
262
299
|
throw new Error(`Invalid log level: ${level}. Use LogLevel.SILENT (0) through LogLevel.DEBUG (4)`);
|
|
@@ -281,10 +318,18 @@ export function getLogLevel() {
|
|
|
281
318
|
* @param {boolean} [withTimestamps=true] - Include timestamps
|
|
282
319
|
*/
|
|
283
320
|
export function enableFileLogging(filePath, withTimestamps = true) {
|
|
321
|
+
// Why: Validate filePath type to catch type errors
|
|
322
|
+
if (typeof filePath !== 'string') {
|
|
323
|
+
throw new Error(`enableFileLogging: filePath must be a string, got ${typeof filePath}`);
|
|
324
|
+
}
|
|
284
325
|
// Why: Don't accept empty/null paths - would cause confusing errors later
|
|
285
326
|
if (!filePath) {
|
|
286
327
|
throw new Error('File path required for enableFileLogging()');
|
|
287
328
|
}
|
|
329
|
+
// Why: Validate withTimestamps type to catch type errors
|
|
330
|
+
if (typeof withTimestamps !== 'boolean') {
|
|
331
|
+
throw new Error(`enableFileLogging: withTimestamps must be a boolean, got ${typeof withTimestamps}`);
|
|
332
|
+
}
|
|
288
333
|
Logger.logToFile = filePath;
|
|
289
334
|
Logger.timestamps = withTimestamps;
|
|
290
335
|
}
|