@emasoft/svg-matrix 1.0.34 → 1.2.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.
Files changed (56) hide show
  1. package/README.md +58 -2
  2. package/bin/svg-matrix.js +7 -6
  3. package/bin/svgm.js +109 -40
  4. package/dist/svg-matrix.min.js +7 -7
  5. package/dist/svg-toolbox.min.js +125 -205
  6. package/dist/svgm.min.js +115 -195
  7. package/dist/version.json +5 -5
  8. package/package.json +6 -1
  9. package/scripts/postinstall.js +72 -41
  10. package/scripts/test-postinstall.js +18 -16
  11. package/scripts/version-sync.js +144 -49
  12. package/src/animation-optimization.js +190 -98
  13. package/src/animation-references.js +11 -3
  14. package/src/arc-length.js +23 -20
  15. package/src/bezier-analysis.js +9 -13
  16. package/src/bezier-intersections.js +18 -4
  17. package/src/browser-verify.js +35 -8
  18. package/src/clip-path-resolver.js +285 -114
  19. package/src/convert-path-data.js +20 -8
  20. package/src/css-specificity.js +33 -9
  21. package/src/douglas-peucker.js +272 -141
  22. package/src/geometry-to-path.js +79 -22
  23. package/src/gjk-collision.js +287 -126
  24. package/src/index.js +56 -21
  25. package/src/inkscape-support.js +122 -101
  26. package/src/logger.js +43 -27
  27. package/src/marker-resolver.js +201 -121
  28. package/src/mask-resolver.js +231 -98
  29. package/src/matrix.js +9 -5
  30. package/src/mesh-gradient.js +22 -14
  31. package/src/off-canvas-detection.js +53 -17
  32. package/src/path-optimization.js +356 -171
  33. package/src/path-simplification.js +671 -256
  34. package/src/pattern-resolver.js +1 -3
  35. package/src/polygon-clip.js +396 -78
  36. package/src/svg-boolean-ops.js +90 -23
  37. package/src/svg-collections.js +1546 -667
  38. package/src/svg-flatten.js +152 -38
  39. package/src/svg-matrix-lib.js +6 -4
  40. package/src/svg-parser.js +5 -1
  41. package/src/svg-rendering-context.js +3 -1
  42. package/src/svg-toolbox-lib.js +2 -2
  43. package/src/svg-toolbox.js +99 -457
  44. package/src/svg-validation-data.js +513 -345
  45. package/src/svg2-polyfills.js +177 -87
  46. package/src/svgm-lib.js +10 -6
  47. package/src/transform-optimization.js +168 -51
  48. package/src/transforms2d.js +73 -40
  49. package/src/transforms3d.js +34 -27
  50. package/src/use-symbol-resolver.js +175 -76
  51. package/src/vector.js +80 -44
  52. package/src/vendor/inkscape-hatch-polyfill.js +143 -108
  53. package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
  54. package/src/vendor/inkscape-mesh-polyfill.js +953 -766
  55. package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
  56. package/src/verification.js +3 -4
package/dist/version.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
- "version": "1.0.34",
3
- "buildTime": "2025-12-31T03:54:49.761Z",
2
+ "version": "1.2.0",
3
+ "buildTime": "2025-12-31T23:29:02.390Z",
4
4
  "files": [
5
5
  {
6
6
  "name": "svg-matrix.min.js",
7
- "size": 55707
7
+ "size": 56106
8
8
  },
9
9
  {
10
10
  "name": "svg-toolbox.min.js",
11
- "size": 580878
11
+ "size": 577198
12
12
  },
13
13
  {
14
14
  "name": "svgm.min.js",
15
- "size": 605772
15
+ "size": 602581
16
16
  }
17
17
  ]
18
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emasoft/svg-matrix",
3
- "version": "1.0.34",
3
+ "version": "1.2.0",
4
4
  "description": "Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -70,6 +70,11 @@
70
70
  "./dist/svg-toolbox.min.js": "./dist/svg-toolbox.min.js",
