@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/bin/svgm.js
CHANGED
|
@@ -121,6 +121,10 @@ let config = { ...DEFAULT_CONFIG };
|
|
|
121
121
|
* @returns {void}
|
|
122
122
|
*/
|
|
123
123
|
function log(msg) {
|
|
124
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
125
|
+
if (typeof msg !== "string") {
|
|
126
|
+
throw new TypeError(`log: expected string, got ${typeof msg}`);
|
|
127
|
+
}
|
|
124
128
|
if (!config.quiet) console.log(msg);
|
|
125
129
|
}
|
|
126
130
|
|
|
@@ -130,6 +134,11 @@ function log(msg) {
|
|
|
130
134
|
* @returns {void}
|
|
131
135
|
*/
|
|
132
136
|
function logError(msg) {
|
|
137
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
138
|
+
if (typeof msg !== "string") {
|
|
139
|
+
console.error(`${colors.red}error:${colors.reset} Invalid error message type: ${typeof msg}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
133
142
|
console.error(`${colors.red}error:${colors.reset} ${msg}`);
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -288,6 +297,10 @@ const DEFAULT_PIPELINE = [
|
|
|
288
297
|
* @returns {string} Normalized path
|
|
289
298
|
*/
|
|
290
299
|
function normalizePath(p) {
|
|
300
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
301
|
+
if (typeof p !== "string") {
|
|
302
|
+
throw new TypeError(`normalizePath: expected string, got ${typeof p}`);
|
|
303
|
+
}
|
|
291
304
|
return p.replace(/\\/g, "/");
|
|
292
305
|
}
|
|
293
306
|
|
|
@@ -297,6 +310,10 @@ function normalizePath(p) {
|
|
|
297
310
|
* @returns {string} Absolute normalized path
|
|
298
311
|
*/
|
|
299
312
|
function resolvePath(p) {
|
|
313
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
314
|
+
if (typeof p !== "string") {
|
|
315
|
+
throw new TypeError(`resolvePath: expected string, got ${typeof p}`);
|
|
316
|
+
}
|
|
300
317
|
return isAbsolute(p)
|
|
301
318
|
? normalizePath(p)
|
|
302
319
|
: normalizePath(resolve(process.cwd(), p));
|
|
@@ -308,6 +325,8 @@ function resolvePath(p) {
|
|
|
308
325
|
* @returns {boolean} True if directory exists
|
|
309
326
|
*/
|
|
310
327
|
function isDir(p) {
|
|
328
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
329
|
+
if (typeof p !== "string") return false;
|
|
311
330
|
try {
|
|
312
331
|
return statSync(p).isDirectory();
|
|
313
332
|
} catch {
|
|
@@ -321,6 +340,8 @@ function isDir(p) {
|
|
|
321
340
|
* @returns {boolean} True if file exists
|
|
322
341
|
*/
|
|
323
342
|
function isFile(p) {
|
|
343
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
344
|
+
if (typeof p !== "string") return false;
|
|
324
345
|
try {
|
|
325
346
|
return statSync(p).isFile();
|
|
326
347
|
} catch {
|
|
@@ -334,6 +355,10 @@ function isFile(p) {
|
|
|
334
355
|
* @returns {void}
|
|
335
356
|
*/
|
|
336
357
|
function ensureDir(dir) {
|
|
358
|
+
// Why: Validate parameter to prevent runtime errors with null/undefined
|
|
359
|
+
if (typeof dir !== "string") {
|
|
360
|
+
throw new TypeError(`ensureDir: expected string, got ${typeof dir}`);
|
|
361
|
+
}
|
|
337
362
|
if (!existsSync(dir)) {
|
|
338
363
|
mkdirSync(dir, { recursive: true });
|
|
339
364
|
}
|
|
@@ -347,14 +372,32 @@ function ensureDir(dir) {
|
|
|
347
372
|
* @returns {string[]} Array of SVG file paths
|
|
348
373
|
*/
|
|
349
374
|
function getSvgFiles(dir, recursive = false, exclude = []) {
|
|
375
|
+
// Why: Validate parameters to prevent runtime errors
|
|
376
|
+
if (typeof dir !== "string") {
|
|
377
|
+
throw new TypeError(`getSvgFiles: expected string dir, got ${typeof dir}`);
|
|
378
|
+
}
|
|
379
|
+
if (!existsSync(dir)) {
|
|
380
|
+
throw new Error(`getSvgFiles: directory does not exist: ${dir}`);
|
|
381
|
+
}
|
|
382
|
+
if (!Array.isArray(exclude)) {
|
|
383
|
+
throw new TypeError(`getSvgFiles: expected array exclude, got ${typeof exclude}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
350
386
|
const files = [];
|
|
351
387
|
function scan(d) {
|
|
352
388
|
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
353
389
|
const fullPath = join(d, entry.name);
|
|
354
390
|
// Check exclusion patterns
|
|
355
391
|
const shouldExclude = exclude.some((pattern) => {
|
|
356
|
-
|
|
357
|
-
|
|
392
|
+
try {
|
|
393
|
+
// Why: Wrap RegExp constructor in try-catch to handle invalid patterns
|
|
394
|
+
const regex = new RegExp(pattern);
|
|
395
|
+
return regex.test(fullPath) || regex.test(entry.name);
|
|
396
|
+
} catch (_e) {
|
|
397
|
+
// Why: Catch invalid regex patterns (unused error marked with underscore per ESLint)
|
|
398
|
+
logError(`Invalid exclusion pattern: ${pattern}`);
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
358
401
|
});
|
|
359
402
|
if (shouldExclude) continue;
|
|
360
403
|
|
|
@@ -381,6 +424,16 @@ function getSvgFiles(dir, recursive = false, exclude = []) {
|
|
|
381
424
|
* @returns {Promise<string>} Optimized SVG content
|
|
382
425
|
*/
|
|
383
426
|
async function optimizeSvg(content, options = {}) {
|
|
427
|
+
// Why: Validate parameters to prevent runtime errors
|
|
428
|
+
if (typeof content !== "string") {
|
|
429
|
+
throw new TypeError(`optimizeSvg: expected string content, got ${typeof content}`);
|
|
430
|
+
}
|
|
431
|
+
if (content.length === 0) {
|
|
432
|
+
throw new Error("optimizeSvg: content is empty");
|
|
433
|
+
}
|
|
434
|
+
if (options !== null && typeof options !== "object") {
|
|
435
|
+
throw new TypeError(`optimizeSvg: expected object options, got ${typeof options}`);
|
|
436
|
+
}
|
|
384
437
|
const doc = parseSVG(content);
|
|
385
438
|
const pipeline = DEFAULT_PIPELINE;
|
|
386
439
|
|
|
@@ -464,6 +517,10 @@ async function optimizeSvg(content, options = {}) {
|
|
|
464
517
|
* @returns {string} Minified XML
|
|
465
518
|
*/
|
|
466
519
|
function minifyXml(xml) {
|
|
520
|
+
// Why: Validate parameter to prevent runtime errors
|
|
521
|
+
if (typeof xml !== "string") {
|
|
522
|
+
throw new TypeError(`minifyXml: expected string, got ${typeof xml}`);
|
|
523
|
+
}
|
|
467
524
|
return (
|
|
468
525
|
xml
|
|
469
526
|
// Remove newlines and collapse whitespace between tags
|
|
@@ -480,6 +537,13 @@ function minifyXml(xml) {
|
|
|
480
537
|
* @returns {string} Prettified XML
|
|
481
538
|
*/
|
|
482
539
|
function prettifyXml(xml, indent = 2) {
|
|
540
|
+
// Why: Validate parameters to prevent runtime errors
|
|
541
|
+
if (typeof xml !== "string") {
|
|
542
|
+
throw new TypeError(`prettifyXml: expected string xml, got ${typeof xml}`);
|
|
543
|
+
}
|
|
544
|
+
if (typeof indent !== "number" || indent < 0 || indent > 16) {
|
|
545
|
+
throw new RangeError(`prettifyXml: indent must be number 0-16, got ${indent}`);
|
|
546
|
+
}
|
|
483
547
|
// Simple XML prettifier
|
|
484
548
|
const indentStr = " ".repeat(indent);
|
|
485
549
|
let formatted = "";
|
|
@@ -523,6 +587,14 @@ function prettifyXml(xml, indent = 2) {
|
|
|
523
587
|
* @returns {string} Data URI string
|
|
524
588
|
*/
|
|
525
589
|
function toDataUri(content, format) {
|
|
590
|
+
// Why: Validate parameters to prevent runtime errors
|
|
591
|
+
if (typeof content !== "string") {
|
|
592
|
+
throw new TypeError(`toDataUri: expected string content, got ${typeof content}`);
|
|
593
|
+
}
|
|
594
|
+
const validFormats = ["base64", "enc", "unenc"];
|
|
595
|
+
if (!validFormats.includes(format)) {
|
|
596
|
+
throw new Error(`toDataUri: format must be one of ${validFormats.join(", ")}, got ${format}`);
|
|
597
|
+
}
|
|
526
598
|
if (format === "base64") {
|
|
527
599
|
return (
|
|
528
600
|
"data:image/svg+xml;base64," + Buffer.from(content).toString("base64")
|
|
@@ -545,10 +617,30 @@ function toDataUri(content, format) {
|
|
|
545
617
|
* @returns {Promise<Object>} Processing result with success status and metrics
|
|
546
618
|
*/
|
|
547
619
|
async function processFile(inputPath, outputPath, options) {
|
|
620
|
+
// Why: Validate parameters to prevent runtime errors
|
|
621
|
+
if (typeof inputPath !== "string") {
|
|
622
|
+
return { success: false, error: `Invalid input path: ${typeof inputPath}`, inputPath };
|
|
623
|
+
}
|
|
624
|
+
if (typeof outputPath !== "string") {
|
|
625
|
+
return { success: false, error: `Invalid output path: ${typeof outputPath}`, inputPath };
|
|
626
|
+
}
|
|
627
|
+
if (options !== null && typeof options !== "object") {
|
|
628
|
+
return { success: false, error: `Invalid options: ${typeof options}`, inputPath };
|
|
629
|
+
}
|
|
630
|
+
|
|
548
631
|
try {
|
|
549
632
|
let content = readFileSync(inputPath, "utf8");
|
|
550
633
|
const originalSize = Buffer.byteLength(content);
|
|
551
634
|
|
|
635
|
+
// Why: Validate file size to prevent processing extremely large files
|
|
636
|
+
if (originalSize > CONSTANTS.MAX_FILE_SIZE_BYTES) {
|
|
637
|
+
return {
|
|
638
|
+
success: false,
|
|
639
|
+
error: `File too large: ${originalSize} bytes (max ${CONSTANTS.MAX_FILE_SIZE_BYTES} bytes)`,
|
|
640
|
+
inputPath
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
552
644
|
// Apply embedding if enabled
|
|
553
645
|
if (options.embed) {
|
|
554
646
|
const doc = parseSVG(content);
|
|
@@ -594,7 +686,10 @@ async function processFile(inputPath, outputPath, options) {
|
|
|
594
686
|
}
|
|
595
687
|
|
|
596
688
|
const savings = originalSize - optimizedSize;
|
|
597
|
-
|
|
689
|
+
// Why: Handle case where optimizedSize > originalSize (negative savings)
|
|
690
|
+
const percent = originalSize > 0
|
|
691
|
+
? ((savings / originalSize) * 100).toFixed(1)
|
|
692
|
+
: "0.0";
|
|
598
693
|
|
|
599
694
|
return {
|
|
600
695
|
success: true,
|
|
@@ -716,6 +811,11 @@ function showPlugins() {
|
|
|
716
811
|
* @returns {Object} Parsed configuration
|
|
717
812
|
*/
|
|
718
813
|
function loadConfigFile(configPath) {
|
|
814
|
+
// Why: Validate parameter to prevent runtime errors
|
|
815
|
+
if (typeof configPath !== "string") {
|
|
816
|
+
logError(`Invalid config path: expected string, got ${typeof configPath}`);
|
|
817
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
818
|
+
}
|
|
719
819
|
try {
|
|
720
820
|
const absolutePath = resolvePath(configPath);
|
|
721
821
|
if (!existsSync(absolutePath)) {
|
|
@@ -723,7 +823,14 @@ function loadConfigFile(configPath) {
|
|
|
723
823
|
process.exit(CONSTANTS.EXIT_ERROR);
|
|
724
824
|
}
|
|
725
825
|
const content = readFileSync(absolutePath, "utf8");
|
|
726
|
-
|
|
826
|
+
// Why: Use safeLoad to prevent arbitrary code execution via YAML (security fix)
|
|
827
|
+
const loadedConfig = yaml.load(content, { schema: yaml.FAILSAFE_SCHEMA });
|
|
828
|
+
|
|
829
|
+
// Why: Validate loaded config is an object to prevent runtime errors
|
|
830
|
+
if (loadedConfig === null || typeof loadedConfig !== "object") {
|
|
831
|
+
logError(`Invalid config file: expected object, got ${typeof loadedConfig}`);
|
|
832
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
833
|
+
}
|
|
727
834
|
|
|
728
835
|
// Convert YAML config structure to CLI config structure
|
|
729
836
|
const result = {};
|
|
@@ -780,12 +887,42 @@ function loadConfigFile(configPath) {
|
|
|
780
887
|
// ============================================================================
|
|
781
888
|
// ARGUMENT PARSING
|
|
782
889
|
// ============================================================================
|
|
890
|
+
/**
|
|
891
|
+
* Helper to safely get next argument with bounds checking.
|
|
892
|
+
* @param {string[]} args - Arguments array
|
|
893
|
+
* @param {number} i - Current index
|
|
894
|
+
* @param {string} flag - Flag name for error message
|
|
895
|
+
* @returns {string} Next argument value
|
|
896
|
+
*/
|
|
897
|
+
function getNextArg(args, i, flag) {
|
|
898
|
+
// Why: Validate parameters to prevent runtime errors
|
|
899
|
+
if (!Array.isArray(args)) {
|
|
900
|
+
logError(`getNextArg: expected array args, got ${typeof args}`);
|
|
901
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
902
|
+
}
|
|
903
|
+
if (typeof i !== "number" || i < 0) {
|
|
904
|
+
logError(`getNextArg: expected non-negative number i, got ${typeof i}`);
|
|
905
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
906
|
+
}
|
|
907
|
+
// Why: Validate bounds to prevent accessing undefined arguments
|
|
908
|
+
if (i + 1 >= args.length) {
|
|
909
|
+
logError(`${flag} requires a value`);
|
|
910
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
911
|
+
}
|
|
912
|
+
return args[i + 1];
|
|
913
|
+
}
|
|
914
|
+
|
|
783
915
|
/**
|
|
784
916
|
* Parse command-line arguments.
|
|
785
917
|
* @param {string[]} args - Command-line arguments
|
|
786
918
|
* @returns {Object} Parsed configuration object
|
|
787
919
|
*/
|
|
788
920
|
function parseArgs(args) {
|
|
921
|
+
// Why: Validate parameter to prevent runtime errors
|
|
922
|
+
if (!Array.isArray(args)) {
|
|
923
|
+
logError(`parseArgs: expected array, got ${typeof args}`);
|
|
924
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
925
|
+
}
|
|
789
926
|
let cfg = { ...DEFAULT_CONFIG };
|
|
790
927
|
const inputs = [];
|
|
791
928
|
let i = 0;
|
|
@@ -826,12 +963,16 @@ function parseArgs(args) {
|
|
|
826
963
|
|
|
827
964
|
case "-s":
|
|
828
965
|
case "--string":
|
|
829
|
-
|
|
966
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
967
|
+
cfg.string = getNextArg(args, i, arg);
|
|
968
|
+
i++;
|
|
830
969
|
break;
|
|
831
970
|
|
|
832
971
|
case "-f":
|
|
833
972
|
case "--folder":
|
|
834
|
-
|
|
973
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
974
|
+
cfg.folder = getNextArg(args, i, arg);
|
|
975
|
+
i++;
|
|
835
976
|
break;
|
|
836
977
|
|
|
837
978
|
case "-o":
|
|
@@ -850,11 +991,24 @@ function parseArgs(args) {
|
|
|
850
991
|
|
|
851
992
|
case "-p":
|
|
852
993
|
case "--precision":
|
|
853
|
-
|
|
994
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
995
|
+
{
|
|
996
|
+
const precisionArg = getNextArg(args, i, arg);
|
|
997
|
+
const parsed = parseInt(precisionArg, 10);
|
|
998
|
+
// Why: Validate parseInt result to prevent NaN values
|
|
999
|
+
if (isNaN(parsed)) {
|
|
1000
|
+
logError(`--precision requires a valid number, got: ${precisionArg}`);
|
|
1001
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1002
|
+
}
|
|
1003
|
+
cfg.precision = parsed;
|
|
1004
|
+
i++;
|
|
1005
|
+
}
|
|
854
1006
|
break;
|
|
855
1007
|
|
|
856
1008
|
case "--datauri":
|
|
857
|
-
|
|
1009
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1010
|
+
cfg.datauri = getNextArg(args, i, arg);
|
|
1011
|
+
i++;
|
|
858
1012
|
break;
|
|
859
1013
|
|
|
860
1014
|
case "--multipass":
|
|
@@ -866,11 +1020,24 @@ function parseArgs(args) {
|
|
|
866
1020
|
break;
|
|
867
1021
|
|
|
868
1022
|
case "--indent":
|
|
869
|
-
|
|
1023
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1024
|
+
{
|
|
1025
|
+
const indentArg = getNextArg(args, i, arg);
|
|
1026
|
+
const parsed = parseInt(indentArg, 10);
|
|
1027
|
+
// Why: Validate parseInt result to prevent NaN values
|
|
1028
|
+
if (isNaN(parsed)) {
|
|
1029
|
+
logError(`--indent requires a valid number, got: ${indentArg}`);
|
|
1030
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1031
|
+
}
|
|
1032
|
+
cfg.indent = parsed;
|
|
1033
|
+
i++;
|
|
1034
|
+
}
|
|
870
1035
|
break;
|
|
871
1036
|
|
|
872
1037
|
case "--eol":
|
|
873
|
-
|
|
1038
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1039
|
+
cfg.eol = getNextArg(args, i, arg);
|
|
1040
|
+
i++;
|
|
874
1041
|
break;
|
|
875
1042
|
|
|
876
1043
|
case "--final-newline":
|
|
@@ -902,12 +1069,14 @@ function parseArgs(args) {
|
|
|
902
1069
|
|
|
903
1070
|
case "--preserve-ns":
|
|
904
1071
|
{
|
|
905
|
-
|
|
1072
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1073
|
+
const val = argValue || getNextArg(args, i, arg);
|
|
1074
|
+
if (!argValue) i++; // Only increment if we used getNextArg
|
|
906
1075
|
if (!val) {
|
|
907
1076
|
logError(
|
|
908
1077
|
"--preserve-ns requires a comma-separated list of namespaces",
|
|
909
1078
|
);
|
|
910
|
-
process.exit(
|
|
1079
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
911
1080
|
}
|
|
912
1081
|
cfg.preserveNamespaces = val
|
|
913
1082
|
.split(",")
|
|
@@ -934,7 +1103,9 @@ function parseArgs(args) {
|
|
|
934
1103
|
break;
|
|
935
1104
|
|
|
936
1105
|
case "--config":
|
|
937
|
-
|
|
1106
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1107
|
+
cfg.configFile = getNextArg(args, i, arg);
|
|
1108
|
+
i++;
|
|
938
1109
|
break;
|
|
939
1110
|
|
|
940
1111
|
case "--embed":
|
|
@@ -961,7 +1132,9 @@ function parseArgs(args) {
|
|
|
961
1132
|
break;
|
|
962
1133
|
|
|
963
1134
|
case "--embed-svg-mode":
|
|
964
|
-
|
|
1135
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1136
|
+
cfg.embedExternalSVGMode = getNextArg(args, i, arg);
|
|
1137
|
+
i++;
|
|
965
1138
|
break;
|
|
966
1139
|
|
|
967
1140
|
case "--embed-css":
|
|
@@ -995,15 +1168,39 @@ function parseArgs(args) {
|
|
|
995
1168
|
break;
|
|
996
1169
|
|
|
997
1170
|
case "--embed-max-depth":
|
|
998
|
-
|
|
1171
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1172
|
+
{
|
|
1173
|
+
const depthArg = getNextArg(args, i, arg);
|
|
1174
|
+
const parsed = parseInt(depthArg, 10);
|
|
1175
|
+
// Why: Validate parseInt result to prevent NaN values
|
|
1176
|
+
if (isNaN(parsed)) {
|
|
1177
|
+
logError(`--embed-max-depth requires a valid number, got: ${depthArg}`);
|
|
1178
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1179
|
+
}
|
|
1180
|
+
cfg.embedMaxRecursionDepth = parsed;
|
|
1181
|
+
i++;
|
|
1182
|
+
}
|
|
999
1183
|
break;
|
|
1000
1184
|
|
|
1001
1185
|
case "--embed-timeout":
|
|
1002
|
-
|
|
1186
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1187
|
+
{
|
|
1188
|
+
const timeoutArg = getNextArg(args, i, arg);
|
|
1189
|
+
const parsed = parseInt(timeoutArg, 10);
|
|
1190
|
+
// Why: Validate parseInt result to prevent NaN values
|
|
1191
|
+
if (isNaN(parsed)) {
|
|
1192
|
+
logError(`--embed-timeout requires a valid number, got: ${timeoutArg}`);
|
|
1193
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1194
|
+
}
|
|
1195
|
+
cfg.embedTimeout = parsed;
|
|
1196
|
+
i++;
|
|
1197
|
+
}
|
|
1003
1198
|
break;
|
|
1004
1199
|
|
|
1005
1200
|
case "--embed-on-missing":
|
|
1006
|
-
|
|
1201
|
+
// Why: Use helper to safely get next argument with bounds checking
|
|
1202
|
+
cfg.embedOnMissingResource = getNextArg(args, i, arg);
|
|
1203
|
+
i++;
|
|
1007
1204
|
break;
|
|
1008
1205
|
|
|
1009
1206
|
default:
|
|
@@ -1019,23 +1216,25 @@ function parseArgs(args) {
|
|
|
1019
1216
|
cfg.inputs = inputs;
|
|
1020
1217
|
|
|
1021
1218
|
// Validate numeric arguments
|
|
1219
|
+
// Why: Check type first before using isNaN to prevent incorrect validation
|
|
1022
1220
|
if (
|
|
1023
|
-
cfg.precision !== undefined &&
|
|
1024
|
-
(
|
|
1221
|
+
cfg.precision !== undefined && cfg.precision !== null &&
|
|
1222
|
+
(typeof cfg.precision !== "number" || isNaN(cfg.precision) ||
|
|
1223
|
+
cfg.precision < CONSTANTS.MIN_PRECISION || cfg.precision > CONSTANTS.MAX_PRECISION)
|
|
1025
1224
|
) {
|
|
1026
|
-
logError(
|
|
1225
|
+
logError(`--precision must be a number between ${CONSTANTS.MIN_PRECISION} and ${CONSTANTS.MAX_PRECISION}`);
|
|
1027
1226
|
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1028
1227
|
}
|
|
1029
1228
|
if (
|
|
1030
|
-
cfg.indent !== undefined &&
|
|
1031
|
-
(isNaN(cfg.indent) || cfg.indent < 0 || cfg.indent > 16)
|
|
1229
|
+
cfg.indent !== undefined && cfg.indent !== null &&
|
|
1230
|
+
(typeof cfg.indent !== "number" || isNaN(cfg.indent) || cfg.indent < 0 || cfg.indent > 16)
|
|
1032
1231
|
) {
|
|
1033
1232
|
logError("--indent must be a number between 0 and 16");
|
|
1034
1233
|
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1035
1234
|
}
|
|
1036
1235
|
if (
|
|
1037
|
-
cfg.embedMaxRecursionDepth !== undefined &&
|
|
1038
|
-
(isNaN(cfg.embedMaxRecursionDepth) ||
|
|
1236
|
+
cfg.embedMaxRecursionDepth !== undefined && cfg.embedMaxRecursionDepth !== null &&
|
|
1237
|
+
(typeof cfg.embedMaxRecursionDepth !== "number" || isNaN(cfg.embedMaxRecursionDepth) ||
|
|
1039
1238
|
cfg.embedMaxRecursionDepth < 1 ||
|
|
1040
1239
|
cfg.embedMaxRecursionDepth > 100)
|
|
1041
1240
|
) {
|
|
@@ -1043,8 +1242,8 @@ function parseArgs(args) {
|
|
|
1043
1242
|
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1044
1243
|
}
|
|
1045
1244
|
if (
|
|
1046
|
-
cfg.embedTimeout !== undefined &&
|
|
1047
|
-
(isNaN(cfg.embedTimeout) ||
|
|
1245
|
+
cfg.embedTimeout !== undefined && cfg.embedTimeout !== null &&
|
|
1246
|
+
(typeof cfg.embedTimeout !== "number" || isNaN(cfg.embedTimeout) ||
|
|
1048
1247
|
cfg.embedTimeout < 1000 ||
|
|
1049
1248
|
cfg.embedTimeout > 300000)
|
|
1050
1249
|
) {
|
|
@@ -1099,6 +1298,11 @@ function parseArgs(args) {
|
|
|
1099
1298
|
* @returns {Promise<void>}
|
|
1100
1299
|
*/
|
|
1101
1300
|
async function main() {
|
|
1301
|
+
// Why: Validate process.argv exists and is an array to prevent runtime errors
|
|
1302
|
+
if (!Array.isArray(process.argv)) {
|
|
1303
|
+
logError("process.argv is not an array");
|
|
1304
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1305
|
+
}
|
|
1102
1306
|
const args = process.argv.slice(2);
|
|
1103
1307
|
|
|
1104
1308
|
if (args.length === 0) {
|
|
@@ -1209,7 +1413,12 @@ async function main() {
|
|
|
1209
1413
|
if (config.output === "-") {
|
|
1210
1414
|
outputPath = "-";
|
|
1211
1415
|
} else if (Array.isArray(config.output)) {
|
|
1212
|
-
|
|
1416
|
+
// Why: Bounds check when accessing array to prevent undefined values
|
|
1417
|
+
if (config.output.length === 0) {
|
|
1418
|
+
logError("Output array is empty");
|
|
1419
|
+
process.exit(CONSTANTS.EXIT_ERROR);
|
|
1420
|
+
}
|
|
1421
|
+
outputPath = config.output[i] || config.output[config.output.length - 1];
|
|
1213
1422
|
} else if (files.length > 1 || isDir(resolvePath(config.output))) {
|
|
1214
1423
|
// Multiple files or output is a directory
|
|
1215
1424
|
outputPath = join(resolvePath(config.output), basename(inputPath));
|
package/package.json
CHANGED