@argos-ci/core 5.1.2 → 5.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.js DELETED
@@ -1,1519 +0,0 @@
1
- // src/config.ts
2
- import convict from "convict";
3
-
4
- // src/ci-environment/git.ts
5
- import { execSync } from "child_process";
6
-
7
- // src/debug.ts
8
- import createDebug from "debug";
9
- var KEY = "@argos-ci/core";
10
- var debug = createDebug(KEY);
11
- var isDebugEnabled = createDebug.enabled(KEY);
12
- var debugTime = (arg) => {
13
- if (isDebugEnabled) {
14
- console.time(arg);
15
- }
16
- };
17
- var debugTimeEnd = (arg) => {
18
- if (isDebugEnabled) {
19
- console.timeEnd(arg);
20
- }
21
- };
22
-
23
- // src/ci-environment/git.ts
24
- function checkIsGitRepository() {
25
- try {
26
- return execSync("git rev-parse --is-inside-work-tree").toString().trim() === "true";
27
- } catch {
28
- return false;
29
- }
30
- }
31
- function head() {
32
- try {
33
- return execSync("git rev-parse HEAD").toString().trim();
34
- } catch {
35
- return null;
36
- }
37
- }
38
- function branch() {
39
- try {
40
- const headRef = execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
41
- if (headRef === "HEAD") {
42
- return null;
43
- }
44
- return headRef;
45
- } catch {
46
- return null;
47
- }
48
- }
49
- function getRepositoryURL() {
50
- try {
51
- const url = execSync("git config --get remote.origin.url").toString().trim();
52
- return url;
53
- } catch {
54
- return null;
55
- }
56
- }
57
- function gitMergeBase(input) {
58
- try {
59
- return execSync(`git merge-base ${input.head} ${input.base}`).toString().trim();
60
- } catch (error) {
61
- if (checkIsExecError(error) && error.status === 1 && error.stderr.toString() === "") {
62
- return null;
63
- }
64
- throw error;
65
- }
66
- }
67
- function gitFetch(input) {
68
- execSync(
69
- `git fetch --force --update-head-ok --depth ${input.depth} origin ${input.ref}:${input.target}`
70
- );
71
- }
72
- function checkIsExecError(error) {
73
- return error instanceof Error && "status" in error && typeof error.status === "number" && "stderr" in error && Buffer.isBuffer(error.stderr);
74
- }
75
- function getMergeBaseCommitSha(input) {
76
- let depth = 200;
77
- const argosBaseRef = `argos/${input.base}`;
78
- const argosHeadRef = `argos/${input.head}`;
79
- while (depth < 1e3) {
80
- gitFetch({ ref: input.head, depth, target: argosHeadRef });
81
- gitFetch({ ref: input.base, depth, target: argosBaseRef });
82
- const mergeBase = gitMergeBase({
83
- base: argosBaseRef,
84
- head: argosHeadRef
85
- });
86
- if (mergeBase) {
87
- return mergeBase;
88
- }
89
- depth += 200;
90
- }
91
- if (isDebugEnabled) {
92
- const headShas = listShas(argosHeadRef);
93
- const baseShas = listShas(argosBaseRef);
94
- debug(
95
- `No merge base found for ${input.head} and ${input.base} with depth ${depth}`
96
- );
97
- debug(
98
- `Found ${headShas.length} commits in ${input.head}: ${headShas.join(", ")}`
99
- );
100
- debug(
101
- `Found ${baseShas.length} commits in ${input.base}: ${baseShas.join(", ")}`
102
- );
103
- }
104
- return null;
105
- }
106
- function listShas(path, maxCount) {
107
- const maxCountArg = maxCount ? `--max-count=${maxCount}` : "";
108
- const raw = execSync(`git log --format="%H" ${maxCountArg} ${path}`.trim());
109
- const shas = raw.toString().trim().split("\n");
110
- return shas;
111
- }
112
- function listParentCommits(input) {
113
- const limit = 200;
114
- try {
115
- execSync(`git fetch --depth=${limit} origin ${input.sha}`);
116
- } catch (error) {
117
- if (error instanceof Error && error.message.includes("not our ref")) {
118
- return [];
119
- }
120
- }
121
- return listShas(input.sha, limit);
122
- }
123
-
124
- // src/ci-environment/services/bitrise.ts
125
- function getPrNumber(context) {
126
- const { env } = context;
127
- return env.BITRISE_PULL_REQUEST ? Number(env.BITRISE_PULL_REQUEST) : null;
128
- }
129
- function getRepository(context) {
130
- const { env } = context;
131
- if (env.BITRISEIO_GIT_REPOSITORY_OWNER && env.BITRISEIO_GIT_REPOSITORY_SLUG) {
132
- return `${env.BITRISEIO_GIT_REPOSITORY_OWNER}/${env.BITRISEIO_GIT_REPOSITORY_SLUG}`;
133
- }
134
- return null;
135
- }
136
- var service = {
137
- name: "Bitrise",
138
- key: "bitrise",
139
- detect: ({ env }) => Boolean(env.BITRISE_IO),
140
- config: (context) => {
141
- const { env } = context;
142
- const repository = getRepository(context);
143
- return {
144
- commit: env.BITRISE_GIT_COMMIT || null,
145
- branch: env.BITRISE_GIT_BRANCH || null,
146
- repository,
147
- originalRepository: repository,
148
- jobId: null,
149
- runId: null,
150
- runAttempt: null,
151
- prNumber: getPrNumber({ env }),
152
- prHeadCommit: null,
153
- prBaseBranch: null,
154
- nonce: env.BITRISEIO_PIPELINE_ID || null,
155
- mergeQueue: false
156
- };
157
- },
158
- getMergeBaseCommitSha,
159
- listParentCommits
160
- };
161
- var bitrise_default = service;
162
-
163
- // src/util/url.ts
164
- function getRepositoryNameFromURL(url) {
165
- const sshMatch = url.match(/^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
166
- if (sshMatch && sshMatch[1] && sshMatch[2]) {
167
- return `${sshMatch[1]}/${sshMatch[2]}`;
168
- }
169
- const httpsMatch = url.match(
170
- /^(?:https?|git):\/\/[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/
171
- );
172
- if (httpsMatch && httpsMatch[1] && httpsMatch[2]) {
173
- return `${httpsMatch[1]}/${httpsMatch[2]}`;
174
- }
175
- return null;
176
- }
177
-
178
- // src/ci-environment/services/buildkite.ts
179
- function getRepository2(context) {
180
- const { env } = context;
181
- if (env.BUILDKITE_REPO) {
182
- return getRepositoryNameFromURL(env.BUILDKITE_REPO);
183
- }
184
- return null;
185
- }
186
- var service2 = {
187
- name: "Buildkite",
188
- key: "buildkite",
189
- detect: ({ env }) => Boolean(env.BUILDKITE),
190
- config: (context) => {
191
- const { env } = context;
192
- const repository = getRepository2(context);
193
- return {
194
- // Buildkite doesn't work well so we fallback to git to ensure we have commit and branch
195
- commit: env.BUILDKITE_COMMIT || head() || null,
196
- branch: env.BUILDKITE_BRANCH || branch() || null,
197
- repository,
198
- originalRepository: repository,
199
- jobId: null,
200
- runId: null,
201
- runAttempt: null,
202
- prNumber: env.BUILDKITE_PULL_REQUEST ? Number(env.BUILDKITE_PULL_REQUEST) : null,
203
- prHeadCommit: null,
204
- prBaseBranch: null,
205
- nonce: env.BUILDKITE_BUILD_ID || null,
206
- mergeQueue: false
207
- };
208
- },
209
- getMergeBaseCommitSha,
210
- listParentCommits
211
- };
212
- var buildkite_default = service2;
213
-
214
- // src/ci-environment/services/heroku.ts
215
- var service3 = {
216
- name: "Heroku",
217
- key: "heroku",
218
- detect: ({ env }) => Boolean(env.HEROKU_TEST_RUN_ID),
219
- config: ({ env }) => ({
220
- commit: env.HEROKU_TEST_RUN_COMMIT_VERSION || null,
221
- branch: env.HEROKU_TEST_RUN_BRANCH || null,
222
- owner: null,
223
- repository: null,
224
- originalRepository: null,
225
- jobId: null,
226
- runId: null,
227
- runAttempt: null,
228
- prNumber: null,
229
- prHeadCommit: null,
230
- prBaseBranch: null,
231
- nonce: env.HEROKU_TEST_RUN_ID || null,
232
- mergeQueue: false
233
- }),
234
- getMergeBaseCommitSha,
235
- listParentCommits
236
- };
237
- var heroku_default = service3;
238
-
239
- // src/ci-environment/services/github-actions.ts
240
- import { existsSync, readFileSync } from "fs";
241
-
242
- // src/ci-environment/github.ts
243
- function getGitHubRepository(ctx) {
244
- return ctx.env.GITHUB_REPOSITORY || null;
245
- }
246
- function assertGitHubRepository(ctx) {
247
- const repo = getGitHubRepository(ctx);
248
- if (!repo) {
249
- throw new Error("GITHUB_REPOSITORY is missing");
250
- }
251
- return repo;
252
- }
253
- function getGitHubToken({ env }) {
254
- if (!env.GITHUB_TOKEN) {
255
- if (!env.DISABLE_GITHUB_TOKEN_WARNING) {
256
- console.log(
257
- `
258
- Argos couldn\u2019t find a relevant pull request in the current environment.
259
- To resolve this, Argos requires a GITHUB_TOKEN to fetch the pull request associated with the head SHA. Please ensure the following environment variable is added:
260
-
261
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
262
-
263
- For more details, check out the documentation: Read more at https://argos-ci.com/docs/run-on-preview-deployment
264
-
265
- If you want to disable this warning, you can set the following environment variable:
266
-
267
- DISABLE_GITHUB_TOKEN_WARNING: true
268
- `.trim()
269
- );
270
- }
271
- return null;
272
- }
273
- return env.GITHUB_TOKEN;
274
- }
275
- async function fetchGitHubAPI(ctx, url) {
276
- const githubToken = getGitHubToken(ctx);
277
- if (!githubToken) {
278
- return null;
279
- }
280
- const response = await fetch(url, {
281
- headers: {
282
- Accept: "application/vnd.github+json",
283
- Authorization: `Bearer ${githubToken}`,
284
- "X-GitHub-Api-Version": "2022-11-28"
285
- },
286
- signal: AbortSignal.timeout(1e4)
287
- });
288
- return response;
289
- }
290
- var GITHUB_API_BASE_URL = "https://api.github.com";
291
- async function getPullRequestFromHeadSha(ctx, sha) {
292
- debug(`Fetching pull request details from head sha: ${sha}`);
293
- const githubRepository = assertGitHubRepository(ctx);
294
- const url = new URL(`/repos/${githubRepository}/pulls`, GITHUB_API_BASE_URL);
295
- url.search = new URLSearchParams({
296
- state: "open",
297
- sort: "updated",
298
- per_page: "30",
299
- page: "1"
300
- }).toString();
301
- const response = await fetchGitHubAPI(ctx, url);
302
- if (!response) {
303
- return null;
304
- }
305
- if (!response.ok) {
306
- throw new Error(
307
- `Non-OK response (status: ${response.status}) while fetching pull request details from head sha (${sha})`
308
- );
309
- }
310
- const result = await response.json();
311
- if (result.length === 0) {
312
- debug("No results, no pull request found");
313
- return null;
314
- }
315
- const matchingPr = result.find((pr) => pr.head.sha === sha);
316
- if (matchingPr) {
317
- debug("Pull request found", matchingPr);
318
- return matchingPr;
319
- }
320
- debug("No matching pull request found");
321
- return null;
322
- }
323
- async function getPullRequestFromPrNumber(ctx, prNumber) {
324
- debug(`Fetching pull request #${prNumber}`);
325
- const githubRepository = assertGitHubRepository(ctx);
326
- const response = await fetchGitHubAPI(
327
- ctx,
328
- new URL(
329
- `/repos/${githubRepository}/pulls/${prNumber}`,
330
- GITHUB_API_BASE_URL
331
- )
332
- );
333
- if (!response) {
334
- return null;
335
- }
336
- if (response.status === 404) {
337
- debug(
338
- "No pull request found, pr detection from branch was probably a mistake"
339
- );
340
- return null;
341
- }
342
- if (!response.ok) {
343
- throw new Error(
344
- `Non-OK response (status: ${response.status}) while fetching pull request #${prNumber}`
345
- );
346
- }
347
- const result = await response.json();
348
- return result;
349
- }
350
- function getPRNumberFromMergeGroupBranch(branch2) {
351
- const prMatch = /queue\/[^/]*\/pr-(\d+)-/.exec(branch2);
352
- if (prMatch) {
353
- const prNumber = Number(prMatch[1]);
354
- return prNumber;
355
- }
356
- return null;
357
- }
358
-
359
- // src/ci-environment/services/github-actions.ts
360
- function readEventPayload({ env }) {
361
- if (!env.GITHUB_EVENT_PATH) {
362
- return null;
363
- }
364
- if (!existsSync(env.GITHUB_EVENT_PATH)) {
365
- return null;
366
- }
367
- return JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, "utf-8"));
368
- }
369
- function getVercelDeploymentPayload(payload) {
370
- if (process.env.GITHUB_EVENT_NAME === "repository_dispatch" && payload && "action" in payload && payload.action === "vercel.deployment.success") {
371
- return payload;
372
- }
373
- return null;
374
- }
375
- function getMergeGroupPayload(payload) {
376
- if (payload && process.env.GITHUB_EVENT_NAME === "merge_group" && "action" in payload && payload.action === "checks_requested") {
377
- return payload;
378
- }
379
- return null;
380
- }
381
- function getBranchFromContext(context) {
382
- const { env } = context;
383
- if (env.GITHUB_HEAD_REF) {
384
- return env.GITHUB_HEAD_REF;
385
- }
386
- if (!env.GITHUB_REF) {
387
- return null;
388
- }
389
- const branchRegex = /refs\/heads\/(.*)/;
390
- const matches = branchRegex.exec(env.GITHUB_REF);
391
- return matches?.[1] ?? null;
392
- }
393
- function getBranchFromPayload(payload) {
394
- if ("workflow_run" in payload && payload.workflow_run) {
395
- return payload.workflow_run.head_branch;
396
- }
397
- if ("deployment" in payload && payload.deployment) {
398
- return payload.deployment.environment;
399
- }
400
- return null;
401
- }
402
- function getBranch(args) {
403
- const { payload, mergeGroupPayload, vercelPayload, pullRequest, context } = args;
404
- if (mergeGroupPayload && pullRequest?.head.ref) {
405
- return pullRequest.head.ref;
406
- }
407
- if (vercelPayload) {
408
- return vercelPayload.client_payload.git.ref;
409
- }
410
- if (payload) {
411
- const fromPayload = getBranchFromPayload(payload);
412
- if (fromPayload) {
413
- return fromPayload;
414
- }
415
- }
416
- const fromContext = getBranchFromContext(context);
417
- if (fromContext) {
418
- return fromContext;
419
- }
420
- if (pullRequest) {
421
- return pullRequest.head.ref;
422
- }
423
- return null;
424
- }
425
- function getRepository3(context, payload) {
426
- if (payload && "pull_request" in payload && payload.pull_request) {
427
- const pr = payload.pull_request;
428
- if (pr.head && pr.head.repo && pr.head.repo.full_name) {
429
- return pr.head.repo.full_name;
430
- }
431
- }
432
- return getGitHubRepository(context);
433
- }
434
- function getSha(context, vercelPayload, payload) {
435
- if (context.env.GITHUB_EVENT_NAME === "pull_request_target") {
436
- if (!payload) {
437
- throw new Error('Payload is missing in "pull_request_target" event');
438
- }
439
- const pullRequest = getPullRequestFromPayload(payload);
440
- if (!pullRequest) {
441
- throw new Error('Pull request missing in "pull_request_target" event');
442
- }
443
- return pullRequest.head.sha;
444
- }
445
- if (vercelPayload) {
446
- return vercelPayload.client_payload.git.sha;
447
- }
448
- if (!context.env.GITHUB_SHA) {
449
- throw new Error("GITHUB_SHA is missing");
450
- }
451
- return context.env.GITHUB_SHA;
452
- }
453
- function getPullRequestFromPayload(payload) {
454
- if ("pull_request" in payload && payload.pull_request && payload.pull_request) {
455
- return payload.pull_request;
456
- }
457
- if ("workflow_run" in payload && payload.workflow_run && payload.workflow_run.pull_requests[0]) {
458
- return payload.workflow_run.pull_requests[0];
459
- }
460
- if ("check_run" in payload && payload.check_run && "pull_requests" in payload.check_run && payload.check_run.pull_requests[0]) {
461
- return payload.check_run.pull_requests[0];
462
- }
463
- return null;
464
- }
465
- async function getPullRequest(args) {
466
- const { payload, vercelPayload, mergeGroupPayload, context, sha } = args;
467
- if (vercelPayload || !payload) {
468
- return getPullRequestFromHeadSha(context, sha);
469
- }
470
- if (mergeGroupPayload) {
471
- const prNumber = getPRNumberFromMergeGroupBranch(
472
- mergeGroupPayload.merge_group.head_ref
473
- );
474
- if (!prNumber) {
475
- debug(
476
- `No PR found from merge group head ref: ${mergeGroupPayload.merge_group.head_ref}`
477
- );
478
- return null;
479
- }
480
- debug(
481
- `PR #${prNumber} found from merge group head ref (${mergeGroupPayload.merge_group.head_ref})`
482
- );
483
- return getPullRequestFromPrNumber(context, prNumber);
484
- }
485
- return getPullRequestFromPayload(payload);
486
- }
487
- var service4 = {
488
- name: "GitHub Actions",
489
- key: "github-actions",
490
- detect: (context) => Boolean(context.env.GITHUB_ACTIONS),
491
- config: async (context) => {
492
- const { env } = context;
493
- const payload = readEventPayload(context);
494
- const vercelPayload = getVercelDeploymentPayload(payload);
495
- const mergeGroupPayload = getMergeGroupPayload(payload);
496
- const sha = getSha(context, vercelPayload, payload);
497
- const pullRequest = await getPullRequest({
498
- payload,
499
- vercelPayload,
500
- mergeGroupPayload,
501
- sha,
502
- context
503
- });
504
- const branch2 = getBranch({
505
- payload,
506
- vercelPayload,
507
- mergeGroupPayload,
508
- context,
509
- pullRequest
510
- });
511
- return {
512
- commit: sha,
513
- repository: getRepository3(context, payload),
514
- originalRepository: getGitHubRepository(context),
515
- jobId: env.GITHUB_JOB || null,
516
- runId: env.GITHUB_RUN_ID || null,
517
- runAttempt: env.GITHUB_RUN_ATTEMPT ? Number(env.GITHUB_RUN_ATTEMPT) : null,
518
- nonce: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}`,
519
- branch: branch2,
520
- prNumber: pullRequest?.number || null,
521
- prHeadCommit: pullRequest?.head.sha ?? null,
522
- prBaseBranch: pullRequest?.base.ref ?? null,
523
- mergeQueue: Boolean(mergeGroupPayload)
524
- };
525
- },
526
- getMergeBaseCommitSha,
527
- listParentCommits
528
- };
529
- var github_actions_default = service4;
530
-
531
- // src/ci-environment/services/circleci.ts
532
- function getPrNumber2(context) {
533
- const { env } = context;
534
- const matches = /pull\/(\d+)/.exec(env.CIRCLE_PULL_REQUEST || "");
535
- if (matches) {
536
- return Number(matches[1]);
537
- }
538
- return null;
539
- }
540
- function getRepository4(context) {
541
- const { env } = context;
542
- if (env.CIRCLE_PR_REPONAME && env.CIRCLE_PR_USERNAME) {
543
- return `${env.CIRCLE_PR_USERNAME}/${env.CIRCLE_PR_REPONAME}`;
544
- }
545
- return getOriginalRepository(context);
546
- }
547
- function getOriginalRepository(context) {
548
- const { env } = context;
549
- if (env.CIRCLE_PROJECT_USERNAME && env.CIRCLE_PROJECT_REPONAME) {
550
- return `${env.CIRCLE_PROJECT_USERNAME}/${env.CIRCLE_PROJECT_REPONAME}`;
551
- }
552
- return null;
553
- }
554
- var service5 = {
555
- name: "CircleCI",
556
- key: "circleci",
557
- detect: ({ env }) => Boolean(env.CIRCLECI),
558
- config: (context) => {
559
- const { env } = context;
560
- return {
561
- commit: env.CIRCLE_SHA1 || null,
562
- branch: env.CIRCLE_BRANCH || null,
563
- repository: getRepository4(context),
564
- originalRepository: getOriginalRepository(context),
565
- jobId: null,
566
- runId: null,
567
- runAttempt: null,
568
- prNumber: getPrNumber2({ env }),
569
- prHeadCommit: null,
570
- prBaseBranch: null,
571
- nonce: env.CIRCLE_WORKFLOW_ID || env.CIRCLE_BUILD_NUM || null,
572
- mergeQueue: false
573
- };
574
- },
575
- getMergeBaseCommitSha,
576
- listParentCommits
577
- };
578
- var circleci_default = service5;
579
-
580
- // src/ci-environment/services/travis.ts
581
- function getRepository5(context) {
582
- const { env } = context;
583
- if (env.TRAVIS_PULL_REQUEST_SLUG) {
584
- return env.TRAVIS_PULL_REQUEST_SLUG;
585
- }
586
- return getOriginalRepository2(context);
587
- }
588
- function getOriginalRepository2(context) {
589
- const { env } = context;
590
- return env.TRAVIS_REPO_SLUG || null;
591
- }
592
- function getPrNumber3(context) {
593
- const { env } = context;
594
- if (env.TRAVIS_PULL_REQUEST) {
595
- return Number(env.TRAVIS_PULL_REQUEST);
596
- }
597
- return null;
598
- }
599
- var service6 = {
600
- name: "Travis CI",
601
- key: "travis",
602
- detect: ({ env }) => Boolean(env.TRAVIS),
603
- config: (ctx) => {
604
- const { env } = ctx;
605
- return {
606
- commit: env.TRAVIS_COMMIT || null,
607
- branch: env.TRAVIS_BRANCH || null,
608
- repository: getRepository5(ctx),
609
- originalRepository: getOriginalRepository2(ctx),
610
- jobId: null,
611
- runId: null,
612
- runAttempt: null,
613
- prNumber: getPrNumber3(ctx),
614
- prHeadCommit: null,
615
- prBaseBranch: null,
616
- nonce: env.TRAVIS_BUILD_ID || null,
617
- mergeQueue: false
618
- };
619
- },
620
- getMergeBaseCommitSha,
621
- listParentCommits
622
- };
623
- var travis_default = service6;
624
-
625
- // src/ci-environment/services/gitlab.ts
626
- function getRepository6(context) {
627
- const { env } = context;
628
- if (env.CI_MERGE_REQUEST_PROJECT_PATH) {
629
- return env.CI_MERGE_REQUEST_PROJECT_PATH;
630
- }
631
- return getOriginalRepository3(context);
632
- }
633
- function getOriginalRepository3(context) {
634
- const { env } = context;
635
- return env.CI_PROJECT_PATH || null;
636
- }
637
- var service7 = {
638
- name: "GitLab",
639
- key: "gitlab",
640
- detect: ({ env }) => env.GITLAB_CI === "true",
641
- config: (context) => {
642
- const { env } = context;
643
- return {
644
- commit: env.CI_COMMIT_SHA || null,
645
- branch: env.CI_COMMIT_REF_NAME || null,
646
- repository: getRepository6(context),
647
- originalRepository: getOriginalRepository3(context),
648
- jobId: null,
649
- runId: null,
650
- runAttempt: null,
651
- prNumber: null,
652
- prHeadCommit: null,
653
- prBaseBranch: null,
654
- nonce: env.CI_PIPELINE_ID || null,
655
- mergeQueue: false
656
- };
657
- },
658
- getMergeBaseCommitSha,
659
- listParentCommits
660
- };
661
- var gitlab_default = service7;
662
-
663
- // src/ci-environment/services/git.ts
664
- function getRepository7() {
665
- const repositoryURL = getRepositoryURL();
666
- if (!repositoryURL) {
667
- return null;
668
- }
669
- return getRepositoryNameFromURL(repositoryURL);
670
- }
671
- var service8 = {
672
- name: "Git",
673
- key: "git",
674
- detect: () => checkIsGitRepository(),
675
- config: () => {
676
- const repository = getRepository7();
677
- return {
678
- commit: head() || null,
679
- branch: branch() || null,
680
- repository,
681
- originalRepository: repository,
682
- jobId: null,
683
- runId: null,
684
- runAttempt: null,
685
- prNumber: null,
686
- prHeadCommit: null,
687
- prBaseBranch: null,
688
- nonce: null,
689
- mergeQueue: false
690
- };
691
- },
692
- getMergeBaseCommitSha,
693
- listParentCommits
694
- };
695
- var git_default = service8;
696
-
697
- // src/ci-environment/index.ts
698
- var services = [
699
- heroku_default,
700
- github_actions_default,
701
- circleci_default,
702
- travis_default,
703
- buildkite_default,
704
- gitlab_default,
705
- bitrise_default,
706
- git_default
707
- ];
708
- function createContext() {
709
- return { env: process.env };
710
- }
711
- function getCiService(context) {
712
- return services.find((service9) => service9.detect(context));
713
- }
714
- function getMergeBaseCommitSha2(input) {
715
- const context = createContext();
716
- const service9 = getCiService(context);
717
- if (!service9) {
718
- return null;
719
- }
720
- return service9.getMergeBaseCommitSha(input, context);
721
- }
722
- function listParentCommits2(input) {
723
- const context = createContext();
724
- const service9 = getCiService(context);
725
- if (!service9) {
726
- return null;
727
- }
728
- return service9.listParentCommits(input, context);
729
- }
730
- async function getCiEnvironment() {
731
- const context = createContext();
732
- debug("Detecting CI environment", context);
733
- const service9 = getCiService(context);
734
- if (service9) {
735
- debug("Internal service matched", service9.name);
736
- const variables = await service9.config(context);
737
- const ciEnvironment = {
738
- name: service9.name,
739
- key: service9.key,
740
- ...variables
741
- };
742
- debug("CI environment", ciEnvironment);
743
- return ciEnvironment;
744
- }
745
- return null;
746
- }
747
-
748
- // src/config.ts
749
- var mustBeApiBaseUrl = (value) => {
750
- const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
751
- if (!URL_REGEX.test(value)) {
752
- throw new Error("Invalid Argos API base URL");
753
- }
754
- };
755
- var mustBeCommit = (value) => {
756
- const SHA1_REGEX = /^[0-9a-f]{40}$/;
757
- if (!SHA1_REGEX.test(value)) {
758
- const SHA1_SHORT_REGEX = /^[0-9a-f]{7}$/;
759
- if (SHA1_SHORT_REGEX.test(value)) {
760
- throw new Error("Short SHA1 is not allowed");
761
- }
762
- throw new Error("Invalid commit");
763
- }
764
- };
765
- var mustBeArgosToken = (value) => {
766
- if (value && value.length !== 40) {
767
- throw new Error("Invalid Argos repository token (must be 40 characters)");
768
- }
769
- };
770
- var minInteger = (min) => (value) => {
771
- if (!Number.isInteger(value)) {
772
- throw new Error("must be an integer");
773
- }
774
- if (value < min) {
775
- throw new Error(`must be at least ${min}`);
776
- }
777
- };
778
- var toInt = (value) => {
779
- if (value === "") {
780
- return null;
781
- }
782
- const num = Number(value);
783
- if (!Number.isInteger(num) || Number.isNaN(num)) {
784
- return num;
785
- }
786
- return num;
787
- };
788
- var toFloat = (value) => parseFloat(value);
789
- convict.addFormat({
790
- name: "parallel-total",
791
- validate: minInteger(-1),
792
- coerce: toInt
793
- });
794
- convict.addFormat({
795
- name: "parallel-index",
796
- validate: minInteger(1),
797
- coerce: toInt
798
- });
799
- convict.addFormat({
800
- name: "float-percent",
801
- validate: (val) => {
802
- if (val !== 0 && (!val || val > 1 || val < 0)) {
803
- throw new Error("Must be a float between 0 and 1, inclusive.");
804
- }
805
- },
806
- coerce: toFloat
807
- });
808
- var schema = {
809
- apiBaseUrl: {
810
- env: "ARGOS_API_BASE_URL",
811
- default: "https://api.argos-ci.com/v2/",
812
- format: mustBeApiBaseUrl
813
- },
814
- commit: {
815
- env: "ARGOS_COMMIT",
816
- default: null,
817
- format: mustBeCommit
818
- },
819
- branch: {
820
- env: "ARGOS_BRANCH",
821
- default: null,
822
- format: String
823
- },
824
- token: {
825
- env: "ARGOS_TOKEN",
826
- default: null,
827
- format: mustBeArgosToken
828
- },
829
- buildName: {
830
- env: "ARGOS_BUILD_NAME",
831
- default: null,
832
- format: String,
833
- nullable: true
834
- },
835
- mode: {
836
- env: "ARGOS_MODE",
837
- format: ["ci", "monitoring"],
838
- default: null,
839
- nullable: true
840
- },
841
- prNumber: {
842
- env: "ARGOS_PR_NUMBER",
843
- format: Number,
844
- default: null,
845
- nullable: true
846
- },
847
- prHeadCommit: {
848
- env: "ARGOS_PR_HEAD_COMMIT",
849
- format: String,
850
- default: null,
851
- nullable: true
852
- },
853
- prBaseBranch: {
854
- env: "ARGOS_PR_BASE_BRANCH",
855
- format: String,
856
- default: null,
857
- nullable: true
858
- },
859
- parallel: {
860
- env: "ARGOS_PARALLEL",
861
- default: false,
862
- format: Boolean
863
- },
864
- parallelNonce: {
865
- env: "ARGOS_PARALLEL_NONCE",
866
- format: String,
867
- default: null,
868
- nullable: true
869
- },
870
- parallelIndex: {
871
- env: "ARGOS_PARALLEL_INDEX",
872
- format: "parallel-index",
873
- default: null,
874
- nullable: true
875
- },
876
- parallelTotal: {
877
- env: "ARGOS_PARALLEL_TOTAL",
878
- format: "parallel-total",
879
- default: null,
880
- nullable: true
881
- },
882
- referenceBranch: {
883
- env: "ARGOS_REFERENCE_BRANCH",
884
- format: String,
885
- default: null,
886
- nullable: true
887
- },
888
- referenceCommit: {
889
- env: "ARGOS_REFERENCE_COMMIT",
890
- format: String,
891
- default: null,
892
- nullable: true
893
- },
894
- jobId: {
895
- format: String,
896
- default: null,
897
- nullable: true
898
- },
899
- runId: {
900
- format: String,
901
- default: null,
902
- nullable: true
903
- },
904
- runAttempt: {
905
- format: "nat",
906
- default: null,
907
- nullable: true
908
- },
909
- repository: {
910
- format: String,
911
- default: null,
912
- nullable: true
913
- },
914
- originalRepository: {
915
- format: String,
916
- default: null,
917
- nullable: true
918
- },
919
- ciProvider: {
920
- format: String,
921
- default: null,
922
- nullable: true
923
- },
924
- threshold: {
925
- env: "ARGOS_THRESHOLD",
926
- format: "float-percent",
927
- default: null,
928
- nullable: true
929
- },
930
- previewBaseUrl: {
931
- env: "ARGOS_PREVIEW_BASE_URL",
932
- format: String,
933
- default: null,
934
- nullable: true
935
- },
936
- skipped: {
937
- env: "ARGOS_SKIPPED",
938
- format: Boolean,
939
- default: false
940
- },
941
- mergeQueue: {
942
- format: Boolean,
943
- default: false
944
- },
945
- subset: {
946
- env: "ARGOS_SUBSET",
947
- format: Boolean,
948
- default: false
949
- }
950
- };
951
- function createConfig() {
952
- return convict(schema, { args: [], env: {} });
953
- }
954
- function getDefaultConfig() {
955
- return Object.entries(schema).reduce(
956
- (cfg, [key, entry]) => {
957
- cfg[key] = "env" in entry && entry.env && process.env[entry.env] ? process.env[entry.env] : entry.default;
958
- return cfg;
959
- },
960
- {}
961
- );
962
- }
963
- async function readConfig(options = {}) {
964
- const config = createConfig();
965
- const ciEnv = await getCiEnvironment();
966
- const defaultConfig = getDefaultConfig();
967
- config.load({
968
- apiBaseUrl: options.apiBaseUrl || defaultConfig.apiBaseUrl,
969
- commit: options.commit || defaultConfig.commit || ciEnv?.commit || null,
970
- branch: options.branch || defaultConfig.branch || ciEnv?.branch || null,
971
- token: options.token || defaultConfig.token || null,
972
- buildName: options.buildName || defaultConfig.buildName || null,
973
- prNumber: options.prNumber || defaultConfig.prNumber || ciEnv?.prNumber || null,
974
- prHeadCommit: defaultConfig.prHeadCommit || ciEnv?.prHeadCommit || null,
975
- prBaseBranch: defaultConfig.prBaseBranch || ciEnv?.prBaseBranch || null,
976
- referenceBranch: options.referenceBranch || defaultConfig.referenceBranch || null,
977
- referenceCommit: options.referenceCommit || defaultConfig.referenceCommit || null,
978
- repository: ciEnv?.repository || null,
979
- originalRepository: ciEnv?.originalRepository || null,
980
- jobId: ciEnv?.jobId || null,
981
- runId: ciEnv?.runId || null,
982
- runAttempt: ciEnv?.runAttempt || null,
983
- parallel: options.parallel ?? defaultConfig.parallel ?? false,
984
- parallelNonce: options.parallelNonce || defaultConfig.parallelNonce || ciEnv?.nonce || null,
985
- parallelTotal: options.parallelTotal ?? defaultConfig.parallelTotal ?? null,
986
- parallelIndex: options.parallelIndex ?? defaultConfig.parallelIndex ?? null,
987
- mode: options.mode || defaultConfig.mode || null,
988
- ciProvider: ciEnv?.key || null,
989
- previewBaseUrl: defaultConfig.previewBaseUrl || null,
990
- skipped: options.skipped ?? defaultConfig.skipped ?? false,
991
- subset: options.subset ?? defaultConfig.subset ?? false,
992
- mergeQueue: ciEnv?.mergeQueue ?? false
993
- });
994
- if (!config.get("branch") || !config.get("commit")) {
995
- throw new Error(
996
- "Argos requires a branch and a commit to be set. If you are running in a non-git environment consider setting ARGOS_BRANCH and ARGOS_COMMIT environment variables."
997
- );
998
- }
999
- config.validate();
1000
- return config.get();
1001
- }
1002
- async function getConfigFromOptions({
1003
- parallel,
1004
- ...options
1005
- }) {
1006
- return readConfig({
1007
- ...options,
1008
- parallel: parallel !== void 0 ? Boolean(parallel) : void 0,
1009
- parallelNonce: parallel ? parallel.nonce : void 0,
1010
- parallelTotal: parallel ? parallel.total : void 0,
1011
- parallelIndex: parallel ? parallel.index : void 0
1012
- });
1013
- }
1014
-
1015
- // src/finalize.ts
1016
- import { createClient, throwAPIError } from "@argos-ci/api-client";
1017
-
1018
- // src/auth.ts
1019
- var base64Encode = (obj) => Buffer.from(JSON.stringify(obj), "utf8").toString("base64");
1020
- function getAuthToken(args) {
1021
- const {
1022
- token,
1023
- ciProvider,
1024
- originalRepository: repository,
1025
- jobId,
1026
- runId,
1027
- prNumber
1028
- } = args;
1029
- if (token) {
1030
- return token;
1031
- }
1032
- switch (ciProvider) {
1033
- case "github-actions": {
1034
- if (!repository || !jobId || !runId) {
1035
- throw new Error(
1036
- `Automatic GitHub Actions variables detection failed. Please add the 'ARGOS_TOKEN'`
1037
- );
1038
- }
1039
- const [owner, repo] = repository.split("/");
1040
- return `tokenless-github-${base64Encode({
1041
- owner,
1042
- repository: repo,
1043
- jobId,
1044
- runId,
1045
- prNumber: prNumber ?? void 0
1046
- })}`;
1047
- }
1048
- default:
1049
- throw new Error("Missing Argos repository token 'ARGOS_TOKEN'");
1050
- }
1051
- }
1052
-
1053
- // src/finalize.ts
1054
- async function finalize(params) {
1055
- const config = await readConfig({
1056
- parallelNonce: params.parallel?.nonce
1057
- });
1058
- const authToken = getAuthToken(config);
1059
- const apiClient = createClient({
1060
- baseUrl: config.apiBaseUrl,
1061
- authToken
1062
- });
1063
- if (!config.parallelNonce) {
1064
- throw new Error("parallel.nonce is required to finalize the build");
1065
- }
1066
- const finalizeBuildsResult = await apiClient.POST("/builds/finalize", {
1067
- body: {
1068
- parallelNonce: config.parallelNonce
1069
- }
1070
- });
1071
- if (finalizeBuildsResult.error) {
1072
- throwAPIError(finalizeBuildsResult.error);
1073
- }
1074
- return finalizeBuildsResult.data;
1075
- }
1076
-
1077
- // src/upload.ts
1078
- import { createClient as createClient3, throwAPIError as throwAPIError3 } from "@argos-ci/api-client";
1079
-
1080
- // src/discovery.ts
1081
- import { extname, resolve } from "path";
1082
- import glob from "fast-glob";
1083
- async function discoverSnapshots(patterns, { root = process.cwd(), ignore } = {}) {
1084
- debug(
1085
- `Discovering snapshots with patterns: ${Array.isArray(patterns) ? patterns.join(", ") : patterns} in ${root}`
1086
- );
1087
- const matches = await glob(patterns, { onlyFiles: true, ignore, cwd: root });
1088
- return matches.map((match) => {
1089
- debug(`Found screenshot: ${match}`);
1090
- const path = resolve(root, match);
1091
- return {
1092
- name: match,
1093
- path
1094
- };
1095
- });
1096
- }
1097
- function checkIsValidImageFile(filename) {
1098
- const lowerFilename = extname(filename).toLowerCase();
1099
- return lowerFilename === ".png" || lowerFilename === ".jpg" || lowerFilename === ".jpeg";
1100
- }
1101
-
1102
- // src/optimize.ts
1103
- import { promisify } from "util";
1104
- import { basename } from "path";
1105
- import sharp from "sharp";
1106
- import tmp from "tmp";
1107
- var tmpFile = promisify(tmp.file);
1108
- var MAX_PIXELS = 8e7;
1109
- var DEFAULT_MAX_WIDTH = 2048;
1110
- async function optimizeScreenshot(filepath) {
1111
- if (!checkIsValidImageFile(filepath)) {
1112
- return filepath;
1113
- }
1114
- try {
1115
- const [resultFilePath, metadata] = await Promise.all([
1116
- tmpFile(),
1117
- sharp(filepath).metadata()
1118
- ]);
1119
- const { width, height } = metadata;
1120
- const maxDimensions = (() => {
1121
- if (!width || !height) {
1122
- return {
1123
- width: DEFAULT_MAX_WIDTH,
1124
- height: Math.floor(MAX_PIXELS / DEFAULT_MAX_WIDTH)
1125
- };
1126
- }
1127
- const nbPixels = width * height;
1128
- if (nbPixels <= MAX_PIXELS) {
1129
- return null;
1130
- }
1131
- if (width < height) {
1132
- return {
1133
- width: DEFAULT_MAX_WIDTH,
1134
- height: Math.floor(MAX_PIXELS / DEFAULT_MAX_WIDTH)
1135
- };
1136
- }
1137
- const scaleFactor = Math.sqrt(MAX_PIXELS / nbPixels);
1138
- return {
1139
- width: Math.floor(width * scaleFactor),
1140
- height: Math.floor(height * scaleFactor)
1141
- };
1142
- })();
1143
- let operation = sharp(filepath);
1144
- if (maxDimensions) {
1145
- operation = operation.resize(maxDimensions.width, maxDimensions.height, {
1146
- fit: "inside",
1147
- withoutEnlargement: true
1148
- });
1149
- }
1150
- await operation.png({ force: true }).toFile(resultFilePath);
1151
- if (width && height && maxDimensions) {
1152
- const { width: maxWidth, height: maxHeight } = maxDimensions;
1153
- const widthRatio = maxWidth / width;
1154
- const heightRatio = maxHeight / height;
1155
- const scaleFactor = Math.min(widthRatio, heightRatio);
1156
- const newWidth = Math.floor(width * scaleFactor);
1157
- const newHeight = Math.floor(height * scaleFactor);
1158
- console.warn(
1159
- `Image ${basename(filepath)} resized from ${width}x${height} to ${newWidth}x${newHeight}.`
1160
- );
1161
- }
1162
- return resultFilePath;
1163
- } catch (error) {
1164
- const message = error instanceof Error ? error.message : "Unknown Error";
1165
- throw new Error(`Error while processing image (${filepath}): ${message}`, {
1166
- cause: error
1167
- });
1168
- }
1169
- }
1170
-
1171
- // src/hashing.ts
1172
- import { createReadStream } from "fs";
1173
- import { createHash } from "crypto";
1174
- var hashFile = async (filepath) => {
1175
- const fileStream = createReadStream(filepath);
1176
- const hash = createHash("sha256");
1177
- await new Promise((resolve2, reject) => {
1178
- fileStream.on("error", reject);
1179
- hash.on("error", reject);
1180
- hash.on("finish", resolve2);
1181
- fileStream.pipe(hash);
1182
- });
1183
- return hash.digest("hex");
1184
- };
1185
-
1186
- // src/s3.ts
1187
- import { readFile } from "fs/promises";
1188
- async function uploadFile(input) {
1189
- const file = await readFile(input.path);
1190
- const response = await fetch(input.url, {
1191
- method: "PUT",
1192
- headers: {
1193
- "Content-Type": input.contentType,
1194
- "Content-Length": file.length.toString()
1195
- },
1196
- signal: AbortSignal.timeout(3e4),
1197
- body: new Uint8Array(file)
1198
- });
1199
- if (!response.ok) {
1200
- throw new Error(
1201
- `Failed to upload file to ${input.url}: ${response.status} ${response.statusText}`
1202
- );
1203
- }
1204
- }
1205
-
1206
- // src/util/chunk.ts
1207
- var chunk = (collection, size) => {
1208
- const result = [];
1209
- for (let x = 0; x < Math.ceil(collection.length / size); x++) {
1210
- const start = x * size;
1211
- const end = start + size;
1212
- result.push(collection.slice(start, end));
1213
- }
1214
- return result;
1215
- };
1216
-
1217
- // src/upload.ts
1218
- import {
1219
- getPlaywrightTracePath,
1220
- readMetadata
1221
- } from "@argos-ci/util";
1222
-
1223
- // src/version.ts
1224
- import { readVersionFromPackage } from "@argos-ci/util";
1225
- import { createRequire } from "module";
1226
- var require2 = createRequire(import.meta.url);
1227
- async function getArgosCoreSDKIdentifier() {
1228
- const pkgPath = require2.resolve("@argos-ci/core/package.json");
1229
- const version = await readVersionFromPackage(pkgPath);
1230
- return `@argos-ci/core@${version}`;
1231
- }
1232
-
1233
- // src/mime-type.ts
1234
- import mime from "mime-types";
1235
- function getSnapshotMimeType(filepath) {
1236
- const type = mime.lookup(filepath);
1237
- if (!type) {
1238
- throw new Error(`Unable to determine snapshot file type for: ${filepath}`);
1239
- }
1240
- return type;
1241
- }
1242
-
1243
- // src/skip.ts
1244
- import { createClient as createClient2, throwAPIError as throwAPIError2 } from "@argos-ci/api-client";
1245
- async function skip(params) {
1246
- const [config, argosSdk] = await Promise.all([
1247
- getConfigFromOptions(params),
1248
- getArgosCoreSDKIdentifier()
1249
- ]);
1250
- const authToken = getAuthToken(config);
1251
- const apiClient = createClient2({
1252
- baseUrl: config.apiBaseUrl,
1253
- authToken
1254
- });
1255
- const createBuildResponse = await apiClient.POST("/builds", {
1256
- body: {
1257
- commit: config.commit,
1258
- branch: config.branch,
1259
- name: config.buildName,
1260
- mode: config.mode,
1261
- prNumber: config.prNumber,
1262
- prHeadCommit: config.prHeadCommit,
1263
- referenceBranch: config.referenceBranch,
1264
- referenceCommit: config.referenceCommit,
1265
- argosSdk,
1266
- ciProvider: config.ciProvider,
1267
- runId: config.runId,
1268
- runAttempt: config.runAttempt,
1269
- skipped: true,
1270
- screenshotKeys: [],
1271
- pwTraceKeys: [],
1272
- parentCommits: []
1273
- }
1274
- });
1275
- if (createBuildResponse.error) {
1276
- throwAPIError2(createBuildResponse.error);
1277
- }
1278
- return { build: createBuildResponse.data.build };
1279
- }
1280
-
1281
- // src/upload.ts
1282
- var CHUNK_SIZE = 10;
1283
- async function upload(params) {
1284
- debug("Starting upload with params", params);
1285
- const [config, argosSdk] = await Promise.all([
1286
- getConfigFromOptions(params),
1287
- getArgosCoreSDKIdentifier()
1288
- ]);
1289
- const authToken = getAuthToken(config);
1290
- const apiClient = createClient3({
1291
- baseUrl: config.apiBaseUrl,
1292
- authToken
1293
- });
1294
- if (config.skipped) {
1295
- const { build } = await skip(params);
1296
- return { build, screenshots: [] };
1297
- }
1298
- const previewUrlFormatter = params.previewUrl ?? (config.previewBaseUrl ? { baseUrl: config.previewBaseUrl } : void 0);
1299
- const globs = params.files ?? ["**/*.{png,jpg,jpeg}"];
1300
- debug("Using config and files", config, globs);
1301
- const files = await discoverSnapshots(globs, {
1302
- root: params.root,
1303
- ignore: params.ignore
1304
- });
1305
- debug("Found snapshots", files);
1306
- const snapshots = await Promise.all(
1307
- files.map(async (snapshot) => {
1308
- const contentType = getSnapshotMimeType(snapshot.path);
1309
- const [metadata, pwTracePath, optimizedPath] = await Promise.all([
1310
- readMetadata(snapshot.path),
1311
- getPlaywrightTracePath(snapshot.path),
1312
- contentType.startsWith("image/") ? optimizeScreenshot(snapshot.path) : snapshot.path
1313
- ]);
1314
- const [hash, pwTraceHash] = await Promise.all([
1315
- hashFile(optimizedPath),
1316
- pwTracePath ? hashFile(pwTracePath) : null
1317
- ]);
1318
- const threshold = metadata?.transient?.threshold ?? null;
1319
- const baseName = metadata?.transient?.baseName ?? null;
1320
- const parentName = metadata?.transient?.parentName ?? null;
1321
- if (metadata) {
1322
- delete metadata.transient;
1323
- if (metadata.url && previewUrlFormatter) {
1324
- metadata.previewUrl = formatPreviewUrl(
1325
- metadata.url,
1326
- previewUrlFormatter
1327
- );
1328
- }
1329
- }
1330
- return {
1331
- ...snapshot,
1332
- hash,
1333
- optimizedPath,
1334
- metadata,
1335
- threshold,
1336
- baseName,
1337
- parentName,
1338
- pwTrace: pwTracePath && pwTraceHash ? { path: pwTracePath, hash: pwTraceHash } : null,
1339
- contentType
1340
- };
1341
- })
1342
- );
1343
- debug("Fetch project");
1344
- const projectResponse = await apiClient.GET("/project");
1345
- if (projectResponse.error) {
1346
- throwAPIError3(projectResponse.error);
1347
- }
1348
- debug("Project fetched", projectResponse.data);
1349
- const { defaultBaseBranch, hasRemoteContentAccess } = projectResponse.data;
1350
- const referenceCommit = (() => {
1351
- if (config.referenceCommit) {
1352
- debug("Found reference commit in config", config.referenceCommit);
1353
- return config.referenceCommit;
1354
- }
1355
- if (hasRemoteContentAccess) {
1356
- return null;
1357
- }
1358
- const base = config.referenceBranch || config.prBaseBranch || defaultBaseBranch;
1359
- const sha = getMergeBaseCommitSha2({ base, head: config.branch });
1360
- if (sha) {
1361
- debug("Found merge base", sha);
1362
- } else {
1363
- debug("No merge base found");
1364
- }
1365
- return sha;
1366
- })();
1367
- const parentCommits = (() => {
1368
- if (hasRemoteContentAccess) {
1369
- return null;
1370
- }
1371
- if (referenceCommit) {
1372
- const commits = listParentCommits2({ sha: referenceCommit });
1373
- if (commits) {
1374
- debug("Found parent commits", commits);
1375
- } else {
1376
- debug("No parent commits found");
1377
- }
1378
- return commits;
1379
- }
1380
- return null;
1381
- })();
1382
- debug("Creating build");
1383
- const [pwTraceKeys, snapshotKeys] = snapshots.reduce(
1384
- ([pwTraceKeys2, snapshotKeys2], snapshot) => {
1385
- if (snapshot.pwTrace && !pwTraceKeys2.includes(snapshot.pwTrace.hash)) {
1386
- pwTraceKeys2.push(snapshot.pwTrace.hash);
1387
- }
1388
- if (!snapshotKeys2.includes(snapshot.hash)) {
1389
- snapshotKeys2.push(snapshot.hash);
1390
- }
1391
- return [pwTraceKeys2, snapshotKeys2];
1392
- },
1393
- [[], []]
1394
- );
1395
- const createBuildResponse = await apiClient.POST("/builds", {
1396
- body: {
1397
- commit: config.commit,
1398
- branch: config.branch,
1399
- name: config.buildName,
1400
- mode: config.mode,
1401
- parallel: config.parallel,
1402
- parallelNonce: config.parallelNonce,
1403
- screenshotKeys: snapshotKeys,
1404
- pwTraceKeys,
1405
- prNumber: config.prNumber,
1406
- prHeadCommit: config.prHeadCommit,
1407
- referenceBranch: config.referenceBranch,
1408
- referenceCommit,
1409
- parentCommits,
1410
- argosSdk,
1411
- ciProvider: config.ciProvider,
1412
- runId: config.runId,
1413
- runAttempt: config.runAttempt,
1414
- mergeQueue: config.mergeQueue,
1415
- subset: config.subset
1416
- }
1417
- });
1418
- if (createBuildResponse.error) {
1419
- throwAPIError3(createBuildResponse.error);
1420
- }
1421
- const result = createBuildResponse.data;
1422
- debug("Got uploads url", result);
1423
- const uploadFiles = [
1424
- ...result.screenshots.map(({ key, putUrl }) => {
1425
- const snapshot = snapshots.find((s) => s.hash === key);
1426
- if (!snapshot) {
1427
- throw new Error(`Invariant: snapshot with hash ${key} not found`);
1428
- }
1429
- return {
1430
- url: putUrl,
1431
- path: snapshot.optimizedPath,
1432
- contentType: snapshot.contentType
1433
- };
1434
- }),
1435
- ...result.pwTraces?.map(({ key, putUrl }) => {
1436
- const snapshot = snapshots.find(
1437
- (s) => s.pwTrace && s.pwTrace.hash === key
1438
- );
1439
- if (!snapshot || !snapshot.pwTrace) {
1440
- throw new Error(`Invariant: trace with ${key} not found`);
1441
- }
1442
- return {
1443
- url: putUrl,
1444
- path: snapshot.pwTrace.path,
1445
- contentType: "application/json"
1446
- };
1447
- }) ?? []
1448
- ];
1449
- await uploadFilesToS3(uploadFiles);
1450
- debug("Updating build");
1451
- const uploadBuildResponse = await apiClient.PUT("/builds/{buildId}", {
1452
- params: {
1453
- path: {
1454
- buildId: result.build.id
1455
- }
1456
- },
1457
- body: {
1458
- screenshots: snapshots.map((snapshot) => ({
1459
- key: snapshot.hash,
1460
- name: snapshot.name,
1461
- metadata: snapshot.metadata,
1462
- pwTraceKey: snapshot.pwTrace?.hash ?? null,
1463
- threshold: snapshot.threshold ?? config?.threshold ?? null,
1464
- baseName: snapshot.baseName,
1465
- parentName: snapshot.parentName,
1466
- contentType: snapshot.contentType
1467
- })),
1468
- parallel: config.parallel,
1469
- parallelTotal: config.parallelTotal,
1470
- parallelIndex: config.parallelIndex,
1471
- metadata: params.metadata
1472
- }
1473
- });
1474
- if (uploadBuildResponse.error) {
1475
- throwAPIError3(uploadBuildResponse.error);
1476
- }
1477
- return { build: uploadBuildResponse.data.build, screenshots: snapshots };
1478
- }
1479
- async function uploadFilesToS3(files) {
1480
- debug(`Split files in chunks of ${CHUNK_SIZE}`);
1481
- const chunks = chunk(files, CHUNK_SIZE);
1482
- debug(`Starting upload of ${chunks.length} chunks`);
1483
- for (let i = 0; i < chunks.length; i++) {
1484
- debug(`Uploading chunk ${i + 1}/${chunks.length}`);
1485
- const timeLabel = `Chunk ${i + 1}/${chunks.length}`;
1486
- debugTime(timeLabel);
1487
- const chunk2 = chunks[i];
1488
- if (!chunk2) {
1489
- throw new Error(`Invariant: chunk ${i} is empty`);
1490
- }
1491
- await Promise.all(
1492
- chunk2.map(async ({ url, path, contentType }) => {
1493
- await uploadFile({
1494
- url,
1495
- path,
1496
- contentType
1497
- });
1498
- })
1499
- );
1500
- debugTimeEnd(timeLabel);
1501
- }
1502
- }
1503
- function formatPreviewUrl(url, formatter) {
1504
- if (typeof formatter === "function") {
1505
- return formatter(url);
1506
- }
1507
- const urlObj = new URL(url);
1508
- return new URL(
1509
- urlObj.pathname + urlObj.search + urlObj.hash,
1510
- formatter.baseUrl
1511
- ).href;
1512
- }
1513
- export {
1514
- finalize,
1515
- getConfigFromOptions,
1516
- readConfig,
1517
- skip,
1518
- upload
1519
- };