mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-06 12:57:46 +00:00
Use changesets instead of lerna (#9914)
`changesets` will improve our release workflow, since we will no longer need to manually curate and publish the changelog and GitHub Release. Instead, you simply merge the publish PR that the changesets GH action maintains as we push commits to `main`.
This commit is contained in:
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
75
.github/workflows/publish.yml
vendored
75
.github/workflows/publish.yml
vendored
@@ -1,75 +0,0 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '!*'
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check Release
|
||||
id: check-release
|
||||
run: |
|
||||
tag="$(git describe --tags --exact-match 2> /dev/null || :)"
|
||||
if [[ -z "$tag" ]];
|
||||
then
|
||||
echo "IS_RELEASE=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "IS_RELEASE=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Setup Go
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- name: Setup Node
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: install npm@9
|
||||
run: npm i -g npm@9
|
||||
- name: install pnpm@8.3.1
|
||||
run: npm i -g pnpm@8.3.1
|
||||
- name: Install
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: pnpm install
|
||||
- name: Build
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: pnpm build
|
||||
env:
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
- name: Publish
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
run: pnpm publish-from-github
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: 'true'
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
- name: Trigger Update
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
|
||||
script: |
|
||||
const script = require('./utils/trigger-update-workflow.js')
|
||||
await script({ github, context })
|
||||
63
.github/workflows/release.yml
vendored
Normal file
63
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TURBO_REMOTE_ONLY: 'true'
|
||||
TURBO_TEAM: 'vercel'
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: install npm@9
|
||||
run: npm i -g npm@9
|
||||
|
||||
- name: install pnpm@8.3.1
|
||||
run: npm i -g pnpm@8.3.1
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build Packages
|
||||
run: pnpm build
|
||||
env:
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm version:prepare
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: 'true'
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }}
|
||||
GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }}
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
|
||||
- name: Trigger Update (if a Publish Happened)
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GH_TOKEN_PULL_REQUESTS }}
|
||||
script: |
|
||||
const script = require('./utils/trigger-update-workflow.js')
|
||||
await script({ github, context })
|
||||
18
package.json
18
package.json
@@ -4,10 +4,8 @@
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "pnpm@8.3.1",
|
||||
"dependencies": {
|
||||
"lerna": "5.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "2.26.1",
|
||||
"@types/node": "14.18.33",
|
||||
"@typescript-eslint/eslint-plugin": "5.21.0",
|
||||
"@typescript-eslint/parser": "5.21.0",
|
||||
@@ -32,16 +30,10 @@
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-eager": "2.0.2",
|
||||
"ts-jest": "29.1.0",
|
||||
"typescript": "4.9.5",
|
||||
"turbo": "1.9.3"
|
||||
"turbo": "1.9.3",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"version": "pnpm install && git add pnpm-lock.yaml",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "echo 'Run `pnpm changelog` for instructions'",
|
||||
"publish-from-github": "./utils/publish.sh",
|
||||
"changelog": "node utils/changelog.js",
|
||||
"build": "node utils/gen.js && turbo --no-update-notifier run build",
|
||||
"vercel-build": "pnpm build && pnpm run pack && cd api && node -r ts-eager/register ./_lib/script/build.ts",
|
||||
"pre-commit": "lint-staged",
|
||||
@@ -53,7 +45,9 @@
|
||||
"lint": "eslint . --cache --ext .ts,.js",
|
||||
"prettier-check": "prettier --check .",
|
||||
"prepare": "husky install",
|
||||
"pack": "cd utils && node -r ts-eager/register ./pack.ts"
|
||||
"pack": "cd utils && node -r ts-eager/register ./pack.ts",
|
||||
"version:prepare": "changeset version && pnpm install --no-frozen-lockfile",
|
||||
"release": "changeset publish"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./{*,{api,packages,test,utils}/**/*}.{js,ts}": [
|
||||
|
||||
3066
pnpm-lock.yaml
generated
3066
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
112
utils/changelog.js
vendored
112
utils/changelog.js
vendored
@@ -1,112 +0,0 @@
|
||||
const { join } = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const parseCommits = require('./changelog/parse');
|
||||
const filterLog = require('./changelog/filter');
|
||||
const groupLog = require('./changelog/group');
|
||||
|
||||
process.chdir(join(__dirname, '..'));
|
||||
|
||||
async function getLatestStableTag() {
|
||||
const headers = {};
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (token) {
|
||||
headers['authorization'] = `token ${token}`;
|
||||
}
|
||||
|
||||
const res = await fetch(
|
||||
'https://api.github.com/repos/vercel/vercel/releases/latest',
|
||||
{
|
||||
headers,
|
||||
}
|
||||
);
|
||||
const result = await res.json();
|
||||
if (!result.tag_name) {
|
||||
const message = result.message || JSON.stringify(result);
|
||||
throw new Error(`Failed to fetch releases from github: ${message}`);
|
||||
}
|
||||
|
||||
return result.tag_name;
|
||||
}
|
||||
|
||||
function serializeLog(groupedLog) {
|
||||
const serialized = [];
|
||||
|
||||
for (let area of Object.keys(groupedLog)) {
|
||||
if (serialized.length) {
|
||||
// only push a padding-line above area if we already have content
|
||||
serialized.push('');
|
||||
}
|
||||
|
||||
serialized.push(`### ${area}`);
|
||||
serialized.push('');
|
||||
|
||||
for (let line of groupedLog[area]) {
|
||||
serialized.push(`- ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
function generateLog(tagName) {
|
||||
const logLines = execSync(
|
||||
`git log --pretty=format:"%s [%an] &&& %H" ${tagName}...HEAD`
|
||||
)
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n');
|
||||
|
||||
const commits = parseCommits(logLines);
|
||||
const filteredCommits = filterLog(commits);
|
||||
const groupedLog = groupLog(filteredCommits);
|
||||
return serializeLog(groupedLog);
|
||||
}
|
||||
|
||||
function findUniqPackagesAffected(tagName) {
|
||||
const pkgs = new Set(
|
||||
execSync(`git diff --name-only ${tagName}...HEAD`)
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(line => line.startsWith('packages/'))
|
||||
.map(line => line.split('/')[1])
|
||||
.map(pkgName => {
|
||||
try {
|
||||
return require(`../packages/${pkgName}/package.json`).name;
|
||||
} catch {
|
||||
// Failed to read package.json (perhaps the pkg was deleted)
|
||||
}
|
||||
})
|
||||
.filter(s => Boolean(s))
|
||||
);
|
||||
|
||||
if (pkgs.size === 0) {
|
||||
pkgs.add('vercel');
|
||||
}
|
||||
|
||||
return pkgs;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const tagName = await getLatestStableTag();
|
||||
if (!tagName) {
|
||||
throw new Error('Unable to find last GitHub Release tag.');
|
||||
}
|
||||
|
||||
const log = generateLog(tagName);
|
||||
const formattedLog = log.join('\n') || 'NO CHANGES DETECTED';
|
||||
console.log(`Changes since the last stable release (${tagName}):`);
|
||||
console.log(`\n${formattedLog}\n`);
|
||||
|
||||
const pkgs = findUniqPackagesAffected(tagName);
|
||||
const pub = Array.from(pkgs).join(',');
|
||||
console.log('To publish a stable release, execute the following:');
|
||||
console.log(
|
||||
`\npnpx lerna version --message "Publish Stable" --exact --no-private --force-publish=${pub}\n`
|
||||
);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Filters out "Revert" commits as well as the commits they revert, if found.
|
||||
*/
|
||||
function filterReverts(commits) {
|
||||
const revertCommits = commits.filter(commit => commit.revertsHashes.length);
|
||||
const commitHashes = commits.map(commit => commit.hash);
|
||||
|
||||
let hashesToRemove = [];
|
||||
revertCommits.forEach(revertCommit => {
|
||||
const allFound = revertCommit.revertsHashes.every(hash => {
|
||||
return commitHashes.includes(hash);
|
||||
});
|
||||
|
||||
if (allFound) {
|
||||
hashesToRemove = [
|
||||
...hashesToRemove,
|
||||
...revertCommit.revertsHashes,
|
||||
revertCommit.hash,
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
return commits.filter(commit => !hashesToRemove.includes(commit.hash));
|
||||
}
|
||||
|
||||
function normalizeLog(commits) {
|
||||
commits = commits.filter(line => !line.subject.startsWith('Publish '));
|
||||
return filterReverts(commits);
|
||||
}
|
||||
|
||||
module.exports = normalizeLog;
|
||||
@@ -1,14 +0,0 @@
|
||||
function groupLog(commits) {
|
||||
const grouped = {};
|
||||
|
||||
for (let commit of commits) {
|
||||
for (let area of commit.areas) {
|
||||
grouped[area] = grouped[area] || [];
|
||||
grouped[area].push(commit.subject);
|
||||
}
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
module.exports = groupLog;
|
||||
@@ -1,55 +0,0 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const REVERT_MESSAGE_COMMIT_PATTERN = /This reverts commit ([^.^ ]+)/;
|
||||
const AREA_PATTERN = /\[([^\]]+)\]/g;
|
||||
|
||||
function getCommitMessage(hash) {
|
||||
return execSync(`git log --format=%B -n 1 ${hash}`).toString().trim();
|
||||
}
|
||||
|
||||
function parseRevertCommit(message) {
|
||||
// EX: This reverts commit 6dff0875f5f361decdb95ad70a400195006c6bba.
|
||||
// EX: This reverts commit 6dff0875f5f361decdb95ad70a400195006c6bba (#123123).
|
||||
const fullMessageLines = message
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(line => line.startsWith('This reverts commit'));
|
||||
return fullMessageLines.map(
|
||||
line => line.match(REVERT_MESSAGE_COMMIT_PATTERN)[1]
|
||||
);
|
||||
}
|
||||
|
||||
function parseAreas(subject) {
|
||||
const areaChunk = subject.split(' ')[0] || '';
|
||||
const areas = areaChunk.match(AREA_PATTERN);
|
||||
if (!areas) {
|
||||
return ['UNCATEGORIZED'];
|
||||
}
|
||||
|
||||
return areas.map(area => area.substring(1, area.length - 1));
|
||||
}
|
||||
|
||||
function parseCommits(logLines) {
|
||||
const commits = [];
|
||||
|
||||
logLines.forEach(line => {
|
||||
let [subject, hash] = line.split(' &&& ');
|
||||
subject = subject.trim();
|
||||
|
||||
const message = getCommitMessage(hash);
|
||||
const revertsHashes = parseRevertCommit(message);
|
||||
const areas = parseAreas(subject);
|
||||
|
||||
commits.push({
|
||||
hash,
|
||||
areas,
|
||||
subject,
|
||||
message,
|
||||
revertsHashes,
|
||||
});
|
||||
});
|
||||
|
||||
return commits;
|
||||
}
|
||||
|
||||
module.exports = parseCommits;
|
||||
36
utils/publish.sh
vendored
36
utils/publish.sh
vendored
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# `yarn` overwrites this value to use the yarn registry, which we
|
||||
# can't publish to. Unset so that the default npm registry is used.
|
||||
unset npm_config_registry
|
||||
|
||||
if [ -z "$NPM_TOKEN" ]; then
|
||||
echo "NPM_TOKEN not found. Did you forget to assign the GitHub Action secret?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
|
||||
echo "Logged in to npm as: $(npm whoami)"
|
||||
echo "Version of npm is: $(npm --version)"
|
||||
|
||||
dist_tag=""
|
||||
tag="$(git describe --tags --exact-match 2> /dev/null || :)"
|
||||
|
||||
if [ -z "$tag" ]; then
|
||||
echo "Not a tagged commit, skipping publish"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$tag" =~ -canary ]]; then
|
||||
echo "Publishing canary release"
|
||||
dist_tag="--dist-tag canary"
|
||||
else
|
||||
echo "Publishing stable release"
|
||||
fi
|
||||
|
||||
pnpm run lerna publish from-git $dist_tag --no-verify-access --yes
|
||||
|
||||
# always update canary dist-tag as we no longer publish canary versions separate
|
||||
node ./utils/update-canary-tag.js
|
||||
32
utils/update-canary-tag.js
vendored
32
utils/update-canary-tag.js
vendored
@@ -1,32 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const changedFiles = execSync('git diff HEAD~ --name-only')
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map(file => file.trim());
|
||||
|
||||
const changedPackageVersions = new Map();
|
||||
|
||||
for (const file of changedFiles) {
|
||||
if (file.match(/packages\/.+\/package.json/)) {
|
||||
const packageData = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '..', file), 'utf8')
|
||||
);
|
||||
changedPackageVersions.set(packageData.name, packageData.version);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [package, version] of changedPackageVersions) {
|
||||
if (version.includes('canary')) {
|
||||
console.log(
|
||||
`skipping ${package}@${version} as it is already a canary version`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
execSync(`npm dist-tag add ${package}@${version} canary`).toString()
|
||||
);
|
||||
console.log(`updated canary dist-tag for ${package}@${version}`);
|
||||
}
|
||||
}
|
||||
54
utils/update-legacy-name.js
vendored
54
utils/update-legacy-name.js
vendored
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Updates the `package.json` file to contain the legacy "now" `name` field.
|
||||
* The provided argument should be a tag containing the new name.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const npa = require('npm-package-arg');
|
||||
|
||||
const parsed = npa(process.argv[2]);
|
||||
|
||||
// Find the correct directory for this package
|
||||
const packagesDir = join(__dirname, '..', 'packages');
|
||||
const packageDir = fs.readdirSync(packagesDir).find(p => {
|
||||
if (p.startsWith('.')) return false;
|
||||
try {
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(join(packagesDir, p, 'package.json'), 'utf8')
|
||||
);
|
||||
return pkg.name === parsed.name;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
if (!packageDir) {
|
||||
throw new Error(`Could not find the package directory for "${parsed.name}"`);
|
||||
}
|
||||
|
||||
const pkgJsonPath = join(packagesDir, packageDir, 'package.json');
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
||||
const vcName = pkg.name;
|
||||
const version = pkg.version;
|
||||
|
||||
if (pkg.name === '@vercel/client') {
|
||||
// The legacy name for `@vercel/client` is `now-client` (global scope)
|
||||
pkg.name = 'now-client';
|
||||
} else {
|
||||
pkg.name = pkg.name.replace('vercel', 'now');
|
||||
if (pkg.bin && pkg.bin.vercel) {
|
||||
// The legacy "bin" for Now CLI is "now"
|
||||
pkg.bin = { now: pkg.bin.vercel };
|
||||
}
|
||||
}
|
||||
|
||||
const nowName = pkg.name;
|
||||
console.error(`Updated package name: "${vcName}" -> "${nowName}"`);
|
||||
|
||||
fs.writeFileSync(pkgJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
|
||||
// Log the directory name to stdout for the `publish-legacy.sh`
|
||||
// script to consume for the `npm publish` that happens next.
|
||||
const IFS = '|';
|
||||
console.log([packageDir, vcName, nowName, version].join(IFS));
|
||||
Reference in New Issue
Block a user