71
71
  "./dist/svgm.min.js": "./dist/svgm.min.js"
72
72
  },
73
+ "browser": {
74
+ "./src/svg-matrix-lib.js": "./dist/svg-matrix.min.js",
75
+ "./src/svg-toolbox-lib.js": "./dist/svg-toolbox.min.js",
76
+ "./src/svgm-lib.js": "./dist/svgm.min.js"
77
+ },
73
78
  "author": "Emasoft",
74
79
  "license": "MIT",
75
80
  "homepage": "https://github.com/Emasoft/SVG-MATRIX#readme",
@@ -10,74 +10,105 @@
10
10
  * @license MIT
11
11
  */
12
12
 
13
- import { readFileSync, createWriteStream, existsSync } from 'fs';
14
- import { fileURLToPath } from 'url';
15
- import { dirname, join } from 'path';
13
+ import { readFileSync, createWriteStream, existsSync } from "fs";
14
+ import { fileURLToPath } from "url";
15
+ import { dirname, join } from "path";
16
16
 
17
17
  function getVersion() {
18
18
  try {
19
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
21
- return pkg.version || 'unknown';
20
+ const pkg = JSON.parse(
21
+ readFileSync(join(__dirname, "..", "package.json"), "utf8"),
22
+ );
23
+ return pkg.version || "unknown";
22
24
  } catch {
23
- return 'unknown';
25
+ return "unknown";
24
26
  }
25
27
  }
26
28
 
