@capraconsulting/cals-cli 2.22.2 → 2.23.2
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 +130 -109
- package/lib/cals-cli.js.map +1 -1
- package/lib/index.es.js +25 -11
- package/lib/index.es.js.map +1 -1
- package/lib/index.js +73 -59
- package/lib/index.js.map +1 -1
- package/lib/snyk/types.d.ts +7 -2
- package/lib/snyk/util.test.d.ts +1 -0
- package/package.json +26 -26
package/lib/cals-cli.js
CHANGED
|
@@ -47,7 +47,7 @@ var read__default = /*#__PURE__*/_interopDefaultLegacy(read);
|
|
|
47
47
|
var findUp__default = /*#__PURE__*/_interopDefaultLegacy(findUp);
|
|
48
48
|
var execa__default = /*#__PURE__*/_interopDefaultLegacy(execa);
|
|
49
49
|
|
|
50
|
-
var version = "2.
|
|
50
|
+
var version = "2.23.2";
|
|
51
51
|
var engines = {
|
|
52
52
|
node: ">=12.0.0"
|
|
53
53
|
};
|
|
@@ -357,7 +357,7 @@ function getRepoId(orgName, repoName) {
|
|
|
357
357
|
}
|
|
358
358
|
function checkAgainstSchema(value) {
|
|
359
359
|
var _a;
|
|
360
|
-
const ajv = new AJV__default[
|
|
360
|
+
const ajv = new AJV__default["default"]({ allErrors: true });
|
|
361
361
|
const valid = ajv.validate(schema, value);
|
|
362
362
|
return valid
|
|
363
363
|
? { definition: value }
|
|
@@ -434,7 +434,7 @@ class DefinitionFile {
|
|
|
434
434
|
this.path = path;
|
|
435
435
|
}
|
|
436
436
|
async getContents() {
|
|
437
|
-
return new Promise((resolve, reject) => fs__default[
|
|
437
|
+
return new Promise((resolve, reject) => fs__default["default"].readFile(this.path, "utf-8", (err, data) => {
|
|
438
438
|
if (err)
|
|
439
439
|
reject(err);
|
|
440
440
|
else
|
|
@@ -446,7 +446,7 @@ class DefinitionFile {
|
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
function parseDefinition(value) {
|
|
449
|
-
const result = checkAgainstSchema(yaml__default[
|
|
449
|
+
const result = checkAgainstSchema(yaml__default["default"].load(value));
|
|
450
450
|
if ("error" in result) {
|
|
451
451
|
throw new Error("Definition content invalid: " + result.error);
|
|
452
452
|
}
|
|
@@ -476,7 +476,7 @@ class GitHubTokenCliProvider {
|
|
|
476
476
|
if (process.env.CALS_GITHUB_TOKEN) {
|
|
477
477
|
return process.env.CALS_GITHUB_TOKEN;
|
|
478
478
|
}
|
|
479
|
-
const result = await keytar__default[
|
|
479
|
+
const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
|
|
480
480
|
if (result == null) {
|
|
481
481
|
process.stderr.write("No token found. Register using `cals github set-token`\n");
|
|
482
482
|
return undefined;
|
|
@@ -484,10 +484,10 @@ class GitHubTokenCliProvider {
|
|
|
484
484
|
return result;
|
|
485
485
|
}
|
|
486
486
|
async markInvalid() {
|
|
487
|
-
await keytar__default[
|
|
487
|
+
await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
|
|
488
488
|
}
|
|
489
489
|
async setToken(value) {
|
|
490
|
-
await keytar__default[
|
|
490
|
+
await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
493
|
|
|
@@ -560,7 +560,7 @@ class GitHubService {
|
|
|
560
560
|
this.tokenProvider = props.tokenProvider;
|
|
561
561
|
// Control concurrency to GitHub API at service level so we
|
|
562
562
|
// can maximize concurrency all other places.
|
|
563
|
-
this.semaphore = pLimit__default[
|
|
563
|
+
this.semaphore = pLimit__default["default"](6);
|
|
564
564
|
this.octokit.hook.wrap("request", async (request, options) => {
|
|
565
565
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
566
566
|
this._requestCount++;
|
|
@@ -640,7 +640,7 @@ class GitHubService {
|
|
|
640
640
|
const headers = {
|
|
641
641
|
Authorization: `Bearer ${token}`,
|
|
642
642
|
};
|
|
643
|
-
const response = await this.semaphore(() => fetch__default[
|
|
643
|
+
const response = await this.semaphore(() => fetch__default["default"](url, {
|
|
644
644
|
method: "POST",
|
|
645
645
|
headers,
|
|
646
646
|
body: JSON.stringify({ query }),
|
|
@@ -1049,7 +1049,7 @@ class SnykTokenCliProvider {
|
|
|
1049
1049
|
if (process.env.CALS_SNYK_TOKEN) {
|
|
1050
1050
|
return process.env.CALS_SNYK_TOKEN;
|
|
1051
1051
|
}
|
|
1052
|
-
const result = await keytar__default[
|
|
1052
|
+
const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
|
|
1053
1053
|
if (result == null) {
|
|
1054
1054
|
process.stderr.write("No token found. Register using `cals snyk set-token`\n");
|
|
1055
1055
|
return undefined;
|
|
@@ -1057,10 +1057,10 @@ class SnykTokenCliProvider {
|
|
|
1057
1057
|
return result;
|
|
1058
1058
|
}
|
|
1059
1059
|
async markInvalid() {
|
|
1060
|
-
await keytar__default[
|
|
1060
|
+
await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
|
|
1061
1061
|
}
|
|
1062
1062
|
async setToken(value) {
|
|
1063
|
-
await keytar__default[
|
|
1063
|
+
await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
|
|
1064
1064
|
}
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
@@ -1082,7 +1082,7 @@ class SnykService {
|
|
|
1082
1082
|
if (token === undefined) {
|
|
1083
1083
|
throw new Error("Missing token for Snyk");
|
|
1084
1084
|
}
|
|
1085
|
-
const response = await fetch__default[
|
|
1085
|
+
const response = await fetch__default["default"](`https://snyk.io/api/v1/org/${encodeURIComponent(snykAccountId)}/projects`, {
|
|
1086
1086
|
method: "GET",
|
|
1087
1087
|
headers: {
|
|
1088
1088
|
Accept: "application/json",
|
|
@@ -1110,18 +1110,32 @@ function createSnykService(props) {
|
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
1112
|
function getGitHubRepo(snykProject) {
|
|
1113
|
-
if (snykProject.origin
|
|
1114
|
-
|
|
1113
|
+
if (snykProject.origin === "github") {
|
|
1114
|
+
const match = /^([^/]+)\/([^:]+)(:(.+))?$/.exec(snykProject.name);
|
|
1115
|
+
if (match === null) {
|
|
1116
|
+
throw Error(`Could not extract components from Snyk project name: ${snykProject.name} (id: ${snykProject.id})`);
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
owner: match[1],
|
|
1120
|
+
name: match[2],
|
|
1121
|
+
};
|
|
1115
1122
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1123
|
+
else if (snykProject.origin === "cli" &&
|
|
1124
|
+
snykProject.remoteRepoUrl != null) {
|
|
1125
|
+
// The remoteRepoUrl can be overriden when using the CLI, so don't
|
|
1126
|
+
// fail if we cannot extract the value.
|
|
1127
|
+
const match = /github.com\/([^/]+)\/(.+)\.git$/.exec(snykProject.remoteRepoUrl);
|
|
1128
|
+
if (match === null) {
|
|
1129
|
+
return undefined;
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
owner: match[1],
|
|
1133
|
+
name: match[2],
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
return undefined;
|
|
1119
1138
|
}
|
|
1120
|
-
return {
|
|
1121
|
-
owner: match[1],
|
|
1122
|
-
name: match[2],
|
|
1123
|
-
file: match[3],
|
|
1124
|
-
};
|
|
1125
1139
|
}
|
|
1126
1140
|
function getGitHubRepoId(repo) {
|
|
1127
1141
|
return repo ? `${repo.owner}/${repo.name}` : undefined;
|
|
@@ -1139,13 +1153,13 @@ class CacheProvider {
|
|
|
1139
1153
|
* The caller is responsible for handling proper validation,
|
|
1140
1154
|
*/
|
|
1141
1155
|
retrieveJson(cachekey) {
|
|
1142
|
-
const cachefile = path__default[
|
|
1143
|
-
if (!fs__default[
|
|
1156
|
+
const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
|
|
1157
|
+
if (!fs__default["default"].existsSync(cachefile)) {
|
|
1144
1158
|
return undefined;
|
|
1145
1159
|
}
|
|
1146
|
-
const data = fs__default[
|
|
1160
|
+
const data = fs__default["default"].readFileSync(cachefile, "utf-8");
|
|
1147
1161
|
return {
|
|
1148
|
-
cacheTime: fs__default[
|
|
1162
|
+
cacheTime: fs__default["default"].statSync(cachefile).mtime.getTime(),
|
|
1149
1163
|
data: (data === "undefined" ? undefined : JSON.parse(data)),
|
|
1150
1164
|
};
|
|
1151
1165
|
}
|
|
@@ -1153,11 +1167,11 @@ class CacheProvider {
|
|
|
1153
1167
|
* Save data to cache.
|
|
1154
1168
|
*/
|
|
1155
1169
|
storeJson(cachekey, data) {
|
|
1156
|
-
const cachefile = path__default[
|
|
1157
|
-
if (!fs__default[
|
|
1158
|
-
fs__default[
|
|
1170
|
+
const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
|
|
1171
|
+
if (!fs__default["default"].existsSync(this.config.cacheDir)) {
|
|
1172
|
+
fs__default["default"].mkdirSync(this.config.cacheDir, { recursive: true });
|
|
1159
1173
|
}
|
|
1160
|
-
fs__default[
|
|
1174
|
+
fs__default["default"].writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
|
|
1161
1175
|
}
|
|
1162
1176
|
async json(cachekey, block, cachetime = this.defaultCacheTime) {
|
|
1163
1177
|
const cacheItem = this.mustValidate
|
|
@@ -1175,16 +1189,16 @@ class CacheProvider {
|
|
|
1175
1189
|
* Delete all cached data.
|
|
1176
1190
|
*/
|
|
1177
1191
|
cleanup() {
|
|
1178
|
-
rimraf__default[
|
|
1192
|
+
rimraf__default["default"].sync(this.config.cacheDir);
|
|
1179
1193
|
}
|
|
1180
1194
|
}
|
|
1181
1195
|
|
|
1182
1196
|
class Config {
|
|
1183
1197
|
constructor() {
|
|
1184
|
-
this.cwd = path__default[
|
|
1185
|
-
this.configFile = path__default[
|
|
1186
|
-
this.cacheDir = cachedir__default[
|
|
1187
|
-
this.agent = new https__default[
|
|
1198
|
+
this.cwd = path__default["default"].resolve(process.cwd());
|
|
1199
|
+
this.configFile = path__default["default"].join(os__default["default"].homedir(), ".cals-config.json");
|
|
1200
|
+
this.cacheDir = cachedir__default["default"]("cals-cli");
|
|
1201
|
+
this.agent = new https__default["default"].Agent({
|
|
1188
1202
|
keepAlive: true,
|
|
1189
1203
|
});
|
|
1190
1204
|
this.configCached = undefined;
|
|
@@ -1199,12 +1213,12 @@ class Config {
|
|
|
1199
1213
|
return config;
|
|
1200
1214
|
}
|
|
1201
1215
|
readConfig() {
|
|
1202
|
-
if (!fs__default[
|
|
1216
|
+
if (!fs__default["default"].existsSync(this.configFile)) {
|
|
1203
1217
|
return {};
|
|
1204
1218
|
}
|
|
1205
1219
|
try {
|
|
1206
1220
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
1207
|
-
return JSON.parse(fs__default[
|
|
1221
|
+
return JSON.parse(fs__default["default"].readFileSync(this.configFile, "utf-8"));
|
|
1208
1222
|
}
|
|
1209
1223
|
catch (e) {
|
|
1210
1224
|
console.error("Failed", e);
|
|
@@ -1226,15 +1240,15 @@ class Config {
|
|
|
1226
1240
|
...this.readConfig(),
|
|
1227
1241
|
[key]: value, // undefined will remove
|
|
1228
1242
|
};
|
|
1229
|
-
fs__default[
|
|
1243
|
+
fs__default["default"].writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
|
|
1230
1244
|
this.configCached = updatedConfig;
|
|
1231
1245
|
}
|
|
1232
1246
|
}
|
|
1233
1247
|
|
|
1234
1248
|
const CLEAR_WHOLE_LINE = 0;
|
|
1235
1249
|
function clearLine(stdout) {
|
|
1236
|
-
readline__default[
|
|
1237
|
-
readline__default[
|
|
1250
|
+
readline__default["default"].clearLine(stdout, CLEAR_WHOLE_LINE);
|
|
1251
|
+
readline__default["default"].cursorTo(stdout, 0);
|
|
1238
1252
|
}
|
|
1239
1253
|
class Reporter {
|
|
1240
1254
|
constructor(opts = {}) {
|
|
@@ -1242,7 +1256,7 @@ class Reporter {
|
|
|
1242
1256
|
this.stderr = process.stderr;
|
|
1243
1257
|
this.stdin = process.stdin;
|
|
1244
1258
|
this.isTTY = this.stdout.isTTY;
|
|
1245
|
-
this.format = chalk__default[
|
|
1259
|
+
this.format = chalk__default["default"];
|
|
1246
1260
|
this.startTime = Date.now();
|
|
1247
1261
|
this.nonInteractive = !!opts.nonInteractive;
|
|
1248
1262
|
this.isVerbose = !!opts.verbose;
|
|
@@ -1303,7 +1317,7 @@ function getDefinitionFile(argv) {
|
|
|
1303
1317
|
throw Error("Missing --definition-file option");
|
|
1304
1318
|
}
|
|
1305
1319
|
const definitionFile = argv.definitionFile;
|
|
1306
|
-
if (!fs__default[
|
|
1320
|
+
if (!fs__default["default"].existsSync(definitionFile)) {
|
|
1307
1321
|
throw Error(`The file ${definitionFile} does not exist`);
|
|
1308
1322
|
}
|
|
1309
1323
|
return new DefinitionFile(definitionFile);
|
|
@@ -1360,9 +1374,9 @@ function reorderListToSimilarAsBefore(oldList, updatedList, selector, insertLast
|
|
|
1360
1374
|
}
|
|
1361
1375
|
|
|
1362
1376
|
async function getReposFromGitHub(github, orgs) {
|
|
1363
|
-
return (await pMap__default[
|
|
1377
|
+
return (await pMap__default["default"](orgs, async (org) => {
|
|
1364
1378
|
const repos = await github.getOrgRepoList({ org: org.login });
|
|
1365
|
-
return pMap__default[
|
|
1379
|
+
return pMap__default["default"](repos, async (repo) => {
|
|
1366
1380
|
const detailedRepo = await github.getRepository(repo.owner.login, repo.name);
|
|
1367
1381
|
if (detailedRepo === undefined) {
|
|
1368
1382
|
throw Error(`Repo not found: ${repo.owner.login}/${repo.name}`);
|
|
@@ -1376,11 +1390,11 @@ async function getReposFromGitHub(github, orgs) {
|
|
|
1376
1390
|
})).flat();
|
|
1377
1391
|
}
|
|
1378
1392
|
async function getTeams(github, orgs) {
|
|
1379
|
-
const intermediate = await pMap__default[
|
|
1393
|
+
const intermediate = await pMap__default["default"](orgs, async (org) => {
|
|
1380
1394
|
const teams = await github.getTeamList(org);
|
|
1381
1395
|
return {
|
|
1382
1396
|
org,
|
|
1383
|
-
teams: await pMap__default[
|
|
1397
|
+
teams: await pMap__default["default"](teams, async (team) => ({
|
|
1384
1398
|
team,
|
|
1385
1399
|
users: await github.getTeamMemberListIncludingInvited(org, team),
|
|
1386
1400
|
})),
|
|
@@ -1413,7 +1427,7 @@ function getFormattedTeams(oldTeams, teams) {
|
|
|
1413
1427
|
: undefined;
|
|
1414
1428
|
}
|
|
1415
1429
|
async function getOrgs(github, orgs) {
|
|
1416
|
-
return pMap__default[
|
|
1430
|
+
return pMap__default["default"](orgs, (it) => github.getOrg(it));
|
|
1417
1431
|
}
|
|
1418
1432
|
function removeDuplicates(items, selector) {
|
|
1419
1433
|
const ids = [];
|
|
@@ -1428,7 +1442,7 @@ function removeDuplicates(items, selector) {
|
|
|
1428
1442
|
return result;
|
|
1429
1443
|
}
|
|
1430
1444
|
async function getMembers(github, orgs) {
|
|
1431
|
-
return removeDuplicates((await pMap__default[
|
|
1445
|
+
return removeDuplicates((await pMap__default["default"](orgs, (org) => github.getOrgMembersListIncludingInvited(org.login)))
|
|
1432
1446
|
.flat()
|
|
1433
1447
|
.map((it) => it.login), (it) => it);
|
|
1434
1448
|
}
|
|
@@ -1543,7 +1557,7 @@ async function dumpSetup(config, reporter, github, snyk, outfile, definitionFile
|
|
|
1543
1557
|
// package. However it often produced invalid yaml, so we have removed
|
|
1544
1558
|
// it. We might want to revisit it to preserve comments.
|
|
1545
1559
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
|
|
1546
|
-
const doc = yaml__default[
|
|
1560
|
+
const doc = yaml__default["default"].load(await definitionFile.getContents());
|
|
1547
1561
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1548
1562
|
doc.snyk = generatedDefinition.snyk;
|
|
1549
1563
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
@@ -1551,7 +1565,7 @@ async function dumpSetup(config, reporter, github, snyk, outfile, definitionFile
|
|
|
1551
1565
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
1552
1566
|
doc.github = generatedDefinition.github;
|
|
1553
1567
|
// Convert to/from plain JSON so that undefined elements are removed.
|
|
1554
|
-
fs__default[
|
|
1568
|
+
fs__default["default"].writeFileSync(outfile, yaml__default["default"].dump(JSON.parse(JSON.stringify(doc))));
|
|
1555
1569
|
reporter.info(`Saved to ${outfile}`);
|
|
1556
1570
|
reporter.info(`Number of GitHub requests: ${github.requestCount}`);
|
|
1557
1571
|
}
|
|
@@ -1596,7 +1610,7 @@ const command$i = {
|
|
|
1596
1610
|
.demandCommand()
|
|
1597
1611
|
.usage(`cals definition`),
|
|
1598
1612
|
handler: () => {
|
|
1599
|
-
yargs__default[
|
|
1613
|
+
yargs__default["default"].showHelp();
|
|
1600
1614
|
},
|
|
1601
1615
|
};
|
|
1602
1616
|
|
|
@@ -1621,7 +1635,7 @@ class DetectifyTokenCliProvider {
|
|
|
1621
1635
|
if (process.env.CALS_DETECTIFY_TOKEN) {
|
|
1622
1636
|
return process.env.CALS_DETECTIFY_TOKEN;
|
|
1623
1637
|
}
|
|
1624
|
-
const result = await keytar__default[
|
|
1638
|
+
const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
|
|
1625
1639
|
if (result == null) {
|
|
1626
1640
|
process.stderr.write("No token found. Register using `cals detectify set-token`\n");
|
|
1627
1641
|
return undefined;
|
|
@@ -1629,10 +1643,10 @@ class DetectifyTokenCliProvider {
|
|
|
1629
1643
|
return result;
|
|
1630
1644
|
}
|
|
1631
1645
|
async markInvalid() {
|
|
1632
|
-
await keytar__default[
|
|
1646
|
+
await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
|
|
1633
1647
|
}
|
|
1634
1648
|
async setToken(value) {
|
|
1635
|
-
await keytar__default[
|
|
1649
|
+
await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
|
|
1636
1650
|
}
|
|
1637
1651
|
}
|
|
1638
1652
|
|
|
@@ -1652,7 +1666,7 @@ class DetectifyService {
|
|
|
1652
1666
|
if (token === undefined) {
|
|
1653
1667
|
throw new Error("Missing token for Detectify");
|
|
1654
1668
|
}
|
|
1655
|
-
const response = await fetch__default[
|
|
1669
|
+
const response = await fetch__default["default"](url, {
|
|
1656
1670
|
method: "GET",
|
|
1657
1671
|
headers: {
|
|
1658
1672
|
Accept: "application/json",
|
|
@@ -1730,7 +1744,7 @@ async function setToken$2({ reporter, token, tokenProvider, }) {
|
|
|
1730
1744
|
reporter.info("Need API token to talk to Detectify");
|
|
1731
1745
|
reporter.info("See API keys under https://detectify.com/dashboard/team");
|
|
1732
1746
|
token = await new Promise((resolve, reject) => {
|
|
1733
|
-
read__default[
|
|
1747
|
+
read__default["default"]({
|
|
1734
1748
|
prompt: "Enter new Detectify API token: ",
|
|
1735
1749
|
silent: true,
|
|
1736
1750
|
}, (err, answer) => {
|
|
@@ -1771,7 +1785,7 @@ Notes:
|
|
|
1771
1785
|
and provide a link to generate one:
|
|
1772
1786
|
$ cals detectify set-token`),
|
|
1773
1787
|
handler: () => {
|
|
1774
|
-
yargs__default[
|
|
1788
|
+
yargs__default["default"].showHelp();
|
|
1775
1789
|
},
|
|
1776
1790
|
};
|
|
1777
1791
|
|
|
@@ -1787,9 +1801,9 @@ const command$d = {
|
|
|
1787
1801
|
async function analyzeDirectory(reporter, config, github, org) {
|
|
1788
1802
|
const repos = await github.getOrgRepoList({ org });
|
|
1789
1803
|
const reposDict = repos.reduce((acc, cur) => ({ ...acc, [cur.name]: cur }), {});
|
|
1790
|
-
const dirs = fs__default[
|
|
1804
|
+
const dirs = fs__default["default"]
|
|
1791
1805
|
.readdirSync(config.cwd)
|
|
1792
|
-
.filter((it) => fs__default[
|
|
1806
|
+
.filter((it) => fs__default["default"].statSync(path__default["default"].join(config.cwd, it)).isDirectory())
|
|
1793
1807
|
// Skip hidden folders
|
|
1794
1808
|
.filter((it) => !it.startsWith("."))
|
|
1795
1809
|
.sort((a, b) => a.localeCompare(b));
|
|
@@ -1865,7 +1879,7 @@ function getChangedRepoAttribs(definitionRepo, actualRepo) {
|
|
|
1865
1879
|
async function getUnknownRepos(github, definition, limitToOrg) {
|
|
1866
1880
|
const knownRepos = getRepos(definition).map((it) => it.id);
|
|
1867
1881
|
const orgs = getGitHubOrgs(definition).filter((orgName) => limitToOrg === undefined || limitToOrg === orgName);
|
|
1868
|
-
return lodash.sortBy((await pMap__default[
|
|
1882
|
+
return lodash.sortBy((await pMap__default["default"](orgs, (orgName) => github.getOrgRepoList({ org: orgName })))
|
|
1869
1883
|
.flat()
|
|
1870
1884
|
.filter((it) => !knownRepos.includes(`${it.owner.login}/${it.name}`)), (it) => `${it.owner.login}/${it.name}`);
|
|
1871
1885
|
}
|
|
@@ -1960,7 +1974,7 @@ async function createChangeSetItemsForProjects(github, definition, limitToOrg) {
|
|
|
1960
1974
|
const orgs = definition.projects
|
|
1961
1975
|
.flatMap((it) => it.github)
|
|
1962
1976
|
.filter((org) => limitToOrg === undefined || limitToOrg === org.organization);
|
|
1963
|
-
changes.push(...(await pMap__default[
|
|
1977
|
+
changes.push(...(await pMap__default["default"](orgs, async (org) => pMap__default["default"](org.repos || [], (projectRepo) => getProjectRepoChanges({
|
|
1964
1978
|
github,
|
|
1965
1979
|
org,
|
|
1966
1980
|
projectRepo,
|
|
@@ -2058,7 +2072,7 @@ async function createChangeSetItemsForTeams(github, definition, org) {
|
|
|
2058
2072
|
}
|
|
2059
2073
|
});
|
|
2060
2074
|
const overlappingTeams = actualTeams.filter((it) => wantedTeamNames.includes(it.name));
|
|
2061
|
-
await pMap__default[
|
|
2075
|
+
await pMap__default["default"](overlappingTeams, async (actualTeam) => {
|
|
2062
2076
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
2063
2077
|
const wantedTeam = teams.find((it) => it.name === actualTeam.name);
|
|
2064
2078
|
const actualMembers = await github.getTeamMemberListIncludingInvited(org, actualTeam);
|
|
@@ -2256,7 +2270,7 @@ function createOrgGetter(github) {
|
|
|
2256
2270
|
const semaphores = {};
|
|
2257
2271
|
function getSemaphore(orgName) {
|
|
2258
2272
|
if (!(orgName in semaphores)) {
|
|
2259
|
-
semaphores[orgName] = pLimit__default[
|
|
2273
|
+
semaphores[orgName] = pLimit__default["default"](1);
|
|
2260
2274
|
}
|
|
2261
2275
|
return semaphores[orgName];
|
|
2262
2276
|
}
|
|
@@ -2313,7 +2327,7 @@ async function process$1(reporter, github, definition, getOrg, execute, limitToO
|
|
|
2313
2327
|
}
|
|
2314
2328
|
if (execute && changes.length > 0) {
|
|
2315
2329
|
const cont = await new Promise((resolve, reject) => {
|
|
2316
|
-
read__default[
|
|
2330
|
+
read__default["default"]({
|
|
2317
2331
|
prompt: "Confirm you want to execute the changes [y/N]: ",
|
|
2318
2332
|
timeout: 60000,
|
|
2319
2333
|
}, (err, answer) => {
|
|
@@ -2363,7 +2377,7 @@ const command$b = {
|
|
|
2363
2377
|
|
|
2364
2378
|
async function generateCloneCommands({ reporter, config, github, org, ...opt }) {
|
|
2365
2379
|
if (!opt.listGroups && !opt.all && opt.group === undefined) {
|
|
2366
|
-
yargs__default[
|
|
2380
|
+
yargs__default["default"].showHelp();
|
|
2367
2381
|
return;
|
|
2368
2382
|
}
|
|
2369
2383
|
const repos = await github.getOrgRepoList({ org });
|
|
@@ -2383,7 +2397,7 @@ async function generateCloneCommands({ reporter, config, github, org, ...opt })
|
|
|
2383
2397
|
.filter((it) => opt.name === undefined || it.name.includes(opt.name))
|
|
2384
2398
|
.filter((it) => opt.topic === undefined || includesTopic(it, opt.topic))
|
|
2385
2399
|
.filter((it) => !opt.excludeExisting ||
|
|
2386
|
-
!fs__default[
|
|
2400
|
+
!fs__default["default"].existsSync(path__default["default"].resolve(config.cwd, it.name)))
|
|
2387
2401
|
.forEach((repo) => {
|
|
2388
2402
|
// The output of this is used to pipe into e.g. bash.
|
|
2389
2403
|
// We cannot use reporter.log as it adds additional characters.
|
|
@@ -2690,7 +2704,7 @@ async function setToken$1({ reporter, token, tokenProvider, }) {
|
|
|
2690
2704
|
reporter.info("Need API token to talk to GitHub");
|
|
2691
2705
|
reporter.info("https://github.com/settings/tokens/new?scopes=repo:status,read:repo_hook");
|
|
2692
2706
|
token = await new Promise((resolve, reject) => {
|
|
2693
|
-
read__default[
|
|
2707
|
+
read__default["default"]({
|
|
2694
2708
|
prompt: "Enter new GitHub API token: ",
|
|
2695
2709
|
silent: true,
|
|
2696
2710
|
}, (err, answer) => {
|
|
@@ -2758,33 +2772,35 @@ class GitRepo {
|
|
|
2758
2772
|
this.logCommand = logCommand;
|
|
2759
2773
|
}
|
|
2760
2774
|
async cloneGitHubRepo(org, name, cloneType) {
|
|
2761
|
-
const parent = path__default[
|
|
2762
|
-
if (!fs__default[
|
|
2763
|
-
await fs__default[
|
|
2775
|
+
const parent = path__default["default"].dirname(this.path);
|
|
2776
|
+
if (!fs__default["default"].existsSync(parent)) {
|
|
2777
|
+
await fs__default["default"].promises.mkdir(parent, { recursive: true });
|
|
2764
2778
|
}
|
|
2765
2779
|
const cloneUrl = cloneType === CloneType.SSH
|
|
2766
2780
|
? `git@github.com:${org}/${name}.git`
|
|
2767
2781
|
: `https://github.com/${org}/${name}.git`;
|
|
2768
2782
|
try {
|
|
2769
|
-
const result = await execa__default[
|
|
2783
|
+
const result = await execa__default["default"]("git", ["clone", cloneUrl, this.path], {
|
|
2770
2784
|
cwd: parent,
|
|
2771
2785
|
});
|
|
2772
2786
|
await this.logCommand(result);
|
|
2773
2787
|
}
|
|
2774
2788
|
catch (e) {
|
|
2789
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2775
2790
|
await this.logCommand(e);
|
|
2776
2791
|
throw e;
|
|
2777
2792
|
}
|
|
2778
2793
|
}
|
|
2779
2794
|
async git(args) {
|
|
2780
2795
|
try {
|
|
2781
|
-
const result = await execa__default[
|
|
2796
|
+
const result = await execa__default["default"]("git", args, {
|
|
2782
2797
|
cwd: this.path,
|
|
2783
2798
|
});
|
|
2784
2799
|
await this.logCommand(result);
|
|
2785
2800
|
return result;
|
|
2786
2801
|
}
|
|
2787
2802
|
catch (e) {
|
|
2803
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2788
2804
|
await this.logCommand(e);
|
|
2789
2805
|
throw e;
|
|
2790
2806
|
}
|
|
@@ -2845,11 +2861,11 @@ const CALS_LOG = ".cals.log";
|
|
|
2845
2861
|
* backward slashes in paths.
|
|
2846
2862
|
*/
|
|
2847
2863
|
function getRelpath(it) {
|
|
2848
|
-
return path__default[
|
|
2864
|
+
return path__default["default"].join(it.group, it.name);
|
|
2849
2865
|
}
|
|
2850
2866
|
async function appendFile(path, data) {
|
|
2851
2867
|
return new Promise((resolve, reject) => {
|
|
2852
|
-
fs__default[
|
|
2868
|
+
fs__default["default"].appendFile(path, data, { encoding: "utf-8" }, (err) => {
|
|
2853
2869
|
if (err !== null) {
|
|
2854
2870
|
reject(err);
|
|
2855
2871
|
}
|
|
@@ -2866,7 +2882,7 @@ function getAliases(repo) {
|
|
|
2866
2882
|
}
|
|
2867
2883
|
async function updateReposInParallel(reporter, items) {
|
|
2868
2884
|
// Perform git operations in parallel, but limit how much.
|
|
2869
|
-
const semaphore = pLimit__default[
|
|
2885
|
+
const semaphore = pLimit__default["default"](30);
|
|
2870
2886
|
const promises = items.map((repo) => semaphore(async () => {
|
|
2871
2887
|
try {
|
|
2872
2888
|
return {
|
|
@@ -2928,8 +2944,8 @@ async function updateRepos(reporter, foundRepos) {
|
|
|
2928
2944
|
}
|
|
2929
2945
|
}
|
|
2930
2946
|
function guessDefinitionRepoName(rootdir, cals) {
|
|
2931
|
-
const p = path__default[
|
|
2932
|
-
const relativePath = path__default[
|
|
2947
|
+
const p = path__default["default"].resolve(rootdir, cals.resourcesDefinition.path);
|
|
2948
|
+
const relativePath = path__default["default"].relative(rootdir, p);
|
|
2933
2949
|
if (relativePath.slice(0, 1) == ".") {
|
|
2934
2950
|
return null;
|
|
2935
2951
|
}
|
|
@@ -2942,8 +2958,8 @@ function guessDefinitionRepoName(rootdir, cals) {
|
|
|
2942
2958
|
return parts[1];
|
|
2943
2959
|
}
|
|
2944
2960
|
async function getDefinition(rootdir, cals) {
|
|
2945
|
-
const p = path__default[
|
|
2946
|
-
if (!fs__default[
|
|
2961
|
+
const p = path__default["default"].resolve(rootdir, cals.resourcesDefinition.path);
|
|
2962
|
+
if (!fs__default["default"].existsSync(p)) {
|
|
2947
2963
|
throw Error(`The file ${p} does not exist`);
|
|
2948
2964
|
}
|
|
2949
2965
|
return new DefinitionFile(p).getDefinition();
|
|
@@ -2952,9 +2968,9 @@ async function getDefinition(rootdir, cals) {
|
|
|
2952
2968
|
* Get directory names within a directory.
|
|
2953
2969
|
*/
|
|
2954
2970
|
function getDirNames(parent) {
|
|
2955
|
-
return (fs__default[
|
|
2971
|
+
return (fs__default["default"]
|
|
2956
2972
|
.readdirSync(parent)
|
|
2957
|
-
.filter((it) => fs__default[
|
|
2973
|
+
.filter((it) => fs__default["default"].statSync(path__default["default"].join(parent, it)).isDirectory())
|
|
2958
2974
|
// Skip hidden folders
|
|
2959
2975
|
.filter((it) => !it.startsWith("."))
|
|
2960
2976
|
.sort((a, b) => a.localeCompare(b)));
|
|
@@ -2966,7 +2982,7 @@ async function getReposInOrg(cals, rootdir) {
|
|
|
2966
2982
|
.filter((it) => cals.resourcesDefinition.tags === undefined ||
|
|
2967
2983
|
(it.project.tags || []).some((tag) => { var _a; return (_a = cals.resourcesDefinition.tags) === null || _a === void 0 ? void 0 : _a.includes(tag); }) ||
|
|
2968
2984
|
// Always include if already checked out to avoid stale state.
|
|
2969
|
-
fs__default[
|
|
2985
|
+
fs__default["default"].existsSync(path__default["default"].join(rootdir, it.project.name, it.repo.name)));
|
|
2970
2986
|
}
|
|
2971
2987
|
function getExpectedRepo(item) {
|
|
2972
2988
|
return {
|
|
@@ -2979,8 +2995,8 @@ function getExpectedRepo(item) {
|
|
|
2979
2995
|
};
|
|
2980
2996
|
}
|
|
2981
2997
|
function getGitRepo(rootdir, relpath) {
|
|
2982
|
-
return new GitRepo(path__default[
|
|
2983
|
-
await appendFile(path__default[
|
|
2998
|
+
return new GitRepo(path__default["default"].resolve(rootdir, relpath), async (result) => {
|
|
2999
|
+
await appendFile(path__default["default"].resolve(rootdir, CALS_LOG), JSON.stringify({
|
|
2984
3000
|
time: new Date().toISOString(),
|
|
2985
3001
|
context: relpath,
|
|
2986
3002
|
type: "exec-result",
|
|
@@ -3034,7 +3050,7 @@ async function getExpectedRepos(reporter, github, cals, rootdir) {
|
|
|
3034
3050
|
}
|
|
3035
3051
|
async function getInput(prompt) {
|
|
3036
3052
|
return new Promise((resolve, reject) => {
|
|
3037
|
-
read__default[
|
|
3053
|
+
read__default["default"]({
|
|
3038
3054
|
prompt,
|
|
3039
3055
|
timeout: 60000,
|
|
3040
3056
|
}, (err, answer) => {
|
|
@@ -3071,15 +3087,15 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3071
3087
|
const foundRepos = [];
|
|
3072
3088
|
// Categorize all dirs.
|
|
3073
3089
|
for (const topdir of getDirNames(rootdir)) {
|
|
3074
|
-
const isGitDir = fs__default[
|
|
3090
|
+
const isGitDir = fs__default["default"].existsSync(path__default["default"].join(rootdir, topdir, ".git"));
|
|
3075
3091
|
if (isGitDir) {
|
|
3076
3092
|
// Do not traverse deeper inside another Git repo, as that might
|
|
3077
3093
|
// mean we do not have the proper grouped structure.
|
|
3078
3094
|
unknownDirs.push(topdir);
|
|
3079
3095
|
continue;
|
|
3080
3096
|
}
|
|
3081
|
-
for (const subdir of getDirNames(path__default[
|
|
3082
|
-
const p = path__default[
|
|
3097
|
+
for (const subdir of getDirNames(path__default["default"].join(rootdir, topdir))) {
|
|
3098
|
+
const p = path__default["default"].join(topdir, subdir);
|
|
3083
3099
|
const expectedRepo = expectedRepos.find((it) => getRelpath(it) === p ||
|
|
3084
3100
|
it.aliases.some((alias) => getRelpath(alias) === p));
|
|
3085
3101
|
if (expectedRepo === undefined) {
|
|
@@ -3107,9 +3123,9 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3107
3123
|
for (const it of archivedRepos) {
|
|
3108
3124
|
reporter.info(` ${it.actualRelpath}`);
|
|
3109
3125
|
}
|
|
3110
|
-
const thisDirName = path__default[
|
|
3126
|
+
const thisDirName = path__default["default"].basename(process.cwd());
|
|
3111
3127
|
const archiveDir = `../${thisDirName}-archive`;
|
|
3112
|
-
const hasArchiveDir = fs__default[
|
|
3128
|
+
const hasArchiveDir = fs__default["default"].existsSync(archiveDir);
|
|
3113
3129
|
if (hasArchiveDir) {
|
|
3114
3130
|
reporter.info("To move these:");
|
|
3115
3131
|
for (const it of archivedRepos) {
|
|
@@ -3132,17 +3148,17 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3132
3148
|
const shouldMove = await askMoveConfirm();
|
|
3133
3149
|
if (shouldMove) {
|
|
3134
3150
|
for (const it of movedRepos) {
|
|
3135
|
-
const src = path__default[
|
|
3136
|
-
const dest = path__default[
|
|
3137
|
-
const destParent = path__default[
|
|
3138
|
-
if (fs__default[
|
|
3151
|
+
const src = path__default["default"].join(rootdir, it.actualRelpath);
|
|
3152
|
+
const dest = path__default["default"].join(rootdir, getRelpath(it));
|
|
3153
|
+
const destParent = path__default["default"].join(rootdir, it.group);
|
|
3154
|
+
if (fs__default["default"].existsSync(dest)) {
|
|
3139
3155
|
throw new Error(`Target directory already exists: ${dest} - cannot move ${it.actualRelpath}`);
|
|
3140
3156
|
}
|
|
3141
3157
|
reporter.info(`Moving ${it.actualRelpath} -> ${getRelpath(it)}`);
|
|
3142
|
-
if (!fs__default[
|
|
3143
|
-
await fs__default[
|
|
3158
|
+
if (!fs__default["default"].existsSync(destParent)) {
|
|
3159
|
+
await fs__default["default"].promises.mkdir(destParent, { recursive: true });
|
|
3144
3160
|
}
|
|
3145
|
-
await fs__default[
|
|
3161
|
+
await fs__default["default"].promises.rename(src, dest);
|
|
3146
3162
|
}
|
|
3147
3163
|
// We would have to update expectedRepos if we want to continue.
|
|
3148
3164
|
// Let's try keeping this simple.
|
|
@@ -3186,7 +3202,7 @@ async function sync$1({ reporter, github, cals, rootdir, askClone, askMove, }) {
|
|
|
3186
3202
|
}
|
|
3187
3203
|
}
|
|
3188
3204
|
async function loadCalsManifest(config, reporter) {
|
|
3189
|
-
const p = await findUp__default[
|
|
3205
|
+
const p = await findUp__default["default"](CALS_YAML, { cwd: config.cwd });
|
|
3190
3206
|
if (p === undefined) {
|
|
3191
3207
|
reporter.error(`File ${CALS_YAML} not found. See help`);
|
|
3192
3208
|
process.exitCode = 1;
|
|
@@ -3195,12 +3211,12 @@ async function loadCalsManifest(config, reporter) {
|
|
|
3195
3211
|
// TODO: Verify file has expected contents.
|
|
3196
3212
|
// (Can we easily generate schema for type and verify?)
|
|
3197
3213
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-explicit-any
|
|
3198
|
-
const cals = yaml__default[
|
|
3214
|
+
const cals = yaml__default["default"].load(fs__default["default"].readFileSync(p, "utf-8"));
|
|
3199
3215
|
if (cals.version !== 2) {
|
|
3200
3216
|
throw new Error(`Unexpected version in ${p}`);
|
|
3201
3217
|
}
|
|
3202
3218
|
return {
|
|
3203
|
-
dir: path__default[
|
|
3219
|
+
dir: path__default["default"].dirname(p),
|
|
3204
3220
|
cals,
|
|
3205
3221
|
};
|
|
3206
3222
|
}
|
|
@@ -3305,20 +3321,23 @@ Notes:
|
|
|
3305
3321
|
option to avoid stale cache. The cache can also be cleared with
|
|
3306
3322
|
the "cals delete-cache" command.`),
|
|
3307
3323
|
handler: () => {
|
|
3308
|
-
yargs__default[
|
|
3324
|
+
yargs__default["default"].showHelp();
|
|
3309
3325
|
},
|
|
3310
3326
|
};
|
|
3311
3327
|
|
|
3312
3328
|
function totalSeverityCount(project) {
|
|
3313
|
-
|
|
3329
|
+
var _a;
|
|
3330
|
+
return (((_a = project.issueCountsBySeverity.critical) !== null && _a !== void 0 ? _a : 0) +
|
|
3331
|
+
project.issueCountsBySeverity.high +
|
|
3314
3332
|
project.issueCountsBySeverity.medium +
|
|
3315
3333
|
project.issueCountsBySeverity.low);
|
|
3316
3334
|
}
|
|
3317
3335
|
function buildStatsLine(stats) {
|
|
3336
|
+
var _a;
|
|
3318
3337
|
function item(num, str) {
|
|
3319
3338
|
return num === 0 ? lodash.repeat(" ", str.length + 4) : sprintfJs.sprintf("%3d %s", num, str);
|
|
3320
3339
|
}
|
|
3321
|
-
return sprintfJs.sprintf("%s %s %s", item(stats.high, "high"), item(stats.medium, "medium"), item(stats.low, "low"));
|
|
3340
|
+
return sprintfJs.sprintf("%s %s %s %s", item((_a = stats.critical) !== null && _a !== void 0 ? _a : 0, "critical"), item(stats.high, "high"), item(stats.medium, "medium"), item(stats.low, "low"));
|
|
3322
3341
|
}
|
|
3323
3342
|
async function report({ reporter, snyk, definitionFile, }) {
|
|
3324
3343
|
const definition = await definitionFile.getDefinition();
|
|
@@ -3342,6 +3361,7 @@ async function report({ reporter, snyk, definitionFile, }) {
|
|
|
3342
3361
|
}
|
|
3343
3362
|
else {
|
|
3344
3363
|
reporter.info(sprintfJs.sprintf("%-70s %s", "Total count", buildStatsLine({
|
|
3364
|
+
critical: lodash.sumBy(reposWithIssues, (it) => { var _a; return (_a = it.issueCountsBySeverity.critical) !== null && _a !== void 0 ? _a : 0; }),
|
|
3345
3365
|
high: lodash.sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.high),
|
|
3346
3366
|
medium: lodash.sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.medium),
|
|
3347
3367
|
low: lodash.sumBy(reposWithIssues, (it) => it.issueCountsBySeverity.low),
|
|
@@ -3350,6 +3370,7 @@ async function report({ reporter, snyk, definitionFile, }) {
|
|
|
3350
3370
|
byProjects.forEach((repos) => {
|
|
3351
3371
|
const project = repos[0].project;
|
|
3352
3372
|
const totalCount = {
|
|
3373
|
+
critical: lodash.sumBy(repos, (it) => { var _a; return (_a = it.repo.issueCountsBySeverity.critical) !== null && _a !== void 0 ? _a : 0; }),
|
|
3353
3374
|
high: lodash.sumBy(repos, (it) => it.repo.issueCountsBySeverity.high),
|
|
3354
3375
|
medium: lodash.sumBy(repos, (it) => it.repo.issueCountsBySeverity.medium),
|
|
3355
3376
|
low: lodash.sumBy(repos, (it) => it.repo.issueCountsBySeverity.low),
|
|
@@ -3378,7 +3399,7 @@ async function setToken({ reporter, token, tokenProvider, }) {
|
|
|
3378
3399
|
reporter.info("Need API token to talk to Snyk");
|
|
3379
3400
|
reporter.info("See https://app.snyk.io/account");
|
|
3380
3401
|
token = await new Promise((resolve, reject) => {
|
|
3381
|
-
read__default[
|
|
3402
|
+
read__default["default"]({
|
|
3382
3403
|
prompt: "Enter new Snyk API token: ",
|
|
3383
3404
|
silent: true,
|
|
3384
3405
|
}, (err, answer) => {
|
|
@@ -3449,12 +3470,12 @@ Notes:
|
|
|
3449
3470
|
and provide a link to generate one:
|
|
3450
3471
|
$ cals snyk set-token`),
|
|
3451
3472
|
handler: () => {
|
|
3452
|
-
yargs__default[
|
|
3473
|
+
yargs__default["default"].showHelp();
|
|
3453
3474
|
},
|
|
3454
3475
|
};
|
|
3455
3476
|
|
|
3456
3477
|
async function main() {
|
|
3457
|
-
if (!semver__default[
|
|
3478
|
+
if (!semver__default["default"].satisfies(process.version, engines.node)) {
|
|
3458
3479
|
console.error(`Required node version ${engines.node} not satisfied with current version ${process.version}.`);
|
|
3459
3480
|
process.exit(1);
|
|
3460
3481
|
}
|
|
@@ -3466,12 +3487,12 @@ async function main() {
|
|
|
3466
3487
|
/ /___/ ___ |/ /______/ /
|
|
3467
3488
|
\\____/_/ |_/_____/____/
|
|
3468
3489
|
cli ${version}
|
|
3469
|
-
built ${"
|
|
3490
|
+
built ${"2022-02-07T10:11:46+0000"}
|
|
3470
3491
|
|
|
3471
3492
|
https://github.com/capralifecycle/cals-cli/
|
|
3472
3493
|
|
|
3473
3494
|
Usage: cals <command>`;
|
|
3474
|
-
await yargs__default[
|
|
3495
|
+
await yargs__default["default"]
|
|
3475
3496
|
.usage(header)
|
|
3476
3497
|
.scriptName("cals")
|
|
3477
3498
|
.locale("en")
|