Browse Source

meta: prepare release workflow for beta versions

Antoine du Hamel 1 year ago
parent
commit
cb829d8c2a

+ 12 - 16
.github/workflows/release-candidate.yml

@@ -1,26 +1,22 @@
-name: Release candidate
+name: Release beta candidate
 on:
 on:
   push:
   push:
-    branches: release
+    branches: release-beta
 
 
 env:
 env:
+  BETA_BRANCH: 4.x
   YARN_ENABLE_GLOBAL_CACHE: false
   YARN_ENABLE_GLOBAL_CACHE: false
 
 
 jobs:
 jobs:
-  prepare-release:
+  prepare-beta-release:
     name: Prepare release candidate Pull Request
     name: Prepare release candidate Pull Request
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     steps:
     steps:
       - name: Checkout sources
       - name: Checkout sources
         uses: actions/checkout@v3
         uses: actions/checkout@v3
         with:
         with:
-          branch: release
-      - name: Rebase
-        run: |
-          git fetch origin HEAD --depth=1
-          git config --global user.email "actions@github.com"
-          git config --global user.name "GitHub Actions"
-          git rebase FETCH_HEAD
+          branch: release-beta
+          fetch-depth: 3 # the prepare commit, the merge commit, and the base ones.
       - name: Get yarn cache directory path
       - name: Get yarn cache directory path
         id: yarn-cache-dir-path
         id: yarn-cache-dir-path
         run:
         run:
@@ -43,15 +39,13 @@ jobs:
           # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
           # https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
           CYPRESS_INSTALL_BINARY: 0
           CYPRESS_INSTALL_BINARY: 0
       - name: Bump candidate packages version
       - name: Bump candidate packages version
-        run: corepack yarn version apply --all --json | jq -s > releases.json
+        run:
+          corepack yarn version apply --all --prerelease=beta.%n --json | jq -s
+          > releases.json
       - name: Prepare changelog
       - name: Prepare changelog
         run:
         run:
           corepack yarn workspace @uppy-dev/release update-changelogs
           corepack yarn workspace @uppy-dev/release update-changelogs
           releases.json | xargs git add
           releases.json | xargs git add
-      - name: Update contributors table
-        run:
-          corepack yarn contributors:save && corepack yarn remark -foq README.md
-          && git add README.md
       - name: Update CDN URLs
       - name: Update CDN URLs
         run:
         run:
           corepack yarn workspace @uppy-dev/release update-version-URLs | xargs
           corepack yarn workspace @uppy-dev/release update-version-URLs | xargs
@@ -68,13 +62,15 @@ jobs:
           echo "This is a release candidate for the following packages:" >> commitMessage
           echo "This is a release candidate for the following packages:" >> commitMessage
           echo >> commitMessage
           echo >> commitMessage
           jq -r 'map("- `"+.ident+"`: "+.oldVersion+" -> "+.newVersion) | join("\n") ' < releases.json >> commitMessage
           jq -r 'map("- `"+.ident+"`: "+.oldVersion+" -> "+.newVersion) | join("\n") ' < releases.json >> commitMessage
+          git config --global user.email "actions@github.com"
+          git config --global user.name "GitHub Actions"
           git commit -n --amend --file commitMessage
           git commit -n --amend --file commitMessage
       - name: Open Pull Request
       - name: Open Pull Request
         id: pr_opening
         id: pr_opening
         run: |
         run: |
           git push origin HEAD:release-candidate
           git push origin HEAD:release-candidate
           gh api repos/${{ github.repository }}/pulls \
           gh api repos/${{ github.repository }}/pulls \
-            -F base="$(gh api /repos/${{ github.repository }} | jq -r .default_branch)" \
+            -F base="${{ env.BETA_BRANCH }}" \
             -F head="release-candidate" \
             -F head="release-candidate" \
             -F title="$(head -1 commitMessage)" \
             -F title="$(head -1 commitMessage)" \
             -F body="$(git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1) == "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print substr($0,2) } }')" \
             -F body="$(git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1) == "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print substr($0,2) } }')" \

+ 5 - 3
.github/workflows/release.yml

@@ -57,7 +57,7 @@ jobs:
       - name: Publish to the npm registry
       - name: Publish to the npm registry
         run:
         run:
           corepack yarn workspaces foreach --no-private npm publish --access
           corepack yarn workspaces foreach --no-private npm publish --access
-          public --tolerate-republish
+          public --tag next --tolerate-republish
         env:
         env:
           YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
           YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
       - name: Merge PR
       - name: Merge PR
