diff --git a/packages/monorepo-release/package.json b/packages/monorepo-release/package.json index 8d61795..7cb555e 100644 --- a/packages/monorepo-release/package.json +++ b/packages/monorepo-release/package.json @@ -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": { diff --git a/packages/monorepo-release/src/analyze.ts b/packages/monorepo-release/src/analyze.ts index b470c83..585398b 100644 --- a/packages/monorepo-release/src/analyze.ts +++ b/packages/monorepo-release/src/analyze.ts @@ -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" @@ -37,23 +37,16 @@ export async function analyze(config: Config): Promise { // 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( - (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( @@ -99,58 +92,58 @@ export async function analyze(config: Config): Promise { log.debug("Identifying packages that need a new release.") const packagesNeedRelease: Set = 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 + >((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, - ) + } + return acc + }, {}) if (packagesNeedRelease.size) { const allPackagesToRelease = Object.entries(grouppedPackages).reduce( @@ -173,11 +166,15 @@ export async function analyze(config: Config): Promise { } const packagesToRelease: Map = 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 @@ -186,7 +183,7 @@ export async function analyze(config: Config): Promise { pkgName, releaseType, packagesToRelease, - commits, + pkg, ) const { dependents } = grouppedPackages[pkgName] @@ -201,10 +198,7 @@ export async function analyze(config: Config): Promise { 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: [], }, ) @@ -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 diff --git a/packages/monorepo-release/src/utils.ts b/packages/monorepo-release/src/utils.ts index 4abde47..825c6c4 100644 --- a/packages/monorepo-release/src/utils.ts +++ b/packages/monorepo-release/src/utils.ts @@ -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( @@ -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) { @@ -81,3 +82,12 @@ export function pluralize( return word } } + +export function streamToArray(stream: Readable): Promise { + return new Promise((resolve, reject) => { + const arr: any[] = [] + stream.on("data", (d) => arr.push(d)) + stream.on("end", () => resolve(arr)) + stream.on("error", reject) + }) +} diff --git a/packages/monorepo-release/tsconfig.json b/packages/monorepo-release/tsconfig.json index 57f8951..5158004 100644 --- a/packages/monorepo-release/tsconfig.json +++ b/packages/monorepo-release/tsconfig.json @@ -12,7 +12,7 @@ "skipDefaultLibCheck": true, "skipLibCheck": true, "esModuleInterop": true, - "noImplicitAny": false, + "noImplicitAny": true, "declaration": true } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4814e1..3232ca4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,9 +25,6 @@ importers: semver: specifier: 7.5.4 version: 7.5.4 - stream-to-array: - specifier: 2.3.0 - version: 2.3.0 yoctocolors: specifier: ^1.0.0 version: 1.0.0 @@ -223,10 +220,6 @@ packages: color-convert: 2.0.1 dev: false - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: false - /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: @@ -928,12 +921,6 @@ packages: readable-stream: 2.3.8 dev: false - /stream-to-array@2.3.0: - resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - dependencies: - any-promise: 1.3.0 - dev: false - /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: