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!: support different repository providers #55

Merged
merged 10 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 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,11 +33,10 @@ const ConfigDefaults: ChangelogConfig = {
ci: { title: "🤖 CI" },
},
cwd: null,
github: "",
from: "",
to: "",
output: "CHANGELOG.md",
scopeMap: {},
scopeMap: {}
};

export async function loadChangelogConfig(
Expand Down Expand Up @@ -69,16 +70,11 @@ 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 =
typeof pkg.repository === "string"
? pkg.repository
: pkg.repository.url;
if (/^\w+\/\w+$/.test(repo)) {
config.github = repo;
}
const repoUrl = typeof pkg.repository === "string" ? pkg.repository : pkg.repository.url;
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
81 changes: 81 additions & 0 deletions src/repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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",
};

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 {}

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

const m = repoUrl.match(proiderRe)?.groups ?? {};
if (m.repo && m.provider) {
repo = m.repo;
provider = m.provider in domainToProvider ? domainToProvider[m.provider] : m.provider;
domain = providerToDomain[provider] ? 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,
};
}
162 changes: 160 additions & 2 deletions test/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {
getGitDiff,
loadChangelogConfig,
parseCommits,
getRepoConfig,
formatReference,
} from "../src";
import { RepoConfig } from "./../src/repo";

