ci: canary releases (#6308)

This commit is contained in:
Elliot DeNolf
2024-05-09 23:12:47 -04:00
committed by GitHub
parent ffa20aa7d0
commit f41576dd65
7 changed files with 401 additions and 7 deletions

View File

@@ -2,12 +2,12 @@ import chalk from 'chalk'
import pLimit from 'p-limit'
import { getPackageDetails } from './getPackageDetails.js'
import { packageWhitelist } from './whitelist.js'
import { packagePublishList } from './publishList.js'
const npmRequestLimit = pLimit(40)
export const getPackageRegistryVersions = async (): Promise<void> => {
const packageDetails = await getPackageDetails(packageWhitelist)
const packageDetails = await getPackageDetails(packagePublishList)
const results = await Promise.all(
packageDetails.map(async (pkg) =>

231
scripts/lib/getWorkspace.ts Normal file
View File

@@ -0,0 +1,231 @@
import type { ReleaseType } from 'semver'
import { execSync } from 'child_process'
import execa from 'execa'
import fse from 'fs-extra'
import { fileURLToPath } from 'node:url'
import pLimit from 'p-limit'
import path from 'path'
import semver from 'semver'
import { getPackageDetails } from './getPackageDetails.js'
import { packagePublishList } from './publishList.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const projectRoot = path.resolve(dirname, '../../')
const rootPackageJsonPath = path.resolve(projectRoot, 'package.json')
const npmPublishLimit = pLimit(5)
const cwd = path.resolve(dirname, '../../')
const execaOpts: execa.Options = { stdio: 'inherit' }
type PackageDetails = {
/** Name in package.json / npm registry */
name: string
/** Full path to package relative to project root */
packagePath: `packages/${string}`
/** Short name is the directory name */
shortName: string
/** Version in package.json */
version: string
}
type PackageReleaseType = 'canary' | ReleaseType
type PublishResult = {
name: string
success: boolean
details?: string
}
type PublishOpts = {
dryRun?: boolean
tag?: 'beta' | 'canary' | 'latest'
}
type Workspace = {
version: () => Promise<string>
tag: string
packages: PackageDetails[]
showVersions: () => Promise<void>
bumpVersion: (type: PackageReleaseType) => Promise<void>
build: () => Promise<void>
publish: (opts: PublishOpts) => Promise<void>
publishSync: (opts: PublishOpts) => Promise<void>
}
export const getWorkspace = async () => {
const build = async () => {
await execa('pnpm', ['install'], execaOpts)
const buildResult = await execa('pnpm', ['build:all', '--output-logs=errors-only'], execaOpts)
if (buildResult.exitCode !== 0) {
console.error('Build failed')
console.log(buildResult.stderr)
throw new Error('Build failed')
}
}
// Publish one package at a time
const publishSync: Workspace['publishSync'] = async ({ dryRun, tag = 'canary' }) => {
const packageDetails = await getPackageDetails(packagePublishList)
const results: PublishResult[] = []
for (const pkg of packageDetails) {
const res = await publishSinglePackage(pkg, { dryRun, tag })
results.push(res)
}
console.log(`\n\nResults:\n`)
console.log(
results
.map((result) => {
if (!result.success) {
console.error(result.details)
return `${result.name}`
}
return `${result.name}`
})
.join('\n') + '\n',
)
}
const publish = async () => {
const packageDetails = await getPackageDetails(packagePublishList)
const results = await Promise.allSettled(
packageDetails.map((pkg) => publishPackageThrottled(pkg, { dryRun: true })),
)
console.log(`\n\nResults:\n`)
console.log(
results
.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`
}
return summary
})
.join('\n') + '\n',
)
}
const showVersions = async () => {
const { packages, version } = await getCurrentPackageState()
console.log(`\n Version: ${version}\n`)
console.log(` Changes (${packages.length} packages):\n`)
console.log(`${packages.map((p) => ` - ${p.name.padEnd(32)} ${p.version}`).join('\n')}\n`)
}
const setVersion = async (version: string) => {
const rootPackageJson = await fse.readJSON(rootPackageJsonPath)
rootPackageJson.version = version
await fse.writeJSON(rootPackageJsonPath, rootPackageJson, { spaces: 2 })
const packageJsons = await getPackageDetails(packagePublishList)
await Promise.all(
packageJsons.map(async (pkg) => {
const packageJson = await fse.readJSON(`${pkg.packagePath}/package.json`)
packageJson.version = version
await fse.writeJSON(`${pkg.packagePath}/package.json`, packageJson, { spaces: 2 })
}),
)
}
const bumpVersion = async (bumpType: PackageReleaseType) => {
const { version: monorepoVersion, packages: packageDetails } = await getCurrentPackageState()
let nextReleaseVersion
if (bumpType === 'canary') {
const hash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim().slice(0, 7)
nextReleaseVersion = semver.inc(monorepoVersion, 'patch') + `-canary.${hash}`
} else {
nextReleaseVersion = semver.inc(monorepoVersion, bumpType)
}
console.log(`\n Version: ${monorepoVersion} => ${nextReleaseVersion}\n`)
console.log(` Bump: ${bumpType}`)
console.log(` Changes (${packageDetails.length} packages):\n`)
console.log(
`${packageDetails.map((p) => ` - ${p.name.padEnd(32)} ${p.version} => ${nextReleaseVersion}`).join('\n')}\n`,
)
await setVersion(nextReleaseVersion)
}
const workspace: Workspace = {
version: async () => (await fse.readJSON(rootPackageJsonPath)).version,
tag: 'latest',
packages: await getPackageDetails(packagePublishList),
showVersions,
bumpVersion,
build,
publish,
publishSync,
}
return workspace
}
async function getCurrentPackageState(): Promise<{
packages: PackageDetails[]
version: string
}> {
const packageDetails = await getPackageDetails(packagePublishList)
const rootPackageJson = await fse.readJSON(rootPackageJsonPath)
return { packages: packageDetails, version: rootPackageJson.version }
}
/** Publish with promise concurrency throttling */
async function publishPackageThrottled(pkg: PackageDetails, opts?: { dryRun?: boolean }) {
const { dryRun = true } = opts ?? {}
return npmPublishLimit(() => publishSinglePackage(pkg, { dryRun }))
}
async function publishSinglePackage(pkg: PackageDetails, opts: PublishOpts) {
console.log(`🚀 ${pkg.name} publishing...`)
const { dryRun, tag = 'canary' } = opts
try {
const cmdArgs = ['publish', '-C', pkg.packagePath, '--no-git-checks', '--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(`\n\n❌ ${pkg.name} ERROR: pnpm publish failed\n\n${stderr}`)
return {
name: pkg.name,
success: false,
details: `Exit Code: ${exitCode}, stderr: ${stderr}`,
}
}
console.log(`${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)}`,
}
}
}

View File

@@ -1,5 +1,9 @@
// Update this list with any packages to publish
export const packageWhitelist = [
/**
* Packages that should be published to NPM
*
* Note that this does not include all packages in the monorepo
*/
export const packagePublishList = [
'payload',
'translations',
'ui',
@@ -32,5 +36,11 @@ export const packageWhitelist = [
'plugin-search',
'plugin-seo',
'plugin-stripe',
// 'plugin-sentry',
// Unpublished
// 'plugin-sentry'
// 'storage-uploadthing',
// 'eslint-config-payload',
// 'eslint-plugin-payload',
// 'live-preview-vue',
]

72
scripts/publish-canary.ts Executable file
View File

@@ -0,0 +1,72 @@
import type { ExecSyncOptions } from 'child_process'
import type execa from 'execa'
import chalk from 'chalk'
import { loadChangelogConfig } from 'changelogen'
import { execSync } from 'child_process'
import fse from 'fs-extra'
import minimist from 'minimist'
import { fileURLToPath } from 'node:url'
import pLimit from 'p-limit'
import path from 'path'
import prompts from 'prompts'
import semver from 'semver'
import type { PackageDetails } from './lib/getPackageDetails.js'
import { getPackageDetails } from './lib/getPackageDetails.js'
import { getPackageRegistryVersions } from './lib/getPackageRegistryVersions.js'
import { getWorkspace } from './lib/getWorkspace.js'
import { packagePublishList } from './lib/publishList.js'
import { getRecommendedBump } from './utils/getRecommendedBump.js'
import { updateChangelog } from './utils/updateChangelog.js'
const npmPublishLimit = pLimit(5)
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const cwd = path.resolve(dirname, '..')
const execOpts: ExecSyncOptions = { stdio: 'inherit' }
const execaOpts: execa.Options = { stdio: 'inherit' }
const args = minimist(process.argv.slice(2))
// const {
// bump = 'patch', // Semver release type
// changelog = false, // Whether to update the changelog. WARNING: This gets throttled on too many commits
// 'dry-run': dryRun,
// 'git-tag': gitTag = true, // Whether to run git tag and commit operations
// 'git-commit': gitCommit = true, // Whether to run git commit operations
// tag = 'latest',
// } = args
const dryRun = true
const logPrefix = dryRun ? chalk.bold.magenta('[dry-run] >') : ''
async function main() {
const workspace = await getWorkspace()
await workspace.bumpVersion('canary')
await workspace.build()
await workspace.publishSync({ dryRun: false, tag: 'canary' })
header('🎉 Done!')
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
function abort(message = 'Abort', exitCode = 1) {
console.error(chalk.bold.red(`\n${message}\n`))
process.exit(exitCode)
}
function header(message: string, opts?: { enable?: boolean }) {
const { enable } = opts ?? {}
if (!enable) return
console.log(chalk.bold.green(`${message}\n`))
}

View File

@@ -16,7 +16,7 @@ import type { PackageDetails } from './lib/getPackageDetails.js'
import { getPackageDetails } from './lib/getPackageDetails.js'
import { getPackageRegistryVersions } from './lib/getPackageRegistryVersions.js'
import { packageWhitelist } from './lib/whitelist.js'
import { packagePublishList } from './lib/publishList.js'
import { getRecommendedBump } from './utils/getRecommendedBump.js'
import { updateChangelog } from './utils/updateChangelog.js'
@@ -143,7 +143,7 @@ async function main() {
console.log(chalk.gray(changelogContent) + '\n\n')
console.log(`Release URL: ${chalk.dim(releaseUrl)}`)
let packageDetails = await getPackageDetails(packageWhitelist)
let packageDetails = await getPackageDetails(packagePublishList)
console.log(chalk.bold(`\n Version: ${monorepoVersion} => ${chalk.green(nextReleaseVersion)}\n`))
console.log(chalk.bold.yellow(` Bump: ${bump}`))