@emasoft/svg-matrix 1.1.0 → 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 (55) hide show
  1. package/bin/svg-matrix.js +7 -6
  2. package/bin/svgm.js +109 -40
  3. package/dist/svg-matrix.min.js +7 -7
  4. package/dist/svg-toolbox.min.js +148 -228
  5. package/dist/svgm.min.js +152 -232
  6. package/dist/version.json +5 -5
  7. package/package.json +1 -1
  8. package/scripts/postinstall.js +72 -41
  9. package/scripts/test-postinstall.js +18 -16
  10. package/scripts/version-sync.js +78 -60
  11. package/src/animation-optimization.js +190 -98
  12. package/src/animation-references.js +11 -3
  13. package/src/arc-length.js +23 -20
  14. package/src/bezier-analysis.js +9 -13
  15. package/src/bezier-intersections.js +18 -4
  16. package/src/browser-verify.js +35 -8
  17. package/src/clip-path-resolver.js +285 -114
  18. package/src/convert-path-data.js +20 -8
  19. package/src/css-specificity.js +33 -9
  20. package/src/douglas-peucker.js +272 -141
  21. package/src/geometry-to-path.js +79 -22
  22. package/src/gjk-collision.js +287 -126
  23. package/src/index.js +56 -21
  24. package/src/inkscape-support.js +122 -101
  25. package/src/logger.js +43 -27
  26. package/src/marker-resolver.js +201 -121
  27. package/src/mask-resolver.js +231 -98
  28. package/src/matrix.js +9 -5
  29. package/src/mesh-gradient.js +22 -14
  30. package/src/off-canvas-detection.js +53 -17
  31. package/src/path-optimization.js +356 -171
  32. package/src/path-simplification.js +671 -256
  33. package/src/pattern-resolver.js +1 -3
  34. package/src/polygon-clip.js +396 -78
  35. package/src/svg-boolean-ops.js +90 -23
  36. package/src/svg-collections.js +1546 -667
  37. package/src/svg-flatten.js +152 -38
  38. package/src/svg-matrix-lib.js +2 -2
  39. package/src/svg-parser.js +5 -1
  40. package/src/svg-rendering-context.js +3 -1
  41. package/src/svg-toolbox-lib.js +2 -2
  42. package/src/svg-toolbox.js +99 -457
  43. package/src/svg-validation-data.js +513 -345
  44. package/src/svg2-polyfills.js +156 -93
  45. package/src/svgm-lib.js +8 -4
  46. package/src/transform-optimization.js +168 -51
  47. package/src/transforms2d.js +73 -40
  48. package/src/transforms3d.js +34 -27
  49. package/src/use-symbol-resolver.js +175 -76
  50. package/src/vector.js +80 -44
  51. package/src/vendor/inkscape-hatch-polyfill.js +143 -108
  52. package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
  53. package/src/vendor/inkscape-mesh-polyfill.js +953 -766
  54. package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
  55. package/src/verification.js +3 -4
package/dist/version.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
- "version": "1.1.0",
3
- "buildTime": "2025-12-31T20:26:40.049Z",
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": 55697
7
+ "size": 56106
8
8
  },
9
9
  {
10
10
  "name": "svg-toolbox.min.js",
11
- "size": 580973
11
+ "size": 577198
12
12
  },
13
13
  {
14
14
  "name": "svgm.min.js",
15
- "size": 606344
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.1.0",
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",
@@ -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,7 +71,7 @@ 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
  }
