@argos-ci/core 1.0.0 → 1.2.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/dist/index.d.ts CHANGED
@@ -38,11 +38,34 @@ declare const upload: (params: UploadParameters) => Promise<{
38
38
  url: string;
39
39
  };
40
40
  screenshots: {
41
- metadata: import("@argos-ci/util").ScreenshotMetadata | null;
42
- optimizedPath: string;
43
41
  hash: string;
42
+ optimizedPath: string;
43
+ metadata: import("@argos-ci/util").ScreenshotMetadata | null;
44
+ pwTrace: {
45
+ path: string;
46
+ hash: string;
47
+ } | null;
44
48
  name: string;
45
49
  path: string;
46
50
  }[];
47
51
  }>;
48
- export { UploadParameters, upload };
52
+ interface Config {
53
+ apiBaseUrl: string;
54
+ commit: string;
55
+ branch: string;
56
+ token: string | null;
57
+ buildName: string | null;
58
+ parallel: boolean;
59
+ parallelNonce: string | null;
60
+ parallelTotal: number | null;
61
+ referenceBranch: string | null;
62
+ referenceCommit: string | null;
63
+ owner: string | null;
64
+ repository: string | null;
65
+ jobId: string | null;
66
+ runId: string | null;
67
+ prNumber: number | null;
68
+ prHeadCommit: string | null;
69
+ }
70
+ declare const readConfig: (options?: Partial<Config>) => Config;
71
+ export { UploadParameters, upload, Config, readConfig };
package/dist/index.mjs CHANGED
@@ -10,128 +10,7 @@ import tmp from 'tmp';
10
10
  import { createHash } from 'node:crypto';
11
11
  import axios from 'axios';
12
12
  import { readFile } from 'node:fs/promises';
13
- import { readMetadata } from '@argos-ci/util';
14
-
15
- const mustBeApiBaseUrl = (value)=>{
16
- const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
17
- if (!URL_REGEX.test(value)) {
18
- throw new Error("Invalid Argos API base URL");
19
- }
20
- };
21
- const mustBeCommit = (value)=>{
22
- const SHA1_REGEX = /^[0-9a-f]{40}$/;
23
- if (!SHA1_REGEX.test(value)) {
24
- const SHA1_SHORT_REGEX = /^[0-9a-f]{7}$/;
25
- if (SHA1_SHORT_REGEX.test(value)) {
26
- throw new Error("Short SHA1 is not allowed");
27
- }
28
- throw new Error("Invalid commit");
29
- }
30
- };
31
- const mustBeArgosToken = (value)=>{
32
- if (value && value.length !== 40) {
33
- throw new Error("Invalid Argos repository token (must be 40 characters)");
34
- }
35
- };
36
- const schema = {
37
- apiBaseUrl: {
38
- env: "ARGOS_API_BASE_URL",
39
- default: "https://api.argos-ci.com/v2/",
40
- format: mustBeApiBaseUrl
41
- },
42
- commit: {
43
- env: "ARGOS_COMMIT",
44
- default: null,
45
- format: mustBeCommit
46
- },
47
- branch: {
48
- env: "ARGOS_BRANCH",
49
- default: null,
50
- format: String
51
- },
52
- token: {
53
- env: "ARGOS_TOKEN",
54
- default: null,
55
- format: mustBeArgosToken
56
- },
57
- buildName: {
58
- env: "ARGOS_BUILD_NAME",
59
- default: null,
60
- format: String,
61
- nullable: true
62
- },
63
- prNumber: {
64
- env: "ARGOS_PR_NUMBER",
65
- format: Number,
66
- default: null,
67
- nullable: true
68
- },
69
- prHeadCommit: {
70
- env: "ARGOS_PR_HEAD_COMMIT",
71
- format: String,
72
- default: null,
73
- nullable: true
74
- },
75
- parallel: {
76
- env: "ARGOS_PARALLEL",
77
- default: false,
78
- format: Boolean
79
- },
80
- parallelNonce: {
81
- env: "ARGOS_PARALLEL_NONCE",
82
- format: String,
83
- default: null,
84
- nullable: true
85
- },
86
- parallelTotal: {
87
- env: "ARGOS_PARALLEL_TOTAL",
88
- format: "nat",
89
- default: null,
90
- nullable: true
91
- },
92
- referenceBranch: {
93
- env: "ARGOS_REFERENCE_BRANCH",
94
- format: String,
95
- default: null,
96
- nullable: true
97
- },
98
- referenceCommit: {
99
- env: "ARGOS_REFERENCE_COMMIT",
100
- format: String,
101
- default: null,
102
- nullable: true
103
- },
104
- ciService: {
105
- format: String,
106
- default: null,
107
- nullable: true
108
- },
109
- jobId: {
110
- format: String,
111
- default: null,
112
- nullable: true
113
- },
114
- runId: {
115
- format: String,
116
- default: null,
117
- nullable: true
118
- },
119
- owner: {
120
- format: String,
121
- default: null,
122
- nullable: true
123
- },
124
- repository: {
125
- format: String,
126
- default: null,
127
- nullable: true
128
- }
129
- };
130
- const createConfig = ()=>{
131
- return convict(schema, {
132
- args: []
133
- });
134
- };
13
+ import { readMetadata, getPlaywrightTracePath } from '@argos-ci/util';
135
14
 