27
29
  function shouldDisableColors() {
28
30
  if (process.env.NO_COLOR !== undefined) return true;
29
- if (process.platform === 'win32') {
30
- return !(process.env.WT_SESSION || process.env.ConEmuANSI === 'ON' ||
31
- process.env.TERM_PROGRAM || process.env.ANSICON);
31
+ if (process.platform === "win32") {
32
+ return !(
33
+ process.env.WT_SESSION ||
34
+ process.env.ConEmuANSI === "ON" ||
35
+ process.env.TERM_PROGRAM ||
36
+ process.env.ANSICON
37
+ );
32
38
  }
33
39
  return false;
34
40
  }
35
41
 
36
42
  function getColors(disabled) {
37
43
  if (disabled) {
38
- return { reset: '', bright: '', dim: '', cyan: '', green: '',
39
- yellow: '', magenta: '', blue: '', white: '' };
44
+ return {
45
+ reset: "",
46
+ bright: "",
47
+ dim: "",
48
+ cyan: "",
49
+ green: "",
50
+ yellow: "",
51
+ magenta: "",
52
+ blue: "",
53
+ white: "",
54
+ };
40
55
  }
41
56
  return {
42
- reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m',
43
- cyan: '\x1b[36m', green: '\x1b[32m', yellow: '\x1b[33m',
44
- magenta: '\x1b[35m', blue: '\x1b[34m', white: '\x1b[37m',
57
+ reset: "\x1b[0m",
58
+ bright: "\x1b[1m",
59
+ dim: "\x1b[2m",
60
+ cyan: "\x1b[36m",
61
+ green: "\x1b[32m",
62
+ yellow: "\x1b[33m",
63
+ magenta: "\x1b[35m",
64
+ blue: "\x1b[34m",
65
+ white: "\x1b[37m",
45
66
  };
46
67
  }
47
68
 
48
69
  function supportsUnicode() {
49
- if (process.platform === 'win32') {
50
- return !!(process.env.WT_SESSION || process.env.CHCP === '65001' ||
51
- process.env.ConEmuANSI === 'ON' || process.env.TERM_PROGRAM);
70
+ if (process.platform === "win32") {
71
+ return !!(
72
+ process.env.WT_SESSION ||
73
+ process.env.CHCP === "65001" ||
74
+ process.env.ConEmuANSI === "ON" ||
75
+ process.env.TERM_PROGRAM
76
+ );
52
77
  }
53
78
  return true;
54
79
  }
55
80
 
56
81
  function isCI() {
57
- return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION ||
58
- process.env.GITHUB_ACTIONS || process.env.GITLAB_CI ||
59
- process.env.CIRCLECI || process.env.TRAVIS ||
60
- process.env.JENKINS_URL || process.env.BUILD_ID);
82
+ return !!(
83
+ process.env.CI ||
84
+ process.env.CONTINUOUS_INTEGRATION ||
85
+ process.env.GITHUB_ACTIONS ||
86
+ process.env.GITLAB_CI ||
87
+ process.env.CIRCLECI ||
88
+ process.env.TRAVIS ||
89
+ process.env.JENKINS_URL ||
90
+ process.env.BUILD_ID
91
+ );
61
92
  }
62
93
 
63
94
  // Strip ANSI escape codes for accurate length calculation
64
95
  function stripAnsi(s) {
65
- return s.replace(/\x1b\[[0-9;]*m/g, '');
96
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
66
97
  }
67
98
 
68
99
  // Pad string to width W (accounting for ANSI codes)
69
100
  function pad(s, w) {
70
101
  const visible = stripAnsi(s).length;
71
- return s + ' '.repeat(Math.max(0, w - visible));
102
+ return s + " ".repeat(Math.max(0, w - visible));
72
103
  }
73
104
 
74
105
  // Get a writable stream that bypasses npm's output suppression
75
106
  function getOutputStream() {
76
107
  // On Unix, try to write directly to /dev/tty (the terminal)
77
108
  // This bypasses npm's stdout/stderr redirection
78
- if (process.platform !== 'win32' && existsSync('/dev/tty')) {
109
+ if (process.platform !== "win32" && existsSync("/dev/tty")) {
79
110
  try {
80
- return createWriteStream('/dev/tty');
111
+ return createWriteStream("/dev/tty");
81
112
  } catch {
82
113
  // Fall back to stderr
83
114
  }
@@ -95,8 +126,8 @@ function showWelcome() {
95
126
  const u = supportsUnicode();
96
127
 
97
128
  const B = u
98
- ? { tl: '', tr: '', bl: '', br: '', h: '', v: '', dot: '' }
99
- : { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|', dot: '*' };
129
+ ? { tl: "", tr: "", bl: "", br: "", h: "", v: "", dot: "" }
130
+ : { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", dot: "*" };
100
131
 
101
132
  const W = 70;
102
133
  const hr = B.h.repeat(W);
@@ -111,34 +142,34 @@ ${c.cyan}${B.tl}${hr}${B.tr}${c.reset}
111
142
  ${c.cyan}${B.v}${c.reset}${R(` ${c.bright}@emasoft/svg-matrix${c.reset} v${version}`)}${c.cyan}${B.v}${c.reset}
112
143
  ${c.cyan}${B.v}${c.reset}${R(` ${c.dim}Arbitrary-precision SVG transforms with decimal.js${c.reset}`)}${c.cyan}${B.v}${c.reset}
113
144
  ${c.cyan}${B.v}${hr}${B.v}${c.reset}
114
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
145
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
115
146
  ${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}CLI Commands:${c.reset}`)}${c.cyan}${B.v}${c.reset}
116
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
147
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
117
148
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}svg-matrix flatten${c.reset} ${c.dim}Bake transforms into path coordinates${c.reset}`)}${c.cyan}${B.v}${c.reset}
118
149
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}svg-matrix convert${c.reset} ${c.dim}Convert shapes to <path> elements${c.reset}`)}${c.cyan}${B.v}${c.reset}
119
150
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}svg-matrix normalize${c.reset} ${c.dim}Convert paths to cubic Beziers${c.reset}`)}${c.cyan}${B.v}${c.reset}
120
151
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}svg-matrix info${c.reset} ${c.dim}Show SVG file information${c.reset}`)}${c.cyan}${B.v}${c.reset}
121
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
152
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
122
153
  ${c.cyan}${B.v}${c.reset}${R(` ${c.dim}Run${c.reset} svg-matrix --help ${c.dim}or${c.reset} svg-matrix <cmd> --help`)}${c.cyan}${B.v}${c.reset}
123
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
154
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
124
155
  ${c.cyan}${B.v}${hr}${B.v}${c.reset}
125
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
156
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
126
157
  ${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}JavaScript API:${c.reset}`)}${c.cyan}${B.v}${c.reset}
