ci: conventional commits changelog (#3843)

* feat(live-preview): another oen

* wip: changelog script

* wippppp

* chore: this worked

* wip: changelog working

* chore(script): working changelog gen

* chore(script): update changelog during release
This commit is contained in:
Elliot DeNolf
2023-10-24 14:10:14 -04:00
committed by GitHub
parent 7eee0ec355
commit 00a1ce754a
9 changed files with 723 additions and 49 deletions

View File

@@ -13,13 +13,22 @@ export type PackageDetails = {
newCommits: number
shortName: string
packagePath: string
prevGitTag: string
prevGitTagHash: string
publishedVersion: string
publishDate: string
version: string
}
export const getPackageDetails = async (): Promise<PackageDetails[]> => {
const packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
export const getPackageDetails = async (pkg?: string): Promise<PackageDetails[]> => {
let packageDirs: string[] = []
if (pkg) {
packageDirs = fse.readdirSync(packagesDir).filter((d) => d === pkg)
} else {
packageDirs = fse.readdirSync(packagesDir).filter((d) => d !== 'eslint-config-payload')
}
console.log(packageDirs)
const packageDetails = await Promise.all(
packageDirs.map(async (dirName) => {
const packageJson = await fse.readJson(`${packagesDir}/${dirName}/package.json`)
@@ -49,6 +58,8 @@ export const getPackageDetails = async (): Promise<PackageDetails[]> => {
newCommits: newCommits.total,
shortName: dirName,
packagePath: `packages/${dirName}`,
prevGitTag,
prevGitTagHash,
publishedVersion,
publishDate,
version: packageJson.version,

View File

@@ -1,4 +1,5 @@
import fse from 'fs-extra'
import path from 'path'
import { ExecSyncOptions, execSync } from 'child_process'
import chalk from 'chalk'
import prompts from 'prompts'
@@ -6,12 +7,16 @@ import minimist from 'minimist'
import chalkTemplate from 'chalk-template'
import { PackageDetails, getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
import semver from 'semver'
import { updateChangelog } from './utils/updateChangelog'
import simpleGit from 'simple-git'
const git = simpleGit(path.resolve(__dirname, '..'))
const execOpts: ExecSyncOptions = { stdio: 'inherit' }
const args = minimist(process.argv.slice(2))
async function main() {
const { tag = 'latest', bump = 'patch' } = args
const { tag = 'latest', bump = 'patch', pkg } = args
if (!semver.RELEASE_TYPES.includes(bump)) {
abort(`Invalid bump type: ${bump}.\n\nMust be one of: ${semver.RELEASE_TYPES.join(', ')}`)
@@ -21,33 +26,38 @@ async function main() {
abort(`Prerelease bumps must have tag: beta or canary`)
}
const packageDetails = await getPackageDetails()
const packageDetails = await getPackageDetails(pkg)
showPackageDetails(packageDetails)
const { packagesToRelease } = (await prompts({
type: 'multiselect',
name: 'packagesToRelease',
message: 'Select packages to release',
instructions: 'Space to select. Enter to submit.',
choices: packageDetails.map((p) => {
const title = p?.newCommits ? chalk.bold.green(p?.shortName) : p?.shortName
return {
title,
value: p.shortName,
}
}),
})) as { packagesToRelease: string[] }
let packagesToRelease: string[] = []
if (packageDetails.length > 1 && !pkg) {
;({ packagesToRelease } = (await prompts({
type: 'multiselect',
name: 'packagesToRelease',
message: 'Select packages to release',
instructions: 'Space to select. Enter to submit.',
choices: packageDetails.map((p) => {
const title = p?.newCommits ? chalk.bold.green(p?.shortName) : p?.shortName
return {
title,
value: p.shortName,
}
}),
})) as { packagesToRelease: string[] })
if (!packagesToRelease) {
abort()
}
if (!packagesToRelease) {
abort()
}
if (packagesToRelease.length === 0) {
abort('Please specify a package to publish')
}
if (packagesToRelease.length === 0) {
abort('Please specify a package to publish')
}
if (packagesToRelease.find((p) => p === 'payload' && packagesToRelease.length > 1)) {
abort('Cannot publish payload with other packages. Release Payload first.')
if (packagesToRelease.find((p) => p === 'payload' && packagesToRelease.length > 1)) {
abort('Cannot publish payload with other packages. Release Payload first.')
}
} else {
packagesToRelease = [packageDetails[0].shortName]
}
const packageMap = packageDetails.reduce(
@@ -94,9 +104,19 @@ ${packagesToRelease
const packageObj = await fse.readJson(`${packagePath}/package.json`)
const newVersion = packageObj.version
if (pkg === 'payload') {
const shouldUpdateChangelog = await confirm(`🧑‍💻 Update Changelog?`)
if (shouldUpdateChangelog) {
updateChangelog({ pkg: packageMap[pkg], bump })
}
}
const tagName = `${shortName}/${newVersion}`
const shouldCommit = await confirm(`🧑‍💻 Commit Release?`)
if (shouldCommit) {
if (pkg === 'payload') {
execSync(`git add CHANGELOG.md`, execOpts)
}
execSync(`git add ${packagePath}/package.json`, execOpts)
execSync(`git commit -m "chore(release): ${tagName} [skip ci]" `, execOpts)
}

133
scripts/update-changelog.ts Executable file
View File

@@ -0,0 +1,133 @@
import fse, { createWriteStream, createReadStream } from 'fs-extra'
import { ExecSyncOptions, execSync } from 'child_process'
import chalk from 'chalk'
import path from 'path'
import prompts from 'prompts'
import minimist from 'minimist'
import chalkTemplate from 'chalk-template'
import { PackageDetails, getPackageDetails, showPackageDetails } from './lib/getPackageDetails'
import semver from 'semver'
import addStream from 'add-stream'
import tempfile from 'tempfile'
import concatSream from 'concat-stream'
import getStream from 'get-stream'
import conventionalChangelogCore, {
Options,
Context,
GitRawCommitsOptions,
ParserOptions,
WriterOptions,
} from 'conventional-changelog-core'
import conventionalChangelog, { Options as ChangelogOptions } from 'conventional-changelog'
const execOpts: ExecSyncOptions = { stdio: 'inherit' }
const args = minimist(process.argv.slice(2))
async function main() {
const { tag = 'latest', bump = 'patch' } = args
const packageName = args._[0]
const packageDetails = await getPackageDetails()
showPackageDetails(packageDetails)
let pkg: PackageDetails | undefined
if (packageName) {
pkg = packageDetails.find((p) => p.shortName === packageName)
if (!pkg) {
abort(`Package not found: ${packageName}`)
}
} else {
;({ pkg } = (await prompts({
type: 'select',
name: 'pkg',
message: 'Select package to update changelog',
choices: packageDetails.map((p) => {
const title = p?.newCommits ? chalk.bold.green(p?.shortName) : p?.shortName
return {
title,
value: p,
}
}),
})) as { pkg: PackageDetails })
}
console.log({ pkg })
if (!pkg) {
abort()
process.exit(1)
}
// Prefix to find prev tag
const tagPrefix = pkg.shortName === 'payload' ? 'v' : pkg.prevGitTag.split('/')[0] + '/'
const generateChangelog = await confirm('Generate changelog?')
if (!generateChangelog) {
abort()
}
const nextReleaseVersion = semver.inc(pkg.version, bump) as string
const changelogStream = conventionalChangelog(
{
preset: 'conventionalcommits',
append: true, // Does this work?
// currentTag: pkg.prevGitTag, // The prefix is added automatically apparently?
tagPrefix,
pkg: {
path: `${pkg.packagePath}/package.json`,
},
},
{
version: nextReleaseVersion, // next release
},
{
path: 'packages',
// path: pkg.packagePath,
// from: pkg.prevGitTag,
// to: 'HEAD'
},
).on('error', (err) => {
console.error(err.stack)
console.error(err.toString())
process.exit(1)
})
const changelogFile = 'CHANGELOG.md'
const readStream = fse.createReadStream(changelogFile)
const tmp = tempfile()
changelogStream
.pipe(addStream(readStream))
.pipe(createWriteStream(tmp))
.on('finish', () => {
createReadStream(tmp).pipe(createWriteStream(changelogFile))
})
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
async function abort(message = 'Abort', exitCode = 1) {
console.error(chalk.bold.red(`\n${message}\n`))
process.exit(exitCode)
}
async function confirm(message: string): Promise<boolean> {
const { confirm } = await prompts(
{
name: 'confirm',
initial: false,
message,
type: 'confirm',
},
{
onCancel: () => {
abort()
},
},
)
return confirm
}

View File

@@ -0,0 +1,54 @@
import addStream from 'add-stream'
import { ExecSyncOptions } from 'child_process'
import conventionalChangelog from 'conventional-changelog'
import fse, { createReadStream, createWriteStream } from 'fs-extra'
import minimist from 'minimist'
import semver, { ReleaseType } from 'semver'
import tempfile from 'tempfile'
import { PackageDetails } from '../lib/getPackageDetails'
type Args = {
pkg: PackageDetails
bump: ReleaseType
}
export const updateChangelog = ({ pkg, bump }: Args) => {
// Prefix to find prev tag
const tagPrefix = pkg.shortName === 'payload' ? 'v' : pkg.prevGitTag.split('/')[0] + '/'
const nextReleaseVersion = semver.inc(pkg.version, bump) as string
const changelogStream = conventionalChangelog(
{
preset: 'conventionalcommits',
tagPrefix,
pkg: {
path: `${pkg.packagePath}/package.json`,
},
},
{
version: nextReleaseVersion, // next release
},
{
path: 'packages',
// path: pkg.packagePath,
// from: pkg.prevGitTag,
// to: 'HEAD'
},
).on('error', (err) => {
console.error(err.stack)
console.error(err.toString())
process.exit(1)
})
const changelogFile = 'CHANGELOG.md'
const readStream = fse.createReadStream(changelogFile)
const tmp = tempfile()
changelogStream
.pipe(addStream(readStream))
.pipe(createWriteStream(tmp))
.on('finish', () => {
createReadStream(tmp).pipe(createWriteStream(changelogFile))
})
}