Files
archived-next-auth/scripts/release/analyze.ts
2022-07-13 13:59:45 +02:00

169 lines
4.7 KiB
TypeScript

import type {
Commit,
GroupedCommits as GrouppedCommits,
PackageToRelease,
} from "./types"
import { debug, pkgJson, execSync } from "./utils"
import semver from "semver"
import parseCommit from "@commitlint/parse"
import gitLog from "git-log-parser"
import streamToArray from "stream-to-array"
export async function analyze(options: {
dryRun: boolean
packages: Record<string, string>
BREAKING_COMMIT_MSG: string
RELEASE_COMMIT_MSG: string
RELEASE_COMMIT_TYPES: string[]
}): Promise<PackageToRelease[]> {
const {
packages,
BREAKING_COMMIT_MSG,
RELEASE_COMMIT_MSG,
RELEASE_COMMIT_TYPES,
} = options
const packageFolders = Object.values(options.packages)
console.log("Identifying latest tag...")
const latestTag = execSync("git describe --tags --abbrev=0", {
stdio: "pipe",
})
.toString()
.trim()
console.log(`Latest tag identified: ${latestTag}`)
console.log()
console.log("Identifying commits since the latest tag...")
const range = `${latestTag}..HEAD`
// Get the commits since the latest tag
const commitsSinceLatestTag = await new Promise<Commit[]>(
(resolve, reject) => {
const stream = gitLog.parse({ _: range })
streamToArray(stream, (err: Error, arr: any[]) => {
if (err) return reject(err)
Promise.all(
arr.map(async (d) => {
const parsed = await parseCommit(d.subject)
return { ...d, parsed }
})
).then((res) => resolve(res.filter(Boolean)))
})
}
)
console.log(commitsSinceLatestTag.length, `commits found since ${latestTag}`)
debug(
"Analyzing the following commits:",
commitsSinceLatestTag.map((c) => ` ${c.subject}`).join("\n")
)
const lastCommit = commitsSinceLatestTag[0]
if (lastCommit?.parsed.raw === RELEASE_COMMIT_MSG) {
debug("Already released...")
return []
}
console.log()
console.log("Identifying commits that touched package code...")
function getChangedFiles(commitSha: string) {
return execSync(
`git diff-tree --no-commit-id --name-only -r ${commitSha}`,
{ stdio: "pipe" }
)
.toString()
.trim()
.split("\n")
}
const packageCommits = commitsSinceLatestTag.filter(({ commit }) => {
const changedFiles = getChangedFiles(commit.short)
return packageFolders.some((packageFolder) =>
changedFiles.some((changedFile) => changedFile.startsWith(packageFolder))
)
})
console.log(packageCommits.length, "commits touched package code")
console.log()
console.log("Identifying packages that need a new release...")
const packagesNeedRelease: string[] = []
const grouppedPackages = packageCommits.reduce((acc, commit) => {
const changedFilesInCommit = getChangedFiles(commit.commit.short)
for (const [pkg, src] of Object.entries(packages)) {
if (
changedFilesInCommit.some((changedFile) => changedFile.startsWith(src))
) {
if (!(pkg in acc)) {
acc[pkg] = { features: [], bugfixes: [], other: [], breaking: [] }
}
const { type } = commit.parsed
if (RELEASE_COMMIT_TYPES.includes(type)) {
if (!packagesNeedRelease.includes(pkg)) {
packagesNeedRelease.push(pkg)
}
if (type === "feat") {
acc[pkg].features.push(commit)
if (commit.body.includes(BREAKING_COMMIT_MSG)) {
const [, changesBody] = commit.body.split(BREAKING_COMMIT_MSG)
acc[pkg].breaking.push({
...commit,
body: changesBody.trim(),
})
}
} else acc[pkg].bugfixes.push(commit)
} else {
acc[pkg].other.push(commit)
}
}
}
return acc
}, {} as Record<string, GrouppedCommits>)
if (packagesNeedRelease.length) {
console.log(
packagesNeedRelease.length,
`new release(s) needed: ${packagesNeedRelease.join(", ")}`
)
} else {
console.log("No packages needed a new release, BYE!")
return []
}
console.log()
const packagesToRelease: PackageToRelease[] = []
for await (const pkgName of packagesNeedRelease) {
const commits = grouppedPackages[pkgName]
const releaseType: semver.ReleaseType = commits.breaking.length
? "major" // 1.x.x
: commits.features.length
? "minor" // x.1.x
: "patch" // x.x.1
const packageJson = await pkgJson.read(packages[pkgName])
const oldVersion = packageJson.version!
const newSemVer = semver.parse(semver.inc(oldVersion, releaseType))!
packagesToRelease.push({
name: pkgName,
oldVersion,
newVersion: `${newSemVer.major}.${newSemVer.minor}.${newSemVer.patch}`,
commits,
path: packages[pkgName],
})
}
return packagesToRelease
}