@capraconsulting/cals-cli 2.25.21 → 2.25.23

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.
Files changed (63) hide show
  1. package/lib/cache.d.ts +27 -27
  2. package/lib/cals-cli.d.ts +1 -1
  3. package/lib/cals-cli.js +2748 -2748
  4. package/lib/cli/commands/definition/dump-setup.d.ts +3 -3
  5. package/lib/cli/commands/definition/util.d.ts +6 -6
  6. package/lib/cli/commands/definition/util.test.d.ts +1 -1
  7. package/lib/cli/commands/definition/validate.d.ts +3 -3
  8. package/lib/cli/commands/definition.d.ts +3 -3
  9. package/lib/cli/commands/delete-cache.d.ts +3 -3
  10. package/lib/cli/commands/getting-started.d.ts +3 -3
  11. package/lib/cli/commands/github/analyze-directory.d.ts +3 -3
  12. package/lib/cli/commands/github/configure.d.ts +3 -3
  13. package/lib/cli/commands/github/generate-clone-commands.d.ts +3 -3
  14. package/lib/cli/commands/github/list-pull-requests-stats.d.ts +3 -3
  15. package/lib/cli/commands/github/list-repos.d.ts +3 -3
  16. package/lib/cli/commands/github/list-webhooks.d.ts +3 -3
  17. package/lib/cli/commands/github/set-token.d.ts +3 -3
  18. package/lib/cli/commands/github/sync.d.ts +9 -9
  19. package/lib/cli/commands/github/util.d.ts +3 -3
  20. package/lib/cli/commands/github.d.ts +3 -3
  21. package/lib/cli/commands/snyk/report.d.ts +3 -3
  22. package/lib/cli/commands/snyk/set-token.d.ts +3 -3
  23. package/lib/cli/commands/snyk/sync.d.ts +3 -3
  24. package/lib/cli/commands/snyk.d.ts +3 -3
  25. package/lib/cli/index.d.ts +1 -1
  26. package/lib/cli/index.test.d.ts +1 -1
  27. package/lib/cli/reporter.d.ts +27 -27
  28. package/lib/cli/util.d.ts +11 -11
  29. package/lib/config.d.ts +14 -14
  30. package/lib/definition/definition.d.ts +13 -13
  31. package/lib/definition/definition.test.d.ts +1 -1
  32. package/lib/definition/index.d.ts +2 -2
  33. package/lib/definition/types.d.ts +78 -78
  34. package/lib/git/GitRepo.d.ts +31 -31
  35. package/lib/git/util.d.ts +16 -16
  36. package/lib/git/util.test.d.ts +1 -1
  37. package/lib/github/changeset/changeset.d.ts +21 -21
  38. package/lib/github/changeset/execute.d.ts +10 -10
  39. package/lib/github/changeset/types.d.ts +93 -93
  40. package/lib/github/index.d.ts +2 -2
  41. package/lib/github/service.d.ts +91 -91
  42. package/lib/github/token.d.ts +11 -11
  43. package/lib/github/types.d.ts +85 -85
  44. package/lib/github/util.d.ts +8 -8
  45. package/lib/index.d.ts +14 -14
  46. package/lib/index.es.js +1519 -1519
  47. package/lib/index.js +1519 -1519
  48. package/lib/load-secrets/index.d.ts +2 -2
  49. package/lib/load-secrets/load-secrets.d.ts +7 -7
  50. package/lib/load-secrets/types.d.ts +22 -22
  51. package/lib/snyk/index.d.ts +3 -3
  52. package/lib/snyk/service.d.ts +21 -21
  53. package/lib/snyk/token.d.ts +11 -11
  54. package/lib/snyk/types.d.ts +27 -27
  55. package/lib/snyk/util.d.ts +3 -3
  56. package/lib/snyk/util.test.d.ts +1 -1
  57. package/lib/sonarcloud/index.d.ts +2 -2
  58. package/lib/sonarcloud/service.d.ts +33 -33
  59. package/lib/sonarcloud/token.d.ts +8 -8
  60. package/lib/testing/executor.d.ts +25 -25
  61. package/lib/testing/index.d.ts +2 -2
  62. package/lib/testing/lib.d.ts +64 -64
  63. package/package.json +6 -6
package/lib/index.js CHANGED
@@ -44,148 +44,148 @@ var keytar__default = /*#__PURE__*/_interopDefaultLegacy(keytar);
44
44
  var read__default = /*#__PURE__*/_interopDefaultLegacy(read);
45
45
  var execa__default = /*#__PURE__*/_interopDefaultLegacy(execa);
46
46
 
47
- var version = "2.25.21";
47
+ var version = "2.25.23";
48
48
 