136
15
  /**
137
16
  * Check if the current directory is a git repository.
@@ -178,7 +57,8 @@ const service$6 = {
178
57
  jobId: null,
179
58
  runId: null,
180
59
  prNumber: env.BUILDKITE_PULL_REQUEST ? Number(env.BUILDKITE_PULL_REQUEST) : null,
181
- prHeadCommit: null
60
+ prHeadCommit: null,
61
+ nonce: env.BUILDKITE_BUILD_ID || null
182
62
  };
183
63
  }
184
64
  };
@@ -194,7 +74,8 @@ const service$5 = {
194
74
  jobId: null,
195
75
  runId: null,
196
76
  prNumber: null,
197
- prHeadCommit: null
77
+ prHeadCommit: null,
78
+ nonce: env.HEROKU_TEST_RUN_ID || null
198
79
  })
199
80
  };
200
81
 
@@ -238,7 +119,8 @@ const service$4 = {
238
119
  jobId: env.GITHUB_JOB || null,
239
120
  runId: env.GITHUB_RUN_ID || null,
240
121
  prNumber: (payload === null || payload === void 0 ? void 0 : (_payload_pull_request1 = payload.pull_request) === null || _payload_pull_request1 === void 0 ? void 0 : _payload_pull_request1.number) || null,
241
- prHeadCommit: (payload === null || payload === void 0 ? void 0 : (_payload_pull_request2 = payload.pull_request) === null || _payload_pull_request2 === void 0 ? void 0 : _payload_pull_request2.head.sha) ?? null
122
+ prHeadCommit: (payload === null || payload === void 0 ? void 0 : (_payload_pull_request2 = payload.pull_request) === null || _payload_pull_request2 === void 0 ? void 0 : _payload_pull_request2.head.sha) ?? null,
123
+ nonce: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` || null
242
124
  };
243
125
  }
244
126
  };
@@ -265,7 +147,8 @@ const service$3 = {
265
147
  prNumber: getPrNumber$1({
266
148
  env
267
149
  }),
268
- prHeadCommit: null
150
+ prHeadCommit: null,
151
+ nonce: env.CIRCLE_WORKFLOW_ID || env.CIRCLE_BUILD_NUM || null
269
152
  };
270
153
  }
271
154
  };
@@ -295,7 +178,8 @@ const service$2 = {
295
178
  jobId: null,
296
179
  runId: null,
297
180
  prNumber: getPrNumber(ctx),
298
- prHeadCommit: null
181
+ prHeadCommit: null,
182
+ nonce: env.TRAVIS_BUILD_ID || null
299
183
  };
300
184
  }
301
185
  };
@@ -312,7 +196,8 @@ const service$1 = {
312
196
  jobId: null,
313
197
  runId: null,
314
198
  prNumber: null,
315
- prHeadCommit: null
199
+ prHeadCommit: null,
200
+ nonce: env.CI_PIPELINE_ID || null
316
201
  };
317
202
  }
318
203
  };
@@ -330,7 +215,8 @@ const service = {
330
215
  jobId: null,
331
216
  runId: null,
332
217
  prNumber: null,
333
- prHeadCommit: null
218
+ prHeadCommit: null,
219
+ nonce: null
334
220
  };
335
221
  }
336
222
  };
@@ -381,6 +267,152 @@ const getCiEnvironment = ({ env = process.env } = {})=>{
381
267
  return null;
382
268
  };
383
269
 
270
+ const mustBeApiBaseUrl = (value)=>{
271
+ const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
272
+ if (!URL_REGEX.test(value)) {
273
+ throw new Error("Invalid Argos API base URL");
274
+ }
275
+ };
276
+ const mustBeCommit = (value)=>{
277
+ const SHA1_REGEX = /^[0-9a-f]{40}$/;
278
+ if (!SHA1_REGEX.test(value)) {
279
+ const SHA1_SHORT_REGEX = /^[0-9a-f]{7}$/;
280
+ if (SHA1_SHORT_REGEX.test(value)) {
281
+ throw new Error("Short SHA1 is not allowed");
282
+ }
283
+ throw new Error("Invalid commit");
284
+ }
285
+ };
286
+ const mustBeArgosToken = (value)=>{
287
+ if (value && value.length !== 40) {
288
+ throw new Error("Invalid Argos repository token (must be 40 characters)");
289
+ }
290
+ };
291
+ const schema = {
292
+ apiBaseUrl: {
293
+ env: "ARGOS_API_BASE_URL",
294
+ default: "https://api.argos-ci.com/v2/",
295
+ format: mustBeApiBaseUrl
296
+ },
297
+ commit: {
298
+ env: "ARGOS_COMMIT",
299
+ default: null,
300
+ format: mustBeCommit
301
+ },
302
+ branch: {
303
+ env: "ARGOS_BRANCH",
304
+ default: null,
305
+ format: String
306
+ },
307
+ token: {
308
+ env: "ARGOS_TOKEN",
309
+ default: null,
310
+ format: mustBeArgosToken
311
+ },
312
+ buildName: {
313
+ env: "ARGOS_BUILD_NAME",
314
+ default: null,
315
+ format: String,
316
+ nullable: true
317
+ },
318
+ prNumber: {
319
+ env: "ARGOS_PR_NUMBER",
320
+ format: Number,
321
+ default: null,
322
+ nullable: true
323
+ },
324
+ prHeadCommit: {
325
+ env: "ARGOS_PR_HEAD_COMMIT",
326
+ format: String,
327
+ default: null,
328
+ nullable: true
329
+ },
330
+ parallel: {
331
+ env: "ARGOS_PARALLEL",
332
+ default: false,
333
+ format: Boolean
334
+ },
335
+ parallelNonce: {
336
+ env: "ARGOS_PARALLEL_NONCE",
337
+ format: String,
338
+ default: null,
339
+ nullable: true
340
+ },
341
+ parallelTotal: {
342
+ env: "ARGOS_PARALLEL_TOTAL",
343
+ format: "nat",
344
+ default: null,
345
+ nullable: true
346
+ },
347
+ referenceBranch: {
348
+ env: "ARGOS_REFERENCE_BRANCH",
349
+ format: String,
350
+ default: null,
351
+ nullable: true
352
+ },
353
+ referenceCommit: {
354
+ env: "ARGOS_REFERENCE_COMMIT",
355
+ format: String,
356
+ default: null,
357
+ nullable: true
358
+ },
359
+ ciService: {
360
+ format: String,
361
+ default: null,
362
+ nullable: true
363
+ },
364
+ jobId: {
365
+ format: String,
366
+ default: null,
367
+ nullable: true
368
+ },
369
+ runId: {
370
+ format: String,
371
+ default: null,
372
+ nullable: true
373
+ },
374
+ owner: {
375
+ format: String,
376
+ default: null,
377
+ nullable: true
378
+ },
379
+ repository: {
380
+ format: String,
381
+ default: null,
382
+ nullable: true
383
+ }
384
+ };
385
+ const createConfig = ()=>{
386
+ return convict(schema, {
387
+ args: []
388
+ });
389
+ };
390
+ const readConfig = (options = {})=>{
391
+ const config = createConfig();
392
+ const ciEnv = getCiEnvironment();
393
+ config.load({
394
+ apiBaseUrl: options.apiBaseUrl ?? config.get("apiBaseUrl"),
395
+ commit: options.commit ?? config.get("commit") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.commit) ?? null,
396
+ branch: options.branch ?? config.get("branch") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.branch) ?? null,
397
+ token: options.token ?? config.get("token") ?? null,
398
+ buildName: options.buildName ?? config.get("buildName") ?? null,
399
+ prNumber: options.prNumber ?? config.get("prNumber") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.prNumber) ?? null,
400
+ prHeadCommit: config.get("prHeadCommit") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.prHeadCommit) ?? null,
401
+ referenceBranch: options.referenceBranch ?? config.get("referenceBranch") ?? null,
402
+ referenceCommit: options.referenceCommit ?? config.get("referenceCommit") ?? null,
403
+ ciService: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.name) ?? null,
404
+ owner: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.owner) ?? null,
405
+ repository: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.repository) ?? null,
406
+ jobId: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.jobId) ?? null,
407
+ runId: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.runId) ?? null,
408
+ parallel: options.parallel ?? config.get("parallel") ?? false,
409
+ parallelNonce: options.parallelNonce ?? config.get("parallelNonce") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.nonce) ?? null,
410
+ parallelTotal: options.parallelTotal ?? config.get("parallelTotal") ?? null
411
+ });
412
+ config.validate();
413
+ return config.get();
414
+ };
415
+
384
416
  const discoverScreenshots = async (patterns, { root = process.cwd(), ignore } = {})=>{
385
417
  const matches = await glob(patterns, {
386
418
  onlyFiles: true,
@@ -496,7 +528,7 @@ const upload$1 = async (input)=>{
496
528
  url: input.url,
497
529
  data: file,
498
530
  headers: {
499
- "Content-Type": "image/png"
531
+ "Content-Type": input.contentType
500
532
  }
501
533
  });
502
534
  };
@@ -517,34 +549,14 @@ const upload$1 = async (input)=>{
517
549
  /**
518
550
  * Size of the chunks used to upload screenshots to Argos.
519
551
  */ const CHUNK_SIZE = 10;
