@vizzly-testing/cli 0.30.0 → 0.32.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/core/region-coverage.js +109 -21
- 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/comparison-service.js +10 -1
- package/dist/tdd/services/hotspot-service.js +0 -13
- package/dist/tdd/tdd-service.js +144 -138
- 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 +14 -11
- 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
package/dist/types/index.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export interface ScreenshotOptions {
|
|
|
77
77
|
minClusterSize?: number;
|
|
78
78
|
fullPage?: boolean;
|
|
79
79
|
buildId?: string;
|
|
80
|
+
[key: string]: unknown;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export interface ScreenshotResult {
|
|
@@ -84,6 +85,7 @@ export interface ScreenshotResult {
|
|
|
84
85
|
status?: 'passed' | 'failed' | 'new';
|
|
85
86
|
name?: string;
|
|
86
87
|
diffPercentage?: number;
|
|
88
|
+
[key: string]: unknown;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
// ============================================================================
|
|
@@ -262,6 +264,12 @@ export class ValidationError extends VizzlyError {
|
|
|
262
264
|
export interface VizzlySDKInstance extends EventEmitter {
|
|
263
265
|
config: VizzlyConfig;
|
|
264
266
|
|
|
267
|
+
/** Reload file config and re-apply current in-memory overrides */
|
|
268
|
+
init(): Promise<VizzlyConfig>;
|
|
269
|
+
|
|
270
|
+
/** Merge new config values into the active SDK config */
|
|
271
|
+
updateConfig(newConfig: Partial<VizzlyConfig>): VizzlyConfig;
|
|
272
|
+
|
|
265
273
|
/** Start the Vizzly server */
|
|
266
274
|
start(): Promise<{ port: number; url: string }>;
|
|
267
275
|
|
|
@@ -286,6 +294,28 @@ export interface VizzlySDKInstance extends EventEmitter {
|
|
|
286
294
|
name: string,
|
|
287
295
|
imageBuffer: Buffer | string
|
|
288
296
|
): Promise<ComparisonResult>;
|
|
297
|
+
|
|
298
|
+
/** Create an uploader using the SDK config */
|
|
299
|
+
createUploader(options?: {
|
|
300
|
+
upload?: UploadConfig;
|
|
301
|
+
signal?: AbortSignal;
|
|
302
|
+
batchSize?: number;
|
|
303
|
+
timeout?: number;
|
|
304
|
+
}): Uploader;
|
|
305
|
+
|
|
306
|
+
/** Create a local TDD service using the SDK config */
|
|
307
|
+
createTDDService(options?: {
|
|
308
|
+
workingDir?: string;
|
|
309
|
+
setBaseline?: boolean;
|
|
310
|
+
authService?: unknown;
|
|
311
|
+
}): TddService;
|
|
312
|
+
|
|
313
|
+
/** Start a local TDD service */
|
|
314
|
+
startTDD(options?: {
|
|
315
|
+
workingDir?: string;
|
|
316
|
+
setBaseline?: boolean;
|
|
317
|
+
authService?: unknown;
|
|
318
|
+
}): Promise<unknown>;
|
|
289
319
|
}
|
|
290
320
|
|
|
291
321
|
export class VizzlySDK extends EventEmitter implements VizzlySDKInstance {
|
|
@@ -293,6 +323,8 @@ export class VizzlySDK extends EventEmitter implements VizzlySDKInstance {
|
|
|
293
323
|
|
|
294
324
|
constructor(config: VizzlyConfig, services: unknown);
|
|
295
325
|
|
|
326
|
+
init(): Promise<VizzlyConfig>;
|
|
327
|
+
updateConfig(newConfig: Partial<VizzlyConfig>): VizzlyConfig;
|
|
296
328
|
start(): Promise<{ port: number; url: string }>;
|
|
297
329
|
stop(): Promise<void>;
|
|
298
330
|
getConfig(): VizzlyConfig;
|
|
@@ -306,6 +338,22 @@ export class VizzlySDK extends EventEmitter implements VizzlySDKInstance {
|
|
|
306
338
|
name: string,
|
|
307
339
|
imageBuffer: Buffer | string
|
|
308
340
|
): Promise<ComparisonResult>;
|
|
341
|
+
createUploader(options?: {
|
|
342
|
+
upload?: UploadConfig;
|
|
343
|
+
signal?: AbortSignal;
|
|
344
|
+
batchSize?: number;
|
|
345
|
+
timeout?: number;
|
|
346
|
+
}): Uploader;
|
|
347
|
+
createTDDService(options?: {
|
|
348
|
+
workingDir?: string;
|
|
349
|
+
setBaseline?: boolean;
|
|
350
|
+
authService?: unknown;
|
|
351
|
+
}): TddService;
|
|
352
|
+
startTDD(options?: {
|
|
353
|
+
workingDir?: string;
|
|
354
|
+
setBaseline?: boolean;
|
|
355
|
+
authService?: unknown;
|
|
356
|
+
}): Promise<unknown>;
|
|
309
357
|
}
|
|
310
358
|
|
|
311
359
|
// ============================================================================
|
|
@@ -344,15 +392,8 @@ export interface TddService {
|
|
|
344
392
|
}
|
|
345
393
|
|
|
346
394
|
export interface Services {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
configService: unknown;
|
|
350
|
-
projectService: unknown;
|
|
351
|
-
uploader: Uploader;
|
|
352
|
-
buildManager: unknown;
|
|
353
|
-
serverManager: unknown;
|
|
354
|
-
tddService: TddService;
|
|
355
|
-
testRunner: unknown;
|
|
395
|
+
serverManager: ServerManager;
|
|
396
|
+
testRunner: TestRunnerService;
|
|
356
397
|
}
|
|
357
398
|
|
|
358
399
|
// ============================================================================
|
|
@@ -392,11 +433,47 @@ export interface PluginServerManager {
|
|
|
392
433
|
stop(): Promise<void>;
|
|
393
434
|
}
|
|
394
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Internal server manager returned by createServices().
|
|
438
|
+
*/
|
|
439
|
+
export interface ServerManager extends PluginServerManager {
|
|
440
|
+
/** Get current TDD results from the local server handler */
|
|
441
|
+
getTddResults(): Promise<TddResults | null>;
|
|
442
|
+
/** Current HTTP server facade, if running */
|
|
443
|
+
readonly server: unknown;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Internal TestRunner returned by createServices().
|
|
448
|
+
*/
|
|
449
|
+
export interface TestRunnerService extends PluginTestRunner {
|
|
450
|
+
/** Initialize daemon mode without running tests */
|
|
451
|
+
initialize(options: Record<string, unknown>): Promise<void>;
|
|
452
|
+
/** Run tests through the configured test command */
|
|
453
|
+
run(options: Record<string, unknown>): Promise<unknown>;
|
|
454
|
+
/** Cancel the active test process and stop the server */
|
|
455
|
+
cancel(): Promise<void>;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface PluginGitInfo {
|
|
459
|
+
branch: string;
|
|
460
|
+
commit: string | null;
|
|
461
|
+
message: string | null;
|
|
462
|
+
prNumber: number | null;
|
|
463
|
+
buildName: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export interface PluginGit {
|
|
467
|
+
/** Detect branch, commit, commit message, PR number, and build name */
|
|
468
|
+
detect(options?: { buildPrefix?: string }): Promise<PluginGitInfo>;
|
|
469
|
+
}
|
|
470
|
+
|
|
395
471
|
/**
|
|
396
472
|
* Stable services interface for plugins.
|
|
397
473
|
* This is the public API contract - internal services are NOT exposed.
|
|
398
474
|
*/
|
|
399
475
|
export interface PluginServices {
|
|
476
|
+
git: PluginGit;
|
|
400
477
|
testRunner: PluginTestRunner;
|
|
401
478
|
serverManager: PluginServerManager;
|
|
402
479
|
}
|
|
@@ -432,8 +509,6 @@ export interface PluginContext {
|
|
|
432
509
|
services: PluginServices;
|
|
433
510
|
/** Output utilities for logging */
|
|
434
511
|
output: OutputUtils;
|
|
435
|
-
/** @deprecated Use output instead. Alias for backwards compatibility. */
|
|
436
|
-
logger: OutputUtils;
|
|
437
512
|
}
|
|
438
513
|
|
|
439
514
|
/** Create stable plugin services from internal services */
|
|
@@ -444,12 +519,31 @@ export function createPluginServices(services: Services): PluginServices;
|
|
|
444
519
|
// ============================================================================
|
|
445
520
|
|
|
446
521
|
export interface OutputUtils {
|
|
447
|
-
info(message: string): void;
|
|
448
|
-
warn(message: string): void;
|
|
449
|
-
error(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
522
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
523
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
524
|
+
error(
|
|
525
|
+
message: string,
|
|
526
|
+
error?: Error | null,
|
|
527
|
+
data?: Record<string, unknown>
|
|
528
|
+
): void;
|
|
529
|
+
success(message: string, data?: Record<string, unknown>): void;
|
|
530
|
+
debug(
|
|
531
|
+
component: string,
|
|
532
|
+
message: string,
|
|
533
|
+
data?: Record<string, unknown>
|
|
534
|
+
): void;
|
|
535
|
+
configure(options: OutputConfigureOptions): void;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export interface OutputConfigureOptions {
|
|
539
|
+
json?: boolean | string;
|
|
540
|
+
jsonFields?: string[] | null;
|
|
541
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
542
|
+
verbose?: boolean;
|
|
543
|
+
color?: boolean;
|
|
544
|
+
silent?: boolean;
|
|
545
|
+
logFile?: string | null;
|
|
546
|
+
resetTimer?: boolean;
|
|
453
547
|
}
|
|
454
548
|
|
|
455
549
|
// ============================================================================
|
|
@@ -471,8 +565,9 @@ export function vizzlyScreenshot(
|
|
|
471
565
|
threshold?: number;
|
|
472
566
|
minClusterSize?: number;
|
|
473
567
|
fullPage?: boolean;
|
|
568
|
+
[key: string]: unknown;
|
|
474
569
|
}
|
|
475
|
-
): Promise<
|
|
570
|
+
): Promise<ScreenshotResult | null>;
|
|
476
571
|
|
|
477
572
|
/** Configure the Vizzly client */
|
|
478
573
|
export function configure(config?: {
|
|
@@ -510,10 +605,7 @@ export function createTDDService(
|
|
|
510
605
|
): TddService;
|
|
511
606
|
|
|
512
607
|
/** Create all services with dependencies */
|
|
513
|
-
export function createServices(
|
|
514
|
-
config: VizzlyConfig,
|
|
515
|
-
command?: string
|
|
516
|
-
): Services;
|
|
608
|
+
export function createServices(config: VizzlyConfig): Services;
|
|
517
609
|
|
|
518
610
|
/** Load configuration from file and environment */
|
|
519
611
|
export function loadConfig(options?: { cwd?: string }): Promise<VizzlyConfig>;
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -41,6 +41,18 @@ export class VizzlySDK extends EventEmitter {
|
|
|
41
41
|
|
|
42
42
|
constructor(config: import('./index').VizzlyConfig, services: unknown);
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Reload file config and re-apply current in-memory overrides.
|
|
46
|
+
*/
|
|
47
|
+
init(): Promise<import('./index').VizzlyConfig>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Merge new config values into the active SDK config.
|
|
51
|
+
*/
|
|
52
|
+
updateConfig(
|
|
53
|
+
newConfig: Partial<import('./index').VizzlyConfig>
|
|
54
|
+
): import('./index').VizzlyConfig;
|
|
55
|
+
|
|
44
56
|
/**
|
|
45
57
|
* Start the Vizzly server
|
|
46
58
|
* @returns Server information including port and URL
|
|
@@ -90,6 +102,34 @@ export class VizzlySDK extends EventEmitter {
|
|
|
90
102
|
name: string,
|
|
91
103
|
imageBuffer: Buffer | string
|
|
92
104
|
): Promise<import('./index').ComparisonResult>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create an uploader using the SDK config.
|
|
108
|
+
*/
|
|
109
|
+
createUploader(options?: {
|
|
110
|
+
upload?: import('./index').UploadConfig;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
batchSize?: number;
|
|
113
|
+
timeout?: number;
|
|
114
|
+
}): import('./index').Uploader;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a local TDD service using the SDK config.
|
|
118
|
+
*/
|
|
119
|
+
createTDDService(options?: {
|
|
120
|
+
workingDir?: string;
|
|
121
|
+
setBaseline?: boolean;
|
|
122
|
+
authService?: unknown;
|
|
123
|
+
}): import('./index').TddService;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Start a local TDD service.
|
|
127
|
+
*/
|
|
128
|
+
startTDD(options?: {
|
|
129
|
+
workingDir?: string;
|
|
130
|
+
setBaseline?: boolean;
|
|
131
|
+
authService?: unknown;
|
|
132
|
+
}): Promise<unknown>;
|
|
93
133
|
}
|
|
94
134
|
|
|
95
135
|
/**
|
package/dist/uploader/core.js
CHANGED
|
@@ -14,6 +14,7 @@ import { basename } from 'node:path';
|
|
|
14
14
|
export const DEFAULT_BATCH_SIZE = 50;
|
|
15
15
|
export const DEFAULT_SHA_CHECK_BATCH_SIZE = 100;
|
|
16
16
|
export const DEFAULT_TIMEOUT = 30000; // 30 seconds
|
|
17
|
+
export const DEFAULT_BUILD_POLL_INTERVAL = 1000; // 1 second
|
|
17
18
|
|
|
18
19
|
// ============================================================================
|
|
19
20
|
// Validation
|
|
@@ -363,8 +364,8 @@ export function resolveTimeout(options, uploadConfig) {
|
|
|
363
364
|
* @param {number} timeout - Timeout in ms
|
|
364
365
|
* @returns {boolean} True if timed out
|
|
365
366
|
*/
|
|
366
|
-
export function isTimedOut(startTime, timeout) {
|
|
367
|
-
return
|
|
367
|
+
export function isTimedOut(startTime, timeout, now = Date.now()) {
|
|
368
|
+
return now - startTime >= timeout;
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
/**
|
|
@@ -372,8 +373,8 @@ export function isTimedOut(startTime, timeout) {
|
|
|
372
373
|
* @param {number} startTime - Start timestamp
|
|
373
374
|
* @returns {number} Elapsed time in ms
|
|
374
375
|
*/
|
|
375
|
-
export function getElapsedTime(startTime) {
|
|
376
|
-
return
|
|
376
|
+
export function getElapsedTime(startTime, now = Date.now()) {
|
|
377
|
+
return now - startTime;
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
/**
|
package/dist/uploader/index.js
CHANGED
|
@@ -1,11 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Uploader Module
|
|
3
3
|
*
|
|
4
|
-
* Exports pure functions (core)
|
|
4
|
+
* Exports pure functions (core), I/O operations, and the public uploader
|
|
5
|
+
* factory for screenshot uploading.
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
9
|
+
import { glob } from 'glob';
|
|
10
|
+
import { checkShas, createApiClient, createBuild } from '../api/index.js';
|
|
11
|
+
import { TimeoutError, UploadError, ValidationError } from '../errors/vizzly-error.js';
|
|
12
|
+
import { getDefaultBranch } from '../utils/git.js';
|
|
13
|
+
import * as output from '../utils/output.js';
|
|
14
|
+
import { resolveBatchSize, resolveTimeout } from './core.js';
|
|
15
|
+
import { upload as uploadOperation, waitForBuild as waitForBuildOperation } from './operations.js';
|
|
16
|
+
export function createUploader({
|
|
17
|
+
apiKey,
|
|
18
|
+
apiUrl,
|
|
19
|
+
userAgent,
|
|
20
|
+
command,
|
|
21
|
+
upload: uploadConfig = {}
|
|
22
|
+
} = {}, options = {}) {
|
|
23
|
+
let signal = options.signal || new AbortController().signal;
|
|
24
|
+
let client = createApiClient({
|
|
25
|
+
baseUrl: apiUrl,
|
|
26
|
+
token: apiKey,
|
|
27
|
+
command: command || 'upload',
|
|
28
|
+
sdkUserAgent: userAgent,
|
|
29
|
+
allowNoToken: true
|
|
30
|
+
});
|
|
31
|
+
let batchSize = resolveBatchSize(options, uploadConfig);
|
|
32
|
+
let timeout = resolveTimeout(options, uploadConfig);
|
|
33
|
+
let deps = options.deps || {
|
|
34
|
+
client,
|
|
35
|
+
createBuild,
|
|
36
|
+
getDefaultBranch,
|
|
37
|
+
glob,
|
|
38
|
+
readFile,
|
|
39
|
+
stat,
|
|
40
|
+
checkShas,
|
|
41
|
+
createError: (message, code, context) => {
|
|
42
|
+
let error = new UploadError(message, context);
|
|
43
|
+
error.code = code;
|
|
44
|
+
return error;
|
|
45
|
+
},
|
|
46
|
+
createValidationError: (message, context) => new ValidationError(message, context),
|
|
47
|
+
createUploadError: (message, context) => new UploadError(message, context),
|
|
48
|
+
createTimeoutError: (message, context) => new TimeoutError(message, context),
|
|
49
|
+
output
|
|
50
|
+
};
|
|
51
|
+
async function upload(uploadOptions) {
|
|
52
|
+
return uploadOperation({
|
|
53
|
+
uploadOptions,
|
|
54
|
+
config: {
|
|
55
|
+
apiKey,
|
|
56
|
+
apiUrl
|
|
57
|
+
},
|
|
58
|
+
signal,
|
|
59
|
+
batchSize,
|
|
60
|
+
deps: {
|
|
61
|
+
...deps,
|
|
62
|
+
client: deps.client || client
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function waitForBuild(buildId, waitTimeout = timeout) {
|
|
67
|
+
return waitForBuildOperation({
|
|
68
|
+
buildId,
|
|
69
|
+
timeout: waitTimeout,
|
|
70
|
+
signal,
|
|
71
|
+
client: deps.client || client,
|
|
72
|
+
deps: {
|
|
73
|
+
createError: deps.createError,
|
|
74
|
+
createTimeoutError: deps.createTimeoutError
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
upload,
|
|
80
|
+
waitForBuild
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
7
84
|
// Core - pure functions
|
|
8
|
-
export { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, computeSha256, DEFAULT_BATCH_SIZE, DEFAULT_SHA_CHECK_BATCH_SIZE, DEFAULT_TIMEOUT, extractBrowserFromFilename, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, resolveBatchSize, resolveTimeout, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
|
|
85
|
+
export { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, computeSha256, DEFAULT_BATCH_SIZE, DEFAULT_BUILD_POLL_INTERVAL, DEFAULT_SHA_CHECK_BATCH_SIZE, DEFAULT_TIMEOUT, extractBrowserFromFilename, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, resolveBatchSize, resolveTimeout, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
|
|
9
86
|
|
|
10
87
|
// Operations - I/O with dependency injection
|
|
11
88
|
export { checkExistingFiles, findScreenshots, processFiles, upload, uploadFiles, waitForBuild } from './operations.js';
|
|
@@ -4,7 +4,34 @@
|
|
|
4
4
|
* Each operation takes its dependencies as parameters for testability.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, DEFAULT_SHA_CHECK_BATCH_SIZE, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
|
|
7
|
+
import { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, DEFAULT_BUILD_POLL_INTERVAL, DEFAULT_SHA_CHECK_BATCH_SIZE, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
|
|
8
|
+
function delay(ms, signal) {
|
|
9
|
+
if (ms <= 0) {
|
|
10
|
+
return Promise.resolve();
|
|
11
|
+
}
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
let timeoutId;
|
|
14
|
+
function cleanup() {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
signal?.removeEventListener?.('abort', handleAbort);
|
|
17
|
+
}
|
|
18
|
+
function handleAbort() {
|
|
19
|
+
cleanup();
|
|
20
|
+
reject(new Error('Operation cancelled'));
|
|
21
|
+
}
|
|
22
|
+
timeoutId = setTimeout(() => {
|
|
23
|
+
cleanup();
|
|
24
|
+
resolve();
|
|
25
|
+
}, ms);
|
|
26
|
+
if (signal?.aborted) {
|
|
27
|
+
handleAbort();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
signal?.addEventListener?.('abort', handleAbort, {
|
|
31
|
+
once: true
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
8
35
|
|
|
9
36
|
// ============================================================================
|
|
10
37
|
// File Discovery
|
|
@@ -228,10 +255,13 @@ export async function waitForBuild({
|
|
|
228
255
|
}) {
|
|
229
256
|
let {
|
|
230
257
|
createError,
|
|
231
|
-
createTimeoutError
|
|
258
|
+
createTimeoutError,
|
|
259
|
+
now = () => Date.now(),
|
|
260
|
+
pollInterval = DEFAULT_BUILD_POLL_INTERVAL,
|
|
261
|
+
wait = delay
|
|
232
262
|
} = deps;
|
|
233
|
-
let startTime =
|
|
234
|
-
while (!isTimedOut(startTime, timeout)) {
|
|
263
|
+
let startTime = now();
|
|
264
|
+
while (!isTimedOut(startTime, timeout, now())) {
|
|
235
265
|
if (signal.aborted) {
|
|
236
266
|
throw createError('Operation cancelled', 'UPLOAD_CANCELLED', {
|
|
237
267
|
buildId
|
|
@@ -253,11 +283,16 @@ export async function waitForBuild({
|
|
|
253
283
|
if (build.status === 'failed') {
|
|
254
284
|
throw createError(`Build failed: ${build.error || 'Unknown error'}`, 'BUILD_FAILED');
|
|
255
285
|
}
|
|
286
|
+
let remaining = timeout - getElapsedTime(startTime, now());
|
|
287
|
+
if (remaining <= 0) {
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
await wait(Math.min(pollInterval, remaining), signal);
|
|
256
291
|
}
|
|
257
292
|
throw createTimeoutError(`Build timed out after ${timeout}ms`, {
|
|
258
293
|
buildId,
|
|
259
294
|
timeout,
|
|
260
|
-
elapsed: getElapsedTime(startTime)
|
|
295
|
+
elapsed: getElapsedTime(startTime, now())
|
|
261
296
|
});
|
|
262
297
|
}
|
|
263
298
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function getAppBaseUrl(apiUrl) {
|
|
2
|
+
try {
|
|
3
|
+
let url = new URL(apiUrl);
|
|
4
|
+
let apiPathIndex = url.pathname.indexOf('/api');
|
|
5
|
+
if (apiPathIndex !== -1) {
|
|
6
|
+
url.pathname = url.pathname.slice(0, apiPathIndex) || '/';
|
|
7
|
+
url.search = '';
|
|
8
|
+
url.hash = '';
|
|
9
|
+
}
|
|
10
|
+
return url.toString().replace(/\/$/, '');
|
|
11
|
+
} catch {
|
|
12
|
+
return apiUrl.replace(/\/api(?:\/.*)?$/, '');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Race an operation against a timeout and always clear the timer when either
|
|
3
|
+
* side settles.
|
|
4
|
+
*
|
|
5
|
+
* @param {Promise<unknown>} operation - Operation promise to wait for.
|
|
6
|
+
* @param {number} ms - Timeout in milliseconds.
|
|
7
|
+
* @param {string} message - Error message when the timeout wins.
|
|
8
|
+
* @param {Object} [timers] - Timer implementation for deterministic tests.
|
|
9
|
+
* @returns {Promise<unknown>} The operation result.
|
|
10
|
+
*/
|
|
11
|
+
export async function withTimeout(operation, ms, message, timers = {
|
|
12
|
+
setTimeout,
|
|
13
|
+
clearTimeout
|
|
14
|
+
}) {
|
|
15
|
+
let timeoutId;
|
|
16
|
+
let timeoutPromise = new Promise((_, reject) => {
|
|
17
|
+
timeoutId = timers.setTimeout(() => reject(new Error(message)), ms);
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
return await Promise.race([operation, timeoutPromise]);
|
|
21
|
+
} finally {
|
|
22
|
+
if (timeoutId !== undefined) {
|
|
23
|
+
timers.clearTimeout(timeoutId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/utils/ci-env.js
CHANGED
|
@@ -176,6 +176,13 @@ export function getCommitMessage() {
|
|
|
176
176
|
// Generic
|
|
177
177
|
null;
|
|
178
178
|
}
|
|
179
|
+
function parsePullRequestNumber(value) {
|
|
180
|
+
if (!/^\d+$/.test(value)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
let number = Number(value);
|
|
184
|
+
return Number.isSafeInteger(number) && number > 0 ? number : null;
|
|
185
|
+
}
|
|
179
186
|
|
|
180
187
|
/**
|
|
181
188
|
* Get the pull request number from CI environment variables
|
|
@@ -184,54 +191,54 @@ export function getCommitMessage() {
|
|
|
184
191
|
export function getPullRequestNumber() {
|
|
185
192
|
// Check VIZZLY override first
|
|
186
193
|
if (process.env.VIZZLY_PR_NUMBER) {
|
|
187
|
-
return
|
|
194
|
+
return parsePullRequestNumber(process.env.VIZZLY_PR_NUMBER);
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
// GitHub Actions - extract from GITHUB_REF
|
|
191
198
|
if (process.env.GITHUB_ACTIONS && process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
192
|
-
|
|
193
|
-
if (prMatch?.[1]) return
|
|
199
|
+
let prMatch = process.env.GITHUB_REF?.match(/refs\/pull\/(\d+)\/merge/);
|
|
200
|
+
if (prMatch?.[1]) return parsePullRequestNumber(prMatch[1]);
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
// GitLab CI
|
|
197
204
|
if (process.env.CI_MERGE_REQUEST_ID) {
|
|
198
|
-
return
|
|
205
|
+
return parsePullRequestNumber(process.env.CI_MERGE_REQUEST_ID);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
// CircleCI - extract from PR URL
|
|
202
209
|
if (process.env.CIRCLE_PULL_REQUEST) {
|
|
203
|
-
|
|
204
|
-
if (prMatch?.[1]) return
|
|
210
|
+
let prMatch = process.env.CIRCLE_PULL_REQUEST.match(/\/pull\/(\d+)$/);
|
|
211
|
+
if (prMatch?.[1]) return parsePullRequestNumber(prMatch[1]);
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
// Travis CI
|
|
208
215
|
if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false') {
|
|
209
|
-
return
|
|
216
|
+
return parsePullRequestNumber(process.env.TRAVIS_PULL_REQUEST);
|
|
210
217
|
}
|
|
211
218
|
|
|
212
219
|
// Buildkite
|
|
213
220
|
if (process.env.BUILDKITE_PULL_REQUEST && process.env.BUILDKITE_PULL_REQUEST !== 'false') {
|
|
214
|
-
return
|
|
221
|
+
return parsePullRequestNumber(process.env.BUILDKITE_PULL_REQUEST);
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
// Drone CI
|
|
218
225
|
if (process.env.DRONE_PULL_REQUEST) {
|
|
219
|
-
return
|
|
226
|
+
return parsePullRequestNumber(process.env.DRONE_PULL_REQUEST);
|
|
220
227
|
}
|
|
221
228
|
|
|
222
229
|
// Jenkins (GitHub Pull Request Builder plugin)
|
|
223
230
|
if (process.env.ghprbPullId) {
|
|
224
|
-
return
|
|
231
|
+
return parsePullRequestNumber(process.env.ghprbPullId);
|
|
225
232
|
}
|
|
226
233
|
|
|
227
234
|
// Azure DevOps
|
|
228
235
|
if (process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
|
|
229
|
-
return
|
|
236
|
+
return parsePullRequestNumber(process.env.SYSTEM_PULLREQUEST_PULLREQUESTID);
|
|
230
237
|
}
|
|
231
238
|
|
|
232
239
|
// AppVeyor
|
|
233
240
|
if (process.env.APPVEYOR_PULL_REQUEST_NUMBER) {
|
|
234
|
-
return
|
|
241
|
+
return parsePullRequestNumber(process.env.APPVEYOR_PULL_REQUEST_NUMBER);
|
|
235
242
|
}
|
|
236
243
|
return null;
|
|
237
244
|
}
|