@vizzly-testing/cli 0.8.0 → 0.9.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.
- package/README.md +26 -13
- package/dist/cli.js +25 -1
- package/dist/client/index.js +77 -11
- package/dist/commands/init.js +23 -17
- package/dist/commands/tdd-daemon.js +312 -0
- package/dist/commands/tdd.js +45 -14
- package/dist/reporter/reporter-bundle.css +1 -0
- package/dist/reporter/reporter-bundle.iife.js +57 -0
- package/dist/server/handlers/api-handler.js +98 -30
- package/dist/server/handlers/tdd-handler.js +264 -77
- package/dist/server/http-server.js +358 -15
- package/dist/services/html-report-generator.js +77 -0
- package/dist/services/report-generator/report.css +56 -0
- package/dist/services/screenshot-server.js +6 -3
- package/dist/services/server-manager.js +2 -9
- package/dist/services/tdd-service.js +188 -25
- package/dist/services/test-runner.js +43 -1
- package/dist/types/commands/tdd-daemon.d.ts +18 -0
- package/dist/types/container/index.d.ts +1 -3
- package/dist/types/reporter/src/components/app-router.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/comparison-actions.d.ts +5 -0
- package/dist/types/reporter/src/components/comparison/comparison-card.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-list.d.ts +6 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/view-mode-selector.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +3 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-filters.d.ts +16 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +5 -0
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +4 -0
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +8 -0
- package/dist/types/reporter/src/components/ui/smart-image.d.ts +7 -0
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +5 -0
- package/dist/types/reporter/src/components/ui/toast.d.ts +4 -0
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +6 -0
- package/dist/types/reporter/src/components/views/stats-view.d.ts +6 -0
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +5 -0
- package/dist/types/reporter/src/hooks/use-comparison-filters.d.ts +20 -0
- package/dist/types/reporter/src/hooks/use-image-loader.d.ts +1 -0
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +7 -0
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +9 -0
- package/dist/types/reporter/src/main.d.ts +1 -0
- package/dist/types/reporter/src/services/api-client.d.ts +4 -0
- package/dist/types/reporter/src/utils/comparison-helpers.d.ts +16 -0
- package/dist/types/reporter/src/utils/constants.d.ts +37 -0
- package/dist/types/reporter/vite.config.d.ts +2 -0
- package/dist/types/reporter/vite.dev.config.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -2
- package/dist/types/server/handlers/api-handler.d.ts +5 -14
- package/dist/types/server/handlers/tdd-handler.d.ts +18 -17
- package/dist/types/server/http-server.d.ts +2 -1
- package/dist/types/services/base-service.d.ts +1 -2
- package/dist/types/services/html-report-generator.d.ts +3 -3
- package/dist/types/services/screenshot-server.d.ts +1 -1
- package/dist/types/services/server-manager.d.ts +25 -35
- package/dist/types/services/tdd-service.d.ts +7 -1
- package/dist/types/services/test-runner.d.ts +6 -1
- package/dist/types/utils/build-history.d.ts +16 -0
- package/dist/types/utils/config-loader.d.ts +1 -1
- package/dist/types/utils/console-ui.d.ts +1 -1
- package/dist/types/utils/git.d.ts +4 -4
- package/dist/types/utils/security.d.ts +2 -1
- package/dist/utils/build-history.js +103 -0
- package/dist/utils/security.js +14 -5
- package/docs/api-reference.md +1 -3
- package/docs/getting-started.md +1 -1
- package/docs/tdd-mode.md +176 -112
- package/package.json +17 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export namespace VIEW_MODES {
|
|
2
|
+
let OVERLAY: string;
|
|
3
|
+
let TOGGLE: string;
|
|
4
|
+
let ONION: string;
|
|
5
|
+
let SIDE_BY_SIDE: string;
|
|
6
|
+
}
|
|
7
|
+
export namespace FILTER_TYPES {
|
|
8
|
+
let ALL: string;
|
|
9
|
+
let FAILED: string;
|
|
10
|
+
let PASSED: string;
|
|
11
|
+
let NEW: string;
|
|
12
|
+
}
|
|
13
|
+
export namespace SORT_TYPES {
|
|
14
|
+
let PRIORITY: string;
|
|
15
|
+
let NAME: string;
|
|
16
|
+
let TIME: string;
|
|
17
|
+
}
|
|
18
|
+
export namespace CONNECTION_STATUS {
|
|
19
|
+
let CONNECTING: string;
|
|
20
|
+
let CONNECTED: string;
|
|
21
|
+
let DISCONNECTED: string;
|
|
22
|
+
}
|
|
23
|
+
export namespace COMPARISON_STATUS {
|
|
24
|
+
let PASSED_1: string;
|
|
25
|
+
export { PASSED_1 as PASSED };
|
|
26
|
+
let FAILED_1: string;
|
|
27
|
+
export { FAILED_1 as FAILED };
|
|
28
|
+
let NEW_1: string;
|
|
29
|
+
export { NEW_1 as NEW };
|
|
30
|
+
export let BASELINE_CREATED: string;
|
|
31
|
+
export let ERROR: string;
|
|
32
|
+
}
|
|
33
|
+
export namespace USER_ACTION {
|
|
34
|
+
let ACCEPTING: string;
|
|
35
|
+
let ACCEPTED: string;
|
|
36
|
+
let REJECTED: string;
|
|
37
|
+
}
|
|
@@ -48,7 +48,7 @@ export function createVizzly(config?: any, options?: {}): Promise<VizzlySDK>;
|
|
|
48
48
|
* @class
|
|
49
49
|
* @extends {EventEmitter}
|
|
50
50
|
*/
|
|
51
|
-
export class VizzlySDK
|
|
51
|
+
export class VizzlySDK {
|
|
52
52
|
/**
|
|
53
53
|
* @param {import('../types').VizzlyConfig} config - Configuration
|
|
54
54
|
* @param {import('../utils/logger').Logger} logger - Logger instance
|
|
@@ -104,5 +104,4 @@ export { loadConfig } from "../utils/config-loader.js";
|
|
|
104
104
|
export { createLogger } from "../utils/logger.js";
|
|
105
105
|
export { createUploader } from "../services/uploader.js";
|
|
106
106
|
export { createTDDService } from "../services/tdd-service.js";
|
|
107
|
-
import { EventEmitter } from 'events';
|
|
108
107
|
import { ScreenshotServer } from '../services/screenshot-server.js';
|
|
@@ -8,7 +8,6 @@ export function createApiHandler(apiService: any): {
|
|
|
8
8
|
message: string;
|
|
9
9
|
error?: undefined;
|
|
10
10
|
name?: undefined;
|
|
11
|
-
skipped?: undefined;
|
|
12
11
|
};
|
|
13
12
|
} | {
|
|
14
13
|
statusCode: number;
|
|
@@ -19,31 +18,23 @@ export function createApiHandler(apiService: any): {
|
|
|
19
18
|
count?: undefined;
|
|
20
19
|
message?: undefined;
|
|
21
20
|
name?: undefined;
|
|
22
|
-
skipped?: undefined;
|
|
23
21
|
};
|
|
24
22
|
} | {
|
|
25
23
|
statusCode: number;
|
|
26
24
|
body: {
|
|
27
25
|
success: boolean;
|
|
28
26
|
name: any;
|
|
29
|
-
skipped: any;
|
|
30
27
|
count: number;
|
|
31
28
|
disabled?: undefined;
|
|
32
29
|
message?: undefined;
|
|
33
30
|
error?: undefined;
|
|
34
31
|
};
|
|
35
|
-
} | {
|
|
36
|
-
statusCode: number;
|
|
37
|
-
body: {
|
|
38
|
-
success: boolean;
|
|
39
|
-
name: any;
|
|
40
|
-
disabled: boolean;
|
|
41
|
-
message: string;
|
|
42
|
-
count?: undefined;
|
|
43
|
-
error?: undefined;
|
|
44
|
-
skipped?: undefined;
|
|
45
|
-
};
|
|
46
32
|
}>;
|
|
47
33
|
getScreenshotCount: () => number;
|
|
34
|
+
flush: () => Promise<{
|
|
35
|
+
uploaded: number;
|
|
36
|
+
failed: number;
|
|
37
|
+
total: number;
|
|
38
|
+
}>;
|
|
48
39
|
cleanup: () => void;
|
|
49
40
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export function createTddHandler(config: any, workingDir: any, baselineBuild: any, baselineComparison: any, setBaseline?: boolean): {
|
|
2
2
|
initialize: () => Promise<void>;
|
|
3
|
-
registerBuild: (buildId: any) => void;
|
|
4
3
|
handleScreenshot: (buildId: any, name: any, image: any, properties?: {}) => Promise<{
|
|
5
4
|
statusCode: number;
|
|
6
5
|
body: {
|
|
@@ -81,22 +80,24 @@ export function createTddHandler(config: any, workingDir: any, baselineBuild: an
|
|
|
81
80
|
message?: undefined;
|
|
82
81
|
};
|
|
83
82
|
}>;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
83
|
+
getResults: () => Promise<{
|
|
84
|
+
total: number;
|
|
85
|
+
passed: number;
|
|
86
|
+
failed: number;
|
|
87
|
+
new: number;
|
|
88
|
+
errors: number;
|
|
89
|
+
comparisons: any[];
|
|
90
|
+
baseline: any;
|
|
91
|
+
}>;
|
|
92
|
+
acceptBaseline: (screenshotName: any) => Promise<any>;
|
|
93
|
+
acceptAllBaselines: () => Promise<{
|
|
94
|
+
count: number;
|
|
95
|
+
}>;
|
|
96
|
+
resetBaselines: () => Promise<{
|
|
97
|
+
success: boolean;
|
|
98
|
+
deletedBaselines: number;
|
|
99
|
+
deletedCurrents: number;
|
|
100
|
+
deletedDiffs: number;
|
|
100
101
|
}>;
|
|
101
102
|
cleanup: () => void;
|
|
102
103
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export function createHttpServer(port: any, screenshotHandler: any
|
|
1
|
+
export function createHttpServer(port: any, screenshotHandler: any): {
|
|
2
2
|
start: () => Promise<any>;
|
|
3
3
|
stop: () => Promise<any>;
|
|
4
|
+
finishBuild: (buildId: any) => Promise<any>;
|
|
4
5
|
getServer: () => any;
|
|
5
6
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Base class for all services
|
|
8
8
|
* @extends EventEmitter
|
|
9
9
|
*/
|
|
10
|
-
export class BaseService
|
|
10
|
+
export class BaseService {
|
|
11
11
|
/**
|
|
12
12
|
* @param {Object} config - Service configuration
|
|
13
13
|
* @param {ServiceOptions} options - Service options
|
|
@@ -69,4 +69,3 @@ export type ServiceOptions = {
|
|
|
69
69
|
*/
|
|
70
70
|
signal?: AbortSignal;
|
|
71
71
|
};
|
|
72
|
-
import { EventEmitter } from 'events';
|
|
@@ -2,9 +2,9 @@ export class HtmlReportGenerator {
|
|
|
2
2
|
constructor(workingDir: any, config: any);
|
|
3
3
|
workingDir: any;
|
|
4
4
|
config: any;
|
|
5
|
-
reportDir:
|
|
6
|
-
reportPath:
|
|
7
|
-
cssPath:
|
|
5
|
+
reportDir: any;
|
|
6
|
+
reportPath: any;
|
|
7
|
+
cssPath: any;
|
|
8
8
|
/**
|
|
9
9
|
* Sanitize HTML content to prevent XSS attacks
|
|
10
10
|
* @param {string} text - Text to sanitize
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export class ScreenshotServer extends BaseService {
|
|
2
2
|
constructor(config: any, logger: any, buildManager: any);
|
|
3
3
|
buildManager: any;
|
|
4
|
-
server:
|
|
4
|
+
server: any;
|
|
5
5
|
onStart(): Promise<any>;
|
|
6
6
|
onStop(): Promise<any>;
|
|
7
7
|
handleRequest(req: any, res: any): Promise<void>;
|
|
@@ -3,11 +3,11 @@ export class ServerManager extends BaseService {
|
|
|
3
3
|
httpServer: {
|
|
4
4
|
start: () => Promise<any>;
|
|
5
5
|
stop: () => Promise<any>;
|
|
6
|
+
finishBuild: (buildId: any) => Promise<any>;
|
|
6
7
|
getServer: () => any;
|
|
7
8
|
};
|
|
8
9
|
handler: {
|
|
9
10
|
initialize: () => Promise<void>;
|
|
10
|
-
registerBuild: (buildId: any) => void;
|
|
11
11
|
handleScreenshot: (buildId: any, name: any, image: any, properties?: {}) => Promise<{
|
|
12
12
|
statusCode: number;
|
|
13
13
|
body: {
|
|
@@ -88,22 +88,24 @@ export class ServerManager extends BaseService {
|
|
|
88
88
|
message?: undefined;
|
|
89
89
|
};
|
|
90
90
|
}>;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
91
|
+
getResults: () => Promise<{
|
|
92
|
+
total: number;
|
|
93
|
+
passed: number;
|
|
94
|
+
failed: number;
|
|
95
|
+
new: number;
|
|
96
|
+
errors: number;
|
|
97
|
+
comparisons: any[];
|
|
98
|
+
baseline: any;
|
|
99
|
+
}>;
|
|
100
|
+
acceptBaseline: (screenshotName: any) => Promise<any>;
|
|
101
|
+
acceptAllBaselines: () => Promise<{
|
|
102
|
+
count: number;
|
|
103
|
+
}>;
|
|
104
|
+
resetBaselines: () => Promise<{
|
|
105
|
+
success: boolean;
|
|
106
|
+
deletedBaselines: number;
|
|
107
|
+
deletedCurrents: number;
|
|
108
|
+
deletedDiffs: number;
|
|
107
109
|
}>;
|
|
108
110
|
cleanup: () => void;
|
|
109
111
|
} | {
|
|
@@ -116,7 +118,6 @@ export class ServerManager extends BaseService {
|
|
|
116
118
|
message: string;
|
|
117
119
|
error?: undefined;
|
|
118
120
|
name?: undefined;
|
|
119
|
-
skipped?: undefined;
|
|
120
121
|
};
|
|
121
122
|
} | {
|
|
122
123
|
statusCode: number;
|
|
@@ -127,45 +128,34 @@ export class ServerManager extends BaseService {
|
|
|
127
128
|
count?: undefined;
|
|
128
129
|
message?: undefined;
|
|
129
130
|
name?: undefined;
|
|
130
|
-
skipped?: undefined;
|
|
131
131
|
};
|
|
132
132
|
} | {
|
|
133
133
|
statusCode: number;
|
|
134
134
|
body: {
|
|
135
135
|
success: boolean;
|
|
136
136
|
name: any;
|
|
137
|
-
skipped: any;
|
|
138
137
|
count: number;
|
|
139
138
|
disabled?: undefined;
|
|
140
139
|
message?: undefined;
|
|
141
140
|
error?: undefined;
|
|
142
141
|
};
|
|
143
|
-
} | {
|
|
144
|
-
statusCode: number;
|
|
145
|
-
body: {
|
|
146
|
-
success: boolean;
|
|
147
|
-
name: any;
|
|
148
|
-
disabled: boolean;
|
|
149
|
-
message: string;
|
|
150
|
-
count?: undefined;
|
|
151
|
-
error?: undefined;
|
|
152
|
-
skipped?: undefined;
|
|
153
|
-
};
|
|
154
142
|
}>;
|
|
155
143
|
getScreenshotCount: () => number;
|
|
144
|
+
flush: () => Promise<{
|
|
145
|
+
uploaded: number;
|
|
146
|
+
failed: number;
|
|
147
|
+
total: number;
|
|
148
|
+
}>;
|
|
156
149
|
cleanup: () => void;
|
|
157
150
|
};
|
|
158
|
-
emitter: EventEmitter<[never]>;
|
|
159
151
|
start(buildId?: any, tddMode?: boolean, setBaseline?: boolean): Promise<void>;
|
|
160
152
|
buildId: any;
|
|
161
153
|
tddMode: boolean;
|
|
162
154
|
setBaseline: boolean;
|
|
163
155
|
createApiService(): Promise<import("./api-service.js").ApiService>;
|
|
164
156
|
get server(): {
|
|
165
|
-
emitter: EventEmitter<[never]>;
|
|
166
157
|
getScreenshotCount: (buildId: any) => any;
|
|
167
|
-
finishBuild: (buildId: any) => any
|
|
158
|
+
finishBuild: (buildId: any) => Promise<any>;
|
|
168
159
|
};
|
|
169
160
|
}
|
|
170
161
|
import { BaseService } from './base-service.js';
|
|
171
|
-
import { EventEmitter } from 'events';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export function createTDDService(config: any, options?: {}): TddService;
|
|
5
5
|
export class TddService {
|
|
6
|
-
constructor(config: any, workingDir?:
|
|
6
|
+
constructor(config: any, workingDir?: any, setBaseline?: boolean);
|
|
7
7
|
config: any;
|
|
8
8
|
setBaseline: boolean;
|
|
9
9
|
api: ApiService;
|
|
@@ -72,5 +72,11 @@ export class TddService {
|
|
|
72
72
|
* @private
|
|
73
73
|
*/
|
|
74
74
|
private updateSingleBaseline;
|
|
75
|
+
/**
|
|
76
|
+
* Accept a current screenshot as the new baseline
|
|
77
|
+
* @param {string} name - Screenshot name to accept
|
|
78
|
+
* @returns {Object} Result object
|
|
79
|
+
*/
|
|
80
|
+
acceptBaseline(name: string): any;
|
|
75
81
|
}
|
|
76
82
|
import { ApiService } from '../services/api-service.js';
|
|
@@ -3,7 +3,12 @@ export class TestRunner extends BaseService {
|
|
|
3
3
|
buildManager: any;
|
|
4
4
|
serverManager: any;
|
|
5
5
|
tddService: any;
|
|
6
|
-
testProcess:
|
|
6
|
+
testProcess: any;
|
|
7
|
+
/**
|
|
8
|
+
* Initialize server for daemon mode (no test execution)
|
|
9
|
+
* @param {Object} options - Options for server initialization
|
|
10
|
+
*/
|
|
11
|
+
initialize(options: any): Promise<void>;
|
|
7
12
|
run(options: any): Promise<{
|
|
8
13
|
testsPassed: number;
|
|
9
14
|
testsFailed: number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive a build to history directory
|
|
3
|
+
* @param {string} workingDir - Working directory
|
|
4
|
+
* @param {string} buildId - Build ID to archive
|
|
5
|
+
* @param {Array} builds - Build data
|
|
6
|
+
* @param {Array} comparisons - Comparison data
|
|
7
|
+
* @param {Object} summary - Summary stats
|
|
8
|
+
* @param {number} maxHistory - Maximum number of builds to keep (default: 3)
|
|
9
|
+
*/
|
|
10
|
+
export function archiveBuild(workingDir: string, buildId: string, builds: any[], comparisons: any[], summary: any, maxHistory?: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Get list of archived builds
|
|
13
|
+
* @param {string} workingDir - Working directory
|
|
14
|
+
* @returns {Array} Array of build metadata
|
|
15
|
+
*/
|
|
16
|
+
export function getArchivedBuilds(workingDir: string): any[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export function getCommonAncestor(commit1: any, commit2: any, cwd?:
|
|
2
|
-
export function getCurrentCommitSha(cwd?:
|
|
3
|
-
export function getCurrentBranch(cwd?:
|
|
4
|
-
export function getDefaultBranch(cwd?:
|
|
1
|
+
export function getCommonAncestor(commit1: any, commit2: any, cwd?: any): Promise<any>;
|
|
2
|
+
export function getCurrentCommitSha(cwd?: any): Promise<any>;
|
|
3
|
+
export function getCurrentBranch(cwd?: any): Promise<any>;
|
|
4
|
+
export function getDefaultBranch(cwd?: any): Promise<any>;
|
|
5
5
|
export function generateBuildName(): string;
|
|
6
6
|
/**
|
|
7
7
|
* Get the current commit message
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Sanitizes a screenshot name to prevent path traversal and ensure safe file naming
|
|
3
3
|
* @param {string} name - Original screenshot name
|
|
4
4
|
* @param {number} maxLength - Maximum allowed length (default: 255)
|
|
5
|
+
* @param {boolean} allowSlashes - Whether to allow forward slashes (for browser version strings)
|
|
5
6
|
* @returns {string} Sanitized screenshot name
|
|
6
7
|
*/
|
|
7
|
-
export function sanitizeScreenshotName(name: string, maxLength?: number): string;
|
|
8
|
+
export function sanitizeScreenshotName(name: string, maxLength?: number, allowSlashes?: boolean): string;
|
|
8
9
|
/**
|
|
9
10
|
* Validates that a path stays within the allowed working directory bounds
|
|
10
11
|
* @param {string} targetPath - Path to validate
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Archive a build to history directory
|
|
6
|
+
* @param {string} workingDir - Working directory
|
|
7
|
+
* @param {string} buildId - Build ID to archive
|
|
8
|
+
* @param {Array} builds - Build data
|
|
9
|
+
* @param {Array} comparisons - Comparison data
|
|
10
|
+
* @param {Object} summary - Summary stats
|
|
11
|
+
* @param {number} maxHistory - Maximum number of builds to keep (default: 3)
|
|
12
|
+
*/
|
|
13
|
+
export function archiveBuild(workingDir, buildId, builds, comparisons, summary, maxHistory = 3) {
|
|
14
|
+
let historyDir = join(workingDir, '.vizzly', 'history');
|
|
15
|
+
|
|
16
|
+
// Create history directory if it doesn't exist
|
|
17
|
+
if (!existsSync(historyDir)) {
|
|
18
|
+
mkdirSync(historyDir, {
|
|
19
|
+
recursive: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Save current build to history
|
|
24
|
+
let buildDir = join(historyDir, buildId);
|
|
25
|
+
if (!existsSync(buildDir)) {
|
|
26
|
+
mkdirSync(buildDir, {
|
|
27
|
+
recursive: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
let buildData = {
|
|
31
|
+
buildId,
|
|
32
|
+
timestamp: Date.now(),
|
|
33
|
+
builds,
|
|
34
|
+
comparisons,
|
|
35
|
+
summary
|
|
36
|
+
};
|
|
37
|
+
writeFileSync(join(buildDir, 'report.json'), JSON.stringify(buildData, null, 2));
|
|
38
|
+
|
|
39
|
+
// Clean up old builds (keep last N)
|
|
40
|
+
cleanupOldBuilds(historyDir, maxHistory);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get list of archived builds
|
|
45
|
+
* @param {string} workingDir - Working directory
|
|
46
|
+
* @returns {Array} Array of build metadata
|
|
47
|
+
*/
|
|
48
|
+
export function getArchivedBuilds(workingDir) {
|
|
49
|
+
let historyDir = join(workingDir, '.vizzly', 'history');
|
|
50
|
+
if (!existsSync(historyDir)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
let buildDirs = readdirSync(historyDir, {
|
|
55
|
+
withFileTypes: true
|
|
56
|
+
}).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name).sort().reverse(); // Newest first
|
|
57
|
+
|
|
58
|
+
return buildDirs.map(buildId => {
|
|
59
|
+
let reportPath = join(historyDir, buildId, 'report.json');
|
|
60
|
+
if (existsSync(reportPath)) {
|
|
61
|
+
try {
|
|
62
|
+
let data = JSON.parse(require('fs').readFileSync(reportPath, 'utf8'));
|
|
63
|
+
return {
|
|
64
|
+
buildId: data.buildId,
|
|
65
|
+
timestamp: data.timestamp,
|
|
66
|
+
summary: data.summary
|
|
67
|
+
};
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}).filter(Boolean);
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Remove old builds, keeping only the last N
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
function cleanupOldBuilds(historyDir, maxHistory) {
|
|
84
|
+
try {
|
|
85
|
+
let buildDirs = readdirSync(historyDir, {
|
|
86
|
+
withFileTypes: true
|
|
87
|
+
}).filter(dirent => dirent.isDirectory()).map(dirent => dirent.name).sort().reverse(); // Newest first
|
|
88
|
+
|
|
89
|
+
// Remove builds beyond maxHistory
|
|
90
|
+
if (buildDirs.length > maxHistory) {
|
|
91
|
+
let toRemove = buildDirs.slice(maxHistory);
|
|
92
|
+
toRemove.forEach(buildId => {
|
|
93
|
+
let buildDir = join(historyDir, buildId);
|
|
94
|
+
rmSync(buildDir, {
|
|
95
|
+
recursive: true,
|
|
96
|
+
force: true
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// Ignore cleanup errors
|
|
102
|
+
}
|
|
103
|
+
}
|
package/dist/utils/security.js
CHANGED
|
@@ -11,9 +11,10 @@ const logger = createServiceLogger('SECURITY');
|
|
|
11
11
|
* Sanitizes a screenshot name to prevent path traversal and ensure safe file naming
|
|
12
12
|
* @param {string} name - Original screenshot name
|
|
13
13
|
* @param {number} maxLength - Maximum allowed length (default: 255)
|
|
14
|
+
* @param {boolean} allowSlashes - Whether to allow forward slashes (for browser version strings)
|
|
14
15
|
* @returns {string} Sanitized screenshot name
|
|
15
16
|
*/
|
|
16
|
-
export function sanitizeScreenshotName(name, maxLength = 255) {
|
|
17
|
+
export function sanitizeScreenshotName(name, maxLength = 255, allowSlashes = false) {
|
|
17
18
|
if (typeof name !== 'string' || name.length === 0) {
|
|
18
19
|
throw new Error('Screenshot name must be a non-empty string');
|
|
19
20
|
}
|
|
@@ -22,7 +23,12 @@ export function sanitizeScreenshotName(name, maxLength = 255) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Block directory traversal patterns
|
|
25
|
-
if (name.includes('..') || name.includes('
|
|
26
|
+
if (name.includes('..') || name.includes('\\')) {
|
|
27
|
+
throw new Error('Screenshot name contains invalid path characters');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Block forward slashes unless explicitly allowed (e.g., for browser version strings)
|
|
31
|
+
if (!allowSlashes && name.includes('/')) {
|
|
26
32
|
throw new Error('Screenshot name contains invalid path characters');
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -31,9 +37,10 @@ export function sanitizeScreenshotName(name, maxLength = 255) {
|
|
|
31
37
|
throw new Error('Screenshot name cannot be an absolute path');
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
// Allow only safe characters: alphanumeric, hyphens, underscores, and
|
|
40
|
+
// Allow only safe characters: alphanumeric, hyphens, underscores, dots, and optionally slashes
|
|
35
41
|
// Replace other characters with underscores
|
|
36
|
-
let
|
|
42
|
+
let allowedChars = allowSlashes ? /[^a-zA-Z0-9._/-]/g : /[^a-zA-Z0-9._-]/g;
|
|
43
|
+
let sanitized = name.replace(allowedChars, '_');
|
|
37
44
|
|
|
38
45
|
// Prevent names that start with dots (hidden files)
|
|
39
46
|
if (sanitized.startsWith('.')) {
|
|
@@ -116,7 +123,9 @@ export function validateScreenshotProperties(properties = {}) {
|
|
|
116
123
|
// Validate common properties with safe constraints
|
|
117
124
|
if (properties.browser && typeof properties.browser === 'string') {
|
|
118
125
|
try {
|
|
119
|
-
|
|
126
|
+
// Extract browser name without version (e.g., "Chrome/139.0.7258.138" -> "Chrome")
|
|
127
|
+
let browserName = properties.browser.split('/')[0];
|
|
128
|
+
validated.browser = sanitizeScreenshotName(browserName, 50);
|
|
120
129
|
} catch (error) {
|
|
121
130
|
// Skip invalid browser names, don't include them
|
|
122
131
|
logger.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
|
package/docs/api-reference.md
CHANGED
|
@@ -496,9 +496,7 @@ Configuration loaded via cosmiconfig in this order:
|
|
|
496
496
|
|
|
497
497
|
// Comparison Configuration
|
|
498
498
|
comparison: {
|
|
499
|
-
threshold: number
|
|
500
|
-
ignoreAntialiasing: boolean, // Ignore antialiasing (default: true)
|
|
501
|
-
ignoreColors: boolean // Ignore color differences (default: false)
|
|
499
|
+
threshold: number // Pixel difference threshold (default: 0.1)
|
|
502
500
|
}
|
|
503
501
|
}
|
|
504
502
|
```
|
package/docs/getting-started.md
CHANGED