@capraconsulting/cals-cli 3.13.0 → 3.14.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 +4 -16
- package/lib/cals-cli.mjs +599 -2461
- package/lib/cals-cli.mjs.map +1 -1
- package/lib/cli/reporter.d.ts +5 -6
- package/lib/cli/util.d.ts +1 -6
- package/lib/config.d.ts +0 -2
- package/lib/definition/index.d.ts +1 -1
- package/lib/definition/types.d.ts +0 -52
- package/lib/github/index.d.ts +0 -2
- package/lib/github/service.d.ts +2 -67
- package/lib/github/types.d.ts +0 -63
- package/lib/github/util.d.ts +0 -1
- package/lib/index.d.ts +0 -10
- package/lib/index.es.js +11 -1391
- package/lib/index.es.js.map +1 -1
- package/lib/index.js +11 -1391
- package/lib/index.js.map +1 -1
- package/package.json +10 -23
- package/lib/cli/commands/definition/dump-setup.d.ts +0 -3
- package/lib/cli/commands/definition/util.d.ts +0 -6
- package/lib/cli/commands/definition/util.test.d.ts +0 -1
- package/lib/cli/commands/definition/validate.d.ts +0 -3
- package/lib/cli/commands/definition.d.ts +0 -3
- package/lib/cli/commands/delete-cache.d.ts +0 -3
- package/lib/cli/commands/getting-started.d.ts +0 -3
- package/lib/cli/commands/github/analyze-directory.d.ts +0 -3
- package/lib/cli/commands/github/configure.d.ts +0 -3
- package/lib/cli/commands/github/list-pull-requests-stats.d.ts +0 -3
- package/lib/cli/commands/github/list-webhooks.d.ts +0 -3
- package/lib/cli/commands/github/util.d.ts +0 -3
- package/lib/cli/commands/snyk/report.d.ts +0 -3
- package/lib/cli/commands/snyk/set-token.d.ts +0 -3
- package/lib/cli/commands/snyk/sync.d.ts +0 -3
- package/lib/cli/commands/snyk.d.ts +0 -3
- package/lib/github/changeset/changeset.d.ts +0 -21
- package/lib/github/changeset/execute.d.ts +0 -10
- package/lib/github/changeset/types.d.ts +0 -88
- package/lib/snyk/index.d.ts +0 -3
- package/lib/snyk/service.d.ts +0 -30
- package/lib/snyk/token.d.ts +0 -11
- package/lib/snyk/types.d.ts +0 -62
- package/lib/snyk/util.d.ts +0 -3
- package/lib/snyk/util.test.d.ts +0 -1
- package/lib/sonarcloud/index.d.ts +0 -3
- package/lib/sonarcloud/service.d.ts +0 -33
- package/lib/sonarcloud/token.d.ts +0 -8
- package/lib/testing/executor.d.ts +0 -25
- package/lib/testing/index.d.ts +0 -2
- package/lib/testing/lib.d.ts +0 -63
package/lib/index.js
CHANGED
|
@@ -1,162 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import process$1 from 'node:process';
|
|
4
|
-
import readline from 'node:readline';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import 'node:util';
|
|
7
|
-
import https from 'node:https';
|
|
8
|
-
import os from 'node:os';
|
|
9
|
-
import cachedir from 'cachedir';
|
|
10
2
|
import AJV from 'ajv';
|
|
11
3
|
import yaml from 'js-yaml';
|
|
12
4
|
import { Buffer } from 'node:buffer';
|
|
13
5
|
import { performance } from 'node:perf_hooks';
|
|
6
|
+
import * as process from 'node:process';
|
|
7
|
+
import process__default from 'node:process';
|
|
14
8
|
import { Octokit } from '@octokit/rest';
|
|
15
|
-
import fetch from 'node-fetch';
|
|
16
9
|
import pLimit from 'p-limit';
|
|
17
|
-
import * as process from 'process';
|
|
18
10
|
import keytar from 'keytar';
|
|
19
|
-
import { strict } from 'node:assert';
|
|
20
|
-
import { Transform } from 'node:stream';
|
|
21
|
-
import { execa } from 'execa';
|
|
22
|
-
import { read } from 'read';
|
|
23
11
|
|
|
24
|
-
var version = "3.
|
|
25
|
-
|
|
26
|
-
class CacheProvider {
|
|
27
|
-
constructor(config) {
|
|
28
|
-
this.config = config;
|
|
29
|
-
}
|
|
30
|
-
mustValidate = false;
|
|
31
|
-
config;
|
|
32
|
-
defaultCacheTime = 1800;
|
|
33
|
-
/**
|
|
34
|
-
* Retrieve cache if existent, ignoring the time.
|
|
35
|
-
*
|
|
36
|
-
* The caller is responsible for handling proper validation,
|
|
37
|
-
*/
|
|
38
|
-
retrieveJson(cachekey) {
|
|
39
|
-
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
40
|
-
if (!fs.existsSync(cachefile)) {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
const data = fs.readFileSync(cachefile, "utf-8");
|
|
44
|
-
return {
|
|
45
|
-
cacheTime: fs.statSync(cachefile).mtime.getTime(),
|
|
46
|
-
data: (data === "undefined" ? undefined : JSON.parse(data)),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Save data to cache.
|
|
51
|
-
*/
|
|
52
|
-
storeJson(cachekey, data) {
|
|
53
|
-
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
54
|
-
if (!fs.existsSync(this.config.cacheDir)) {
|
|
55
|
-
fs.mkdirSync(this.config.cacheDir, { recursive: true });
|
|
56
|
-
}
|
|
57
|
-
fs.writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
|
|
58
|
-
}
|
|
59
|
-
async json(cachekey, block, cachetime = this.defaultCacheTime) {
|
|
60
|
-
const cacheItem = this.mustValidate
|
|
61
|
-
? undefined
|
|
62
|
-
: this.retrieveJson(cachekey);
|
|
63
|
-
const expire = new Date(Date.now() - cachetime * 1000).getTime();
|
|
64
|
-
if (cacheItem !== undefined && cacheItem.cacheTime > expire) {
|
|
65
|
-
return cacheItem.data;
|
|
66
|
-
}
|
|
67
|
-
const result = await block();
|
|
68
|
-
this.storeJson(cachekey, result);
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Delete all cached data.
|
|
73
|
-
*/
|
|
74
|
-
cleanup() {
|
|
75
|
-
fs.rmSync(this.config.cacheDir, { recursive: true, force: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const CLEAR_WHOLE_LINE = 0;
|
|
80
|
-
function clearLine(stdout) {
|
|
81
|
-
readline.clearLine(stdout, CLEAR_WHOLE_LINE);
|
|
82
|
-
readline.cursorTo(stdout, 0);
|
|
83
|
-
}
|
|
84
|
-
class Reporter {
|
|
85
|
-
constructor(opts = {}) {
|
|
86
|
-
this.nonInteractive = !!opts.nonInteractive;
|
|
87
|
-
this.isVerbose = !!opts.verbose;
|
|
88
|
-
}
|
|
89
|
-
stdout = process$1.stdout;
|
|
90
|
-
stderr = process$1.stderr;
|
|
91
|
-
nonInteractive;
|
|
92
|
-
isVerbose;
|
|
93
|
-
format = chalk;
|
|
94
|
-
error(msg) {
|
|
95
|
-
clearLine(this.stderr);
|
|
96
|
-
this.stderr.write(`${this.format.red("error")} ${msg}\n`);
|
|
97
|
-
}
|
|
98
|
-
log(msg) {
|
|
99
|
-
clearLine(this.stdout);
|
|
100
|
-
this.stdout.write(`${msg}\n`);
|
|
101
|
-
}
|
|
102
|
-
warn(msg) {
|
|
103
|
-
clearLine(this.stderr);
|
|
104
|
-
this.stderr.write(`${this.format.yellow("warning")} ${msg}\n`);
|
|
105
|
-
}
|
|
106
|
-
info(msg) {
|
|
107
|
-
clearLine(this.stdout);
|
|
108
|
-
this.stdout.write(`${this.format.blue("info")} ${msg}\n`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
class Config {
|
|
113
|
-
cwd = path.resolve(process$1.cwd());
|
|
114
|
-
configFile = path.join(os.homedir(), ".cals-config.json");
|
|
115
|
-
cacheDir = cachedir("cals-cli");
|
|
116
|
-
agent = new https.Agent({
|
|
117
|
-
keepAlive: true,
|
|
118
|
-
});
|
|
119
|
-
configCached = undefined;
|
|
120
|
-
get config() {
|
|
121
|
-
const existingConfig = this.configCached;
|
|
122
|
-
if (existingConfig !== undefined) {
|
|
123
|
-
return existingConfig;
|
|
124
|
-
}
|
|
125
|
-
const config = this.readConfig();
|
|
126
|
-
this.configCached = config;
|
|
127
|
-
return config;
|
|
128
|
-
}
|
|
129
|
-
readConfig() {
|
|
130
|
-
if (!fs.existsSync(this.configFile)) {
|
|
131
|
-
return {};
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(fs.readFileSync(this.configFile, "utf-8"));
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
console.error("Failed", e);
|
|
138
|
-
throw new Error("Failed to read config");
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
getConfig(key) {
|
|
142
|
-
return this.config[key];
|
|
143
|
-
}
|
|
144
|
-
requireConfig(key) {
|
|
145
|
-
const result = this.config[key];
|
|
146
|
-
if (result === undefined) {
|
|
147
|
-
throw Error(`Configuration for ${key} missing. Add manually to ${this.configFile}`);
|
|
148
|
-
}
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
updateConfig(key, value) {
|
|
152
|
-
const updatedConfig = {
|
|
153
|
-
...this.readConfig(),
|
|
154
|
-
[key]: value, // undefined will remove
|
|
155
|
-
};
|
|
156
|
-
fs.writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
|
|
157
|
-
this.configCached = updatedConfig;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
12
|
+
var version = "3.14.0";
|
|
160
13
|
|
|
161
14
|
function uniq(array) {
|
|
162
15
|
return Array.from(new Set(array));
|
|
@@ -164,53 +17,6 @@ function uniq(array) {
|
|
|
164
17
|
|
|
165
18
|
var type = "object";
|
|
166
19
|
var properties = {
|
|
167
|
-
snyk: {
|
|
168
|
-
type: "object",
|
|
169
|
-
properties: {
|
|
170
|
-
accountId: {
|
|
171
|
-
type: "string"
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
required: [
|
|
175
|
-
"accountId"
|
|
176
|
-
]
|
|
177
|
-
},
|
|
178
|
-
github: {
|
|
179
|
-
type: "object",
|
|
180
|
-
properties: {
|
|
181
|
-
users: {
|
|
182
|
-
type: "array",
|
|
183
|
-
items: {
|
|
184
|
-
$ref: "#/definitions/User"
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
teams: {
|
|
188
|
-
type: "array",
|
|
189
|
-
items: {
|
|
190
|
-
type: "object",
|
|
191
|
-
properties: {
|
|
192
|
-
organization: {
|
|
193
|
-
type: "string"
|
|
194
|
-
},
|
|
195
|
-
teams: {
|
|
196
|
-
type: "array",
|
|
197
|
-
items: {
|
|
198
|
-
$ref: "#/definitions/Team"
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
required: [
|
|
203
|
-
"organization",
|
|
204
|
-
"teams"
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
required: [
|
|
210
|
-
"teams",
|
|
211
|
-
"users"
|
|
212
|
-
]
|
|
213
|
-
},
|
|
214
20
|
projects: {
|
|
215
21
|
type: "array",
|
|
216
22
|
items: {
|
|
@@ -219,105 +25,9 @@ var properties = {
|
|
|
219
25
|
}
|
|
220
26
|
};
|
|
221
27
|
var required = [
|
|
222
|
-
"github",
|
|
223
28
|
"projects"
|
|
224
29
|
];
|
|
225
30
|
var definitions = {
|
|
226
|
-
User: {
|
|
227
|
-
anyOf: [
|
|
228
|
-
{
|
|
229
|
-
$ref: "#/definitions/UserBot"
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
$ref: "#/definitions/UserEmployee"
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
$ref: "#/definitions/UserExternal"
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
},
|
|
239
|
-
UserBot: {
|
|
240
|
-
type: "object",
|
|
241
|
-
properties: {
|
|
242
|
-
type: {
|
|
243
|
-
type: "string",
|
|
244
|
-
"const": "bot"
|
|
245
|
-
},
|
|
246
|
-
login: {
|
|
247
|
-
type: "string"
|
|
248
|
-
},
|
|
249
|
-
name: {
|
|
250
|
-
type: "string"
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
required: [
|
|
254
|
-
"login",
|
|
255
|
-
"name",
|
|
256
|
-
"type"
|
|
257
|
-
]
|
|
258
|
-
},
|
|
259
|
-
UserEmployee: {
|
|
260
|
-
type: "object",
|
|
261
|
-
properties: {
|
|
262
|
-
type: {
|
|
263
|
-
type: "string",
|
|
264
|
-
"const": "employee"
|
|
265
|
-
},
|
|
266
|
-
login: {
|
|
267
|
-
type: "string"
|
|
268
|
-
},
|
|
269
|
-
capraUsername: {
|
|
270
|
-
type: "string"
|
|
271
|
-
},
|
|
272
|
-
name: {
|
|
273
|
-
type: "string"
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
required: [
|
|
277
|
-
"capraUsername",
|
|
278
|
-
"login",
|
|
279
|
-
"name",
|
|
280
|
-
"type"
|
|
281
|
-
]
|
|
282
|
-
},
|
|
283
|
-
UserExternal: {
|
|
284
|
-
type: "object",
|
|
285
|
-
properties: {
|
|
286
|
-
type: {
|
|
287
|
-
type: "string",
|
|
288
|
-
"const": "external"
|
|
289
|
-
},
|
|
290
|
-
login: {
|
|
291
|
-
type: "string"
|
|
292
|
-
},
|
|
293
|
-
name: {
|
|
294
|
-
type: "string"
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
required: [
|
|
298
|
-
"login",
|
|
299
|
-
"name",
|
|
300
|
-
"type"
|
|
301
|
-
]
|
|
302
|
-
},
|
|
303
|
-
Team: {
|
|
304
|
-
type: "object",
|
|
305
|
-
properties: {
|
|
306
|
-
name: {
|
|
307
|
-
type: "string"
|
|
308
|
-
},
|
|
309
|
-
members: {
|
|
310
|
-
type: "array",
|
|
311
|
-
items: {
|
|
312
|
-
type: "string"
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
required: [
|
|
317
|
-
"members",
|
|
318
|
-
"name"
|
|
319
|
-
]
|
|
320
|
-
},
|
|
321
31
|
Project: {
|
|
322
32
|
type: "object",
|
|
323
33
|
properties: {
|
|
@@ -337,12 +47,6 @@ var definitions = {
|
|
|
337
47
|
items: {
|
|
338
48
|
$ref: "#/definitions/DefinitionRepo"
|
|
339
49
|
}
|
|
340
|
-
},
|
|
341
|
-
teams: {
|
|
342
|
-
type: "array",
|
|
343
|
-
items: {
|
|
344
|
-
$ref: "#/definitions/RepoTeam"
|
|
345
|
-
}
|
|
346
50
|
}
|
|
347
51
|
},
|
|
348
52
|
required: [
|
|
@@ -355,10 +59,6 @@ var definitions = {
|
|
|
355
59
|
items: {
|
|
356
60
|
type: "string"
|
|
357
61
|
}
|
|
358
|
-
},
|
|
359
|
-
responsible: {
|
|
360
|
-
description: "Some external-defined entity being responsible for the project.",
|
|
361
|
-
type: "string"
|
|
362
62
|
}
|
|
363
63
|
},
|
|
364
64
|
required: [
|
|
@@ -380,28 +80,6 @@ var definitions = {
|
|
|
380
80
|
},
|
|
381
81
|
archived: {
|
|
382
82
|
type: "boolean"
|
|
383
|
-
},
|
|
384
|
-
issues: {
|
|
385
|
-
type: "boolean"
|
|
386
|
-
},
|
|
387
|
-
wiki: {
|
|
388
|
-
type: "boolean"
|
|
389
|
-
},
|
|
390
|
-
teams: {
|
|
391
|
-
type: "array",
|
|
392
|
-
items: {
|
|
393
|
-
$ref: "#/definitions/RepoTeam"
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
snyk: {
|
|
397
|
-
type: "boolean"
|
|
398
|
-
},
|
|
399
|
-
"public": {
|
|
400
|
-
type: "boolean"
|
|
401
|
-
},
|
|
402
|
-
responsible: {
|
|
403
|
-
description: "Some external-defined entity being responsible for the repository.\n\nWill override the project-defined responsible.",
|
|
404
|
-
type: "string"
|
|
405
83
|
}
|
|
406
84
|
},
|
|
407
85
|
required: [
|
|
@@ -422,29 +100,6 @@ var definitions = {
|
|
|
422
100
|
"name",
|
|
423
101
|
"project"
|
|
424
102
|
]
|
|
425
|
-
},
|
|
426
|
-
RepoTeam: {
|
|
427
|
-
type: "object",
|
|
428
|
-
properties: {
|
|
429
|
-
name: {
|
|
430
|
-
type: "string"
|
|
431
|
-
},
|
|
432
|
-
permission: {
|
|
433
|
-
$ref: "#/definitions/Permission"
|
|
434
|
-
}
|
|
435
|
-
},
|
|
436
|
-
required: [
|
|
437
|
-
"name",
|
|
438
|
-
"permission"
|
|
439
|
-
]
|
|
440
|
-
},
|
|
441
|
-
Permission: {
|
|
442
|
-
"enum": [
|
|
443
|
-
"admin",
|
|
444
|
-
"pull",
|
|
445
|
-
"push"
|
|
446
|
-
],
|
|
447
|
-
type: "string"
|
|
448
103
|
}
|
|
449
104
|
};
|
|
450
105
|
var $schema = "http://json-schema.org/draft-07/schema#";
|
|
@@ -456,9 +111,6 @@ var schema = {
|
|
|
456
111
|
$schema: $schema
|
|
457
112
|
};
|
|
458
113
|
|
|
459
|
-
function getTeamId(org, teamName) {
|
|
460
|
-
return `${org}/${teamName}`;
|
|
461
|
-
}
|
|
462
114
|
function getRepoId(orgName, repoName) {
|
|
463
115
|
return `${orgName}/${repoName}`;
|
|
464
116
|
}
|
|
@@ -470,33 +122,6 @@ function checkAgainstSchema(value) {
|
|
|
470
122
|
: { error: ajv.errorsText() ?? "Unknown error" };
|
|
471
123
|
}
|
|
472
124
|
function requireValidDefinition(definition) {
|
|
473
|
-
// Verify no duplicates in users and extract known logins.
|
|
474
|
-
const loginList = definition.github.users.reduce((acc, user) => {
|
|
475
|
-
if (acc.includes(user.login)) {
|
|
476
|
-
throw new Error(`Duplicate login: ${user.login}`);
|
|
477
|
-
}
|
|
478
|
-
return [...acc, user.login];
|
|
479
|
-
}, []);
|
|
480
|
-
// Verify no duplicates in teams and extract team names.
|
|
481
|
-
const teamIdList = definition.github.teams.reduce((acc, orgTeams) => {
|
|
482
|
-
return orgTeams.teams.reduce((acc1, team) => {
|
|
483
|
-
const id = getTeamId(orgTeams.organization, team.name);
|
|
484
|
-
if (acc1.includes(id)) {
|
|
485
|
-
throw new Error(`Duplicate team: ${id}`);
|
|
486
|
-
}
|
|
487
|
-
return [...acc1, id];
|
|
488
|
-
}, acc);
|
|
489
|
-
}, []);
|
|
490
|
-
// Verify team members exists as users.
|
|
491
|
-
definition.github.teams
|
|
492
|
-
.flatMap((it) => it.teams)
|
|
493
|
-
.forEach((team) => {
|
|
494
|
-
team.members.forEach((login) => {
|
|
495
|
-
if (!loginList.includes(login)) {
|
|
496
|
-
throw new Error(`Team member ${login} in team ${team.name} is not registered in user list`);
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
});
|
|
500
125
|
// Verify no duplicates in project names.
|
|
501
126
|
definition.projects.reduce((acc, project) => {
|
|
502
127
|
if (acc.includes(project.name)) {
|
|
@@ -504,25 +129,6 @@ function requireValidDefinition(definition) {
|
|
|
504
129
|
}
|
|
505
130
|
return [...acc, project.name];
|
|
506
131
|
}, []);
|
|
507
|
-
definition.projects.forEach((project) => {
|
|
508
|
-
project.github.forEach((org) => {
|
|
509
|
-
(org.teams || []).forEach((team) => {
|
|
510
|
-
const id = getTeamId(org.organization, team.name);
|
|
511
|
-
if (!teamIdList.includes(id)) {
|
|
512
|
-
throw new Error(`Project team ${id} in project ${project.name} is not registered in team list`);
|
|
513
|
-
}
|
|
514
|
-
}) // Verify repo teams exists as teams.
|
|
515
|
-
;
|
|
516
|
-
(org.repos || []).forEach((repo) => {
|
|
517
|
-
(repo.teams || []).forEach((team) => {
|
|
518
|
-
const id = getTeamId(org.organization, team.name);
|
|
519
|
-
if (!teamIdList.includes(id)) {
|
|
520
|
-
throw new Error(`Repo team ${id} for repo ${repo.name} in project ${project.name} is not registered in team list`);
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
});
|
|
524
|
-
});
|
|
525
|
-
});
|
|
526
132
|
// Verify no duplicates in repos.
|
|
527
133
|
definition.projects
|
|
528
134
|
.flatMap((project) => project.github.flatMap((org) => (org.repos || []).map((repo) => getRepoId(org.organization, repo.name))))
|
|
@@ -571,7 +177,7 @@ function getGitHubOrgs(definition) {
|
|
|
571
177
|
return uniq(githubOrganizations);
|
|
572
178
|
}
|
|
573
179
|
|
|
574
|
-
var index$
|
|
180
|
+
var index$1 = /*#__PURE__*/Object.freeze({
|
|
575
181
|
__proto__: null,
|
|
576
182
|
DefinitionFile: DefinitionFile,
|
|
577
183
|
getGitHubOrgs: getGitHubOrgs,
|
|
@@ -580,23 +186,16 @@ var index$3 = /*#__PURE__*/Object.freeze({
|
|
|
580
186
|
parseDefinition: parseDefinition
|
|
581
187
|
});
|
|
582
188
|
|
|
583
|
-
function createReporter(argv) {
|
|
584
|
-
return new Reporter({
|
|
585
|
-
verbose: !!argv.verbose,
|
|
586
|
-
nonInteractive: !!argv.nonInteractive,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
|
|
590
189
|
class GitHubTokenCliProvider {
|
|
591
190
|
keyringService = "cals";
|
|
592
191
|
keyringAccount = "github-token";
|
|
593
192
|
async getToken() {
|
|
594
|
-
if (
|
|
595
|
-
return
|
|
193
|
+
if (process__default.env.CALS_GITHUB_TOKEN) {
|
|
194
|
+
return process__default.env.CALS_GITHUB_TOKEN;
|
|
596
195
|
}
|
|
597
196
|
const result = await keytar.getPassword(this.keyringService, this.keyringAccount);
|
|
598
197
|
if (result == null) {
|
|
599
|
-
|
|
198
|
+
process__default.stderr.write("No token found. Register using `cals github set-token`\n");
|
|
600
199
|
return undefined;
|
|
601
200
|
}
|
|
602
201
|
return result;
|
|
@@ -609,26 +208,12 @@ class GitHubTokenCliProvider {
|
|
|
609
208
|
}
|
|
610
209
|
}
|
|
611
210
|
|
|
612
|
-
async function undefinedForNotFound(value) {
|
|
613
|
-
try {
|
|
614
|
-
return await value;
|
|
615
|
-
}
|
|
616
|
-
catch (e) {
|
|
617
|
-
if (e.name === "HttpError" && e.status === 404) {
|
|
618
|
-
return undefined;
|
|
619
|
-
}
|
|
620
|
-
throw e;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
211
|
class GitHubService {
|
|
625
|
-
config;
|
|
626
212
|
octokit;
|
|
627
213
|
cache;
|
|
628
214
|
tokenProvider;
|
|
629
215
|
semaphore;
|
|
630
216
|
constructor(props) {
|
|
631
|
-
this.config = props.config;
|
|
632
217
|
this.octokit = props.octokit;
|
|
633
218
|
this.cache = props.cache;
|
|
634
219
|
this.tokenProvider = props.tokenProvider;
|
|
@@ -636,7 +221,6 @@ class GitHubService {
|
|
|
636
221
|
// can maximize concurrency all other places.
|
|
637
222
|
this.semaphore = pLimit(6);
|
|
638
223
|
this.octokit.hook.wrap("request", async (request, options) => {
|
|
639
|
-
this._requestCount++;
|
|
640
224
|
if (options.method !== "GET") {
|
|
641
225
|
return this.semaphore(() => request(options));
|
|
642
226
|
}
|
|
@@ -696,10 +280,6 @@ class GitHubService {
|
|
|
696
280
|
return response;
|
|
697
281
|
});
|
|
698
282
|
}
|
|
699
|
-
_requestCount = 0;
|
|
700
|
-
get requestCount() {
|
|
701
|
-
return this._requestCount;
|
|
702
|
-
}
|
|
703
283
|
async runGraphqlQuery(query) {
|
|
704
284
|
const token = await this.tokenProvider.getToken();
|
|
705
285
|
if (token === undefined) {
|
|
@@ -716,7 +296,6 @@ class GitHubService {
|
|
|
716
296
|
method: "POST",
|
|
717
297
|
headers,
|
|
718
298
|
body: JSON.stringify({ query }),
|
|
719
|
-
agent: this.config.agent,
|
|
720
299
|
});
|
|
721
300
|
requestDuration = performance.now() - requestStart;
|
|
722
301
|
return result;
|
|
@@ -796,987 +375,28 @@ class GitHubService {
|
|
|
796
375
|
return repos.sort((a, b) => a.name.localeCompare(b.name));
|
|
797
376
|
});
|
|
798
377
|
}
|
|
799
|
-
async getOrgMembersList(org) {
|
|
800
|
-
const options = this.octokit.orgs.listMembers.endpoint.merge({
|
|
801
|
-
org,
|
|
802
|
-
});
|
|
803
|
-
return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
|
|
804
|
-
}
|
|
805
|
-
async getOrgMembersInvitedList(org) {
|
|
806
|
-
const options = this.octokit.orgs.listPendingInvitations.endpoint.merge({
|
|
807
|
-
org,
|
|
808
|
-
});
|
|
809
|
-
return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
|
|
810
|
-
}
|
|
811
|
-
async getOrgMembersListIncludingInvited(org) {
|
|
812
|
-
return [
|
|
813
|
-
...(await this.getOrgMembersList(org)).map((it) => ({
|
|
814
|
-
type: "member",
|
|
815
|
-
login: it.login,
|
|
816
|
-
data: it,
|
|
817
|
-
})),
|
|
818
|
-
...(await this.getOrgMembersInvitedList(org)).map((it) => ({
|
|
819
|
-
type: "invited",
|
|
820
|
-
// TODO: Fix ?? case properly
|
|
821
|
-
login: it.login ?? "invalid",
|
|
822
|
-
data: it,
|
|
823
|
-
})),
|
|
824
|
-
];
|
|
825
|
-
}
|
|
826
|
-
async getRepository(owner, repo) {
|
|
827
|
-
return this.cache.json(`get-repository-${owner}-${repo}`, async () => {
|
|
828
|
-
const response = await undefinedForNotFound(this.octokit.repos.get({
|
|
829
|
-
owner,
|
|
830
|
-
repo,
|
|
831
|
-
}));
|
|
832
|
-
return response === undefined ? undefined : response.data;
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
async getRepositoryTeamsList(repo) {
|
|
836
|
-
return this.cache.json(`repository-teams-list-${repo.id}`, async () => {
|
|
837
|
-
const options = this.octokit.repos.listTeams.endpoint.merge({
|
|
838
|
-
owner: repo.owner.login,
|
|
839
|
-
repo: repo.name,
|
|
840
|
-
});
|
|
841
|
-
return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
async getRepositoryHooks(owner, repo) {
|
|
845
|
-
return this.cache.json(`repository-hooks-${owner}-${repo}`, async () => {
|
|
846
|
-
const options = this.octokit.repos.listWebhooks.endpoint.merge({
|
|
847
|
-
owner,
|
|
848
|
-
repo,
|
|
849
|
-
});
|
|
850
|
-
return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
async getOrg(org) {
|
|
854
|
-
const orgResponse = await this.octokit.orgs.get({
|
|
855
|
-
org,
|
|
856
|
-
});
|
|
857
|
-
return orgResponse.data;
|
|
858
|
-
}
|
|
859
|
-
async getTeamList(org) {
|
|
860
|
-
return this.cache.json(`team-list-${org.login}`, async () => {
|
|
861
|
-
const options = this.octokit.teams.list.endpoint.merge({
|
|
862
|
-
org: org.login,
|
|
863
|
-
});
|
|
864
|
-
return (await this.octokit.paginate(options));
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
async getTeamMemberList(org, team) {
|
|
868
|
-
return this.cache.json(`team-member-list-${team.id}`, async () => {
|
|
869
|
-
const options = this.octokit.teams.listMembersInOrg.endpoint.merge({
|
|
870
|
-
org: org.login,
|
|
871
|
-
team_slug: team.slug,
|
|
872
|
-
});
|
|
873
|
-
return (await this.octokit.paginate(options));
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
async getTeamMemberInvitedList(org, team) {
|
|
877
|
-
return this.cache.json(`team-member-invited-list-${team.id}`, async () => {
|
|
878
|
-
const options = this.octokit.teams.listPendingInvitationsInOrg.endpoint.merge({
|
|
879
|
-
org: org.login,
|
|
880
|
-
team_slug: team.slug,
|
|
881
|
-
});
|
|
882
|
-
return (await this.octokit.paginate(options));
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
async getTeamMemberListIncludingInvited(org, team) {
|
|
886
|
-
return [
|
|
887
|
-
...(await this.getTeamMemberList(org, team)).map((it) => ({
|
|
888
|
-
type: "member",
|
|
889
|
-
login: it.login,
|
|
890
|
-
data: it,
|
|
891
|
-
})),
|
|
892
|
-
...(await this.getTeamMemberInvitedList(org, team)).map((it) => ({
|
|
893
|
-
type: "invited",
|
|
894
|
-
// TODO: Fix ?? case properly
|
|
895
|
-
login: it.login ?? "invalid",
|
|
896
|
-
data: it,
|
|
897
|
-
})),
|
|
898
|
-
];
|
|
899
|
-
}
|
|
900
|
-
async getSearchedPullRequestList(owner) {
|
|
901
|
-
// NOTE: Changes to this must by synced with SearchedPullRequestListQueryResult.
|
|
902
|
-
const getQuery = (after) => `{
|
|
903
|
-
search(
|
|
904
|
-
query: "is:open is:pr user:${owner} owner:${owner} archived:false",
|
|
905
|
-
type: ISSUE,
|
|
906
|
-
first: 50${after === null
|
|
907
|
-
? ""
|
|
908
|
-
: `,
|
|
909
|
-
after: "${after}"`}
|
|
910
|
-
) {
|
|
911
|
-
pageInfo {
|
|
912
|
-
hasNextPage
|
|
913
|
-
endCursor
|
|
914
|
-
}
|
|
915
|
-
edges {
|
|
916
|
-
node {
|
|
917
|
-
__typename
|
|
918
|
-
... on PullRequest {
|
|
919
|
-
number
|
|
920
|
-
baseRepository {
|
|
921
|
-
name
|
|
922
|
-
owner {
|
|
923
|
-
login
|
|
924
|
-
}
|
|
925
|
-
defaultBranchRef {
|
|
926
|
-
name
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
author {
|
|
930
|
-
login
|
|
931
|
-
}
|
|
932
|
-
title
|
|
933
|
-
commits(first: 3) {
|
|
934
|
-
nodes {
|
|
935
|
-
commit {
|
|
936
|
-
messageHeadline
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
createdAt
|
|
941
|
-
updatedAt
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}`;
|
|
947
|
-
const pulls = [];
|
|
948
|
-
let after = null;
|
|
949
|
-
while (true) {
|
|
950
|
-
const query = getQuery(after);
|
|
951
|
-
const res = await this.runGraphqlQuery(query);
|
|
952
|
-
pulls.push(...res.search.edges.map((it) => it.node));
|
|
953
|
-
if (!res.search.pageInfo.hasNextPage) {
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
after = res.search.pageInfo.endCursor;
|
|
957
|
-
}
|
|
958
|
-
return pulls.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
959
|
-
}
|
|
960
|
-
async getHasVulnerabilityAlertsEnabled(owner, repo) {
|
|
961
|
-
try {
|
|
962
|
-
const response = await this.octokit.repos.checkVulnerabilityAlerts({
|
|
963
|
-
owner: owner,
|
|
964
|
-
repo: repo,
|
|
965
|
-
});
|
|
966
|
-
if (response.status !== 204) {
|
|
967
|
-
console.log(response);
|
|
968
|
-
throw new Error("Unknown response - see previous log line");
|
|
969
|
-
}
|
|
970
|
-
return true;
|
|
971
|
-
}
|
|
972
|
-
catch (e) {
|
|
973
|
-
if (e.status === 404) {
|
|
974
|
-
return false;
|
|
975
|
-
}
|
|
976
|
-
throw e;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
async enableVulnerabilityAlerts(owner, repo) {
|
|
980
|
-
await this.octokit.repos.enableVulnerabilityAlerts({
|
|
981
|
-
owner: owner,
|
|
982
|
-
repo: repo,
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Get the vulnerability alerts for a repository.
|
|
987
|
-
*/
|
|
988
|
-
async getVulnerabilityAlerts(owner, repo) {
|
|
989
|
-
// NOTE: Changes to this must by synced with VulnerabilityAlertsQueryResult.
|
|
990
|
-
const getQuery = (after) => `{
|
|
991
|
-
repository(owner: "${owner}", name: "${repo}") {
|
|
992
|
-
vulnerabilityAlerts(first: 100${after === null ? "" : `, after: "${after}"`}) {
|
|
993
|
-
pageInfo {
|
|
994
|
-
hasNextPage
|
|
995
|
-
endCursor
|
|
996
|
-
}
|
|
997
|
-
edges {
|
|
998
|
-
node {
|
|
999
|
-
state
|
|
1000
|
-
dismissReason
|
|
1001
|
-
vulnerableManifestFilename
|
|
1002
|
-
vulnerableManifestPath
|
|
1003
|
-
vulnerableRequirements
|
|
1004
|
-
securityAdvisory {
|
|
1005
|
-
description
|
|
1006
|
-
identifiers { type value }
|
|
1007
|
-
references { url }
|
|
1008
|
-
severity
|
|
1009
|
-
}
|
|
1010
|
-
securityVulnerability {
|
|
1011
|
-
package { name ecosystem }
|
|
1012
|
-
firstPatchedVersion { identifier }
|
|
1013
|
-
vulnerableVersionRange
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
}`;
|
|
1020
|
-
return this.cache.json(`vulnerability-alerts-${owner}-${repo}`, async () => {
|
|
1021
|
-
const result = [];
|
|
1022
|
-
let after = null;
|
|
1023
|
-
while (true) {
|
|
1024
|
-
const query = getQuery(after);
|
|
1025
|
-
const res = await this.runGraphqlQuery(query);
|
|
1026
|
-
result.push(...(res.repository?.vulnerabilityAlerts.edges?.map((it) => it.node) ?? []));
|
|
1027
|
-
if (!res.repository?.vulnerabilityAlerts.pageInfo.hasNextPage) {
|
|
1028
|
-
break;
|
|
1029
|
-
}
|
|
1030
|
-
after = res.repository?.vulnerabilityAlerts.pageInfo.endCursor;
|
|
1031
|
-
}
|
|
1032
|
-
return result;
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
/**
|
|
1036
|
-
* Get the Renovate Dependency Dashboard issue.
|
|
1037
|
-
*/
|
|
1038
|
-
async getRenovateDependencyDashboardIssue(owner, repo) {
|
|
1039
|
-
// NOTE: Changes to this must by synced with RenovateDependencyDashboardIssueQueryResult.
|
|
1040
|
-
const getQuery = (after) => `{
|
|
1041
|
-
repository(owner: "${owner}", name: "${repo}") {
|
|
1042
|
-
issues(
|
|
1043
|
-
orderBy: {field: UPDATED_AT, direction: DESC},
|
|
1044
|
-
filterBy: {createdBy: "renovate[bot]"},
|
|
1045
|
-
states: [OPEN],
|
|
1046
|
-
first: 100${after === null ? "" : `, after: "${after}"`}
|
|
1047
|
-
) {
|
|
1048
|
-
pageInfo {
|
|
1049
|
-
hasNextPage
|
|
1050
|
-
endCursor
|
|
1051
|
-
}
|
|
1052
|
-
edges {
|
|
1053
|
-
node {
|
|
1054
|
-
number
|
|
1055
|
-
state
|
|
1056
|
-
title
|
|
1057
|
-
body
|
|
1058
|
-
userContentEdits(first: 5) {
|
|
1059
|
-
nodes {
|
|
1060
|
-
createdAt
|
|
1061
|
-
editor {
|
|
1062
|
-
login
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}`;
|
|
1071
|
-
const issues = await this.cache.json(`renovate-bot-issues-${owner}-${repo}`, async () => {
|
|
1072
|
-
const result = [];
|
|
1073
|
-
let after = null;
|
|
1074
|
-
while (true) {
|
|
1075
|
-
const query = getQuery(after);
|
|
1076
|
-
const res = await this.runGraphqlQuery(query);
|
|
1077
|
-
const nodes = res.repository?.issues.edges?.map((it) => it.node) ?? [];
|
|
1078
|
-
result.push(...nodes
|
|
1079
|
-
.filter((it) => it.title === "Dependency Dashboard")
|
|
1080
|
-
.map((it) => ({
|
|
1081
|
-
number: it.number,
|
|
1082
|
-
body: it.body,
|
|
1083
|
-
lastUpdatedByRenovate: it.userContentEdits?.nodes?.filter((it) => it.editor?.login === "renovate")?.[0]?.createdAt ?? null,
|
|
1084
|
-
})));
|
|
1085
|
-
if (!res.repository?.issues.pageInfo.hasNextPage) {
|
|
1086
|
-
break;
|
|
1087
|
-
}
|
|
1088
|
-
after = res.repository?.issues.pageInfo.endCursor;
|
|
1089
|
-
}
|
|
1090
|
-
return result;
|
|
1091
|
-
});
|
|
1092
|
-
if (issues.length == 0) {
|
|
1093
|
-
return undefined;
|
|
1094
|
-
}
|
|
1095
|
-
return issues[0];
|
|
1096
|
-
}
|
|
1097
378
|
}
|
|
1098
|
-
async function createOctokit(
|
|
379
|
+
async function createOctokit(tokenProvider) {
|
|
1099
380
|
return new Octokit({
|
|
1100
381
|
auth: await tokenProvider.getToken(),
|
|
1101
|
-
request: {
|
|
1102
|
-
agent: config.agent,
|
|
1103
|
-
},
|
|
1104
382
|
});
|
|
1105
383
|
}
|
|
1106
384
|
async function createGitHubService(props) {
|
|
1107
385
|
const tokenProvider = props.tokenProvider ?? new GitHubTokenCliProvider();
|
|
1108
386
|
return new GitHubService({
|
|
1109
|
-
|
|
1110
|
-
octokit: await createOctokit(props.config, tokenProvider),
|
|
387
|
+
octokit: await createOctokit(tokenProvider),
|
|
1111
388
|
cache: props.cache,
|
|
1112
389
|
tokenProvider,
|
|
1113
390
|
});
|
|
1114
391
|
}
|
|
1115
392
|
|
|
1116
|
-
var index
|
|
393
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
1117
394
|
__proto__: null,
|
|
1118
395
|
GitHubService: GitHubService,
|
|
1119
396
|
createGitHubService: createGitHubService
|
|
1120
397
|
});
|
|
1121
398
|
|
|
1122
|
-
class SnykTokenCliProvider {
|
|
1123
|
-
keyringService = "cals";
|
|
1124
|
-
keyringAccount = "snyk-token";
|
|
1125
|
-
async getToken() {
|
|
1126
|
-
if (process$1.env.CALS_SNYK_TOKEN) {
|
|
1127
|
-
return process$1.env.CALS_SNYK_TOKEN;
|
|
1128
|
-
}
|
|
1129
|
-
const result = await keytar.getPassword(this.keyringService, this.keyringAccount);
|
|
1130
|
-
if (result == null) {
|
|
1131
|
-
process$1.stderr.write("No token found. Register using `cals snyk set-token`\n");
|
|
1132
|
-
return undefined;
|
|
1133
|
-
}
|
|
1134
|
-
return result;
|
|
1135
|
-
}
|
|
1136
|
-
async markInvalid() {
|
|
1137
|
-
await keytar.deletePassword(this.keyringService, this.keyringAccount);
|
|
1138
|
-
}
|
|
1139
|
-
async setToken(value) {
|
|
1140
|
-
await keytar.setPassword(this.keyringService, this.keyringAccount, value);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
class SnykService {
|
|
1145
|
-
config;
|
|
1146
|
-
tokenProvider;
|
|
1147
|
-
constructor(props) {
|
|
1148
|
-
this.config = props.config;
|
|
1149
|
-
this.tokenProvider = props.tokenProvider;
|
|
1150
|
-
}
|
|
1151
|
-
async getProjects(definition) {
|
|
1152
|
-
const snykAccountId = definition.snyk?.accountId;
|
|
1153
|
-
if (snykAccountId === undefined) {
|
|
1154
|
-
return [];
|
|
1155
|
-
}
|
|
1156
|
-
return this.getProjectsByAccountId(snykAccountId);
|
|
1157
|
-
}
|
|
1158
|
-
async getProjectsByAccountId(snykAccountId,
|
|
1159
|
-
/**
|
|
1160
|
-
* The slug name of a Snyk organization.
|
|
1161
|
-
*
|
|
1162
|
-
* NOTE: This is only used to construct the browsable URL for a given project, and is not being used
|
|
1163
|
-
* in API calls to Snyk.
|
|
1164
|
-
*
|
|
1165
|
-
* @default - the slug corresponding to Lifligs Snyk organization ("it").
|
|
1166
|
-
*/
|
|
1167
|
-
snykOrgSlugId) {
|
|
1168
|
-
const token = await this.tokenProvider.getToken();
|
|
1169
|
-
if (token === undefined) {
|
|
1170
|
-
throw new Error("Missing token for Snyk");
|
|
1171
|
-
}
|
|
1172
|
-
let backportedProjects = [];
|
|
1173
|
-
const snykRestApiVersion = "2025-11-05";
|
|
1174
|
-
let nextUrl = `/rest/orgs/${encodeURIComponent(snykAccountId)}/projects?version=${snykRestApiVersion}&meta.latest_dependency_total=true&meta.latest_issue_counts=true&limit=100`;
|
|
1175
|
-
/* The Snyk REST API only allows us to retrieve 100 projects at a time.
|
|
1176
|
-
* The "links.next" value in the response gives us a pointer to the next 100 results.
|
|
1177
|
-
* We continue calling the Snyk API and retrieving more projects until links.next is null
|
|
1178
|
-
* */
|
|
1179
|
-
while (nextUrl) {
|
|
1180
|
-
const response = await fetch(`https://api.snyk.io${nextUrl}`, {
|
|
1181
|
-
method: "GET",
|
|
1182
|
-
headers: {
|
|
1183
|
-
Accept: "application/json",
|
|
1184
|
-
Authorization: `token ${token}`,
|
|
1185
|
-
},
|
|
1186
|
-
agent: this.config.agent,
|
|
1187
|
-
});
|
|
1188
|
-
if (response.status === 401) {
|
|
1189
|
-
process$1.stderr.write("Unauthorized - removing token\n");
|
|
1190
|
-
await this.tokenProvider.markInvalid();
|
|
1191
|
-
}
|
|
1192
|
-
if (!response.ok) {
|
|
1193
|
-
throw new Error(`Response from Snyk not OK (${response.status}): ${JSON.stringify(response)}`);
|
|
1194
|
-
}
|
|
1195
|
-
// Check if the Sunset header is present in the response
|
|
1196
|
-
const sunsetHeader = response.headers.get("Sunset") || response.headers.get("sunset");
|
|
1197
|
-
if (sunsetHeader) {
|
|
1198
|
-
console.warn(`Snyk endpoint with version ${snykRestApiVersion} has been marked as deprecated with deprecation date ${sunsetHeader}`);
|
|
1199
|
-
}
|
|
1200
|
-
const jsonResponse = (await response.json());
|
|
1201
|
-
/* We transform the data to a standard format that we used for data from Snyk API v1 in order for
|
|
1202
|
-
the data to be backover compatible with existing consuments */
|
|
1203
|
-
backportedProjects = [
|
|
1204
|
-
...backportedProjects,
|
|
1205
|
-
...jsonResponse.data.map((project) => {
|
|
1206
|
-
return {
|
|
1207
|
-
id: project.id,
|
|
1208
|
-
name: project.attributes.name,
|
|
1209
|
-
type: project.attributes.type,
|
|
1210
|
-
created: project.attributes.created,
|
|
1211
|
-
origin: project.attributes.origin,
|
|
1212
|
-
testFrequency: project.attributes.settings.recurring_tests.frequency,
|
|
1213
|
-
isMonitored: project.attributes.status === "active",
|
|
1214
|
-
totalDependencies: project.meta.latest_dependency_total.total,
|
|
1215
|
-
issueCountsBySeverity: project.meta.latest_issue_counts,
|
|
1216
|
-
lastTestedDate: project.meta.latest_dependency_total.updated_at,
|
|
1217
|
-
browseUrl: `https://app.snyk.io/org/${snykOrgSlugId ?? "it"}/project/${project.id}`,
|
|
1218
|
-
};
|
|
1219
|
-
}),
|
|
1220
|
-
];
|
|
1221
|
-
/* Update nextUrl with pointer to the next page of results based
|
|
1222
|
-
* on the "links.next" field in the JSON response */
|
|
1223
|
-
nextUrl = jsonResponse.links.next;
|
|
1224
|
-
}
|
|
1225
|
-
return backportedProjects;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
function createSnykService(props) {
|
|
1229
|
-
return new SnykService({
|
|
1230
|
-
config: props.config,
|
|
1231
|
-
tokenProvider: props.tokenProvider ?? new SnykTokenCliProvider(),
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
function getGitHubRepo(snykProject) {
|
|
1236
|
-
if (snykProject.origin === "github") {
|
|
1237
|
-
const match = /^([^/]+)\/([^:]+)(:(.+))?$/.exec(snykProject.name);
|
|
1238
|
-
if (match === null) {
|
|
1239
|
-
throw Error(`Could not extract components from Snyk project name: ${snykProject.name} (id: ${snykProject.id})`);
|
|
1240
|
-
}
|
|
1241
|
-
return {
|
|
1242
|
-
owner: match[1],
|
|
1243
|
-
name: match[2],
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
if (snykProject.origin === "cli" && snykProject.remoteRepoUrl != null) {
|
|
1247
|
-
// The remoteRepoUrl can be overridden when using the CLI, so don't
|
|
1248
|
-
// fail if we cannot extract the value.
|
|
1249
|
-
const match = /github.com\/([^/]+)\/(.+)\.git$/.exec(snykProject.remoteRepoUrl);
|
|
1250
|
-
if (match === null) {
|
|
1251
|
-
return undefined;
|
|
1252
|
-
}
|
|
1253
|
-
return {
|
|
1254
|
-
owner: match[1],
|
|
1255
|
-
name: match[2],
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
return undefined;
|
|
1259
|
-
}
|
|
1260
|
-
function getGitHubRepoId(repo) {
|
|
1261
|
-
return repo ? `${repo.owner}/${repo.name}` : undefined;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
var index$1 = /*#__PURE__*/Object.freeze({
|
|
1265
|
-
__proto__: null,
|
|
1266
|
-
SnykService: SnykService,
|
|
1267
|
-
createSnykService: createSnykService,
|
|
1268
|
-
getGitHubRepo: getGitHubRepo,
|
|
1269
|
-
getGitHubRepoId: getGitHubRepoId
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
|
-
class SonarCloudTokenCliProvider {
|
|
1273
|
-
async getToken() {
|
|
1274
|
-
if (process$1.env.CALS_SONARCLOUD_TOKEN) {
|
|
1275
|
-
return Promise.resolve(process$1.env.CALS_SONARCLOUD_TOKEN);
|
|
1276
|
-
}
|
|
1277
|
-
process$1.stderr.write("No environmental variable found. Set variable `CALS_SONARCLOUD_TOKEN` to token value\n");
|
|
1278
|
-
return undefined;
|
|
1279
|
-
}
|
|
1280
|
-
async markInvalid() {
|
|
1281
|
-
await Promise.resolve();
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
class SonarCloudService {
|
|
1286
|
-
config;
|
|
1287
|
-
tokenProvider;
|
|
1288
|
-
constructor(props) {
|
|
1289
|
-
this.config = props.config;
|
|
1290
|
-
this.tokenProvider = props.tokenProvider;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Returns metrics for project with given key.
|
|
1294
|
-
* ONLY test coverage metrics are returned as of now
|
|
1295
|
-
*/
|
|
1296
|
-
async getMetricsByProjectKey(sonarCloudProjectKey) {
|
|
1297
|
-
const token = await this.tokenProvider.getToken();
|
|
1298
|
-
if (token === undefined) {
|
|
1299
|
-
throw new Error("Missing token for SonarCloud");
|
|
1300
|
-
}
|
|
1301
|
-
const response = await fetch(`https://sonarcloud.io/api/measures/component?component=${encodeURIComponent(sonarCloudProjectKey)}&metricKeys=coverage`, {
|
|
1302
|
-
method: "GET",
|
|
1303
|
-
headers: {
|
|
1304
|
-
Accept: "application/json",
|
|
1305
|
-
Authorization: `Basic ${Buffer.from(token.concat(":"), "utf8").toString("base64")}`,
|
|
1306
|
-
},
|
|
1307
|
-
agent: this.config.agent,
|
|
1308
|
-
});
|
|
1309
|
-
if (response.status === 401) {
|
|
1310
|
-
process$1.stderr.write("Unauthorized - removing token\n");
|
|
1311
|
-
await this.tokenProvider.markInvalid();
|
|
1312
|
-
}
|
|
1313
|
-
if (response.status === 404) {
|
|
1314
|
-
process$1.stderr.write(`Project ${sonarCloudProjectKey} does not exist in SonarCloud\n`);
|
|
1315
|
-
return undefined;
|
|
1316
|
-
}
|
|
1317
|
-
if (!response.ok) {
|
|
1318
|
-
throw new Error(`Response from SonarCloud not OK (${response.status}): ${await response.text()}`);
|
|
1319
|
-
}
|
|
1320
|
-
return (await response.json());
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
function createSonarCloudService(props) {
|
|
1324
|
-
return new SonarCloudService({
|
|
1325
|
-
config: props.config,
|
|
1326
|
-
tokenProvider: props.tokenProvider ?? new SonarCloudTokenCliProvider(),
|
|
1327
|
-
});
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
var index = /*#__PURE__*/Object.freeze({
|
|
1331
|
-
__proto__: null,
|
|
1332
|
-
SonarCloudService: SonarCloudService,
|
|
1333
|
-
SonarCloudTokenCliProvider: SonarCloudTokenCliProvider,
|
|
1334
|
-
createSonarCloudService: createSonarCloudService
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
class TestExecutor {
|
|
1338
|
-
shutdown = false;
|
|
1339
|
-
cleanupTask = null;
|
|
1340
|
-
usingWithCleanupTasks = false;
|
|
1341
|
-
tasks = [];
|
|
1342
|
-
/**
|
|
1343
|
-
* Check if we are currently in shutdown state due to user
|
|
1344
|
-
* asking to abort (Ctrl+C).
|
|
1345
|
-
*/
|
|
1346
|
-
checkCanContinue() {
|
|
1347
|
-
if (this.shutdown) {
|
|
1348
|
-
throw new Error("In shutdown mode - aborting");
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
async runTasks() {
|
|
1352
|
-
console.warn("Running cleanup tasks");
|
|
1353
|
-
while (true) {
|
|
1354
|
-
// We must run tasks in reverse order due to dependencies.
|
|
1355
|
-
// E.g. we cannot delete a Docker network before deleting
|
|
1356
|
-
// the container using it.
|
|
1357
|
-
const task = this.tasks.pop();
|
|
1358
|
-
if (task === undefined) {
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
try {
|
|
1362
|
-
await task();
|
|
1363
|
-
}
|
|
1364
|
-
catch (error) {
|
|
1365
|
-
console.error(error);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Register a task that will be run during cleanup phase.
|
|
1371
|
-
*/
|
|
1372
|
-
registerCleanupTask(task) {
|
|
1373
|
-
if (!this.usingWithCleanupTasks) {
|
|
1374
|
-
throw new Error("registerCleanupTask run outside runWithCleanupTasks");
|
|
1375
|
-
}
|
|
1376
|
-
this.tasks.push(task);
|
|
1377
|
-
this.checkCanContinue();
|
|
1378
|
-
}
|
|
1379
|
-
/**
|
|
1380
|
-
* Run the code block while ensuring we can run cleanup tasks
|
|
1381
|
-
* after the execution or if the process is interrupted.
|
|
1382
|
-
*
|
|
1383
|
-
* The main method of the program should be executed by using
|
|
1384
|
-
* this method.
|
|
1385
|
-
*/
|
|
1386
|
-
async runWithCleanupTasks(body) {
|
|
1387
|
-
try {
|
|
1388
|
-
strict.strictEqual(this.usingWithCleanupTasks, false);
|
|
1389
|
-
this.usingWithCleanupTasks = true;
|
|
1390
|
-
// We capture Ctrl+C so that we can perform cleanup task,
|
|
1391
|
-
// since the cleanup tasks involve async code which is not
|
|
1392
|
-
// supported during NodeJS normal exit handling.
|
|
1393
|
-
//
|
|
1394
|
-
// This will not abort the running tasks until after
|
|
1395
|
-
// we have completed the cleanup tasks. The running tasks
|
|
1396
|
-
// can stop earlier by calling checkCanContinue.
|
|
1397
|
-
process$1.on("SIGINT", () => {
|
|
1398
|
-
console.warn("Caught interrupt signal - forcing termination");
|
|
1399
|
-
if (this.cleanupTask != null) {
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
this.shutdown = true;
|
|
1403
|
-
this.cleanupTask = this.runTasks().then(() => {
|
|
1404
|
-
process$1.exit(1);
|
|
1405
|
-
});
|
|
1406
|
-
});
|
|
1407
|
-
await body(this);
|
|
1408
|
-
}
|
|
1409
|
-
catch (error) {
|
|
1410
|
-
console.error(error.stack || error.message || error);
|
|
1411
|
-
process$1.exitCode = 1;
|
|
1412
|
-
}
|
|
1413
|
-
finally {
|
|
1414
|
-
console.log("Reached finally block");
|
|
1415
|
-
this.usingWithCleanupTasks = false;
|
|
1416
|
-
if (this.cleanupTask == null) {
|
|
1417
|
-
this.cleanupTask = this.runTasks();
|
|
1418
|
-
}
|
|
1419
|
-
await this.cleanupTask;
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
function createTestExecutor() {
|
|
1424
|
-
return new TestExecutor();
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/**
|
|
1428
|
-
* Generate a value that can be used as part of resource names.
|
|
1429
|
-
*
|
|
1430
|
-
* Gives a value formatted as "yyyymmdd-xxxxxx", e.g. "20200523-3f2c87".
|
|
1431
|
-
*/
|
|
1432
|
-
function generateRunId() {
|
|
1433
|
-
const low = 0x100000;
|
|
1434
|
-
const high = 0xffffff;
|
|
1435
|
-
const range = high - low + 1;
|
|
1436
|
-
const now = new Date();
|
|
1437
|
-
return [
|
|
1438
|
-
now.getUTCFullYear(),
|
|
1439
|
-
(now.getUTCMonth() + 1).toString().padStart(2, "0"),
|
|
1440
|
-
now.getUTCDate().toString().padStart(2, "0"),
|
|
1441
|
-
"-",
|
|
1442
|
-
(Math.floor(Math.random() * range) + low).toString(16),
|
|
1443
|
-
].join("");
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Generate a name that can be used for a resource.
|
|
1447
|
-
*/
|
|
1448
|
-
function generateName(extra) {
|
|
1449
|
-
const s = extra === undefined ? "" : `-${extra}`;
|
|
1450
|
-
return `autotest-${generateRunId()}${s}`;
|
|
1451
|
-
}
|
|
1452
|
-
/**
|
|
1453
|
-
* Create a new Docker network.
|
|
1454
|
-
*/
|
|
1455
|
-
async function createNetwork(executor) {
|
|
1456
|
-
executor.checkCanContinue();
|
|
1457
|
-
const networkName = generateName();
|
|
1458
|
-
await execa("docker", ["network", "create", networkName]);
|
|
1459
|
-
const lsRes = await execa("docker", [
|
|
1460
|
-
"network",
|
|
1461
|
-
"ls",
|
|
1462
|
-
"-q",
|
|
1463
|
-
"-f",
|
|
1464
|
-
`name=${networkName}`,
|
|
1465
|
-
]);
|
|
1466
|
-
const networkId = lsRes.stdout.trim();
|
|
1467
|
-
console.log(`Network ${networkName} (${networkId}) created`);
|
|
1468
|
-
executor.registerCleanupTask(async () => {
|
|
1469
|
-
await execa("docker", ["network", "rm", networkId]);
|
|
1470
|
-
console.log(`Network ${networkName} (${networkId}) deleted`);
|
|
1471
|
-
});
|
|
1472
|
-
return {
|
|
1473
|
-
id: networkId,
|
|
1474
|
-
};
|
|
1475
|
-
}
|
|
1476
|
-
/**
|
|
1477
|
-
* Execute curl within the Docker network.
|
|
1478
|
-
*/
|
|
1479
|
-
async function curl(executor, network, ...args) {
|
|
1480
|
-
executor.checkCanContinue();
|
|
1481
|
-
const result = await execa("docker", [
|
|
1482
|
-
"run",
|
|
1483
|
-
"-i",
|
|
1484
|
-
"--rm",
|
|
1485
|
-
"--network",
|
|
1486
|
-
network.id,
|
|
1487
|
-
"byrnedo/alpine-curl",
|
|
1488
|
-
...args,
|
|
1489
|
-
]);
|
|
1490
|
-
return result.stdout;
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Repeatedly check for a condition until timeout.
|
|
1494
|
-
*
|
|
1495
|
-
* The condition can throw an error without aborting the loop.
|
|
1496
|
-
* To abort the condition must return false.
|
|
1497
|
-
*/
|
|
1498
|
-
async function pollForCondition({ container, attempts, waitIntervalSec, condition, }) {
|
|
1499
|
-
function log(value) {
|
|
1500
|
-
console.log(`${container.name} (poll): ${value}`);
|
|
1501
|
-
}
|
|
1502
|
-
container.executor.checkCanContinue();
|
|
1503
|
-
log(`Waiting for condition.. Checking ${attempts} times by ${waitIntervalSec} sec`);
|
|
1504
|
-
const start = performance.now();
|
|
1505
|
-
const duration = () => {
|
|
1506
|
-
const end = performance.now();
|
|
1507
|
-
return Math.round((end - start) / 1000);
|
|
1508
|
-
};
|
|
1509
|
-
for (let i = 0; i < attempts; i++) {
|
|
1510
|
-
container.executor.checkCanContinue();
|
|
1511
|
-
if (!(await isRunning(container.executor, container))) {
|
|
1512
|
-
throw new Error(`Container ${container.name} not running as expected`);
|
|
1513
|
-
}
|
|
1514
|
-
try {
|
|
1515
|
-
const result = await condition();
|
|
1516
|
-
if (!result) {
|
|
1517
|
-
break;
|
|
1518
|
-
}
|
|
1519
|
-
log(`Took ${duration()} seconds for condition`);
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
catch {
|
|
1523
|
-
log("Still waiting...");
|
|
1524
|
-
await new Promise((resolve) => setTimeout(resolve, waitIntervalSec * 1000));
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
throw new Error(`Failed to wait for container ${container.name}`);
|
|
1528
|
-
}
|
|
1529
|
-
async function waitForHttpOk({ container, url, attempts = 30, waitIntervalSec = 1, }) {
|
|
1530
|
-
await pollForCondition({
|
|
1531
|
-
container,
|
|
1532
|
-
attempts,
|
|
1533
|
-
waitIntervalSec,
|
|
1534
|
-
condition: async () => {
|
|
1535
|
-
await curl(container.executor, container.network, "-fsS", url);
|
|
1536
|
-
return true;
|
|
1537
|
-
},
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
async function waitForPostgresAvailable({ container, attempts = 30, waitIntervalSec = 1, username = "user", password = "password", dbname, }) {
|
|
1541
|
-
await pollForCondition({
|
|
1542
|
-
container,
|
|
1543
|
-
attempts,
|
|
1544
|
-
waitIntervalSec,
|
|
1545
|
-
condition: async () => {
|
|
1546
|
-
await execa("docker", [
|
|
1547
|
-
"exec",
|
|
1548
|
-
"-e",
|
|
1549
|
-
`PGPASSWORD=${password}`,
|
|
1550
|
-
container.name,
|
|
1551
|
-
"psql",
|
|
1552
|
-
"-h",
|
|
1553
|
-
"localhost",
|
|
1554
|
-
"-U",
|
|
1555
|
-
username,
|
|
1556
|
-
"-c",
|
|
1557
|
-
"select 1",
|
|
1558
|
-
dbname,
|
|
1559
|
-
]);
|
|
1560
|
-
return true;
|
|
1561
|
-
},
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
async function isRunning(executor, container) {
|
|
1565
|
-
executor.checkCanContinue();
|
|
1566
|
-
try {
|
|
1567
|
-
await execa("docker", ["inspect", container.name]);
|
|
1568
|
-
return true;
|
|
1569
|
-
}
|
|
1570
|
-
catch {
|
|
1571
|
-
return false;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
/**
|
|
1575
|
-
* A stream transform that injects a prefix into every line
|
|
1576
|
-
* and also forces every chunk to end with a newline so that
|
|
1577
|
-
* it can be interleaved with other output.
|
|
1578
|
-
*/
|
|
1579
|
-
class OutputPrefixTransform extends Transform {
|
|
1580
|
-
constructor(prefix) {
|
|
1581
|
-
super({
|
|
1582
|
-
objectMode: true,
|
|
1583
|
-
transform: (chunk, encoding, callback) => {
|
|
1584
|
-
let result = chunk.toString(encoding);
|
|
1585
|
-
if (result.endsWith("\n")) {
|
|
1586
|
-
result = result.slice(0, -1);
|
|
1587
|
-
}
|
|
1588
|
-
// Some loggers emit newline then ANSI reset code causing
|
|
1589
|
-
// blank lines if we do not remove the newline.
|
|
1590
|
-
// TODO: Consider removing all ANSI escape codes as it causes
|
|
1591
|
-
// some confusing output when interleaved.
|
|
1592
|
-
if (result.endsWith("\n\u001B[0m")) {
|
|
1593
|
-
result = result.slice(0, -5) + result.slice(-4);
|
|
1594
|
-
}
|
|
1595
|
-
result = `${prefix + result.replace(/\n/g, `\n${prefix}`)}\n`;
|
|
1596
|
-
callback(null, result);
|
|
1597
|
-
},
|
|
1598
|
-
});
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
function pipeToConsole(result, name) {
|
|
1602
|
-
result.stdout
|
|
1603
|
-
?.pipe(new OutputPrefixTransform(`${name}: `))
|
|
1604
|
-
.pipe(process$1.stdout);
|
|
1605
|
-
result.stderr
|
|
1606
|
-
?.pipe(new OutputPrefixTransform(`${name} (stderr): `))
|
|
1607
|
-
.pipe(process$1.stderr);
|
|
1608
|
-
}
|
|
1609
|
-
function checkPidRunning(pid) {
|
|
1610
|
-
try {
|
|
1611
|
-
process$1.kill(pid, 0);
|
|
1612
|
-
return true;
|
|
1613
|
-
}
|
|
1614
|
-
catch {
|
|
1615
|
-
return false;
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
async function getContainerId({ executor, name, hasFailed, pid, }) {
|
|
1619
|
-
function log(value) {
|
|
1620
|
-
console.log(`${name} (get-container-id): ${value}`);
|
|
1621
|
-
}
|
|
1622
|
-
async function check() {
|
|
1623
|
-
let result;
|
|
1624
|
-
try {
|
|
1625
|
-
result = (await execa("docker", ["inspect", name, "-f", "{{.Id}}"]))
|
|
1626
|
-
.stdout;
|
|
1627
|
-
}
|
|
1628
|
-
catch {
|
|
1629
|
-
result = null;
|
|
1630
|
-
}
|
|
1631
|
-
// Debugging to help us solve CALS-366.
|
|
1632
|
-
const ps = execa("docker", ["ps"]);
|
|
1633
|
-
pipeToConsole(ps, `${name} (ps)`);
|
|
1634
|
-
await ps;
|
|
1635
|
-
// Debugging to help us solve CALS-366.
|
|
1636
|
-
if (!checkPidRunning(pid)) {
|
|
1637
|
-
log("Process not running");
|
|
1638
|
-
}
|
|
1639
|
-
return result;
|
|
1640
|
-
}
|
|
1641
|
-
// If the container is not running, retry a few times to cover
|
|
1642
|
-
// the initial starting where we might check before the container
|
|
1643
|
-
// is running.
|
|
1644
|
-
// Increased from 25 to 100 to see if it helps for solving CALS-366.
|
|
1645
|
-
for (let i = 0; i < 100; i++) {
|
|
1646
|
-
if (i > 0) {
|
|
1647
|
-
// Delay a bit before checking again.
|
|
1648
|
-
log("Retrying in a bit...");
|
|
1649
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1650
|
-
}
|
|
1651
|
-
executor.checkCanContinue();
|
|
1652
|
-
if (hasFailed()) {
|
|
1653
|
-
break;
|
|
1654
|
-
}
|
|
1655
|
-
const id = await check();
|
|
1656
|
-
if (id !== null) {
|
|
1657
|
-
log(`Resolved to ${id}`);
|
|
1658
|
-
return id;
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
throw new Error(`Could not find ID for container with name ${name}`);
|
|
1662
|
-
}
|
|
1663
|
-
async function pullImage({ imageId }) {
|
|
1664
|
-
console.log(`Pulling ${imageId}`);
|
|
1665
|
-
const process = execa("docker", ["pull", imageId]);
|
|
1666
|
-
pipeToConsole(process, `pull-image (${imageId})`);
|
|
1667
|
-
await process;
|
|
1668
|
-
}
|
|
1669
|
-
async function checkImageExistsLocally({ imageId, }) {
|
|
1670
|
-
const result = await execa("docker", ["images", "-q", imageId]);
|
|
1671
|
-
const found = result.stdout != "";
|
|
1672
|
-
console.log(`image ${imageId} ${found ? "was present locally" : "was not found locally"}`);
|
|
1673
|
-
return found;
|
|
1674
|
-
}
|
|
1675
|
-
async function startContainer({ executor, network, imageId, alias, env, dockerArgs = [], pull = false, }) {
|
|
1676
|
-
executor.checkCanContinue();
|
|
1677
|
-
const containerName = generateName(alias);
|
|
1678
|
-
// Prefer pulling image here so that the call on getContainerId
|
|
1679
|
-
// will not time out due to pulling the image.
|
|
1680
|
-
// If pull is false, we will still fallback to pulling if we cannot
|
|
1681
|
-
// find the image locally.
|
|
1682
|
-
if (pull || !(await checkImageExistsLocally({ imageId }))) {
|
|
1683
|
-
await pullImage({
|
|
1684
|
-
imageId,
|
|
1685
|
-
});
|
|
1686
|
-
}
|
|
1687
|
-
const args = [
|
|
1688
|
-
"run",
|
|
1689
|
-
"--rm",
|
|
1690
|
-
"--network",
|
|
1691
|
-
network.id,
|
|
1692
|
-
"--name",
|
|
1693
|
-
containerName,
|
|
1694
|
-
...dockerArgs,
|
|
1695
|
-
];
|
|
1696
|
-
if (alias != null) {
|
|
1697
|
-
args.push(`--network-alias=${alias}`);
|
|
1698
|
-
}
|
|
1699
|
-
if (env != null) {
|
|
1700
|
-
for (const [key, value] of Object.entries(env)) {
|
|
1701
|
-
args.push("-e", `${key}=${value}`);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
args.push(imageId);
|
|
1705
|
-
console.log(`Starting ${imageId}`);
|
|
1706
|
-
const process = execa("docker", args);
|
|
1707
|
-
pipeToConsole(process, alias ?? containerName);
|
|
1708
|
-
let failed = false;
|
|
1709
|
-
process.catch(() => {
|
|
1710
|
-
failed = true;
|
|
1711
|
-
});
|
|
1712
|
-
if (!process.pid) {
|
|
1713
|
-
throw new Error("No process identifier (PID) was returned for the process that was started when running trying to run Docker container");
|
|
1714
|
-
}
|
|
1715
|
-
const id = await getContainerId({
|
|
1716
|
-
executor,
|
|
1717
|
-
name: containerName,
|
|
1718
|
-
hasFailed: () => failed,
|
|
1719
|
-
pid: process.pid,
|
|
1720
|
-
});
|
|
1721
|
-
executor.registerCleanupTask(async () => {
|
|
1722
|
-
console.log(`Stopping container ${containerName}`);
|
|
1723
|
-
const r = execa("docker", ["stop", containerName]);
|
|
1724
|
-
pipeToConsole(r, `${alias ?? containerName} (stop)`);
|
|
1725
|
-
try {
|
|
1726
|
-
await r;
|
|
1727
|
-
}
|
|
1728
|
-
catch (e) {
|
|
1729
|
-
if (!(e.stderr || "").includes("No such container")) {
|
|
1730
|
-
throw e;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
});
|
|
1734
|
-
return {
|
|
1735
|
-
id,
|
|
1736
|
-
name: containerName,
|
|
1737
|
-
network,
|
|
1738
|
-
process,
|
|
1739
|
-
executor,
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
async function runNpmRunScript(name, options) {
|
|
1743
|
-
const result = execa("npm", ["run", name], {
|
|
1744
|
-
env: options?.env,
|
|
1745
|
-
});
|
|
1746
|
-
pipeToConsole(result, `npm run ${name}`);
|
|
1747
|
-
await result;
|
|
1748
|
-
}
|
|
1749
|
-
/**
|
|
1750
|
-
* This likely does not cover all situations.
|
|
1751
|
-
*/
|
|
1752
|
-
async function getDockerHostAddress() {
|
|
1753
|
-
if (process$1.platform === "darwin" || process$1.platform === "win32") {
|
|
1754
|
-
return "host.docker.internal";
|
|
1755
|
-
}
|
|
1756
|
-
if (fs.existsSync("/.dockerenv")) {
|
|
1757
|
-
const process = execa("ip", ["route"]);
|
|
1758
|
-
pipeToConsole(process, "ip route");
|
|
1759
|
-
const res = await process;
|
|
1760
|
-
try {
|
|
1761
|
-
return res.stdout
|
|
1762
|
-
.split("\n")
|
|
1763
|
-
.filter((it) => it.includes("default via"))
|
|
1764
|
-
.map((it) => /default via ([\d.]+) /.exec(it)[1])[0];
|
|
1765
|
-
}
|
|
1766
|
-
catch {
|
|
1767
|
-
throw new Error("Failed to extract docker host address");
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
return "localhost";
|
|
1771
|
-
}
|
|
1772
|
-
async function waitForEnterToContinue(prompt = "Press enter to continue") {
|
|
1773
|
-
await read({
|
|
1774
|
-
prompt,
|
|
1775
|
-
silent: true,
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
399
|
const VERSION = version;
|
|
1780
400
|
|
|
1781
|
-
export {
|
|
401
|
+
export { VERSION, index$1 as definition, index as github };
|
|
1782
402
|
//# sourceMappingURL=index.js.map
|