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

Improve automatic config detection #102

Merged
merged 4 commits into from
Jun 16, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 5 additions & 2 deletions src/__mocks__/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Configuration } from "../configuration";

const Changelog = require.requireActual("../changelog").default;

const defaultConfig = {
Expand All @@ -11,12 +13,13 @@ const defaultConfig = {
"Type: Documentation": ":memo: Documentation",
"Type: Maintenance": ":house: Maintenance",
},
ignoreCommitters: [],
cacheDir: ".changelog",
};

class MockedChangelog extends Changelog {
private getConfig() {
return defaultConfig;
private loadConfig(options: Partial<Configuration>): Configuration {
return Object.assign({}, defaultConfig, options);
}
private getToday() {
return "2099-01-01";
Expand Down
36 changes: 17 additions & 19 deletions src/changelog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const pMap = require("p-map");

import progressBar from "./progress-bar";
import * as Configuration from "./configuration";
import { Configuration, load as loadConfig } from "./configuration";
import findPullRequestId from "./find-pull-request-id";
import * as Git from "./git";
import GithubAPI, { GitHubUserResponse } from "./github-api";
Expand All @@ -16,32 +16,35 @@ interface Options {
}

export default class Changelog {
private config: any;
private readonly config: Configuration;
private github: GithubAPI;
private renderer: MarkdownRenderer;

constructor(options: Options = {}) {
this.config = Object.assign(this.getConfig(), options);
constructor(options: Partial<Configuration> = {}) {
this.config = this.loadConfig(options);
this.github = new GithubAPI(this.config);
this.renderer = new MarkdownRenderer({
categories: Object.keys(this.config.labels).map(key => this.config.labels[key]),
baseIssueUrl: this.github.getBaseIssueUrl(this.config.repo),
});
}

public async createMarkdown() {
const releases = await this.listReleases();
public async createMarkdown(options: Options = {}) {
const from = options.tagFrom || (await Git.lastTag());
const to = options.tagTo || "HEAD";

const releases = await this.listReleases(from, to);

return this.renderer.renderMarkdown(releases);
}

private getConfig() {
return Configuration.fromGitRoot(process.cwd());
private loadConfig(options: Partial<Configuration>): Configuration {
return loadConfig(options);
}

private async getCommitInfos(): Promise<CommitInfo[]> {
private async getCommitInfos(from: string, to: string): Promise<CommitInfo[]> {
// Step 1: Get list of commits between tag A and B (local)
const commits = await this.getListOfCommits();
const commits = this.getListOfCommits(from, to);

// Step 2: Find tagged commits (local)
const commitInfos = await this.toCommitInfos(commits);
Expand All @@ -58,9 +61,9 @@ export default class Changelog {
return commitInfos;
}

private async listReleases(): Promise<Release[]> {
private async listReleases(from: string, to: string): Promise<Release[]> {
// Get all info about commits in a certain tags range
const commits = await this.getCommitInfos();
const commits = await this.getCommitInfos(from, to);

// Step 6: Group commits by release (local)
let releases = this.groupByRelease(commits);
Expand Down Expand Up @@ -91,12 +94,11 @@ export default class Changelog {
return parts[1];
}

private async getListOfCommits(): Promise<Git.CommitListItem[]> {
private getListOfCommits(from: string, to: string): Git.CommitListItem[] {
// Determine the tags range to get the commits for. Custom from/to can be
// provided via command-line options.
// Default is "from last tag".
const tagFrom = this.config.tagFrom || (await Git.lastTag());
return Git.listCommits(tagFrom, this.config.tagTo);
return Git.listCommits(from, to);
}

private async getCommitters(commits: CommitInfo[]): Promise<GitHubUserResponse[]> {
Expand All @@ -117,10 +119,6 @@ export default class Changelog {
}

private ignoreCommitter(login: string): boolean {
if (!this.config.ignoreCommitters) {
return false;
}

return this.config.ignoreCommitters.some((c: string) => c === login || login.indexOf(c) > -1);
}

Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function run() {
};

try {
let result = await new Changelog(options).createMarkdown();
let result = await new Changelog().createMarkdown(options);
console.log(result);
} catch (e) {
if (e instanceof ConfigurationError) {
Expand Down
21 changes: 1 addition & 20 deletions src/configuration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,10 @@ const os = require("os");
const fs = require("fs-extra");
const path = require("path");

import { findRepoFromPkg, fromGitRoot, fromPath } from "./configuration";
import { findRepoFromPkg, fromPath } from "./configuration";
import ConfigurationError from "./configuration-error";

describe("Configuration", function() {
describe("fromGitRoot", function() {
it("reads the configuration from 'lerna.json'", function() {
const rootPath = path.resolve(`${__dirname}/..`);
const result = fromGitRoot(path.join(rootPath, "src"));
expect(result).toEqual({
repo: "lerna/lerna-changelog",
labels: {
breaking: ":boom: Breaking Change",
enhancement: ":rocket: Enhancement",
bug: ":bug: Bug Fix",
documentation: ":memo: Documentation",
internal: ":house: Internal",
},
cacheDir: ".changelog",
rootPath,
});
});
});

describe("fromPath", function() {
const tmpDir = `${os.tmpDir()}/changelog-test`;

Expand Down
77 changes: 49 additions & 28 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,75 @@ const normalize = require("normalize-git-url");

import ConfigurationError from "./configuration-error";

export function fromGitRoot(cwd: string): any {
const rootPath = execa.sync("git", ["rev-parse", "--show-toplevel"], { cwd }).stdout;
return fromPath(rootPath);
export interface Configuration {
repo: string;
rootPath: string;
labels: { [key: string]: string };
ignoreCommitters: string[];
cacheDir?: string;
}

export function fromPath(rootPath: string): any {
const config = fromPackageConfig(rootPath) || fromLernaConfig(rootPath) || guessConfig(rootPath);
export function load(options: Partial<Configuration> = {}): Configuration {
let cwd = process.cwd();
let rootPath = execa.sync("git", ["rev-parse", "--show-toplevel"], { cwd }).stdout;

if (!config) {
throw new ConfigurationError(
"Missing changelog config in `lerna.json`.\n" +
"See docs for setup: https://github.com/lerna/lerna-changelog#readme"
);
return fromPath(rootPath, options);
}

export function fromPath(rootPath: string, options: Partial<Configuration> = {}): Configuration {
// Step 1: load partial config from `package.json` or `lerna.json`
let config = fromPackageConfig(rootPath) || fromLernaConfig(rootPath) || {};

// Step 2: override partial config with passed in options
Object.assign(config, options);

// Step 3: fill partial config with defaults
let { repo, labels, cacheDir, ignoreCommitters } = config;

if (!repo) {
repo = findRepo(rootPath);
if (!repo) {
throw new ConfigurationError('Could not infer "repo” from the "package.json" file.');
}
}

if (!labels) {
labels = {
breaking: ":boom: Breaking Change",
enhancement: ":rocket: Enhancement",
bug: ":bug: Bug Fix",
documentation: ":memo: Documentation",
internal: ":house: Internal",
};
}

config.rootPath = rootPath;
if (!ignoreCommitters) {
ignoreCommitters = [];
}

return config;
return {
repo,
rootPath,
labels,
ignoreCommitters,
cacheDir,
};
}

function fromLernaConfig(rootPath: string): any | undefined {
function fromLernaConfig(rootPath: string): Partial<Configuration> | undefined {
const lernaPath = path.join(rootPath, "lerna.json");
if (fs.existsSync(lernaPath)) {
return JSON.parse(fs.readFileSync(lernaPath)).changelog;
}
}

function fromPackageConfig(rootPath: string): any | undefined {
function fromPackageConfig(rootPath: string): Partial<Configuration> | undefined {
const pkgPath = path.join(rootPath, "package.json");
if (fs.existsSync(pkgPath)) {
return JSON.parse(fs.readFileSync(pkgPath)).changelog;
}
}

function guessConfig(rootPath: string): any | undefined {
const repo = findRepo(rootPath);
if (!repo) {
return;
}

const labels = {
enhancement: ":rocket: Enhancement",
bug: ":bug: Bug Fix",
};

return { repo, labels };
}

function findRepo(rootPath: string): string | undefined {
const pkgPath = path.join(rootPath, "package.json");
if (!fs.existsSync(pkgPath)) {
Expand Down