describe("git", () => {
test("getGitDiff should work", async () => {
Expand Down Expand Up @@ -295,7 +298,7 @@ describe("git", () => {
### 🩹 Fixes

- Consider docs and refactor as semver patch for bump ([648ccf1](https://github.com/unjs/changelogen/commit/648ccf1))
- **scope:** ⚠️ Breaking change example, close #123 ([#134](https://github.com/unjs/changelogen/pull/134), [#123](https://github.com/unjs/changelogen/ssue/123))
- **scope:** ⚠️ Breaking change example, close #123 ([#134](https://github.com/unjs/changelogen/pull/134), [#123](https://github.com/unjs/changelogen/issues/123))

### 🏡 Chore

Expand All @@ -308,11 +311,166 @@ describe("git", () => {

#### ⚠️ Breaking Changes

- **scope:** ⚠️ Breaking change example, close #123 ([#134](https://github.com/unjs/changelogen/pull/134), [#123](https://github.com/unjs/changelogen/ssue/123))
- **scope:** ⚠️ Breaking change example, close #123 ([#134](https://github.com/unjs/changelogen/pull/134), [#123](https://github.com/unjs/changelogen/issues/123))

### ❤️ Contributors

- Pooya Parsa ([@pi0](http://github.com/pi0))"
`);
});

test("parse host config", () => {
expect(getRepoConfig(undefined)).toMatchObject({});
expect(getRepoConfig("")).toMatchObject({});
expect(getRepoConfig("unjs")).toMatchObject({});

const github = {
provider: "github",
repo: "unjs/changelogen",
domain: "github.com",
};
expect(getRepoConfig("unjs/changelogen")).toStrictEqual(github);
expect(getRepoConfig("github:unjs/changelogen")).toStrictEqual(github);
expect(getRepoConfig("https://github.com/unjs/changelogen")).toStrictEqual(
github
);
expect(
getRepoConfig("https://github.com/unjs/changelogen.git")
).toStrictEqual(github);
expect(getRepoConfig("[email protected]:unjs/changelogen.git")).toStrictEqual(
github
);

const gitlab = {
provider: "gitlab",
repo: "unjs/changelogen",
domain: "gitlab.com",
};

expect(getRepoConfig("gitlab:unjs/changelogen")).toStrictEqual(gitlab);
expect(getRepoConfig("https://gitlab.com/unjs/changelogen")).toStrictEqual(
gitlab
);
expect(
getRepoConfig("https://gitlab.com/unjs/changelogen.git")
).toStrictEqual(gitlab);
expect(getRepoConfig("[email protected]:unjs/changelogen.git")).toStrictEqual(
gitlab
);

const bitbucket = {
provider: "bitbucket",
repo: "unjs/changelogen",
domain: "bitbucket.org",
};

expect(getRepoConfig("bitbucket:unjs/changelogen")).toStrictEqual(
bitbucket
);
expect(
getRepoConfig("https://bitbucket.org/unjs/changelogen")
).toStrictEqual(bitbucket);
expect(
getRepoConfig("https://bitbucket.org/unjs/changelogen.git")
).toStrictEqual(bitbucket);
expect(
getRepoConfig("https://[email protected]/unjs/changelogen.git")
).toStrictEqual(bitbucket);
expect(
getRepoConfig("[email protected]:unjs/changelogen.git")
).toStrictEqual(bitbucket);

const selfhosted = {
repo: "unjs/changelogen",
domain: "git.unjs.io",
};

expect(getRepoConfig("selfhosted:unjs/changelogen")).toMatchObject({
provider: "selfhosted",
repo: "unjs/changelogen",
});

expect(getRepoConfig("https://git.unjs.io/unjs/changelogen")).toMatchObject(selfhosted);

expect(
getRepoConfig("https://git.unjs.io/unjs/changelogen.git")
).toMatchObject(selfhosted);
expect(
getRepoConfig("https://[email protected]/unjs/changelogen.git")
).toMatchObject(selfhosted);
expect(getRepoConfig("[email protected]:unjs/changelogen.git")).toMatchObject(
selfhosted
);
});

test("format reference", () => {
expect(formatReference({ type: "hash", value: "3828bda" })).toBe("3828bda");
expect(formatReference({ type: "pull-request", value: "#123" })).toBe(
"#123"
);
expect(formatReference({ type: "issue", value: "#14" })).toBe("#14");

const github: RepoConfig = {
provider: "github",
repo: "unjs/changelogen",
domain: "github.com",
};

expect(formatReference({ type: "hash", value: "3828bda" }, github)).toBe(
"[3828bda](https://github.com/unjs/changelogen/commit/3828bda)"
);
expect(
formatReference({ type: "pull-request", value: "#123" }, github)
).toBe("[#123](https://github.com/unjs/changelogen/pull/123)");
expect(formatReference({ type: "issue", value: "#14" }, github)).toBe(
"[#14](https://github.com/unjs/changelogen/issues/14)"
);

const gitlab: RepoConfig = {
provider: "gitlab",
repo: "unjs/changelogen",
domain: "gitlab.com",
};

expect(formatReference({ type: "hash", value: "3828bda" }, gitlab)).toBe(
"[3828bda](https://gitlab.com/unjs/changelogen/commit/3828bda)"
);
expect(
formatReference({ type: "pull-request", value: "#123" }, gitlab)
).toBe("[#123](https://gitlab.com/unjs/changelogen/merge_requests/123)");
expect(formatReference({ type: "issue", value: "#14" }, gitlab)).toBe(
"[#14](https://gitlab.com/unjs/changelogen/issues/14)"
);

const bitbucket: RepoConfig = {
provider: "bitbucket",
repo: "unjs/changelogen",
domain: "bitbucket.org",
};

expect(formatReference({ type: "hash", value: "3828bda" }, bitbucket)).toBe(
"[3828bda](https://bitbucket.org/unjs/changelogen/commit/3828bda)"
);
expect(
formatReference({ type: "pull-request", value: "#123" }, bitbucket)
).toBe("[#123](https://bitbucket.org/unjs/changelogen/pull-requests/123)");
expect(formatReference({ type: "issue", value: "#14" }, bitbucket)).toBe(
"[#14](https://bitbucket.org/unjs/changelogen/issues/14)"
);

const unkown: RepoConfig = {
repo: "unjs/changelogen",
domain: "git.unjs.io",
};

expect(
formatReference({ type: "hash", value: "3828bda" }, unkown)
).toBe("3828bda");
expect(
formatReference({ type: "pull-request", value: "#123" }, unkown)
).toBe("#123");
expect(formatReference({ type: "issue", value: "#14" }, unkown)).toBe(
"#14"
);
});
});