deploy-vir 3.4.3 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/commit.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type PartialWithUndefined } from '@augment-vir/common';
1
2
  import { type DefaultLogFields, type ListLogLine } from 'simple-git';
2
3
  /**
3
4
  * A git commit.
@@ -6,10 +7,11 @@ import { type DefaultLogFields, type ListLogLine } from 'simple-git';
6
7
  */
7
8
  export type Commit = DefaultLogFields & ListLogLine;
8
9
  /**
9
- * Extract the human author name for a commit. Commits authored by a bot (a name with a `[bot]`
10
- * suffix, like GitHub's merge queue bot) are attributed to their first non-bot `Co-authored-by:`
11
- * trailer instead.
10
+ * Extract the owning person's name for a commit: the first assignee on the commit's pull request,
11
+ * or that pull request's author (requires the `gh` CLI). Whoever merged a pull request is often not
12
+ * whoever owns the change, so the commit's own author is only used when no pull request can be
13
+ * read.
12
14
  *
13
15
  * @category Internal
14
16
  */
15
- export declare function getCommitAuthorName(commit: Readonly<Pick<Commit, 'author_name' | 'body'>>): string;
17
+ export declare function getCommitAuthorName(commit: Readonly<Pick<Commit, 'author_name'> & PartialWithUndefined<Pick<Commit, 'hash' | 'message'>>>): Promise<string>;
package/dist/commit.js CHANGED
@@ -1,19 +1,69 @@
1
- const coAuthorRegExp = /^co[- \t]*authored[- \t]*by:(.*)$/gim;
2
- const botNameSuffix = '[bot]';
1
+ import { check } from '@augment-vir/assert';
2
+ import { extractErrorMessage, log } from '@augment-vir/common';
3
+ import { runShellCommand } from '@augment-vir/node';
4
+ const pullRequestNumberRegExp = /#(\d+)/;
3
5
  /**
4
- * Extract the human author name for a commit. Commits authored by a bot (a name with a `[bot]`
5
- * suffix, like GitHub's merge queue bot) are attributed to their first non-bot `Co-authored-by:`
6
- * trailer instead.
6
+ * Each `gh` lookup is a network request and each commit's author name gets read more than once (CLI
7
+ * output and notifications), so results are cached by their lookup key.
8
+ */
9
+ const pullRequestAuthorCache = new Map();
10
+ /**
11
+ * Extract the owning person's name for a commit: the first assignee on the commit's pull request,
12
+ * or that pull request's author (requires the `gh` CLI). Whoever merged a pull request is often not
13
+ * whoever owns the change, so the commit's own author is only used when no pull request can be
14
+ * read.
7
15
  *
8
16
  * @category Internal
9
17
  */