@@ -69,7 +83,7 @@ function getIndexJsDocVersion() {
69
83
  */
70
84
  function getLibVersion(filename) {
71
85
  try {
72
- const content = readFileSync(join(ROOT, 'src', filename), 'utf8');
86
+ const content = readFileSync(join(ROOT, "src", filename), "utf8");
73
87
  const match = content.match(/export const VERSION = ['"]([^'"]+)['"]/);
74
88
  return match ? match[1] : null;
75
89
  } catch {
@@ -84,25 +98,22 @@ function getLibVersion(filename) {
84
98
  * @returns {boolean} True if updated
85
99
  */
86
100
  function updateLibVersion(filename, version) {
87
- const filePath = join(ROOT, 'src', filename);
101
+ const filePath = join(ROOT, "src", filename);
88
102
  try {
89
- let content = readFileSync(filePath, 'utf8');
103
+ let content = readFileSync(filePath, "utf8");
90
104
  const original = content;
91
105
 
92
106
  // Update VERSION constant
93
107
  content = content.replace(
94
108
  /export const VERSION = ['"][^'"]+['"]/,
95
- `export const VERSION = "${version}"`
109
+ `export const VERSION = "${version}"`,
96
110
  );
97
111
 
98
112
  // Update @version in jsdoc
99
- content = content.replace(
100
- /@version\s+\S+/,
101
- `@version ${version}`
102
- );
113
+ content = content.replace(/@version\s+\S+/, `@version ${version}`);
103
114
 
104
115
  if (content !== original) {
105
- writeFileSync(filePath, content, 'utf8');
116
+ writeFileSync(filePath, content, "utf8");
106
117
  return true;
107
118
  }
108
119
  } catch {
@@ -117,7 +128,9 @@ function updateLibVersion(filename, version) {
117
128
  */
118
129
  function getLockfileVersion() {
119
130
  try {
120
- 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
+ );
121
134
  return lock.version;
122
135
  } catch {
123
136
  return null;
@@ -130,24 +143,21 @@ function getLockfileVersion() {
130
143
  * @returns {boolean} True if updated
131
144
  */
132
145
  function updateIndexVersion(version) {
133
- const filePath = join(ROOT, 'src', 'index.js');
134
- let content = readFileSync(filePath, 'utf8');
146
+ const filePath = join(ROOT, "src", "index.js");
147
+ let content = readFileSync(filePath, "utf8");
135
148
  const original = content;
136
149
 
137
150
  // Update VERSION constant
138
151
  content = content.replace(
139
152
  /export const VERSION = ['"][^'"]+['"]/,
140
- `export const VERSION = '${version}'`
153
+ `export const VERSION = '${version}'`,
141
154
  );
142
155
 
143
156
  // Update @version in jsdoc
144
- content = content.replace(
145
- /@version\s+\S+/,
146
- `@version ${version}`
147
- );
157
+ content = content.replace(/@version\s+\S+/, `@version ${version}`);
148
158
 
149
159
  if (content !== original) {
150
- writeFileSync(filePath, content, 'utf8');
160
+ writeFileSync(filePath, content, "utf8");
151
161
  return true;
152
162
  }
153
163
  return false;
@@ -159,19 +169,19 @@ function updateIndexVersion(version) {
159
169
  * @returns {boolean} True if updated
160
170
  */
161
171
  function updateLockfileVersion(version) {
162
- const filePath = join(ROOT, 'package-lock.json');
172
+ const filePath = join(ROOT, "package-lock.json");
163
173
  try {
164
- const lock = JSON.parse(readFileSync(filePath, 'utf8'));
174
+ const lock = JSON.parse(readFileSync(filePath, "utf8"));
165
175
  const original = JSON.stringify(lock);
166
176
 
167
177
  lock.version = version;
168
- if (lock.packages && lock.packages['']) {
169
- lock.packages[''].version = version;
178
+ if (lock.packages && lock.packages[""]) {
179
+ lock.packages[""].version = version;
170
180
  }
171
181
 
172
- const updated = JSON.stringify(lock, null, 2) + '\n';
182
+ const updated = JSON.stringify(lock, null, 2) + "\n";
173
183
  if (updated !== original) {
174
- writeFileSync(filePath, updated, 'utf8');
184
+ writeFileSync(filePath, updated, "utf8");
175
185
  return true;
176
186
  }
177
187
  } catch {
@@ -182,9 +192,9 @@ function updateLockfileVersion(version) {
182
192
 
183
193
  // Library entry points to sync (relative to src/)
184
194
  const LIB_ENTRY_POINTS = [
185
- 'svg-matrix-lib.js',
186
- 'svg-toolbox-lib.js',
187
- 'svgm-lib.js',
195
+ "svg-matrix-lib.js",
196
+ "svg-toolbox-lib.js",
197
+ "svgm-lib.js",
188
198
  ];
189
199
 
190
200
  /**
@@ -198,10 +208,10 @@ function checkVersions() {
198
208
  const lockVersion = getLockfileVersion();
199
209
 
200
210
  const versions = {
201
- 'package.json': pkgVersion,
202
- 'src/index.js (VERSION)': indexVersion,
203
- 'src/index.js (@version)': jsDocVersion,
204
- '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,
205
215
  };
206
216
 
207
217
  // WHY: Check VERSION constants in all library entry points
@@ -214,10 +224,11 @@ function checkVersions() {
214
224
  }
215
225
  }
216
226
 
217
- const inSync = indexVersion === pkgVersion &&
218
- jsDocVersion === pkgVersion &&
219
- (lockVersion === null || lockVersion === pkgVersion) &&
220
- allLibsInSync;
227
+ const inSync =
228
+ indexVersion === pkgVersion &&
229
+ jsDocVersion === pkgVersion &&
230
+ (lockVersion === null || lockVersion === pkgVersion) &&
231
+ allLibsInSync;
221
232
 
222
233
  return { inSync, versions, canonical: pkgVersion };
223
234
  }
@@ -228,7 +239,7 @@ function checkVersions() {
228
239
  function main() {
229
240
  const args = process.argv.slice(2);
230
241
 
231
- if (args.includes('--help') || args.includes('-h')) {
242
+ if (args.includes("--help") || args.includes("-h")) {
232
243
  console.log(`
233
244
  ${colors.bright}version-sync.js${colors.reset} - Synchronize version numbers across the codebase
234
245
 
@@ -247,22 +258,25 @@ ${colors.cyan}Files updated:${colors.reset}
247
258
  process.exit(0);
248
259
  }
249
260
 
250
- const checkOnly = args.includes('--check');
261
+ const checkOnly = args.includes("--check");
251
262
  const { inSync, versions, canonical } = checkVersions();
252
263
 
253
264
  console.log(`${colors.bright}Version Check${colors.reset}`);
254
- console.log(`${colors.dim}${''.repeat(50)}${colors.reset}`);
265
+ console.log(`${colors.dim}${"".repeat(50)}${colors.reset}`);
255
266
 
256
267
  for (const [file, version] of Object.entries(versions)) {
257
- const status = version === canonical
258
- ? `${colors.green}OK${colors.reset}`
259
- : version === null
260
- ? `${colors.yellow}N/A${colors.reset}`
261
- : `${colors.red}MISMATCH${colors.reset}`;
262
- 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
+ );
263
277
  }
264
278
 
265
- console.log(`${colors.dim}${''.repeat(50)}${colors.reset}`);
279
+ console.log(`${colors.dim}${"".repeat(50)}${colors.reset}`);
266
280
  console.log(` Canonical version: ${colors.cyan}${canonical}${colors.reset}`);
267
281
 
268
282
  if (inSync) {
@@ -272,7 +286,9 @@ ${colors.cyan}Files updated:${colors.reset}
272
286
 
273
287
  if (checkOnly) {
274
288
  console.log(`\n${colors.red}Version mismatch detected!${colors.reset}`);
275
- 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
+ );
276
292
  process.exit(1);
277
293
  }
278
294
 
@@ -298,7 +314,9 @@ ${colors.cyan}Files updated:${colors.reset}
298
314
  }
299
315
 
300
316
  if (updated > 0) {
301
- 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
+ );
302
320
  } else {
303
321
  console.log(`\n${colors.green}All files already in sync.${colors.reset}`);
304
322
  }