@@ -85,6 +85,7 @@ jobs:
         run:
         run:
           gh release create uppy@${{ steps.uppyVersion.outputs.version }} -t
           gh release create uppy@${{ steps.uppyVersion.outputs.version }} -t
           "Uppy ${{ steps.uppyVersion.outputs.version }}" -F CHANGELOG.diff.md
           "Uppy ${{ steps.uppyVersion.outputs.version }}" -F CHANGELOG.diff.md
+          --prerelease
         env:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       - name: Upload `uppy` to CDN
       - name: Upload `uppy` to CDN
@@ -112,9 +113,10 @@ jobs:
           }}/git/refs/heads/release-candidate || echo "Already deleted"
           }}/git/refs/heads/release-candidate || echo "Already deleted"
         env:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      - name: Remove release branch
+      - name: Remove release-beta branch
         run:
         run:
-          gh api -X DELETE repos/${{ github.repository }}/git/refs/heads/release
+          gh api -X DELETE repos/${{ github.repository
+          }}/git/refs/heads/release-beta
         env:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       - name: Disable Release workflow
       - name: Disable Release workflow

+ 22 - 15
private/release/choose-semverness.js

@@ -3,7 +3,6 @@
 import { createWriteStream, mkdirSync } from 'node:fs'
 import { createWriteStream, mkdirSync } from 'node:fs'
 import { spawnSync } from 'node:child_process'
 import { spawnSync } from 'node:child_process'
 
 
