@vizzly-testing/cli 0.30.0 → 0.31.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 +9 -9
- package/dist/api/client.js +6 -3
- package/dist/cli.js +58 -29
- package/dist/client/index.js +21 -29
- package/dist/commands/api.js +145 -90
- package/dist/commands/baselines.js +78 -47
- package/dist/commands/builds.js +9 -8
- package/dist/commands/comparisons.js +7 -8
- package/dist/commands/config-cmd.js +15 -7
- package/dist/commands/context.js +329 -18
- package/dist/commands/doctor.js +116 -81
- package/dist/commands/init.js +202 -201
- package/dist/commands/login.js +134 -65
- package/dist/commands/logout.js +17 -6
- package/dist/commands/preview.js +29 -57
- package/dist/commands/project.js +120 -0
- package/dist/commands/projects.js +9 -2
- package/dist/commands/review.js +236 -202
- package/dist/commands/run.js +75 -72
- package/dist/commands/status.js +228 -150
- package/dist/commands/tdd-daemon-process.js +98 -0
- package/dist/commands/tdd-daemon.js +318 -212
- package/dist/commands/tdd.js +23 -8
- package/dist/commands/upload.js +10 -9
- package/dist/commands/whoami.js +63 -25
- package/dist/config/core.js +36 -35
- package/dist/context/local-workspace-provider.js +132 -12
- package/dist/context/provider-resolver.js +5 -1
- package/dist/index.js +5 -4
- package/dist/plugin-loader.js +55 -49
- package/dist/project/core.js +2 -2
- package/dist/project/operations.js +6 -3
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +7 -7
- package/dist/sdk/index.js +162 -107
- package/dist/server/handlers/tdd-handler.js +30 -54
- package/dist/server/routers/baseline.js +195 -178
- package/dist/server/routers/events.js +212 -174
- package/dist/server/routers/projects.js +14 -5
- package/dist/server/routers/screenshot.js +1 -31
- package/dist/services/build-manager.js +19 -97
- package/dist/services/config-service.js +26 -34
- package/dist/services/index.js +2 -4
- package/dist/services/static-report-generator.js +11 -12
- package/dist/services/test-runner.js +21 -27
- package/dist/tdd/index.js +1 -1
- package/dist/tdd/metadata/region-metadata.js +0 -37
- package/dist/tdd/runtime-context.js +78 -0
- package/dist/tdd/server-registry.js +38 -17
- package/dist/tdd/services/baseline-manager.js +23 -0
- package/dist/tdd/services/hotspot-service.js +0 -13
- package/dist/tdd/tdd-service.js +82 -113
- package/dist/types/client.d.ts +36 -1
- package/dist/types/index.d.ts +114 -22
- package/dist/types/sdk.d.ts +40 -0
- package/dist/uploader/core.js +5 -4
- package/dist/uploader/index.js +79 -2
- package/dist/uploader/operations.js +40 -5
- package/dist/utils/api-url.js +14 -0
- package/dist/utils/async-utils.js +26 -0
- package/dist/utils/ci-env.js +19 -12
- package/dist/utils/config-loader.js +39 -65
- package/dist/utils/config-schema.js +19 -55
- package/dist/utils/fetch-utils.js +12 -5
- package/dist/utils/git.js +35 -72
- package/dist/utils/global-config.js +20 -1
- package/dist/utils/output.js +19 -7
- package/dist/utils/patterns.js +14 -0
- package/dist/utils/project-link-store.js +105 -0
- package/dist/utils/screenshot-options.js +28 -0
- package/dist/utils/secret-store.js +65 -0
- package/package.json +11 -8
- package/dist/services/screenshot-server.js +0 -65
- package/dist/services/server-manager.js +0 -81
- package/dist/services/uploader.js +0 -100
- package/dist/tdd/services/region-service.js +0 -75
- package/dist/utils/build-history.js +0 -103
|
@@ -157,6 +157,16 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
157
157
|
const tddService = new TddService(config, workingDir, setBaseline);
|
|
158
158
|
const reportPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
159
159
|
const detailsPath = join(workingDir, '.vizzly', 'comparison-details.json');
|
|
160
|
+
let clearJsonFile = path => {
|
|
161
|
+
if (existsSync(path)) {
|
|
162
|
+
unlinkSync(path);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
let prepareRunArtifacts = () => {
|
|
166
|
+
clearJsonFile(reportPath);
|
|
167
|
+
clearJsonFile(detailsPath);
|
|
168
|
+
tddService.clearRunData?.();
|
|
169
|
+
};
|
|
160
170
|
|
|
161
171
|
/**
|
|
162
172
|
* Read heavy comparison details from comparison-details.json
|
|
@@ -267,6 +277,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
267
277
|
};
|
|
268
278
|
const initialize = async () => {
|
|
269
279
|
output.debug('tdd', 'initializing local mode');
|
|
280
|
+
prepareRunArtifacts();
|
|
270
281
|
|
|
271
282
|
// In baseline update mode, skip all baseline loading/downloading
|
|
272
283
|
if (setBaseline) {
|
|
@@ -631,68 +642,33 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
631
642
|
let deletedBaselines = 0;
|
|
632
643
|
let deletedCurrents = 0;
|
|
633
644
|
let deletedDiffs = 0;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
const {
|
|
643
|
-
unlinkSync
|
|
644
|
-
} = await import('node:fs');
|
|
645
|
-
unlinkSync(baselinePath);
|
|
646
|
-
deletedBaselines++;
|
|
647
|
-
// Silent deletion
|
|
648
|
-
} catch (error) {
|
|
649
|
-
output.warn(`Failed to delete baseline for ${comparison.name}: ${error.message}`);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Delete current screenshot if it exists
|
|
655
|
-
if (comparison.current) {
|
|
656
|
-
const currentPath = join(workingDir, '.vizzly', comparison.current.replace('/images/', ''));
|
|
657
|
-
if (existsSync(currentPath)) {
|
|
658
|
-
try {
|
|
659
|
-
const {
|
|
660
|
-
unlinkSync
|
|
661
|
-
} = await import('node:fs');
|
|
662
|
-
unlinkSync(currentPath);
|
|
663
|
-
deletedCurrents++;
|
|
664
|
-
// Silent deletion
|
|
665
|
-
} catch (error) {
|
|
666
|
-
output.warn(`Failed to delete current screenshot for ${comparison.name}: ${error.message}`);
|
|
667
|
-
}
|
|
645
|
+
let deleteResetFile = (imagePath, label, name) => {
|
|
646
|
+
if (!imagePath) return 0;
|
|
647
|
+
try {
|
|
648
|
+
let filePath = safePath(workingDir, '.vizzly', imagePath.replace('/images/', ''));
|
|
649
|
+
if (!existsSync(filePath)) return 0;
|
|
650
|
+
unlinkSync(filePath);
|
|
651
|
+
if (label === 'diff') {
|
|
652
|
+
output.debug(`Deleted diff for ${name}`);
|
|
668
653
|
}
|
|
654
|
+
return 1;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
output.warn(`Failed to delete ${label} for ${name}: ${error.message}`);
|
|
657
|
+
return 0;
|
|
669
658
|
}
|
|
659
|
+
};
|
|
670
660
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const {
|
|
677
|
-
unlinkSync
|
|
678
|
-
} = await import('node:fs');
|
|
679
|
-
unlinkSync(diffPath);
|
|
680
|
-
deletedDiffs++;
|
|
681
|
-
output.debug(`Deleted diff for ${comparison.name}`);
|
|
682
|
-
} catch (error) {
|
|
683
|
-
output.warn(`Failed to delete diff for ${comparison.name}: ${error.message}`);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
661
|
+
// Delete all baseline, current, and diff images
|
|
662
|
+
for (let comparison of reportData.comparisons) {
|
|
663
|
+
deletedBaselines += deleteResetFile(comparison.baseline, 'baseline', comparison.name);
|
|
664
|
+
deletedCurrents += deleteResetFile(comparison.current, 'current screenshot', comparison.name);
|
|
665
|
+
deletedDiffs += deleteResetFile(comparison.diff, 'diff', comparison.name);
|
|
687
666
|
}
|
|
688
667
|
|
|
689
668
|
// Delete baseline metadata
|
|
690
|
-
|
|
669
|
+
let metadataPath = join(workingDir, '.vizzly', 'baselines', 'metadata.json');
|
|
691
670
|
if (existsSync(metadataPath)) {
|
|
692
671
|
try {
|
|
693
|
-
const {
|
|
694
|
-
unlinkSync
|
|
695
|
-
} = await import('node:fs');
|
|
696
672
|
unlinkSync(metadataPath);
|
|
697
673
|
output.debug('Deleted baseline metadata');
|
|
698
674
|
} catch (error) {
|
|
@@ -6,6 +6,169 @@
|
|
|
6
6
|
import * as output from '../../utils/output.js';
|
|
7
7
|
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
8
8
|
import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
9
|
+
function sendBaselineUnavailable(res) {
|
|
10
|
+
sendError(res, 400, 'Baseline management not available');
|
|
11
|
+
}
|
|
12
|
+
async function parseRequiredComparisonId(req, res) {
|
|
13
|
+
let {
|
|
14
|
+
id
|
|
15
|
+
} = await parseJsonBody(req);
|
|
16
|
+
if (!id) {
|
|
17
|
+
sendError(res, 400, 'Comparison ID required');
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
async function handleComparisonAction({
|
|
23
|
+
req,
|
|
24
|
+
res,
|
|
25
|
+
screenshotHandler,
|
|
26
|
+
actionName,
|
|
27
|
+
action,
|
|
28
|
+
successMessage
|
|
29
|
+
}) {
|
|
30
|
+
if (!screenshotHandler?.[actionName]) {
|
|
31
|
+
sendBaselineUnavailable(res);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
let id = await parseRequiredComparisonId(req, res);
|
|
36
|
+
if (!id) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
await screenshotHandler[actionName](id);
|
|
40
|
+
sendSuccess(res, {
|
|
41
|
+
success: true,
|
|
42
|
+
message: successMessage(id)
|
|
43
|
+
});
|
|
44
|
+
return true;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === 'NOT_FOUND') {
|
|
47
|
+
sendError(res, 404, error.message);
|
|
48
|
+
} else {
|
|
49
|
+
output.error(`Error ${action}:`, error);
|
|
50
|
+
sendError(res, 500, error.message);
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function handleAcceptAllBaselines(res, screenshotHandler) {
|
|
56
|
+
if (!screenshotHandler?.acceptAllBaselines) {
|
|
57
|
+
sendBaselineUnavailable(res);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
let result = await screenshotHandler.acceptAllBaselines();
|
|
62
|
+
sendSuccess(res, {
|
|
63
|
+
success: true,
|
|
64
|
+
message: `Accepted ${result.count} baselines`,
|
|
65
|
+
count: result.count
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
output.error('Error accepting all baselines:', error);
|
|
70
|
+
sendError(res, 500, error.message);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function handleResetBaselines(res, screenshotHandler) {
|
|
75
|
+
if (!screenshotHandler?.resetBaselines) {
|
|
76
|
+
sendBaselineUnavailable(res);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await screenshotHandler.resetBaselines();
|
|
81
|
+
sendSuccess(res, {
|
|
82
|
+
success: true,
|
|
83
|
+
message: 'Baselines reset to previous state'
|
|
84
|
+
});
|
|
85
|
+
return true;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
output.error('Error resetting baselines:', error);
|
|
88
|
+
sendError(res, 500, error.message);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function shouldFallbackFromOAuth(error) {
|
|
93
|
+
return error.message?.includes('Not authenticated') || error.message?.includes('401');
|
|
94
|
+
}
|
|
95
|
+
async function downloadBaselinesWithOAuth({
|
|
96
|
+
authService,
|
|
97
|
+
tddService,
|
|
98
|
+
buildId,
|
|
99
|
+
organizationSlug,
|
|
100
|
+
projectSlug
|
|
101
|
+
}) {
|
|
102
|
+
output.info(`Downloading baselines from build ${buildId}...`);
|
|
103
|
+
output.debug('baseline', `Using OAuth for ${organizationSlug}/${projectSlug}`);
|
|
104
|
+
let apiResponse = await authService.authenticatedRequest(`/api/cli/${projectSlug}/builds/${buildId}/tdd-baselines`, {
|
|
105
|
+
method: 'GET',
|
|
106
|
+
headers: {
|
|
107
|
+
'X-Organization': organizationSlug
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (!apiResponse) {
|
|
111
|
+
throw new Error(`Build ${buildId} not found or API returned null`);
|
|
112
|
+
}
|
|
113
|
+
return tddService.processDownloadedBaselines(apiResponse, buildId);
|
|
114
|
+
}
|
|
115
|
+
async function downloadBaselinesWithToken(tddService, buildId) {
|
|
116
|
+
output.info(`Downloading baselines from build ${buildId}...`);
|
|
117
|
+
return tddService.downloadBaselines('test', null, buildId, null);
|
|
118
|
+
}
|
|
119
|
+
async function handleBaselineDownload({
|
|
120
|
+
req,
|
|
121
|
+
res,
|
|
122
|
+
tddService,
|
|
123
|
+
authService
|
|
124
|
+
}) {
|
|
125
|
+
if (!tddService) {
|
|
126
|
+
sendServiceUnavailable(res, 'TDD service not available (only available in TDD mode)');
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
let body = await parseJsonBody(req);
|
|
131
|
+
let {
|
|
132
|
+
buildId,
|
|
133
|
+
organizationSlug,
|
|
134
|
+
projectSlug
|
|
135
|
+
} = body;
|
|
136
|
+
if (!buildId) {
|
|
137
|
+
sendError(res, 400, 'buildId is required');
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
let result;
|
|
141
|
+
if (authService && organizationSlug && projectSlug) {
|
|
142
|
+
try {
|
|
143
|
+
result = await downloadBaselinesWithOAuth({
|
|
144
|
+
authService,
|
|
145
|
+
tddService,
|
|
146
|
+
buildId,
|
|
147
|
+
organizationSlug,
|
|
148
|
+
projectSlug
|
|
149
|
+
});
|
|
150
|
+
} catch (oauthError) {
|
|
151
|
+
if (!shouldFallbackFromOAuth(oauthError)) {
|
|
152
|
+
throw oauthError;
|
|
153
|
+
}
|
|
154
|
+
output.debug('baseline', `OAuth failed, trying other auth methods: ${oauthError.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!result) {
|
|
158
|
+
result = await downloadBaselinesWithToken(tddService, buildId);
|
|
159
|
+
}
|
|
160
|
+
sendSuccess(res, {
|
|
161
|
+
success: true,
|
|
162
|
+
message: `Baselines downloaded from build ${buildId}`,
|
|
163
|
+
...result
|
|
164
|
+
});
|
|
165
|
+
return true;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
output.error('Error downloading baselines:', error);
|
|
168
|
+
sendError(res, 500, error.message);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
9
172
|
|
|
10
173
|
/**
|
|
11
174
|
* Create baseline router
|
|
@@ -23,204 +186,58 @@ export function createBaselineRouter({
|
|
|
23
186
|
return async function handleBaselineRoute(req, res, pathname) {
|
|
24
187
|
// Accept a single screenshot as baseline
|
|
25
188
|
if (req.method === 'POST' && pathname === '/api/baseline/accept') {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!id) {
|
|
35
|
-
sendError(res, 400, 'Comparison ID required');
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
await screenshotHandler.acceptBaseline(id);
|
|
39
|
-
sendSuccess(res, {
|
|
40
|
-
success: true,
|
|
41
|
-
message: `Baseline accepted for comparison ${id}`
|
|
42
|
-
});
|
|
43
|
-
return true;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
output.error('Error accepting baseline:', error);
|
|
46
|
-
sendError(res, 500, error.message);
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
189
|
+
return handleComparisonAction({
|
|
190
|
+
req,
|
|
191
|
+
res,
|
|
192
|
+
screenshotHandler,
|
|
193
|
+
actionName: 'acceptBaseline',
|
|
194
|
+
action: 'accepting baseline',
|
|
195
|
+
successMessage: id => `Baseline accepted for comparison ${id}`
|
|
196
|
+
});
|
|
49
197
|
}
|
|
50
198
|
|
|
51
199
|
// Accept all screenshots as baseline
|
|
52
200
|
if (req.method === 'POST' && pathname === '/api/baseline/accept-all') {
|
|
53
|
-
|
|
54
|
-
sendError(res, 400, 'Baseline management not available');
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const result = await screenshotHandler.acceptAllBaselines();
|
|
59
|
-
sendSuccess(res, {
|
|
60
|
-
success: true,
|
|
61
|
-
message: `Accepted ${result.count} baselines`,
|
|
62
|
-
count: result.count
|
|
63
|
-
});
|
|
64
|
-
return true;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
output.error('Error accepting all baselines:', error);
|
|
67
|
-
sendError(res, 500, error.message);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
201
|
+
return handleAcceptAllBaselines(res, screenshotHandler);
|
|
70
202
|
}
|
|
71
203
|
|
|
72
204
|
// Reject a single comparison (keep current baseline, discard changes)
|
|
73
205
|
if (req.method === 'POST' && pathname === '/api/baseline/reject') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!id) {
|
|
83
|
-
sendError(res, 400, 'Comparison ID required');
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
await screenshotHandler.rejectBaseline(id);
|
|
87
|
-
sendSuccess(res, {
|
|
88
|
-
success: true,
|
|
89
|
-
message: `Changes rejected for comparison ${id}`
|
|
90
|
-
});
|
|
91
|
-
return true;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
output.error('Error rejecting baseline:', error);
|
|
94
|
-
sendError(res, 500, error.message);
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
206
|
+
return handleComparisonAction({
|
|
207
|
+
req,
|
|
208
|
+
res,
|
|
209
|
+
screenshotHandler,
|
|
210
|
+
actionName: 'rejectBaseline',
|
|
211
|
+
action: 'rejecting baseline',
|
|
212
|
+
successMessage: id => `Changes rejected for comparison ${id}`
|
|
213
|
+
});
|
|
97
214
|
}
|
|
98
215
|
|
|
99
216
|
// Delete a comparison entirely (removes from report and deletes files)
|
|
100
217
|
if (req.method === 'POST' && pathname === '/api/baseline/delete') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!id) {
|
|
110
|
-
sendError(res, 400, 'Comparison ID required');
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
await screenshotHandler.deleteComparison(id);
|
|
114
|
-
sendSuccess(res, {
|
|
115
|
-
success: true,
|
|
116
|
-
message: `Comparison ${id} deleted`
|
|
117
|
-
});
|
|
118
|
-
return true;
|
|
119
|
-
} catch (error) {
|
|
120
|
-
if (error.code === 'NOT_FOUND') {
|
|
121
|
-
sendError(res, 404, error.message);
|
|
122
|
-
} else {
|
|
123
|
-
output.error('Error deleting comparison:', error);
|
|
124
|
-
sendError(res, 500, error.message);
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
218
|
+
return handleComparisonAction({
|
|
219
|
+
req,
|
|
220
|
+
res,
|
|
221
|
+
screenshotHandler,
|
|
222
|
+
actionName: 'deleteComparison',
|
|
223
|
+
action: 'deleting comparison',
|
|
224
|
+
successMessage: id => `Comparison ${id} deleted`
|
|
225
|
+
});
|
|
128
226
|
}
|
|
129
227
|
|
|
130
228
|
// Reset baselines to previous state
|
|
131
229
|
if (req.method === 'POST' && pathname === '/api/baseline/reset') {
|
|
132
|
-
|
|
133
|
-
sendError(res, 400, 'Baseline management not available');
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
await screenshotHandler.resetBaselines();
|
|
138
|
-
sendSuccess(res, {
|
|
139
|
-
success: true,
|
|
140
|
-
message: 'Baselines reset to previous state'
|
|
141
|
-
});
|
|
142
|
-
return true;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
output.error('Error resetting baselines:', error);
|
|
145
|
-
sendError(res, 500, error.message);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
230
|
+
return handleResetBaselines(res, screenshotHandler);
|
|
148
231
|
}
|
|
149
232
|
|
|
150
233
|
// Download baselines from a remote build
|
|
151
234
|
if (req.method === 'POST' && pathname === '/api/baselines/download') {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
let {
|
|
159
|
-
buildId,
|
|
160
|
-
organizationSlug,
|
|
161
|
-
projectSlug
|
|
162
|
-
} = body;
|
|
163
|
-
if (!buildId) {
|
|
164
|
-
sendError(res, 400, 'buildId is required');
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Try OAuth authentication first (allows access to any project user has membership to)
|
|
169
|
-
// This is the preferred method when user is logged in via the dashboard
|
|
170
|
-
if (authService && organizationSlug && projectSlug) {
|
|
171
|
-
try {
|
|
172
|
-
output.info(`Downloading baselines from build ${buildId}...`);
|
|
173
|
-
output.debug('baseline', `Using OAuth for ${organizationSlug}/${projectSlug}`);
|
|
174
|
-
|
|
175
|
-
// Use the CLI endpoint which accepts OAuth and checks project membership
|
|
176
|
-
let apiResponse = await authService.authenticatedRequest(`/api/cli/${projectSlug}/builds/${buildId}/tdd-baselines`, {
|
|
177
|
-
method: 'GET',
|
|
178
|
-
headers: {
|
|
179
|
-
'X-Organization': organizationSlug
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
if (!apiResponse) {
|
|
183
|
-
throw new Error(`Build ${buildId} not found or API returned null`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Process the baselines through tddService
|
|
187
|
-
let result = await tddService.processDownloadedBaselines(apiResponse, buildId);
|
|
188
|
-
sendSuccess(res, {
|
|
189
|
-
success: true,
|
|
190
|
-
message: `Baselines downloaded from build ${buildId}`,
|
|
191
|
-
...result
|
|
192
|
-
});
|
|
193
|
-
return true;
|
|
194
|
-
} catch (oauthError) {
|
|
195
|
-
// If OAuth fails with auth error, fall through to other methods
|
|
196
|
-
if (!oauthError.message?.includes('Not authenticated') && !oauthError.message?.includes('401')) {
|
|
197
|
-
throw oauthError;
|
|
198
|
-
}
|
|
199
|
-
output.debug('baseline', `OAuth failed, trying other auth methods: ${oauthError.message}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Fall back to using tddService directly (requires VIZZLY_TOKEN env var)
|
|
204
|
-
output.info(`Downloading baselines from build ${buildId}...`);
|
|
205
|
-
let result = await tddService.downloadBaselines('test',
|
|
206
|
-
// environment
|
|
207
|
-
null,
|
|
208
|
-
// branch (not needed when buildId is specified)
|
|
209
|
-
buildId,
|
|
210
|
-
// specific build to download from
|
|
211
|
-
null // comparisonId (not needed)
|
|
212
|
-
);
|
|
213
|
-
sendSuccess(res, {
|
|
214
|
-
success: true,
|
|
215
|
-
message: `Baselines downloaded from build ${buildId}`,
|
|
216
|
-
...result
|
|
217
|
-
});
|
|
218
|
-
return true;
|
|
219
|
-
} catch (error) {
|
|
220
|
-
output.error('Error downloading baselines:', error);
|
|
221
|
-
sendError(res, 500, error.message);
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
235
|
+
return handleBaselineDownload({
|
|
236
|
+
req,
|
|
237
|
+
res,
|
|
238
|
+
tddService,
|
|
239
|
+
authService
|
|
240
|
+
});
|
|
224
241
|
}
|
|
225
242
|
return false;
|
|
226
243
|
};
|