Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lookup Github username if token is available #155

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
branches:
- main

permissions: {}

jobs:
ci:
runs-on: ubuntu-latest
Expand All @@ -24,4 +26,6 @@ jobs:
- run: pnpm lint
- run: pnpm build
- run: pnpm vitest --coverage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: codecov/codecov-action@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ coverage
dist
types
.conf*
.env
120 changes: 120 additions & 0 deletions src/author.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { upperFirst } from "scule";
import { ResolvedChangelogConfig } from "./config";
import { GitCommit } from "./git";
import { getGithubLoginByCommit } from "./github";

export interface CommitAuthor {
commits: string[];
github?: string;
email: Set<string>;
name: string;
}

export async function resolveAuthorInfo(
config: ResolvedChangelogConfig,
info: CommitAuthor
): Promise<CommitAuthor> {
if (info.github) {
return info;
}

try {
for (const email of info.email) {
const { user } = await fetch(`https://ungh.cc/users/find/${email}`)
.then((r) => r.json())
.catch(() => ({ user: null }));
if (user) {
info.github = user.username;
break;
}
}
} catch {}

if (info.github) {
return info;
}

// token not provided, skip github resolving
if (!config.tokens.github) {
return info;
}

for (const commit in info.commits) {
try {
info.github = await getGithubLoginByCommit(config, commit);
if (info.github) {
break;
}
} catch {}
}

return info;
}

export async function resolveAuthors(
commits: GitCommit[],
config: ResolvedChangelogConfig
): Promise<CommitAuthor[]> {
const _authors = new Map<string, CommitAuthor>();
for (const commit of commits) {
if (!commit.author) {
continue;
}
const name = formatName(commit.author.name);
if (!name || name.includes("[bot]")) {
continue;
}
if (
config.excludeAuthors &&
config.excludeAuthors.some(
(v) => name.includes(v) || commit.author.email?.includes(v)
)
) {
continue;
}
if (_authors.has(name)) {
const entry = _authors.get(name);
entry.email.add(commit.author.email);
entry.commits.push(commit.shortHash);
} else {
_authors.set(name, {
name,
email: new Set([commit.author.email]),
commits: [commit.shortHash],
});
}
}

// Try to map authors to github usernames
const resolved = await Promise.all(
[..._authors.values()].map((info) => resolveAuthorInfo(config, info))
);

// check for duplicate logins
const loginSet = new Set<string>();
return resolved.filter((i) => {
if (i.github && loginSet.has(i.github)) {
return false;
}
if (i.github) {
if (
config.excludeAuthors &&
config.excludeAuthors.some((v) => i.github.includes(v))
) {
return false;
}

loginSet.add(i.github);
}
return true;
});
}

// --- Internal utils ---

function formatName(name = "") {
return name
.split(" ")
.map((p) => upperFirst(p.trim()))
.join(" ");
}
17 changes: 14 additions & 3 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,24 @@ export async function listGithubReleases(
});
}

export async function getGithubLoginByCommit(
config: ResolvedChangelogConfig,
commit: string
): Promise<string> {
const data = await githubFetch(
config,
`/repos/${config.repo.repo}/commits/${commit}`
);
return data?.author?.login;
}

export async function getGithubReleaseByTag(
config: ResolvedChangelogConfig,
tag: string
): Promise<GithubRelease> {
return await githubFetch(
config,
`/repos/${config.repo.repo}/releases/tags/${tag}`,
{}
`/repos/${config.repo.repo}/releases/tags/${tag}`
);
}

Expand Down Expand Up @@ -151,9 +161,10 @@ async function githubFetch(
? "https://api.github.com"
: `https://${config.repo.domain}/api/v3`,
headers: {
"x-github-api-version": "2022-11-28",
...opts.headers,
authorization: config.tokens.github
? `Token ${config.tokens.github}`
? `Bearer ${config.tokens.github}`
: undefined,
},
});
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./markdown";
export * from "./config";
export * from "./semver";
export * from "./repo";
export * from "./author";
52 changes: 2 additions & 50 deletions src/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { upperFirst } from "scule";
import { convert } from "convert-gitmoji";
import { fetch } from "node-fetch-native";
import type { ResolvedChangelogConfig } from "./config";
import type { GitCommit, Reference } from "./git";
import { formatReference, formatCompareChanges } from "./repo";
import { resolveAuthors } from "./author";

export async function generateMarkDown(
commits: GitCommit[],
Expand Down Expand Up @@ -42,48 +42,7 @@ export async function generateMarkDown(
markdown.push("", "#### ⚠️ Breaking Changes", "", ...breakingChanges);
}

const _authors = new Map<string, { email: Set<string>; github?: string }>();
for (const commit of commits) {
if (!commit.author) {
continue;
}
const name = formatName(commit.author.name);
if (!name || name.includes("[bot]")) {
continue;
}
if (
config.excludeAuthors &&
config.excludeAuthors.some(
(v) => name.includes(v) || commit.author.email?.includes(v)
)
) {
continue;
}
if (_authors.has(name)) {
const entry = _authors.get(name);
entry.email.add(commit.author.email);
} else {
_authors.set(name, { email: new Set([commit.author.email]) });
}
}

// Try to map authors to github usernames
await Promise.all(
[..._authors.keys()].map(async (authorName) => {
const meta = _authors.get(authorName);
for (const email of meta.email) {
const { user } = await fetch(`https://ungh.cc/users/find/${email}`)
.then((r) => r.json())
.catch(() => ({ user: null }));
if (user) {
meta.github = user.username;
break;
}
}
})
);

const authors = [..._authors.entries()].map((e) => ({ name: e[0], ...e[1] }));
const authors = await resolveAuthors(commits, config);

if (authors.length > 0) {
markdown.push(
Expand Down Expand Up @@ -169,13 +128,6 @@ function formatReferences(
// return title.length <= 3 ? title.toUpperCase() : upperFirst(title)
// }

function formatName(name = "") {
return name
.split(" ")
.map((p) => upperFirst(p.trim()))
.join(" ");
}

function groupBy(items: any[], key: string) {
const groups = {};
for (const item of items) {
Expand Down
Loading
Loading