520
- const getConfigFromOptions = (options)=>{
521
- const config = createConfig();
522
- const ciEnv = getCiEnvironment();
523
- config.load({
524
- apiBaseUrl: options.apiBaseUrl ?? config.get("apiBaseUrl"),
525
- commit: options.commit ?? config.get("commit") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.commit) ?? null,
526
- branch: options.branch ?? config.get("branch") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.branch) ?? null,
527
- token: options.token ?? config.get("token") ?? null,
528
- buildName: options.buildName ?? config.get("buildName") ?? null,
529
- prNumber: options.prNumber ?? config.get("prNumber") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.prNumber) ?? null,
530
- prHeadCommit: config.get("prHeadCommit") ?? (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.prHeadCommit) ?? null,
531
- referenceBranch: options.referenceBranch ?? config.get("referenceBranch") ?? null,
532
- referenceCommit: options.referenceCommit ?? config.get("referenceCommit") ?? null,
533
- ciService: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.name) ?? null,
534
- owner: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.owner) ?? null,
535
- repository: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.repository) ?? null,
536
- jobId: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.jobId) ?? null,
537
- runId: (ciEnv === null || ciEnv === void 0 ? void 0 : ciEnv.runId) ?? null
552
+ const getConfigFromOptions = ({ parallel, ...options })=>{
553
+ const config = readConfig({
554
+ ...options,
555
+ parallel: Boolean(parallel),
556
+ parallelNonce: parallel ? parallel.nonce : null,
557
+ parallelTotal: parallel ? parallel.total : null
538
558
  });
539
- if (options.parallel) {
540
- config.load({
541
- parallel: Boolean(options.parallel),
542
- parallelNonce: options.parallel ? options.parallel.nonce : null,
543
- parallelTotal: options.parallel ? options.parallel.total : null
544
- });
545
- }
546
- config.validate();
547
- return config.get();
559
+ return config;
548
560
  };