49
- class CacheProvider {
50
- constructor(config) {
51
- this.mustValidate = false;
52
- this.defaultCacheTime = 1800;
53
- this.config = config;
54
- }
55
- /**
56
- * Retrieve cache if existent, ignoring the time.
57
- *
58
- * The caller is responsible for handling proper validation,
59
- */
60
- retrieveJson(cachekey) {
61
- const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
62
- if (!fs__default["default"].existsSync(cachefile)) {
63
- return undefined;
64
- }
65
- const data = fs__default["default"].readFileSync(cachefile, "utf-8");
66
- return {
67
- cacheTime: fs__default["default"].statSync(cachefile).mtime.getTime(),
68
- data: (data === "undefined" ? undefined : JSON.parse(data)),
69
- };
70
- }
71
- /**
72
- * Save data to cache.
73
- */
74
- storeJson(cachekey, data) {
75
- const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
76
- if (!fs__default["default"].existsSync(this.config.cacheDir)) {
77
- fs__default["default"].mkdirSync(this.config.cacheDir, { recursive: true });
78
- }
79
- fs__default["default"].writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
80
- }
81
- async json(cachekey, block, cachetime = this.defaultCacheTime) {
82
- const cacheItem = this.mustValidate
83
- ? undefined
84
- : this.retrieveJson(cachekey);
85
- const expire = new Date(new Date().getTime() - cachetime * 1000).getTime();
86
- if (cacheItem !== undefined && cacheItem.cacheTime > expire) {
87
- return cacheItem.data;
88
- }
89
- const result = await block();
90
- this.storeJson(cachekey, result);
91
- return result;
92
- }
93
- /**
94
- * Delete all cached data.
95
- */
96
- cleanup() {
97
- rimraf__default["default"].sync(this.config.cacheDir);
98
- }
49
+ class CacheProvider {
50
+ constructor(config) {
51
+ this.mustValidate = false;
52
+ this.defaultCacheTime = 1800;
53
+ this.config = config;
54
+ }
55
+ /**
56
+ * Retrieve cache if existent, ignoring the time.
57
+ *
58
+ * The caller is responsible for handling proper validation,
59
+ */
60
+ retrieveJson(cachekey) {
61
+ const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
62
+ if (!fs__default["default"].existsSync(cachefile)) {
63
+ return undefined;
64
+ }
65
+ const data = fs__default["default"].readFileSync(cachefile, "utf-8");
66
+ return {
67
+ cacheTime: fs__default["default"].statSync(cachefile).mtime.getTime(),
68
+ data: (data === "undefined" ? undefined : JSON.parse(data)),
69
+ };
70
+ }
71
+ /**
72
+ * Save data to cache.
73
+ */
74
+ storeJson(cachekey, data) {
75
+ const cachefile = path__default["default"].join(this.config.cacheDir, `${cachekey}.json`);
76
+ if (!fs__default["default"].existsSync(this.config.cacheDir)) {
77
+ fs__default["default"].mkdirSync(this.config.cacheDir, { recursive: true });
78
+ }
79
+ fs__default["default"].writeFileSync(cachefile, data === undefined ? "undefined" : JSON.stringify(data));
80
+ }
81
+ async json(cachekey, block, cachetime = this.defaultCacheTime) {
82
+ const cacheItem = this.mustValidate
83
+ ? undefined
84
+ : this.retrieveJson(cachekey);
85
+ const expire = new Date(new Date().getTime() - cachetime * 1000).getTime();
86
+ if (cacheItem !== undefined && cacheItem.cacheTime > expire) {
87
+ return cacheItem.data;
88
+ }
89
+ const result = await block();
90
+ this.storeJson(cachekey, result);
91
+ return result;
92
+ }
93
+ /**
94
+ * Delete all cached data.
95
+ */
96
+ cleanup() {
97
+ rimraf__default["default"].sync(this.config.cacheDir);
98
+ }
99
99
  }
100
100
 
101
- const CLEAR_WHOLE_LINE = 0;
102
- function clearLine(stdout) {
103
- readline__default["default"].clearLine(stdout, CLEAR_WHOLE_LINE);
104
- readline__default["default"].cursorTo(stdout, 0);
105
- }
106
- class Reporter {
107
- constructor(opts = {}) {
108
- this.stdout = process.stdout;
109
- this.stderr = process.stderr;
110
- this.stdin = process.stdin;
111
- this.isTTY = this.stdout.isTTY;
112
- this.format = chalk__default["default"];
113
- this.startTime = Date.now();
114
- this.nonInteractive = !!opts.nonInteractive;
115
- this.isVerbose = !!opts.verbose;
116
- }
117
- error(msg) {
118
- clearLine(this.stderr);
119
- this.stderr.write(`${this.format.red("error")} ${msg}\n`);
120
- }
121
- log(msg) {
122
- clearLine(this.stdout);
123
- this.stdout.write(`${msg}\n`);
124
- }
125
- warn(msg) {
126
- clearLine(this.stderr);
127
- this.stderr.write(`${this.format.yellow("warning")} ${msg}\n`);
128
- }
129
- success(msg) {
130
- clearLine(this.stdout);
131
- this.stdout.write(`${this.format.green("success")} ${msg}\n`);
132
- }
133
- info(msg) {
134
- clearLine(this.stdout);
135
- this.stdout.write(`${this.format.blue("info")} ${msg}\n`);
136
- }
101
+ const CLEAR_WHOLE_LINE = 0;
102
+ function clearLine(stdout) {
103
+ readline__default["default"].clearLine(stdout, CLEAR_WHOLE_LINE);
104
+ readline__default["default"].cursorTo(stdout, 0);
105
+ }
106
+ class Reporter {
107
+ constructor(opts = {}) {
108
+ this.stdout = process.stdout;
109
+ this.stderr = process.stderr;
110
+ this.stdin = process.stdin;
111
+ this.isTTY = this.stdout.isTTY;
112
+ this.format = chalk__default["default"];
113
+ this.startTime = Date.now();
114
+ this.nonInteractive = !!opts.nonInteractive;
115
+ this.isVerbose = !!opts.verbose;
116
+ }
117
+ error(msg) {
118
+ clearLine(this.stderr);
119
+ this.stderr.write(`${this.format.red("error")} ${msg}\n`);
120
+ }
121
+ log(msg) {
122
+ clearLine(this.stdout);
123
+ this.stdout.write(`${msg}\n`);
124
+ }
125
+ warn(msg) {
126
+ clearLine(this.stderr);
127
+ this.stderr.write(`${this.format.yellow("warning")} ${msg}\n`);
128
+ }
129
+ success(msg) {
130
+ clearLine(this.stdout);
131
+ this.stdout.write(`${this.format.green("success")} ${msg}\n`);
132
+ }
133
+ info(msg) {
134
+ clearLine(this.stdout);
135
+ this.stdout.write(`${this.format.blue("info")} ${msg}\n`);
136
+ }
137
137
  }
138
138
 
139
- class Config {
140
- constructor() {
141
- this.cwd = path__default["default"].resolve(process.cwd());
142
- this.configFile = path__default["default"].join(os__default["default"].homedir(), ".cals-config.json");
143
- this.cacheDir = cachedir__default["default"]("cals-cli");
144
- this.agent = new https__default["default"].Agent({
145
- keepAlive: true,
146
- });
147
- this.configCached = undefined;
148
- }
149
- get config() {
150
- const existingConfig = this.configCached;
151
- if (existingConfig !== undefined) {
152
- return existingConfig;
153
- }
154
- const config = this.readConfig();
155
- this.configCached = config;
156
- return config;
157
- }
158
- readConfig() {
159
- if (!fs__default["default"].existsSync(this.configFile)) {
160
- return {};
161
- }
162
- try {
163
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
164
- return JSON.parse(fs__default["default"].readFileSync(this.configFile, "utf-8"));
165
- }
166
- catch (e) {
167
- console.error("Failed", e);
168
- throw new Error("Failed to read config");
169
- }
170
- }
171
- getConfig(key) {
172
- return this.config[key];
173
- }
174
- requireConfig(key) {
175
- const result = this.config[key];
176
- if (result === undefined) {
177
- throw Error(`Configuration for ${key} missing. Add manually to ${this.configFile}`);
178
- }
179
- return result;
180
- }
181
- updateConfig(key, value) {
182
- const updatedConfig = {
183
- ...this.readConfig(),
184
- [key]: value, // undefined will remove
185
- };
186
- fs__default["default"].writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
187
- this.configCached = updatedConfig;
188
- }
139
+ class Config {
140
+ constructor() {
141
+ this.cwd = path__default["default"].resolve(process.cwd());
142
+ this.configFile = path__default["default"].join(os__default["default"].homedir(), ".cals-config.json");
143
+ this.cacheDir = cachedir__default["default"]("cals-cli");
144
+ this.agent = new https__default["default"].Agent({
145
+ keepAlive: true,
146
+ });
147
+ this.configCached = undefined;
148
+ }
149
+ get config() {
150
+ const existingConfig = this.configCached;
151
+ if (existingConfig !== undefined) {
152
+ return existingConfig;
153
+ }
154
+ const config = this.readConfig();
155
+ this.configCached = config;
156
+ return config;
157
+ }
158
+ readConfig() {
159
+ if (!fs__default["default"].existsSync(this.configFile)) {
160
+ return {};
161
+ }
162
+ try {
163
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
164
+ return JSON.parse(fs__default["default"].readFileSync(this.configFile, "utf-8"));
165
+ }
166
+ catch (e) {
167
+ console.error("Failed", e);
168
+ throw new Error("Failed to read config");
169
+ }
170
+ }
171
+ getConfig(key) {
172
+ return this.config[key];
173
+ }
174
+ requireConfig(key) {
175
+ const result = this.config[key];
176
+ if (result === undefined) {
177
+ throw Error(`Configuration for ${key} missing. Add manually to ${this.configFile}`);
178
+ }
179
+ return result;
180
+ }
181
+ updateConfig(key, value) {
182
+ const updatedConfig = {
183
+ ...this.readConfig(),
184
+ [key]: value, // undefined will remove
185
+ };
186
+ fs__default["default"].writeFileSync(this.configFile, JSON.stringify(updatedConfig, null, " "));
187
+ this.configCached = updatedConfig;
188
+ }
189
189
  }
190
190
 
191
191
  var type = "object";
@@ -488,282 +488,282 @@ var schema = {
488
488
  $schema: $schema
489
489
  };
490
490
 
491
- function getTeamId(org, teamName) {
492
- return `${org}/${teamName}`;
493
- }
494
- function getRepoId(orgName, repoName) {
495
- return `${orgName}/${repoName}`;
496
- }
497
- function checkAgainstSchema(value) {
498
- var _a;
499
- const ajv = new AJV__default["default"]({ allErrors: true });
500
- const valid = ajv.validate(schema, value);
501
- return valid
502
- ? { definition: value }
503
- : { error: (_a = ajv.errorsText()) !== null && _a !== void 0 ? _a : "Unknown error" };
504
- }
505
- function requireValidDefinition(definition) {
506
- // Verify no duplicates in users and extract known logins.
507
- const loginList = definition.github.users.reduce((acc, user) => {
508
- if (acc.includes(user.login)) {
509
- throw new Error(`Duplicate login: ${user.login}`);
510
- }
511
- return [...acc, user.login];
512
- }, []);
513
- // Verify no duplicates in teams and extract team names.
514
- const teamIdList = definition.github.teams.reduce((acc, orgTeams) => {
515
- return orgTeams.teams.reduce((acc1, team) => {
516
- const id = getTeamId(orgTeams.organization, team.name);
517
- if (acc1.includes(id)) {
518
- throw new Error(`Duplicate team: ${id}`);
519
- }
520
- return [...acc1, id];
521
- }, acc);
522
- }, []);
523
- // Verify team members exists as users.
524
- definition.github.teams
525
- .map((it) => it.teams)
526
- .flat()
527
- .forEach((team) => {
528
- team.members.forEach((login) => {
529
- if (!loginList.includes(login)) {
530
- throw new Error(`Team member ${login} in team ${team.name} is not registered in user list`);
531
- }
532
- });
533
- });
534
- // Verify no duplicates in project names.
535
- definition.projects.reduce((acc, project) => {
536
- if (acc.includes(project.name)) {
537
- throw new Error(`Duplicate project: ${project.name}`);
538
- }
539
- return [...acc, project.name];
540
- }, []);
541
- definition.projects.forEach((project) => {
542
- project.github.forEach((org) => {
543
- (org.teams || []).forEach((team) => {
544
- const id = getTeamId(org.organization, team.name);
545
- if (!teamIdList.includes(id)) {
546
- throw new Error(`Project team ${id} in project ${project.name} is not registered in team list`);
547
- }
548
- });
549
- (org.repos || []).forEach((repo) => {
550
- (repo.teams || []).forEach((team) => {
551
- const id = getTeamId(org.organization, team.name);
552
- if (!teamIdList.includes(id)) {
553
- throw new Error(`Repo team ${id} for repo ${repo.name} in project ${project.name} is not registered in team list`);
554
- }
555
- });
556
- });
557
- });
558
- });
559
- // Verify no duplicates in repos.
560
- definition.projects
561
- .flatMap((project) => project.github
562
- .map((org) => (org.repos || []).map((repo) => getRepoId(org.organization, repo.name)))
563
- .flat())
564
- .reduce((acc, repoName) => {
565
- if (acc.includes(repoName)) {
566
- throw new Error(`Duplicate repo: ${repoName}`);
567
- }
568
- return [...acc, repoName];
569
- }, []);
570
- }
571
- class DefinitionFile {
572
- constructor(path) {
573
- this.path = path;
574
- }
575
- async getContents() {
576
- return new Promise((resolve, reject) => fs__default["default"].readFile(this.path, "utf-8", (err, data) => {
577
- if (err)
578
- reject(err);
579
- else
580
- resolve(data);
581
- }));
582
- }
583
- async getDefinition() {
584
- return parseDefinition(await this.getContents());
585
- }
586
- }
587
- function parseDefinition(value) {
588
- const result = checkAgainstSchema(yaml__default["default"].load(value));
589
- if ("error" in result) {
590
- throw new Error("Definition content invalid: " + result.error);
591
- }
592
- requireValidDefinition(result.definition);
593
- return result.definition;
594
- }
595
- function getRepos(definition) {
596
- return definition.projects.flatMap((project) => project.github
597
- .map((org) => (org.repos || []).map((repo) => ({
598
- id: getRepoId(org.organization, repo.name),
599
- orgName: org.organization,
600
- project,
601
- repo,
602
- })))
603
- .flat());
604
- }
605
- function getGitHubOrgs(definition) {
606
- return lodash.uniq(definition.projects.flatMap((project) => project.github.map((it) => it.organization)));
491
+ function getTeamId(org, teamName) {
492
+ return `${org}/${teamName}`;
493
+ }
494
+ function getRepoId(orgName, repoName) {
495
+ return `${orgName}/${repoName}`;
496
+ }
497
+ function checkAgainstSchema(value) {
498
+ var _a;
499
+ const ajv = new AJV__default["default"]({ allErrors: true });
500
+ const valid = ajv.validate(schema, value);
501
+ return valid
502
+ ? { definition: value }
503
+ : { error: (_a = ajv.errorsText()) !== null && _a !== void 0 ? _a : "Unknown error" };
504
+ }
505
+ function requireValidDefinition(definition) {
506
+ // Verify no duplicates in users and extract known logins.
507
+ const loginList = definition.github.users.reduce((acc, user) => {
508
+ if (acc.includes(user.login)) {
509
+ throw new Error(`Duplicate login: ${user.login}`);
510
+ }
511
+ return [...acc, user.login];
512
+ }, []);
513
+ // Verify no duplicates in teams and extract team names.
514
+ const teamIdList = definition.github.teams.reduce((acc, orgTeams) => {
515
+ return orgTeams.teams.reduce((acc1, team) => {
516
+ const id = getTeamId(orgTeams.organization, team.name);
517
+ if (acc1.includes(id)) {
518
+ throw new Error(`Duplicate team: ${id}`);
519
+ }
520
+ return [...acc1, id];
521
+ }, acc);
522
+ }, []);
523
+ // Verify team members exists as users.
524
+ definition.github.teams
525
+ .map((it) => it.teams)
526
+ .flat()
527
+ .forEach((team) => {
528
+ team.members.forEach((login) => {
529
+ if (!loginList.includes(login)) {
530
+ throw new Error(`Team member ${login} in team ${team.name} is not registered in user list`);
531
+ }
532
+ });
533
+ });
534
+ // Verify no duplicates in project names.
535
+ definition.projects.reduce((acc, project) => {
536
+ if (acc.includes(project.name)) {
537
+ throw new Error(`Duplicate project: ${project.name}`);
538
+ }
539
+ return [...acc, project.name];
540
+ }, []);
541
+ definition.projects.forEach((project) => {
542
+ project.github.forEach((org) => {
543
+ (org.teams || []).forEach((team) => {
544
+ const id = getTeamId(org.organization, team.name);
545
+ if (!teamIdList.includes(id)) {
546
+ throw new Error(`Project team ${id} in project ${project.name} is not registered in team list`);
547
+ }
548
+ });
549
+ (org.repos || []).forEach((repo) => {
550
+ (repo.teams || []).forEach((team) => {
551
+ const id = getTeamId(org.organization, team.name);
552
+ if (!teamIdList.includes(id)) {
553
+ throw new Error(`Repo team ${id} for repo ${repo.name} in project ${project.name} is not registered in team list`);
554
+ }
555
+ });
556
+ });
557
+ });
558
+ });
559
+ // Verify no duplicates in repos.
560
+ definition.projects
561
+ .flatMap((project) => project.github
562
+ .map((org) => (org.repos || []).map((repo) => getRepoId(org.organization, repo.name)))
563
+ .flat())
564
+ .reduce((acc, repoName) => {
565
+ if (acc.includes(repoName)) {
566
+ throw new Error(`Duplicate repo: ${repoName}`);
567
+ }
568
+ return [...acc, repoName];
569
+ }, []);
570
+ }
571
+ class DefinitionFile {
572
+ constructor(path) {
573
+ this.path = path;
574
+ }
575
+ async getContents() {
576
+ return new Promise((resolve, reject) => fs__default["default"].readFile(this.path, "utf-8", (err, data) => {
577
+ if (err)
578
+ reject(err);
579
+ else
580
+ resolve(data);
581
+ }));
582
+ }
583
+ async getDefinition() {
584
+ return parseDefinition(await this.getContents());
585
+ }
586
+ }
587
+ function parseDefinition(value) {
588
+ const result = checkAgainstSchema(yaml__default["default"].load(value));
589
+ if ("error" in result) {
590
+ throw new Error("Definition content invalid: " + result.error);
591
+ }
592
+ requireValidDefinition(result.definition);
593
+ return result.definition;
594
+ }
595
+ function getRepos(definition) {
596
+ return definition.projects.flatMap((project) => project.github
597
+ .map((org) => (org.repos || []).map((repo) => ({
598
+ id: getRepoId(org.organization, repo.name),
599
+ orgName: org.organization,
600
+ project,
601
+ repo,
602
+ })))
603
+ .flat());
604
+ }
605
+ function getGitHubOrgs(definition) {
606
+ return lodash.uniq(definition.projects.flatMap((project) => project.github.map((it) => it.organization)));
607
607
  }
608
608
 
609
- function createReporter(argv) {
610
- return new Reporter({
611
- verbose: !!argv.verbose,
612
- nonInteractive: !!argv.nonInteractive,
613
- });
609
+ function createReporter(argv) {
610
+ return new Reporter({
611
+ verbose: !!argv.verbose,
612
+ nonInteractive: !!argv.nonInteractive,
613
+ });
614
614
  }
615
615
 
616
- class GitHubTokenCliProvider {
617
- constructor() {
618
- this.keyringService = "cals";
619
- this.keyringAccount = "github-token";
620
- }
621
- async getToken() {
622
- if (process.env.CALS_GITHUB_TOKEN) {
623
- return process.env.CALS_GITHUB_TOKEN;
624
- }
625
- const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
626
- if (result == null) {
627
- process.stderr.write("No token found. Register using `cals github set-token`\n");
628
- return undefined;
629
- }
630
- return result;
631
- }
632
- async markInvalid() {
633
- await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
634
- }
635
- async setToken(value) {
636
- await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
637
- }
616
+ class GitHubTokenCliProvider {
617
+ constructor() {
618
+ this.keyringService = "cals";
619
+ this.keyringAccount = "github-token";
620
+ }
621
+ async getToken() {
622
+ if (process.env.CALS_GITHUB_TOKEN) {
623
+ return process.env.CALS_GITHUB_TOKEN;
624
+ }
625
+ const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
626
+ if (result == null) {
627
+ process.stderr.write("No token found. Register using `cals github set-token`\n");
628
+ return undefined;
629
+ }
630
+ return result;
631
+ }
632
+ async markInvalid() {
633
+ await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
634
+ }
635
+ async setToken(value) {
636
+ await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
637
+ }
638
638
  }
639
639
 
640
- async function undefinedForNotFound(value) {
641
- try {
642
- return await value;
643
- }
644
- catch (e) {
645
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
646
- if (e.name === "HttpError" && e.status === 404) {
647
- return undefined;
648
- }
649
- else {
650
- throw e;
651
- }
652
- }
640
+ async function undefinedForNotFound(value) {
641
+ try {
642
+ return await value;
643
+ }
644
+ catch (e) {
645
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
646
+ if (e.name === "HttpError" && e.status === 404) {
647
+ return undefined;
648
+ }
649
+ else {
650
+ throw e;
651
+ }
652
+ }
653
653
  }
654
654
 
655
- class GitHubService {
656
- constructor(props) {
657
- this._requestCount = 0;
658
- this.config = props.config;
659
- this.octokit = props.octokit;
660
- this.cache = props.cache;
661
- this.tokenProvider = props.tokenProvider;
662
- // Control concurrency to GitHub API at service level so we
663
- // can maximize concurrency all other places.
664
- this.semaphore = pLimit__default["default"](6);
665
- this.octokit.hook.wrap("request", async (request, options) => {
666
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
667
- this._requestCount++;
668
- if (options.method !== "GET") {
669
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
670
- return this.semaphore(() => request(options));
671
- }
672
- // Try to cache ETag for GET requests to save on rate limiting.
673
- // Hits on ETag does not count towards rate limiting.
674
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
675
- const rest = {
676
- ...options,
677
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
678
- };
679
- delete rest.method;
680
- delete rest.baseUrl;
681
- delete rest.headers;
682
- delete rest.mediaType;
683
- delete rest.request;
684
- // Build a key that is used to identify this request.
685
- const key = Buffer.from(JSON.stringify(rest)).toString("base64");
686
- const cacheItem = this.cache.retrieveJson(key);
687
- if (cacheItem !== undefined) {
688
- // Copying doesn't work, seems we need to mutate this.
689
- options.headers["If-None-Match"] = cacheItem.data.etag;
690
- }
691
- const getResponse = async (allowRetry = true) => {
692
- try {
693
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
694
- return await request(options);
695
- }
696
- catch (e) {
697
- // Handle no change in ETag.
698
- if (e.status === 304) {
699
- return undefined;
700
- }
701
- // GitHub seems to throw a lot of 502 errors.
702
- // Let's give it a few seconds and retry one time.
703
- if (e.status === 502 && allowRetry) {
704
- await new Promise((resolve) => setTimeout(resolve, 2000));
705
- return await getResponse(false);
706
- }
707
- throw e;
708
- }
709
- };
710
- const response = await this.semaphore(async () => {
711
- return getResponse();
712
- });
713
- if (response === undefined) {
714
- // Undefined is returned for cached data.
715
- if (cacheItem === undefined) {
716
- throw new Error("Missing expected cache item");
717
- }
718
- // Use previous value.
719
- return cacheItem.data.data;
720
- }
721
- // New value. Store Etag.
722
- if (response.headers.etag) {
723
- this.cache.storeJson(key, {
724
- etag: response.headers.etag,
725
- data: response,
726
- });
727
- }
728
- return response;
729
- /* eslint-enable @typescript-eslint/no-unsafe-member-access */
730
- });
731
- }
732
- get requestCount() {
733
- return this._requestCount;
734
- }
735
- async runGraphqlQuery(query) {
736
- const token = await this.tokenProvider.getToken();
737
- if (token === undefined) {
738
- throw new Error("Missing token for GitHub");
739
- }
740
- const url = "https://api.github.com/graphql";
741
- const headers = {
742
- Authorization: `Bearer ${token}`,
743
- };
744
- const response = await this.semaphore(() => fetch__default["default"](url, {
745
- method: "POST",
746
- headers,
747
- body: JSON.stringify({ query }),
748
- agent: this.config.agent,
749
- }));
750
- if (response.status === 401) {
751
- process.stderr.write("Unauthorized\n");
752
- await this.tokenProvider.markInvalid();
753
- }
754
- if (!response.ok) {
755
- throw new Error(`Response from GitHub not OK (${response.status}): ${JSON.stringify(response)}`);
756
- }
757
- const json = (await response.json());
758
- if (!!json.errors) {
759
- throw new Error(`Error from GitHub GraphQL API: ${JSON.stringify(json.errors)}`);
760
- }
761
- if (json.data == null) {
762
- throw new Error(`No data received from GitHub GraphQL API (unknown reason)`);
763
- }
764
- return json.data;
765
- }
766
- async getOrgRepoList({ org }) {
655
+ class GitHubService {
656
+ constructor(props) {
657
+ this._requestCount = 0;
658
+ this.config = props.config;
659
+ this.octokit = props.octokit;
660
+ this.cache = props.cache;
661
+ this.tokenProvider = props.tokenProvider;
662
+ // Control concurrency to GitHub API at service level so we
663
+ // can maximize concurrency all other places.
664
+ this.semaphore = pLimit__default["default"](6);
665
+ this.octokit.hook.wrap("request", async (request, options) => {
666
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
667
+ this._requestCount++;
668
+ if (options.method !== "GET") {
669
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
670
+ return this.semaphore(() => request(options));
671
+ }
672
+ // Try to cache ETag for GET requests to save on rate limiting.
673
+ // Hits on ETag does not count towards rate limiting.
674
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
675
+ const rest = {
676
+ ...options,
677
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
678
+ };
679
+ delete rest.method;
680
+ delete rest.baseUrl;
681
+ delete rest.headers;
682
+ delete rest.mediaType;
683
+ delete rest.request;
684
+ // Build a key that is used to identify this request.
685
+ const key = Buffer.from(JSON.stringify(rest)).toString("base64");
686
+ const cacheItem = this.cache.retrieveJson(key);
687
+ if (cacheItem !== undefined) {
688
+ // Copying doesn't work, seems we need to mutate this.
689
+ options.headers["If-None-Match"] = cacheItem.data.etag;
690
+ }
691
+ const getResponse = async (allowRetry = true) => {
692
+ try {
693
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
694
+ return await request(options);
695
+ }
696
+ catch (e) {
697
+ // Handle no change in ETag.
698
+ if (e.status === 304) {
699
+ return undefined;
700
+ }
701
+ // GitHub seems to throw a lot of 502 errors.
702
+ // Let's give it a few seconds and retry one time.
703
+ if (e.status === 502 && allowRetry) {
704
+ await new Promise((resolve) => setTimeout(resolve, 2000));
705
+ return await getResponse(false);
706
+ }
707
+ throw e;
708
+ }
709
+ };
710
+ const response = await this.semaphore(async () => {
711
+ return getResponse();
712
+ });
713
+ if (response === undefined) {
714
+ // Undefined is returned for cached data.
715
+ if (cacheItem === undefined) {
716
+ throw new Error("Missing expected cache item");
717
+ }
718
+ // Use previous value.
719
+ return cacheItem.data.data;
720
+ }
721
+ // New value. Store Etag.
722
+ if (response.headers.etag) {
723
+ this.cache.storeJson(key, {
724
+ etag: response.headers.etag,
725
+ data: response,
726
+ });
727
+ }
728
+ return response;
729
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
730
+ });
731
+ }
732
+ get requestCount() {
733
+ return this._requestCount;
734
+ }
735
+ async runGraphqlQuery(query) {
736
+ const token = await this.tokenProvider.getToken();
737
+ if (token === undefined) {
738
+ throw new Error("Missing token for GitHub");
739
+ }
740
+ const url = "https://api.github.com/graphql";
741
+ const headers = {
742
+ Authorization: `Bearer ${token}`,
743
+ };
744
+ const response = await this.semaphore(() => fetch__default["default"](url, {
745
+ method: "POST",
746
+ headers,
747
+ body: JSON.stringify({ query }),
748
+ agent: this.config.agent,
749
+ }));
750
+ if (response.status === 401) {
751
+ process.stderr.write("Unauthorized\n");
752
+ await this.tokenProvider.markInvalid();
753
+ }
754
+ if (!response.ok) {
755
+ throw new Error(`Response from GitHub not OK (${response.status}): ${JSON.stringify(response)}`);
756
+ }
757
+ const json = (await response.json());
758
+ if (!!json.errors) {
759
+ throw new Error(`Error from GitHub GraphQL API: ${JSON.stringify(json.errors)}`);
760
+ }
761
+ if (json.data == null) {
762
+ throw new Error(`No data received from GitHub GraphQL API (unknown reason)`);
763
+ }
764
+ return json.data;
765
+ }
766
+ async getOrgRepoList({ org }) {
767
767
  const getQuery = (after) => `{
768
768
  organization(login: "${org}") {
769
769
  repositories(first: 100${after === null ? "" : `, after: "${after}"`}) {
@@ -796,144 +796,144 @@ class GitHubService {
796
796
  }
797
797
  }
798
798
  }
799
- }`;
800
- return this.cache.json(`repos-${org}`, async () => {
801
- const repos = [];
802
- let after = null;
803
- while (true) {
804
- const query = getQuery(after);
805
- const res = await this.runGraphqlQuery(query);
806
- if (res.organization == null) {
807
- throw new Error("Missing organization");
808
- }
809
- if (res.organization.repositories.nodes == null) {
810
- throw new Error("Missing organization nodes");
811
- }
812
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
813
- repos.push(...res.organization.repositories.nodes);
814
- if (!res.organization.repositories.pageInfo.hasNextPage) {
815
- break;
816
- }
817
- after = res.organization.repositories.pageInfo.endCursor;
818
- }
819
- return repos.sort((a, b) => a.name.localeCompare(b.name));
820
- });
821
- }
822
- async getOrgMembersList(org) {
823
- const options = this.octokit.orgs.listMembers.endpoint.merge({
824
- org,
825
- });
826
- return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
827
- }
828
- async getOrgMembersInvitedList(org) {
829
- const options = this.octokit.orgs.listPendingInvitations.endpoint.merge({
830
- org,
831
- });
832
- return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
833
- }
834
- async getOrgMembersListIncludingInvited(org) {
835
- return [
836
- ...(await this.getOrgMembersList(org)).map((it) => ({
837
- type: "member",
838
- login: it.login,
839
- data: it,
840
- })),
841
- ...(await this.getOrgMembersInvitedList(org)).map((it) => {
842
- var _a;
843
- return ({
844
- type: "invited",
845
- // TODO: Fix ?? case properly
846
- login: (_a = it.login) !== null && _a !== void 0 ? _a : "invalid",
847
- data: it,
848
- });
849
- }),
850
- ];
851
- }
852
- async getRepository(owner, repo) {
853
- return this.cache.json(`get-repository-${owner}-${repo}`, async () => {
854
- const response = await undefinedForNotFound(this.octokit.repos.get({
855
- owner,
856
- repo,
857
- }));
858
- return response === undefined ? undefined : response.data;
859
- });
860
- }
861
- async getRepositoryTeamsList(repo) {
862
- return this.cache.json(`repository-teams-list-${repo.id}`, async () => {
863
- const options = this.octokit.repos.listTeams.endpoint.merge({
864
- owner: repo.owner.login,
865
- repo: repo.name,
866
- });
867
- return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
868
- });
869
- }
870
- async getRepositoryHooks(owner, repo) {
871
- return this.cache.json(`repository-hooks-${owner}-${repo}`, async () => {
872
- const options = this.octokit.repos.listWebhooks.endpoint.merge({
873
- owner,
874
- repo,
875
- });
876
- return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
877
- });
878
- }
879
- async getOrg(org) {
880
- const orgResponse = await this.octokit.orgs.get({
881
- org,
882
- });
883
- return orgResponse.data;
884
- }
885
- async getTeamList(org) {
886
- return this.cache.json(`team-list-${org.login}`, async () => {
887
- const options = this.octokit.teams.list.endpoint.merge({
888
- org: org.login,
889
- });
890
- return (await this.octokit.paginate(options));
891
- });
892
- }
893
- async getTeamMemberList(org, team) {
894
- return this.cache.json(`team-member-list-${team.id}`, async () => {
895
- const options = this.octokit.teams.listMembersInOrg.endpoint.merge({
896
- org: org.login,
897
- team_slug: team.slug,
898
- });
899
- return (await this.octokit.paginate(options));
900
- });
901
- }
902
- async getTeamMemberInvitedList(org, team) {
903
- return this.cache.json(`team-member-invited-list-${team.id}`, async () => {
904
- const options = this.octokit.teams.listPendingInvitationsInOrg.endpoint.merge({
905
- org: org.login,
906
- team_slug: team.slug,
907
- });
908
- return (await this.octokit.paginate(options));
909
- });
910
- }
911
- async getTeamMemberListIncludingInvited(org, team) {
912
- return [
913
- ...(await this.getTeamMemberList(org, team)).map((it) => ({
914
- type: "member",
915
- login: it.login,
916
- data: it,
917
- })),
918
- ...(await this.getTeamMemberInvitedList(org, team)).map((it) => {
919
- var _a;
920
- return ({
921
- type: "invited",
922
- // TODO: Fix ?? case properly
923
- login: (_a = it.login) !== null && _a !== void 0 ? _a : "invalid",
924
- data: it,
925
- });
926
- }),
927
- ];
928
- }
929
- async getSearchedPullRequestList() {
930
- // NOTE: Changes to this must by synced with SearchedPullRequestListQueryResult.
799
+ }`;
800
+ return this.cache.json(`repos-${org}`, async () => {
801
+ const repos = [];
802
+ let after = null;
803
+ while (true) {
804
+ const query = getQuery(after);
805
+ const res = await this.runGraphqlQuery(query);
806
+ if (res.organization == null) {
807
+ throw new Error("Missing organization");
808
+ }
809
+ if (res.organization.repositories.nodes == null) {
810
+ throw new Error("Missing organization nodes");
811
+ }
812
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
813
+ repos.push(...res.organization.repositories.nodes);
814
+ if (!res.organization.repositories.pageInfo.hasNextPage) {
815
+ break;
816
+ }
817
+ after = res.organization.repositories.pageInfo.endCursor;
818
+ }
819
+ return repos.sort((a, b) => a.name.localeCompare(b.name));
820
+ });
821
+ }
822
+ async getOrgMembersList(org) {
823
+ const options = this.octokit.orgs.listMembers.endpoint.merge({
824
+ org,
825
+ });
826
+ return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
827
+ }
828
+ async getOrgMembersInvitedList(org) {
829
+ const options = this.octokit.orgs.listPendingInvitations.endpoint.merge({
830
+ org,
831
+ });
832
+ return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
833
+ }
834
+ async getOrgMembersListIncludingInvited(org) {
835
+ return [
836
+ ...(await this.getOrgMembersList(org)).map((it) => ({
837
+ type: "member",
838
+ login: it.login,
839
+ data: it,
840
+ })),
841
+ ...(await this.getOrgMembersInvitedList(org)).map((it) => {
842
+ var _a;
843
+ return ({
844
+ type: "invited",
845
+ // TODO: Fix ?? case properly
846
+ login: (_a = it.login) !== null && _a !== void 0 ? _a : "invalid",
847
+ data: it,
848
+ });
849
+ }),
850
+ ];
851
+ }
852
+ async getRepository(owner, repo) {
853
+ return this.cache.json(`get-repository-${owner}-${repo}`, async () => {
854
+ const response = await undefinedForNotFound(this.octokit.repos.get({
855
+ owner,
856
+ repo,
857
+ }));
858
+ return response === undefined ? undefined : response.data;
859
+ });
860
+ }
861
+ async getRepositoryTeamsList(repo) {
862
+ return this.cache.json(`repository-teams-list-${repo.id}`, async () => {
863
+ const options = this.octokit.repos.listTeams.endpoint.merge({
864
+ owner: repo.owner.login,
865
+ repo: repo.name,
866
+ });
867
+ return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
868
+ });
869
+ }
870
+ async getRepositoryHooks(owner, repo) {
871
+ return this.cache.json(`repository-hooks-${owner}-${repo}`, async () => {
872
+ const options = this.octokit.repos.listWebhooks.endpoint.merge({
873
+ owner,
874
+ repo,
875
+ });
876
+ return ((await undefinedForNotFound(this.octokit.paginate(options))) || []);
877
+ });
878
+ }
879
+ async getOrg(org) {
880
+ const orgResponse = await this.octokit.orgs.get({
881
+ org,
882
+ });
883
+ return orgResponse.data;
884
+ }
885
+ async getTeamList(org) {
886
+ return this.cache.json(`team-list-${org.login}`, async () => {
887
+ const options = this.octokit.teams.list.endpoint.merge({
888
+ org: org.login,
889
+ });
890
+ return (await this.octokit.paginate(options));
891
+ });
892
+ }
893
+ async getTeamMemberList(org, team) {
894
+ return this.cache.json(`team-member-list-${team.id}`, async () => {
895
+ const options = this.octokit.teams.listMembersInOrg.endpoint.merge({
896
+ org: org.login,
897
+ team_slug: team.slug,
898
+ });
899
+ return (await this.octokit.paginate(options));
900
+ });
901
+ }
902
+ async getTeamMemberInvitedList(org, team) {
903
+ return this.cache.json(`team-member-invited-list-${team.id}`, async () => {
904
+ const options = this.octokit.teams.listPendingInvitationsInOrg.endpoint.merge({
905
+ org: org.login,
906
+ team_slug: team.slug,
907
+ });
908
+ return (await this.octokit.paginate(options));
909
+ });
910
+ }
911
+ async getTeamMemberListIncludingInvited(org, team) {
912
+ return [
913
+ ...(await this.getTeamMemberList(org, team)).map((it) => ({
914
+ type: "member",
915
+ login: it.login,
916
+ data: it,
917
+ })),
918
+ ...(await this.getTeamMemberInvitedList(org, team)).map((it) => {
919
+ var _a;
920
+ return ({
921
+ type: "invited",
922
+ // TODO: Fix ?? case properly
923
+ login: (_a = it.login) !== null && _a !== void 0 ? _a : "invalid",
924
+ data: it,
925
+ });
926
+ }),
927
+ ];
928
+ }
929
+ async getSearchedPullRequestList() {
930
+ // NOTE: Changes to this must by synced with SearchedPullRequestListQueryResult.
931
931
  const getQuery = (after) => `{
932
932
  search(
933
933
  query: "is:open is:pr user:capralifecycle user:capraconsulting archived:false",
934
934
  type: ISSUE,
935
- first: 100${after === null
936
- ? ""
935
+ first: 100${after === null
936
+ ? ""
937
937
  : `,
938
938
  after: "${after}"`}
939
939
  ) {
@@ -972,51 +972,51 @@ class GitHubService {
972
972
  }
973
973
  }
974
974
  }
975
- }`;
976
- const pulls = [];
977
- let after = null;
978
- while (true) {
979
- const query = getQuery(after);
980
- const res = await this.runGraphqlQuery(query);
981
- pulls.push(...res.search.edges.map((it) => it.node));
982
- if (!res.search.pageInfo.hasNextPage) {
983
- break;
984
- }
985
- after = res.search.pageInfo.endCursor;
986
- }
987
- return pulls.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
988
- }
989
- async getHasVulnerabilityAlertsEnabled(owner, repo) {
990
- try {
991
- const response = await this.octokit.repos.checkVulnerabilityAlerts({
992
- owner: owner,
993
- repo: repo,
994
- });
995
- if (response.status !== 204) {
996
- console.log(response);
997
- throw new Error("Unknown response - see previous log line");
998
- }
999
- return true;
1000
- }
1001
- catch (e) {
1002
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1003
- if (e.status === 404) {
1004
- return false;
1005
- }
1006
- throw e;
1007
- }
1008
- }
1009
- async enableVulnerabilityAlerts(owner, repo) {
1010
- await this.octokit.repos.enableVulnerabilityAlerts({
1011
- owner: owner,
1012
- repo: repo,
1013
- });
1014
- }
1015
- /**
1016
- * Get the vulnerability alerts for a repository.
1017
- */
1018
- async getVulnerabilityAlerts(owner, repo) {
1019
- // NOTE: Changes to this must by synced with VulnerabilityAlertsQueryResult.
975
+ }`;
976
+ const pulls = [];
977
+ let after = null;
978
+ while (true) {
979
+ const query = getQuery(after);
980
+ const res = await this.runGraphqlQuery(query);
981
+ pulls.push(...res.search.edges.map((it) => it.node));
982
+ if (!res.search.pageInfo.hasNextPage) {
983
+ break;
984
+ }
985
+ after = res.search.pageInfo.endCursor;
986
+ }
987
+ return pulls.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
988
+ }
989
+ async getHasVulnerabilityAlertsEnabled(owner, repo) {
990
+ try {
991
+ const response = await this.octokit.repos.checkVulnerabilityAlerts({
992
+ owner: owner,
993
+ repo: repo,
994
+ });
995
+ if (response.status !== 204) {
996
+ console.log(response);
997
+ throw new Error("Unknown response - see previous log line");
998
+ }
999
+ return true;
1000
+ }
1001
+ catch (e) {
1002
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1003
+ if (e.status === 404) {
1004
+ return false;
1005
+ }
1006
+ throw e;
1007
+ }
1008
+ }
1009
+ async enableVulnerabilityAlerts(owner, repo) {
1010
+ await this.octokit.repos.enableVulnerabilityAlerts({
1011
+ owner: owner,
1012
+ repo: repo,
1013
+ });
1014
+ }
1015
+ /**
1016
+ * Get the vulnerability alerts for a repository.
1017
+ */
1018
+ async getVulnerabilityAlerts(owner, repo) {
1019
+ // NOTE: Changes to this must by synced with VulnerabilityAlertsQueryResult.
1020
1020
  const getQuery = (after) => `{
1021
1021
  repository(owner: "${owner}", name: "${repo}") {
1022
1022
  vulnerabilityAlerts(first: 100${after === null ? "" : `, after: "${after}"`}) {
@@ -1046,28 +1046,28 @@ class GitHubService {
1046
1046
  }
1047
1047
  }
1048
1048
  }
1049
- }`;
1050
- return this.cache.json(`vulnerability-alerts-${owner}-${repo}`, async () => {
1051
- var _a, _b, _c, _d, _e;
1052
- const result = [];
1053
- let after = null;
1054
- while (true) {
1055
- const query = getQuery(after);
1056
- const res = await this.runGraphqlQuery(query);
1057
- result.push(...((_c = (_b = (_a = res.repository) === null || _a === void 0 ? void 0 : _a.vulnerabilityAlerts.edges) === null || _b === void 0 ? void 0 : _b.map((it) => it.node)) !== null && _c !== void 0 ? _c : []));
1058
- if (!((_d = res.repository) === null || _d === void 0 ? void 0 : _d.vulnerabilityAlerts.pageInfo.hasNextPage)) {
1059
- break;
1060
- }
1061
- after = (_e = res.repository) === null || _e === void 0 ? void 0 : _e.vulnerabilityAlerts.pageInfo.endCursor;
1062
- }
1063
- return result;
1064
- });
1065
- }
1066
- /**
1067
- * Get the Renovate Dependency Dashboard issue.
1068
- */
1069
- async getRenovateDependencyDashboardIssue(owner, repo) {
1070
- // NOTE: Changes to this must by synced with RenovateDependencyDashboardIssueQueryResult.
1049
+ }`;
1050
+ return this.cache.json(`vulnerability-alerts-${owner}-${repo}`, async () => {
1051
+ var _a, _b, _c, _d, _e;
1052
+ const result = [];
1053
+ let after = null;
1054
+ while (true) {
1055
+ const query = getQuery(after);
1056
+ const res = await this.runGraphqlQuery(query);
1057
+ result.push(...((_c = (_b = (_a = res.repository) === null || _a === void 0 ? void 0 : _a.vulnerabilityAlerts.edges) === null || _b === void 0 ? void 0 : _b.map((it) => it.node)) !== null && _c !== void 0 ? _c : []));
1058
+ if (!((_d = res.repository) === null || _d === void 0 ? void 0 : _d.vulnerabilityAlerts.pageInfo.hasNextPage)) {
1059
+ break;
1060
+ }
1061
+ after = (_e = res.repository) === null || _e === void 0 ? void 0 : _e.vulnerabilityAlerts.pageInfo.endCursor;
1062
+ }
1063
+ return result;
1064
+ });
1065
+ }
1066
+ /**
1067
+ * Get the Renovate Dependency Dashboard issue.
1068
+ */
1069
+ async getRenovateDependencyDashboardIssue(owner, repo) {
1070
+ // NOTE: Changes to this must by synced with RenovateDependencyDashboardIssueQueryResult.
1071
1071
  const getQuery = (after) => `{
1072
1072
  repository(owner: "${owner}", name: "${repo}") {
1073
1073
  issues(
@@ -1098,55 +1098,55 @@ class GitHubService {
1098
1098
  }
1099
1099
  }
1100
1100
  }
1101
- }`;
1102
- const issues = await this.cache.json(`renovate-bot-issues-${owner}-${repo}`, async () => {
1103
- var _a, _b, _c, _d, _e;
1104
- const result = [];
1105
- let after = null;
1106
- while (true) {
1107
- const query = getQuery(after);
1108
- const res = await this.runGraphqlQuery(query);
1109
- const nodes = (_c = (_b = (_a = res.repository) === null || _a === void 0 ? void 0 : _a.issues.edges) === null || _b === void 0 ? void 0 : _b.map((it) => it.node)) !== null && _c !== void 0 ? _c : [];
1110
- result.push(...nodes
1111
- .filter((it) => it.title === "Dependency Dashboard")
1112
- .map((it) => {
1113
- var _a, _b, _c, _d, _e;
1114
- return ({
1115
- number: it.number,
1116
- body: it.body,
1117
- lastUpdatedByRenovate: (_e = (_d = (_c = (_b = (_a = it.userContentEdits) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.filter((it) => { var _a; return ((_a = it.editor) === null || _a === void 0 ? void 0 : _a.login) === "renovate"; })) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.createdAt) !== null && _e !== void 0 ? _e : null,
1118
- });
1119
- }));
1120
- if (!((_d = res.repository) === null || _d === void 0 ? void 0 : _d.issues.pageInfo.hasNextPage)) {
1121
- break;
1122
- }
1123
- after = (_e = res.repository) === null || _e === void 0 ? void 0 : _e.issues.pageInfo.endCursor;
1124
- }
1125
- return result;
1126
- });
1127
- if (issues.length == 0) {
1128
- return undefined;
1129
- }
1130
- return issues[0];
1131
- }
1132
- }
1133
- async function createOctokit(config, tokenProvider) {
1134
- return new rest.Octokit({
1135
- auth: await tokenProvider.getToken(),
1136
- request: {
1137
- agent: config.agent,
1138
- },
1139
- });
1140
- }
1141
- async function createGitHubService(props) {
1142
- var _a;
1143
- const tokenProvider = (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new GitHubTokenCliProvider();
1144
- return new GitHubService({
1145
- config: props.config,
1146
- octokit: await createOctokit(props.config, tokenProvider),
1147
- cache: props.cache,
1148
- tokenProvider,
1149
- });
1101
+ }`;
1102
+ const issues = await this.cache.json(`renovate-bot-issues-${owner}-${repo}`, async () => {
1103
+ var _a, _b, _c, _d, _e;
1104
+ const result = [];
1105
+ let after = null;
1106
+ while (true) {
1107
+ const query = getQuery(after);
1108
+ const res = await this.runGraphqlQuery(query);
1109
+ const nodes = (_c = (_b = (_a = res.repository) === null || _a === void 0 ? void 0 : _a.issues.edges) === null || _b === void 0 ? void 0 : _b.map((it) => it.node)) !== null && _c !== void 0 ? _c : [];
1110
+ result.push(...nodes
1111
+ .filter((it) => it.title === "Dependency Dashboard")
1112
+ .map((it) => {
1113
+ var _a, _b, _c, _d, _e;
1114
+ return ({
1115
+ number: it.number,
1116
+ body: it.body,
1117
+ lastUpdatedByRenovate: (_e = (_d = (_c = (_b = (_a = it.userContentEdits) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.filter((it) => { var _a; return ((_a = it.editor) === null || _a === void 0 ? void 0 : _a.login) === "renovate"; })) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.createdAt) !== null && _e !== void 0 ? _e : null,
1118
+ });
1119
+ }));
1120
+ if (!((_d = res.repository) === null || _d === void 0 ? void 0 : _d.issues.pageInfo.hasNextPage)) {
1121
+ break;
1122
+ }
1123
+ after = (_e = res.repository) === null || _e === void 0 ? void 0 : _e.issues.pageInfo.endCursor;
1124
+ }
1125
+ return result;
1126
+ });
1127
+ if (issues.length == 0) {
1128
+ return undefined;
1129
+ }
1130
+ return issues[0];
1131
+ }
1132
+ }
1133
+ async function createOctokit(config, tokenProvider) {
1134
+ return new rest.Octokit({
1135
+ auth: await tokenProvider.getToken(),
1136
+ request: {
1137
+ agent: config.agent,
1138
+ },
1139
+ });
1140
+ }
1141
+ async function createGitHubService(props) {
1142
+ var _a;
1143
+ const tokenProvider = (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new GitHubTokenCliProvider();
1144
+ return new GitHubService({
1145
+ config: props.config,
1146
+ octokit: await createOctokit(props.config, tokenProvider),
1147
+ cache: props.cache,
1148
+ tokenProvider,
1149
+ });
1150
1150
  }
1151
1151
 
1152
1152
  var index$4 = /*#__PURE__*/Object.freeze({
@@ -1164,275 +1164,275 @@ var index$3 = /*#__PURE__*/Object.freeze({
1164
1164
  GitHubService: GitHubService
1165
1165
  });
1166
1166
 
1167
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
1168
- class LoadSecrets {
1169
- constructor(props) {
1170
- this.smClientForRegions = {};
1171
- this.stsClient = new clientSts.STSClient({
1172
- region: "eu-west-1",
1173
- });
1174
- this.reporter = props.reporter;
1175
- this.silent = props.silent;
1176
- }
1177
- getSmClient(region) {
1178
- if (!this.smClientForRegions[region]) {
1179
- this.smClientForRegions[region] = new clientSecretsManager.SecretsManagerClient({
1180
- region,
1181
- });
1182
- }
1183
- return this.smClientForRegions[region];
1184
- }
1185
- async getInput(options) {
1186
- return new Promise((resolve, reject) => {
1187
- read__default["default"](options, (err, answer) => {
1188
- if (err) {
1189
- reject(err);
1190
- }
1191
- resolve(answer);
1192
- });
1193
- });
1194
- }
1195
- async getSecretDetails(client, secretId) {
1196
- try {
1197
- return await client.send(new clientSecretsManager.DescribeSecretCommand({ SecretId: secretId }));
1198
- }
1199
- catch (e) {
1200
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1201
- if (e.name && e.name === "ResourceNotFoundException") {
1202
- return null;
1203
- }
1204
- throw e;
1205
- }
1206
- }
1207
- async handleJsonUpdate(secret) {
1208
- this.reporter.log("The secret is of type JSON with these expected fields:");
1209
- for (const field of secret.fields) {
1210
- const key = typeof field === "string" ? field : field.key;
1211
- const desc = typeof field === "string"
1212
- ? ""
1213
- : field.description
1214
- ? ` (${field.description})`
1215
- : "";
1216
- this.reporter.log(` - ${key}${desc}`);
1217
- }
1218
- this.reporter.log("");
1219
- // TODO: Ability to specify full json value as one line.
1220
- const collectedValues = {};
1221
- for (const field of secret.fields) {
1222
- const key = typeof field === "string" ? field : field.key;
1223
- this.reporter.log(`Field: ${this.reporter.format.greenBright(key)}`);
1224
- if (typeof field !== "string" && field.example != null) {
1225
- this.reporter.log(`Example: ${this.reporter.format.magentaBright(field.example)}`);
1226
- }
1227
- const value = await this.getInput({
1228
- prompt: "Enter value (Ctrl+C to abort): ",
1229
- silent: this.silent,
1230
- });
1231
- collectedValues[key] = value;
1232
- this.reporter.log("");
1233
- }
1234
- return JSON.stringify(collectedValues, undefined, " ");
1235
- }
1236
- getFullName(secretGroup, secret) {
1237
- return `${secretGroup.namePrefix}${secret.name}`;
1238
- }
1239
- async syncTags(client, secret, tags) {
1240
- const keysToRemove = secret
1241
- .Tags.filter((existingTag) => !tags.some((it) => it.Key === existingTag.Key))
1242
- .map((it) => it.Key);
1243
- if (keysToRemove.length > 0) {
1244
- this.reporter.log(`Removing obsolete tags: ${keysToRemove.join(", ")}`);
1245
- await client.send(new clientSecretsManager.UntagResourceCommand({
1246
- SecretId: secret.ARN,
1247
- TagKeys: keysToRemove,
1248
- }));
1249
- }
1250
- const tagsToUpdate = tags.filter((expectedTag) => {
1251
- const existing = secret.Tags.find((it) => it.Key === expectedTag.Key);
1252
- return existing == null || existing.Value != expectedTag.Value;
1253
- });
1254
- if (tagsToUpdate.length > 0) {
1255
- this.reporter.log(`Storing tags: ${tagsToUpdate.map((it) => it.Key).join(", ")}`);
1256
- await client.send(new clientSecretsManager.TagResourceCommand({
1257
- SecretId: secret.ARN,
1258
- Tags: tagsToUpdate,
1259
- }));
1260
- }
1261
- }
1262
- async getSecretValue(client, secretId) {
1263
- const result = await client.send(new clientSecretsManager.GetSecretValueCommand({
1264
- SecretId: secretId,
1265
- }));
1266
- if (result.SecretString == null) {
1267
- throw new Error("Missing SecretString (is it a binary?)");
1268
- }
1269
- return result.SecretString;
1270
- }
1271
- async handleUpdate(secretGroup, secret) {
1272
- const client = this.getSmClient(secretGroup.region);
1273
- const fullName = this.getFullName(secretGroup, secret);
1274
- const describeSecret = await this.getSecretDetails(client, fullName);
1275
- this.reporter.log(`Secret: ${this.reporter.format.greenBright(fullName)}`);
1276
- if (describeSecret == null) {
1277
- this.reporter.log("The secret does not already exist and will be created");
1278
- }
1279
- else {
1280
- this.reporter.log("Current value:");
1281
- this.reporter.log(this.reporter.format.yellowBright((await this.getSecretValue(client, fullName)).replace(/^/gm, " ")));
1282
- }
1283
- this.reporter.log("");
1284
- let secretValue;
1285
- if (secret.type === "json") {
1286
- try {
1287
- secretValue = await this.handleJsonUpdate(secret);
1288
- }
1289
- catch (e) {
1290
- if (e instanceof Error && e.message === "canceled") {
1291
- this.reporter.log("Aborted");
1292
- return;
1293
- }
1294
- throw e;
1295
- }
1296
- }
1297
- else {
1298
- throw new Error(`Unsupported type`);
1299
- }
1300
- this.reporter.log("Storing secret value:");
1301
- this.reporter.log(this.reporter.format.yellowBright(secretValue.replace(/^/gm, " ")));
1302
- const tags = [
1303
- {
1304
- Key: "Source",
1305
- Value: "load-secrets script",
1306
- },
1307
- ];
1308
- let arn;
1309
- let version;
1310
- if (describeSecret == null) {
1311
- const createResult = await client.send(new clientSecretsManager.CreateSecretCommand({
1312
- Name: fullName,
1313
- Description: "Created by load-secrets",
1314
- SecretString: secretValue,
1315
- Tags: tags,
1316
- }));
1317
- if (createResult.VersionId == null) {
1318
- throw new Error("Expected versionId");
1319
- }
1320
- arn = createResult.ARN;
1321
- version = createResult.VersionId;
1322
- }
1323
- else {
1324
- if (describeSecret.DeletedDate != null) {
1325
- await client.send(new clientSecretsManager.RestoreSecretCommand({
1326
- SecretId: fullName,
1327
- }));
1328
- }
1329
- const updateResult = await client.send(new clientSecretsManager.PutSecretValueCommand({
1330
- SecretId: fullName,
1331
- SecretString: secretValue,
1332
- }));
1333
- if (updateResult.VersionId == null) {
1334
- throw new Error("Expected versionId");
1335
- }
1336
- await this.syncTags(client, describeSecret, tags);
1337
- arn = updateResult.ARN;
1338
- version = updateResult.VersionId;
1339
- }
1340
- this.reporter.log("");
1341
- this.reporter.log("Secret stored:");
1342
- this.reporter.log(`ARN: ${this.reporter.format.greenBright(arn)}`);
1343
- this.reporter.log(`Version: ${this.reporter.format.greenBright(version)}`);
1344
- }
1345
- checkSecretGroup(secretGroup) {
1346
- if (!secretGroup.namePrefix.startsWith("/") ||
1347
- !secretGroup.namePrefix.endsWith("/")) {
1348
- throw new Error(`namePrefix should start and end with /. Current value: ${secretGroup.namePrefix}`);
1349
- }
1350
- }
1351
- getSecretDescription(details) {
1352
- var _a, _b;
1353
- return details == null
1354
- ? "not yet created"
1355
- : (details === null || details === void 0 ? void 0 : details.DeletedDate) != null
1356
- ? `scheduled for deletion ${details.DeletedDate.toISOString()}`
1357
- : `last changed ${(_b = (_a = details.LastChangedDate) === null || _a === void 0 ? void 0 : _a.toISOString()) !== null && _b !== void 0 ? _b : "unknown"}`;
1358
- }
1359
- /**
1360
- * Returns false if aborted.
1361
- */
1362
- async selectAndUpdate(secretGroups) {
1363
- const secrets = [];
1364
- this.reporter.log("Select secret to write:");
1365
- this.reporter.log("");
1366
- for (const secretGroup of secretGroups) {
1367
- this.reporter.log(`${secretGroup.description} (prefix: ${secretGroup.namePrefix})`);
1368
- for (let i = 0; i < secretGroup.secrets.length; i++) {
1369
- const offset = secrets.length;
1370
- const secret = secretGroup.secrets[i];
1371
- secrets.push({
1372
- secret: secret,
1373
- secretGroup,
1374
- });
1375
- const client = this.getSmClient(secretGroup.region);
1376
- const details = await this.getSecretDetails(client, this.getFullName(secretGroup, secret));
1377
- const desc = this.getSecretDescription(details);
1378
- this.reporter.log(` (${offset}) ${secret.name} (${desc})`);
1379
- }
1380
- this.reporter.log("");
1381
- }
1382
- let index;
1383
- try {
1384
- const answer = await this.getInput({
1385
- prompt: "Enter index (or enter to quit): ",
1386
- });
1387
- if (answer.trim() === "") {
1388
- return false;
1389
- }
1390
- index = parseInt(answer);
1391
- if (!secrets[index]) {
1392
- throw new Error();
1393
- }
1394
- }
1395
- catch (e) {
1396
- this.reporter.warn("Secret not found - aborting");
1397
- return false;
1398
- }
1399
- this.reporter.log("");
1400
- await this.handleUpdate(secrets[index].secretGroup, secrets[index].secret);
1401
- this.reporter.log("");
1402
- return true;
1403
- }
1404
- async process(secretGroups) {
1405
- this.reporter.info("Checking account for current credentials");
1406
- this.reporter.info("If any error is given, make sure you have valid credentials active");
1407
- const currentAccount = await this.stsClient.send(new clientSts.GetCallerIdentityCommand({}));
1408
- this.reporter.info(`Running for account ${currentAccount.Account}`);
1409
- this.reporter.log("");
1410
- const matchedSecretGroups = secretGroups.filter((it) => it.accountId === currentAccount.Account);
1411
- if (matchedSecretGroups.length === 0) {
1412
- this.reporter.error(`No secrets specified for this account - aborting`);
1413
- return;
1414
- }
1415
- for (const secretGroup of matchedSecretGroups) {
1416
- this.checkSecretGroup(secretGroup);
1417
- }
1418
- while (await this.selectAndUpdate(matchedSecretGroups)) { }
1419
- }
1420
- }
1421
- /**
1422
- * Load secrets interactively into Secrets Manager.
1423
- */
1424
- function loadSecretsCli(props) {
1425
- const loadSecrets = new LoadSecrets({
1426
- reporter: createReporter({}),
1427
- // For now we show the secrets, so that we get positive feedback that the value
1428
- // is correctly entered.
1429
- silent: false,
1430
- });
1431
- loadSecrets.process(props.secretGroups).catch((error) => {
1432
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1433
- console.error(error.stack || error.message || error);
1434
- process.exitCode = 1;
1435
- });
1167
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
1168
+ class LoadSecrets {
1169
+ constructor(props) {
1170
+ this.smClientForRegions = {};
1171
+ this.stsClient = new clientSts.STSClient({
1172
+ region: "eu-west-1",
1173
+ });
1174
+ this.reporter = props.reporter;
1175
+ this.silent = props.silent;
1176
+ }
1177
+ getSmClient(region) {
1178
+ if (!this.smClientForRegions[region]) {
1179
+ this.smClientForRegions[region] = new clientSecretsManager.SecretsManagerClient({
1180
+ region,
1181
+ });
1182
+ }
1183
+ return this.smClientForRegions[region];
1184
+ }
1185
+ async getInput(options) {
1186
+ return new Promise((resolve, reject) => {
1187
+ read__default["default"](options, (err, answer) => {
1188
+ if (err) {
1189
+ reject(err);
1190
+ }
1191
+ resolve(answer);
1192
+ });
1193
+ });
1194
+ }
1195
+ async getSecretDetails(client, secretId) {
1196
+ try {
1197
+ return await client.send(new clientSecretsManager.DescribeSecretCommand({ SecretId: secretId }));
1198
+ }
1199
+ catch (e) {
1200
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1201
+ if (e.name && e.name === "ResourceNotFoundException") {
1202
+ return null;
1203
+ }
1204
+ throw e;
1205
+ }
1206
+ }
1207
+ async handleJsonUpdate(secret) {
1208
+ this.reporter.log("The secret is of type JSON with these expected fields:");
1209
+ for (const field of secret.fields) {
1210
+ const key = typeof field === "string" ? field : field.key;
1211
+ const desc = typeof field === "string"
1212
+ ? ""
1213
+ : field.description
1214
+ ? ` (${field.description})`
1215
+ : "";
1216
+ this.reporter.log(` - ${key}${desc}`);
1217
+ }
1218
+ this.reporter.log("");
1219
+ // TODO: Ability to specify full json value as one line.
1220
+ const collectedValues = {};
1221
+ for (const field of secret.fields) {
1222
+ const key = typeof field === "string" ? field : field.key;
1223
+ this.reporter.log(`Field: ${this.reporter.format.greenBright(key)}`);
1224
+ if (typeof field !== "string" && field.example != null) {
1225
+ this.reporter.log(`Example: ${this.reporter.format.magentaBright(field.example)}`);
1226
+ }
1227
+ const value = await this.getInput({
1228
+ prompt: "Enter value (Ctrl+C to abort): ",
1229
+ silent: this.silent,
1230
+ });
1231
+ collectedValues[key] = value;
1232
+ this.reporter.log("");
1233
+ }
1234
+ return JSON.stringify(collectedValues, undefined, " ");
1235
+ }
1236
+ getFullName(secretGroup, secret) {
1237
+ return `${secretGroup.namePrefix}${secret.name}`;
1238
+ }
1239
+ async syncTags(client, secret, tags) {
1240
+ const keysToRemove = secret
1241
+ .Tags.filter((existingTag) => !tags.some((it) => it.Key === existingTag.Key))
1242
+ .map((it) => it.Key);
1243
+ if (keysToRemove.length > 0) {
1244
+ this.reporter.log(`Removing obsolete tags: ${keysToRemove.join(", ")}`);
1245
+ await client.send(new clientSecretsManager.UntagResourceCommand({
1246
+ SecretId: secret.ARN,
1247
+ TagKeys: keysToRemove,
1248
+ }));
1249
+ }
1250
+ const tagsToUpdate = tags.filter((expectedTag) => {
1251
+ const existing = secret.Tags.find((it) => it.Key === expectedTag.Key);
1252
+ return existing == null || existing.Value != expectedTag.Value;
1253
+ });
1254
+ if (tagsToUpdate.length > 0) {
1255
+ this.reporter.log(`Storing tags: ${tagsToUpdate.map((it) => it.Key).join(", ")}`);
1256
+ await client.send(new clientSecretsManager.TagResourceCommand({
1257
+ SecretId: secret.ARN,
1258
+ Tags: tagsToUpdate,
1259
+ }));
1260
+ }
1261
+ }
1262
+ async getSecretValue(client, secretId) {
1263
+ const result = await client.send(new clientSecretsManager.GetSecretValueCommand({
1264
+ SecretId: secretId,
1265
+ }));
1266
+ if (result.SecretString == null) {
1267
+ throw new Error("Missing SecretString (is it a binary?)");
1268
+ }
1269
+ return result.SecretString;
1270
+ }
1271
+ async handleUpdate(secretGroup, secret) {
1272
+ const client = this.getSmClient(secretGroup.region);
1273
+ const fullName = this.getFullName(secretGroup, secret);
1274
+ const describeSecret = await this.getSecretDetails(client, fullName);
1275
+ this.reporter.log(`Secret: ${this.reporter.format.greenBright(fullName)}`);
1276
+ if (describeSecret == null) {
1277
+ this.reporter.log("The secret does not already exist and will be created");
1278
+ }
1279
+ else {
1280
+ this.reporter.log("Current value:");
1281
+ this.reporter.log(this.reporter.format.yellowBright((await this.getSecretValue(client, fullName)).replace(/^/gm, " ")));
1282
+ }
1283
+ this.reporter.log("");
1284
+ let secretValue;
1285
+ if (secret.type === "json") {
1286
+ try {
1287
+ secretValue = await this.handleJsonUpdate(secret);
1288
+ }
1289
+ catch (e) {
1290
+ if (e instanceof Error && e.message === "canceled") {
1291
+ this.reporter.log("Aborted");
1292
+ return;
1293
+ }
1294
+ throw e;
1295
+ }
1296
+ }
1297
+ else {
1298
+ throw new Error(`Unsupported type`);
1299
+ }
1300
+ this.reporter.log("Storing secret value:");
1301
+ this.reporter.log(this.reporter.format.yellowBright(secretValue.replace(/^/gm, " ")));
1302
+ const tags = [
1303
+ {
1304
+ Key: "Source",
1305
+ Value: "load-secrets script",
1306
+ },
1307
+ ];
1308
+ let arn;
1309
+ let version;
1310
+ if (describeSecret == null) {
1311
+ const createResult = await client.send(new clientSecretsManager.CreateSecretCommand({
1312
+ Name: fullName,
1313
+ Description: "Created by load-secrets",
1314
+ SecretString: secretValue,
1315
+ Tags: tags,
1316
+ }));
1317
+ if (createResult.VersionId == null) {
1318
+ throw new Error("Expected versionId");
1319
+ }
1320
+ arn = createResult.ARN;
1321
+ version = createResult.VersionId;
1322
+ }
1323
+ else {
1324
+ if (describeSecret.DeletedDate != null) {
1325
+ await client.send(new clientSecretsManager.RestoreSecretCommand({
1326
+ SecretId: fullName,
1327
+ }));
1328
+ }
1329
+ const updateResult = await client.send(new clientSecretsManager.PutSecretValueCommand({
1330
+ SecretId: fullName,
1331
+ SecretString: secretValue,
1332
+ }));
1333
+ if (updateResult.VersionId == null) {
1334
+ throw new Error("Expected versionId");
1335
+ }
1336
+ await this.syncTags(client, describeSecret, tags);
1337
+ arn = updateResult.ARN;
1338
+ version = updateResult.VersionId;
1339
+ }
1340
+ this.reporter.log("");
1341
+ this.reporter.log("Secret stored:");
1342
+ this.reporter.log(`ARN: ${this.reporter.format.greenBright(arn)}`);
1343
+ this.reporter.log(`Version: ${this.reporter.format.greenBright(version)}`);
1344
+ }
1345
+ checkSecretGroup(secretGroup) {
1346
+ if (!secretGroup.namePrefix.startsWith("/") ||
1347
+ !secretGroup.namePrefix.endsWith("/")) {
1348
+ throw new Error(`namePrefix should start and end with /. Current value: ${secretGroup.namePrefix}`);
1349
+ }
1350
+ }
1351
+ getSecretDescription(details) {
1352
+ var _a, _b;
1353
+ return details == null
1354
+ ? "not yet created"
1355
+ : (details === null || details === void 0 ? void 0 : details.DeletedDate) != null
1356
+ ? `scheduled for deletion ${details.DeletedDate.toISOString()}`
1357
+ : `last changed ${(_b = (_a = details.LastChangedDate) === null || _a === void 0 ? void 0 : _a.toISOString()) !== null && _b !== void 0 ? _b : "unknown"}`;
1358
+ }
1359
+ /**
1360
+ * Returns false if aborted.
1361
+ */
1362
+ async selectAndUpdate(secretGroups) {
1363
+ const secrets = [];
1364
+ this.reporter.log("Select secret to write:");
1365
+ this.reporter.log("");
1366
+ for (const secretGroup of secretGroups) {
1367
+ this.reporter.log(`${secretGroup.description} (prefix: ${secretGroup.namePrefix})`);
1368
+ for (let i = 0; i < secretGroup.secrets.length; i++) {
1369
+ const offset = secrets.length;
1370
+ const secret = secretGroup.secrets[i];
1371
+ secrets.push({
1372
+ secret: secret,
1373
+ secretGroup,
1374
+ });
1375
+ const client = this.getSmClient(secretGroup.region);
1376
+ const details = await this.getSecretDetails(client, this.getFullName(secretGroup, secret));
1377
+ const desc = this.getSecretDescription(details);
1378
+ this.reporter.log(` (${offset}) ${secret.name} (${desc})`);
1379
+ }
1380
+ this.reporter.log("");
1381
+ }
1382
+ let index;
1383
+ try {
1384
+ const answer = await this.getInput({
1385
+ prompt: "Enter index (or enter to quit): ",
1386
+ });
1387
+ if (answer.trim() === "") {
1388
+ return false;
1389
+ }
1390
+ index = parseInt(answer);
1391
+ if (!secrets[index]) {
1392
+ throw new Error();
1393
+ }
1394
+ }
1395
+ catch (e) {
1396
+ this.reporter.warn("Secret not found - aborting");
1397
+ return false;
1398
+ }
1399
+ this.reporter.log("");
1400
+ await this.handleUpdate(secrets[index].secretGroup, secrets[index].secret);
1401
+ this.reporter.log("");
1402
+ return true;
1403
+ }
1404
+ async process(secretGroups) {
1405
+ this.reporter.info("Checking account for current credentials");
1406
+ this.reporter.info("If any error is given, make sure you have valid credentials active");
1407
+ const currentAccount = await this.stsClient.send(new clientSts.GetCallerIdentityCommand({}));
1408
+ this.reporter.info(`Running for account ${currentAccount.Account}`);
1409
+ this.reporter.log("");
1410
+ const matchedSecretGroups = secretGroups.filter((it) => it.accountId === currentAccount.Account);
1411
+ if (matchedSecretGroups.length === 0) {
1412
+ this.reporter.error(`No secrets specified for this account - aborting`);
1413
+ return;
1414
+ }
1415
+ for (const secretGroup of matchedSecretGroups) {
1416
+ this.checkSecretGroup(secretGroup);
1417
+ }
1418
+ while (await this.selectAndUpdate(matchedSecretGroups)) { }
1419
+ }
1420
+ }
1421
+ /**
1422
+ * Load secrets interactively into Secrets Manager.
1423
+ */
1424
+ function loadSecretsCli(props) {
1425
+ const loadSecrets = new LoadSecrets({
1426
+ reporter: createReporter({}),
1427
+ // For now we show the secrets, so that we get positive feedback that the value
1428
+ // is correctly entered.
1429
+ silent: false,
1430
+ });
1431
+ loadSecrets.process(props.secretGroups).catch((error) => {
1432
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1433
+ console.error(error.stack || error.message || error);
1434
+ process.exitCode = 1;
1435
+ });
1436
1436
  }
1437
1437
 
1438
1438
  var index$2 = /*#__PURE__*/Object.freeze({
@@ -1440,105 +1440,105 @@ var index$2 = /*#__PURE__*/Object.freeze({
1440
1440
  loadSecretsCli: loadSecretsCli
1441
1441
  });
1442
1442
 
1443
- class SnykTokenCliProvider {
1444
- constructor() {
1445
- this.keyringService = "cals";
1446
- this.keyringAccount = "snyk-token";
1447
- }
1448
- async getToken() {
1449
- if (process.env.CALS_SNYK_TOKEN) {
1450
- return process.env.CALS_SNYK_TOKEN;
1451
- }
1452
- const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
1453
- if (result == null) {
1454
- process.stderr.write("No token found. Register using `cals snyk set-token`\n");
1455
- return undefined;
1456
- }
1457
- return result;
1458
- }
1459
- async markInvalid() {
1460
- await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
1461
- }
1462
- async setToken(value) {
1463
- await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
1464
- }
1443
+ class SnykTokenCliProvider {
1444
+ constructor() {
1445
+ this.keyringService = "cals";
1446
+ this.keyringAccount = "snyk-token";
1447
+ }
1448
+ async getToken() {
1449
+ if (process.env.CALS_SNYK_TOKEN) {
1450
+ return process.env.CALS_SNYK_TOKEN;
1451
+ }
1452
+ const result = await keytar__default["default"].getPassword(this.keyringService, this.keyringAccount);
1453
+ if (result == null) {
1454
+ process.stderr.write("No token found. Register using `cals snyk set-token`\n");
1455
+ return undefined;
1456
+ }
1457
+ return result;
1458
+ }
1459
+ async markInvalid() {
1460
+ await keytar__default["default"].deletePassword(this.keyringService, this.keyringAccount);
1461
+ }
1462
+ async setToken(value) {
1463
+ await keytar__default["default"].setPassword(this.keyringService, this.keyringAccount, value);
1464
+ }
1465
1465
  }
1466
1466
 
1467
- class SnykService {
1468
- constructor(props) {
1469
- this.config = props.config;
1470
- this.tokenProvider = props.tokenProvider;
1471
- }
1472
- async getProjects(definition) {
1473
- var _a;
1474
- const snykAccountId = (_a = definition.snyk) === null || _a === void 0 ? void 0 : _a.accountId;
1475
- if (snykAccountId === undefined) {
1476
- return [];
1477
- }
1478
- return this.getProjectsByAccountId(snykAccountId);
1479
- }
1480
- async getProjectsByAccountId(snykAccountId) {
1481
- const token = await this.tokenProvider.getToken();
1482
- if (token === undefined) {
1483
- throw new Error("Missing token for Snyk");
1484
- }
1485
- const response = await fetch__default["default"](`https://snyk.io/api/v1/org/${encodeURIComponent(snykAccountId)}/projects`, {
1486
- method: "GET",
1487
- headers: {
1488
- Accept: "application/json",
1489
- Authorization: `token ${token}`,
1490
- },
1491
- agent: this.config.agent,
1492
- });
1493
- if (response.status === 401) {
1494
- process.stderr.write("Unauthorized - removing token\n");
1495
- await this.tokenProvider.markInvalid();
1496
- }
1497
- if (!response.ok) {
1498
- throw new Error(`Response from Snyk not OK (${response.status}): ${JSON.stringify(response)}`);
1499
- }
1500
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1501
- return (await response.json()).projects;
1502
- }
1503
- }
1504
- function createSnykService(props) {
1505
- var _a;
1506
- return new SnykService({
1507
- config: props.config,
1508
- tokenProvider: (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new SnykTokenCliProvider(),
1509
- });
1467
+ class SnykService {
1468
+ constructor(props) {
1469
+ this.config = props.config;
1470
+ this.tokenProvider = props.tokenProvider;
1471
+ }
1472
+ async getProjects(definition) {
1473
+ var _a;
1474
+ const snykAccountId = (_a = definition.snyk) === null || _a === void 0 ? void 0 : _a.accountId;
1475
+ if (snykAccountId === undefined) {
1476
+ return [];
1477
+ }
1478
+ return this.getProjectsByAccountId(snykAccountId);
1479
+ }
1480
+ async getProjectsByAccountId(snykAccountId) {
1481
+ const token = await this.tokenProvider.getToken();
1482
+ if (token === undefined) {
1483
+ throw new Error("Missing token for Snyk");
1484
+ }
1485
+ const response = await fetch__default["default"](`https://snyk.io/api/v1/org/${encodeURIComponent(snykAccountId)}/projects`, {
1486
+ method: "GET",
1487
+ headers: {
1488
+ Accept: "application/json",
1489
+ Authorization: `token ${token}`,
1490
+ },
1491
+ agent: this.config.agent,
1492
+ });
1493
+ if (response.status === 401) {
1494
+ process.stderr.write("Unauthorized - removing token\n");
1495
+ await this.tokenProvider.markInvalid();
1496
+ }
1497
+ if (!response.ok) {
1498
+ throw new Error(`Response from Snyk not OK (${response.status}): ${JSON.stringify(response)}`);
1499
+ }
1500
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1501
+ return (await response.json()).projects;
1502
+ }
1503
+ }
1504
+ function createSnykService(props) {
1505
+ var _a;
1506
+ return new SnykService({
1507
+ config: props.config,
1508
+ tokenProvider: (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new SnykTokenCliProvider(),
1509
+ });
1510
1510
  }
1511
1511
 
1512
- function getGitHubRepo(snykProject) {
1513
- if (snykProject.origin === "github") {
1514
- const match = /^([^/]+)\/([^:]+)(:(.+))?$/.exec(snykProject.name);
1515
- if (match === null) {
1516
- throw Error(`Could not extract components from Snyk project name: ${snykProject.name} (id: ${snykProject.id})`);
1517
- }
1518
- return {
1519
- owner: match[1],
1520
- name: match[2],
1521
- };
1522
- }
1523
- else if (snykProject.origin === "cli" &&
1524
- snykProject.remoteRepoUrl != null) {
1525
- // The remoteRepoUrl can be overriden when using the CLI, so don't
1526
- // fail if we cannot extract the value.
1527
- const match = /github.com\/([^/]+)\/(.+)\.git$/.exec(snykProject.remoteRepoUrl);
1528
- if (match === null) {
1529
- return undefined;
1530
- }
1531
- return {
1532
- owner: match[1],
1533
- name: match[2],
1534
- };
1535
- }
1536
- else {
1537
- return undefined;
1538
- }
1539
- }
1540
- function getGitHubRepoId(repo) {
1541
- return repo ? `${repo.owner}/${repo.name}` : undefined;
1512
+ function getGitHubRepo(snykProject) {
1513
+ if (snykProject.origin === "github") {
1514
+ const match = /^([^/]+)\/([^:]+)(:(.+))?$/.exec(snykProject.name);
1515
+ if (match === null) {
1516
+ throw Error(`Could not extract components from Snyk project name: ${snykProject.name} (id: ${snykProject.id})`);
1517
+ }
1518
+ return {
1519
+ owner: match[1],
1520
+ name: match[2],
1521
+ };
1522
+ }
1523
+ else if (snykProject.origin === "cli" &&
1524
+ snykProject.remoteRepoUrl != null) {
1525
+ // The remoteRepoUrl can be overriden when using the CLI, so don't
1526
+ // fail if we cannot extract the value.
1527
+ const match = /github.com\/([^/]+)\/(.+)\.git$/.exec(snykProject.remoteRepoUrl);
1528
+ if (match === null) {
1529
+ return undefined;
1530
+ }
1531
+ return {
1532
+ owner: match[1],
1533
+ name: match[2],
1534
+ };
1535
+ }
1536
+ else {
1537
+ return undefined;
1538
+ }
1539
+ }
1540
+ function getGitHubRepoId(repo) {
1541
+ return repo ? `${repo.owner}/${repo.name}` : undefined;
1542
1542
  }
1543
1543
 
1544
1544
  var index$1 = /*#__PURE__*/Object.freeze({
@@ -1549,61 +1549,61 @@ var index$1 = /*#__PURE__*/Object.freeze({
1549
1549
  getGitHubRepoId: getGitHubRepoId
1550
1550
  });
1551
1551
 
1552
- class SonarCloudTokenCliProvider {
1553
- async getToken() {
1554
- if (process.env.CALS_SONARCLOUD_TOKEN) {
1555
- return Promise.resolve(process.env.CALS_SONARCLOUD_TOKEN);
1556
- }
1557
- process.stderr.write("No environmental variable found. Set variable `CALS_SONARCLOUD_TOKEN` to token value\n");
1558
- return undefined;
1559
- }
1560
- async markInvalid() {
1561
- await Promise.resolve();
1562
- }
1552
+ class SonarCloudTokenCliProvider {
1553
+ async getToken() {
1554
+ if (process.env.CALS_SONARCLOUD_TOKEN) {
1555
+ return Promise.resolve(process.env.CALS_SONARCLOUD_TOKEN);
1556
+ }
1557
+ process.stderr.write("No environmental variable found. Set variable `CALS_SONARCLOUD_TOKEN` to token value\n");
1558
+ return undefined;
1559
+ }
1560
+ async markInvalid() {
1561
+ await Promise.resolve();
1562
+ }
1563
1563
  }
1564
1564
 
1565
- class SonarCloudService {
1566
- constructor(props) {
1567
- this.config = props.config;
1568
- this.tokenProvider = props.tokenProvider;
1569
- }
1570
- /**
1571
- * Returns metrics for project with given key.
1572
- * ONLY test coverage metrics are returned as of now
1573
- */
1574
- async getMetricsByProjectKey(sonarCloudProjectKey) {
1575
- const token = await this.tokenProvider.getToken();
1576
- if (token === undefined) {
1577
- throw new Error("Missing token for SonarCloud");
1578
- }
1579
- const response = await fetch__default["default"](`https://sonarcloud.io/api/measures/component?component=${encodeURIComponent(sonarCloudProjectKey)}&metricKeys=coverage`, {
1580
- method: "GET",
1581
- headers: {
1582
- Accept: "application/json",
1583
- Authorization: `Basic ${Buffer.from(token.concat(":"), "utf8").toString("base64")}`,
1584
- },
1585
- agent: this.config.agent,
1586
- });
1587
- if (response.status === 401) {
1588
- process.stderr.write("Unauthorized - removing token\n");
1589
- await this.tokenProvider.markInvalid();
1590
- }
1591
- if (response.status === 404) {
1592
- process.stderr.write("Project does not exist in SonarCloud\n");
1593
- return undefined;
1594
- }
1595
- if (!response.ok) {
1596
- throw new Error(`Response from SonarCloud not OK (${response.status}): ${JSON.stringify(response)}`);
1597
- }
1598
- return (await response.json());
1599
- }
1600
- }
1601
- function createSonarCloudService(props) {
1602
- var _a;
1603
- return new SonarCloudService({
1604
- config: props.config,
1605
- tokenProvider: (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new SonarCloudTokenCliProvider(),
1606
- });
1565
+ class SonarCloudService {
1566
+ constructor(props) {
1567
+ this.config = props.config;
1568
+ this.tokenProvider = props.tokenProvider;
1569
+ }
1570
+ /**
1571
+ * Returns metrics for project with given key.
1572
+ * ONLY test coverage metrics are returned as of now
1573
+ */
1574
+ async getMetricsByProjectKey(sonarCloudProjectKey) {
1575
+ const token = await this.tokenProvider.getToken();
1576
+ if (token === undefined) {
1577
+ throw new Error("Missing token for SonarCloud");
1578
+ }
1579
+ const response = await fetch__default["default"](`https://sonarcloud.io/api/measures/component?component=${encodeURIComponent(sonarCloudProjectKey)}&metricKeys=coverage`, {
1580
+ method: "GET",
1581
+ headers: {
1582
+ Accept: "application/json",
1583
+ Authorization: `Basic ${Buffer.from(token.concat(":"), "utf8").toString("base64")}`,
1584
+ },
1585
+ agent: this.config.agent,
1586
+ });
1587
+ if (response.status === 401) {
1588
+ process.stderr.write("Unauthorized - removing token\n");
1589
+ await this.tokenProvider.markInvalid();
1590
+ }
1591
+ if (response.status === 404) {
1592
+ process.stderr.write("Project does not exist in SonarCloud\n");
1593
+ return undefined;
1594
+ }
1595
+ if (!response.ok) {
1596
+ throw new Error(`Response from SonarCloud not OK (${response.status}): ${JSON.stringify(response)}`);
1597
+ }
1598
+ return (await response.json());
1599
+ }
1600
+ }
1601
+ function createSonarCloudService(props) {
1602
+ var _a;
1603
+ return new SonarCloudService({
1604
+ config: props.config,
1605
+ tokenProvider: (_a = props.tokenProvider) !== null && _a !== void 0 ? _a : new SonarCloudTokenCliProvider(),
1606
+ });
1607
1607
  }
1608
1608
 
1609
1609
  var index = /*#__PURE__*/Object.freeze({
@@ -1613,455 +1613,455 @@ var index = /*#__PURE__*/Object.freeze({
1613
1613
  SonarCloudTokenCliProvider: SonarCloudTokenCliProvider
1614
1614
  });
1615
1615
 
1616
- class TestExecutor {
1617
- constructor() {
1618
- this.shutdown = false;
1619
- this.cleanupTask = null;
1620
- this.usingWithCleanupTasks = false;
1621
- this.tasks = [];
1622
- }
1623
- /**
1624
- * Check if we are currently in shutdown state due to user
1625
- * asking to abort (Ctrl+C).
1626
- */
1627
- checkCanContinue() {
1628
- if (this.shutdown) {
1629
- throw new Error("In shutdown mode - aborting");
1630
- }
1631
- }
1632
- async runTasks() {
1633
- console.warn("Running cleanup tasks");
1634
- while (true) {
1635
- // We must run tasks in reverse order due to dependencies.
1636
- // E.g. we cannot delete a Docker network before deleting
1637
- // the container using it.
1638
- const task = this.tasks.pop();
1639
- if (task === undefined) {
1640
- return;
1641
- }
1642
- try {
1643
- await task();
1644
- }
1645
- catch (error) {
1646
- console.error(error);
1647
- }
1648
- }
1649
- }
1650
- /**
1651
- * Register a task that will be run during cleanup phase.
1652
- */
1653
- registerCleanupTask(task) {
1654
- if (!this.usingWithCleanupTasks) {
1655
- throw new Error("registerCleanupTask run outside runWithCleanupTasks");
1656
- }
1657
- this.tasks.push(task);
1658
- this.checkCanContinue();
1659
- }
1660
- /**
1661
- * Run the code block while ensuring we can run cleanup tasks
1662
- * after the execution or if the process is interrupted.
1663
- *
1664
- * The main method of the program should be executed by using
1665
- * this method.
1666
- */
1667
- async runWithCleanupTasks(body) {
1668
- try {
1669
- assert.strict.strictEqual(this.usingWithCleanupTasks, false);
1670
- this.usingWithCleanupTasks = true;
1671
- // We capture Ctrl+C so that we can perform cleanup task,
1672
- // since the cleanup tasks involve async code which is not
1673
- // supported during NodeJS normal exit handling.
1674
- //
1675
- // This will not abort the running tasks until after
1676
- // we have completed the cleanup tasks. The running tasks
1677
- // can stop earlier by calling checkCanContinue.
1678
- process.on("SIGINT", () => {
1679
- console.warn("Caught interrupt signal - forcing termination");
1680
- if (this.cleanupTask != null) {
1681
- return;
1682
- }
1683
- this.shutdown = true;
1684
- this.cleanupTask = this.runTasks().then(() => {
1685
- process.exit(1);
1686
- });
1687
- });
1688
- await body(this);
1689
- }
1690
- catch (error) {
1691
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1692
- console.error(error.stack || error.message || error);
1693
- process.exitCode = 1;
1694
- }
1695
- finally {
1696
- console.log(`Reached finally block`);
1697
- this.usingWithCleanupTasks = false;
1698
- if (this.cleanupTask == null) {
1699
- this.cleanupTask = this.runTasks();
1700
- }
1701
- await this.cleanupTask;
1702
- }
1703
- }
1704
- }
1705
- function createTestExecutor() {
1706
- return new TestExecutor();
1616
+ class TestExecutor {
1617
+ constructor() {
1618
+ this.shutdown = false;
1619
+ this.cleanupTask = null;
1620
+ this.usingWithCleanupTasks = false;
1621
+ this.tasks = [];
1622
+ }
1623
+ /**
1624
+ * Check if we are currently in shutdown state due to user
1625
+ * asking to abort (Ctrl+C).
1626
+ */
1627
+ checkCanContinue() {
1628
+ if (this.shutdown) {
1629
+ throw new Error("In shutdown mode - aborting");
1630
+ }
1631
+ }
1632
+ async runTasks() {
1633
+ console.warn("Running cleanup tasks");
1634
+ while (true) {
1635
+ // We must run tasks in reverse order due to dependencies.
1636
+ // E.g. we cannot delete a Docker network before deleting
1637
+ // the container using it.
1638
+ const task = this.tasks.pop();
1639
+ if (task === undefined) {
1640
+ return;
1641
+ }
1642
+ try {
1643
+ await task();
1644
+ }
1645
+ catch (error) {
1646
+ console.error(error);
1647
+ }
1648
+ }
1649
+ }
1650
+ /**
1651
+ * Register a task that will be run during cleanup phase.
1652
+ */
1653
+ registerCleanupTask(task) {
1654
+ if (!this.usingWithCleanupTasks) {
1655
+ throw new Error("registerCleanupTask run outside runWithCleanupTasks");
1656
+ }
1657
+ this.tasks.push(task);
1658
+ this.checkCanContinue();
1659
+ }
1660
+ /**
1661
+ * Run the code block while ensuring we can run cleanup tasks
1662
+ * after the execution or if the process is interrupted.
1663
+ *
1664
+ * The main method of the program should be executed by using
1665
+ * this method.
1666
+ */
1667
+ async runWithCleanupTasks(body) {
1668
+ try {
1669
+ assert.strict.strictEqual(this.usingWithCleanupTasks, false);
1670
+ this.usingWithCleanupTasks = true;
1671
+ // We capture Ctrl+C so that we can perform cleanup task,
1672
+ // since the cleanup tasks involve async code which is not
1673
+ // supported during NodeJS normal exit handling.
1674
+ //
1675
+ // This will not abort the running tasks until after
1676
+ // we have completed the cleanup tasks. The running tasks
1677
+ // can stop earlier by calling checkCanContinue.
1678
+ process.on("SIGINT", () => {
1679
+ console.warn("Caught interrupt signal - forcing termination");
1680
+ if (this.cleanupTask != null) {
1681
+ return;
1682
+ }
1683
+ this.shutdown = true;
1684
+ this.cleanupTask = this.runTasks().then(() => {
1685
+ process.exit(1);
1686
+ });
1687
+ });
1688
+ await body(this);
1689
+ }
1690
+ catch (error) {
1691
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
1692
+ console.error(error.stack || error.message || error);
1693
+ process.exitCode = 1;
1694
+ }
1695
+ finally {
1696
+ console.log(`Reached finally block`);
1697
+ this.usingWithCleanupTasks = false;
1698
+ if (this.cleanupTask == null) {
1699
+ this.cleanupTask = this.runTasks();
1700
+ }
1701
+ await this.cleanupTask;
1702
+ }
1703
+ }
1704
+ }
1705
+ function createTestExecutor() {
1706
+ return new TestExecutor();
1707
1707
  }
1708
1708
 
1709
- /**
1710
- * Generate a value that can be used as part of resource names.
1711
- *
1712
- * Gives a value formatted as "yyyymmdd-xxxxxx", e.g. "20200523-3f2c87".
1713
- */
1714
- function generateRunId() {
1715
- const low = parseInt("100000", 16);
1716
- const high = parseInt("ffffff", 16);
1717
- const range = high - low + 1;
1718
- const now = new Date();
1719
- return [
1720
- now.getUTCFullYear(),
1721
- (now.getUTCMonth() + 1).toString().padStart(2, "0"),
1722
- now.getUTCDate().toString().padStart(2, "0"),
1723
- "-",
1724
- (Math.floor(Math.random() * range) + low).toString(16),
1725
- ].join("");
1726
- }
1727
- /**
1728
- * Generate a name that can be used for a resource.
1729
- */
1730
- function generateName(extra) {
1731
- const s = extra === undefined ? "" : `-${extra}`;
1732
- return `autotest-${generateRunId()}${s}`;
1733
- }
1734
- /**
1735
- * Create a new Docker network.
1736
- */
1737
- async function createNetwork(executor) {
1738
- executor.checkCanContinue();
1739
- const networkName = generateName();
1740
- await execa__default["default"]("docker", ["network", "create", networkName]);
1741
- const lsRes = await execa__default["default"]("docker", [
1742
- "network",
1743
- "ls",
1744
- "-q",
1745
- "-f",
1746
- `name=${networkName}`,
1747
- ]);
1748
- const networkId = lsRes.stdout.trim();
1749
- console.log(`Network ${networkName} (${networkId}) created`);
1750
- executor.registerCleanupTask(async () => {
1751
- await execa__default["default"]("docker", ["network", "rm", networkId]);
1752
- console.log(`Network ${networkName} (${networkId}) deleted`);
1753
- });
1754
- return {
1755
- id: networkId,
1756
- };
1757
- }
1758
- /**
1759
- * Execute curl within the Docker network.
1760
- */
1761
- async function curl(executor, network, ...args) {
1762
- executor.checkCanContinue();
1763
- const result = await execa__default["default"]("docker", [
1764
- "run",
1765
- "-i",
1766
- "--rm",
1767
- "--network",
1768
- network.id,
1769
- "byrnedo/alpine-curl",
1770
- ...args,
1771
- ]);
1772
- return result.stdout;
1773
- }
1774
- /**
1775
- * Repeatedly check for a condition until timeout.
1776
- *
1777
- * The condition can throw an error without aborting the loop.
1778
- * To abort the condition must return false.
1779
- */
1780
- async function pollForCondition({ container, attempts, waitIntervalSec, condition, }) {
1781
- function log(value) {
1782
- console.log(`${container.name} (poll): ${value}`);
1783
- }
1784
- container.executor.checkCanContinue();
1785
- log(`Waiting for condition.. Checking ${attempts} times by ${waitIntervalSec} sec`);
1786
- const start = perf_hooks.performance.now();
1787
- const duration = () => {
1788
- const end = perf_hooks.performance.now();
1789
- return Math.round((end - start) / 1000);
1790
- };
1791
- for (let i = 0; i < attempts; i++) {
1792
- container.executor.checkCanContinue();
1793
- if (!(await isRunning(container.executor, container))) {
1794
- throw new Error(`Container ${container.name} not running as expected`);
1795
- }
1796
- try {
1797
- const result = await condition();
1798
- if (!result) {
1799
- break;
1800
- }
1801
- log(`Took ${duration()} seconds for condition`);
1802
- return;
1803
- }
1804
- catch (e) {
1805
- log("Still waiting...");
1806
- await new Promise((resolve) => setTimeout(resolve, waitIntervalSec * 1000));
1807
- }
1808
- }
1809
- throw new Error(`Failed to wait for container ${container.name}`);
1810
- }
1811
- async function waitForHttpOk({ container, url, attempts = 30, waitIntervalSec = 1, }) {
1812
- await pollForCondition({
1813
- container,
1814
- attempts,
1815
- waitIntervalSec,
1816
- condition: async () => {
1817
- await curl(container.executor, container.network, "-fsS", url);
1818
- return true;
1819
- },
1820
- });
1821
- }
1822
- async function waitForPostgresAvailable({ container, attempts = 30, waitIntervalSec = 1, username = "user", password = "password", dbname, }) {
1823
- await pollForCondition({
1824
- container,
1825
- attempts,
1826
- waitIntervalSec,
1827
- condition: async () => {
1828
- await execa__default["default"]("docker", [
1829
- "exec",
1830
- "-e",
1831
- `PGPASSWORD=${password}`,
1832
- container.name,
1833
- "psql",
1834
- "-h",
1835
- "localhost",
1836
- "-U",
1837
- username,
1838
- "-c",
1839
- "select 1",
1840
- dbname,
1841
- ]);
1842
- return true;
1843
- },
1844
- });
1845
- }
1846
- async function isRunning(executor, container) {
1847
- executor.checkCanContinue();
1848
- try {
1849
- await execa__default["default"]("docker", ["inspect", container.name]);
1850
- return true;
1851
- }
1852
- catch (e) {
1853
- return false;
1854
- }
1855
- }
1856
- /**
1857
- * A stream transform that injects a prefix into every line
1858
- * and also forces every chunk to end with a newline so that
1859
- * it can be interleaved with other output.
1860
- */
1861
- class OutputPrefixTransform extends stream.Transform {
1862
- constructor(prefix) {
1863
- super({
1864
- objectMode: true,
1865
- transform: (chunk, encoding, callback) => {
1866
- let result = chunk.toString(encoding);
1867
- if (result.endsWith("\n")) {
1868
- result = result.slice(0, -1);
1869
- }
1870
- // Some loggers emit newline then ANSI reset code causing
1871
- // blank lines if we do not remove the newline.
1872
- // TODO: Consider removing all ANSI escape codes as it causes
1873
- // some confusing output when interleaved.
1874
- if (result.endsWith("\n\u001B[0m")) {
1875
- result = result.slice(0, -5) + result.slice(-4);
1876
- }
1877
- result = prefix + result.replace(/\n/g, `\n${prefix}`) + "\n";
1878
- callback(null, result);
1879
- },
1880
- });
1881
- }
1882
- }
1883
- function pipeToConsole(result, name) {
1884
- var _a, _b;
1885
- (_a = result.stdout) === null || _a === void 0 ? void 0 : _a.pipe(new OutputPrefixTransform(`${name}: `)).pipe(process.stdout);
1886
- (_b = result.stderr) === null || _b === void 0 ? void 0 : _b.pipe(new OutputPrefixTransform(`${name} (stderr): `)).pipe(process.stderr);
1887
- }
1888
- function checkPidRunning(pid) {
1889
- try {
1890
- process.kill(pid, 0);
1891
- return true;
1892
- }
1893
- catch (e) {
1894
- return false;
1895
- }
1896
- }
1897
- async function getContainerId({ executor, name, hasFailed, pid, }) {
1898
- function log(value) {
1899
- console.log(`${name} (get-container-id): ${value}`);
1900
- }
1901
- async function check() {
1902
- let result;
1903
- try {
1904
- result = (await execa__default["default"]("docker", ["inspect", name, "-f", "{{.Id}}"]))
1905
- .stdout;
1906
- }
1907
- catch (e) {
1908
- result = null;
1909
- }
1910
- // Debugging to help us solve CALS-366.
1911
- const ps = execa__default["default"]("docker", ["ps"]);
1912
- pipeToConsole(ps, `${name} (ps)`);
1913
- await ps;
1914
- // Debugging to help us solve CALS-366.
1915
- if (!checkPidRunning(pid)) {
1916
- log("Process not running");
1917
- }
1918
- return result;
1919
- }
1920
- // If the container is not running, retry a few times to cover
1921
- // the initial starting where we might check before the container
1922
- // is running.
1923
- // Increased from 25 to 100 to see if it helps for solving CALS-366.
1924
- for (let i = 0; i < 100; i++) {
1925
- if (i > 0) {
1926
- // Delay a bit before checking again.
1927
- log("Retrying in a bit...");
1928
- await new Promise((resolve) => setTimeout(resolve, 200));
1929
- }
1930
- executor.checkCanContinue();
1931
- if (hasFailed()) {
1932
- break;
1933
- }
1934
- const id = await check();
1935
- if (id !== null) {
1936
- log(`Resolved to ${id}`);
1937
- return id;
1938
- }
1939
- }
1940
- throw new Error(`Could not find ID for container with name ${name}`);
1941
- }
1942
- async function pullImage({ imageId }) {
1943
- console.log(`Pulling ${imageId}`);
1944
- const process = execa__default["default"]("docker", ["pull", imageId]);
1945
- pipeToConsole(process, `pull-image (${imageId})`);
1946
- await process;
1947
- }
1948
- async function checkImageExistsLocally({ imageId, }) {
1949
- const result = await execa__default["default"]("docker", ["images", "-q", imageId]);
1950
- const found = result.stdout != "";
1951
- console.log(`image ${imageId} ${found ? "was present locally" : "was not found locally"}`);
1952
- return found;
1953
- }
1954
- async function startContainer({ executor, network, imageId, alias, env, dockerArgs = [], pull = false, }) {
1955
- executor.checkCanContinue();
1956
- const containerName = generateName(alias);
1957
- // Prefer pulling image here so that the call on getContainerId
1958
- // will not time out due to pulling the image.
1959
- // If pull is false, we will still fallback to pulling if we cannot
1960
- // find the image locally.
1961
- if (pull || !(await checkImageExistsLocally({ imageId }))) {
1962
- await pullImage({
1963
- imageId,
1964
- });
1965
- }
1966
- const args = [
1967
- "run",
1968
- "--rm",
1969
- "--network",
1970
- network.id,
1971
- "--name",
1972
- containerName,
1973
- ...dockerArgs,
1974
- ];
1975
- if (alias != null) {
1976
- args.push(`--network-alias=${alias}`);
1977
- }
1978
- if (env != null) {
1979
- for (const [key, value] of Object.entries(env)) {
1980
- args.push("-e", `${key}=${value}`);
1981
- }
1982
- }
1983
- args.push(imageId);
1984
- console.log(`Starting ${imageId}`);
1985
- const process = execa__default["default"]("docker", args);
1986
- pipeToConsole(process, alias !== null && alias !== void 0 ? alias : containerName);
1987
- let failed = false;
1988
- process.catch(() => {
1989
- failed = true;
1990
- });
1991
- if (!process.pid) {
1992
- throw new Error("No process identifier (PID) was returned for the process that was started when running trying to run Docker container");
1993
- }
1994
- const id = await getContainerId({
1995
- executor,
1996
- name: containerName,
1997
- hasFailed: () => failed,
1998
- pid: process.pid,
1999
- });
2000
- executor.registerCleanupTask(async () => {
2001
- console.log(`Stopping container ${containerName}`);
2002
- const r = execa__default["default"]("docker", ["stop", containerName]);
2003
- pipeToConsole(r, (alias !== null && alias !== void 0 ? alias : containerName) + " (stop)");
2004
- try {
2005
- await r;
2006
- }
2007
- catch (e) {
2008
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
2009
- if (!(e.stderr || "").includes("No such container")) {
2010
- throw e;
2011
- }
2012
- }
2013
- });
2014
- return {
2015
- id,
2016
- name: containerName,
2017
- network,
2018
- process,
2019
- executor,
2020
- };
2021
- }
2022
- async function runNpmRunScript(name, options) {
2023
- const result = execa__default["default"]("npm", ["run", name], {
2024
- env: options === null || options === void 0 ? void 0 : options.env,
2025
- });
2026
- pipeToConsole(result, `npm run ${name}`);
2027
- await result;
2028
- }
2029
- /**
2030
- * This likely does not cover all situations.
2031
- */
2032
- async function getDockerHostAddress() {
2033
- if (process.platform === "darwin" || process.platform === "win32") {
2034
- return "host.docker.internal";
2035
- }
2036
- if (fs__default["default"].existsSync("/.dockerenv")) {
2037
- const process = execa__default["default"]("ip", ["route"]);
2038
- pipeToConsole(process, "ip route");
2039
- const res = await process;
2040
- try {
2041
- return (res.stdout
2042
- .split("\n")
2043
- .filter((it) => it.includes("default via"))
2044
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2045
- .map((it) => /default via ([\d\.]+) /.exec(it)[1])[0]);
2046
- }
2047
- catch (e) {
2048
- throw new Error("Failed to extract docker host address");
2049
- }
2050
- }
2051
- return "localhost";
2052
- }
2053
- async function waitForEnterToContinue(prompt = "Press enter to continue") {
2054
- return new Promise((resolve, reject) => {
2055
- read__default["default"]({
2056
- prompt,
2057
- silent: true,
2058
- }, (err) => {
2059
- if (err) {
2060
- reject(err);
2061
- }
2062
- resolve();
2063
- });
2064
- });
1709
+ /**
1710
+ * Generate a value that can be used as part of resource names.
1711
+ *
1712
+ * Gives a value formatted as "yyyymmdd-xxxxxx", e.g. "20200523-3f2c87".
1713
+ */
1714
+ function generateRunId() {
1715
+ const low = parseInt("100000", 16);
1716
+ const high = parseInt("ffffff", 16);
1717
+ const range = high - low + 1;
1718
+ const now = new Date();
1719
+ return [
1720
+ now.getUTCFullYear(),
1721
+ (now.getUTCMonth() + 1).toString().padStart(2, "0"),
1722
+ now.getUTCDate().toString().padStart(2, "0"),
1723
+ "-",
1724
+ (Math.floor(Math.random() * range) + low).toString(16),
1725
+ ].join("");
1726
+ }
1727
+ /**
1728
+ * Generate a name that can be used for a resource.
1729
+ */
1730
+ function generateName(extra) {
1731
+ const s = extra === undefined ? "" : `-${extra}`;
1732
+ return `autotest-${generateRunId()}${s}`;
1733
+ }
1734
+ /**
1735
+ * Create a new Docker network.
1736
+ */
1737
+ async function createNetwork(executor) {
1738
+ executor.checkCanContinue();
1739
+ const networkName = generateName();
1740
+ await execa__default["default"]("docker", ["network", "create", networkName]);
1741
+ const lsRes = await execa__default["default"]("docker", [
1742
+ "network",
1743
+ "ls",
1744
+ "-q",
1745
+ "-f",
1746
+ `name=${networkName}`,
1747
+ ]);
1748
+ const networkId = lsRes.stdout.trim();
1749
+ console.log(`Network ${networkName} (${networkId}) created`);
1750
+ executor.registerCleanupTask(async () => {
1751
+ await execa__default["default"]("docker", ["network", "rm", networkId]);
1752
+ console.log(`Network ${networkName} (${networkId}) deleted`);
1753
+ });
1754
+ return {
1755
+ id: networkId,
1756
+ };
1757
+ }
1758
+ /**
1759
+ * Execute curl within the Docker network.
1760
+ */
1761
+ async function curl(executor, network, ...args) {
1762
+ executor.checkCanContinue();
1763
+ const result = await execa__default["default"]("docker", [
1764
+ "run",
1765
+ "-i",
1766
+ "--rm",
1767
+ "--network",
1768
+ network.id,
1769
+ "byrnedo/alpine-curl",
1770
+ ...args,
1771
+ ]);
1772
+ return result.stdout;
1773
+ }
1774
+ /**
1775
+ * Repeatedly check for a condition until timeout.
1776
+ *
1777
+ * The condition can throw an error without aborting the loop.
1778
+ * To abort the condition must return false.
1779
+ */
1780
+ async function pollForCondition({ container, attempts, waitIntervalSec, condition, }) {
1781
+ function log(value) {
1782
+ console.log(`${container.name} (poll): ${value}`);
1783
+ }
1784
+ container.executor.checkCanContinue();
1785
+ log(`Waiting for condition.. Checking ${attempts} times by ${waitIntervalSec} sec`);
1786
+ const start = perf_hooks.performance.now();
1787
+ const duration = () => {
1788
+ const end = perf_hooks.performance.now();
1789
+ return Math.round((end - start) / 1000);
1790
+ };
1791
+ for (let i = 0; i < attempts; i++) {
1792
+ container.executor.checkCanContinue();
1793
+ if (!(await isRunning(container.executor, container))) {
1794
+ throw new Error(`Container ${container.name} not running as expected`);
1795
+ }
1796
+ try {
1797
+ const result = await condition();
1798
+ if (!result) {
1799
+ break;
1800
+ }
1801
+ log(`Took ${duration()} seconds for condition`);
1802
+ return;
1803
+ }
1804
+ catch (e) {
1805
+ log("Still waiting...");
1806
+ await new Promise((resolve) => setTimeout(resolve, waitIntervalSec * 1000));
1807
+ }
1808
+ }
1809
+ throw new Error(`Failed to wait for container ${container.name}`);
1810
+ }
1811
+ async function waitForHttpOk({ container, url, attempts = 30, waitIntervalSec = 1, }) {
1812
+ await pollForCondition({
1813
+ container,
1814
+ attempts,
1815
+ waitIntervalSec,
1816
+ condition: async () => {
1817
+ await curl(container.executor, container.network, "-fsS", url);
1818
+ return true;
1819
+ },
1820
+ });
1821
+ }
1822
+ async function waitForPostgresAvailable({ container, attempts = 30, waitIntervalSec = 1, username = "user", password = "password", dbname, }) {
1823
+ await pollForCondition({
1824
+ container,
1825
+ attempts,
1826
+ waitIntervalSec,
1827
+ condition: async () => {
1828
+ await execa__default["default"]("docker", [
1829
+ "exec",
1830
+ "-e",
1831
+ `PGPASSWORD=${password}`,
1832
+ container.name,
1833
+ "psql",
1834
+ "-h",
1835
+ "localhost",
1836
+ "-U",
1837
+ username,
1838
+ "-c",
1839
+ "select 1",
1840
+ dbname,
1841
+ ]);
1842
+ return true;
1843
+ },
1844
+ });
1845
+ }
1846
+ async function isRunning(executor, container) {
1847
+ executor.checkCanContinue();
1848
+ try {
1849
+ await execa__default["default"]("docker", ["inspect", container.name]);
1850
+ return true;
1851
+ }
1852
+ catch (e) {
1853
+ return false;
1854
+ }
1855
+ }
1856
+ /**
1857
+ * A stream transform that injects a prefix into every line
1858
+ * and also forces every chunk to end with a newline so that
1859
+ * it can be interleaved with other output.
1860
+ */
1861
+ class OutputPrefixTransform extends stream.Transform {
1862
+ constructor(prefix) {
1863
+ super({
1864
+ objectMode: true,
1865
+ transform: (chunk, encoding, callback) => {
1866
+ let result = chunk.toString(encoding);
1867
+ if (result.endsWith("\n")) {
1868
+ result = result.slice(0, -1);
1869
+ }
1870
+ // Some loggers emit newline then ANSI reset code causing
1871
+ // blank lines if we do not remove the newline.
1872
+ // TODO: Consider removing all ANSI escape codes as it causes
1873
+ // some confusing output when interleaved.
1874
+ if (result.endsWith("\n\u001B[0m")) {
1875
+ result = result.slice(0, -5) + result.slice(-4);
1876
+ }
1877
+ result = prefix + result.replace(/\n/g, `\n${prefix}`) + "\n";
1878
+ callback(null, result);
1879
+ },
1880
+ });
1881
+ }
1882
+ }
1883
+ function pipeToConsole(result, name) {
1884
+ var _a, _b;
1885
+ (_a = result.stdout) === null || _a === void 0 ? void 0 : _a.pipe(new OutputPrefixTransform(`${name}: `)).pipe(process.stdout);
1886
+ (_b = result.stderr) === null || _b === void 0 ? void 0 : _b.pipe(new OutputPrefixTransform(`${name} (stderr): `)).pipe(process.stderr);
1887
+ }
1888
+ function checkPidRunning(pid) {
1889
+ try {
1890
+ process.kill(pid, 0);
1891
+ return true;
1892
+ }
1893
+ catch (e) {
1894
+ return false;
1895
+ }
1896
+ }
1897
+ async function getContainerId({ executor, name, hasFailed, pid, }) {
1898
+ function log(value) {
1899
+ console.log(`${name} (get-container-id): ${value}`);
1900
+ }
1901
+ async function check() {
1902
+ let result;
1903
+ try {
1904
+ result = (await execa__default["default"]("docker", ["inspect", name, "-f", "{{.Id}}"]))
1905
+ .stdout;
1906
+ }
1907
+ catch (e) {
1908
+ result = null;
1909
+ }
1910
+ // Debugging to help us solve CALS-366.
1911
+ const ps = execa__default["default"]("docker", ["ps"]);
1912
+ pipeToConsole(ps, `${name} (ps)`);
1913
+ await ps;
1914
+ // Debugging to help us solve CALS-366.
1915
+ if (!checkPidRunning(pid)) {
1916
+ log("Process not running");
1917
+ }
1918
+ return result;
1919
+ }
1920
+ // If the container is not running, retry a few times to cover
1921
+ // the initial starting where we might check before the container
1922
+ // is running.
1923
+ // Increased from 25 to 100 to see if it helps for solving CALS-366.
1924
+ for (let i = 0; i < 100; i++) {
1925
+ if (i > 0) {
1926
+ // Delay a bit before checking again.
1927
+ log("Retrying in a bit...");
1928
+ await new Promise((resolve) => setTimeout(resolve, 200));
1929
+ }
1930
+ executor.checkCanContinue();
1931
+ if (hasFailed()) {
1932
+ break;
1933
+ }
1934
+ const id = await check();
1935
+ if (id !== null) {
1936
+ log(`Resolved to ${id}`);
1937
+ return id;
1938
+ }
1939
+ }
1940
+ throw new Error(`Could not find ID for container with name ${name}`);
1941
+ }
1942
+ async function pullImage({ imageId }) {
1943
+ console.log(`Pulling ${imageId}`);
1944
+ const process = execa__default["default"]("docker", ["pull", imageId]);
1945
+ pipeToConsole(process, `pull-image (${imageId})`);
1946
+ await process;
1947
+ }
1948
+ async function checkImageExistsLocally({ imageId, }) {
1949
+ const result = await execa__default["default"]("docker", ["images", "-q", imageId]);
1950
+ const found = result.stdout != "";
1951
+ console.log(`image ${imageId} ${found ? "was present locally" : "was not found locally"}`);
1952
+ return found;
1953
+ }
1954
+ async function startContainer({ executor, network, imageId, alias, env, dockerArgs = [], pull = false, }) {
1955
+ executor.checkCanContinue();
1956
+ const containerName = generateName(alias);
1957
+ // Prefer pulling image here so that the call on getContainerId
1958
+ // will not time out due to pulling the image.
1959
+ // If pull is false, we will still fallback to pulling if we cannot
1960
+ // find the image locally.
1961
+ if (pull || !(await checkImageExistsLocally({ imageId }))) {
1962
+ await pullImage({
1963
+ imageId,
1964
+ });
1965
+ }
1966
+ const args = [
1967
+ "run",
1968
+ "--rm",
1969
+ "--network",
1970
+ network.id,
1971
+ "--name",
1972
+ containerName,
1973
+ ...dockerArgs,
1974
+ ];
1975
+ if (alias != null) {
1976
+ args.push(`--network-alias=${alias}`);
1977
+ }
1978
+ if (env != null) {
1979
+ for (const [key, value] of Object.entries(env)) {
1980
+ args.push("-e", `${key}=${value}`);
1981
+ }
1982
+ }
1983
+ args.push(imageId);
1984
+ console.log(`Starting ${imageId}`);
1985
+ const process = execa__default["default"]("docker", args);
1986
+ pipeToConsole(process, alias !== null && alias !== void 0 ? alias : containerName);
1987
+ let failed = false;
1988
+ process.catch(() => {
1989
+ failed = true;
1990
+ });
1991
+ if (!process.pid) {
1992
+ throw new Error("No process identifier (PID) was returned for the process that was started when running trying to run Docker container");
1993
+ }
1994
+ const id = await getContainerId({
1995
+ executor,
1996
+ name: containerName,
1997
+ hasFailed: () => failed,
1998
+ pid: process.pid,
1999
+ });
2000
+ executor.registerCleanupTask(async () => {
2001
+ console.log(`Stopping container ${containerName}`);
2002
+ const r = execa__default["default"]("docker", ["stop", containerName]);
2003
+ pipeToConsole(r, (alias !== null && alias !== void 0 ? alias : containerName) + " (stop)");
2004
+ try {
2005
+ await r;
2006
+ }
2007
+ catch (e) {
2008
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
2009
+ if (!(e.stderr || "").includes("No such container")) {
2010
+ throw e;
2011
+ }
2012
+ }
2013
+ });
2014
+ return {
2015
+ id,
2016
+ name: containerName,
2017
+ network,
2018
+ process,
2019
+ executor,
2020
+ };
2021
+ }
2022
+ async function runNpmRunScript(name, options) {
2023
+ const result = execa__default["default"]("npm", ["run", name], {
2024
+ env: options === null || options === void 0 ? void 0 : options.env,
2025
+ });
2026
+ pipeToConsole(result, `npm run ${name}`);
2027
+ await result;
2028
+ }
2029
+ /**
2030
+ * This likely does not cover all situations.
2031
+ */
2032
+ async function getDockerHostAddress() {
2033
+ if (process.platform === "darwin" || process.platform === "win32") {
2034
+ return "host.docker.internal";
2035
+ }
2036
+ if (fs__default["default"].existsSync("/.dockerenv")) {
2037
+ const process = execa__default["default"]("ip", ["route"]);
2038
+ pipeToConsole(process, "ip route");
2039
+ const res = await process;
2040
+ try {
2041
+ return (res.stdout
2042
+ .split("\n")
2043
+ .filter((it) => it.includes("default via"))
2044
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2045
+ .map((it) => /default via ([\d\.]+) /.exec(it)[1])[0]);
2046
+ }
2047
+ catch (e) {
2048
+ throw new Error("Failed to extract docker host address");
2049
+ }
2050
+ }
2051
+ return "localhost";
2052
+ }
2053
+ async function waitForEnterToContinue(prompt = "Press enter to continue") {
2054
+ return new Promise((resolve, reject) => {
2055
+ read__default["default"]({
2056
+ prompt,
2057
+ silent: true,
2058
+ }, (err) => {
2059
+ if (err) {
2060
+ reject(err);
2061
+ }
2062
+ resolve();
2063
+ });
2064
+ });
2065
2065
  }
2066
2066
 
2067
2067
  const VERSION = version;