10
- export function getCommitAuthorName(commit) {
11
- if (!commit.author_name.endsWith(botNameSuffix)) {
12
- return commit.author_name;
18
+ export async function getCommitAuthorName(commit) {
19
+ const pullRequestAuthorName = await getPullRequestAuthorName({
20
+ pullRequestNumber: commit.message?.match(pullRequestNumberRegExp)?.[1],
21
+ hash: commit.hash,
22
+ });
23
+ return pullRequestAuthorName || commit.author_name;
24
+ }
25
+ function getPullRequestAuthorName({ pullRequestNumber, hash, }) {
26
+ const cacheKey = pullRequestNumber || hash;
27
+ if (!cacheKey) {
28
+ return Promise.resolve(undefined);
13
29
  }
14
- const coAuthorName = Array.from(commit.body.matchAll(coAuthorRegExp))
15
- /** Strip the trailing `<email>` from each trailer. */
16
- .map((match) => (match[1] || '').split('<')[0]?.trim())
17
- .find((name) => name && !name.endsWith(botNameSuffix));
18
- return coAuthorName || commit.author_name;
30
+ const cached = pullRequestAuthorCache.get(cacheKey);
31
+ if (cached) {
32
+ return cached;
33
+ }
34
+ const pending = fetchPullRequestAuthorName(pullRequestNumber
35
+ ? `gh pr view ${pullRequestNumber} --json assignees,author`
36
+ : `gh pr list --search ${cacheKey} --state merged --limit 1 --json assignees,author`);
37
+ pullRequestAuthorCache.set(cacheKey, pending);
38
+ return pending;
39
+ }
40
+ async function fetchPullRequestAuthorName(command) {
41
+ try {
42
+ const output = await runShellCommand(command, {
43
+ rejectOnError: true,
44
+ });
45
+ return extractPullRequestAuthorName(JSON.parse(output.stdout));
46
+ }
47
+ catch (error) {
48
+ log.faint(`Failed to read a pull request author: ${extractErrorMessage(error)}`);
49
+ return undefined;
50
+ }
51
+ }
52
+ function extractPullRequestAuthorName(parsedJson) {
53
+ /** `gh pr list` outputs an array, `gh pr view` outputs a single pull request. */
54
+ const pullRequest = check.isArray(parsedJson) ? parsedJson[0] : parsedJson;
55
+ const assignees = check.hasKey(pullRequest, 'assignees') && check.isArray(pullRequest.assignees)
56
+ ? pullRequest.assignees
57
+ : [];
58
+ return [
59
+ ...assignees,
60
+ check.hasKey(pullRequest, 'author') ? pullRequest.author : undefined,
61
+ ]
62
+ .map(extractUserName)
63
+ .find((name) => name);
64
+ }
65
+ function extractUserName(user) {
66
+ const name = check.hasKey(user, 'name') && check.isString(user.name) ? user.name : '';
67
+ const login = check.hasKey(user, 'login') && check.isString(user.login) ? user.login : '';
68
+ return name || login || undefined;
19
69
  }
package/dist/git.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { assert, assertWrap, check } from '@augment-vir/assert';
2
- import { awaitedBlockingMap, log, logColors } from '@augment-vir/common';
2
+ import { awaitedBlockingMap, awaitedForEach, log, logColors } from '@augment-vir/common';
3
3
  import { confirm } from '@inquirer/prompts';
4
4
  import { getCommitAuthorName } from './commit.js';
5
5
  import { KnownError } from './known.error.js';
@@ -13,7 +13,9 @@ export async function getGitRemoteName(git, repoConfig) {
13
13
  assert.isTruthy(repoConfig.name, 'Repo config name cannot be empty.');
14
14
  assert.isTruthy(repoConfig.gitUrl, 'Repo git URL cannot be empty.');
15
15
  const remotes = await git.getRemotes(true);
16
- const remoteMatchByUrl = remotes.find((remote) => remote.refs.fetch === repoConfig.gitUrl || remote.refs.push === repoConfig.gitUrl);
16
+ const remoteMatchByUrl = remotes.find((remote) => {
17
+ return remote.refs.fetch === repoConfig.gitUrl || remote.refs.push === repoConfig.gitUrl;
18
+ });
17
19
  if (remoteMatchByUrl) {
18
20
  return remoteMatchByUrl.name;
19
21
  }
@@ -65,13 +67,13 @@ export async function pushDeploy(git, branchConfig, repoConfig, remoteName, noti
65
67
  ])).all;
66
68
  const requiresForcePush = commitsBehind.length > 0;
67
69
  log.info(`\n${requiresForcePush ? 'Only on' : 'Releasing from'} ${logColors.bold}${fromBranch}${logColors.reset}:`);
68
- commitsAhead.forEach((commit, index) => {
69
- log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) - ${commit.message}`);
70
+ await awaitedForEach(commitsAhead, async (commit, index) => {
71
+ log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${await getCommitAuthorName(commit)}) - ${commit.message}`);
70
72
  });