549
561
  /**
550
562
  * Upload screenshots to argos-ci.com.
@@ -568,27 +580,50 @@ const getConfigFromOptions = (options)=>{
568
580
  debug("Found screenshots", foundScreenshots);
569
581
  // Optimize & compute hashes
570
582
  const screenshots = await Promise.all(foundScreenshots.map(async (screenshot)=>{
571
- const [metadata, optimizedPath] = await Promise.all([
583
+ const [metadata, pwTracePath, optimizedPath] = await Promise.all([
572
584
  readMetadata(screenshot.path),
585
+ getPlaywrightTracePath(screenshot.path),
573
586
  optimizeScreenshot(screenshot.path)
574
587
  ]);
575
- const hash = await hashFile(optimizedPath);
588
+ const [hash, pwTraceHash] = await Promise.all([
589
+ hashFile(optimizedPath),
590
+ pwTracePath ? hashFile(pwTracePath) : null
591
+ ]);
576
592
  return {
577
593
  ...screenshot,
578
- metadata,
594
+ hash,
579
595
  optimizedPath,
580
- hash
596
+ metadata,
597
+ pwTrace: pwTracePath && pwTraceHash ? {
598
+ path: pwTracePath,
599
+ hash: pwTraceHash
600
+ } : null
581
601
  };
582
602
  }));
583
603
  // Create build
584
604
  debug("Creating build");
605
+ const screenshotKeys = Array.from(new Set(screenshots.map((screenshot)=>screenshot.hash)));
606
+ const pwTraces = screenshotKeys.reduce((pwTraces, key)=>{
607
+ const screenshot = screenshots.find((screenshot)=>screenshot.hash === key);
608
+ if (!screenshot) {
609
+ throw new Error(`Invariant: screenshot with hash ${key} not found`);
610
+ }
611
+ if (screenshot.pwTrace) {
612
+ pwTraces.push({
613
+ screenshotKey: screenshot.hash,
614
+ traceKey: screenshot.pwTrace.hash
615
+ });
616
+ }
617
+ return pwTraces;
618
+ }, []);
585
619
  const result = await apiClient.createBuild({
586
620
  commit: config.commit,
587
621
  branch: config.branch,
588
622
  name: config.buildName,
589
623
  parallel: config.parallel,
590
624
  parallelNonce: config.parallelNonce,
591
- screenshotKeys: Array.from(new Set(screenshots.map((screenshot)=>screenshot.hash))),
625
+ screenshotKeys,
626
+ pwTraces,
592
627
  prNumber: config.prNumber,
593
628
  prHeadCommit: config.prHeadCommit,
594
629
  referenceBranch: config.referenceBranch,
@@ -599,18 +634,36 @@ const getConfigFromOptions = (options)=>{
599
634
  const chunks = chunk(result.screenshots, CHUNK_SIZE);
600
635
  debug(`Starting upload of ${chunks.length} chunks`);
601
636
  for(let i = 0; i < chunks.length; i++){
637
+ // Upload screenshots
602
638
  debug(`Uploading chunk ${i + 1}/${chunks.length}`);
603
639
  const timeLabel = `Chunk ${i + 1}/${chunks.length}`;
604
640
  debugTime(timeLabel);
605
- await Promise.all(chunks[i].map(async ({ key, putUrl })=>{
641
+ await Promise.all(chunks[i].map(async ({ key, putUrl, putTraceUrl })=>{
606
642
  const screenshot = screenshots.find((s)=>s.hash === key);
607
643
  if (!screenshot) {
608
644
  throw new Error(`Invariant: screenshot with hash ${key} not found`);
609
645
  }
610
- await upload$1({
611
- url: putUrl,
612
- path: screenshot.optimizedPath
613
- });
646
+ await Promise.all([
647
+ // Upload screenshot
648
+ upload$1({
649
+ url: putUrl,
650
+ path: screenshot.optimizedPath,
651
+ contentType: "image/png"
652
+ }),
653
+ // Upload trace
654
+ (async ()=>{
655
+ if (putTraceUrl) {
656
+ if (!screenshot.pwTrace) {
657
+ throw new Error(`Invariant: screenshot with hash ${key} has a putTraceUrl but no pwTrace`);
658
+ }
659
+ await upload$1({
660
+ url: putTraceUrl,
661
+ path: screenshot.pwTrace.path,
662
+ contentType: "application/zip"
663
+ });
664
+ }
665
+ })()
666
+ ]);
614
667
  }));
615
668
  debugTimeEnd(timeLabel);
616
669
  }
@@ -618,11 +671,15 @@ const getConfigFromOptions = (options)=>{
618
671
  debug("Updating build");
619
672
  await apiClient.updateBuild({
620
673
  buildId: result.build.id,
621
- screenshots: screenshots.map((screenshot)=>({
674
+ screenshots: screenshots.map((screenshot)=>{
675
+ var _screenshot_pwTrace;
676
+ return {
622
677
  key: screenshot.hash,
623
678
  name: screenshot.name,
624
- metadata: screenshot.metadata
625
- })),
679
+ metadata: screenshot.metadata,
680
+ pwTraceKey: ((_screenshot_pwTrace = screenshot.pwTrace) === null || _screenshot_pwTrace === void 0 ? void 0 : _screenshot_pwTrace.hash) ?? null
681
+ };
682
+ }),
626
683
  parallel: config.parallel,
627
684
  parallelTotal: config.parallelTotal
628
685
  });
@@ -632,4 +689,4 @@ const getConfigFromOptions = (options)=>{
632
689
  };
633
690
  };
634
691
 
635
- export { upload };
692
+ export { readConfig, upload };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@argos-ci/core",
3
3
  "description": "Visual testing solution to avoid visual regression. The core component of Argos SDK that handles build creation.",
4
- "version": "1.0.0",
4
+ "version": "1.2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@argos-ci/util": "1.0.0",
43
+ "@argos-ci/util": "1.1.0",
44
44
  "axios": "^1.5.0",
45
45
  "convict": "^6.2.4",
46
46
  "debug": "^4.3.4",
@@ -59,5 +59,5 @@
59
59
  "build": "rollup -c",
60
60
  "e2e": "node ./e2e/upload.cjs && node ./e2e/upload.mjs"
61
61
  },
62
- "gitHead": "f864cced9e48669efab2a35fde612570600b94a7"
62
+ "gitHead": "47a939474ca0c0d55ca5360dcaa5f8912547ed76"
63
63
  }