@argos-ci/cli 0.4.4 → 0.4.6-alpha.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.mjs +1 -573
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -2,581 +2,9 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { program } from 'commander';
|
|
5
|
-
import
|
|
6
|
-
import { execSync } from 'node:child_process';
|
|
7
|
-
import { existsSync, readFileSync, createReadStream } from 'node:fs';
|
|
8
|
-
import createDebug from 'debug';
|
|
9
|
-
import envCi from 'env-ci';
|
|
10
|
-
import glob from 'fast-glob';
|
|
11
|
-
import { promisify } from 'node:util';
|
|
12
|
-
import sharp from 'sharp';
|
|
13
|
-
import tmp from 'tmp';
|
|
14
|
-
import { createHash } from 'node:crypto';
|
|
15
|
-
import axios from 'axios';
|
|
5
|
+
import { upload } from '@argos-ci/core';
|
|
16
6
|
import ora from 'ora';
|
|
17
7
|
|
|
18
|
-
const mustBeApiBaseUrl = (value)=>{
|
|
19
|
-
const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
|
|
20
|
-
if (!URL_REGEX.test(value)) {
|
|
21
|
-
throw new Error("Invalid Argos API base URL");
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
const mustBeCommit = (value)=>{
|
|
25
|
-
const SHA1_REGEX = /^[0-9a-f]{40}$/;
|
|
26
|
-
if (!SHA1_REGEX.test(value)) {
|
|
27
|
-
const SHA1_SHORT_REGEX = /^[0-9a-f]{7}$/;
|
|
28
|
-
if (SHA1_SHORT_REGEX.test(value)) {
|
|
29
|
-
throw new Error("Short SHA1 is not allowed");
|
|
30
|
-
}
|
|
31
|
-
throw new Error("Invalid commit");
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const mustBeArgosToken = (value)=>{
|
|
35
|
-
if (value && value.length !== 40) {
|
|
36
|
-
throw new Error("Must be a valid Argos repository token");
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const schema = {
|
|
40
|
-
apiBaseUrl: {
|
|
41
|
-
env: "ARGOS_API_BASE_URL",
|
|
42
|
-
default: "https://api.argos-ci.com/v2/",
|
|
43
|
-
format: mustBeApiBaseUrl
|
|
44
|
-
},
|
|
45
|
-
commit: {
|
|
46
|
-
env: "ARGOS_COMMIT",
|
|
47
|
-
default: null,
|
|
48
|
-
format: mustBeCommit
|
|
49
|
-
},
|
|
50
|
-
branch: {
|
|
51
|
-
env: "ARGOS_BRANCH",
|
|
52
|
-
default: null,
|
|
53
|
-
format: String
|
|
54
|
-
},
|
|
55
|
-
token: {
|
|
56
|
-
env: "ARGOS_TOKEN",
|
|
57
|
-
default: null,
|
|
58
|
-
format: mustBeArgosToken
|
|
59
|
-
},
|
|
60
|
-
buildName: {
|
|
61
|
-
env: "ARGOS_BUILD_NAME",
|
|
62
|
-
default: null,
|
|
63
|
-
format: String,
|
|
64
|
-
nullable: true
|
|
65
|
-
},
|
|
66
|
-
prNumber: {
|
|
67
|
-
env: "ARGOS_PR_NUMBER",
|
|
68
|
-
format: Number,
|
|
69
|
-
default: null,
|
|
70
|
-
nullable: true
|
|
71
|
-
},
|
|
72
|
-
parallel: {
|
|
73
|
-
env: "ARGOS_PARALLEL",
|
|
74
|
-
default: false,
|
|
75
|
-
format: Boolean
|
|
76
|
-
},
|
|
77
|
-
parallelNonce: {
|
|
78
|
-
env: "ARGOS_PARALLEL_NONCE",
|
|
79
|
-
format: String,
|
|
80
|
-
default: null,
|
|
81
|
-
nullable: true
|
|
82
|
-
},
|
|
83
|
-
parallelTotal: {
|
|
84
|
-
env: "ARGOS_PARALLEL_TOTAL",
|
|
85
|
-
format: "nat",
|
|
86
|
-
default: null,
|
|
87
|
-
nullable: true
|
|
88
|
-
},
|
|
89
|
-
ciService: {
|
|
90
|
-
format: String,
|
|
91
|
-
default: null,
|
|
92
|
-
nullable: true
|
|
93
|
-
},
|
|
94
|
-
jobId: {
|
|
95
|
-
format: String,
|
|
96
|
-
default: null,
|
|
97
|
-
nullable: true
|
|
98
|
-
},
|
|
99
|
-
runId: {
|
|
100
|
-
format: String,
|
|
101
|
-
default: null,
|
|
102
|
-
nullable: true
|
|
103
|
-
},
|
|
104
|
-
owner: {
|
|
105
|
-
format: String,
|
|
106
|
-
default: null,
|
|
107
|
-
nullable: true
|
|
108
|
-
},
|
|
109
|
-
repository: {
|
|
110
|
-
format: String,
|
|
111
|
-
default: null,
|
|
112
|
-
nullable: true
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const createConfig = ()=>{
|
|
116
|
-
return convict(schema, {
|
|
117
|
-
args: []
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Returns the head commit.
|
|
123
|
-
*/ const head = ()=>{
|
|
124
|
-
try {
|
|
125
|
-
return execSync("git rev-parse HEAD").toString().trim();
|
|
126
|
-
} catch {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
/**
|
|
131
|
-
* Returns the current branch.
|
|
132
|
-
*/ const branch = ()=>{
|
|
133
|
-
try {
|
|
134
|
-
const headRef = execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
|
|
135
|
-
if (headRef === "HEAD") {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
return headRef;
|
|
139
|
-
} catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const service$4 = {
|
|
145
|
-
name: "Buildkite",
|
|
146
|
-
detect: ({ env })=>Boolean(env.BUILDKITE),
|
|
147
|
-
config: ({ env })=>{
|
|
148
|
-
return {
|
|
149
|
-
// Buildkite doesn't work well so we fallback to git to ensure we have commit and branch
|
|
150
|
-
commit: env.BUILDKITE_COMMIT || head() || null,
|
|
151
|
-
branch: env.BUILDKITE_BRANCH || branch() || null,
|
|
152
|
-
owner: env.BUILDKITE_ORGANIZATION_SLUG || null,
|
|
153
|
-
repository: env.BUILDKITE_PROJECT_SLUG || null,
|
|
154
|
-
jobId: null,
|
|
155
|
-
runId: null,
|
|
156
|
-
prNumber: env.BUILDKITE_PULL_REQUEST ? Number(env.BUILDKITE_PULL_REQUEST) : null
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const service$3 = {
|
|
162
|
-
name: "Heroku",
|
|
163
|
-
detect: ({ env })=>Boolean(env.HEROKU_TEST_RUN_ID),
|
|
164
|
-
config: ({ env })=>({
|
|
165
|
-
commit: env.HEROKU_TEST_RUN_COMMIT_VERSION || null,
|
|
166
|
-
branch: env.HEROKU_TEST_RUN_BRANCH || null,
|
|
167
|
-
owner: null,
|
|
168
|
-
repository: null,
|
|
169
|
-
jobId: null,
|
|
170
|
-
runId: null,
|
|
171
|
-
prNumber: null
|
|
172
|
-
})
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const getBranch = ({ env })=>{
|
|
176
|
-
if (env.GITHUB_HEAD_REF) {
|
|
177
|
-
return env.GITHUB_HEAD_REF;
|
|
178
|
-
}
|
|
179
|
-
const branchRegex = /refs\/heads\/(.*)/;
|
|
180
|
-
const matches = branchRegex.exec(env.GITHUB_REF || "");
|
|
181
|
-
if (matches) {
|
|
182
|
-
return matches[1];
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
185
|
-
};
|
|
186
|
-
const getRepository$1 = ({ env })=>{
|
|
187
|
-
if (!env.GITHUB_REPOSITORY) return null;
|
|
188
|
-
return env.GITHUB_REPOSITORY.split("/")[1];
|
|
189
|
-
};
|
|
190
|
-
const readEventPayload = ({ env })=>{
|
|
191
|
-
if (!env.GITHUB_EVENT_PATH) return null;
|
|
192
|
-
if (!existsSync(env.GITHUB_EVENT_PATH)) return null;
|
|
193
|
-
return JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, "utf-8"));
|
|
194
|
-
};
|
|
195
|
-
const service$2 = {
|
|
196
|
-
name: "GitHub Actions",
|
|
197
|
-
detect: ({ env })=>Boolean(env.GITHUB_ACTIONS),
|
|
198
|
-
config: ({ env })=>{
|
|
199
|
-
const payload = readEventPayload({
|
|
200
|
-
env
|
|
201
|
-
});
|
|
202
|
-
return {
|
|
203
|
-
commit: payload?.pull_request?.head.sha || process.env.GITHUB_SHA || null,
|
|
204
|
-
branch: payload?.pull_request?.head.ref || getBranch({
|
|
205
|
-
env
|
|
206
|
-
}) || null,
|
|
207
|
-
owner: env.GITHUB_REPOSITORY_OWNER || null,
|
|
208
|
-
repository: getRepository$1({
|
|
209
|
-
env
|
|
210
|
-
}),
|
|
211
|
-
jobId: env.GITHUB_JOB || null,
|
|
212
|
-
runId: env.GITHUB_RUN_ID || null,
|
|
213
|
-
prNumber: payload?.pull_request?.number || null
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const getPrNumber$1 = ({ env })=>{
|
|
219
|
-
const branchRegex = /pull\/(\d+)/;
|
|
220
|
-
const matches = branchRegex.exec(env.CIRCLE_PULL_REQUEST || "");
|
|
221
|
-
if (matches) {
|
|
222
|
-
return Number(matches[1]);
|
|
223
|
-
}
|
|
224
|
-
return null;
|
|
225
|
-
};
|
|
226
|
-
const service$1 = {
|
|
227
|
-
name: "CircleCI",
|
|
228
|
-
detect: ({ env })=>Boolean(env.CIRCLECI),
|
|
229
|
-
config: ({ env })=>{
|
|
230
|
-
return {
|
|
231
|
-
commit: env.CIRCLE_SHA1 || null,
|
|
232
|
-
branch: env.CIRCLE_BRANCH || null,
|
|
233
|
-
owner: env.CIRCLE_PROJECT_USERNAME || null,
|
|
234
|
-
repository: env.CIRCLE_PROJECT_REPONAME || null,
|
|
235
|
-
jobId: null,
|
|
236
|
-
runId: null,
|
|
237
|
-
prNumber: getPrNumber$1({
|
|
238
|
-
env
|
|
239
|
-
})
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const getOwner = ({ env })=>{
|
|
245
|
-
if (!env.TRAVIS_REPO_SLUG) return null;
|
|
246
|
-
return env.TRAVIS_REPO_SLUG.split("/")[0] || null;
|
|
247
|
-
};
|
|
248
|
-
const getRepository = ({ env })=>{
|
|
249
|
-
if (!env.TRAVIS_REPO_SLUG) return null;
|
|
250
|
-
return env.TRAVIS_REPO_SLUG.split("/")[1] || null;
|
|
251
|
-
};
|
|
252
|
-
const getPrNumber = ({ env })=>{
|
|
253
|
-
if (env.TRAVIS_PULL_REQUEST) return Number(env.TRAVIS_PULL_REQUEST);
|
|
254
|
-
return null;
|
|
255
|
-
};
|
|
256
|
-
const service = {
|
|
257
|
-
name: "Travis CI",
|
|
258
|
-
detect: ({ env })=>Boolean(env.TRAVIS),
|
|
259
|
-
config: (ctx)=>{
|
|
260
|
-
const { env } = ctx;
|
|
261
|
-
return {
|
|
262
|
-
commit: env.TRAVIS_COMMIT || null,
|
|
263
|
-
branch: env.TRAVIS_BRANCH || null,
|
|
264
|
-
owner: getOwner(ctx),
|
|
265
|
-
repository: getRepository(ctx),
|
|
266
|
-
jobId: null,
|
|
267
|
-
runId: null,
|
|
268
|
-
prNumber: getPrNumber(ctx)
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
const KEY = "@argos-ci/core";
|
|
274
|
-
const debug = createDebug(KEY);
|
|
275
|
-
const debugTime = (arg)=>{
|
|
276
|
-
const enabled = createDebug.enabled(KEY);
|
|
277
|
-
if (enabled) {
|
|
278
|
-
console.time(arg);
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
const debugTimeEnd = (arg)=>{
|
|
282
|
-
const enabled = createDebug.enabled(KEY);
|
|
283
|
-
if (enabled) {
|
|
284
|
-
console.timeEnd(arg);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const getCiEnvironmentFromEnvCi = (ctx)=>{
|
|
289
|
-
const ciContext = envCi(ctx);
|
|
290
|
-
const name = ciContext.isCi ? ciContext.name ?? null : ciContext.commit ? "Git" : null;
|
|
291
|
-
const commit = ciContext.commit ?? null;
|
|
292
|
-
const branch = (ciContext.branch || ciContext.prBranch) ?? null;
|
|
293
|
-
const slug = ciContext.slug ? ciContext.slug.split("/") : null;
|
|
294
|
-
const owner = slug ? slug[0] : null;
|
|
295
|
-
const repository = slug ? slug[1] : null;
|
|
296
|
-
const jobId = ciContext.job ?? null;
|
|
297
|
-
const runId = null;
|
|
298
|
-
const prNumber = null;
|
|
299
|
-
return commit ? {
|
|
300
|
-
name,
|
|
301
|
-
commit,
|
|
302
|
-
branch,
|
|
303
|
-
owner,
|
|
304
|
-
repository,
|
|
305
|
-
jobId,
|
|
306
|
-
runId,
|
|
307
|
-
prNumber
|
|
308
|
-
} : null;
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const services = [
|
|
312
|
-
service$3,
|
|
313
|
-
service$2,
|
|
314
|
-
service$1,
|
|
315
|
-
service,
|
|
316
|
-
service$4
|
|
317
|
-
];
|
|
318
|
-
const getCiEnvironment = ({ env =process.env } = {})=>{
|
|
319
|
-
const ctx = {
|
|
320
|
-
env
|
|
321
|
-
};
|
|
322
|
-
debug("Detecting CI environment", {
|
|
323
|
-
env
|
|
324
|
-
});
|
|
325
|
-
const service = services.find((service)=>service.detect(ctx));
|
|
326
|
-
// Service matched
|
|
327
|
-
if (service) {
|
|
328
|
-
debug("Internal service matched", service.name);
|
|
329
|
-
const variables = service.config(ctx);
|
|
330
|
-
const ciEnvironment = {
|
|
331
|
-
name: service.name,
|
|
332
|
-
...variables
|
|
333
|
-
};
|
|
334
|
-
debug("CI environment", ciEnvironment);
|
|
335
|
-
return ciEnvironment;
|
|
336
|
-
}
|
|
337
|
-
// We fallback on "env-ci" library, not very good but it's better than nothing
|
|
338
|
-
debug("Falling back on env-ci");
|
|
339
|
-
const ciEnvironment1 = getCiEnvironmentFromEnvCi(ctx);
|
|
340
|
-
debug("CI environment", ciEnvironment1);
|
|
341
|
-
return ciEnvironment1;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const discoverScreenshots = async (patterns, { root =process.cwd() , ignore } = {})=>{
|
|
345
|
-
const matches = await glob(patterns, {
|
|
346
|
-
onlyFiles: true,
|
|
347
|
-
ignore,
|
|
348
|
-
cwd: root
|
|
349
|
-
});
|
|
350
|
-
return matches.map((match)=>({
|
|
351
|
-
name: match,
|
|
352
|
-
path: resolve(root, match)
|
|
353
|
-
}));
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const tmpFile = promisify(tmp.file);
|
|
357
|
-
const optimizeScreenshot = async (filepath)=>{
|
|
358
|
-
const resultFilePath = await tmpFile();
|
|
359
|
-
await sharp(filepath).resize(2048, 20480, {
|
|
360
|
-
fit: "inside",
|
|
361
|
-
withoutEnlargement: true
|
|
362
|
-
}).png({
|
|
363
|
-
force: true
|
|
364
|
-
}).toFile(resultFilePath);
|
|
365
|
-
return resultFilePath;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
const hashFile = async (filepath)=>{
|
|
369
|
-
const fileStream = createReadStream(filepath);
|
|
370
|
-
const hash = createHash("sha256");
|
|
371
|
-
await new Promise((resolve, reject)=>{
|
|
372
|
-
fileStream.on("error", reject);
|
|
373
|
-
hash.on("error", reject);
|
|
374
|
-
hash.on("finish", resolve);
|
|
375
|
-
fileStream.pipe(hash);
|
|
376
|
-
});
|
|
377
|
-
return hash.digest("hex");
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const base64Encode = (obj)=>Buffer.from(JSON.stringify(obj), "utf8").toString("base64");
|
|
381
|
-
const getBearerToken = ({ token , ciService , owner , repository , jobId , runId , prNumber })=>{
|
|
382
|
-
if (token) return `Bearer ${token}`;
|
|
383
|
-
switch(ciService){
|
|
384
|
-
case "GitHub Actions":
|
|
385
|
-
{
|
|
386
|
-
if (!owner || !repository || !jobId || !runId) {
|
|
387
|
-
throw new Error(`Automatic ${ciService} variables detection failed. Please add the 'ARGOS_TOKEN'`);
|
|
388
|
-
}
|
|
389
|
-
return `Bearer tokenless-github-${base64Encode({
|
|
390
|
-
owner,
|
|
391
|
-
repository,
|
|
392
|
-
jobId,
|
|
393
|
-
runId,
|
|
394
|
-
prNumber
|
|
395
|
-
})}`;
|
|
396
|
-
}
|
|
397
|
-
default:
|
|
398
|
-
throw new Error("Missing Argos repository token 'ARGOS_TOKEN'");
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
const createArgosApiClient = (options)=>{
|
|
402
|
-
const axiosInstance = axios.create({
|
|
403
|
-
baseURL: options.baseUrl,
|
|
404
|
-
headers: {
|
|
405
|
-
Authorization: options.bearerToken,
|
|
406
|
-
"Content-Type": "application/json",
|
|
407
|
-
Accept: "application/json"
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
const call = async (method, path, data)=>{
|
|
411
|
-
try {
|
|
412
|
-
debug("Sending request", {
|
|
413
|
-
method,
|
|
414
|
-
path,
|
|
415
|
-
data
|
|
416
|
-
});
|
|
417
|
-
const response = await axiosInstance.request({
|
|
418
|
-
method,
|
|
419
|
-
url: path,
|
|
420
|
-
data
|
|
421
|
-
});
|
|
422
|
-
debug("Getting response", {
|
|
423
|
-
status: response.status,
|
|
424
|
-
data: response.data
|
|
425
|
-
});
|
|
426
|
-
return response.data;
|
|
427
|
-
} catch (error) {
|
|
428
|
-
if (error?.response?.data?.error?.message) {
|
|
429
|
-
// @ts-ignore
|
|
430
|
-
throw new Error(error.response.data.error.message, {
|
|
431
|
-
cause: error
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
throw error;
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
return {
|
|
438
|
-
createBuild: async (input)=>{
|
|
439
|
-
return call("POST", "/builds", input);
|
|
440
|
-
},
|
|
441
|
-
updateBuild: async (input)=>{
|
|
442
|
-
const { buildId , ...body } = input;
|
|
443
|
-
return call("PUT", `/builds/${buildId}`, body);
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
const upload$1 = async (input)=>{
|
|
449
|
-
const file = await readFile(input.path);
|
|
450
|
-
await axios({
|
|
451
|
-
method: "PUT",
|
|
452
|
-
url: input.url,
|
|
453
|
-
data: file,
|
|
454
|
-
headers: {
|
|
455
|
-
"Content-Type": "image/png"
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Split an array into chunks of a given size.
|
|
462
|
-
*/ const chunk = (collection, size)=>{
|
|
463
|
-
const result = [];
|
|
464
|
-
// add each chunk to the result
|
|
465
|
-
for(let x = 0; x < Math.ceil(collection.length / size); x++){
|
|
466
|
-
let start = x * size;
|
|
467
|
-
let end = start + size;
|
|
468
|
-
result.push(collection.slice(start, end));
|
|
469
|
-
}
|
|
470
|
-
return result;
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Size of the chunks used to upload screenshots to Argos.
|
|
475
|
-
*/ const CHUNK_SIZE = 10;
|
|
476
|
-
const getConfigFromOptions = (options)=>{
|
|
477
|
-
const config = createConfig();
|
|
478
|
-
const ciEnv = getCiEnvironment();
|
|
479
|
-
config.load({
|
|
480
|
-
apiBaseUrl: config.get("apiBaseUrl") ?? options.apiBaseUrl,
|
|
481
|
-
commit: config.get("commit") ?? options.commit ?? ciEnv?.commit ?? null,
|
|
482
|
-
branch: config.get("branch") ?? options.branch ?? ciEnv?.branch ?? null,
|
|
483
|
-
token: config.get("token") ?? options.token ?? null,
|
|
484
|
-
buildName: config.get("buildName") ?? options.buildName ?? null,
|
|
485
|
-
prNumber: config.get("prNumber") ?? options.prNumber ?? ciEnv?.prNumber ?? null,
|
|
486
|
-
ciService: ciEnv?.name ?? null,
|
|
487
|
-
owner: ciEnv?.owner ?? null,
|
|
488
|
-
repository: ciEnv?.repository ?? null,
|
|
489
|
-
jobId: ciEnv?.jobId ?? null,
|
|
490
|
-
runId: ciEnv?.runId ?? null
|
|
491
|
-
});
|
|
492
|
-
if (options.parallel) {
|
|
493
|
-
config.load({
|
|
494
|
-
parallel: Boolean(options.parallel),
|
|
495
|
-
parallelNonce: options.parallel ? options.parallel.nonce : null,
|
|
496
|
-
parallelTotal: options.parallel ? options.parallel.total : null
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
config.validate();
|
|
500
|
-
return config.get();
|
|
501
|
-
};
|
|
502
|
-
/**
|
|
503
|
-
* Upload screenshots to argos-ci.com.
|
|
504
|
-
*/ const upload = async (params)=>{
|
|
505
|
-
debug("Starting upload with params", params);
|
|
506
|
-
// Read config
|
|
507
|
-
const config = getConfigFromOptions(params);
|
|
508
|
-
const files = params.files ?? [
|
|
509
|
-
"**/*.{png,jpg,jpeg}"
|
|
510
|
-
];
|
|
511
|
-
debug("Using config and files", config, files);
|
|
512
|
-
const apiClient = createArgosApiClient({
|
|
513
|
-
baseUrl: config.apiBaseUrl,
|
|
514
|
-
bearerToken: getBearerToken(config)
|
|
515
|
-
});
|
|
516
|
-
// Collect screenshots
|
|
517
|
-
const foundScreenshots = await discoverScreenshots(files, {
|
|
518
|
-
root: params.root,
|
|
519
|
-
ignore: params.ignore
|
|
520
|
-
});
|
|
521
|
-
debug("Found screenshots", foundScreenshots);
|
|
522
|
-
// Optimize & compute hashes
|
|
523
|
-
const screenshots = await Promise.all(foundScreenshots.map(async (screenshot)=>{
|
|
524
|
-
const optimizedPath = await optimizeScreenshot(screenshot.path);
|
|
525
|
-
const hash = await hashFile(optimizedPath);
|
|
526
|
-
return {
|
|
527
|
-
...screenshot,
|
|
528
|
-
optimizedPath,
|
|
529
|
-
hash
|
|
530
|
-
};
|
|
531
|
-
}));
|
|
532
|
-
// Create build
|
|
533
|
-
debug("Creating build");
|
|
534
|
-
const result = await apiClient.createBuild({
|
|
535
|
-
commit: config.commit,
|
|
536
|
-
branch: config.branch,
|
|
537
|
-
name: config.buildName,
|
|
538
|
-
parallel: config.parallel,
|
|
539
|
-
parallelNonce: config.parallelNonce,
|
|
540
|
-
screenshotKeys: Array.from(new Set(screenshots.map((screenshot)=>screenshot.hash))),
|
|
541
|
-
prNumber: config.prNumber
|
|
542
|
-
});
|
|
543
|
-
debug("Got screenshots", result);
|
|
544
|
-
debug(`Split screenshots in chunks of ${CHUNK_SIZE}`);
|
|
545
|
-
const chunks = chunk(result.screenshots, CHUNK_SIZE);
|
|
546
|
-
debug(`Starting upload of ${chunks.length} chunks`);
|
|
547
|
-
for(let i = 0; i < chunks.length; i++){
|
|
548
|
-
debug(`Uploading chunk ${i + 1}/${chunks.length}`);
|
|
549
|
-
const timeLabel = `Chunk ${i + 1}/${chunks.length}`;
|
|
550
|
-
debugTime(timeLabel);
|
|
551
|
-
await Promise.all(chunks[i].map(async ({ key , putUrl })=>{
|
|
552
|
-
const screenshot = screenshots.find((s)=>s.hash === key);
|
|
553
|
-
if (!screenshot) {
|
|
554
|
-
throw new Error(`Invariant: screenshot with hash ${key} not found`);
|
|
555
|
-
}
|
|
556
|
-
await upload$1({
|
|
557
|
-
url: putUrl,
|
|
558
|
-
path: screenshot.optimizedPath
|
|
559
|
-
});
|
|
560
|
-
}));
|
|
561
|
-
debugTimeEnd(timeLabel);
|
|
562
|
-
}
|
|
563
|
-
// Update build
|
|
564
|
-
debug("Updating build");
|
|
565
|
-
await apiClient.updateBuild({
|
|
566
|
-
buildId: result.build.id,
|
|
567
|
-
screenshots: screenshots.map((screenshot)=>({
|
|
568
|
-
key: screenshot.hash,
|
|
569
|
-
name: screenshot.name
|
|
570
|
-
})),
|
|
571
|
-
parallel: config.parallel,
|
|
572
|
-
parallelTotal: config.parallelTotal
|
|
573
|
-
});
|
|
574
|
-
return {
|
|
575
|
-
build: result.build,
|
|
576
|
-
screenshots
|
|
577
|
-
};
|
|
578
|
-
};
|
|
579
|
-
|
|
580
8
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
581
9
|
const rawPkg = await readFile(resolve(__dirname, "..", "package.json"), "utf8");
|
|
582
10
|
const pkg = JSON.parse(rawPkg);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argos-ci/cli",
|
|
3
3
|
"description": "Visual testing solution to avoid visual regression. Argos CLI is used to interact with and upload screenshots to argos-ci.com via command line.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.6-alpha.0+2c79db4",
|
|
5
5
|
"bin": {
|
|
6
6
|
"argos": "./bin/argos-cli.js"
|
|
7
7
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@argos-ci/core": "^0.8.0",
|
|
43
|
+
"@argos-ci/core": "^0.8.2-alpha.0+2c79db4",
|
|
44
44
|
"commander": "^9.4.1",
|
|
45
45
|
"ora": "^6.1.2",
|
|
46
46
|
"update-notifier": "^6.0.2"
|
|
@@ -49,5 +49,5 @@
|
|
|
49
49
|
"rollup": "^2.79.1",
|
|
50
50
|
"rollup-plugin-swc3": "^0.6.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "2c79db40e62d541cca2c3e6b124ffe2063e8c838"
|
|
53
53
|
}
|