-import prompts from 'prompts'
 import { TARGET_BRANCH } from './config.js'
 import { TARGET_BRANCH } from './config.js'
 
 
 function maxSemverness (a, b) {
 function maxSemverness (a, b) {
@@ -19,6 +18,7 @@ function maxSemverness (a, b) {
 export default async function pickSemverness (
 export default async function pickSemverness (
   spawnOptions,
   spawnOptions,
   LAST_RELEASE_COMMIT,
   LAST_RELEASE_COMMIT,
+  STABLE_BRANCH_MERGE_BASE_RANGE,
   releaseFileUrl,
   releaseFileUrl,
   packagesList,
   packagesList,
 ) {
 ) {
@@ -45,7 +45,26 @@ export default async function pickSemverness (
       spawnOptions,
       spawnOptions,
     )
     )
     if (stdout.length === 0) {
     if (stdout.length === 0) {
-      console.log(`No commits since last release for ${name}, skipping.`)
+      // eslint-disable-next-line no-shadow
+      const { stdout } = spawnSync(
+        'git',
+        [
+          '--no-pager',
+          'log',
+          '--format=- %s',
+          STABLE_BRANCH_MERGE_BASE_RANGE,
+          '--',
+          location,
+        ],
+        spawnOptions,
+      )
+      if (stdout.length === 0) {
+        console.log(`No commits since last release for ${name}, skipping.`)
+      } else {
+        console.log(`Some commits have landed on the stable branch since last release for ${name}.`)
+        releaseFile.write(`  ${JSON.stringify(name)}: major\n`)
+        uppySemverness = 'major'
+      }
       continue
       continue
     }
     }
     console.log('\n')
     console.log('\n')
@@ -60,19 +79,7 @@ export default async function pickSemverness (
       )}.`,
       )}.`,
     )
     )
 
 
-    const response = await prompts({
-      type: 'select',
-      name: 'value',
-      message: `What should be the semverness of next ${name} release?`,
-      choices: [
-        { title: 'Pre-release', value: 'prerelease' },
-        { title: 'Skip this package', value: '' },
-        { title: 'Patch', value: 'patch' },
-        { title: 'Minor', value: 'minor' },
-        { title: 'Major', value: 'major' },
-      ],
-      initial: 2,
-    })
+    const response = { value: 'major' }
 
 
     if (!response.value) {
     if (!response.value) {
       console.log('Skipping.')
       console.log('Skipping.')

+ 74 - 5
private/release/commit-and-open-pr.js

@@ -1,9 +1,25 @@
 import { spawnSync } from 'node:child_process'
 import { spawnSync } from 'node:child_process'
 import { fileURLToPath } from 'node:url'
 import { fileURLToPath } from 'node:url'
 import prompts from 'prompts'
 import prompts from 'prompts'
-import { REPO_OWNER, REPO_NAME } from './config.js'
+import { REPO_NAME, REPO_OWNER, TARGET_BRANCH } from './config.js'
 
 
-export default async function commit (spawnOptions, ...files) {
+function runProcessOrThrow (...args) {
+  const cp = spawnSync(...args)
+
+  if (cp.status) {
+    console.log(cp.stdout.toString())
+    console.error(cp.stderr.toString())
+    throw new Error(`Non-zero status: ${cp.status}. ${args}`)
+  }
+
+  return cp
+}
+
+function getContentFromProcessSync (...args) {
+  return runProcessOrThrow(...args).stdout.toString().trim()
+}
+
+export default async function commit (spawnOptions, STABLE_HEAD, ...files) {
   console.log(`Now is the time to do manual edits to ${files.join(',')}.`)
   console.log(`Now is the time to do manual edits to ${files.join(',')}.`)
   await prompts({
   await prompts({
     type: 'toggle',
     type: 'toggle',
@@ -16,14 +32,67 @@ export default async function commit (spawnOptions, ...files) {
 
 
   spawnSync('git', ['add', ...files.map(url => fileURLToPath(url))], spawnOptions)
   spawnSync('git', ['add', ...files.map(url => fileURLToPath(url))], spawnOptions)
   spawnSync('git', ['commit', '-n', '-m', 'Prepare next release'], { ...spawnOptions, stdio: 'inherit' })
   spawnSync('git', ['commit', '-n', '-m', 'Prepare next release'], { ...spawnOptions, stdio: 'inherit' })
-  const sha = spawnSync('git', ['rev-parse', 'HEAD'], spawnOptions).stdout.toString().trim()
+
+  // Reverting to the remote head before starting the merge. We keep the git sha
+  // in a variable to cherry-pick it later.
+  const releaseSha = getContentFromProcessSync('git', ['rev-parse', 'HEAD'], spawnOptions)
+  runProcessOrThrow('git', ['reset', 'HEAD^', '--hard'])
+
+  console.log('Attempting to merge changes from stable branch...')
+  {
+    // eslint-disable-next-line no-shadow
+    const { status, stdout, stderr } = spawnSync(
+      'git',
+      [
+        'merge',
+        '--no-edit',
+        '-m',
+        'Merge stable branch',
+        STABLE_HEAD,
+      ],
+      spawnOptions,
+    )
+    if (status) {
+      console.log(stdout.toString())
+      console.error(stderr.toString())
+
+      await prompts({
+        type: 'toggle',
+        name: 'value',
+        message: 'Fix the conflicts, and stage the files. Ready?',
+        initial: true,
+        active: 'yes',
+        inactive: 'yes',
+      })
+
+      // eslint-disable-next-line no-shadow
+      const { status } = spawnSync(
+        'git',
+        [
+          'merge',
+          '--continue',
+        ],
+        { ...spawnOptions, stdio: 'inherit' },
+      )
+
+      if (status) {
+        throw new Error('Merge has failed')
+      }
+    }
+  }
+
+  const mergeSha = getContentFromProcessSync('git', ['rev-parse', 'HEAD'], spawnOptions)
+  runProcessOrThrow('git', ['cherry-pick', releaseSha], spawnOptions)
+  const sha = getContentFromProcessSync('git', ['rev-parse', 'HEAD'], spawnOptions)
+
   const getRemoteCommamnd = `git remote -v | grep '${REPO_OWNER}/${REPO_NAME}' | awk '($3 == "(push)") { print $1; exit }'`
   const getRemoteCommamnd = `git remote -v | grep '${REPO_OWNER}/${REPO_NAME}' | awk '($3 == "(push)") { print $1; exit }'`
   const remote = spawnSync('/bin/sh', ['-c', getRemoteCommamnd]).stdout.toString().trim()
   const remote = spawnSync('/bin/sh', ['-c', getRemoteCommamnd]).stdout.toString().trim()
                  || `git@github.com:${REPO_OWNER}/${REPO_NAME}.git`
                  || `git@github.com:${REPO_OWNER}/${REPO_NAME}.git`
 
 
-  console.log(`Please run \`git push ${remote} ${sha}:refs/heads/release\`.`)
+  console.log(`Please run \`git push ${remote} ${sha}:refs/heads/release-beta\`.`)
   console.log(`An automation will kick off and open a release candidate PR 
   console.log(`An automation will kick off and open a release candidate PR 
     on the GitHub repository. Do not merge it manually! Review the PR (you may need to close and
     on the GitHub repository. Do not merge it manually! Review the PR (you may need to close and
-    re-open so the CI and test will run on it). If everything looks good, approve the PR — 
+    re-open so the CI and test will run on it). If everything looks good, run
+    \`git push ${mergeSha}:refs/heads/${TARGET_BRANCH}\`, and approve the PR —
     this will publish updated packages to npm, then the PR will be merged.`)
     this will publish updated packages to npm, then the PR will be merged.`)
 }
 }

+ 2 - 1
private/release/config.js

@@ -1,3 +1,4 @@
 export const REPO_OWNER = 'transloadit'
 export const REPO_OWNER = 'transloadit'
 export const REPO_NAME = 'uppy'
 export const REPO_NAME = 'uppy'
-export const TARGET_BRANCH = 'main'
+export const TARGET_BRANCH = '4.x'
+export const STABLE_BRANCH = 'main'

+ 11 - 2
private/release/getUpToDateRefsFromGitHub.js

@@ -2,7 +2,7 @@ import fetch from 'node-fetch'
 
 
 import { spawnSync } from 'node:child_process'
 import { spawnSync } from 'node:child_process'
 import prompts from 'prompts'
 import prompts from 'prompts'
-import { TARGET_BRANCH, REPO_NAME, REPO_OWNER } from './config.js'
+import { TARGET_BRANCH, REPO_NAME, REPO_OWNER, STABLE_BRANCH } from './config.js'
 
 
 async function apiCall (endpoint, errorMessage) {
 async function apiCall (endpoint, errorMessage) {
   const response = await fetch(
   const response = await fetch(
@@ -38,6 +38,15 @@ async function getLatestReleaseSHA () {
   ).object.sha
   ).object.sha
 }
 }
 
 
+function getStableBranchMergeBase (REMOTE_HEAD) {
+  spawnSync('git', ['fetch', `https://github.com/${REPO_OWNER}/${REPO_NAME}.git`, STABLE_BRANCH])
+  const STABLE_HEAD = spawnSync('git', ['rev-parse', 'FETCH_HEAD']).stdout.toString().trim()
+  return [[
+    spawnSync('git', ['merge-base', REMOTE_HEAD, 'FETCH_HEAD']).stdout.toString().trim(),
+    STABLE_HEAD,
+  ].join('..'), STABLE_HEAD]
+}
+
 async function getLocalHEAD () {
 async function getLocalHEAD () {
   return spawnSync('git', ['rev-parse', 'HEAD']).stdout.toString().trim()
   return spawnSync('git', ['rev-parse', 'HEAD']).stdout.toString().trim()
 }
 }
@@ -104,5 +113,5 @@ export async function validateGitStatus (spawnOptions) {
     }
     }
   }
   }
 
 
-  return [await latestRelease, LOCAL_HEAD]
+  return [await latestRelease, LOCAL_HEAD, ...getStableBranchMergeBase(REMOTE_HEAD)]
 }
 }

+ 3 - 3
private/release/interactive.js

@@ -14,14 +14,14 @@ const deferredReleaseFile = new URL('./.yarn/versions/next.yml', ROOT)
 const temporaryChangeLog = new URL('./CHANGELOG.next.md', ROOT)
 const temporaryChangeLog = new URL('./CHANGELOG.next.md', ROOT)
 
 
 console.log('Validating local repo status and get previous release info...')
 console.log('Validating local repo status and get previous release info...')
-const [LAST_RELEASE_COMMIT, LOCAL_HEAD] = await validateGitStatus(spawnOptions)
+const [LAST_RELEASE_COMMIT, LOCAL_HEAD, MERGE_BASE, STABLE_HEAD] = await validateGitStatus(spawnOptions)
 try {
 try {
   console.log('Local git repository is ready, starting release process...')
   console.log('Local git repository is ready, starting release process...')
-  await pickSemverness(spawnOptions, LAST_RELEASE_COMMIT, deferredReleaseFile, process.env.PACKAGES.split(' '))
+  await pickSemverness(spawnOptions, LAST_RELEASE_COMMIT, MERGE_BASE, deferredReleaseFile, process.env.PACKAGES.split(' '))
   console.log('Working on the changelog...')
   console.log('Working on the changelog...')
   await formatChangeLog(spawnOptions, LAST_RELEASE_COMMIT, temporaryChangeLog)
   await formatChangeLog(spawnOptions, LAST_RELEASE_COMMIT, temporaryChangeLog)
   console.log('Final step...')
   console.log('Final step...')
-  await commit(spawnOptions, deferredReleaseFile, temporaryChangeLog)
+  await commit(spawnOptions, STABLE_HEAD, deferredReleaseFile, temporaryChangeLog)
 } finally {
 } finally {
   console.log('Rewinding git history...')
   console.log('Rewinding git history...')
   await rewindGitHistory(spawnOptions, LOCAL_HEAD)
   await rewindGitHistory(spawnOptions, LOCAL_HEAD)