Skip to content
This repository was archived by the owner on Apr 13, 2021. It is now read-only.
Open
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
10 changes: 10 additions & 0 deletions src/execWith.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const execWith = execFunction => onError => command => {
try {
return execFunction(command, {
encoding: "utf8",
stdio: ["pipe", "pipe", "ignore"],
}).replace(/\n$/, "")
} catch (e) {
return onError(e)
}
}
41 changes: 41 additions & 0 deletions src/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { execSync } from "child_process"
import { execWith } from "./execWith.mjs"
import { ExtendPipe } from "./Pipe.mjs"
import { GetLatestVersion } from "./pipes/GetLatestVersion.mjs"
import { MakeNewVersion } from "./pipes/MakeNewVersion.mjs"
import { ExitIfNoVersion } from "./pipes/ExitIfNoVersion.mjs"
import { GetVersionCommit } from "./pipes/GetVersionCommit.mjs"
import { GetChanges } from "./pipes/GetChanges.mjs"
import { ForceBump } from "./pipes/ForceBump.mjs"
import { MakeChangelog } from "./pipes/MakeChangelog.mjs"
import { Log } from "./pipes/Log.mjs"

const argv = process.argv.slice(2)

const conventions = {
patch: "^:bug: ",
minor: "^:sparkles: ",
major: "^:boom: ",
}

const execOrElse = execWith(execSync)
const execOrExit = execOrElse(e => {
console.error(e.message || e)
process.exit(1)
})

const stdOut = str => process.stdout.write(str)

const MakeChangelogIfRequired = argv.includes("--changelog")
? MakeChangelog
: ExtendPipe.empty()

GetLatestVersion(execOrElse(() => "0.0.0"))
.concat(GetVersionCommit(execOrExit))
.concat(GetChanges(execOrExit))
.concat(ForceBump)
.concat(MakeNewVersion)
.concat(ExitIfNoVersion(() => process.exit(0)))
.concat(MakeChangelogIfRequired)
.concat(Log(stdOut))
.run({ argv, conventions })
15 changes: 15 additions & 0 deletions src/pipe.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const _fs = Symbol()

const extend = f => obj => ({ ...obj, ...f(obj) })

export const Pipe = (...fs) => ({
[_fs]: fs,
concat: o => Pipe(...fs.concat(o[_fs])),
run: initialContext => fs.reduce((acc, f) => f(acc), initialContext),
})

Pipe.empty = () => Pipe(x => x)

export const ExtendPipe = (...fs) => Pipe(...fs.map(extend))

ExtendPipe.empty = () => ExtendPipe(() => ({}))
6 changes: 6 additions & 0 deletions src/pipes/ExitIfNoVersion.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ExtendPipe } from "../Pipe.mjs"

export const ExitIfNoVersion = onNoNewVersion =>
ExtendPipe(({ newVersion, latestVersion }) =>
newVersion == latestVersion ? onNoNewVersion() : {},
)
14 changes: 14 additions & 0 deletions src/pipes/ForceBump.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ExtendPipe } from "../Pipe.mjs"
import { testConvention } from "../utils.mjs"

export const ForceBump = ExtendPipe(
({ changes, argv, conventions }) => ({
argv: Object.keys(conventions).reduce(
(acc, c) =>
changes.find(testConvention(conventions[c]))
? acc.concat([`--${c}`])
: acc,
argv,
),
}),
)
23 changes: 23 additions & 0 deletions src/pipes/GetChanges.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ExtendPipe } from "../Pipe.mjs"

/**
* git-rev-list - Lists commit objects in reverse chronological order.
* @see https://git-scm.com/docs/git-rev-list
*
* * --oneline
* This is a shorthand for "--pretty=oneline --abbrev-commit".
*
* * --abbrev-commit
* Instead of showing the full 40-byte hexadecimal commit object name,
* show only a partial prefix.
*
* * --pretty=oneline
* Pretty-print the contents of the commit logs in one line.
*/
export const GetChanges = exec =>
ExtendPipe(({ latestVersionCommit }) => ({
changes: exec(`git rev-list ${latestVersionCommit}..HEAD --oneline`)
.split("\n")
.reverse()
.map(change => change.slice(change.indexOf(" ") + 1)),
}))
33 changes: 33 additions & 0 deletions src/pipes/GetLatestVersion.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ExtendPipe } from "../Pipe.mjs"

/**
* git-describe - Give an object a human readable name based on an
* available ref.
* @see https://git-scm.com/docs/git-describe
*
* * --tags
* Instead of using only the annotated tags, use any tag found in
* refs/tags namespace. This option enables matching a lightweight
* (non-annotated) tag.
*
* * --abbrev=<n>
* Instead of using the default 7 hexadecimal digits as the abbreviated
* object name, use <n> digits, or as many digits as needed to form a
* unique object name. An <n> of 0 will suppress long format, only
* showing the closest tag.
*
* * --match <pattern>
* Only consider tags matching the given glob(7) pattern, excluding the
* "refs/tags/" prefix.
*
* @warning The glob "*[0-9].*[0-9].*[0-9]" is not the perfect solution
* as it equally matches "1.0.0" and "1.foo0.bar0". It is used as a
* quick example here and real-life implementation should not rely on
* it.
*/
export const GetLatestVersion = exec =>
ExtendPipe(() => ({
latestVersion: exec(
`git describe --match "*[0-9].*[0-9].*[0-9]" --abbrev=0 HEAD --tags`,
),
}))
32 changes: 32 additions & 0 deletions src/pipes/GetVersionCommit.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ExtendPipe } from "../Pipe.mjs"

