Skip to content

Commit

Permalink
feat!: support different repository providers (#55)
Browse files Browse the repository at this point in the history
* fix: broken ungh link

* feat!: ✨ support for multiple git hosts

addeedd support for github, gitlab, bitbucket and selfhosted git hosts

removed github from config

* test: multiple git hosts

* chore: merge

* refactor: rename host to repo

* simplify repo logic

* lint code

* refactor regex to top

---------

Co-authored-by: Pooya Parsa <pooya@pi0.io>
  • Loading branch information
dnldsht and pi0 authored Mar 1, 2023
1 parent e162ab8 commit 0bb28ab
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 33 deletions.
13 changes: 6 additions & 7 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { resolve } from "node:path";
import { loadConfig } from "c12";
import { readPackageJSON } from "pkg-types";
import { getLastGitTag, getCurrentGitRef } from "./git";
import { getRepoConfig } from "./repo";
import type { SemverBumpType } from "./semver";
import type { RepoConfig } from "./repo";

export interface ChangelogConfig {
cwd: string;
types: Record<string, { title: string; semver?: SemverBumpType }>;
scopeMap: Record<string, string>;
github: string;
repo?: RepoConfig;
from: string;
to: string;
newVersion?: string;
Expand All @@ -31,7 +33,6 @@ const ConfigDefaults: ChangelogConfig = {
ci: { title: "🤖 CI" },
},
cwd: null,
github: "",
from: "",
to: "",
output: "CHANGELOG.md",
Expand Down Expand Up @@ -69,16 +70,14 @@ export async function loadChangelogConfig(
: resolve(cwd, config.output);
}

if (!config.github) {
if (!config.repo) {
const pkg = await readPackageJSON(cwd).catch(() => {});
if (pkg && pkg.repository) {
const repo =
const repoUrl =
typeof pkg.repository === "string"
? pkg.repository
: pkg.repository.url;
if (/^\w+\/\w+$/.test(repo)) {
config.github = repo;
}
config.repo = getRepoConfig(repoUrl);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./git";
export * from "./markdown";
export * from "./config";
export * from "./semver";
export * from "./repo";
31 changes: 7 additions & 24 deletions src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { convert } from "convert-gitmoji";
import { fetch } from "node-fetch-native";
import type { ChangelogConfig } from "./config";
import type { GitCommit, Reference } from "./git";
import { formatReference, formatCompareChanges } from "./repo";

export async function generateMarkDown(
commits: GitCommit[],
Expand All @@ -17,13 +18,8 @@ export async function generateMarkDown(
const v = config.newVersion && `v${config.newVersion}`;
markdown.push("", "## " + (v || `${config.from}...${config.to}`), "");

if (config.github) {
markdown.push(
`[compare changes](https://github.com/${config.github}/compare/${
config.from
}...${v || config.to})`,
""
);
if (config.repo) {
markdown.push(formatCompareChanges(v, config), "");
}

for (const type in config.types) {
Expand Down Expand Up @@ -112,33 +108,20 @@ function formatCommit(commit: GitCommit, config: ChangelogConfig) {
);
}

const refTypeMap: Record<Reference["type"], string> = {
"pull-request": "pull",
hash: "commit",
issue: "ssue",
};

function formatReference(ref: Reference, config: ChangelogConfig) {
if (!config.github) {
return ref.value;
}
return `[${ref.value}](https://github.com/${config.github}/${
refTypeMap[ref.type]
}/${ref.value.replace(/^#/, "")})`;
}

function formatReferences(references: Reference[], config: ChangelogConfig) {
const pr = references.filter((ref) => ref.type === "pull-request");
const issue = references.filter((ref) => ref.type === "issue");
if (pr.length > 0 || issue.length > 0) {
return (
" (" +
[...pr, ...issue].map((ref) => formatReference(ref, config)).join(", ") +
[...pr, ...issue]
.map((ref) => formatReference(ref, config.repo))
.join(", ") +
")"
);
}
if (references.length > 0) {
return " (" + formatReference(references[0], config) + ")";
return " (" + formatReference(references[0], config.repo) + ")";
}
return "";
}
Expand Down
101 changes: 101 additions & 0 deletions src/repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Reference } from "./git";
import type { ChangelogConfig } from "./config";

export type RepoProvider = "github" | "gitlab" | "bitbucket";

export type RepoConfig = {
domain?: string;
repo?: string;
provider?: RepoProvider;
};

const providerToRefSpec: Record<
RepoProvider,
Record<Reference["type"], string>
> = {
github: { "pull-request": "pull", hash: "commit", issue: "issues" },
gitlab: { "pull-request": "merge_requests", hash: "commit", issue: "issues" },
bitbucket: {
"pull-request": "pull-requests",
hash: "commit",
issue: "issues",
},
};

const providerToDomain: Record<RepoProvider, string> = {
github: "github.com",
gitlab: "gitlab.com",
bitbucket: "bitbucket.org",
};

const domainToProvider: Record<string, RepoProvider> = {
"github.com": "github",
"gitlab.com": "gitlab",
"bitbucket.org": "bitbucket",
};

// https://regex101.com/r/NA4Io6/1
const providerURLRegex =
/^(?:(?<user>\w+)@)?(?:(?<provider>[^/:]+):)?(?<repo>\w+\/\w+)(?:\.git)?$/;

function baseUrl(config: RepoConfig) {
return `https://${config.domain}/${config.repo}`;
}

export function formatReference(ref: Reference, repo?: RepoConfig) {
if (!repo || !(repo.provider in providerToRefSpec)) {
return ref.value;
}
const refSpec = providerToRefSpec[repo.provider];
return `[${ref.value}](${baseUrl(repo)}/${
refSpec[ref.type]
}/${ref.value.replace(/^#/, "")})`;
}

export function formatCompareChanges(v: string, config: ChangelogConfig) {
const part =
config.repo.provider === "bitbucket" ? "branches/compare" : "compare";
return `[compare changes](${baseUrl(config.repo)}/${part}/${config.from}...${
v || config.to
})`;
}

export function getRepoConfig(repoUrl = ""): RepoConfig {
let provider;
let repo;
let domain;

let url;
try {
url = new URL(repoUrl);
} catch {}

const m = repoUrl.match(providerURLRegex)?.groups ?? {};
if (m.repo && m.provider) {
repo = m.repo;
provider =
m.provider in domainToProvider
? domainToProvider[m.provider]
: m.provider;
domain =
provider in providerToDomain ? providerToDomain[provider] : provider;
} else if (url) {
domain = url.hostname;
repo = url.pathname
.split("/")
.slice(1, 3)
.join("/")
.replace(/\.git$/, "");
provider = domainToProvider[domain];
} else if (m.repo) {
repo = m.repo;
provider = "github";
domain = providerToDomain[provider];
}

return {
provider,
repo,
domain,
};
}
Loading

0 comments on commit 0bb28ab

Please sign in to comment.