#!/usr/bin/env node // @ts-check const path = require('path') const execa = require('execa') const { Sema } = require('async-sema') const { execSync } = require('child_process') const fs = require('fs') const cwd = process.cwd() ;(async function () { let isCanary = true 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') } } catch (err) { console.log(err) if (err.message && err.message.includes('no tag exactly matches')) { console.log('Nothing to publish, exiting...') return } throw err } console.log(`Publishing ${isCanary ? 'canary' : 'stable'}`) if (!process.env.NPM_TOKEN) { console.log('No NPM_TOKEN, exiting...') return } const packagesDir = path.join(cwd, 'packages') const packageDirs = fs.readdirSync(packagesDir) const publishSema = new Sema(2) const publish = async (pkg, retry = 0) => { let output = '' try { await publishSema.acquire() const child = execa( `npm`, [ 'publish', `${path.join(packagesDir, pkg)}`, '--access', 'public', '--ignore-scripts', ...(isCanary ? ['--tag', 'canary'] : []), ], { stdio: 'pipe' } ) const handleData = (type) => (chunk) => { process[type].write(chunk) output += chunk.toString() } child.stdout?.on('data', handleData('stdout')) child.stderr?.on('data', handleData('stderr')) // Return here to avoid retry logic return await child } catch (err) { console.error(`Failed to publish ${pkg}`, err) if ( output.includes('cannot publish over the previously published versions') ) { console.error('Ignoring already published error', pkg) return } if (retry >= 3) { throw err } } finally { publishSema.release() } // Recursive call need to be outside of the publishSema const retryDelaySeconds = 15 console.log(`retrying in ${retryDelaySeconds}s`) await new Promise((resolve) => setTimeout(resolve, retryDelaySeconds * 1000) ) await publish(pkg, retry + 1) } const undraft = async () => { const githubToken = process.env.RELEASE_BOT_GITHUB_TOKEN if (!githubToken) { throw new Error(`Missing RELEASE_BOT_GITHUB_TOKEN`) } if (isCanary) { 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) return } const undraftRes = await fetch(release.url, { headers: ghHeaders, method: 'PATCH', body: JSON.stringify({ draft: false, name: version, }), }) 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) }) ) if (results.some((item) => item.status === 'rejected')) { console.error(`Not all packages published successfully`, results) process.exit(1) } await undraft() })()