127
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
158
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
128
159
  ${c.cyan}${B.v}${c.reset}${R(` ${c.magenta}import${c.reset} { Matrix, Vector, Transforms2D } ${c.magenta}from${c.reset} '@emasoft/svg-matrix'`)}${c.cyan}${B.v}${c.reset}
129
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
160
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
130
161
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Matrix${c.reset} Arbitrary-precision matrix operations`)}${c.cyan}${B.v}${c.reset}
131
162
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Vector${c.reset} High-precision vector math`)}${c.cyan}${B.v}${c.reset}
132
163
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Transforms2D${c.reset} rotate, scale, translate, skew, reflect`)}${c.cyan}${B.v}${c.reset}
133
164
  ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Transforms3D${c.reset} 3D affine transformations`)}${c.cyan}${B.v}${c.reset}
134
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
165
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
135
166
  ${c.cyan}${B.v}${hr}${B.v}${c.reset}
136
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
137
- ${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}New in v1.0.19:${c.reset}`)}${c.cyan}${B.v}${c.reset}
138
- ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} Enhanced CLI help: svg-matrix <command> --help`)}${c.cyan}${B.v}${c.reset}
139
- ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} High-precision Bezier circle/ellipse approximation`)}${c.cyan}${B.v}${c.reset}
140
- ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} E2E verification always enabled for precision`)}${c.cyan}${B.v}${c.reset}
141
- ${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
167
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
168
+ ${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}New in v1.1.0:${c.reset}`)}${c.cyan}${B.v}${c.reset}
169
+ ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} Universal browser bundles (dist/svg-matrix.min.js, etc.)`)}${c.cyan}${B.v}${c.reset}
170
+ ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} I/O abstraction: loadInput/saveOutput for all environments`)}${c.cyan}${B.v}${c.reset}
171
+ ${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} 70+ SVG toolbox functions, 163+ tests passing`)}${c.cyan}${B.v}${c.reset}
172
+ ${c.cyan}${B.v}${c.reset}${R("")}${c.cyan}${B.v}${c.reset}
142
173
  ${c.cyan}${B.bl}${hr}${B.br}${c.reset}
143
174
 
144
175
  ${c.blue}Docs:${c.reset} https://github.com/Emasoft/SVG-MATRIX#readme
@@ -5,24 +5,24 @@
5
5
  * proper rendering of box characters.
6
6
  */
7
7
 
8
- import { spawn } from 'child_process';
8
+ import { spawn } from "child_process";
9
9
 
10
- console.log('\n=== Testing postinstall.js Unicode/ASCII support ===\n');
10
+ console.log("\n=== Testing postinstall.js Unicode/ASCII support ===\n");
11
11
 
12
12
  const tests = [
13
13
  {
14
- name: 'Unicode mode (UTF-8 locale)',
15
- env: { LANG: 'en_US.UTF-8' },
14
+ name: "Unicode mode (UTF-8 locale)",
15
+ env: { LANG: "en_US.UTF-8" },
16
16
  expectUnicode: true,
17
17
  },
18
18
  {
19
- name: 'ASCII fallback (C locale)',
20
- env: { LANG: 'C' },
19
+ name: "ASCII fallback (C locale)",
20
+ env: { LANG: "C" },
21
21
  expectUnicode: false,
22
22
  },
23
23
  {
24
- name: 'UTF-8 in LC_CTYPE',
25
- env: { LC_CTYPE: 'en_US.UTF-8', LANG: '' },
24
+ name: "UTF-8 in LC_CTYPE",
25
+ env: { LC_CTYPE: "en_US.UTF-8", LANG: "" },
26
26
  expectUnicode: true,
27
27
  },
28
28
  ];
