@vizzly-testing/cli 0.22.1 → 0.22.2
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/dist/client/index.js
CHANGED
|
@@ -188,7 +188,9 @@ function createSimpleClient(serverUrl) {
|
|
|
188
188
|
try {
|
|
189
189
|
// If it's a string, assume it's a file path and send directly
|
|
190
190
|
// Otherwise it's a Buffer, so convert to base64
|
|
191
|
-
|
|
191
|
+
let isFilePath = typeof imageBuffer === 'string';
|
|
192
|
+
let image = isFilePath ? imageBuffer : imageBuffer.toString('base64');
|
|
193
|
+
let type = isFilePath ? 'file-path' : 'base64';
|
|
192
194
|
const {
|
|
193
195
|
status,
|
|
194
196
|
json
|
|
@@ -196,6 +198,7 @@ function createSimpleClient(serverUrl) {
|
|
|
196
198
|
buildId: getBuildId(),
|
|
197
199
|
name,
|
|
198
200
|
image,
|
|
201
|
+
type,
|
|
199
202
|
properties: options,
|
|
200
203
|
fullPage: options.fullPage || false
|
|
201
204
|
}, DEFAULT_TIMEOUT_MS);
|
package/dist/sdk/index.js
CHANGED
|
@@ -47,7 +47,7 @@ export const createApiHandler = (client, {
|
|
|
47
47
|
let vizzlyDisabled = false;
|
|
48
48
|
let screenshotCount = 0;
|
|
49
49
|
let uploadPromises = [];
|
|
50
|
-
const handleScreenshot = async (buildId, name, image, properties = {}) => {
|
|
50
|
+
const handleScreenshot = async (buildId, name, image, properties = {}, type) => {
|
|
51
51
|
if (vizzlyDisabled) {
|
|
52
52
|
output.debug('upload', `${name} (disabled)`);
|
|
53
53
|
return {
|
|
@@ -73,8 +73,11 @@ export const createApiHandler = (client, {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// Support both base64 encoded images and file paths
|
|
76
|
+
// Use explicit type from client if provided (fast path), otherwise detect (slow path)
|
|
77
|
+
// Only accept valid type values to prevent invalid types from bypassing detection
|
|
76
78
|
let imageBuffer;
|
|
77
|
-
|
|
79
|
+
let validTypes = ['base64', 'file-path'];
|
|
80
|
+
const inputType = type && validTypes.includes(type) ? type : detectImageInputType(image);
|
|
78
81
|
if (inputType === 'file-path') {
|
|
79
82
|
// It's a file path - resolve and read the file
|
|
80
83
|
const filePath = resolve(image.replace('file://', ''));
|
|
@@ -267,7 +267,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
267
267
|
output.debug('tdd', `baseline: ${baseline.buildName}`);
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
|
-
const handleScreenshot = async (_buildId, name, image, properties = {}) => {
|
|
270
|
+
const handleScreenshot = async (_buildId, name, image, properties = {}, type) => {
|
|
271
271
|
// Validate and sanitize screenshot name
|
|
272
272
|
let sanitizedName;
|
|
273
273
|
try {
|
|
@@ -306,8 +306,11 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
306
306
|
|
|
307
307
|
// Support both base64 encoded images and file paths
|
|
308
308
|
// Vitest browser mode returns file paths, so we need to handle both
|
|
309
|
+
// Use explicit type from client if provided (fast path), otherwise detect (slow path)
|
|
310
|
+
// Only accept valid type values to prevent invalid types from bypassing detection
|
|
309
311
|
let imageBuffer;
|
|
310
|
-
|
|
312
|
+
let validTypes = ['base64', 'file-path'];
|
|
313
|
+
const inputType = type && validTypes.includes(type) ? type : detectImageInputType(image);
|
|
311
314
|
if (inputType === 'file-path') {
|
|
312
315
|
// It's a file path - resolve and read the file
|
|
313
316
|
const filePath = resolve(image.replace('file://', ''));
|
|
@@ -31,7 +31,8 @@ export function createScreenshotRouter({
|
|
|
31
31
|
buildId,
|
|
32
32
|
name,
|
|
33
33
|
properties,
|
|
34
|
-
image
|
|
34
|
+
image,
|
|
35
|
+
type
|
|
35
36
|
} = body;
|
|
36
37
|
if (!name || !image) {
|
|
37
38
|
sendError(res, 400, 'name and image are required');
|
|
@@ -40,7 +41,7 @@ export function createScreenshotRouter({
|
|
|
40
41
|
|
|
41
42
|
// Use buildId from request body, or fall back to server's buildId
|
|
42
43
|
const effectiveBuildId = buildId || defaultBuildId;
|
|
43
|
-
const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties);
|
|
44
|
+
const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties, type);
|
|
44
45
|
sendJson(res, result.statusCode, result.body);
|
|
45
46
|
return true;
|
|
46
47
|
} catch (error) {
|
|
@@ -81,41 +81,46 @@ export function looksLikeFilePath(str) {
|
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// 0.
|
|
84
|
+
// 0. Length check - file paths are short, base64 screenshots are huge
|
|
85
|
+
// Even the longest realistic file path is < 500 chars
|
|
86
|
+
// This makes detection O(1) for large base64 strings
|
|
87
|
+
// Use same threshold (1000) as detectImageInputType for consistency
|
|
88
|
+
if (str.length > 1000) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 1. Explicitly reject data URIs (they contain : and / which would match path patterns)
|
|
85
93
|
if (str.startsWith('data:')) {
|
|
86
94
|
return false;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
//
|
|
97
|
+
// 2. Check for file:// URI scheme
|
|
90
98
|
if (str.startsWith('file://')) {
|
|
91
99
|
return true;
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
// Windows: starts with drive letter like C:\ or C:/
|
|
97
|
-
if (str.startsWith('/') || /^[A-Za-z]:[/\\]/.test(str)) {
|
|
102
|
+
// 3. Windows absolute paths (C:\ or C:/) - base64 never starts with drive letter
|
|
103
|
+
if (/^[A-Za-z]:[/\\]/.test(str)) {
|
|
98
104
|
return true;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
//
|
|
102
|
-
// ./ or ../ or .\ or ..\
|
|
107
|
+
// 4. Relative path indicators (./ or ../) - base64 never starts with dot
|
|
103
108
|
if (/^\.\.?[/\\]/.test(str)) {
|
|
104
109
|
return true;
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
// 4. Check for path separators (forward or back slash)
|
|
108
|
-
// This catches paths like: subdirectory/file.png or subdirectory\file.png
|
|
109
|
-
if (/[/\\]/.test(str)) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
112
|
// 5. Check for common image file extensions
|
|
114
|
-
// This
|
|
115
|
-
//
|
|
113
|
+
// This is the safest check - base64 never ends with .png/.jpg/etc
|
|
114
|
+
// Catches: /path/file.png, subdir/file.png, file.png
|
|
116
115
|
if (/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|ico)$/i.test(str)) {
|
|
117
116
|
return true;
|
|
118
117
|
}
|
|
118
|
+
|
|
119
|
+
// Note: We intentionally don't check for bare "/" prefix or "/" anywhere
|
|
120
|
+
// because JPEG base64 starts with "/9j/" which would false-positive
|
|
121
|
+
// File paths without extensions are rare for images and will fall through
|
|
122
|
+
// to base64 detection, which is acceptable for backwards compat
|
|
123
|
+
|
|
119
124
|
return false;
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -127,14 +132,13 @@ export function looksLikeFilePath(str) {
|
|
|
127
132
|
* - 'file-path': A file path (relative or absolute)
|
|
128
133
|
* - 'unknown': Cannot determine (ambiguous or invalid)
|
|
129
134
|
*
|
|
130
|
-
* Strategy:
|
|
131
|
-
* 1.
|
|
132
|
-
* 2.
|
|
133
|
-
* 3.
|
|
135
|
+
* Strategy (optimized for performance):
|
|
136
|
+
* 1. Check for data URI prefix first (O(1), definitive)
|
|
137
|
+
* 2. Check file path patterns (O(1) prefix/suffix checks)
|
|
138
|
+
* 3. For large non-path strings, assume base64 (skip expensive validation)
|
|
139
|
+
* 4. Only run full base64 validation on small ambiguous strings
|
|
134
140
|
*
|
|
135
|
-
* This
|
|
136
|
-
* misidentified as file paths. Base64 validation is stricter and should
|
|
137
|
-
* be checked first.
|
|
141
|
+
* This avoids O(n) regex validation on large screenshot buffers.
|
|
138
142
|
*
|
|
139
143
|
* @param {string} str - String to detect
|
|
140
144
|
* @returns {'base64' | 'file-path' | 'unknown'} Detected input type
|
|
@@ -151,15 +155,26 @@ export function detectImageInputType(str) {
|
|
|
151
155
|
return 'unknown';
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
if (isBase64(str)) {
|
|
158
|
+
// 1. Data URIs are definitively base64 (O(1) check)
|
|
159
|
+
if (str.startsWith('data:')) {
|
|
157
160
|
return 'base64';
|
|
158
161
|
}
|
|
159
162
|
|
|
160
|
-
//
|
|
163
|
+
// 2. Check file path patterns (O(1) prefix/suffix checks)
|
|
161
164
|
if (looksLikeFilePath(str)) {
|
|
162
165
|
return 'file-path';
|
|
163
166
|
}
|
|
167
|
+
|
|
168
|
+
// 3. For large strings that aren't file paths, assume base64
|
|
169
|
+
// Screenshots are typically 100KB+ as base64, file paths are <1KB
|
|
170
|
+
// Skip expensive O(n) validation for large strings
|
|
171
|
+
if (str.length > 1000) {
|
|
172
|
+
return 'base64';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 4. Full validation only for small ambiguous strings
|
|
176
|
+
if (isBase64(str)) {
|
|
177
|
+
return 'base64';
|
|
178
|
+
}
|
|
164
179
|
return 'unknown';
|
|
165
180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"description": "Visual review platform for UI developers and designers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"visual-testing",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
90
|
"@vizzly-testing/honeydiff": "^0.8.0",
|
|
91
|
-
"@vizzly-testing/static-site": "^0.0.11",
|
|
92
91
|
"ansis": "^4.2.0",
|
|
93
92
|
"commander": "^14.0.0",
|
|
94
93
|
"cosmiconfig": "^9.0.0",
|