71
73
  if (requiresForcePush) {
72
74
  log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
73
- commitsBehind.forEach((commit, index) => {
74
- log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) - ${commit.message}`);
75
+ await awaitedForEach(commitsBehind, async (commit, index) => {
76
+ log.faint(` ${index + 1}. ${commit.hash.slice(0, 7)} (${await getCommitAuthorName(commit)}) - ${commit.message}`);
75
77
  });
76
78
  }
77
79
  const shouldPush = bypassConfirmation ||
@@ -9,12 +9,13 @@ function truncateString(value, truncateAt = 100) {
9
9
  return value;
10
10
  }
11
11
  }
12
- function formatCommit(baseCommitUrl, commit) {
12
+ async function formatCommit(baseCommitUrl, commit) {
13
+ const authorName = await getCommitAuthorName(commit);
13
14
  if (baseCommitUrl) {
14
- return `<${joinUrlPaths(baseCommitUrl, commit.hash)}|${commit.hash.slice(0, 7)}> (${getCommitAuthorName(commit)}) ${truncateString(commit.message)}`;
15
+ return `<${joinUrlPaths(baseCommitUrl, commit.hash)}|${commit.hash.slice(0, 7)}> (${authorName}) ${truncateString(commit.message)}`;
15
16
  }
16
17
  else {
17
- return `${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) ${truncateString(commit.message)}`;
18
+ return `${commit.hash.slice(0, 7)} (${authorName}) ${truncateString(commit.message)}`;
18
19
  }
19
20
  }
20
21
  const slackMaxSectionLength = 2500;
@@ -43,8 +44,8 @@ function chunkSectionLines(lines) {
43
44
  * @category Internal
44
45
  */
45
46
  export async function sendNotificationToSlack({ deployResult: { deployCommits: { deployedCommits, overwrittenCommits }, branchStatus: { after, before }, toBranchName, }, hookResult, notification, repoConfig, }) {
46
- const deployedCommitBullets = deployedCommits.map((commit) => formatCommit(repoConfig.commitBaseUrl, commit));
47
- const overwrittenCommitBullets = overwrittenCommits.map((commit) => formatCommit(repoConfig.commitBaseUrl, commit));
47
+ const deployedCommitBullets = await Promise.all(deployedCommits.map(async (commit) => await formatCommit(repoConfig.commitBaseUrl, commit)));
48
+ const overwrittenCommitBullets = await Promise.all(overwrittenCommits.map(async (commit) => await formatCommit(repoConfig.commitBaseUrl, commit)));
48
49
  const deployedCommitBulletLines = deployedCommitBullets.map((bullet) => `- ${bullet}`);
49
50
  const overwrittenCommitBulletLines = overwrittenCommitBullets.length
50
51
  ? overwrittenCommitBullets.map((bullet) => `- ${bullet}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-vir",
3
- "version": "3.4.3",
3
+ "version": "3.6.0",
4
4
  "description": "Deploy a branch to another branch with notifications.",
5
5
  "keywords": [
6
6
  "deploy",
@@ -25,7 +25,9 @@
25
25
  "main": "dist/index.js",
26
26
  "module": "dist/index.js",
27
27
  "types": "dist/index.d.ts",
28
- "bin": "bin.js",
28
+ "bin": {
29
+ "deploy-vir": "bin.js"
30
+ },
29
31
  "scripts": {
30
32
  "compile": "virmator compile",
31
33
  "docs": "virmator docs",
@@ -42,39 +44,39 @@
42
44
  "test:update": "npm run test update"
43
45
  },
44
46
  "dependencies": {
45
- "@augment-vir/assert": "^32.2.1",
46
- "@augment-vir/common": "^32.2.1",
47
- "@augment-vir/node": "^32.2.1",
48
- "@inquirer/prompts": "^8.5.2",
49
- "@slack/web-api": "^8.0.0",
50
- "cli-vir": "^1.0.4",
47
+ "@augment-vir/assert": "^32.2.4",
48
+ "@augment-vir/common": "^32.2.4",
49
+ "@augment-vir/node": "^32.2.4",
50
+ "@inquirer/prompts": "^8.7.0",
51
+ "@slack/web-api": "^8.1.1",
52
+ "cli-vir": "^1.0.5",
51
53
  "simple-git": "^3.36.0",
52
- "tsx": "^4.23.1",
53
- "url-vir": "^2.1.10"
54
+ "tsx": "^4.23.13",
55
+ "url-vir": "^2.2.2"
54
56
  },
55
57
  "devDependencies": {
56
- "@augment-vir/test": "^32.2.1",
58
+ "@augment-vir/test": "^32.2.4",
57
59
  "@eslint/eslintrc": "^3.3.6",
58
60
  "@eslint/js": "^10.0.1",
59
61
  "@stylistic/eslint-plugin": "^5.10.0",
60
62
  "@stylistic/eslint-plugin-ts": "^4.4.1",
61
- "@types/node": "^26.1.1",
62
- "@typescript-eslint/eslint-plugin": "^8.65.0",
63
- "cspell": "^10.0.1",
64
- "dependency-cruiser": "^18.1.0",
65
- "esbuild": "^0.28.1",
66
- "eslint": "^10.8.0",
63
+ "@types/node": "^26.4.0",
64
+ "@typescript-eslint/eslint-plugin": "^8.68.0",
65
+ "cspell": "^10.1.1",
66
+ "dependency-cruiser": "^18.2.0",
67
+ "esbuild": "^0.28.2",
68
+ "eslint": "^10.9.1",
67
69
  "eslint-config-prettier": "^10.1.8",
68
- "eslint-plugin-jsdoc": "^63.2.2",
70
+ "eslint-plugin-jsdoc": "^64.3.2",
69
71
  "eslint-plugin-monorepo-cop": "^1.0.2",
70
- "eslint-plugin-playwright": "^2.10.5",
72
+ "eslint-plugin-playwright": "^2.11.0",
71
73
  "eslint-plugin-prettier": "^5.5.6",
72
74
  "eslint-plugin-require-extensions": "^0.1.3",
73
75
  "eslint-plugin-sonarjs": "^4.2.0",
74
- "eslint-plugin-unicorn": "^72.0.0",
76
+ "eslint-plugin-unicorn": "^74.0.0",
75
77
  "istanbul-smart-text-reporter": "^1.1.5",
76
78
  "markdown-code-example-inserter": "^3.0.6",
77
- "npm-check-updates": "^22.2.9",
79
+ "npm-check-updates": "^23.1.0",
78
80
  "prettier": "~3.3.3",
79
81
  "prettier-plugin-interpolated-html-tags": "^2.0.2",
80
82
  "prettier-plugin-jsdoc": "^1.8.1",
@@ -86,8 +88,8 @@
86
88
  "runstorm": "^1.0.6",
87
89
  "typedoc": "^0.28.20",
88
90
  "typescript": "^6.0.3",
89
- "typescript-eslint": "^8.65.0",
90
- "virmator": "^14.30.1"
91
+ "typescript-eslint": "^8.68.0",
92
+ "virmator": "^14.33.0"
91
93
  },
92
94
  "engines": {
93
95
  "node": ">=22"
package/src/commit.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import {check} from '@augment-vir/assert';
2
+ import {extractErrorMessage, log, type PartialWithUndefined} from '@augment-vir/common';
3
+ import {runShellCommand} from '@augment-vir/node';
1
4
  import {type DefaultLogFields, type ListLogLine} from 'simple-git';
2
5
 
3
6
  /**
@@ -7,27 +10,98 @@ import {type DefaultLogFields, type ListLogLine} from 'simple-git';
7
10
  */
8
11
  export type Commit = DefaultLogFields & ListLogLine;
9
12
 
10
- const coAuthorRegExp = /^co[- \t]*authored[- \t]*by:(.*)$/gim;
11
- const botNameSuffix = '[bot]';
13
+ const pullRequestNumberRegExp = /#(\d+)/;
12
14
 
13
15
  /**
14
- * Extract the human author name for a commit. Commits authored by a bot (a name with a `[bot]`
15
- * suffix, like GitHub's merge queue bot) are attributed to their first non-bot `Co-authored-by:`
16
- * trailer instead.
16
+ * Each `gh` lookup is a network request and each commit's author name gets read more than once (CLI
17
+ * output and notifications), so results are cached by their lookup key.
18
+ */
19
+ const pullRequestAuthorCache = new Map<string, Promise<string | undefined>>();
20
+
21
+ /**
22
+ * Extract the owning person's name for a commit: the first assignee on the commit's pull request,
23
+ * or that pull request's author (requires the `gh` CLI). Whoever merged a pull request is often not
24
+ * whoever owns the change, so the commit's own author is only used when no pull request can be
25
+ * read.
17
26
  *
18
27
  * @category Internal
19
28
  */
20
- export function getCommitAuthorName(
21
- commit: Readonly<Pick<Commit, 'author_name' | 'body'>>,
22
- ): string {
23
- if (!commit.author_name.endsWith(botNameSuffix)) {
24
- return commit.author_name;
29
+ export async function getCommitAuthorName(
30
+ commit: Readonly<
31
+ Pick<Commit, 'author_name'> & PartialWithUndefined<Pick<Commit, 'hash' | 'message'>>
32
+ >,
33
+ ): Promise<string> {
34
+ const pullRequestAuthorName = await getPullRequestAuthorName({
35
+ pullRequestNumber: commit.message?.match(pullRequestNumberRegExp)?.[1],
36
+ hash: commit.hash,
37
+ });
38
+
39
+ return pullRequestAuthorName || commit.author_name;
40
+ }
41
+
42
+ function getPullRequestAuthorName({
43
+ pullRequestNumber,
44
+ hash,
45
+ }: Readonly<
46
+ PartialWithUndefined<{
47
+ pullRequestNumber: string;
48
+ hash: string;
49
+ }>
50
+ >): Promise<string | undefined> {
51
+ const cacheKey = pullRequestNumber || hash;
52
+
53
+ if (!cacheKey) {
54
+ return Promise.resolve(undefined);
55
+ }
56
+
57
+ const cached = pullRequestAuthorCache.get(cacheKey);
58
+
59
+ if (cached) {
60
+ return cached;
25
61
  }
26
62
 
27
- const coAuthorName = Array.from(commit.body.matchAll(coAuthorRegExp))
28
- /** Strip the trailing `<email>` from each trailer. */
29
- .map((match) => (match[1] || '').split('<')[0]?.trim())
30
- .find((name) => name && !name.endsWith(botNameSuffix));
63
+ const pending = fetchPullRequestAuthorName(
64
+ pullRequestNumber
65
+ ? `gh pr view ${pullRequestNumber} --json assignees,author`
66
+ : `gh pr list --search ${cacheKey} --state merged --limit 1 --json assignees,author`,
67
+ );
68
+ pullRequestAuthorCache.set(cacheKey, pending);
69
+
70
+ return pending;
71
+ }
72
+
73
+ async function fetchPullRequestAuthorName(command: string): Promise<string | undefined> {
74
+ try {
75
+ const output = await runShellCommand(command, {
76
+ rejectOnError: true,
77
+ });
78
+
79
+ return extractPullRequestAuthorName(JSON.parse(output.stdout));
80
+ } catch (error) {
81
+ log.faint(`Failed to read a pull request author: ${extractErrorMessage(error)}`);
82
+ return undefined;
83
+ }
84
+ }
85
+
86
+ function extractPullRequestAuthorName(parsedJson: unknown): string | undefined {
87
+ /** `gh pr list` outputs an array, `gh pr view` outputs a single pull request. */
88
+ const pullRequest = check.isArray(parsedJson) ? parsedJson[0] : parsedJson;
89
+ const assignees =
90
+ check.hasKey(pullRequest, 'assignees') && check.isArray(pullRequest.assignees)
91
+ ? pullRequest.assignees
92
+ : [];
93
+
94
+ return [
95
+ ...assignees,
96
+ check.hasKey(pullRequest, 'author') ? pullRequest.author : undefined,
97
+ ]
98
+ .map(extractUserName)
99
+ .find((name) => name);
100
+ }
101
+
102
+ function extractUserName(user: unknown): string | undefined {
103
+ const name = check.hasKey(user, 'name') && check.isString(user.name) ? user.name : '';
104
+ const login = check.hasKey(user, 'login') && check.isString(user.login) ? user.login : '';
31
105
 
32
- return coAuthorName || commit.author_name;
106
+ return name || login || undefined;
33
107
  }
package/src/git.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {assert, assertWrap, check} from '@augment-vir/assert';
2
- import {awaitedBlockingMap, log, logColors} from '@augment-vir/common';
2
+ import {awaitedBlockingMap, awaitedForEach, log, logColors} from '@augment-vir/common';
3
3
  import {confirm} from '@inquirer/prompts';
4
4
  import {type SimpleGit} from 'simple-git';
5
5
  import {getCommitAuthorName, type Commit} from './commit.js';
@@ -27,10 +27,9 @@ export async function getGitRemoteName(
27
27
 
28
28
  const remotes = await git.getRemotes(true);
29
29
 
30
- const remoteMatchByUrl = remotes.find(
31
- (remote) =>
32
- remote.refs.fetch === repoConfig.gitUrl || remote.refs.push === repoConfig.gitUrl,
33
- );
30
+ const remoteMatchByUrl = remotes.find((remote) => {
31
+ return remote.refs.fetch === repoConfig.gitUrl || remote.refs.push === repoConfig.gitUrl;
32
+ });
34
33
 
35
34
  if (remoteMatchByUrl) {
36
35
  return remoteMatchByUrl.name;
@@ -157,17 +156,17 @@ export async function pushDeploy(
157
156
  log.info(
158
157
  `\n${requiresForcePush ? 'Only on' : 'Releasing from'} ${logColors.bold}${fromBranch}${logColors.reset}:`,
159
158
  );
160
- commitsAhead.forEach((commit, index) => {
159
+ await awaitedForEach(commitsAhead, async (commit, index) => {
161
160
  log.faint(
162
- ` ${index + 1}. ${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) - ${commit.message}`,
161
+ ` ${index + 1}. ${commit.hash.slice(0, 7)} (${await getCommitAuthorName(commit)}) - ${commit.message}`,
163
162
  );
164
163
  });
165
164
 
166
165
  if (requiresForcePush) {
167
166
  log.info(`\nOnly on ${logColors.bold}${toBranch}${logColors.reset}:`);
168
- commitsBehind.forEach((commit, index) => {
167
+ await awaitedForEach(commitsBehind, async (commit, index) => {
169
168
  log.faint(
170
- ` ${index + 1}. ${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) - ${commit.message}`,
169
+ ` ${index + 1}. ${commit.hash.slice(0, 7)} (${await getCommitAuthorName(commit)}) - ${commit.message}`,
171
170
  );
172
171
  });
173
172
  }
@@ -15,11 +15,13 @@ function truncateString(value: string, truncateAt = 100): string {
15
15
  }
16
16
  }
17
17
 
18
- function formatCommit(baseCommitUrl: string | undefined, commit: Readonly<Commit>) {
18
+ async function formatCommit(baseCommitUrl: string | undefined, commit: Readonly<Commit>) {
19
+ const authorName = await getCommitAuthorName(commit);
20
+
19
21
  if (baseCommitUrl) {
20
- return `<${joinUrlPaths(baseCommitUrl, commit.hash)}|${commit.hash.slice(0, 7)}> (${getCommitAuthorName(commit)}) ${truncateString(commit.message)}`;
22
+ return `<${joinUrlPaths(baseCommitUrl, commit.hash)}|${commit.hash.slice(0, 7)}> (${authorName}) ${truncateString(commit.message)}`;
21
23
  } else {
22
- return `${commit.hash.slice(0, 7)} (${getCommitAuthorName(commit)}) ${truncateString(commit.message)}`;
24
+ return `${commit.hash.slice(0, 7)} (${authorName}) ${truncateString(commit.message)}`;
23
25
  }
24
26
  }
25
27
 
@@ -65,11 +67,13 @@ export async function sendNotificationToSlack({
65
67
  notification,
66
68
  repoConfig,
67
69
  }: Readonly<NotificationParams>) {
68
- const deployedCommitBullets = deployedCommits.map((commit) =>
69
- formatCommit(repoConfig.commitBaseUrl, commit),
70
+ const deployedCommitBullets = await Promise.all(
71
+ deployedCommits.map(async (commit) => await formatCommit(repoConfig.commitBaseUrl, commit)),
70
72
  );
71
- const overwrittenCommitBullets = overwrittenCommits.map((commit) =>
72
- formatCommit(repoConfig.commitBaseUrl, commit),
73
+ const overwrittenCommitBullets = await Promise.all(
74
+ overwrittenCommits.map(
75
+ async (commit) => await formatCommit(repoConfig.commitBaseUrl, commit),
76
+ ),
73
77
  );
74
78
 
75
79
  const deployedCommitBulletLines = deployedCommitBullets.map((bullet) => `- ${bullet}`);