Skip to content

Commit

Permalink
feat(analyze): breaking should bump minor on 0.x.x (#12)
Browse files Browse the repository at this point in the history
* feat(analyze): breaking should bump minor on 0.x.x

* stricter types

* fix types

* drop stream-to-array

* move
  • Loading branch information
balazsorban44 committed Dec 14, 2023
1 parent 2b6a113 commit 3d60d20
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 94 deletions.
1 change: 0 additions & 1 deletion packages/monorepo-release/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"@manypkg/get-packages": "^2.2.0",
"git-log-parser": "1.2.0",
"semver": "7.5.4",
"stream-to-array": "2.3.0",
"yoctocolors": "^1.0.0"
},
"devDependencies": {
Expand Down
144 changes: 69 additions & 75 deletions packages/monorepo-release/src/analyze.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Commit, GrouppedCommits, PackageToRelease } from "./types.js"
import type { Config } from "./config.js"
import { bold } from "yoctocolors"
import { log, execSync, pluralize } from "./utils.js"
import { log, execSync, pluralize, streamToArray } from "./utils.js"
import semver from "semver"
import * as commitlint from "@commitlint/parse"
// @ts-expect-error no types
import gitLog from "git-log-parser"
import streamToArray from "stream-to-array"
import { type Package, getPackages } from "@manypkg/get-packages"
import { getDependentsGraph } from "@changesets/get-dependents-graph"
import { exit } from "./index.js"
Expand Down Expand Up @@ -37,23 +37,16 @@ export async function analyze(config: Config): Promise<PackageToRelease[]> {
// TODO: Allow passing in a range of commits to analyze and print the changelog
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) => {
// @ts-ignore
const parsed = await commitlint.default.default(d.subject)
const stream = gitLog.parse({ _: range })

return { ...d, parsed }
}),
).then((res) => resolve(res.filter(Boolean)))
})
},
// Get the commits since the latest tag
const commitsSinceLatestTag = await streamToArray(stream).then((arr) =>
Promise.all(
arr.map(async (d) => {
d.parsed = await commitlint.default.default(d.subject)
return d
}),
),
)