@@ -37,18 +37,18 @@ async function runTest(test) {
37
37
  // Prepare environment
38
38
  const env = { ...process.env, ...test.env };
39
39
 
40
- const child = spawn('node', ['scripts/postinstall.js'], {
40
+ const child = spawn("node", ["scripts/postinstall.js"], {
41
41
  env,
42
- stdio: 'pipe',
42
+ stdio: "pipe",
43
43
  });
44
44
 
45
- let output = '';
45
+ let output = "";
46
46
 
47
- child.stdout.on('data', (data) => {
47
+ child.stdout.on("data", (data) => {
48
48
  output += data.toString();
49
49
  });
50
50
 
51
- child.on('close', () => {
51
+ child.on("close", () => {
52
52
  // Check for Unicode or ASCII box characters
53
53
  const hasUnicodeBox = /[╭╮╰╯─│]/.test(output);
54
54
  const hasAsciiBox = /[+\-|]/.test(output);
@@ -60,12 +60,14 @@ async function runTest(test) {
60
60
  console.log(` ✓ PASS: ASCII fallback characters detected\n`);
61
61
  resolve(true);
62
62
  } else {
63
- console.log(` ✗ FAIL: Expected ${test.expectUnicode ? 'Unicode' : 'ASCII'} but got different output\n`);
63
+ console.log(
64
+ ` ✗ FAIL: Expected ${test.expectUnicode ? "Unicode" : "ASCII"} but got different output\n`,
65
+ );
64
66
  resolve(false);
65
67
  }
66
68
  });
67
69
 
68
- child.on('error', (error) => {
70
+ child.on("error", (error) => {
69
71
  console.log(` ✗ FAIL: ${error.message}\n`);
70
72
  resolve(false);
71
73
  });
@@ -82,7 +84,7 @@ async function runAllTests() {
82
84
  }
83
85
  }
84
86
 
85
- console.log('=== Test Results ===');
87
+ console.log("=== Test Results ===");
86
88
  console.log(`Passed: ${passed}`);
87
89
  console.log(`Failed: ${failed}`);
88
90
  console.log(`Total: ${passed + failed}\n`);
@@ -18,27 +18,41 @@
18
18
  * @license MIT
19
19
  */
20
20
 
21
- import { readFileSync, writeFileSync } from 'fs';
22
- import { fileURLToPath } from 'url';
23
- import { dirname, join } from 'path';
21
+ import { readFileSync, writeFileSync } from "fs";
22
+ import { fileURLToPath } from "url";
23
+ import { dirname, join } from "path";
24
24
 
25
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
- const ROOT = join(__dirname, '..');
26
+ const ROOT = join(__dirname, "..");
27
27
 
28
28
  // ANSI colors for output
29
- const colors = process.env.NO_COLOR !== undefined ? {
30
- reset: '', red: '', green: '', yellow: '', cyan: '', dim: '', bright: '',
31
- } : {
32
- reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m',
33
- yellow: '\x1b[33m', cyan: '\x1b[36m', dim: '\x1b[2m', bright: '\x1b[1m',
34
- };
29
+ const colors =
30
+ process.env.NO_COLOR !== undefined
31
+ ? {
32
+ reset: "",
33
+ red: "",
34
+ green: "",
35
+ yellow: "",
36
+ cyan: "",
37
+ dim: "",
38
+ bright: "",
39
+ }
40
+ : {
41
+ reset: "\x1b[0m",
42
+ red: "\x1b[31m",
43
+ green: "\x1b[32m",
44
+ yellow: "\x1b[33m",
45
+ cyan: "\x1b[36m",
46
+ dim: "\x1b[2m",
47
+ bright: "\x1b[1m",
48
+ };
35
49
 
36
50
  /**
37
51
  * Read the canonical version from package.json
38
52
  * @returns {string} Version string
39
53
  */
40
54
  function getPackageVersion() {
41
- const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
55
+ const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8"));
42
56
  return pkg.version;
43
57
  }
44
58
 
@@ -47,7 +61,7 @@ function getPackageVersion() {
47
61
  * @returns {string|null} Version string or null if not found
48
62
  */
49
63
  function getIndexVersion() {
50
- const content = readFileSync(join(ROOT, 'src', 'index.js'), 'utf8');
64
+ const content = readFileSync(join(ROOT, "src", "index.js"), "utf8");
51
65
  const match = content.match(/export const VERSION = ['"]([^'"]+)['"]/);
52
66
  return match ? match[1] : null;
53
67
  }
@@ -57,18 +71,66 @@ function getIndexVersion() {
57
71
  * @returns {string|null} Version string or null if not found
58
72
  */
59
73
  function getIndexJsDocVersion() {
60
- const content = readFileSync(join(ROOT, 'src', 'index.js'), 'utf8');
74
+ const content = readFileSync(join(ROOT, "src", "index.js"), "utf8");
61
75
  const match = content.match(/@version\s+(\S+)/);
62
76
  return match ? match[1] : null;
63
77
  }
64
78
 
79
+ /**
80
+ * Get version from a library entry point file
81
+ * @param {string} filename - Filename relative to src/
82
+ * @returns {string|null} Version string or null if not found
83
+ */
84
+ function getLibVersion(filename) {
85
+ try {
86
+ const content = readFileSync(join(ROOT, "src", filename), "utf8");
87
+ const match = content.match(/export const VERSION = ['"]([^'"]+)['"]/);
88
+ return match ? match[1] : null;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Update a library entry point file's VERSION constant and @version jsdoc
96
+ * @param {string} filename - Filename relative to src/
97
+ * @param {string} version - New version
98
+ * @returns {boolean} True if updated
99
+ */
100
+ function updateLibVersion(filename, version) {
101
+ const filePath = join(ROOT, "src", filename);
102
+ try {
103
+ let content = readFileSync(filePath, "utf8");
104
+ const original = content;
105
+
106
+ // Update VERSION constant
107
+ content = content.replace(
108
+ /export const VERSION = ['"][^'"]+['"]/,
109
+ `export const VERSION = "${version}"`,
110
+ );
111
+
112
+ // Update @version in jsdoc
113
+ content = content.replace(/@version\s+\S+/, `@version ${version}`);
114
+
115
+ if (content !== original) {
116
+ writeFileSync(filePath, content, "utf8");
117
+ return true;
118
+ }
119
+ } catch {
120
+ // File might not exist
121
+ }
122
+ return false;
123
+ }
124
+
65
125
  /**
66
126
  * Get version from package-lock.json root
67
127
  * @returns {string|null} Version string or null if not found
68
128
  */
69
129
  function getLockfileVersion() {
70
130
  try {
71
- const lock = JSON.parse(readFileSync(join(ROOT, 'package-lock.json'), 'utf8'));
131
+ const lock = JSON.parse(
132
+ readFileSync(join(ROOT, "package-lock.json"), "utf8"),
133
+ );
72
134
  return lock.version;
73
135
  } catch {
74
136
  return null;
@@ -81,24 +143,21 @@ function getLockfileVersion() {
81
143
  * @returns {boolean} True if updated
82
144
  */
83
145
  function updateIndexVersion(version) {
84
- const filePath = join(ROOT, 'src', 'index.js');
85
- let content = readFileSync(filePath, 'utf8');
146
+ const filePath = join(ROOT, "src", "index.js");
147
+ let content = readFileSync(filePath, "utf8");
86
148
  const original = content;
87
149
 
88
150
  // Update VERSION constant
89
151
  content = content.replace(
90
152
  /export const VERSION = ['"][^'"]+['"]/,
91
- `export const VERSION = '${version}'`
153
+ `export const VERSION = '${version}'`,
92
154
  );
93
155
 
94
156
  // Update @version in jsdoc
95
- content = content.replace(
96
- /@version\s+\S+/,
97
- `@version ${version}`
98
- );
157
+ content = content.replace(/@version\s+\S+/, `@version ${version}`);
99
158
 
100
159
  if (content !== original) {
101
- writeFileSync(filePath, content, 'utf8');
160
+ writeFileSync(filePath, content, "utf8");
102
161
  return true;
103
162
  }
104
163
  return false;
@@ -110,19 +169,19 @@ function updateIndexVersion(version) {
110
169
  * @returns {boolean} True if updated
111
170
  */
112
171
  function updateLockfileVersion(version) {
113
- const filePath = join(ROOT, 'package-lock.json');
172
+ const filePath = join(ROOT, "package-lock.json");
114
173
  try {
115
- const lock = JSON.parse(readFileSync(filePath, 'utf8'));
174
+ const lock = JSON.parse(readFileSync(filePath, "utf8"));
116
175
  const original = JSON.stringify(lock);
117
176
 
118
177
  lock.version = version;
119
- if (lock.packages && lock.packages['']) {
120
- lock.packages[''].version = version;
178
+ if (lock.packages && lock.packages[""]) {
179
+ lock.packages[""].version = version;
121
180
  }
122
181
 
123
- const updated = JSON.stringify(lock, null, 2) + '\n';
182
+ const updated = JSON.stringify(lock, null, 2) + "\n";
124
183
  if (updated !== original) {
125
- writeFileSync(filePath, updated, 'utf8');
184
+ writeFileSync(filePath, updated, "utf8");
126
185
  return true;
127
186
  }
128
187
  } catch {
@@ -131,6 +190,13 @@ function updateLockfileVersion(version) {
131
190
  return false;
132
191
  }
133
192
 
193
+ // Library entry points to sync (relative to src/)
194
+ const LIB_ENTRY_POINTS = [
195
+ "svg-matrix-lib.js",
196
+ "svg-toolbox-lib.js",
197
+ "svgm-lib.js",
198
+ ];
199
+
134
200
  /**
135
201
  * Check if all versions are in sync
136
202
  * @returns {{inSync: boolean, versions: Object}}
@@ -142,15 +208,27 @@ function checkVersions() {
142
208
  const lockVersion = getLockfileVersion();
143
209
 
144
210
  const versions = {
145
- 'package.json': pkgVersion,
146
- 'src/index.js (VERSION)': indexVersion,
147
- 'src/index.js (@version)': jsDocVersion,
148
- 'package-lock.json': lockVersion,
211
+ "package.json": pkgVersion,
212
+ "src/index.js (VERSION)": indexVersion,
213
+ "src/index.js (@version)": jsDocVersion,
214
+ "package-lock.json": lockVersion,
149
215
  };
150
216
 
151
- const inSync = indexVersion === pkgVersion &&
152
- jsDocVersion === pkgVersion &&
153
- (lockVersion === null || lockVersion === pkgVersion);
217
+ // WHY: Check VERSION constants in all library entry points
218
+ let allLibsInSync = true;
219
+ for (const lib of LIB_ENTRY_POINTS) {
220
+ const libVersion = getLibVersion(lib);
221
+ versions[`src/${lib}`] = libVersion;
222
+ if (libVersion !== null && libVersion !== pkgVersion) {
223
+ allLibsInSync = false;
224
+ }
225
+ }
226
+
227
+ const inSync =
228
+ indexVersion === pkgVersion &&
229
+ jsDocVersion === pkgVersion &&
230
+ (lockVersion === null || lockVersion === pkgVersion) &&
231
+ allLibsInSync;
154
232
 
155
233
  return { inSync, versions, canonical: pkgVersion };
156
234
  }
@@ -161,7 +239,7 @@ function checkVersions() {
161
239
  function main() {
162
240
  const args = process.argv.slice(2);
163
241
 
164
- if (args.includes('--help') || args.includes('-h')) {
242
+ if (args.includes("--help") || args.includes("-h")) {
165
243
  console.log(`
166
244
  ${colors.bright}version-sync.js${colors.reset} - Synchronize version numbers across the codebase
167
245
 
@@ -171,29 +249,34 @@ ${colors.cyan}Usage:${colors.reset}
171
249
  node scripts/version-sync.js --help Show this help
172
250
 
173
251
  ${colors.cyan}Files updated:${colors.reset}
174
- - src/index.js (VERSION constant)
175
- - src/index.js (@version jsdoc)
252
+ - src/index.js (VERSION constant and @version jsdoc)
253
+ - src/svg-matrix-lib.js (VERSION constant and @version jsdoc)
254
+ - src/svg-toolbox-lib.js (VERSION constant and @version jsdoc)
255
+ - src/svgm-lib.js (VERSION constant and @version jsdoc)
176
256
  - package-lock.json (if present)
177
257
  `);
178
258
  process.exit(0);
179
259
  }
180
260
 
181
- const checkOnly = args.includes('--check');
261
+ const checkOnly = args.includes("--check");
182
262
  const { inSync, versions, canonical } = checkVersions();
183
263
 
184
264
  console.log(`${colors.bright}Version Check${colors.reset}`);
185
- console.log(`${colors.dim}${''.repeat(50)}${colors.reset}`);
265
+ console.log(`${colors.dim}${"".repeat(50)}${colors.reset}`);
186
266
 
187
267
  for (const [file, version] of Object.entries(versions)) {
188
- const status = version === canonical
189
- ? `${colors.green}OK${colors.reset}`
190
- : version === null
191
- ? `${colors.yellow}N/A${colors.reset}`
192
- : `${colors.red}MISMATCH${colors.reset}`;
193
- console.log(` ${file.padEnd(30)} ${(version || 'N/A').padEnd(12)} ${status}`);
268
+ const status =
269
+ version === canonical
270
+ ? `${colors.green}OK${colors.reset}`
271
+ : version === null
272
+ ? `${colors.yellow}N/A${colors.reset}`
273
+ : `${colors.red}MISMATCH${colors.reset}`;
274
+ console.log(
275
+ ` ${file.padEnd(30)} ${(version || "N/A").padEnd(12)} ${status}`,
276
+ );
194
277
  }
195
278
 
196
- console.log(`${colors.dim}${''.repeat(50)}${colors.reset}`);
279
+ console.log(`${colors.dim}${"".repeat(50)}${colors.reset}`);
197
280
  console.log(` Canonical version: ${colors.cyan}${canonical}${colors.reset}`);
198
281
 
199
282
  if (inSync) {
@@ -203,7 +286,9 @@ ${colors.cyan}Files updated:${colors.reset}
203
286
 
204
287
  if (checkOnly) {
205
288
  console.log(`\n${colors.red}Version mismatch detected!${colors.reset}`);
206
- console.log(`Run ${colors.cyan}node scripts/version-sync.js${colors.reset} to fix.`);
289
+ console.log(
290
+ `Run ${colors.cyan}node scripts/version-sync.js${colors.reset} to fix.`,
291
+ );
207
292
  process.exit(1);
208
293
  }
209
294
 
@@ -220,8 +305,18 @@ ${colors.cyan}Files updated:${colors.reset}
220
305
  updated++;
221
306
  }
222
307
 
308
+ // WHY: Sync VERSION constants in all library entry points
309
+ for (const lib of LIB_ENTRY_POINTS) {
310
+ if (updateLibVersion(lib, canonical)) {
311
+ console.log(` ${colors.green}Updated${colors.reset} src/${lib}`);
312
+ updated++;
313
+ }
314
+ }
315
+
223
316
  if (updated > 0) {
224
- console.log(`\n${colors.green}Synced ${updated} file(s) to version ${canonical}${colors.reset}`);
317
+ console.log(
318
+ `\n${colors.green}Synced ${updated} file(s) to version ${canonical}${colors.reset}`,
319
+ );
225
320
  } else {
226
321
  console.log(`\n${colors.green}All files already in sync.${colors.reset}`);
227
322
  }