ci: more resilient release script (#6202)
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
"reinstall": "pnpm clean:all && pnpm install",
|
||||
"release:alpha": "tsx ./scripts/release.ts --bump prerelease --tag alpha",
|
||||
"release:beta": "tsx ./scripts/release.ts --bump prerelease --tag beta",
|
||||
"script:list-published": "tsx scripts/lib/getPackageRegistryVersions.ts",
|
||||
"script:pack": "tsx scripts/pack-all-to-dest.ts",
|
||||
"pretest": "pnpm build",
|
||||
"test": "pnpm test:int && pnpm test:components && pnpm test:e2e",
|
||||
|
||||
@@ -2,43 +2,12 @@ import chalk from 'chalk'
|
||||
import pLimit from 'p-limit'
|
||||
|
||||
import { getPackageDetails } from './getPackageDetails.js'
|
||||
import { packageWhitelist } from './whitelist.js'
|
||||
|
||||
const npmRequestLimit = pLimit(40)
|
||||
|
||||
const packages = [
|
||||
'payload',
|
||||
'translations',
|
||||
'ui',
|
||||
'next',
|
||||
'graphql',
|
||||
'db-mongodb',
|
||||
'db-postgres',
|
||||
'richtext-slate',
|
||||
'richtext-lexical',
|
||||
|
||||
'create-payload-app',
|
||||
|
||||
'email-nodemailer',
|
||||
|
||||
'storage-s3',
|
||||
'storage-azure',
|
||||
'storage-gcs',
|
||||
'storage-vercel-blob',
|
||||
|
||||
// Plugins
|
||||
'plugin-cloud',
|
||||
'plugin-cloud-storage',
|
||||
'plugin-form-builder',
|
||||
'plugin-nested-docs',
|
||||
'plugin-redirects',
|
||||
'plugin-search',
|
||||
'plugin-seo',
|
||||
// 'plugin-stripe',
|
||||
// 'plugin-sentry',
|
||||
]
|
||||
|
||||
export const getPackageRegistryVersions = async (): Promise<void> => {
|
||||
const packageDetails = await getPackageDetails(packages)
|
||||
const packageDetails = await getPackageDetails(packageWhitelist)
|
||||
|
||||
const results = await Promise.all(
|
||||
packageDetails.map(async (pkg) =>
|
||||
|
||||
34
scripts/lib/whitelist.ts
Normal file
34
scripts/lib/whitelist.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// Update this list with any packages to publish
|
||||
export const packageWhitelist = [
|
||||
'payload',
|
||||
'translations',
|
||||
'ui',
|
||||
'next',
|
||||
'graphql',
|
||||
'db-mongodb',
|
||||
'db-postgres',
|
||||
'richtext-slate',
|
||||
'richtext-lexical',
|
||||
|
||||
'create-payload-app',
|
||||
|
||||
// Adapters
|
||||
'email-nodemailer',
|
||||
'email-resend',
|
||||
|
||||
'storage-s3',
|
||||
'storage-azure',
|
||||
'storage-gcs',
|
||||
'storage-vercel-blob',
|
||||
|
||||
// Plugins
|
||||
'plugin-cloud',
|
||||
'plugin-cloud-storage',
|
||||
'plugin-form-builder',
|
||||
'plugin-nested-docs',
|
||||
'plugin-redirects',
|
||||
'plugin-search',
|
||||
'plugin-seo',
|
||||
'plugin-stripe',
|
||||
// 'plugin-sentry',
|
||||
]
|
||||
@@ -16,46 +16,12 @@ import { simpleGit } from 'simple-git'
|
||||
import type { PackageDetails } from './lib/getPackageDetails.js'
|
||||
|
||||
import { getPackageDetails } from './lib/getPackageDetails.js'
|
||||
import { packageWhitelist } from './lib/whitelist.js'
|
||||
import { getRecommendedBump } from './utils/getRecommendedBump.js'
|
||||
import { updateChangelog } from './utils/updateChangelog.js'
|
||||
|
||||
const npmPublishLimit = pLimit(6)
|
||||
|
||||
// Update this list with any packages to publish
|
||||
const packageWhitelist = [
|
||||
'payload',
|
||||
'translations',
|
||||
'ui',
|
||||
'next',
|
||||
'graphql',
|
||||
'db-mongodb',
|
||||
'db-postgres',
|
||||
'richtext-slate',
|
||||
'richtext-lexical',
|
||||
|
||||
'create-payload-app',
|
||||
|
||||
// Adapters
|
||||
'email-nodemailer',
|
||||
'email-resend',
|
||||
|
||||
'storage-s3',
|
||||
'storage-azure',
|
||||
'storage-gcs',
|
||||
'storage-vercel-blob',
|
||||
|
||||
// Plugins
|
||||
'plugin-cloud',
|
||||
'plugin-cloud-storage',
|
||||
'plugin-form-builder',
|
||||
'plugin-nested-docs',
|
||||
'plugin-redirects',
|
||||
'plugin-search',
|
||||
'plugin-seo',
|
||||
'plugin-stripe',
|
||||
// 'plugin-sentry',
|
||||
]
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const cwd = path.resolve(dirname, '..')
|
||||
@@ -162,7 +128,11 @@ async function main() {
|
||||
|
||||
// Preview/Update changelog
|
||||
header(`${logPrefix}📝 Updating changelog...`)
|
||||
await updateChangelog({
|
||||
const {
|
||||
changelog: changelogContent,
|
||||
releaseNotes,
|
||||
releaseUrl,
|
||||
} = await updateChangelog({
|
||||
bump,
|
||||
dryRun,
|
||||
toVersion: 'HEAD',
|
||||
@@ -171,6 +141,10 @@ async function main() {
|
||||
writeChangelog: changelog,
|
||||
})
|
||||
|
||||
console.log(chalk.green('\nChangelog Preview:\n'))
|
||||
console.log(chalk.gray(changelogContent) + '\n\n')
|
||||
console.log(`Release URL: ${chalk.dim(releaseUrl)}`)
|
||||
|
||||
let packageDetails = await getPackageDetails(packageWhitelist)
|
||||
|
||||
console.log(chalk.bold(`\n Version: ${monorepoVersion} => ${chalk.green(nextReleaseVersion)}\n`))
|
||||
@@ -238,7 +212,7 @@ async function main() {
|
||||
packageDetails = packageDetails.filter((p) => p.name !== 'payload')
|
||||
runCmd(`pnpm publish -C packages/payload --no-git-checks --json --tag ${tag}`, execOpts)
|
||||
|
||||
const results = await Promise.all(
|
||||
const results = await Promise.allSettled(
|
||||
packageDetails.map((pkg) => publishPackageThrottled(pkg, { dryRun })),
|
||||
)
|
||||
|
||||
@@ -247,7 +221,12 @@ async function main() {
|
||||
// New results format
|
||||
console.log(
|
||||
results
|
||||
.map(({ name, success, details }) => {
|
||||
.map((result) => {
|
||||
if (result.status === 'rejected') {
|
||||
console.error(result.reason)
|
||||
return ` ❌ ${String(result.reason)}`
|
||||
}
|
||||
const { name, success, details } = result.value
|
||||
let summary = ` ${success ? '✅' : '❌'} ${name}`
|
||||
if (details) {
|
||||
summary += `\n ${details}\n`
|
||||
@@ -265,6 +244,8 @@ async function main() {
|
||||
// }
|
||||
|
||||
header('🎉 Done!')
|
||||
|
||||
console.log(chalk.bold.green(`\n\nRelease URL: ${releaseUrl}`))
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
@@ -281,23 +262,36 @@ async function publishPackageThrottled(pkg: PackageDetails, opts?: { dryRun?: bo
|
||||
async function publishSinglePackage(pkg: PackageDetails, opts?: { dryRun?: boolean }) {
|
||||
const { dryRun = false } = opts ?? {}
|
||||
console.log(chalk.bold(`🚀 ${pkg.name} publishing...`))
|
||||
const cmdArgs = ['publish', '-C', pkg.packagePath, '--no-git-checks', '--json', '--tag', tag]
|
||||
if (dryRun) {
|
||||
cmdArgs.push('--dry-run')
|
||||
}
|
||||
const { exitCode, stderr } = await execa('pnpm', cmdArgs, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'ignore', 'pipe'],
|
||||
// stdio: 'inherit',
|
||||
})
|
||||
|
||||
if (exitCode !== 0) {
|
||||
console.log(chalk.bold.red(`\n\n❌ ${pkg.name} ERROR: pnpm publish failed\n\n${stderr}`))
|
||||
return { name: pkg.name, success: false, details: stderr }
|
||||
}
|
||||
try {
|
||||
const cmdArgs = ['publish', '-C', pkg.packagePath, '--no-git-checks', '--json', '--tag', tag]
|
||||
if (dryRun) {
|
||||
cmdArgs.push('--dry-run')
|
||||
}
|
||||
const { exitCode, stderr } = await execa('pnpm', cmdArgs, {
|
||||
cwd,
|
||||
stdio: ['ignore', 'ignore', 'pipe'],
|
||||
// stdio: 'inherit',
|
||||
})
|
||||
|
||||
console.log(`${logPrefix} ${chalk.green(`✅ ${pkg.name} published`)}`)
|
||||
return { name: pkg.name, success: true }
|
||||
if (exitCode !== 0) {
|
||||
console.log(chalk.bold.red(`\n\n❌ ${pkg.name} ERROR: pnpm publish failed\n\n${stderr}`))
|
||||
return { name: pkg.name, success: false, details: stderr }
|
||||
}
|
||||
|
||||
console.log(`${logPrefix} ${chalk.green(`✅ ${pkg.name} published`)}`)
|
||||
return { name: pkg.name, success: true }
|
||||
} catch (err: unknown) {
|
||||
console.error(err)
|
||||
return {
|
||||
name: pkg.name,
|
||||
success: false,
|
||||
details:
|
||||
err instanceof Error
|
||||
? `Error publishing ${pkg.name}: ${err.message}`
|
||||
: `Unexpected error publishing ${pkg.name}: ${String(err)}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function abort(message = 'Abort', exitCode = 1) {
|
||||
|
||||
@@ -19,7 +19,22 @@ type Args = {
|
||||
writeChangelog?: boolean
|
||||
}
|
||||
|
||||
export const updateChangelog = async (args: Args = {}) => {
|
||||
type ChangelogResult = {
|
||||
/**
|
||||
* URL to open releases/new with the changelog pre-filled
|
||||
*/
|
||||
releaseUrl: string
|
||||
/**
|
||||
* The changelog content, does not include contributors
|
||||
*/
|
||||
changelog: string
|
||||
/**
|
||||
* The release notes, includes contributors. This is the content used for the releaseUrl
|
||||
*/
|
||||
releaseNotes: string
|
||||
}
|
||||
|
||||
export const updateChangelog = async (args: Args = {}): Promise<ChangelogResult> => {
|
||||
const { toVersion = 'HEAD', dryRun, bump, openReleaseUrl, writeChangelog } = args
|
||||
|
||||
const fromVersion =
|
||||
@@ -31,7 +46,7 @@ export const updateChangelog = async (args: Args = {}) => {
|
||||
tag !== 'latest' ? 'prerelease' : await getRecommendedBump(fromVersion, toVersion)
|
||||
|
||||
if (bump && bump !== recommendedBump) {
|
||||
console.log(`WARNING: Recommended bump is ${recommendedBump}, but you specified ${bump}`)
|
||||
console.log(`WARNING: Recommended bump is '${recommendedBump}', but you specified '${bump}'`)
|
||||
}
|
||||
|
||||
const calculatedBump = bump || recommendedBump
|
||||
@@ -70,38 +85,40 @@ export const updateChangelog = async (args: Args = {}) => {
|
||||
|
||||
const yyyyMMdd = new Date().toISOString().split('T')[0]
|
||||
// Might need to swap out HEAD for the new proposed version
|
||||
let output = `## [${proposedReleaseVersion}](https://github.com/payloadcms/payload/compare/${fromVersion}...${proposedReleaseVersion}) (${yyyyMMdd})\n\n\n`
|
||||
let changelog = `## [${proposedReleaseVersion}](https://github.com/payloadcms/payload/compare/${fromVersion}...${proposedReleaseVersion}) (${yyyyMMdd})\n\n\n`
|
||||
if (sections.feat.length) {
|
||||
output += `### Features\n\n${sections.feat.join('\n')}\n\n`
|
||||
changelog += `### Features\n\n${sections.feat.join('\n')}\n\n`
|
||||
}
|
||||
if (sections.fix.length) {
|
||||
output += `### Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
||||
changelog += `### Bug Fixes\n\n${sections.fix.join('\n')}\n\n`
|
||||
}
|
||||
if (sections.breaking.length) {
|
||||
output += `### BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
|
||||
changelog += `### BREAKING CHANGES\n\n${sections.breaking.join('\n')}\n\n`
|
||||
}
|
||||
|
||||
console.log(chalk.green('\nChangelog Preview:\n'))
|
||||
console.log(chalk.gray(output))
|
||||
|
||||
if (writeChangelog) {
|
||||
const changelogPath = 'CHANGELOG.md'
|
||||
const changelog = await fse.readFile(changelogPath, 'utf8')
|
||||
const newChangelog = output + '\n\n' + changelog
|
||||
const newChangelog = changelog + '\n\n' + changelog
|
||||
await fse.writeFile(changelogPath, newChangelog)
|
||||
console.log(`Changelog updated at ${changelogPath}`)
|
||||
}
|
||||
|
||||
// Add contributors after writing to file
|
||||
output += contributors
|
||||
const releaseNotes = changelog + contributors
|
||||
|
||||
let url = `https://github.com/payloadcms/payload/releases/new?tag=${proposedReleaseVersion}&title=${proposedReleaseVersion}&body=${encodeURIComponent(output)}`
|
||||
let releaseUrl = `https://github.com/payloadcms/payload/releases/new?tag=${proposedReleaseVersion}&title=${proposedReleaseVersion}&body=${encodeURIComponent(releaseNotes)}`
|
||||
if (tag !== 'latest') {
|
||||
url += `&prerelease=1`
|
||||
releaseUrl += `&prerelease=1`
|
||||
}
|
||||
console.log(`Release URL: ${chalk.dim(url)}`)
|
||||
if (!openReleaseUrl) {
|
||||
await open(url)
|
||||
await open(releaseUrl)
|
||||
}
|
||||
|
||||
return {
|
||||
releaseUrl,
|
||||
changelog,
|
||||
releaseNotes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +197,10 @@ function formatCommitForChangelog(commit: GitCommit, includeBreakingNotes = fals
|
||||
const [rawNotes, _] = commit.body.split('\n\n')
|
||||
const notes = rawNotes
|
||||
.split('\n')
|
||||
.filter((l) => !l.toUpperCase().startsWith('BREAKING'))
|
||||
.map((l) => `> ${l}`)
|
||||
.join('\n')
|
||||
.trim()
|
||||
formatted += `\n\n${notes}`
|
||||
formatted += `\n\n${notes}\n\n`
|
||||
}
|
||||
|
||||
return formatted
|
||||
|
||||
Reference in New Issue
Block a user