@emasoft/svg-matrix 1.0.13 → 1.0.15
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 +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +101 -197
package/bin/svg-matrix.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,252 +1,156 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* @fileoverview Post-install welcome message for @emasoft/svg-matrix
|
|
4
|
-
* Displays a
|
|
4
|
+
* Displays a summary of CLI commands and API functions after npm install.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Any error is caught and swallowed with exit(0).
|
|
9
|
-
* - Be respectful of terminal real estate: Keep the message compact.
|
|
10
|
-
* Users want to continue working, not read documentation.
|
|
11
|
-
* - Cross-platform: Handle Windows legacy terminals that don't support ANSI.
|
|
12
|
-
* - Standards-compliant: Respect NO_COLOR environment variable.
|
|
6
|
+
* Note: npm 7+ suppresses lifecycle script output by default.
|
|
7
|
+
* We write directly to /dev/tty on Unix to bypass this.
|
|
13
8
|
*
|
|
14
9
|
* @module scripts/postinstall
|
|
15
10
|
* @license MIT
|
|
16
11
|
*/
|
|
17
12
|
|
|
18
|
-
import { readFileSync } from 'fs';
|
|
13
|
+
import { readFileSync, createWriteStream, existsSync } from 'fs';
|
|
19
14
|
import { fileURLToPath } from 'url';
|
|
20
15
|
import { dirname, join } from 'path';
|
|
21
16
|
|
|
22
|
-
// ============================================================================
|
|
23
|
-
// VERSION DETECTION
|
|
24
|
-
// ============================================================================
|
|
25
|
-
/**
|
|
26
|
-
* Read version from package.json dynamically.
|
|
27
|
-
*
|
|
28
|
-
* Why dynamic reading instead of hardcoding:
|
|
29
|
-
* - Avoids version mismatch bugs where postinstall shows wrong version.
|
|
30
|
-
* - Single source of truth in package.json.
|
|
31
|
-
*
|
|
32
|
-
* Why wrapped in try-catch:
|
|
33
|
-
* - postinstall scripts must NEVER fail - this would break npm install.
|
|
34
|
-
* - Return 'unknown' gracefully if file read fails for any reason.
|
|
35
|
-
*
|
|
36
|
-
* @returns {string} Version string or 'unknown' on error
|
|
37
|
-
*/
|
|
38
17
|
function getVersion() {
|
|
39
18
|
try {
|
|
40
|
-
// Why: ESM doesn't have __dirname, must derive from import.meta.url
|
|
41
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
|
-
// Why: Go up one level because this script is in scripts/ subfolder
|
|
43
20
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
44
21
|
return pkg.version || 'unknown';
|
|
45
22
|
} catch {
|
|
46
|
-
// Why: Return safe default - never throw from postinstall
|
|
47
23
|
return 'unknown';
|
|
48
24
|
}
|
|
49
25
|
}
|
|
50
26
|
|
|
51
|
-
// ============================================================================
|
|
52
|
-
// COLOR SUPPORT DETECTION
|
|
53
|
-
// ============================================================================
|
|
54
|
-
/**
|
|
55
|
-
* Check if colors should be disabled.
|
|
56
|
-
*
|
|
57
|
-
* Why this function:
|
|
58
|
-
* - Not all terminals support ANSI escape codes.
|
|
59
|
-
* - Showing raw escape codes is worse than no colors.
|
|
60
|
-
* - NO_COLOR is an emerging standard: https://no-color.org
|
|
61
|
-
*
|
|
62
|
-
* @returns {boolean} True if colors should be disabled
|
|
63
|
-
*/
|
|
64
27
|
function shouldDisableColors() {
|
|
65
|
-
|
|
66
|
-
// Check for presence, not truthiness, per spec
|
|
67
|
-
if (process.env.NO_COLOR !== undefined) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Why: Windows cmd.exe (not PowerShell/Terminal) doesn't support ANSI by default
|
|
72
|
-
// Check for known ANSI-capable Windows terminals
|
|
28
|
+
if (process.env.NO_COLOR !== undefined) return true;
|
|
73
29
|
if (process.platform === 'win32') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
process.env.ConEmuANSI === 'ON' || // ConEmu explicitly signals support
|
|
77
|
-
process.env.TERM_PROGRAM || // VS Code, Hyper, etc. set this
|
|
78
|
-
process.env.ANSICON; // ANSICON utility for legacy cmd
|
|
79
|
-
// Why: Disable colors unless we detect ANSI support
|
|
80
|
-
return !supportsAnsi;
|
|
30
|
+
return !(process.env.WT_SESSION || process.env.ConEmuANSI === 'ON' ||
|
|
31
|
+
process.env.TERM_PROGRAM || process.env.ANSICON);
|
|
81
32
|
}
|
|
82
|
-
|
|
83
|
-
// Why: Unix terminals generally support ANSI, so enable by default
|
|
84
33
|
return false;
|
|
85
34
|
}
|
|
86
35
|
|
|
87
|
-
/**
|
|
88
|
-
* ANSI color codes for terminal output.
|
|
89
|
-
*
|
|
90
|
-
* Why function instead of constant:
|
|
91
|
-
* - Need to check color support at runtime, not module load time.
|
|
92
|
-
* - Environment variables may change between import and execution.
|
|
93
|
-
*
|
|
94
|
-
* @param {boolean} disabled - Whether colors are disabled
|
|
95
|
-
* @returns {Object} Color code object
|
|
96
|
-
*/
|
|
97
36
|
function getColors(disabled) {
|
|
98
|
-
// Why: Return empty strings instead of undefined to avoid string concat issues
|
|
99
37
|
if (disabled) {
|
|
100
|
-
return {
|
|
101
|
-
|
|
102
|
-
cyan: '', green: '', yellow: '',
|
|
103
|
-
magenta: '', blue: '', white: '',
|
|
104
|
-
};
|
|
38
|
+
return { reset: '', bright: '', dim: '', cyan: '', green: '',
|
|
39
|
+
yellow: '', magenta: '', blue: '', white: '' };
|
|
105
40
|
}
|
|
106
|
-
// Why: Use standard ANSI SGR (Select Graphic Rendition) codes
|
|
107
|
-
// Format: ESC[<code>m where ESC is \x1b
|
|
108
41
|
return {
|
|
109
|
-
reset: '\x1b[0m',
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cyan: '\x1b[36m', // Cyan foreground
|
|
113
|
-
green: '\x1b[32m', // Green foreground
|
|
114
|
-
yellow: '\x1b[33m', // Yellow foreground
|
|
115
|
-
magenta: '\x1b[35m', // Magenta foreground
|
|
116
|
-
blue: '\x1b[34m', // Blue foreground
|
|
117
|
-
white: '\x1b[37m', // White foreground
|
|
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',
|
|
118
45
|
};
|
|
119
46
|
}
|
|
120
47
|
|
|
121
|
-
// ============================================================================
|
|
122
|
-
// UNICODE SUPPORT DETECTION
|
|
123
|
-
// ============================================================================
|
|
124
|
-
/**
|
|
125
|
-
* Check if terminal likely supports Unicode box drawing characters.
|
|
126
|
-
*
|
|
127
|
-
* Why this check:
|
|
128
|
-
* - Some older terminals or SSH sessions may not render Unicode correctly.
|
|
129
|
-
* - Windows cmd.exe before Windows 10 has limited Unicode support.
|
|
130
|
-
* - Better to show ASCII fallback than broken box characters.
|
|
131
|
-
*
|
|
132
|
-
* @returns {boolean} True if Unicode is likely supported
|
|
133
|
-
*/
|
|
134
48
|
function supportsUnicode() {
|
|
135
|
-
// Why: Check common environment indicators for Unicode support
|
|
136
|
-
|
|
137
|
-
// Windows legacy cmd.exe typically has codepage issues
|
|
138
49
|
if (process.platform === 'win32') {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Check for UTF-8 codepage
|
|
142
|
-
if (process.env.CHCP === '65001') return true;
|
|
143
|
-
// ConEmu and other modern terminals
|
|
144
|
-
if (process.env.ConEmuANSI === 'ON') return true;
|
|
145
|
-
if (process.env.TERM_PROGRAM) return true;
|
|
146
|
-
// Default to ASCII on Windows for safety
|
|
147
|
-
return false;
|
|
50
|
+
return !!(process.env.WT_SESSION || process.env.CHCP === '65001' ||
|
|
51
|
+
process.env.ConEmuANSI === 'ON' || process.env.TERM_PROGRAM);
|
|
148
52
|
}
|
|
149
|
-
|
|
150
|
-
// Why: Unix terminals generally support Unicode if LANG/LC_CTYPE mentions UTF-8
|
|
151
|
-
const lang = process.env.LANG || process.env.LC_CTYPE || '';
|
|
152
|
-
if (lang.toLowerCase().includes('utf')) return true;
|
|
153
|
-
|
|
154
|
-
// Why: Modern terminal emulators typically support Unicode
|
|
155
|
-
if (process.env.TERM_PROGRAM) return true;
|
|
156
|
-
|
|
157
|
-
// Default to Unicode on Unix (most modern systems support it)
|
|
158
53
|
return true;
|
|
159
54
|
}
|
|
160
55
|
|
|
161
|
-
// ============================================================================
|
|
162
|
-
// CI DETECTION
|
|
163
|
-
// ============================================================================
|
|
164
|
-
/**
|
|
165
|
-
* Check if running in CI environment.
|
|
166
|
-
*
|
|
167
|
-
* Why skip in CI:
|
|
168
|
-
* - CI logs should be clean and focused on build/test output.
|
|
169
|
-
* - Welcome messages just add noise to CI logs.
|
|
170
|
-
* - CI systems can set CI=true to suppress this and similar messages.
|
|
171
|
-
*
|
|
172
|
-
* @returns {boolean} True if in CI
|
|
173
|
-
*/
|
|
174
56
|
function isCI() {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
process.env.CONTINUOUS_INTEGRATION || // Travis CI
|
|
180
|
-
process.env.GITHUB_ACTIONS || // GitHub Actions (redundant with CI but explicit)
|
|
181
|
-
process.env.GITLAB_CI || // GitLab CI
|
|
182
|
-
process.env.CIRCLECI || // CircleCI
|
|
183
|
-
process.env.TRAVIS || // Travis CI
|
|
184
|
-
process.env.JENKINS_URL || // Jenkins
|
|
185
|
-
process.env.BUILD_ID // Various CI systems
|
|
186
|
-
);
|
|
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);
|
|
187
61
|
}
|
|
188
62
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
*
|
|
192
|
-
|
|
193
|
-
* Why this design:
|
|
194
|
-
* - Skip silently in CI: CI environments don't need welcome messages, and
|
|
195
|
-
* they clutter build logs. Most CI systems set CI=true or similar.
|
|
196
|
-
* - Skip in non-TTY: If stdout isn't a terminal (e.g., piped to file),
|
|
197
|
-
* ANSI codes would be visible as garbage characters.
|
|
198
|
-
* - Compact message: Users just installed the package and want to continue
|
|
199
|
-
* their work. A brief message with the essentials respects their time.
|
|
200
|
-
* Full documentation belongs in README, not terminal output.
|
|
201
|
-
*/
|
|
202
|
-
function showWelcome() {
|
|
203
|
-
// Why: CI builds don't need welcome messages, they just add noise to logs
|
|
204
|
-
if (isCI()) {
|
|
205
|
-
process.exit(0);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Why: Non-TTY means output is being piped/redirected - ANSI codes would be garbage
|
|
209
|
-
if (!process.stdout.isTTY) {
|
|
210
|
-
process.exit(0);
|
|
211
|
-
}
|
|
63
|
+
// Strip ANSI escape codes for accurate length calculation
|
|
64
|
+
function stripAnsi(s) {
|
|
65
|
+
return s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
66
|
+
}
|
|
212
67
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Why: Use ASCII box characters as fallback for terminals without Unicode
|
|
219
|
-
const box = unicode ? {
|
|
220
|
-
tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│'
|
|
221
|
-
} : {
|
|
222
|
-
tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|'
|
|
223
|
-
};
|
|
68
|
+
// Pad string to width W (accounting for ANSI codes)
|
|
69
|
+
function pad(s, w) {
|
|
70
|
+
const visible = stripAnsi(s).length;
|
|
71
|
+
return s + ' '.repeat(Math.max(0, w - visible));
|
|
72
|
+
}
|
|
224
73
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
74
|
+
// Get a writable stream that bypasses npm's output suppression
|
|
75
|
+
function getOutputStream() {
|
|
76
|
+
// On Unix, try to write directly to /dev/tty (the terminal)
|
|
77
|
+
// This bypasses npm's stdout/stderr redirection
|
|
78
|
+
if (process.platform !== 'win32' && existsSync('/dev/tty')) {
|
|
79
|
+
try {
|
|
80
|
+
return createWriteStream('/dev/tty');
|
|
81
|
+
} catch {
|
|
82
|
+
// Fall back to stderr
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// On Windows or if /dev/tty fails, use stderr
|
|
86
|
+
return process.stderr;
|
|
87
|
+
}
|
|
232
88
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
`;
|
|
89
|
+
function showWelcome() {
|
|
90
|
+
// Skip in CI environments - no need to clutter build logs
|
|
91
|
+
if (isCI()) process.exit(0);
|
|
237
92
|
|
|
238
|
-
|
|
93
|
+
const version = getVersion();
|
|
94
|
+
const c = getColors(shouldDisableColors());
|
|
95
|
+
const u = supportsUnicode();
|
|
96
|
+
|
|
97
|
+
const B = u
|
|
98
|
+
? { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│', dot: '•' }
|
|
99
|
+
: { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|', dot: '*' };
|
|
100
|
+
|
|
101
|
+
const W = 66;
|
|
102
|
+
const hr = B.h.repeat(W);
|
|
103
|
+
const R = (s) => pad(s, W);
|
|
104
|
+
|
|
105
|
+
// Get output stream that bypasses npm suppression
|
|
106
|
+
const out = getOutputStream();
|
|
107
|
+
const write = (s) => out.write(s);
|
|
108
|
+
|
|
109
|
+
write(`
|
|
110
|
+
${c.cyan}${B.tl}${hr}${B.tr}${c.reset}
|
|
111
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.bright}@emasoft/svg-matrix${c.reset} v${version}`)}${c.cyan}${B.v}${c.reset}
|
|
112
|
+
${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
|
+
${c.cyan}${B.v}${hr}${B.v}${c.reset}
|
|
114
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
115
|
+
${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}
|
|
117
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}svg-matrix flatten${c.reset} input.svg -o output.svg`)}${c.cyan}${B.v}${c.reset}
|
|
118
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}Bake all transforms into path coordinates${c.reset}`)}${c.cyan}${B.v}${c.reset}
|
|
119
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
120
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}Options:${c.reset}`)}${c.cyan}${B.v}${c.reset}
|
|
121
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}--precision N${c.reset} Output decimal places (default: 6)`)}${c.cyan}${B.v}${c.reset}
|
|
122
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}--clip-segments N${c.reset} Polygon sampling for clips (default: 64)`)}${c.cyan}${B.v}${c.reset}
|
|
123
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}--bezier-arcs N${c.reset} Bezier arcs for curves (default: 8)`)}${c.cyan}${B.v}${c.reset}
|
|
124
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}--e2e-tolerance N${c.reset} Verification tolerance (default: 1e-10)`)}${c.cyan}${B.v}${c.reset}
|
|
125
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.dim}--verbose${c.reset} Show processing details`)}${c.cyan}${B.v}${c.reset}
|
|
126
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
127
|
+
${c.cyan}${B.v}${hr}${B.v}${c.reset}
|
|
128
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
129
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}JavaScript API:${c.reset}`)}${c.cyan}${B.v}${c.reset}
|
|
130
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
131
|
+
${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}
|
|
132
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
133
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Matrix${c.reset} Arbitrary-precision matrix operations`)}${c.cyan}${B.v}${c.reset}
|
|
134
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Vector${c.reset} High-precision vector math`)}${c.cyan}${B.v}${c.reset}
|
|
135
|
+
${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}
|
|
136
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot} Transforms3D${c.reset} 3D affine transformations`)}${c.cyan}${B.v}${c.reset}
|
|
137
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
138
|
+
${c.cyan}${B.v}${hr}${B.v}${c.reset}
|
|
139
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
140
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.yellow}New in v1.0.13:${c.reset}`)}${c.cyan}${B.v}${c.reset}
|
|
141
|
+
${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}
|
|
142
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} E2E verification for clip-path operations`)}${c.cyan}${B.v}${c.reset}
|
|
143
|
+
${c.cyan}${B.v}${c.reset}${R(` ${c.green}${B.dot}${c.reset} Configurable: --clip-segments, --bezier-arcs, --e2e-tolerance`)}${c.cyan}${B.v}${c.reset}
|
|
144
|
+
${c.cyan}${B.v}${c.reset}${R('')}${c.cyan}${B.v}${c.reset}
|
|
145
|
+
${c.cyan}${B.bl}${hr}${B.br}${c.reset}
|
|
146
|
+
|
|
147
|
+
${c.blue}Docs:${c.reset} https://github.com/Emasoft/SVG-MATRIX#readme
|
|
148
|
+
${c.blue}Help:${c.reset} svg-matrix --help
|
|
149
|
+
`);
|
|
239
150
|
}
|
|
240
151
|
|
|
241
|
-
// ============================================================================
|
|
242
|
-
// MAIN ENTRY
|
|
243
|
-
// ============================================================================
|
|
244
|
-
// Why try-catch: postinstall scripts must NEVER fail or throw.
|
|
245
|
-
// A failed postinstall would break `npm install` for users, which is
|
|
246
|
-
// absolutely unacceptable. Catch everything and exit cleanly.
|
|
247
152
|
try {
|
|
248
153
|
showWelcome();
|
|
249
154
|
} catch {
|
|
250
|
-
// Why: Silent exit - don't break npm install for a welcome message
|
|
251
155
|
process.exit(0);
|
|
252
156
|
}
|