/**
* git-rev-list - Lists commit objects in reverse chronological order.
* @see https://git-scm.com/docs/git-rev-list
*
* * --max-parents=0
* Show only commits which have at least (or at most) that many parent
* commits. In particular, --max-parents=0 gives all root commits.
*
* @warning If your repository contains multiple unrelated histories
* merged together, the `git rev-list --max-parents=0 HEAD` command
* will return you more than one commit.
*
* ====================================================================
*
* git-show-ref - List references in a local repository
* @see https://git-scm.com/docs/git-show-ref.
*
* * --hash
* Only show the SHA-1 hash, not the reference name.
*/
export const GetVersionCommit = (
execOnNoTags,
execOnTags = execOnNoTags,
) =>
ExtendPipe(({ latestVersion }) => ({
latestVersionCommit:
latestVersion == "0.0.0"
? execOnNoTags("git rev-list --max-parents=0 HEAD")
: execOnTags(`git show-ref ${latestVersion} -s`),
}))
6 changes: 6 additions & 0 deletions src/pipes/Log.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ExtendPipe } from "../Pipe.mjs"

export const Log = logger =>
ExtendPipe(({ newVersion, changelog }) =>
logger(changelog || newVersion),
)
38 changes: 38 additions & 0 deletions src/pipes/MakeChangelog.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ExtendPipe } from "../Pipe.mjs"
import { testConvention } from "../utils.mjs"

const trimType = convention => change =>
change.replace(new RegExp(convention), "")

const getMatchingChanges = (convention, changes) =>
changes.filter(testConvention(convention)).map(trimType(convention))

const writeToChangelog = (title, items) =>
items.length
? `\n## ${title}\n\n${items
.map(change => `* ${change}\n`)
.join("")}`
: ""

const changelogTag = (_, version, breaks, features, fixes) =>
`# ${version}\n`
.concat(writeToChangelog("Breaking Changes", breaks))
.concat(writeToChangelog("Features & Deprecations", features))
.concat(writeToChangelog("Bug Fixes", fixes))

export const MakeChangelog = ExtendPipe(
({ changes, newVersion, conventions }) => ({
changelog: changelogTag`
# ${newVersion}

## Breaking Changes
${getMatchingChanges(conventions.major, changes)}

## Features & Deprecations
${getMatchingChanges(conventions.minor, changes)}

## Bug Fixes
${getMatchingChanges(conventions.patch, changes)}
`,
}),
)
21 changes: 21 additions & 0 deletions src/pipes/MakeNewVersion.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ExtendPipe } from "../Pipe.mjs"

const splitVersionToNumbers = ({ latestVersion }) => ({
newVersion: latestVersion.split(".").map(Number),
})

const joinVersionNumbers = ({ newVersion }) => ({
newVersion: newVersion.join("."),
})

const bump = (option, bumpFunc) => ({ newVersion, argv }) => ({
newVersion: argv.includes(option) ? bumpFunc(newVersion) : newVersion,
})

export const MakeNewVersion = ExtendPipe(
splitVersionToNumbers,
bump("--patch", tuple => [tuple[0], tuple[1], tuple[2] + 1]),
bump("--minor", tuple => [tuple[0], tuple[1] + 1, 0]),
bump("--major", tuple => [tuple[0] + 1, 0, 0]),
joinVersionNumbers,
)
2 changes: 2 additions & 0 deletions src/utils.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const testConvention = convention => change =>
new RegExp(convention).test(change)
2 changes: 1 addition & 1 deletion tests/0_2_Pipe.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExtendPipe, Pipe } from "../src/pipe.mjs"
import { ExtendPipe, Pipe } from "../src/Pipe.mjs"
import { describe, expect, it } from "./clown.mjs"

describe("Pipe", () => {
Expand Down
20 changes: 10 additions & 10 deletions tests/index.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import "./clown.spec.mjs"
// import "./0_1_execWith.spec.mjs"
// import "./0_2_Pipe.spec.mjs"
// import "./1_GetLatestVersion.spec.mjs"
// import "./2_MakeNewVersion.spec.mjs"
// import "./3_ExitIfNoVersion.spec.mjs"
// import "./4_GetVersionCommit.spec.mjs"
// import "./5_GetChanges.spec.mjs"
// import "./6_ForceBump.spec.mjs"
// import "./7_MakeChangelog.spec.mjs"
// import "./8_Log.spec.mjs"
import "./0_1_execWith.spec.mjs"
import "./0_2_Pipe.spec.mjs"
import "./1_GetLatestVersion.spec.mjs"
import "./2_MakeNewVersion.spec.mjs"
import "./3_ExitIfNoVersion.spec.mjs"
import "./4_GetVersionCommit.spec.mjs"
import "./5_GetChanges.spec.mjs"
import "./6_ForceBump.spec.mjs"
import "./7_MakeChangelog.spec.mjs"
import "./8_Log.spec.mjs"