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:
@@ -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,
|
||||
|
||||
@@ -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
133
scripts/update-changelog.ts
Executable 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
|
||||
}
|
||||
54
scripts/utils/updateChangelog.ts
Executable file
54
scripts/utils/updateChangelog.ts
Executable 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))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user