[16.2.x] Release pipeline updates (#95160)

Co-authored-by: Steven <[email protected]>
This commit is contained in:
Sebastian "Sebbie" Silbermann
2026-06-30 17:55:18 +02:00
committed by GitHub
co-authored by Steven
parent 98941fc427
commit 534d9c144c
20 changed files with 340 additions and 367 deletions
+49 -24
View File
@@ -3,9 +3,13 @@ name: build-and-deploy
on:
push:
# Don't run when tags or graphite base branches are pushed
# Don't run when graphite base branches are pushed
branches-ignore:
- 'graphite-base/**'
# Run on release tags so publishing is driven by the tag push rather than
# the release-commit branch push (see the deploy-target step).
tags:
- 'v*'
# we need the preview tarball for deploy tests
pull_request:
types: [opened, synchronize]
@@ -13,8 +17,11 @@ on:
concurrency:
# Limit concurrent runs to 1 per PR,
# but allow concurrent runs on push if they potentially use different source code
group: ${{ github.event_name == 'pull_request' && format('{0}-pr-{1}', github.workflow, github.ref_name) || format('{0}-sha-{1}', github.workflow, github.sha) }}
# but allow concurrent runs on push if they potentially use different source code.
# The push group includes github.ref so that the branch push and tag push of
# the same release commit (same sha) don't share a group -- otherwise the
# skipped branch run could cancel the production tag run.
group: ${{ github.event_name == 'pull_request' && format('{0}-pr-{1}', github.workflow, github.ref_name) || format('{0}-{1}-{2}', github.workflow, github.ref, github.sha) }}
cancel-in-progress: true
env:
@@ -66,26 +73,34 @@ jobs:
if [[ "$RELEASE_VERSION" == v* ]];
then
echo "value=production" >> $GITHUB_OUTPUT
if [[ "$RELEASE_VERSION" == *-canary.* ]];
if [[ "$GIT_REF" != refs/tags/* ]];
then
RELEASE_ENVIRONMENT="release-canary"
elif [[ "$RELEASE_VERSION" == *-beta.* ]];
then
RELEASE_ENVIRONMENT="release-beta"
elif [[ "$RELEASE_VERSION" == *-rc.* ]];
then
RELEASE_ENVIRONMENT="release-release-candidate"
elif [[ "$RELEASE_VERSION" != *-* ]];
then
RELEASE_ENVIRONMENT="release-stable"
# The release commit reached a branch (e.g. its push to canary).
# Publishing is driven by the tag push for that same commit, so
# skip here to avoid a duplicate production build on the same sha.
echo "value=skipped" >> $GITHUB_OUTPUT
else
echo "value=production" >> $GITHUB_OUTPUT
if [[ "$RELEASE_VERSION" == *-canary.* ]];
then
RELEASE_ENVIRONMENT="release-canary"
elif [[ "$RELEASE_VERSION" == *-beta.* ]];
then
RELEASE_ENVIRONMENT="release-beta"
elif [[ "$RELEASE_VERSION" == *-rc.* ]];
then
RELEASE_ENVIRONMENT="release-release-candidate"
elif [[ "$RELEASE_VERSION" != *-* ]];
then
RELEASE_ENVIRONMENT="release-stable"
fi
if [[ -z "$RELEASE_ENVIRONMENT" ]];
then
echo "::error::Missing release environment for $RELEASE_VERSION"
exit 1
fi
echo "release_environment=$RELEASE_ENVIRONMENT" >> $GITHUB_OUTPUT
fi
if [[ -z "$RELEASE_ENVIRONMENT" ]];
then
echo "::error::Missing release environment for $RELEASE_VERSION"
exit 1
fi
echo "release_environment=$RELEASE_ENVIRONMENT" >> $GITHUB_OUTPUT
elif [ ''"${GIT_REF}"'' == 'refs/heads/next-16-2' ]
then
echo "value=staging" >> $GITHUB_OUTPUT
@@ -100,8 +115,10 @@ jobs:
fi
env:
GIT_REF: ${{ github.ref }}
- name: Print deploy target
run: echo "Deploy target is '${{ steps.deploy-target.outputs.value }}'"
- name: Print deploy plan
run: |
echo "Deploy target is '${{ steps.deploy-target.outputs.value }}'"
echo "Release environment is '${{ steps.deploy-target.outputs.release_environment }}'"
build:
if: ${{ needs.deploy-target.outputs.value != 'skipped' }}
@@ -637,10 +654,18 @@ jobs:
repositories: next.js
permission-contents: write
- run: ./scripts/publish-native.js
- run: ./scripts/publish-release.js
env:
RELEASE_GITHUB_TOKEN: ${{ steps.release-app-token.outputs.token }}
BAIL_ON_NATIVE_WASM_PUBLISH_FAILURE: ${{ vars.BAIL_ON_NATIVE_WASM_PUBLISH_FAILURE }}
- name: Upload pnpm publish summary
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pnpm-publish-summary
path: pnpm-publish-summary.json
if-no-files-found: ignore
publish-turbopack-npm-packages:
# Matches the commit message written by turbopack/xtask/src/publish.rs:377
+1 -1
View File
@@ -96,7 +96,7 @@ jobs:
run: echo "LATEST_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
- name: Check if new commits since last tag
if: ${{ github.event.inputs.releaseType != 'stable' && github.event.inputs.force != true }}
if: ${{ github.event.inputs.releaseType != 'stable' && inputs.force != true }}
run: |
if [ "$LATEST_TAG_COMMIT" = "$LATEST_COMMIT" ]; then
echo "No new commits. Exiting..."
+1 -1
View File
@@ -33,6 +33,6 @@ The type definitions are generated using the TypeScript [`tsc`](https://www.type
If you are actively working on Rust code or you need to test out the most recent Rust code that hasn't been published as a canary yet, you can [install Rust](https://www.rust-lang.org/tools/install) and run `pnpm swc-build-native`.
If you want to test out the wasm build locally, you will need to [install wasm-pack](https://rustwasm.github.io/wasm-pack/installer/). Run `pnpm --filter=@next/swc build-wasm --target <wasm_target>` to build and `node ./scripts/setup-wasm.mjs` to copy it into your `node_modules`. Run next with `NODE_OPTIONS='--no-addons'` to force it to use the wasm binary.
If you want to test out the wasm build locally, you will need to [install wasm-pack](https://wasm-bindgen.github.io/wasm-pack/installer/). Run `pnpm --filter=@next/swc build-wasm --target <wasm_target>` to build and `node ./scripts/setup-wasm.mjs` to copy it into your `node_modules`. Run next with `NODE_OPTIONS='--no-addons'` to force it to use the wasm binary.
If you need to clean the project for any reason, use `pnpm clean`.
-1
View File
@@ -86,7 +86,6 @@
"types": "lerna run types --stream",
"types:test-lib": "tsc --project test/lib/tsconfig.json",
"check-precompiled": "./scripts/check-pre-compiled.sh",
"prepublishOnly": "turbo run build",
"lint-staged": "lint-staged",
"next-with-deps": "./scripts/next-with-deps.sh",
"next": "cross-env NEXT_PRIVATE_LOCAL_DEV=1 NEXT_TELEMETRY_DISABLED=1 NODE_OPTIONS=\"--trace-deprecation --enable-source-maps\" next",
-1
View File
@@ -24,7 +24,6 @@
"dev": "ncc build ./index.ts -w -o dist/",
"prebuild": "node ../../scripts/rm.mjs dist",
"build": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"prepublishOnly": "cd ../../ && turbo run build",
"lint-fix": "pnpm prettier -w --plugin prettier-plugin-tailwindcss 'templates/*-tw/{ts,js}/{app,pages}/**/*.{js,ts,tsx}'"
},
"devDependencies": {
+1 -2
View File
@@ -39,8 +39,7 @@
"scripts": {
"dev": "pnpm build",
"build": "swc -d dist src && pnpm types",
"types": "tsc --skipLibCheck --declaration --emitDeclarationOnly --esModuleInterop --declarationDir dist --rootDir src",
"prepublishOnly": "cd ../../ && turbo run build"
"types": "tsc --skipLibCheck --declaration --emitDeclarationOnly --esModuleInterop --declarationDir dist --rootDir src"
},
"exports": {
".": {
+1 -2
View File
@@ -22,7 +22,6 @@
"scripts": {
"dev": "pnpm build",
"build": "swc -d dist src && pnpm types",
"types": "tsc src/index.ts --skipLibCheck --declaration --emitDeclarationOnly --esModuleInterop --declarationDir dist",
"prepublishOnly": "cd ../../ && turbo run build"
"types": "tsc src/index.ts --skipLibCheck --declaration --emitDeclarationOnly --esModuleInterop --declarationDir dist"
}
}
-1
View File
@@ -15,7 +15,6 @@
"license": "MIT",
"scripts": {
"build": "node ../../scripts/rm.mjs dist && pnpm ncc-fontkit && tsc -d -p tsconfig.build.json",
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "pnpm ncc-fontkit && tsc -d -w -p tsconfig.json",
"typescript": "tsec --noEmit -p tsconfig.json",
"ncc-fontkit": "ncc build ./fontkit.js --minify -o dist/fontkit"
-1
View File
@@ -29,7 +29,6 @@
],
"scripts": {
"build": "pnpm tsc -d -p tsconfig.json",
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "pnpm tsc -d -w -p tsconfig.json",
"test": "jest",
"test:upgrade-fixture": "./scripts/test-upgrade-fixture.sh"
+1 -2
View File
@@ -25,8 +25,7 @@
"prebuild:source": "node ../../scripts/rm.mjs dist",
"types": "tsc --declaration --emitDeclarationOnly --declarationDir dist",
"build:source": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"build": "pnpm build:source && pnpm types",
"prepublishOnly": "cd ../../ && turbo run build"
"build": "pnpm build:source && pnpm types"
},
"devDependencies": {
"@vercel/ncc": "0.34.0",
-1
View File
@@ -13,7 +13,6 @@
"license": "MIT",
"scripts": {
"build": "node ../../scripts/rm.mjs dist && tsc -d -p tsconfig.json",
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "tsc -d -w -p tsconfig.json",
"typescript": "tsec --noEmit -p tsconfig.json"
},
+1 -2
View File
@@ -10,8 +10,7 @@
},
"scripts": {
"build": "microbundle -i src/index.js -o dist/polyfill-module.js -f iife --no-sourcemap --external none --no-pkg-main",
"dev": "pnpm build",
"prepublishOnly": "cd ../../ && turbo run build"
"dev": "pnpm build"
},
"devDependencies": {
"microbundle": "0.15.0"
+1 -2
View File
@@ -10,8 +10,7 @@
},
"scripts": {
"build": "microbundle -i src/index.js -o dist/polyfill-nomodule.js -f iife --no-sourcemap --external none --no-pkg-main",
"dev": "pnpm build",
"prepublishOnly": "cd ../../ && turbo run build"
"dev": "pnpm build"
},
"devDependencies": {
"core-js": "3.38.1",
+1 -2
View File
@@ -27,8 +27,7 @@
"release": "ncc build ./src/index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"build": "pnpm release && pnpm types",
"test": "jest",
"test:watch": "jest --watch",
"prepublishOnly": "cd ../../ && turbo run build"
"test:watch": "jest --watch"
},
"devDependencies": {
"@types/jest": "^29.5.0",
-1
View File
@@ -80,7 +80,6 @@
"scripts": {
"dev": "cross-env NEXT_SERVER_NO_MANGLE=1 taskr",
"build": "taskr release",
"prepublishOnly": "cd ../../ && turbo run build",
"types": "tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --stripInternal --declarationDir dist",
"typescript": "tsec --noEmit",
"ncc-compiled": "taskr ncc",
@@ -13,7 +13,6 @@
"license": "MIT",
"scripts": {
"build": "node ../../scripts/rm.mjs dist && tsc -d -p tsconfig.json",
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "tsc -d -w -p tsconfig.json"
},
"peerDependencies": {
-1
View File
@@ -18,7 +18,6 @@
"license": "MIT",
"scripts": {
"build": "node ../../scripts/rm.mjs dist && tsc -d -p tsconfig.json",
"prepublishOnly": "cd ../../ && turbo run build",
"dev": "tsc -d -w -p tsconfig.json",
"typescript": "tsec --noEmit -p tsconfig.json"
},
-148
View File
@@ -1,148 +0,0 @@
#!/usr/bin/env node
const path = require('path')
const execa = require('execa')
const { Sema } = require('async-sema')
const { readFile, readdir, writeFile, cp } = require('fs/promises')
const cwd = process.cwd()
;(async function () {
try {
const publishSema = new Sema(2)
let version = require('@next/swc/package.json').version
// Copy binaries to package folders, update version, and publish
let nativePackagesDir = path.join(cwd, 'crates/next-napi-bindings/npm')
let platforms = (await readdir(nativePackagesDir)).filter(
(name) => !name.startsWith('.')
)
await Promise.all(
platforms.map(async (platform) => {
await publishSema.acquire()
let output = ''
try {
let binaryName = `next-swc.${platform}.node`
await cp(
path.join(cwd, 'packages/next-swc/native', binaryName),
path.join(nativePackagesDir, platform, binaryName)
)
let pkg = JSON.parse(
await readFile(
path.join(nativePackagesDir, platform, 'package.json')
)
)
pkg.version = version
await writeFile(
path.join(nativePackagesDir, platform, 'package.json'),
JSON.stringify(pkg, null, 2)
)
const child = execa(
`npm`,
[
`publish`,
`${path.join(nativePackagesDir, platform)}`,
`--access`,
`public`,
...(version.includes('canary') ? ['--tag', 'canary'] : []),
],
{ stdio: 'inherit' }
)
const handleData = (type) => (chunk) => {
process[type].write(chunk)
output += chunk.toString()
}
child.stdout?.on('data', handleData('stdout'))
child.stderr?.on('data', handleData('stderr'))
await child
} catch (err) {
// don't block publishing other versions on single platform error
console.error(`Failed to publish`, platform, err)
if (
output.includes(
'cannot publish over the previously published versions'
)
) {
console.error('Ignoring already published error', platform, err)
} else {
// throw err
}
} finally {
publishSema.release()
}
})
)
// Update name/version of wasm packages and publish
const pkgDirectory = 'crates/wasm'
let wasmDir = path.join(cwd, pkgDirectory)
await Promise.all(
['web', 'nodejs'].map(async (wasmTarget) => {
await publishSema.acquire()
let wasmPkg = JSON.parse(
await readFile(path.join(wasmDir, `pkg-${wasmTarget}/package.json`))
)
wasmPkg.name = `@next/swc-wasm-${wasmTarget}`
wasmPkg.version = version
wasmPkg.repository = {
type: 'git',
url: 'https://github.com/vercel/next.js',
directory: pkgDirectory,
}
await writeFile(
path.join(wasmDir, `pkg-${wasmTarget}/package.json`),
JSON.stringify(wasmPkg, null, 2)
)
try {
await execa(
`npm`,
[
'publish',
`${path.join(wasmDir, `pkg-${wasmTarget}`)}`,
'--access',
'public',
...(version.includes('canary') ? ['--tag', 'canary'] : []),
],
{ stdio: 'inherit' }
)
} catch (err) {
// don't block publishing other versions on single platform error
console.error(`Failed to publish`, wasmTarget, err)
if (
err.message &&
err.message.includes(
'You cannot publish over the previously published versions'
)
) {
console.error('Ignoring already published error', wasmTarget)
} else {
// throw err
}
} finally {
publishSema.release()
}
})
)
// Update optional dependencies versions
let nextPkg = JSON.parse(
await readFile(path.join(cwd, 'packages/next/package.json'))
)
for (let platform of platforms) {
let optionalDependencies = nextPkg.optionalDependencies || {}
optionalDependencies['@next/swc-' + platform] = version
nextPkg.optionalDependencies = optionalDependencies
}
await writeFile(
path.join(path.join(cwd, 'packages/next/package.json')),
JSON.stringify(nextPkg, null, 2)
)
} catch (err) {
console.error(err)
process.exit(1)
}
})()
+235 -144
View File
@@ -5,55 +5,38 @@ const path = require('path')
const execa = require('execa')
const semver = require('semver')
const { Sema } = require('async-sema')
const { execSync } = require('child_process')
const fs = require('fs')
const fs = require('fs/promises')
const {
getGitHubToken,
getGitHubTokenMissingMessage,
} = require('./release-github-auth')
const cwd = process.cwd()
const dryRun = process.argv.includes('--dry-run')
const maxPublishAttempts = 4
const publishRetryDelaySeconds = 15
;(async function () {
let isCanary = true
let isReleaseCandidate = false
let isBeta = false
try {
const tagOutput = execSync(
`node ${path.join(__dirname, 'check-is-release.js')}`
).toString()
console.log(tagOutput)
if (tagOutput.trim().startsWith('v')) {
isCanary = tagOutput.includes('-canary')
}
isReleaseCandidate = tagOutput.includes('-rc')
isBeta = tagOutput.includes('-beta')
} catch (err) {
console.log(err)
if (err.message && err.message.includes('no tag exactly matches')) {
console.log('Nothing to publish, exiting...')
return
}
throw err
if (dryRun) {
console.log('Dry run: not publishing to npm')
}
const publishSema = new Sema(2)
let tag = isCanary
? 'canary'
: isReleaseCandidate
? 'rc'
: isBeta
? 'beta'
: 'latest'
const { version } = JSON.parse(
await fs.readFile(path.join(cwd, 'lerna.json'), 'utf-8')
)
const parsedVersion = semver.parse(version)
if (parsedVersion === null) {
throw new Error(`Invalid version in lerna.json: ${version}`)
}
const prereleaseChannel = parsedVersion.prerelease[0]
const isPrerelease = prereleaseChannel != null
console.log(`Publishing ${version}`)
let npmDistTag = isPrerelease ? String(prereleaseChannel) : 'latest'
try {
if (!isCanary && !isReleaseCandidate && !isBeta) {
const version = JSON.parse(
await fs.promises.readFile(path.join(cwd, 'lerna.json'), 'utf-8')
).version
if (!isPrerelease) {
const res = await fetch(
`https://registry.npmjs.org/-/package/next/dist-tags`
)
@@ -67,7 +50,7 @@ const cwd = process.cwd()
// Therefore, we explicitly set the tag as 'backport' for backports.
// But force @latest tag if we accidentally tagged a prerelase as latest
if (!semver.prerelease(tags.latest)) {
tag = 'backport'
npmDistTag = 'backport'
}
}
}
@@ -76,29 +59,13 @@ const cwd = process.cwd()
throw error
}
console.log(`Publishing as "${tag}" dist tag...`)
console.log(`Publishing as "${npmDistTag}" dist tag...`)
const packagesDir = path.join(cwd, 'packages')
const packageDirs = fs.readdirSync(packagesDir)
const publishSema = new Sema(2)
const publish = async (pkg, retry = 0) => {
const publish = async (label, args, attempt = 1) => {
let output = ''
try {
await publishSema.acquire()
const child = execa(
`npm`,
[
'publish',
`${path.join(packagesDir, pkg)}`,
'--access',
'public',
'--ignore-scripts',
'--tag',
tag,
],
{ stdio: 'pipe' }
)
const child = execa('pnpm', args, { stdio: 'pipe' })
const handleData = (type) => (chunk) => {
process[type].write(chunk)
output += chunk.toString()
@@ -108,119 +75,243 @@ const cwd = process.cwd()
// Return here to avoid retry logic
return await child
} catch (err) {
console.error(`Failed to publish ${pkg}`, err)
console.error(
`Failed to publish ${label} (attempt ${attempt} of ${maxPublishAttempts})`,
err
)
if (
output.includes('cannot publish over the previously published versions')
) {
console.error('Ignoring already published error', pkg)
console.error('Ignoring already published error', label)
return
}
if (retry >= 3) {
if (attempt >= maxPublishAttempts) {
throw err
}
} finally {
publishSema.release()
}
// Recursive call need to be outside of the publishSema
const retryDelaySeconds = 15
console.log(`retrying in ${retryDelaySeconds}s`)
console.log(`retrying ${label} in ${publishRetryDelaySeconds}s`)
await new Promise((resolve) =>
setTimeout(resolve, retryDelaySeconds * 1000)
setTimeout(resolve, publishRetryDelaySeconds * 1000)
)
await publish(pkg, retry + 1)
await publish(label, args, attempt + 1)
}
const undraft = async () => {
const githubToken = getGitHubToken()
// Copy binaries to package folders, update version, and publish
const nativePackagesDir = path.join(cwd, 'crates/next-napi-bindings/npm')
const platforms = (await fs.readdir(nativePackagesDir)).filter(
(name) => !name.startsWith('.')
)
if (!githubToken) {
throw new Error(getGitHubTokenMissingMessage())
}
if (isCanary) {
const nativeResults = await Promise.allSettled(
platforms.map(async (platform) => {
const binaryName = `next-swc.${platform}.node`
try {
const ghHeaders = {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${githubToken}`,
'X-GitHub-Api-Version': '2022-11-28',
}
const { version: _version } = require('../lerna.json')
const version = `v${_version}`
let release
let releasesData
// The release might take a minute to show up in
// the list so retry a bit
for (let i = 0; i < 6; i++) {
try {
const releaseUrlRes = await fetch(
`https://api.github.com/repos/vercel/next.js/releases`,
{
headers: ghHeaders,
}
)
releasesData = await releaseUrlRes.json()
release = releasesData.find(
(release) => release.tag_name === version
)
} catch (err) {
console.log(`Fetching release failed`, err)
}
if (!release) {
console.log(`Retrying in 10s...`)
await new Promise((resolve) => setTimeout(resolve, 10 * 1000))
}
}
if (!release) {
console.log(`Failed to find release`, releasesData)
await fs.cp(
path.join(cwd, 'packages/next-swc/native', binaryName),
path.join(nativePackagesDir, platform, binaryName)
)
} catch (error) {
if (dryRun) {
console.warn(
`Binary ${binaryName} not found, but ignoring due to dry run`
)
return
}
throw error
}
const undraftRes = await fetch(release.url, {
headers: ghHeaders,
method: 'PATCH',
body: JSON.stringify({
draft: false,
name: version,
}),
const pkgDir = path.join(nativePackagesDir, platform)
const pkg = JSON.parse(
await fs.readFile(path.join(pkgDir, 'package.json'), {
encoding: 'utf-8',
})
if (undraftRes.ok) {
console.log('un-drafted canary release successfully')
} else {
console.log(`Failed to undraft`, await undraftRes.text())
}
} catch (err) {
console.error(`Failed to undraft release`, err)
}
}
}
const results = await Promise.allSettled(
packageDirs.map(async (packageDir) => {
const pkgJson = JSON.parse(
await fs.promises.readFile(
path.join(packagesDir, packageDir, 'package.json'),
'utf-8'
)
)
if (pkgJson.private) {
console.log(`Skipping private package ${packageDir}`)
return
}
await publish(packageDir)
pkg.version = version
await fs.writeFile(
path.join(pkgDir, 'package.json'),
JSON.stringify(pkg, null, 2)
)
await publish(platform, [
'publish',
pkgDir,
'--access',
'public',
'--no-git-checks',
'--ignore-scripts',
'--tag',
npmDistTag,
...(dryRun ? ['--dry-run'] : []),
])
})
)
if (results.some((item) => item.status === 'rejected')) {
console.error(`Not all packages published successfully`, results)
// Update name/version of wasm packages and publish
const pkgDirectory = 'crates/wasm'
const wasmDir = path.join(cwd, pkgDirectory)
const wasmResults = await Promise.allSettled(
['web', 'nodejs'].map(async (wasmTarget) => {
const pkgDir = path.join(wasmDir, `pkg-${wasmTarget}`)
const wasmPkg = JSON.parse(
await fs.readFile(path.join(pkgDir, 'package.json'), {
encoding: 'utf-8',
})
)
wasmPkg.name = `@next/swc-wasm-${wasmTarget}`
wasmPkg.version = version
wasmPkg.repository = {
type: 'git',
url: 'https://github.com/vercel/next.js',
directory: pkgDirectory,
}
await fs.writeFile(
path.join(pkgDir, 'package.json'),
JSON.stringify(wasmPkg, null, 2)
)
await publish(`wasm-${wasmTarget}`, [
'publish',
pkgDir,
'--access',
'public',
'--no-git-checks',
'--ignore-scripts',
'--tag',
npmDistTag,
...(dryRun ? ['--dry-run'] : []),
])
})
)
if (nativeResults.some((item) => item.status === 'rejected')) {
console.error(
`Not all native packages published successfully`,
nativeResults
)
process.exit(1)
}
await undraft()
})()
if (wasmResults.some((item) => item.status === 'rejected')) {
console.error(`Not all wasm packages published successfully`, wasmResults)
if (process.env.BAIL_ON_NATIVE_WASM_PUBLISH_FAILURE === 'true') {
process.exit(1)
} else {
console.warn(
'Continuing with release even though some wasm packages failed to publish because BAIL_ON_NATIVE_WASM_PUBLISH_FAILURE is not set to true'
)
}
}
// Update optional dependencies versions
const nextPkg = JSON.parse(
await fs.readFile(path.join(cwd, 'packages/next/package.json'), {
encoding: 'utf-8',
})
)
for (const platform of platforms) {
const optionalDependencies = nextPkg.optionalDependencies || {}
optionalDependencies['@next/swc-' + platform] = version
nextPkg.optionalDependencies = optionalDependencies
}
// These props are only needed for development and build so we strip
// before publishing to reduce the total metadata size. The max is
// 100 MB for all versions of a pkg and we are currently at 30MB. Run:
// curl -w '%{size_download}' -so /dev/null https://registry.npmjs.org/next | awk '{print $1/1000000 " MB"}'
for (const field of ['devDependencies', 'taskr', 'scripts']) {
delete nextPkg[field]
}
await fs.writeFile(
path.join(cwd, 'packages/next/package.json'),
JSON.stringify(nextPkg, null, 2)
)
await publish('workspace', [
'--filter',
'./packages/**',
'publish',
'--recursive',
'--access',
'public',
'--no-git-checks',
'--ignore-scripts',
'--report-summary',
'--tag',
npmDistTag,
...(dryRun ? ['--dry-run'] : []),
])
if (dryRun) {
console.log('Dry run: skipping GitHub release un-draft')
return
}
const githubToken = getGitHubToken()
if (!githubToken) {
throw new Error(getGitHubTokenMissingMessage())
}
if (isPrerelease) {
try {
const ghHeaders = {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${githubToken}`,
'X-GitHub-Api-Version': '2022-11-28',
}
const tag = `v${version}`
let release
let releasesData
// The release might take a minute to show up in
// the list so retry a bit
for (let i = 0; i < 6; i++) {
try {
const releaseUrlRes = await fetch(
`https://api.github.com/repos/vercel/next.js/releases`,
{
headers: ghHeaders,
}
)
releasesData = await releaseUrlRes.json()
release = releasesData.find((release) => release.tag_name === tag)
} catch (err) {
console.log(`Fetching release failed`, err)
}
if (!release) {
console.log(`Retrying in 10s...`)
await new Promise((resolve) => setTimeout(resolve, 10 * 1000))
}
}
if (!release) {
console.log(`Failed to find release`, releasesData)
return
}
const undraftRes = await fetch(release.url, {
headers: ghHeaders,
method: 'PATCH',
body: JSON.stringify({
draft: false,
name: tag,
}),
})
if (undraftRes.ok) {
console.log(`un-drafted ${prereleaseChannel} release successfully`)
} else {
console.log(`Failed to undraft`, await undraftRes.text())
}
} catch (err) {
console.error(`Failed to undraft release`, err)
}
}
})().catch((err) => {
console.error(err)
process.exit(1)
})
+48 -29
View File
@@ -19,6 +19,7 @@ async function main() {
const isCanary = releaseType === 'canary'
const isReleaseCandidate = releaseType === 'release-candidate'
const isBeta = releaseType === 'beta'
const dryRun = args.includes('--dry-run')
if (
releaseType !== 'stable' &&
@@ -42,27 +43,33 @@ async function main() {
const githubToken = getGitHubToken()
if (!githubToken) {
console.log(getGitHubTokenMissingMessage())
return
if (dryRun) {
console.log(
'Dry run: keeping commits locally, skipping git push and GitHub release creation'
)
} else {
if (!githubToken) {
console.log(getGitHubTokenMissingMessage())
return
}
const configStorePath = resolveFrom(
path.join(process.cwd(), 'node_modules/release'),
'configstore'
)
const ConfigStore = require(configStorePath)
const config = new ConfigStore('release')
config.set('token', githubToken)
await configureGitHubAuth(githubToken)
await verifyGitHubApiAccess(
githubToken,
'/repos/vercel/next.js/releases?per_page=1',
'release lookup'
)
}
const configStorePath = resolveFrom(
path.join(process.cwd(), 'node_modules/release'),
'configstore'
)
const ConfigStore = require(configStorePath)
const config = new ConfigStore('release')
config.set('token', githubToken)
await configureGitHubAuth(githubToken)
await verifyGitHubApiAccess(
githubToken,
'/repos/vercel/next.js/releases?per_page=1',
'release lookup'
)
console.log(`Running pnpm release-${isCanary ? 'canary' : 'stable'}...`)
const preleaseType =
semverType === 'major'
@@ -87,24 +94,36 @@ async function main() {
lernaArgs.push('--force-publish', '-y', '--no-push')
if (dryRun) {
// So the dry-run can be exercised outside
// of the release branches lerna.json restricts in real publishes.
lernaArgs.push('--allow-branch', '**')
}
const child = execa('pnpm', lernaArgs, {
stdio: 'inherit',
})
await child
await createGitHubReleaseCommit(githubToken)
if (isCanary || isReleaseCandidate || isBeta) {
const releaseChild = execa(
'pnpm',
['release', '--pre', '--skip-questions', '--show-url'],
{
stdio: 'inherit',
}
if (dryRun) {
console.log(
'Dry run: skipping GitHub-signed release commit and GitHub release creation'
)
} else {
await createGitHubReleaseCommit(githubToken)
await releaseChild
if (isCanary || isReleaseCandidate || isBeta) {
const releaseChild = execa(
'pnpm',
['release', '--pre', '--skip-questions', '--show-url'],
{
stdio: 'inherit',
}
)
await releaseChild
}
}
console.log('Release process is finished')