Skip to content

Commit

Permalink
chore: add branch strategy tooling (#6857)
Browse files Browse the repository at this point in the history
  • Loading branch information
jtoar authored Nov 10, 2022
1 parent c734179 commit e459639
Show file tree
Hide file tree
Showing 9 changed files with 859 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"project:sync": "node ./tasks/framework-tools/frameworkSyncToProject.mjs",
"release": "node ./tasks/release/cli.mjs",
"release:test": "NODE_OPTIONS=--experimental-vm-modules ./node_modules/.bin/jest --config ./tasks/release/jest.config.mjs",
"branch-strategy": "yarn node ./tasks/release/branchStrategy/branchStrategyCLI.mjs",
"smoke-test": "cd ./tasks/smoke-test && npx playwright install && npx playwright test",
"test": "lerna run test --concurrency 2 -- --colors --maxWorkers=4",
"test-ci": "lerna run test --concurrency 2 -- --colors --maxWorkers"
Expand Down
23 changes: 23 additions & 0 deletions tasks/release/branchStrategy/branchStrategyCLI.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node
/* eslint-env node, es2022 */

import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'

import * as findPRCommand from './findPRCommand.mjs'
import * as triageMainCommand from './triageMainCommand.mjs'
import * as triageNextCommand from './triageNextCommand.mjs'
import * as validateMilestonesCommand from './validateMilestonesCommand.mjs'

yargs(hideBin(process.argv))
// Config
.scriptName('branch-strategy')
.demandCommand()
.strict()
// Commands
.command(triageMainCommand)
.command(triageNextCommand)
.command(findPRCommand)
.command(validateMilestonesCommand)
// Run
.parse()
162 changes: 162 additions & 0 deletions tasks/release/branchStrategy/branchStrategyLib.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/* eslint-env node, es2022 */

import boxen from 'boxen'
import { $, fs, question, chalk } from 'zx'

export function setupCache(file) {
let cache

try {
cache = JSON.parse(fs.readFileSync(file, 'utf-8'))
cache = new Map(Object.entries(cache))
} catch {
cache = new Map()
}

process.on('exit', () => {
fs.writeFileSync(file, JSON.stringify(Object.fromEntries(cache), null, 2))
})

return cache
}

export const GIT_LOG_OPTIONS = [
'--graph',
'--oneline',
'--boundary',
'--cherry-pick',
'--left-only',
]

export const HASH = /\w{9}/
export const PR = /#(?<pr>\d*)/

export function parseCommit(commit) {
const match = commit.match(HASH)
const [hash] = match

const message = commit.slice(match.index + 10)

const prMatch = message.match(PR)
const pr = prMatch?.groups.pr

return {
hash,
message,
pr,
}
}

export async function isCommitInBranch(branch, message) {
const { stdout } = await $`git log ${branch} --oneline --grep ${message}`
return Boolean(stdout)
}

export function reportNewCommits(commits) {
console.log(
[
`There's ${commits.length} commits in the main branch that aren't in the next branch:`,
'',
commits
.map((commit) => {
const { hash, message } = parseCommit(commit)
return `${chalk.bold(chalk.yellow(hash))} ${message}`
})
.join('\n'),
'',
].join('\n')
)
}

export async function triageCommits(commits) {
for (let commit of commits) {
const { hash, message, pr } = parseCommit(commit)

// eslint-disable-next-line no-constant-condition
while (true) {
const answer = await question(
`Does ${chalk.bold(chalk.yellow(hash))} ${chalk.cyan(
message
)} need to be cherry picked into ${this.branch}? (Y/n/o(pen)) > `
)

commit = this.cache.get(hash)

if (answer === 'o' || answer === 'open') {
await $`open https://github.com/redwoodjs/redwood/pull/${pr}`
continue
}

this.cache.set(hash, {
message: message,
needsCherryPick: answer === '' || answer === 'y' || answer === 'Y',
})

break
}
}
}

export const GIT_LOG_UI = ['o', ' /', '|\\', '| o']

export async function getReleaseBranch() {
const { stdout: gitBranchStdout } = await $`git branch --list release/*`

if (gitBranchStdout.trim().split('\n').length > 1) {
console.log()
console.log("There's more than one release branch")
process.exit(1)
}

return gitBranchStdout.trim()
}

export async function purgeCache(cache, commits, branch) {
const commitHashes = commits.map((commit) => parseCommit(commit).hash)

for (const cachedHash of cache.keys()) {
if (!commitHashes.includes(cachedHash)) {
cache.delete(cachedHash)
}
}

const needsCherryPick = [...cache.entries()].filter(
([, { needsCherryPick }]) => needsCherryPick
)

for (const [hash, { message }] of needsCherryPick) {
if (await isCommitInBranch(branch, message)) {
cache.delete(hash)
}
}
}

export async function updateRemotes() {
await $`git remote update`
console.log()

const { stdout: main } = await $`git rev-list main...origin/main --count`
console.log()

if (parseInt(main.trim())) {
await $`git fetch origin main:main`
console.log()
}

const { stdout: next } = await $`git rev-list next...origin/next --count`
console.log()

if (parseInt(next.trim())) {
await $`git fetch origin next:next`
console.log()
}
}

export function colorKeyBox(colorKey) {
return boxen(colorKey, {
title: 'Key',
padding: 1,
margin: 1,
borderStyle: 'round',
})
}
62 changes: 62 additions & 0 deletions tasks/release/branchStrategy/findPRCommand.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-env node, es2022 */

import { Octokit } from 'octokit'

import {
updateRemotes,
isCommitInBranch,
getReleaseBranch,
} from './branchStrategyLib.mjs'

export const command = 'find-pr <uri>'
export const description = 'Find which branches a PR is in'

export function builder(yargs) {
yargs.positional('pr', {
description: 'The PR URL',
type: 'string',
})
}

export async function handler({ uri }) {
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })

await updateRemotes()

const {
resource: {
mergeCommit: { messageHeadline },
},
} = await octokit.graphql(
`
query GetPR($uri: URI!) {
resource(url: $uri) {
... on PullRequest {
mergeCommit {
messageHeadline
}
}
}
}
`,
{ uri }
)

const isInNext = await isCommitInBranch('next', messageHeadline)
console.log()
const releaseBranch = await getReleaseBranch()
console.log()
const isInRelease = await isCommitInBranch(releaseBranch, messageHeadline)
console.log()

console.log(
[
isInNext
? '✅ This PR is in the next branch'
: `❌ This PR isn't the next branch`,
isInRelease
? `✅ This PR is in the ${releaseBranch} branch`
: `❌ This PR isn't the ${releaseBranch} branch`,
].join('\n')
)
}
130 changes: 130 additions & 0 deletions tasks/release/branchStrategy/triageMainCache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"544374612": {
"message": "Fix dbauth webauthn template (redundant type import) (#6769)",
"needsCherryPick": false
},
"97f6f622e": {
"message": "Fix auth0 decoder import (#6764)",
"needsCherryPick": false
},
"e2ec41f31": {
"message": "Update netlify auth docs (#6748)",
"needsCherryPick": false
},
"ce3426b38": {
"message": "Update auth setup warning message (#6746)",
"needsCherryPick": false
},
"be4c01c1a": {
"message": "Okta: Add packages to setup script (#6732)",
"needsCherryPick": false
},
"810f1fecf": {
"message": "Azure setup auth: Install and import all needed packages (#6736)",
"needsCherryPick": false
},
"c7cb9d975": {
"message": "Setup auth: Update goTrue (#6733)",
"needsCherryPick": false
},
"e05e08071": {
"message": "Auth0 setup: Install correct packages (#6734)",
"needsCherryPick": false
},
"d0be5e823": {
"message": "nhost auth: Add missing packages (#6742)",
"needsCherryPick": false
},
"50586ea0b": {
"message": "Add missing packages to magicLink setup (#6741)",
"needsCherryPick": false
},
"9a2609355": {
"message": "supertokens setup auth: Add missing RW packages (#6744)",
"needsCherryPick": false
},
"af8970fd5": {
"message": "Missing packages for Ethereum auth setup (#6740)",
"needsCherryPick": false
},
"570d7b49d": {
"message": "supabase auth setup: Add missing rw packages (#6743)",
"needsCherryPick": false
},
"968ad3a3c": {
"message": "Update Clerk docs (#6712)",
"needsCherryPick": false
},
"801894efc": {
"message": "Update firebase auth docs (#6717)",
"needsCherryPick": false
},
"443506daf": {
"message": "Clerk: Simplify web implementation (#6713)",
"needsCherryPick": false
},
"60e075f4d": {
"message": "Add auth decoder to clerk auth setup (#6718)",
"needsCherryPick": false
},
"7fbd6ba32": {
"message": "Update the Clerk setup script and templates (#6710)",
"needsCherryPick": false
},
"e01750d96": {
"message": "Fix decouple auth related type errors (#6709)",
"needsCherryPick": false
},
"fa6546440": {
"message": "fix(dbAuth): add required packages to setup command (#6698)",
"needsCherryPick": false
},
"79adb685e": {
"message": "chore: make misc change to trigger canary publishing (#6695)",
"needsCherryPick": false
},
"18eaf3007": {
"message": "chore: run lint fix (#6691)",
"needsCherryPick": false
},
"0942fba9f": {
"message": "Decouple auth (#5985)",
"needsCherryPick": false
},
"c7ce6d6ac": {
"message": "Custom auth: Fix comment in template (#6804)",
"needsCherryPick": false
},
"ca4f2bdb5": {
"message": "fix(deps): update jest monorepo (#6818)",
"needsCherryPick": true
},
"a0b262d0b": {
"message": "fix(deps): update storybook monorepo to v6.5.13 (#6819)",
"needsCherryPick": true
},
"49d829fb5": {
"message": "Handle multiple set-cookie headers (#6812)",
"needsCherryPick": true
},
"64a6dce21": {
"message": "fix(deps): update dependency core-js to v3.26.0 (#6822)",
"needsCherryPick": true
},
"cadb28725": {
"message": "chore(deps): update dependency cypress to v10.11.0 (#6820)",
"needsCherryPick": true
},
"af3716763": {
"message": "fix(deps): update jest monorepo to v29.3.1 (#6848)",
"needsCherryPick": true
},
"7cd1204a5": {
"message": "fix(deps): update prisma monorepo to v4.6.0 (#6851)",
"needsCherryPick": true
},
"1d4b2c4a0": {
"message": "Change to use @iarna/toml instead of toml (#6839)",
"needsCherryPick": true
}
}
Loading

0 comments on commit e459639

Please sign in to comment.