@capraconsulting/cals-cli 3.5.2 → 3.7.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/lib/{cals-cli.js → cals-cli.mjs} +230 -309
- package/lib/{cals-cli.js.map → cals-cli.mjs.map} +1 -1
- package/lib/definition/index.d.ts +1 -1
- package/lib/github/index.d.ts +3 -2
- package/lib/index.d.ts +0 -1
- package/lib/index.es.js +91 -427
- package/lib/index.es.js.map +1 -1
- package/lib/index.js +163 -563
- package/lib/index.js.map +1 -1
- package/lib/snyk/index.d.ts +1 -1
- package/lib/sonarcloud/index.d.ts +2 -1
- package/package.json +21 -17
- package/lib/load-secrets/index.d.ts +0 -2
- package/lib/load-secrets/load-secrets.d.ts +0 -7
- package/lib/load-secrets/types.d.ts +0 -34
|
@@ -1,74 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import yaml from 'js-yaml';
|
|
7
|
+
import { uniq, keyBy, sortBy, groupBy, sumBy, repeat } from 'lodash-es';
|
|
8
|
+
import pMap from 'p-map';
|
|
9
|
+
import AJV from 'ajv';
|
|
10
|
+
import { Octokit } from '@octokit/rest';
|
|
11
|
+
import fetch from 'node-fetch';
|
|
12
|
+
import pLimit from 'p-limit';
|
|
13
|
+
import keytar from 'keytar';
|
|
14
|
+
import * as process$2 from 'process';
|
|
15
|
+
import { performance } from 'perf_hooks';
|
|
16
|
+
import { deprecate } from 'util';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import rimraf from 'rimraf';
|
|
19
|
+
import cachedir from 'cachedir';
|
|
20
|
+
import https from 'https';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import chalk from 'chalk';
|
|
23
|
+
import readline from 'readline';
|
|
24
|
+
import { sprintf } from 'sprintf-js';
|
|
25
|
+
import read from 'read';
|
|
26
|
+
import findUp from 'find-up';
|
|
27
|
+
import execa from 'execa';
|
|
3
28
|
|
|
4
|
-
var
|
|
5
|
-
var yargs = require('yargs');
|
|
6
|
-
var fs = require('fs');
|
|
7
|
-
var yaml = require('js-yaml');
|
|
8
|
-
var lodash = require('lodash');
|
|
9
|
-
var pMap = require('p-map');
|
|
10
|
-
var AJV = require('ajv');
|
|
11
|
-
var rest = require('@octokit/rest');
|
|
12
|
-
var fetch = require('node-fetch');
|
|
13
|
-
var pLimit = require('p-limit');
|
|
14
|
-
var keytar = require('keytar');
|
|
15
|
-
var process$2 = require('process');
|
|
16
|
-
var perf_hooks = require('perf_hooks');
|
|
17
|
-
var util = require('util');
|
|
18
|
-
var path = require('path');
|
|
19
|
-
var rimraf = require('rimraf');
|
|
20
|
-
var cachedir = require('cachedir');
|
|
21
|
-
var https = require('https');
|
|
22
|
-
var os = require('os');
|
|
23
|
-
var chalk = require('chalk');
|
|
24
|
-
var readline = require('readline');
|
|
25
|
-
var sprintfJs = require('sprintf-js');
|
|
26
|
-
var read = require('read');
|
|
27
|
-
var findUp = require('find-up');
|
|
28
|
-
var execa = require('execa');
|
|
29
|
-
|
|
30
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
31
|
-
|
|
32
|
-
function _interopNamespace(e) {
|
|
33
|
-
if (e && e.__esModule) return e;
|
|
34
|
-
var n = Object.create(null);
|
|
35
|
-
if (e) {
|
|
36
|
-
Object.keys(e).forEach(function (k) {
|
|
37
|
-
if (k !== 'default') {
|
|
38
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
39
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
40
|
-
enumerable: true,
|
|
41
|
-
get: function () { return e[k]; }
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
n["default"] = e;
|
|
47
|
-
return Object.freeze(n);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);
|
|
51
|
-
var yargs__default = /*#__PURE__*/_interopDefaultLegacy(yargs);
|
|
52
|
-
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
53
|
-
var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
|
|
54
|
-
var pMap__default = /*#__PURE__*/_interopDefaultLegacy(pMap);
|
|
55
|
-
var AJV__default = /*#__PURE__*/_interopDefaultLegacy(AJV);
|
|
56
|
-
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
57
|
-
var pLimit__default = /*#__PURE__*/_interopDefaultLegacy(pLimit);
|
|
58
|
-
var keytar__default = /*#__PURE__*/_interopDefaultLegacy(keytar);
|
|
59
|
-
var process__namespace = /*#__PURE__*/_interopNamespace(process$2);
|
|
60
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
61
|
-
var rimraf__default = /*#__PURE__*/_interopDefaultLegacy(rimraf);
|
|
62
|
-
var cachedir__default = /*#__PURE__*/_interopDefaultLegacy(cachedir);
|
|
63
|
-
var https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
64
|
-
var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
|
|
65
|
-
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
|
|
66
|
-
var readline__default = /*#__PURE__*/_interopDefaultLegacy(readline);
|
|
67
|
-
var read__default = /*#__PURE__*/_interopDefaultLegacy(read);
|
|
68
|
-
var findUp__default = /*#__PURE__*/_interopDefaultLegacy(findUp);
|
|
69
|
-
var execa__default = /*#__PURE__*/_interopDefaultLegacy(execa);
|
|
70
|
-
|
|
71
|
-
var version = "3.5.2";
|
|
29
|
+
var version = "3.7.0";
|
|
72
30
|
var engines = {
|
|
73
31
|
node: ">=12.0.0"
|
|
74
32
|
};
|
|
@@ -374,12 +332,11 @@ function getRepoId(orgName, repoName) {
|
|
|
374
332
|
return `${orgName}/${repoName}`;
|
|
375
333
|
}
|
|
376
334
|
function checkAgainstSchema(value) {
|
|
377
|
-
|
|
378
|
-
const ajv = new AJV__default["default"]({ allErrors: true });
|
|
335
|
+
const ajv = new AJV({ allErrors: true });
|
|
379
336
|
const valid = ajv.validate(schema, value);
|
|
380
337
|
return valid
|
|
381
338
|
? { definition: value }
|
|
382
|
-
: { error:
|
|
339
|
+
: { error: ajv.errorsText() ?? "Unknown error" };
|
|
383
340
|
}
|
|
384
341
|
function requireValidDefinition(definition) {
|
|
385
342
|
// Verify no duplicates in users and extract known logins.
|
|
@@ -424,7 +381,8 @@ function requireValidDefinition(definition) {
|
|
|
424
381
|
if (!teamIdList.includes(id)) {
|
|
425
382
|
throw new Error(`Project team ${id} in project ${project.name} is not registered in team list`);
|
|
426
383
|
}
|
|
427
|
-
})
|
|
384
|
+
}) // Verify repo teams exists as teams.
|
|
385
|
+
;
|
|
428
386
|
(org.repos || []).forEach((repo) => {
|
|
429
387
|
(repo.teams || []).forEach((team) => {
|
|
430
388
|
const id = getTeamId(org.organization, team.name);
|
|
@@ -448,11 +406,12 @@ function requireValidDefinition(definition) {
|
|
|
448
406
|
}, []);
|
|
449
407
|
}
|
|
450
408
|
class DefinitionFile {
|
|
409
|
+
path;
|
|
451
410
|
constructor(path) {
|
|
452
411
|
this.path = path;
|
|
453
412
|
}
|
|
454
413
|
async getContents() {
|
|
455
|
-
return new Promise((resolve, reject) =>
|
|
414
|
+
return new Promise((resolve, reject) => fs.readFile(this.path, "utf-8", (err, data) => {
|
|
456
415
|
if (err)
|
|
457
416
|
reject(err);
|
|
458
417
|
else
|
|
@@ -464,7 +423,7 @@ class DefinitionFile {
|
|
|
464
423
|
}
|
|
465
424
|
}
|
|
466
425
|
function parseDefinition(value) {
|
|
467
|
-
const result = checkAgainstSchema(
|
|
426
|
+
const result = checkAgainstSchema(yaml.load(value));
|
|
468
427
|
if ("error" in result) {
|
|
469
428
|
throw new Error("Definition content invalid: " + result.error);
|
|
470
429
|
}
|
|
@@ -482,19 +441,17 @@ function getRepos(definition) {
|
|
|
482
441
|
.flat());
|
|
483
442
|
}
|
|
484
443
|
function getGitHubOrgs(definition) {
|
|
485
|
-
return
|
|
444
|
+
return uniq(definition.projects.flatMap((project) => project.github.map((it) => it.organization)));
|
|
486
445
|
}
|
|
487
446
|
|
|
488
447
|
class GitHubTokenCliProvider {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
this.keyringAccount = "github-token";
|
|
492
|
-
}
|
|
448
|
+
keyringService = "cals";
|
|
449
|
+
keyringAccount = "github-token";
|
|
493
450
|
async getToken() {
|
|
494
451
|
if (process.env.CALS_GITHUB_TOKEN) {
|
|
495
452
|
return process.env.CALS_GITHUB_TOKEN;
|
|
496
453
|
}
|
|
497
|
-
const result = await
|
|
454
|
+
const result = await keytar.getPassword(this.keyringService, this.keyringAccount);
|
|
498
455
|
if (result == null) {
|
|
499
456
|
process.stderr.write("No token found. Register using `cals github set-token`\n");
|
|
500
457
|
return undefined;
|
|
@@ -502,10 +459,10 @@ class GitHubTokenCliProvider {
|
|
|
502
459
|
return result;
|
|
503
460
|
}
|
|
504
461
|
async markInvalid() {
|
|
505
|
-
await
|
|
462
|
+
await keytar.deletePassword(this.keyringService, this.keyringAccount);
|
|
506
463
|
}
|
|
507
464
|
async setToken(value) {
|
|
508
|
-
await
|
|
465
|
+
await keytar.setPassword(this.keyringService, this.keyringAccount, value);
|
|
509
466
|
}
|
|
510
467
|
}
|
|
511
468
|
|
|
@@ -570,20 +527,23 @@ async function undefinedForNotFound(value) {
|
|
|
570
527
|
}
|
|
571
528
|
|
|
572
529
|
class GitHubService {
|
|
530
|
+
config;
|
|
531
|
+
octokit;
|
|
532
|
+
cache;
|
|
533
|
+
tokenProvider;
|
|
534
|
+
semaphore;
|
|
573
535
|
constructor(props) {
|
|
574
|
-
this._requestCount = 0;
|
|
575
536
|
this.config = props.config;
|
|
576
537
|
this.octokit = props.octokit;
|
|
577
538
|
this.cache = props.cache;
|
|
578
539
|
this.tokenProvider = props.tokenProvider;
|
|
579
540
|
// Control concurrency to GitHub API at service level so we
|
|
580
541
|
// can maximize concurrency all other places.
|
|
581
|
-
this.semaphore =
|
|
542
|
+
this.semaphore = pLimit(6);
|
|
582
543
|
this.octokit.hook.wrap("request", async (request, options) => {
|
|
583
544
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
584
545
|
this._requestCount++;
|
|
585
546
|
if (options.method !== "GET") {
|
|
586
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
587
547
|
return this.semaphore(() => request(options));
|
|
588
548
|
}
|
|
589
549
|
// Try to cache ETag for GET requests to save on rate limiting.
|
|
@@ -607,7 +567,6 @@ class GitHubService {
|
|
|
607
567
|
}
|
|
608
568
|
const getResponse = async (allowRetry = true) => {
|
|
609
569
|
try {
|
|
610
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
611
570
|
return await request(options);
|
|
612
571
|
}
|
|
613
572
|
catch (e) {
|
|
@@ -646,6 +605,7 @@ class GitHubService {
|
|
|
646
605
|
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
647
606
|
});
|
|
648
607
|
}
|
|
608
|
+
_requestCount = 0;
|
|
649
609
|
get requestCount() {
|
|
650
610
|
return this._requestCount;
|
|
651
611
|
}
|
|
@@ -660,18 +620,18 @@ class GitHubService {
|
|
|
660
620
|
};
|
|
661
621
|
let requestDuration = -1;
|
|
662
622
|
const response = await this.semaphore(() => {
|
|
663
|
-
const requestStart =
|
|
664
|
-
const result =
|
|
623
|
+
const requestStart = performance.now();
|
|
624
|
+
const result = fetch(url, {
|
|
665
625
|
method: "POST",
|
|
666
626
|
headers,
|
|
667
627
|
body: JSON.stringify({ query }),
|
|
668
628
|
agent: this.config.agent,
|
|
669
629
|
});
|
|
670
|
-
requestDuration =
|
|
630
|
+
requestDuration = performance.now() - requestStart;
|
|
671
631
|
return result;
|
|
672
632
|
});
|
|
673
633
|
if (response.status === 401) {
|
|
674
|
-
|
|
634
|
+
process$2.stderr.write("Unauthorized\n");
|
|
675
635
|
await this.tokenProvider.markInvalid();
|
|
676
636
|
}
|
|
677
637
|
// If you get 502 after 10s, it is a timeout.
|
|
@@ -736,7 +696,6 @@ class GitHubService {
|
|
|
736
696
|
if (res.organization.repositories.nodes == null) {
|
|
737
697
|
throw new Error("Missing organization nodes");
|
|
738
698
|
}
|
|
739
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
740
699
|
repos.push(...res.organization.repositories.nodes);
|
|
741
700
|
if (!res.organization.repositories.pageInfo.hasNextPage) {
|
|
742
701
|
break;
|
|
@@ -765,15 +724,12 @@ class GitHubService {
|
|
|
765
724
|
login: it.login,
|
|
766
725
|
data: it,
|
|
767
726
|
})),
|
|
768
|
-
...(await this.getOrgMembersInvitedList(org)).map((it) => {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
data: it,
|
|
775
|
-
});
|
|
776
|
-
}),
|
|
727
|
+
...(await this.getOrgMembersInvitedList(org)).map((it) => ({
|
|
728
|
+
type: "invited",
|
|
729
|
+
// TODO: Fix ?? case properly
|
|
730
|
+
login: it.login ?? "invalid",
|
|
731
|
+
data: it,
|
|
732
|
+
})),
|
|
777
733
|
];
|
|
778
734
|
}
|
|
779
735
|
async getRepository(owner, repo) {
|
|
@@ -842,15 +798,12 @@ class GitHubService {
|
|
|
842
798
|
login: it.login,
|
|
843
799
|
data: it,
|
|
844
800
|
})),
|
|
845
|
-
...(await this.getTeamMemberInvitedList(org, team)).map((it) => {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
data: it,
|
|
852
|
-
});
|
|
853
|
-
}),
|
|
801
|
+
...(await this.getTeamMemberInvitedList(org, team)).map((it) => ({
|
|
802
|
+
type: "invited",
|
|
803
|
+
// TODO: Fix ?? case properly
|
|
804
|
+
login: it.login ?? "invalid",
|
|
805
|
+
data: it,
|
|
806
|
+
})),
|
|
854
807
|
];
|
|
855
808
|
}
|
|
856
809
|
async getSearchedPullRequestList(owner) {
|
|
@@ -975,17 +928,16 @@ class GitHubService {
|
|
|
975
928
|
}
|
|
976
929
|
}`;
|
|
977
930
|
return this.cache.json(`vulnerability-alerts-${owner}-${repo}`, async () => {
|
|
978
|
-
var _a, _b, _c, _d, _e;
|
|
979
931
|
const result = [];
|
|
980
932
|
let after = null;
|
|
981
933
|
while (true) {
|
|
982
934
|
const query = getQuery(after);
|
|
983
935
|
const res = await this.runGraphqlQuery(query);
|
|
984
|
-
result.push(...(
|
|
985
|
-
if (!
|
|
936
|
+
result.push(...(res.repository?.vulnerabilityAlerts.edges?.map((it) => it.node) ?? []));
|
|
937
|
+
if (!res.repository?.vulnerabilityAlerts.pageInfo.hasNextPage) {
|
|
986
938
|
break;
|
|
987
939
|
}
|
|
988
|
-
after =
|
|
940
|
+
after = res.repository?.vulnerabilityAlerts.pageInfo.endCursor;
|
|
989
941
|
}
|
|
990
942
|
return result;
|
|
991
943
|
});
|
|
@@ -1027,27 +979,23 @@ class GitHubService {
|
|
|
1027
979
|
}
|
|
1028
980
|
}`;
|
|
1029
981
|
const issues = await this.cache.json(`renovate-bot-issues-${owner}-${repo}`, async () => {
|
|
1030
|
-
var _a, _b, _c, _d, _e;
|
|
1031
982
|
const result = [];
|
|
1032
983
|
let after = null;
|
|
1033
984
|
while (true) {
|
|
1034
985
|
const query = getQuery(after);
|
|
1035
986
|
const res = await this.runGraphqlQuery(query);
|
|
1036
|
-
const nodes =
|
|
987
|
+
const nodes = res.repository?.issues.edges?.map((it) => it.node) ?? [];
|
|
1037
988
|
result.push(...nodes
|
|
1038
989
|
.filter((it) => it.title === "Dependency Dashboard")
|
|
1039
|
-
.map((it) => {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
});
|
|
1046
|
-
}));
|
|
1047
|
-
if (!((_d = res.repository) === null || _d === void 0 ? void 0 : _d.issues.pageInfo.hasNextPage)) {
|
|
990
|
+
.map((it) => ({
|
|
991
|
+
number: it.number,
|
|
992
|
+
body: it.body,
|
|
993
|
+
lastUpdatedByRenovate: it.userContentEdits?.nodes?.filter((it) => it.editor?.login === "renovate")?.[0]?.createdAt ?? null,
|
|
994
|
+
})));
|
|
995
|
+
if (!res.repository?.issues.pageInfo.hasNextPage) {
|
|
1048
996
|
break;
|
|
1049
997
|
}
|
|
1050
|
-
after =
|
|
998
|
+
after = res.repository?.issues.pageInfo.endCursor;
|
|
1051
999
|
}
|
|
1052
1000
|
return result;
|
|
1053
1001
|
});
|
|
@@ -1058,7 +1006,7 @@ class GitHubService {
|
|
|
1058
1006
|
}
|
|
1059
1007
|
}
|
|
1060
1008
|
async function createOctokit(config, tokenProvider) {
|
|
1061
|
-
return new
|
|
1009
|
+
return new Octokit({
|
|
1062
1010
|
auth: await tokenProvider.getToken(),
|
|
1063
1011
|
request: {
|
|
1064
1012
|
agent: config.agent,
|
|
@@ -1066,8 +1014,7 @@ async function createOctokit(config, tokenProvider) {
|
|
|
1066
1014
|
});
|
|
1067
1015
|
}
|
|
1068
1016
|
async function createGitHubService(props) {
|
|
1069
|
-
|
|
1070
|
-
const tokenProvider = (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new GitHubTokenCliProvider();
|
|
1017
|
+
const tokenProvider = props.tokenProvider ?? new GitHubTokenCliProvider();
|
|
1071
1018
|
return new GitHubService({
|
|
1072
1019
|
config: props.config,
|
|
1073
1020
|
octokit: await createOctokit(props.config, tokenProvider),
|
|
@@ -1077,15 +1024,13 @@ async function createGitHubService(props) {
|
|
|
1077
1024
|
}
|
|
1078
1025
|
|
|
1079
1026
|
class SnykTokenCliProvider {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
this.keyringAccount = "snyk-token";
|
|
1083
|
-
}
|
|
1027
|
+
keyringService = "cals";
|
|
1028
|
+
keyringAccount = "snyk-token";
|
|
1084
1029
|
async getToken() {
|
|
1085
1030
|
if (process.env.CALS_SNYK_TOKEN) {
|
|
1086
1031
|
return process.env.CALS_SNYK_TOKEN;
|
|
1087
1032
|
}
|
|
1088
|
-
const result = await
|
|
1033
|
+
const result = await keytar.getPassword(this.keyringService, this.keyringAccount);
|
|
1089
1034
|
if (result == null) {
|
|
1090
1035
|
process.stderr.write("No token found. Register using `cals snyk set-token`\n");
|
|
1091
1036
|
return undefined;
|
|
@@ -1093,21 +1038,22 @@ class SnykTokenCliProvider {
|
|
|
1093
1038
|
return result;
|
|
1094
1039
|
}
|
|
1095
1040
|
async markInvalid() {
|
|
1096
|
-
await
|
|
1041
|
+
await keytar.deletePassword(this.keyringService, this.keyringAccount);
|
|
1097
1042
|
}
|
|
1098
1043
|
async setToken(value) {
|
|
1099
|
-
await
|
|
1044
|
+
await keytar.setPassword(this.keyringService, this.keyringAccount, value);
|
|
1100
1045
|
}
|
|
1101
1046
|
}
|
|
1102
1047
|
|
|
1103
1048
|
class SnykService {
|
|
1049
|
+
config;
|
|
1050
|
+
tokenProvider;
|
|
1104
1051
|
constructor(props) {
|
|
1105
1052
|
this.config = props.config;
|
|
1106
1053
|
this.tokenProvider = props.tokenProvider;
|
|
1107
1054
|
}
|
|
1108
1055
|
async getProjects(definition) {
|
|
1109
|
-
|
|
1110
|
-
const snykAccountId = (_a = definition.snyk) === null || _a === void 0 ? void 0 : _a.accountId;
|
|
1056
|
+
const snykAccountId = definition.snyk?.accountId;
|
|
1111
1057
|
if (snykAccountId === undefined) {
|
|
1112
1058
|
return [];
|
|
1113
1059
|
}
|
|
@@ -1135,7 +1081,7 @@ class SnykService {
|
|
|
1135
1081
|
* We continue calling the Snyk API and retrieving more projects until links.next is null
|
|
1136
1082
|
* */
|
|
1137
1083
|
while (nextUrl) {
|
|
1138
|
-
const response = await
|
|
1084
|
+
const response = await fetch(`https://api.snyk.io/rest${nextUrl}`, {
|
|
1139
1085
|
method: "GET",
|
|
1140
1086
|
headers: {
|
|
1141
1087
|
Accept: "application/json",
|
|
@@ -1172,7 +1118,7 @@ class SnykService {
|
|
|
1172
1118
|
totalDependencies: project.meta.latest_dependency_total.total,
|
|
1173
1119
|
issueCountsBySeverity: project.meta.latest_issue_counts,
|
|
1174
1120
|
lastTestedDate: project.meta.latest_dependency_total.updated_at,
|
|
1175
|
-
browseUrl: `https://app.snyk.io/org/${snykOrgSlugId
|
|
1121
|
+
browseUrl: `https://app.snyk.io/org/${snykOrgSlugId ?? "it"}/project/${project.id}`,
|
|
1176
1122
|
};
|
|
1177
1123
|
}),
|
|
1178
1124
|
];
|
|
@@ -1184,10 +1130,9 @@ class SnykService {
|
|
|
1184
1130
|
}
|
|
1185
1131
|
}
|
|
1186
1132
|
function createSnykService(props) {
|
|
1187
|
-
var _a;
|
|
1188
1133
|
return new SnykService({
|
|
1189
1134
|
config: props.config,
|
|
1190
|
-
tokenProvider:
|
|
1135
|
+
tokenProvider: props.tokenProvider ?? new SnykTokenCliProvider(),
|
|
1191
1136
|
});
|
|
1192
1137
|
}
|
|
1193
1138
|
|
|
@@ -1225,23 +1170,24 @@ function getGitHubRepoId(repo) {
|
|
|
1225
1170
|
|
|
1226
1171
|
class CacheProvider {
|
|
1227
1172
|
constructor(config) {
|
|
1228
|
-
this.mustValidate = false;
|
|
1229
|
-
this.defaultCacheTime = 1800;
|
|
1230
1173
|
this.config = config;
|
|
1231
1174
|
}
|
|
1175
|
+
mustValidate = false;
|
|
1176
|
+
config;
|
|
1177
|
+
defaultCacheTime = 1800;
|
|
1232
1178
|
/**
|
|
1233
1179
|
* Retrieve cache if existent, ignoring the time.
|
|
1234
1180
|
*
|
|
1235
1181
|
* The caller is responsible for handling proper validation,
|
|
1236
1182
|
*/
|
|
1237
1183
|
retrieveJson(cachekey) {
|
|
1238
|
-
const cachefile =
|
|
1239
|
-
if (!
|
|
1184
|
+
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
1185
|
+
if (!fs.existsSync(cachefile)) {
|
|
1240
1186
|
return undefined;
|
|
1241
1187
|
}
|
|
1242
|
-
const data =
|
|
1188
|
+
const data = fs.readFileSync(cachefile, "utf-8");
|
|
1243
1189
|
return {
|
|
1244
|
-
cacheTime:
|
|
1190
|
+
cacheTime: fs.statSync(cachefile).mtime.getTime(),
|
|
1245
1191
|
data: (data === "undefined" ? undefined : JSON.parse(data)),
|
|
1246
1192
|
};
|
|
1247
1193
|
}
|
|
@@ -1249,11 +1195,11 @@ class CacheProvider {
|
|
|
1249
1195
|
* Save data to cache.
|
|
1250
1196
|
*/
|
|
1251
1197
|
storeJson(cachekey, data) {
|
|
1252
|
-
const cachefile =
|
|
1253
|
-
if (!
|
|
1254
|
-
|
|
1198
|
+
const cachefile = path.join(this.config.cacheDir, `${cachekey}.json`);
|
|
1199
|
+
if (!fs.existsSync(this.config.cacheDir)) {
|
|
1200
|
+
fs.mkdirSync(this.config.cacheDir, { recursive: true });
|
|
1255
1201
|
}
|
|
1256
|
-
|
|
1202
|
+
fs.writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
|
|
1257
1203
|
}
|
|
1258
1204
|
async json(cachekey, block, cachetime = this.defaultCacheTime) {
|
|
1259
1205
|
const cacheItem = this.mustValidate
|
|
@@ -1271,20 +1217,18 @@ class CacheProvider {
|
|
|
1271
1217
|
* Delete all cached data.
|
|
1272
1218
|
*/
|
|
1273
1219
|
cleanup() {
|
|
1274
|
-
|
|
1220
|
+
rimraf.sync(this.config.cacheDir);
|
|
1275
1221
|
}
|
|
1276
1222
|
}
|
|
1277
1223
|
|
|
1278
1224
|
class Config {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
this.configCached = undefined;
|
|
1287
|
-
}
|
|
1225
|
+
cwd = path.resolve(process.cwd());
|
|
1226
|
+
configFile = path.join(os.homedir(), ".cals-config.json");
|
|
1227
|
+
cacheDir = cachedir("cals-cli");
|
|
1228
|
+
agent = new https.Agent({
|
|
1229
|
+
keepAlive: true,
|
|
1230
|
+
});
|
|
1231
|
+
configCached = undefined;
|
|
1288
1232
|
get config() {
|
|
1289
1233
|
const existingConfig = this.configCached;
|
|
1290
1234
|
if (existingConfig !== undefined) {
|
|
@@ -1295,12 +1239,12 @@ class Config {
|
|
|
1295
1239
|
return config;
|
|
1296
1240
|
}
|
|
1297
1241
|
readConfig() {
|
|
1298
|
-
if (!
|
|
1242
|
+
if (!fs.existsSync(this.configFile)) {
|
|
1299
1243
|
return {};
|
|
1300
1244
|
}
|
|
1301
1245
|
try {
|
|
1302
1246
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
1303
|
-
return JSON.parse(
|
|
1247
|
+
return JSON.parse(fs.readFileSync(this.configFile, "utf-8"));
|
|
1304
1248
|
}
|
|
1305
1249
|
catch (e) {
|
|
1306
1250
|
console.error("Failed", e);
|
|
@@ -1322,27 +1266,29 @@ class Config {
|
|
|
1322
1266
|
...this.readConfig(),
|
|
1323
1267
|
[key]: value, // undefined will remove
|
|
1324
1268
|
};
|
|
1325
|
-
|
|
1269
|
+
fs.writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
|
|
1326
1270
|
this.configCached = updatedConfig;
|
|
1327
1271
|
}
|
|
1328
1272
|
}
|
|
1329
1273
|
|
|
1330
1274
|
const CLEAR_WHOLE_LINE = 0;
|
|
1331
1275
|
function clearLine(stdout) {
|
|
1332
|
-
|
|
1333
|
-
|
|
1276
|
+
readline.clearLine(stdout, CLEAR_WHOLE_LINE);
|
|
1277
|
+
readline.cursorTo(stdout, 0);
|
|
1334
1278
|
}
|
|
1335
1279
|
class Reporter {
|
|
1336
1280
|
constructor(opts = {}) {
|
|
1337
|
-
this.stdout = process.stdout;
|
|
1338
|
-
this.stderr = process.stderr;
|
|
1339
|
-
this.stdin = process.stdin;
|
|
1340
|
-
this.isTTY = this.stdout.isTTY;
|
|
1341
|
-
this.format = chalk__default["default"];
|
|
1342
|
-
this.startTime = Date.now();
|
|
1343
1281
|
this.nonInteractive = !!opts.nonInteractive;
|
|
1344
1282
|
this.isVerbose = !!opts.verbose;
|
|
1345
1283
|
}
|
|
1284
|
+
stdout = process.stdout;
|
|
1285
|
+
stderr = process.stderr;
|
|
1286
|
+
stdin = process.stdin;
|
|
1287
|
+
isTTY = this.stdout.isTTY;
|
|
1288
|
+
nonInteractive;
|
|
1289
|
+
isVerbose;
|
|
1290
|
+
format = chalk;
|
|
1291
|
+
startTime = Date.now();
|
|
1346
1292
|
error(msg) {
|
|
1347
1293
|
clearLine(this.stderr);
|
|
1348
1294
|
this.stderr.write(`${this.format.red("error")} ${msg}\n`);
|
|
@@ -1379,7 +1325,7 @@ function createCacheProvider(config, argv) {
|
|
|
1379
1325
|
}
|
|
1380
1326
|
// old option: --no-cache
|
|
1381
1327
|
if (argv.cache === false) {
|
|
1382
|
-
|
|
1328
|
+
deprecate(() => {
|
|
1383
1329
|
cache.mustValidate = true;
|
|
1384
1330
|
}, "The --no-cache option is deprecated. See new --validate-cache option")();
|
|
1385
1331
|
}
|
|
@@ -1399,7 +1345,7 @@ function getDefinitionFile(argv) {
|
|
|
1399
1345
|
throw Error("Missing --definition-file option");
|
|
1400
1346
|
}
|
|
1401
1347
|
const definitionFile = argv.definitionFile;
|
|
1402
|
-
if (!
|
|
1348
|
+
if (!fs.existsSync(definitionFile)) {
|
|
1403
1349
|
throw Error(`The file ${definitionFile} does not exist`);
|
|
1404
1350
|
}
|
|
1405
1351
|
return new DefinitionFile(definitionFile);
|
|
@@ -1456,9 +1402,9 @@ function reorderListToSimilarAsBefore(oldList, updatedList, selector, insertLast
|
|
|
1456
1402
|
}
|
|
1457
1403
|
|
|
1458
1404
|
async function getReposFromGitHub(github, orgs) {
|
|
1459
|
-
return (await
|
|
1405
|
+
return (await pMap(orgs, async (org) => {
|
|
1460
1406
|
const repos = await github.getOrgRepoList({ org: org.login });
|
|
1461
|
-
return
|
|
1407
|
+
return pMap(repos, async (repo) => {
|
|
1462
1408
|
const detailedRepo = await github.getRepository(repo.owner.login, repo.name);
|
|
1463
1409
|
if (detailedRepo === undefined) {
|
|
1464
1410
|
throw Error(`Repo not found: ${repo.owner.login}/${repo.name}`);
|
|
@@ -1472,11 +1418,11 @@ async function getReposFromGitHub(github, orgs) {
|
|
|
1472
1418
|
})).flat();
|
|
1473
1419
|
}
|
|
1474
1420
|
async function getTeams(github, orgs) {
|
|
1475
|
-
const intermediate = await
|
|
1421
|
+
const intermediate = await pMap(orgs, async (org) => {
|
|
1476
1422
|
const teams = await github.getTeamList(org);
|
|
1477
1423
|
return {
|
|
1478
1424
|
org,
|
|
1479
|
-
teams: await
|
|
1425
|
+
teams: await pMap(teams, async (team) => ({
|
|
1480
1426
|
team,
|
|
1481
1427
|
users: await github.getTeamMemberListIncludingInvited(org, team),
|
|
1482
1428
|
})),
|
|
@@ -1505,11 +1451,11 @@ function getFormattedTeams(oldTeams, teams) {
|
|
|
1505
1451
|
permission: it.permission,
|
|
1506
1452
|
}));
|
|
1507
1453
|
return result
|
|
1508
|
-
? reorderListToSimilarAsBefore(oldTeams
|
|
1454
|
+
? reorderListToSimilarAsBefore(oldTeams ?? [], result, (it) => it.name)
|
|
1509
1455
|
: undefined;
|
|
1510
1456
|
}
|
|
1511
1457
|
async function getOrgs(github, orgs) {
|
|
1512
|
-
return
|
|
1458
|
+
return pMap(orgs, (it) => github.getOrg(it));
|
|
1513
1459
|
}
|
|
1514
1460
|
function removeDuplicates(items, selector) {
|
|
1515
1461
|
const ids = [];
|
|
@@ -1524,7 +1470,7 @@ function removeDuplicates(items, selector) {
|
|
|
1524
1470
|
return result;
|
|
1525
1471
|
}
|
|
1526
1472
|
async function getMembers(github, orgs) {
|
|
1527
|
-
return removeDuplicates((await
|
|
1473
|
+
return removeDuplicates((await pMap(orgs, (org) => github.getOrgMembersListIncludingInvited(org.login)))
|
|
1528
1474
|
.flat()
|
|
1529
1475
|
.map((it) => it.login), (it) => it);
|
|
1530
1476
|
}
|
|
@@ -1538,15 +1484,14 @@ async function getProjects(github, orgs, definition, snyk) {
|
|
|
1538
1484
|
const snykReposPromise = getSnykRepos(snyk, definition);
|
|
1539
1485
|
const repos = await getReposFromGitHub(github, orgs);
|
|
1540
1486
|
const snykRepos = await snykReposPromise;
|
|
1541
|
-
const definitionRepos =
|
|
1487
|
+
const definitionRepos = keyBy(getRepos(definition), (it) => it.id);
|
|
1542
1488
|
const projectGroups = Object.values(repos.reduce((acc, cur) => {
|
|
1543
|
-
var _a, _b, _c, _d;
|
|
1544
1489
|
const org = cur.repository.owner.login;
|
|
1545
1490
|
const repoId = getRepoId(org, cur.repository.name);
|
|
1546
|
-
const projectName =
|
|
1491
|
+
const projectName = definitionRepos[repoId]?.project?.name ?? "Unknown";
|
|
1547
1492
|
const project = acc[projectName] || {
|
|
1548
1493
|
name: projectName,
|
|
1549
|
-
definition:
|
|
1494
|
+
definition: definitionRepos[repoId]?.project,
|
|
1550
1495
|
repos: [],
|
|
1551
1496
|
};
|
|
1552
1497
|
return {
|
|
@@ -1561,39 +1506,36 @@ async function getProjects(github, orgs, definition, snyk) {
|
|
|
1561
1506
|
};
|
|
1562
1507
|
}, {}));
|
|
1563
1508
|
const projects = projectGroups.map((project) => {
|
|
1564
|
-
var _a, _b;
|
|
1565
1509
|
const github = Object.entries(project.repos).map(([org, list]) => {
|
|
1566
|
-
var _a, _b, _c, _d;
|
|
1567
1510
|
const commonTeams = getCommonTeams(list);
|
|
1568
|
-
const oldOrg =
|
|
1511
|
+
const oldOrg = project.definition?.github?.find((it) => it.organization == org);
|
|
1569
1512
|
const repos = list.map((repo) => {
|
|
1570
|
-
var _a, _b;
|
|
1571
1513
|
const repoId = getRepoId(repo.basic.owner.login, repo.basic.name);
|
|
1572
1514
|
const definitionRepo = definitionRepos[repoId];
|
|
1573
1515
|
const result = {
|
|
1574
1516
|
name: repo.basic.name,
|
|
1575
|
-
previousNames: definitionRepo
|
|
1517
|
+
previousNames: definitionRepo?.repo.previousNames,
|
|
1576
1518
|
archived: repo.repository.archived ? true : undefined,
|
|
1577
1519
|
issues: repo.repository.has_issues ? undefined : false,
|
|
1578
1520
|
wiki: repo.repository.has_wiki ? undefined : false,
|
|
1579
|
-
teams: getFormattedTeams(
|
|
1521
|
+
teams: getFormattedTeams(definitionRepo?.repo?.teams ?? [], getSpecificTeams(repo.teams, commonTeams)),
|
|
1580
1522
|
snyk: snykRepos.includes(repoId) ? true : undefined,
|
|
1581
1523
|
public: repo.repository.private ? undefined : true,
|
|
1582
|
-
responsible: definitionRepo
|
|
1524
|
+
responsible: definitionRepo?.repo.responsible,
|
|
1583
1525
|
};
|
|
1584
1526
|
// Try to preserve property order.
|
|
1585
1527
|
return Object.fromEntries(reorderListToSimilarAsBefore(definitionRepo ? Object.entries(definitionRepo.repo) : [], Object.entries(result), (it) => it[0], true));
|
|
1586
1528
|
});
|
|
1587
|
-
const teams = getFormattedTeams(
|
|
1529
|
+
const teams = getFormattedTeams(oldOrg?.teams ?? [], commonTeams);
|
|
1588
1530
|
return {
|
|
1589
1531
|
organization: org,
|
|
1590
1532
|
teams: teams,
|
|
1591
|
-
repos: reorderListToSimilarAsBefore(
|
|
1533
|
+
repos: reorderListToSimilarAsBefore(oldOrg?.repos ?? [], repos, (it) => it.name),
|
|
1592
1534
|
};
|
|
1593
1535
|
});
|
|
1594
1536
|
return {
|
|
1595
1537
|
name: project.name,
|
|
1596
|
-
github: reorderListToSimilarAsBefore(
|
|
1538
|
+
github: reorderListToSimilarAsBefore(project.definition?.github ?? [], github, (it) => it.organization),
|
|
1597
1539
|
};
|
|
1598
1540
|
});
|
|
1599
1541
|
return reorderListToSimilarAsBefore(definition.projects, projects, (it) => it.name);
|
|
@@ -1638,7 +1580,7 @@ async function dumpSetup(config, reporter, github, snyk, outfile, definitionFile
|
|
|
1638
1580
|
// package. However it often produced invalid yaml, so we have removed
|
|
1639
1581
|
// it. We might want to revisit it to preserve comments.
|
|
1640
1582
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
|
|
1641
|
-
const doc =
|
|
1583
|
+
const doc = yaml.load(await definitionFile.getContents());
|
|
1642
1584
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1643
1585
|
doc.snyk = generatedDefinition.snyk;
|
|
1644
1586
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
@@ -1646,7 +1588,7 @@ async function dumpSetup(config, reporter, github, snyk, outfile, definitionFile
|
|
|
1646
1588
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1647
1589
|
doc.github = generatedDefinition.github;
|
|
1648
1590
|
// Convert to/from plain JSON so that undefined elements are removed.
|
|
1649
|
-
|
|
1591
|
+
fs.writeFileSync(outfile, yaml.dump(JSON.parse(JSON.stringify(doc))));
|
|
1650
1592
|
reporter.info(`Saved to ${outfile}`);
|
|
1651
1593
|
reporter.info(`Number of GitHub requests: ${github.requestCount}`);
|
|
1652
1594
|
}
|
|
@@ -1691,7 +1633,7 @@ const command$f = {
|
|
|
1691
1633
|
.demandCommand()
|
|
1692
1634
|
.usage(`cals definition`),
|
|
1693
1635
|
handler: () => {
|
|
1694
|
-
|
|
1636
|
+
yargs(hideBin(process.argv)).showHelp();
|
|
1695
1637
|
},
|
|
1696
1638
|
};
|
|
1697
1639
|
|
|
@@ -1719,9 +1661,9 @@ const command$d = {
|
|
|
1719
1661
|
async function analyzeDirectory(reporter, config, github, org) {
|
|
1720
1662
|
const repos = await github.getOrgRepoList({ org });
|
|
1721
1663
|
const reposDict = repos.reduce((acc, cur) => ({ ...acc, [cur.name]: cur }), {});
|
|
1722
|
-
const dirs =
|
|
1664
|
+
const dirs = fs
|
|
1723
1665
|
.readdirSync(config.cwd)
|
|
1724
|
-
.filter((it) =>
|
|
1666
|
+
.filter((it) => fs.statSync(path.join(config.cwd, it)).isDirectory())
|
|
1725
1667
|
// Skip hidden folders
|
|
1726
1668
|
.filter((it) => !it.startsWith("."))
|
|
1727
1669
|
.sort((a, b) => a.localeCompare(b));
|
|
@@ -1732,18 +1674,18 @@ async function analyzeDirectory(reporter, config, github, org) {
|
|
|
1732
1674
|
};
|
|
1733
1675
|
dirs.forEach((it) => {
|
|
1734
1676
|
if (!(it in reposDict)) {
|
|
1735
|
-
reporter.warn(
|
|
1677
|
+
reporter.warn(sprintf("%-30s <-- Not found in repository list (maybe changed name?)", it));
|
|
1736
1678
|
stats.unknown++;
|
|
1737
1679
|
return;
|
|
1738
1680
|
}
|
|
1739
1681
|
if (reposDict[it].isArchived) {
|
|
1740
|
-
reporter.info(
|
|
1682
|
+
reporter.info(sprintf("%-30s <-- Archived", it));
|
|
1741
1683
|
stats.archived++;
|
|
1742
1684
|
return;
|
|
1743
1685
|
}
|
|
1744
1686
|
stats.ok += 1;
|
|
1745
1687
|
});
|
|
1746
|
-
reporter.info(
|
|
1688
|
+
reporter.info(sprintf("Stats: unknown=%d archived=%d ok=%d", stats.unknown, stats.archived, stats.ok));
|
|
1747
1689
|
reporter.info("Use `cals github generate-clone-commands` to check for repositories not checked out");
|
|
1748
1690
|
}
|
|
1749
1691
|
const command$c = {
|
|
@@ -1766,7 +1708,6 @@ const command$c = {
|
|
|
1766
1708
|
};
|
|
1767
1709
|
|
|
1768
1710
|
function getChangedRepoAttribs(definitionRepo, actualRepo) {
|
|
1769
|
-
var _a, _b;
|
|
1770
1711
|
const attribs = [];
|
|
1771
1712
|
const archived = definitionRepo.archived || false;
|
|
1772
1713
|
if (archived !== actualRepo.archived) {
|
|
@@ -1774,13 +1715,13 @@ function getChangedRepoAttribs(definitionRepo, actualRepo) {
|
|
|
1774
1715
|
archived,
|
|
1775
1716
|
});
|
|
1776
1717
|
}
|
|
1777
|
-
const issues =
|
|
1718
|
+
const issues = definitionRepo.issues ?? true;
|
|
1778
1719
|
if (issues !== actualRepo.has_issues && !actualRepo.archived) {
|
|
1779
1720
|
attribs.push({
|
|
1780
1721
|
issues,
|
|
1781
1722
|
});
|
|
1782
1723
|
}
|
|
1783
|
-
const wiki =
|
|
1724
|
+
const wiki = definitionRepo.wiki ?? true;
|
|
1784
1725
|
if (wiki !== actualRepo.has_wiki && !actualRepo.archived) {
|
|
1785
1726
|
attribs.push({
|
|
1786
1727
|
wiki,
|
|
@@ -1797,7 +1738,7 @@ function getChangedRepoAttribs(definitionRepo, actualRepo) {
|
|
|
1797
1738
|
async function getUnknownRepos(github, definition, limitToOrg) {
|
|
1798
1739
|
const knownRepos = getRepos(definition).map((it) => it.id);
|
|
1799
1740
|
const orgs = getGitHubOrgs(definition).filter((orgName) => limitToOrg === undefined || limitToOrg === orgName);
|
|
1800
|
-
return
|
|
1741
|
+
return sortBy((await pMap(orgs, (orgName) => github.getOrgRepoList({ org: orgName })))
|
|
1801
1742
|
.flat()
|
|
1802
1743
|
.filter((it) => !knownRepos.includes(`${it.owner.login}/${it.name}`)), (it) => `${it.owner.login}/${it.name}`);
|
|
1803
1744
|
}
|
|
@@ -1812,9 +1753,8 @@ function getExpectedTeams(projectTeams, repoTeams) {
|
|
|
1812
1753
|
];
|
|
1813
1754
|
}
|
|
1814
1755
|
async function getRepoTeamChanges({ github, org, projectRepo, repo, }) {
|
|
1815
|
-
var _a, _b;
|
|
1816
1756
|
const changes = [];
|
|
1817
|
-
const expectedTeams = getExpectedTeams(
|
|
1757
|
+
const expectedTeams = getExpectedTeams(org.teams ?? [], projectRepo.teams ?? []);
|
|
1818
1758
|
const existingTeams = await github.getRepositoryTeamsList(repo);
|
|
1819
1759
|
// Check for teams to be added / modified.
|
|
1820
1760
|
for (const repoteam of expectedTeams) {
|
|
@@ -1892,7 +1832,7 @@ async function createChangeSetItemsForProjects(github, definition, limitToOrg) {
|
|
|
1892
1832
|
const orgs = definition.projects
|
|
1893
1833
|
.flatMap((it) => it.github)
|
|
1894
1834
|
.filter((org) => limitToOrg === undefined || limitToOrg === org.organization);
|
|
1895
|
-
changes.push(...(await
|
|
1835
|
+
changes.push(...(await pMap(orgs, async (org) => pMap(org.repos || [], (projectRepo) => getProjectRepoChanges({
|
|
1896
1836
|
github,
|
|
1897
1837
|
org,
|
|
1898
1838
|
projectRepo,
|
|
@@ -1990,8 +1930,7 @@ async function createChangeSetItemsForTeams(github, definition, org) {
|
|
|
1990
1930
|
}
|
|
1991
1931
|
});
|
|
1992
1932
|
const overlappingTeams = actualTeams.filter((it) => wantedTeamNames.includes(it.name));
|
|
1993
|
-
await
|
|
1994
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1933
|
+
await pMap(overlappingTeams, async (actualTeam) => {
|
|
1995
1934
|
const wantedTeam = teams.find((it) => it.name === actualTeam.name);
|
|
1996
1935
|
const actualMembers = await github.getTeamMemberListIncludingInvited(org, actualTeam);
|
|
1997
1936
|
actualMembers
|
|
@@ -2194,7 +2133,7 @@ function createOrgGetter(github) {
|
|
|
2194
2133
|
const semaphores = {};
|
|
2195
2134
|
function getSemaphore(orgName) {
|
|
2196
2135
|
if (!(orgName in semaphores)) {
|
|
2197
|
-
semaphores[orgName] =
|
|
2136
|
+
semaphores[orgName] = pLimit(1);
|
|
2198
2137
|
}
|
|
2199
2138
|
return semaphores[orgName];
|
|
2200
2139
|
}
|
|
@@ -2251,11 +2190,12 @@ async function process$1(reporter, github, definition, getOrg, execute, limitToO
|
|
|
2251
2190
|
}
|
|
2252
2191
|
if (execute && changes.length > 0) {
|
|
2253
2192
|
const cont = await new Promise((resolve, reject) => {
|
|
2254
|
-
|
|
2193
|
+
read({
|
|
2255
2194
|
prompt: "Confirm you want to execute the changes [y/N]: ",
|
|
2256
2195
|
timeout: 60000,
|
|
2257
2196
|
}, (err, answer) => {
|
|
2258
2197
|
if (err) {
|
|
2198
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
2259
2199
|
reject(err);
|
|
2260
2200
|
}
|
|
2261
2201
|
resolve(answer);
|
|
@@ -2301,7 +2241,7 @@ const command$b = {
|
|
|
2301
2241
|
|
|
2302
2242
|
async function generateCloneCommands({ reporter, config, github, org, ...opt }) {
|
|
2303
2243
|
if (!opt.listGroups && !opt.all && opt.group === undefined) {
|
|
2304
|
-
|
|
2244
|
+
yargs(hideBin(process.argv)).showHelp();
|
|
2305
2245
|
return;
|
|
2306
2246
|
}
|
|
2307
2247
|
const repos = await github.getOrgRepoList({ org });
|
|
@@ -2321,11 +2261,11 @@ async function generateCloneCommands({ reporter, config, github, org, ...opt })
|
|
|
2321
2261
|
.filter((it) => opt.name === undefined || it.name.includes(opt.name))
|
|
2322
2262
|
.filter((it) => opt.topic === undefined || includesTopic(it, opt.topic))
|
|
2323
2263
|
.filter((it) => !opt.excludeExisting ||
|
|
2324
|
-
!
|
|
2264
|
+
!fs.existsSync(path.resolve(config.cwd, it.name)))
|
|
2325
2265
|
.forEach((repo) => {
|
|
2326
2266
|
// The output of this is used to pipe into e.g. bash.
|
|
2327
2267
|
// We cannot use reporter.log as it adds additional characters.
|
|
2328
|
-
process.stdout.write(
|
|
2268
|
+
process.stdout.write(sprintf('[ ! -e "%s" ] && git clone %s\n', repo.name, repo.sshUrl));
|
|
2329
2269
|
});
|
|
2330
2270
|
});
|
|
2331
2271
|
}
|
|
@@ -2423,10 +2363,10 @@ async function listPullRequestsStats({ reporter, github, }) {
|
|
|
2423
2363
|
reporter.log("Pull requests stats:");
|
|
2424
2364
|
reporter.log("A pull request is considered old after 60 days");
|
|
2425
2365
|
reporter.log("");
|
|
2426
|
-
reporter.log(
|
|
2427
|
-
reporter.log(
|
|
2366
|
+
reporter.log(sprintf("%-40s %12s %2s %12s %2s", "", "normal", "", "snyk", ""));
|
|
2367
|
+
reporter.log(sprintf("%-40s %7s %7s %7s %7s", "Repo", "old", "recent", "old", "recent"));
|
|
2428
2368
|
categories.forEach((cat) => {
|
|
2429
|
-
reporter.log(
|
|
2369
|
+
reporter.log(sprintf("%-40s %7s %7s %7s %7s", cat.key, cat.old.length === 0 ? "" : cat.old.length, cat.recent.length === 0 ? "" : cat.recent.length, cat.oldSnyk.length === 0 ? "" : cat.oldSnyk.length, cat.recentSnyk.length === 0 ? "" : cat.recentSnyk.length));
|
|
2430
2370
|
});
|
|
2431
2371
|
}
|
|
2432
2372
|
}
|
|
@@ -2591,15 +2531,15 @@ async function listWebhooks(reporter, cache, github, org) {
|
|
|
2591
2531
|
}
|
|
2592
2532
|
switch (hook.name) {
|
|
2593
2533
|
case "web":
|
|
2594
|
-
reporter.log(
|
|
2534
|
+
reporter.log(sprintf(" web: %s (%s) (%s)", hook.config.url, hook.last_response.code, hook.events.join(", ")));
|
|
2595
2535
|
break;
|
|
2596
2536
|
case "jenkinsgit":
|
|
2597
|
-
reporter.log(
|
|
2537
|
+
reporter.log(sprintf(" jenkinsgit: %s (%s) (%s)",
|
|
2598
2538
|
// This is undocumented.
|
|
2599
2539
|
hook.config.jenkins_url, hook.last_response.code, hook.events.join(", ")));
|
|
2600
2540
|
break;
|
|
2601
2541
|
case "docker":
|
|
2602
|
-
reporter.log(
|
|
2542
|
+
reporter.log(sprintf(" docker (%s) (%s)", hook.last_response.code, hook.events.join(", ")));
|
|
2603
2543
|
break;
|
|
2604
2544
|
default:
|
|
2605
2545
|
reporter.log(` ${hook.name}: <unknown type>`);
|
|
@@ -2628,11 +2568,12 @@ async function setToken$1({ reporter, token, tokenProvider, }) {
|
|
|
2628
2568
|
reporter.info("Need API token to talk to GitHub");
|
|
2629
2569
|
reporter.info("https://github.com/settings/tokens/new?scopes=repo:status,read:repo_hook");
|
|
2630
2570
|
token = await new Promise((resolve, reject) => {
|
|
2631
|
-
|
|
2571
|
+
read({
|
|
2632
2572
|
prompt: "Enter new GitHub API token: ",
|
|
2633
2573
|
silent: true,
|
|
2634
2574
|
}, (err, answer) => {
|
|
2635
2575
|
if (err) {
|
|
2576
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
2636
2577
|
reject(err);
|
|
2637
2578
|
}
|
|
2638
2579
|
resolve(answer);
|
|
@@ -2691,20 +2632,22 @@ var CloneType;
|
|
|
2691
2632
|
CloneType[CloneType["SSH"] = 1] = "SSH";
|
|
2692
2633
|
})(CloneType || (CloneType = {}));
|
|
2693
2634
|
class GitRepo {
|
|
2635
|
+
path;
|
|
2636
|
+
logCommand;
|
|
2694
2637
|
constructor(path, logCommand) {
|
|
2695
2638
|
this.path = path;
|
|
2696
2639
|
this.logCommand = logCommand;
|
|
2697
2640
|
}
|
|
2698
2641
|
async cloneGitHubRepo(org, name, cloneType) {
|
|
2699
|
-
const parent =
|
|
2700
|
-
if (!
|
|
2701
|
-
await
|
|
2642
|
+
const parent = path.dirname(this.path);
|
|
2643
|
+
if (!fs.existsSync(parent)) {
|
|
2644
|
+
await fs.promises.mkdir(parent, { recursive: true });
|
|
2702
2645
|
}
|
|
2703
2646
|
const cloneUrl = cloneType === CloneType.SSH
|
|
2704
2647
|
? `git@github.com:${org}/${name}.git`
|
|
2705
2648
|
: `https://github.com/${org}/${name}.git`;
|
|
2706
2649
|
try {
|
|
2707
|
-
const result = await
|
|
2650
|
+
const result = await execa("git", ["clone", cloneUrl, this.path], {
|
|
2708
2651
|
cwd: parent,
|
|
2709
2652
|
});
|
|
2710
2653
|
await this.logCommand(result);
|
|
@@ -2717,7 +2660,7 @@ class GitRepo {
|
|
|
2717
2660
|
}
|
|
2718
2661
|
async git(args) {
|
|
2719
2662
|
try {
|
|
2720
|
-
const result = await
|
|
2663
|
+
const result = await execa("git", args, {
|
|
2721
2664
|
cwd: this.path,
|
|
2722
2665
|
});
|
|
2723
2666
|
await this.logCommand(result);
|
|
@@ -2750,7 +2693,6 @@ class GitRepo {
|
|
|
2750
2693
|
return parseShortlogSummary(result.stdout);
|
|
2751
2694
|
}
|
|
2752
2695
|
async update() {
|
|
2753
|
-
var _a;
|
|
2754
2696
|
const fetchOnly = async () => {
|
|
2755
2697
|
await this.git(["fetch"]);
|
|
2756
2698
|
return {
|
|
@@ -2773,7 +2715,7 @@ class GitRepo {
|
|
|
2773
2715
|
return {
|
|
2774
2716
|
dirty: false,
|
|
2775
2717
|
updated: wasUpdated(result.stdout),
|
|
2776
|
-
updatedRange:
|
|
2718
|
+
updatedRange: getUpdateRange(result.stdout) ?? undefined,
|
|
2777
2719
|
};
|
|
2778
2720
|
}
|
|
2779
2721
|
}
|
|
@@ -2785,11 +2727,11 @@ const CALS_LOG = ".cals.log";
|
|
|
2785
2727
|
* backward slashes in paths.
|
|
2786
2728
|
*/
|
|
2787
2729
|
function getRelpath(it) {
|
|
2788
|
-
return
|
|
2730
|
+
return path.join(it.group, it.name);
|
|
2789
2731
|
}
|
|
2790
2732
|
async function appendFile(path, data) {
|
|
2791
2733
|
return new Promise((resolve, reject) => {
|
|
2792
|
-
|
|
2734
|
+
fs.appendFile(path, data, { encoding: "utf-8" }, (err) => {
|
|
2793
2735
|
if (err !== null) {
|
|
2794
2736
|
reject(err);
|
|
2795
2737
|
}
|
|
@@ -2798,15 +2740,14 @@ async function appendFile(path, data) {
|
|
|
2798
2740
|
});
|
|
2799
2741
|
}
|
|
2800
2742
|
function getAliases(repo) {
|
|
2801
|
-
|
|
2802
|
-
return ((_a = repo.previousNames) !== null && _a !== void 0 ? _a : []).map((it) => ({
|
|
2743
|
+
return (repo.previousNames ?? []).map((it) => ({
|
|
2803
2744
|
group: it.project,
|
|
2804
2745
|
name: it.name,
|
|
2805
2746
|
}));
|
|
2806
2747
|
}
|
|
2807
2748
|
async function updateReposInParallel(reporter, items) {
|
|
2808
2749
|
// Perform git operations in parallel, but limit how much.
|
|
2809
|
-
const semaphore =
|
|
2750
|
+
const semaphore = pLimit(30);
|
|
2810
2751
|
const promises = items.map((repo) => semaphore(async () => {
|
|
2811
2752
|
try {
|
|
2812
2753
|
return {
|
|
@@ -2815,7 +2756,6 @@ async function updateReposInParallel(reporter, items) {
|
|
|
2815
2756
|
};
|
|
2816
2757
|
}
|
|
2817
2758
|
catch (e) {
|
|
2818
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
2819
2759
|
reporter.error(`Failed for ${repo.actualRelpath} - skipping. ${e}`);
|
|
2820
2760
|
return null;
|
|
2821
2761
|
}
|
|
@@ -2851,7 +2791,7 @@ async function updateRepos(reporter, foundRepos) {
|
|
|
2851
2791
|
: undefined;
|
|
2852
2792
|
// We only focus on changes made by humans, which we make
|
|
2853
2793
|
// green while keeping the other changes gray.
|
|
2854
|
-
const repoNameFormat =
|
|
2794
|
+
const repoNameFormat = authors?.every(({ name }) => isBotAuthor(name))
|
|
2855
2795
|
? reporter.format.gray
|
|
2856
2796
|
: reporter.format.greenBright;
|
|
2857
2797
|
reporter.info(`Updated: ${repoNameFormat(repo.id)}`);
|
|
@@ -2868,8 +2808,8 @@ async function updateRepos(reporter, foundRepos) {
|
|
|
2868
2808
|
}
|
|
2869
2809
|
}
|
|
2870
2810
|
function guessDefinitionRepoName(rootdir, cals) {
|
|
2871
|
-
const p =
|
|
2872
|
-
const relativePath =
|
|
2811
|
+
const p = path.resolve(rootdir, cals.resourcesDefinition.path);
|
|
2812
|
+
const relativePath = path.relative(rootdir, p);
|
|
2873
2813
|
if (relativePath.slice(0, 1) == ".") {
|
|
2874
2814
|
return null;
|
|
2875
2815
|
}
|
|
@@ -2882,8 +2822,8 @@ function guessDefinitionRepoName(rootdir, cals) {
|
|
|
2882
2822
|
return parts[1];
|
|
2883
2823
|
}
|
|
2884
2824
|
async function getDefinition(rootdir, cals) {
|
|
2885
|
-
const p =
|
|
2886
|
-
if (!
|
|
2825
|
+
const p = path.resolve(rootdir, cals.resourcesDefinition.path);
|
|
2826
|
+
if (!fs.existsSync(p)) {
|
|
2887
2827
|
throw Error(`The file ${p} does not exist`);
|
|
2888
2828
|
}
|
|
2889
2829
|
return new DefinitionFile(p).getDefinition();
|
|
@@ -2892,9 +2832,9 @@ async function getDefinition(rootdir, cals) {
|
|
|
2892
2832
|
* Get directory names within a directory.
|
|
2893
2833
|
*/
|
|
2894
2834
|
function getDirNames(parent) {
|
|
2895
|
-
return (
|
|
2835
|
+
return (fs
|
|
2896
2836
|
.readdirSync(parent)
|
|
2897
|
-
.filter((it) =>
|
|
2837
|
+
.filter((it) => fs.statSync(path.join(parent, it)).isDirectory())
|
|
2898
2838
|
// Skip hidden folders
|
|
2899
2839
|
.filter((it) => !it.startsWith("."))
|
|
2900
2840
|
.sort((a, b) => a.localeCompare(b)));
|
|
@@ -2904,9 +2844,9 @@ async function getReposInOrg(cals, rootdir) {
|
|
|
2904
2844
|
return getRepos(definition)
|
|
2905
2845
|
.filter((it) => it.orgName === cals.githubOrganization)
|
|
2906
2846
|
.filter((it) => cals.resourcesDefinition.tags === undefined ||
|
|
2907
|
-
(it.project.tags || []).some((tag) =>
|
|
2847
|
+
(it.project.tags || []).some((tag) => cals.resourcesDefinition.tags?.includes(tag)) ||
|
|
2908
2848
|
// Always include if already checked out to avoid stale state.
|
|
2909
|
-
|
|
2849
|
+
fs.existsSync(path.join(rootdir, it.project.name, it.repo.name)));
|
|
2910
2850
|
}
|
|
2911
2851
|
function getExpectedRepo(item) {
|
|
2912
2852
|
return {
|
|
@@ -2919,8 +2859,8 @@ function getExpectedRepo(item) {
|
|
|
2919
2859
|
};
|
|
2920
2860
|
}
|
|
2921
2861
|
function getGitRepo(rootdir, relpath) {
|
|
2922
|
-
return new GitRepo(
|
|
2923
|
-
await appendFile(
|
|
2862
|
+
return new GitRepo(path.resolve(rootdir, relpath), async (result) => {
|
|
2863
|
+
await appendFile(path.resolve(rootdir, CALS_LOG), JSON.stringify({
|
|
2924
2864
|
time: new Date().toISOString(),
|
|
2925
2865
|
context: relpath,
|
|
2926
2866
|
type: "exec-result",
|
|
@@ -2974,11 +2914,12 @@ async function getExpectedRepos(reporter, github, cals, rootdir) {
|
|
|
2974
2914
|
}
|
|
2975
2915
|
async function getInput(prompt) {
|
|
2976
2916
|
return new Promise((resolve, reject) => {
|
|
2977
|
-
|
|
2917
|
+
read({
|
|
2978
2918
|
prompt,
|
|
2979
2919
|
timeout: 60000,
|
|
2980
2920
|
}, (err, answer) => {
|
|
2981
2921
|
if (err) {
|
|
2922
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
2982
2923
|
reject(err);
|
|
2983
2924
|
}
|
|
2984
2925
|
resolve(answer);
|
|
@@ -3011,15 +2952,15 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3011
2952
|
const foundRepos = [];
|
|
3012
2953
|
// Categorize all dirs.
|
|
3013
2954
|
for (const topdir of getDirNames(rootdir)) {
|
|
3014
|
-
const isGitDir =
|
|
2955
|
+
const isGitDir = fs.existsSync(path.join(rootdir, topdir, ".git"));
|
|
3015
2956
|
if (isGitDir) {
|
|
3016
2957
|
// Do not traverse deeper inside another Git repo, as that might
|
|
3017
2958
|
// mean we do not have the proper grouped structure.
|
|
3018
2959
|
unknownDirs.push(topdir);
|
|
3019
2960
|
continue;
|
|
3020
2961
|
}
|
|
3021
|
-
for (const subdir of getDirNames(
|
|
3022
|
-
const p =
|
|
2962
|
+
for (const subdir of getDirNames(path.join(rootdir, topdir))) {
|
|
2963
|
+
const p = path.join(topdir, subdir);
|
|
3023
2964
|
const expectedRepo = expectedRepos.find((it) => getRelpath(it) === p ||
|
|
3024
2965
|
it.aliases.some((alias) => getRelpath(alias) === p));
|
|
3025
2966
|
if (expectedRepo === undefined) {
|
|
@@ -3047,9 +2988,9 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3047
2988
|
for (const it of archivedRepos) {
|
|
3048
2989
|
reporter.info(` ${it.actualRelpath}`);
|
|
3049
2990
|
}
|
|
3050
|
-
const thisDirName =
|
|
2991
|
+
const thisDirName = path.basename(process.cwd());
|
|
3051
2992
|
const archiveDir = `../${thisDirName}-archive`;
|
|
3052
|
-
const hasArchiveDir =
|
|
2993
|
+
const hasArchiveDir = fs.existsSync(archiveDir);
|
|
3053
2994
|
if (hasArchiveDir) {
|
|
3054
2995
|
reporter.info("To move these:");
|
|
3055
2996
|
for (const it of archivedRepos) {
|
|
@@ -3072,17 +3013,17 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3072
3013
|
const shouldMove = await askMoveConfirm();
|
|
3073
3014
|
if (shouldMove) {
|
|
3074
3015
|
for (const it of movedRepos) {
|
|
3075
|
-
const src =
|
|
3076
|
-
const dest =
|
|
3077
|
-
const destParent =
|
|
3078
|
-
if (
|
|
3016
|
+
const src = path.join(rootdir, it.actualRelpath);
|
|
3017
|
+
const dest = path.join(rootdir, getRelpath(it));
|
|
3018
|
+
const destParent = path.join(rootdir, it.group);
|
|
3019
|
+
if (fs.existsSync(dest)) {
|
|
3079
3020
|
throw new Error(`Target directory already exists: ${dest} - cannot move ${it.actualRelpath}`);
|
|
3080
3021
|
}
|
|
3081
3022
|
reporter.info(`Moving ${it.actualRelpath} -> ${getRelpath(it)}`);
|
|
3082
|
-
if (!
|
|
3083
|
-
await
|
|
3023
|
+
if (!fs.existsSync(destParent)) {
|
|
3024
|
+
await fs.promises.mkdir(destParent, { recursive: true });
|
|
3084
3025
|
}
|
|
3085
|
-
await
|
|
3026
|
+
await fs.promises.rename(src, dest);
|
|
3086
3027
|
}
|
|
3087
3028
|
// We would have to update expectedRepos if we want to continue.
|
|
3088
3029
|
// Let's try keeping this simple.
|
|
@@ -3115,7 +3056,7 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3115
3056
|
}
|
|
3116
3057
|
const reposToUpdate = foundRepos.filter((it) =>
|
|
3117
3058
|
// Avoid double-processing the defintion repo.
|
|
3118
|
-
|
|
3059
|
+
definitionRepo?.id !== it.id);
|
|
3119
3060
|
reporter.info(`${reposToUpdate.length} repos identified to be updated`);
|
|
3120
3061
|
await updateRepos(reporter, reposToUpdate);
|
|
3121
3062
|
// Report repos with changes ahead.
|
|
@@ -3126,7 +3067,7 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3126
3067
|
}
|
|
3127
3068
|
}
|
|
3128
3069
|
async function loadCalsManifest(config, reporter) {
|
|
3129
|
-
const p = await
|
|
3070
|
+
const p = await findUp(CALS_YAML, { cwd: config.cwd });
|
|
3130
3071
|
if (p === undefined) {
|
|
3131
3072
|
reporter.error(`File ${CALS_YAML} not found. See help`);
|
|
3132
3073
|
process.exitCode = 1;
|
|
@@ -3135,12 +3076,12 @@ async function loadCalsManifest(config, reporter) {
|
|
|
3135
3076
|
// TODO: Verify file has expected contents.
|
|
3136
3077
|
// (Can we easily generate schema for type and verify?)
|
|
3137
3078
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
|
|
3138
|
-
const cals =
|
|
3079
|
+
const cals = yaml.load(fs.readFileSync(p, "utf-8"));
|
|
3139
3080
|
if (cals.version !== 2) {
|
|
3140
3081
|
throw new Error(`Unexpected version in ${p}`);
|
|
3141
3082
|
}
|
|
3142
3083
|
return {
|
|
3143
|
-
dir:
|
|
3084
|
+
dir: path.dirname(p),
|
|
3144
3085
|
cals,
|
|
3145
3086
|
};
|
|
3146
3087
|
}
|
|
@@ -3245,23 +3186,21 @@ Notes:
|
|
|
3245
3186
|
option to avoid stale cache. The cache can also be cleared with
|
|
3246
3187
|
the "cals delete-cache" command.`),
|
|
3247
3188
|
handler: () => {
|
|
3248
|
-
|
|
3189
|
+
yargs(hideBin(process.argv)).showHelp();
|
|
3249
3190
|
},
|
|
3250
3191
|
};
|
|
3251
3192
|
|
|
3252
3193
|
function totalSeverityCount(project) {
|
|
3253
|
-
|
|
3254
|
-
return (((_a = project.issueCountsBySeverity.critical) !== null && _a !== void 0 ? _a : 0) +
|
|
3194
|
+
return ((project.issueCountsBySeverity.critical ?? 0) +
|
|
3255
3195
|
project.issueCountsBySeverity.high +
|
|
3256
3196
|
project.issueCountsBySeverity.medium +
|
|
3257
3197
|
project.issueCountsBySeverity.low);
|
|
3258
3198
|
}
|
|
3259
3199
|
function buildStatsLine(stats) {
|
|
3260
|
-
var _a;
|
|
3261
3200
|
function item(num, str) {
|
|
3262
|
-
return num === 0 ?
|
|
3201
|
+
return num === 0 ? repeat(" ", str.length + 4) : sprintf("%3d %s", num, str);
|
|
3263
3202
|
}
|
|
3264
|
-
return
|
|
3203
|
+
return sprintf("%s %s %s %s", item(stats.critical ?? 0, "critical"), item(stats.high, "high"), item(stats.medium, "medium"), item(stats.low, "low"));
|
|
3265
3204
|
}
|
|
3266
3205
|
async function report({ reporter, snyk, definitionFile, }) {
|
|
3267
3206
|
const definition = await definitionFile.getDefinition();
|
|
@@ -3279,30 +3218,30 @@ async function report({ reporter, snyk, definitionFile, }) {
|
|
|
3279
3218
|
function getProjectName(project) {
|
|
3280
3219
|
return project ? project.name : "unknown project";
|
|
3281
3220
|
}
|
|
3282
|
-
const byProjects =
|
|
3221
|
+
const byProjects = sortBy(Object.values(groupBy(enhancedRepos, (it) => it.project ? it.project.name : "unknown")), (it) => getProjectName(it[0].project));
|
|
3283
3222
|
if (byProjects.length === 0) {
|
|
3284
3223
|
reporter.info("No issues found");
|
|
3285
3224
|
}
|
|
3286
3225
|
else {
|
|
3287
|
-
reporter.info(
|
|
3288
|
-
critical:
|
|
3289
|
-
high:
|
|
3290
|
-
medium:
|
|
3291
|
-
low:
|
|
3226
|
+
reporter.info(sprintf("%-70s %s", "Total count", buildStatsLine({
|
|
3227
|
+
critical: sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.critical ?? 0),
|
|
3228
|
+
high: sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.high),
|
|
3229
|
+
medium: sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.medium),
|
|
3230
|
+
low: sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.low),
|
|
3292
3231
|
})));
|
|
3293
3232
|
reporter.info("Issues by project:");
|
|
3294
3233
|
byProjects.forEach((repos) => {
|
|
3295
3234
|
const project = repos[0].project;
|
|
3296
3235
|
const totalCount = {
|
|
3297
|
-
critical:
|
|
3298
|
-
high:
|
|
3299
|
-
medium:
|
|
3300
|
-
low:
|
|
3236
|
+
critical: sumBy(repos, (it) => it.repo.issueCountsBySeverity.critical ?? 0),
|
|
3237
|
+
high: sumBy(repos, (it) => it.repo.issueCountsBySeverity.high),
|
|
3238
|
+
medium: sumBy(repos, (it) => it.repo.issueCountsBySeverity.medium),
|
|
3239
|
+
low: sumBy(repos, (it) => it.repo.issueCountsBySeverity.low),
|
|
3301
3240
|
};
|
|
3302
3241
|
reporter.info("");
|
|
3303
|
-
reporter.info(
|
|
3242
|
+
reporter.info(sprintf("%-70s %s", getProjectName(project), buildStatsLine(totalCount)));
|
|
3304
3243
|
for (const { repo } of repos) {
|
|
3305
|
-
reporter.info(
|
|
3244
|
+
reporter.info(sprintf(" %-68s %s", repo.name, buildStatsLine(repo.issueCountsBySeverity)));
|
|
3306
3245
|
}
|
|
3307
3246
|
});
|
|
3308
3247
|
}
|
|
@@ -3323,11 +3262,12 @@ async function setToken({ reporter, token, tokenProvider, }) {
|
|
|
3323
3262
|
reporter.info("Need API token to talk to Snyk");
|
|
3324
3263
|
reporter.info("See https://app.snyk.io/account");
|
|
3325
3264
|
token = await new Promise((resolve, reject) => {
|
|
3326
|
-
|
|
3265
|
+
read({
|
|
3327
3266
|
prompt: "Enter new Snyk API token: ",
|
|
3328
3267
|
silent: true,
|
|
3329
3268
|
}, (err, answer) => {
|
|
3330
3269
|
if (err) {
|
|
3270
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
3331
3271
|
reject(err);
|
|
3332
3272
|
}
|
|
3333
3273
|
resolve(answer);
|
|
@@ -3394,30 +3334,17 @@ Notes:
|
|
|
3394
3334
|
and provide a link to generate one:
|
|
3395
3335
|
$ cals snyk set-token`),
|
|
3396
3336
|
handler: () => {
|
|
3397
|
-
|
|
3337
|
+
yargs(hideBin(process.argv)).showHelp();
|
|
3398
3338
|
},
|
|
3399
3339
|
};
|
|
3400
3340
|
|
|
3401
3341
|
async function main() {
|
|
3402
|
-
if (!
|
|
3342
|
+
if (!semver.satisfies(process.version, engines.node)) {
|
|
3403
3343
|
console.error(`Required node version ${engines.node} not satisfied with current version ${process.version}.`);
|
|
3404
3344
|
process.exit(1);
|
|
3405
3345
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
_________ __ _____
|
|
3409
|
-
/ ____/ | / / / ___/
|
|
3410
|
-
/ / / /| | / / \\__ \\
|
|
3411
|
-
/ /___/ ___ |/ /______/ /
|
|
3412
|
-
\\____/_/ |_/_____/____/
|
|
3413
|
-
cli ${version}
|
|
3414
|
-
built ${"2024-11-30T21:41:56+0000"}
|
|
3415
|
-
|
|
3416
|
-
https://github.com/capralifecycle/cals-cli/
|
|
3417
|
-
|
|
3418
|
-
Usage: cals <command>`;
|
|
3419
|
-
await yargs__default["default"]
|
|
3420
|
-
.usage(header)
|
|
3346
|
+
await yargs(hideBin(process.argv))
|
|
3347
|
+
.usage(`cals-cli v${version} (build: ${"2024-12-11T13:09:43+0000"})`)
|
|
3421
3348
|
.scriptName("cals")
|
|
3422
3349
|
.locale("en")
|
|
3423
3350
|
.help("help")
|
|
@@ -3442,12 +3369,6 @@ Usage: cals <command>`;
|
|
|
3442
3369
|
})
|
|
3443
3370
|
.parse();
|
|
3444
3371
|
}
|
|
3445
|
-
// Definer prosjekt-navn
|
|
3446
|
-
// Definer ønskede miljøer
|
|
3447
|
-
// Baselines som skal brukse
|
|
3448
|
-
// Er bruker logget inn?
|
|
3449
|
-
// Lag repo og endre commit
|
|
3450
|
-
// Osv. osv. osv.
|
|
3451
3372
|
|
|
3452
3373
|
void main();
|
|
3453
|
-
//# sourceMappingURL=cals-cli.
|
|
3374
|
+
//# sourceMappingURL=cals-cli.mjs.map
|