log.info(
Expand Down Expand Up @@ -99,58 +92,58 @@ export async function analyze(config: Config): Promise<PackageToRelease[]> {
log.debug("Identifying packages that need a new release.")

const packagesNeedRelease: Set<string> = new Set()
const grouppedPackages = packageCommits.reduce(
(acc, commit) => {
const changedFilesInCommit = getChangedFiles(commit.commit.short)

for (const { relativeDir, packageJson } of packageList) {
const { name: pkg } = packageJson
if (
changedFilesInCommit.some((changedFile) =>
changedFile.startsWith(relativeDir),
const grouppedPackages = packageCommits.reduce<
Record<string, GrouppedCommits & { version: semver.SemVer | null }>
>((acc, commit) => {
const changedFilesInCommit = getChangedFiles(commit.commit.short)

for (const { relativeDir, packageJson } of packageList) {
const { name: pkg } = packageJson
if (
changedFilesInCommit.some((changedFile) =>
changedFile.startsWith(relativeDir),
)
) {
const dependents = dependentsGraph.get(pkg) ?? []
// Add dependents to the list of packages that need a release
if (dependents.length) {
log.debug(
`\`${bold(pkg)}\` will also bump: ${dependents
.map((d) => bold(d))
.join(", ")}`,
)
) {
const dependents = dependentsGraph.get(pkg) ?? []
// Add dependents to the list of packages that need a release
if (dependents.length) {
log.debug(
`\`${bold(pkg)}\` will also bump: ${dependents
.map((d) => bold(d))
.join(", ")}`,
)
}

if (!(pkg in acc))
acc[pkg] = {
version: semver.parse(packageJson.version),
features: [],
bugfixes: [],
other: [],
breaking: [],
dependents,
}

if (!(pkg in acc))
acc[pkg] = {
features: [],
bugfixes: [],
other: [],
breaking: [],
dependents,
const { type } = commit.parsed
if (RELEASE_COMMIT_TYPES.includes(type)) {
packagesNeedRelease.add(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(),
})
}

const { type } = commit.parsed
if (RELEASE_COMMIT_TYPES.includes(type)) {
packagesNeedRelease.add(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)
}
} else acc[pkg].bugfixes.push(commit)
} else {
acc[pkg].other.push(commit)
}
}
return acc
},
{} as Record<string, GrouppedCommits>,
)
}
return acc
}, {})

if (packagesNeedRelease.size) {
const allPackagesToRelease = Object.entries(grouppedPackages).reduce(
Expand All @@ -173,11 +166,15 @@ export async function analyze(config: Config): Promise<PackageToRelease[]> {
}

const packagesToRelease: Map<string, PackageToRelease> = new Map()
for await (const pkgName of packagesNeedRelease) {
const commits = grouppedPackages[pkgName]
const releaseType: semver.ReleaseType = commits.breaking.length
? "major" // 1.x.x
: commits.features.length
for (const pkgName of packagesNeedRelease) {
const pkg = grouppedPackages[pkgName]
const releaseType: semver.ReleaseType = pkg.breaking.length
? // For 0.x.x we don't need to bump the major even if there are breaking changes
// https://semver.org/#spec-item-4
pkg.version?.major === 0
? "minor" // x.1.x
: "major" // 1.x.x
: pkg.features.length
? "minor" // x.1.x
: "patch" // x.x.1

Expand All @@ -186,7 +183,7 @@ export async function analyze(config: Config): Promise<PackageToRelease[]> {
pkgName,
releaseType,
packagesToRelease,
commits,
pkg,
)

const { dependents } = grouppedPackages[pkgName]
Expand All @@ -201,10 +198,7 @@ export async function analyze(config: Config): Promise<PackageToRelease[]> {
bugfixes: [],
breaking: [],
// List dependency commits under the dependent's "other" category
other: overrideScope(
[...commits.features, ...commits.bugfixes],
pkgName,
),
other: overrideScope([...pkg.features, ...pkg.bugfixes], pkgName),
dependents: [],
},
)
Expand Down Expand Up @@ -264,7 +258,7 @@ function addToPackagesToRelease(
packagesToRelease.set(pkgName, pkgToRelease)
}

function overrideScope(commits: Commit[], scope): Commit[] {
function overrideScope(commits: Commit[], scope: string): Commit[] {
return commits.map((commit) => {
commit.parsed.scope = scope
return commit
Expand Down
16 changes: 13 additions & 3 deletions packages/monorepo-release/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { gray, blue, red, magenta, bold } from "yoctocolors"
import fs from "node:fs/promises"
import path from "node:path"
import { execSync as nodeExecSync } from "node:child_process"
import { Readable } from "node:stream"
import { Config, defaultConfig } from "./config.js"

async function read(
Expand Down Expand Up @@ -37,17 +38,17 @@ function purpleNumber(args: any[]) {
}

export const log = {
debug(...args) {
debug(...args: any[]) {
if (!defaultConfig.verbose) return
const [first, ...rest] = purpleNumber(args)
console.log(gray("[debug]"), `${first}\n${rest.join("\n")}`.trim())
},
info(...args) {
info(...args: any[]) {
if (defaultConfig.peek) return
console.log(blue("[info]"), ...purpleNumber(args))
},
/** Runs even if `config.peek` is set */
peekInfo(...args) {
peekInfo(...args: any[]) {
console.log(args.join("\n"))
},
error(error: Error) {
Expand Down Expand Up @@ -81,3 +82,12 @@ export function pluralize(
return word
}
}

export function streamToArray(stream: Readable): Promise<any[]> {
return new Promise((resolve, reject) => {
const arr: any[] = []
stream.on("data", (d) => arr.push(d))
stream.on("end", () => resolve(arr))
stream.on("error", reject)
})
}
4 changes: 2 additions & 2 deletions packages/monorepo-release/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"esModuleInterop": true,
"noImplicitAny": false,
"noImplicitAny": true,
"declaration": true
}
}
}
13 changes: 0 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3d60d20

Please sign in to comment.