diff --git a/.pnp.cjs b/.pnp.cjs index cf5548e..452bb0d 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -30,6 +30,7 @@ const RAW_RUNTIME_STATE = ["@types/prompts", "npm:2.4.9"],\ ["@types/semver", "npm:7.5.8"],\ ["boxen", "npm:7.1.1"],\ + ["dotenv", "npm:16.4.5"],\ ["execa", "npm:8.0.1"],\ ["prompts", "npm:2.4.2"],\ ["semver", "npm:7.6.0"],\ @@ -992,6 +993,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["dotenv", [\ + ["npm:16.4.5", {\ + "packageLocation": "./.yarn/cache/dotenv-npm-16.4.5-bcb20eb95d-48d9287007.zip/node_modules/dotenv/",\ + "packageDependencies": [\ + ["dotenv", "npm:16.4.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["duplexer", [\ ["npm:0.1.2", {\ "packageLocation": "./.yarn/cache/duplexer-npm-0.1.2-952c810235-c57bcd4bdf.zip/node_modules/duplexer/",\ @@ -2151,6 +2161,7 @@ const RAW_RUNTIME_STATE = ["@types/prompts", "npm:2.4.9"],\ ["@types/semver", "npm:7.5.8"],\ ["boxen", "npm:7.1.1"],\ + ["dotenv", "npm:16.4.5"],\ ["execa", "npm:8.0.1"],\ ["prompts", "npm:2.4.2"],\ ["semver", "npm:7.6.0"],\ diff --git a/.yarn/cache/dotenv-npm-16.4.5-bcb20eb95d-48d9287007.zip b/.yarn/cache/dotenv-npm-16.4.5-bcb20eb95d-48d9287007.zip new file mode 100644 index 0000000..26a22ca Binary files /dev/null and b/.yarn/cache/dotenv-npm-16.4.5-bcb20eb95d-48d9287007.zip differ diff --git a/README.md b/README.md index 95ef3b0..a5bbca5 100755 --- a/README.md +++ b/README.md @@ -1,20 +1,50 @@ # Release tooling -Release tooling for Redwood. See the [package.json](./package.json) scripts for the available commands. +This repository contains release tooling for the [Redwood monorepo](https://github.com/redwoodjs/redwood). ## Quick Start -- In your shell start-up file (e.g. `~/.zshrc` or `~/.bashrc`), add the following environment variable: +- In your shell start-up file (e.g. `~/.zshrc` or `~/.bashrc`) or a `.env` file (that you create) in this directory, add the following environment variable: ```bash - export RWFW_PATH='/path/to/redwoodjs/redwood' + touch .env + + echo "export RWFW_PATH='/path/to/redwoodjs/redwood'" >> .env + echo "export REDWOOD_GITHUB_TOKEN='...'" >> .env + ``` + + Where `RWFW_PATH` is the path to your local copy of the Redwood monorepo and `REDWOOD_GITHUB_TOKEN` is a personal access token with the `public_repo` scope. + +- Check out the `main` and `next` branches from the Redwood monorepo's remote. + + ```bash + cd $RWFW_PATH + + git fetch + git switch main + git switch next ``` - Where `RWFW_PATH` is the path to your local copy of the Redwood monorepo. +- Run `yarn install` in this directory. It should fly by! This project uses Yarn's [Zero-installs](https://yarnpkg.com/features/caching#zero-installs). + +## Commands + +### Triage + +Redwood has a dual-branch strategy. +Most of the work involves moving commits from main to next and then from next to a release branch. +The triage command guides you through this process: + +``` +yarn triage +``` + +### Release -- Make sure that you've checked out the `main` and `next` branches from the Redwood monorepo's remote and have the latest changes on your machine. +When it's time to release, the release command walks you through the process: -## Notes +``` +yarn release +``` -- This project uses Yarn's [Zero-installs](https://yarnpkg.com/features/caching#zero-installs) -- The code for release is incomplete. Use the release script in the Redwood monorepo for the time being. +Ensure you have access to the RedwoodJS organization on NPM first. diff --git a/lib/assert_github_token.test.ts b/lib/assert_github_token.test.ts new file mode 100644 index 0000000..da31d8e --- /dev/null +++ b/lib/assert_github_token.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' + +import { assertGitHubToken } from './assert_github_token.js' + +describe('assertGitHubToken', () => { + it('works', () => { + expect(process.env.REDWOOD_GITHUB_TOKEN).toBeDefined() + expect(assertGitHubToken).not.toThrow() + }) + + it("throws if `REDWOOD_GITHUB_TOKEN` isn't defined", () => { + const originalToken = process.env.REDWOOD_GITHUB_TOKEN + delete process.env.REDWOOD_GITHUB_TOKEN + expect(process.env.REDWOOD_GITHUB_TOKEN).toBeUndefined() + expect(assertGitHubToken).toThrow() + process.env.REDWOOD_GITHUB_TOKEN = originalToken + }) +}) diff --git a/lib/assert_github_token.ts b/lib/assert_github_token.ts new file mode 100644 index 0000000..29bdbd8 --- /dev/null +++ b/lib/assert_github_token.ts @@ -0,0 +1,21 @@ +import { chalk } from 'zx' + +import { CustomError } from './custom_error.js' + +export function assertGitHubToken() { + if (process.env.REDWOOD_GITHUB_TOKEN) { + return + } + + throw new CustomError([ + `The ${chalk.magenta('REDWOOD_GITHUB_TOKEN')} environment variable isn't set. Set it to a GitHub personal access token:`, + '', + ` ${chalk.green("export REDWOOD_GITHUB_TOKEN='...'")}`, + '', + `in one of your shell start-up files (e.g. ${chalk.magenta('~/.bashrc')} or ${chalk.magenta('~/.zshrc')})`, + 'or in a .env file in this directory that you create.', + '', + `You can create a new personal access token at https://github.com/settings/tokens`, + `All it needs is the ${chalk.magenta('public_repo')} scope` + ].join('\n')) +} diff --git a/lib/branches.test.ts b/lib/branches.test.ts new file mode 100644 index 0000000..89e0fb8 --- /dev/null +++ b/lib/branches.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest' + +import { branchExists, getReleaseBranchesFromStdout } from './branches.js' + +describe('branchExists', () => { + it('works', async () => { + const exists = await branchExists('main') + expect(exists).toBe(true) + }) + + it("returns false if the branch doesn't exist", async () => { + const exists = await branchExists('nonexistent-branch') + expect(exists).toBe(false) + }) +}) + +describe('getReleaseBranches', () => { + it('works', () => { + const releaseBranches = getReleaseBranchesFromStdout('release/patch/v7.0.4\nrelease/minor/v7.1.0\nrelease/patch/v7.0.5') + expect(releaseBranches).toEqual(["release/minor/v7.1.0", "release/patch/v7.0.5", "release/patch/v7.0.4"]) + }) + + it("returns an empty array if there's no release branches", () => { + const releaseBranches = getReleaseBranchesFromStdout('') + expect(releaseBranches).toEqual([]) + }) + + it('handles a release branch currently being checked out', () => { + const releaseBranches = getReleaseBranchesFromStdout('release/patch/v7.0.4\n* release/patch/v7.0.5') + expect(releaseBranches).toEqual(["release/patch/v7.0.5", "release/patch/v7.0.4"]) + }) +}) diff --git a/lib/branches.ts b/lib/branches.ts new file mode 100644 index 0000000..7f4a844 --- /dev/null +++ b/lib/branches.ts @@ -0,0 +1,76 @@ +import semver from 'semver' +import { chalk, $ } from 'zx' + +import { CustomError } from './custom_error.js' +import { unwrap } from './zx_helpers.js' + +export async function assertWorkTreeIsClean() { + const workTreeIsClean = unwrap(await $`git status -s`) === '' + if (!workTreeIsClean) { + throw new CustomError( + `The working tree at ${chalk.magenta(process.cwd())} isn't clean. Commit or stash your changes` + ); + } + console.log('✨ The working tree is clean') +} + +export async function branchExists(branch: string) { + return !!unwrap(await $`git branch --list ${branch}`) +} + +export async function assertBranchExistsAndTracksRemote(branch: string, remote: string) { + if (!(await branchExists(branch))) { + throw new CustomError([ + `The ${chalk.magenta(branch)} branch doesn't exist locally. Check it out from the Redwood remote:`, + '', + chalk.green(` git checkout -b ${branch} ${remote}/${branch}`), + ].join('\n')) + } + console.log(`🏠 The ${chalk.magenta(branch)} branch exists locally`) + + const trackingBranch = unwrap(await $`git rev-parse --abbrev-ref ${branch}@{upstream}`) + if (trackingBranch === `${remote}/${branch}`) { + console.log(`🆗 The ${chalk.magenta(branch)} branch tracks ${chalk.magenta(`${remote}/${branch}`)}`) + return + } + + throw new CustomError([ + `The ${chalk.magenta(branch)} branch doesn't track ${chalk.magenta(`${remote}/${branch}`)}`, + `It's currently tracking ${chalk.magenta(trackingBranch)}`, + '', + `Make it track the remote with:`, + '', + chalk.green(` git branch -u ${remote}/${branch}`), + ].join('\n')) +} + +export async function pullBranch(branch: string, remote: string) { + await $`git switch ${branch}` + await $`git pull ${remote} ${branch}` +} + +export async function pushBranch(branch: string, remote: string) { + await $`git push ${remote} ${branch}` +} + +export async function getReleaseBranches() { + const stdout = unwrap(await $`git branch --list release/*`) + return getReleaseBranchesFromStdout(stdout) +} + +export function getReleaseBranchesFromStdout(stdout: string) { + if (stdout === '') { + return [] + } + + const releaseBranches = stdout + .split('\n') + .map((branch) => branch.trim().replace('* ', '')) + .sort((releaseBranchA, releaseBranchB) => { + const [, , versionA] = releaseBranchA.split('/') + const [, , versionB] = releaseBranchB.split('/') + return semver.compare(versionA, versionB) + }) + + return releaseBranches.reverse() +} diff --git a/lib/cherry_pick.ts b/lib/cherry_pick_commits.ts similarity index 97% rename from lib/cherry_pick.ts rename to lib/cherry_pick_commits.ts index 07f72f3..46484e9 100644 --- a/lib/cherry_pick.ts +++ b/lib/cherry_pick_commits.ts @@ -44,9 +44,10 @@ export async function cherryPickCommits(commits: Commit[], { console.log(chalk.green('🌸 Successfully cherry picked')) await afterCherryPick?.(commit) break - } catch (error) { + } catch (_error) { console.log() console.log(chalk.yellow("✋ Couldn't cleanly cherry pick. Resolve the conflicts and run `git cherry-pick --continue`")) + console.log() await question('Press anything to continue > ') await afterCherryPick?.(commit) break diff --git a/lib/commits.test.ts b/lib/commits.test.ts new file mode 100644 index 0000000..24f07a5 --- /dev/null +++ b/lib/commits.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it, test } from 'vitest' + +import { getCommitHash, getCommitMessage, getCommitNotes, getCommitPr } from './commits.js' + +describe('getCommitHash', () => { + it('works', () => { + const hash = getCommitHash("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)") + expect(hash).toEqual('487548234b49bb93bb79ad89c7ac4a91ed6c0dc9') + }) + + it('throws if no hash is found', () => { + expect(() => getCommitHash("|\\")).toThrowErrorMatchingInlineSnapshot(` + [Error: Couldn't find a commit hash in the line "|\\" + This most likely means that a line that's UI isn't being identified as such] + `) + }) + + it('works for non left-right lines', () => { + const hash = getCommitHash("487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)") + expect(hash).toEqual('487548234b49bb93bb79ad89c7ac4a91ed6c0dc9') + + }) +}) + +test('getCommitMessage', async () => { + const message = await getCommitMessage('4f4ad5989b794ddd0065d9c96c3091343c2a63c0') + expect(message).toEqual('Initial commit') +}) + +describe('getCommitNotes', () => { + it('works', async () => { + const notes = await getCommitNotes('4f4ad5989b794ddd0065d9c96c3091343c2a63c0') + expect(notes).toEqual('(jtoar) hello') + }) + + it("returns `undefined` there's no notes", async () => { + const notes = await getCommitNotes('9fd38ae1b3ad6afc5b0c1b2acb627b2bed25abda') + expect(notes).toBeUndefined() + }) +}) + +describe('getCommitPr', () => { + it('works', () => { + expect(getCommitPr("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual('10040') + }) + + it('works for reverts', () => { + expect(getCommitPr("< 5f89c0176f517b894cb2c3f1ab9cee4c7c207393 Revert \"Revert `@testing-library/jest-dom` v6 upgrade (#9713)\" (#9719)")).toEqual('9719') + }) + + it("returns `undefined` if it can't find a PR", () => { + expect(getCommitPr("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toBeUndefined() + }) +}) diff --git a/lib/commits.ts b/lib/commits.ts new file mode 100644 index 0000000..45a9f1c --- /dev/null +++ b/lib/commits.ts @@ -0,0 +1,54 @@ +import { $ } from 'zx' + +import { unwrap } from './zx_helpers.js' + +/** Square brackets (`[` or `]`) in commit messages need to be escaped */ +function sanitizeMessage(message: string) { + return message.replace('[', '\\[').replace(']', '\\]') +} + +/** See if a commit has been cherry picked into a given ref */ +export async function commitIsInRef(ref: string, message: string) { + message = sanitizeMessage(message) + return unwrap(await $`git log ${ref} --oneline --grep ${message}`) +} + +export const commitRegExps = { + hash: /(?\w{40})\s/, + pr: /\(#(?\d+)\)$/, + annotatedTag: /^v\d.\d.\d$/, +} + +/** Get a commit's 40-character hash */ +export function getCommitHash(line: string) { + const match = line.match(commitRegExps.hash) + + if (!match?.groups) { + throw new Error([ + `Couldn't find a commit hash in the line "${line}"`, + "This most likely means that a line that's UI isn't being identified as such", + ].join('\n')) + } + + return match.groups.hash +} + +/** Get a commit's message from its 40-character hash */ +export async function getCommitMessage(hash: string) { + return unwrap(await $`git log --format=%s -n 1 ${hash}`) +} + +/** Get a commit's PR if it has one */ +export function getCommitPr(message: string) { + return message.match(commitRegExps.pr)?.groups?.pr +} + +/** Get a commit's notes if it has any */ +export async function getCommitNotes(hash: string) { + try { + const notes = unwrap(await $`git notes show ${hash}`) + return notes + } catch (error) { + return undefined + } +} diff --git a/lib/cwd.test.ts b/lib/cwd.test.ts new file mode 100644 index 0000000..1e1158a --- /dev/null +++ b/lib/cwd.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' + +import { assertRwfwPathAndSetCwd } from './cwd.js' + +describe('assertRwfwPathAndSetCwd', () => { + it('works', async () => { + const originalCwd = process.cwd() + const resetCwd = await assertRwfwPathAndSetCwd() + expect(process.cwd()).toEqual(process.env.RWFW_PATH) + resetCwd() + expect(process.cwd()).toEqual(originalCwd) + }) + + it("throws if RWFW_PATH isn't set", async () => { + const originalRwfwPath = process.env.RWFW_PATH + delete process.env.RWFW_PATH + expect(process.env.RWFW_PATH).toBeUndefined() + await expect(() => assertRwfwPathAndSetCwd()).rejects.toThrowError() + process.env.RWFW_PATH = originalRwfwPath + }) +}) diff --git a/lib/set_cwd.ts b/lib/cwd.ts similarity index 77% rename from lib/set_cwd.ts rename to lib/cwd.ts index f18fba3..f500b9c 100755 --- a/lib/set_cwd.ts +++ b/lib/cwd.ts @@ -2,7 +2,11 @@ import { cd, chalk, fs } from "zx" import { CustomError } from './custom_error.js' -export async function setCwd() { +/** + * Set the current working directory to the Redwood monorepo via the `RWFW_PATH` environment variable. + * Returns a function that resets the current working directory. + */ +export async function assertRwfwPathAndSetCwd() { let RWFW_PATH = process.env.RWFW_PATH; if (!RWFW_PATH) { @@ -12,6 +16,7 @@ export async function setCwd() { ` ${chalk.green("export RWFW_PATH='/path/to/redwoodjs/redwood'")}`, '', `in one of your shell start-up files (e.g. ${chalk.magenta('~/.bashrc')} or ${chalk.magenta('~/.zshrc')})`, + 'or in a .env file in this directory that you create', ].join('\n')); } try { diff --git a/scripts/test_cwd.sh b/lib/cwd_test.sh similarity index 100% rename from scripts/test_cwd.sh rename to lib/cwd_test.sh diff --git a/lib/get_redwood_remote.test.ts b/lib/get_redwood_remote.test.ts new file mode 100644 index 0000000..c5a2516 --- /dev/null +++ b/lib/get_redwood_remote.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest' + +import { getRedwoodRemoteFromStdout } from './get_redwood_remote.js' + +describe('getRedwoodRemote', () => { + it('works', () => { + const remotes = 'origin git@github.com:redwoodjs/redwood.git (fetch)\norigin git@github.com:redwoodjs/redwood.git (push)' + const redwoodRemote = getRedwoodRemoteFromStdout(remotes) + expect(redwoodRemote).toEqual('origin') + }) + + it("throws if it can't find the redwood remote", () => { + const remotes = 'origin git@github.com:bazinga/bazinga.git (fetch)\norigin git@github.com:bazinga/bazinga.git (push)' + expect(() => { + getRedwoodRemoteFromStdout(remotes) + }).toThrowErrorMatchingInlineSnapshot(`[CustomError: Couldn't find the remote for the Redwood monorepo]`) + }) +}) diff --git a/lib/get_redwood_remote.ts b/lib/get_redwood_remote.ts new file mode 100644 index 0000000..457ff13 --- /dev/null +++ b/lib/get_redwood_remote.ts @@ -0,0 +1,24 @@ +import { chalk, $ } from 'zx' + +import { CustomError } from './custom_error.js' +import { unwrap } from './zx_helpers.js' + +export async function getRedwoodRemote() { + const stdout = unwrap(await $`git remote -v`) + const redwoodRemote = getRedwoodRemoteFromStdout(stdout) + console.log(`📡 Got Redwood remote ${chalk.magenta(redwoodRemote)}`) + return redwoodRemote +} + +export function getRedwoodRemoteFromStdout(remotes: string) { + for (const remote of remotes.split('\n')) { + const match = remote.match(redwoodRemoteRegExp) + if (match?.groups) { + return match.groups.remote + } + } + + throw new CustomError(`Couldn't find the remote for the Redwood monorepo`) +} + +export const redwoodRemoteRegExp = /^(?.+)\s.+redwoodjs\/redwood/ diff --git a/lib/git.ts b/lib/git.ts deleted file mode 100755 index abd577f..0000000 --- a/lib/git.ts +++ /dev/null @@ -1,93 +0,0 @@ -import semver from 'semver' -import { chalk, $ } from 'zx' - -import { CustomError } from './custom_error.js' -import { unwrap } from './zx_helpers.js' - -/** Gets release branches (e.g. `release/major/v7.0.0`, etc.) */ -export async function getReleaseBranches() { - const releaseBranchesStdout = unwrap(await $`git branch --list release/*`) - if (releaseBranchesStdout === '') { - return [] - } - - const releaseBranches = releaseBranchesStdout - .split('\n') - .map((branch) => branch.trim().replace('* ', '')) - .sort((releaseBranchA, releaseBranchB) => { - const [, , versionA] = releaseBranchA.split('/') - const [, , versionB] = releaseBranchB.split('/') - return semver.compare(versionA, versionB) - }) - - return releaseBranches.reverse() -} - -export async function assertWorkTreeIsClean() { - const workTreeIsClean = unwrap(await $`git status -s`) === '' - if (!workTreeIsClean) { - throw new CustomError( - `The working tree at ${chalk.magenta(process.cwd())} isn't clean. Commit or stash your changes` - ); - } - console.log('✨ The working tree is clean') -} - -export async function branchExists(branch: string) { - return !!unwrap(await $`git branch --list ${branch}`) -} - -export async function assertBranchExists(branch: string) { - if (!(await branchExists(branch))) { - throw new CustomError([ - `The ${chalk.magenta(branch)} branch doesn't exist locally. Check it out from the Redwood remote:`, - '', - chalk.green(` git checkout -b ${branch} /${branch}`), - ].join('\n')) - } - console.log(`🏠 The ${chalk.magenta(branch)} branch exists locally`) -} - -export async function getRedwoodRemote() { - const remotes = unwrap(await $`git remote -v`) - - for (const remote of remotes.split('\n')) { - const match = remote.match(/^(?.+)\s.+redwoodjs\/redwood/) - if (match?.groups) { - console.log(`📡 Got Redwood remote ${chalk.magenta(match.groups.remote)}`) - return match.groups.remote - } - } - - throw new CustomError(`Couldn't find the remote for the Redwood monorepo`) -} - -export const commitRegExps = { - hash: /(?\w{40})\s/, - pr: /\(#(?\d+)\)$/, - annotatedTag: /^v\d.\d.\d$/, -} - -/** Get a commit's hash */ -export function getCommitHash(line: string) { - const match = line.match(commitRegExps.hash) - - if (!match?.groups) { - throw new Error([ - `Couldn't find a commit hash in the line "${line}"`, - "This most likely means that a line that's UI isn't being identified as such", - ].join('\n')) - } - - return match.groups.hash -} - -/** Square brackets (`[` or `]`) in commit messages need to be escaped */ -function sanitizeMessage(message: string) { - return message.replace('[', '\\[').replace(']', '\\]') -} - -export async function commitIsInRef(ref: string, message: string) { - message = sanitizeMessage(message) - return unwrap(await $`git log ${ref} --oneline --grep ${message}`) -} diff --git a/lib/github.test.ts b/lib/github.test.ts new file mode 100644 index 0000000..8e8ce4e --- /dev/null +++ b/lib/github.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it, test } from 'vitest' + +import { assertGitHubToken } from './assert_github_token.js' +import { getGitHubFetchHeaders, getUserLogin } from './github.js' + +describe('github ', () => { + describe('assertGitHubToken', () => { + it('works', () => { + expect(process.env.REDWOOD_GITHUB_TOKEN).toBeDefined() + expect(assertGitHubToken).not.toThrow() + }) + + it("throws if `REDWOOD_GITHUB_TOKEN` isn't defined", () => { + const originalToken = process.env.REDWOOD_GITHUB_TOKEN + delete process.env.REDWOOD_GITHUB_TOKEN + expect(process.env.REDWOOD_GITHUB_TOKEN).toBeUndefined() + expect(assertGitHubToken).toThrow() + process.env.REDWOOD_GITHUB_TOKEN = originalToken + }) + }) + + test('getGitHubFetchHeaders', () => { + const headers = getGitHubFetchHeaders() + expect(headers).toHaveProperty('Content-Type', 'application/json') + expect(headers).toHaveProperty('Authorization') + }) + + test('getUserLogin', async () => { + assertGitHubToken() + const login = await getUserLogin() + expect(login).toBeDefined() + }) +}) diff --git a/lib/github.ts b/lib/github.ts index 404b416..e577923 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -1,20 +1,7 @@ -import { chalk, $ } from 'zx' - -import { CustomError } from './custom_error.js' - -export function getGitHubToken() { - const gitHubToken = process.env.REDWOOD_GITHUB_TOKEN - if (!gitHubToken) { - throw new CustomError("The `REDWOOD_GITHUB_TOKEN` environment variable isn't set") - } - return gitHubToken -} - export function getGitHubFetchHeaders() { - const gitHubToken = getGitHubToken() return { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${gitHubToken}`, + 'Authorization': `Bearer ${process.env.REDWOOD_GITHUB_TOKEN}`, } } @@ -28,34 +15,10 @@ export async function gqlGitHub({ query, variables }: { query: string; variables variables, }), }) - const body = await res.json() - return body + return res.json() } export async function getUserLogin() { const { data } = await gqlGitHub({ query: `query { viewer { login } }` }) return data.viewer.login } - -export async function pullBranch(branch: string, remote: string) { - await $`git switch ${branch}` - await $`git pull ${remote} ${branch}` -} - -export async function pushBranch(branch: string, remote: string) { - await $`git push ${remote} ${branch}` -} - -/** - * Fetches notes from the remote. - * - * See https://stackoverflow.com/questions/18268986/git-how-to-push-messages-added-by-git-notes-to-the-central-git-server. - */ -export async function fetchNotes(remote: string) { - await $`git fetch ${remote} 'refs/notes/*:refs/notes/*'` - console.log(`Fetched notes from ${remote}`) -} - -export async function pushNotes(remote: string) { - await $`git push ${remote} 'refs/notes/*'` -} diff --git a/lib/milestones.test.ts b/lib/milestones.test.ts new file mode 100644 index 0000000..e5ede71 --- /dev/null +++ b/lib/milestones.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from 'vitest' + +import { createMilestone, closeMilestone, getPrMilestone, getMilestone, getMilestones, getPrsWithMilestone } from './milestones.js' + +describe('getPrMilestone', () => { + it('works', async () => { + const milestone = await getPrMilestone("https://github.com/redwoodjs/redwood/pull/9803") + expect(milestone).toEqual('chore') + }) +}) + +describe('getPrsWithMilestone', () => { + it('works', async () => { + const prs = await getPrsWithMilestone('v7.0.3') + expect(prs).toMatchInlineSnapshot(` + [ + { + "id": "PR_kwDOC2M2f85nkDDh", + "mergeCommit": { + "messageHeadline": "Update MetaTags to be MetaData in Docs (#10053)", + }, + "mergedAt": "2024-02-22T17:44:49Z", + "number": 10053, + "title": "Update MetaTags to be Metadata in Docs", + "url": "https://github.com/redwoodjs/redwood/pull/10053", + }, + { + "id": "PR_kwDOC2M2f85nszPR", + "mergeCommit": { + "messageHeadline": "fix(render): reduce memory and handle server file (#10055)", + }, + "mergedAt": "2024-02-23T10:04:32Z", + "number": 10055, + "title": "fix(render): reduce memory and handle server file ", + "url": "https://github.com/redwoodjs/redwood/pull/10055", + }, + { + "id": "PR_kwDOC2M2f85nyT1x", + "mergeCommit": { + "messageHeadline": "Update studio.md (#10062)", + }, + "mergedAt": "2024-02-24T01:21:51Z", + "number": 10062, + "title": "Update studio.md", + "url": "https://github.com/redwoodjs/redwood/pull/10062", + }, + ] + `) + }) +}) + +const choreMilestone = { + title: 'chore', + id: "MDk6TWlsZXN0b25lNjc4MjU1MA==", + number: 46, +} + +describe('getMilestones', () => { + it('works', async () => { + const milestones = await getMilestones() + expect(milestones).toEqual(expect.arrayContaining([ + choreMilestone, + { + title: 'next-release', + id: "MI_kwDOC2M2f84Aa82f", + number: 56, + }, + { + title: 'next-release-patch', + id: "MDk6TWlsZXN0b25lNjc1Nzk0MQ==", + number: 44, + }, + ])) + }) +}) + +describe('getMilestone', () => { + it('works', async () => { + const milestone = await getMilestone('chore') + expect(milestone).toEqual(choreMilestone) + }) + + it("throws if it can't get the milestone", async () => { + await expect(() => getMilestone('bazinga')).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Couldn't find an open milestone the the title "bazinga"]`) + }) +}) + +describe('createMilestone and closeMilestone', () => { + describe('closeMilestone', () => { + it("throws if it can't close the milestone", async () => { + await expect(() => closeMilestone('bazinga')).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Couldn't find an open milestone the the title "bazinga"]`) + }) + + it("throws if the milestone is already closed", async () => { + await expect(() => closeMilestone('v7.0.4')).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Couldn't find an open milestone the the title "v7.0.4"]`) + }) + }) + + // it('works', async () => { + // const milestone = await createMilestone('release-tooling-test') + // expect(milestone).toMatchInlineSnapshot() + // const res = await closeMilestone('release-tooling-test') + // expect(res).toMatchInlineSnapshot() + // }) +}) diff --git a/release/lib/milestones.ts b/lib/milestones.ts similarity index 55% rename from release/lib/milestones.ts rename to lib/milestones.ts index b55946f..527cbca 100644 --- a/release/lib/milestones.ts +++ b/lib/milestones.ts @@ -1,8 +1,59 @@ -import { CustomError } from '@lib/custom_error.js' -import { getGitHubFetchHeaders, gqlGitHub } from '@lib/github.js' +import { fileURLToPath } from 'node:url' +import { fs } from 'zx' + +import { CustomError } from './custom_error.js' +import { getGitHubFetchHeaders, gqlGitHub } from './github.js' import type { PR } from './types.js' +export async function getPrMilestone(prUrl: string) { + if (!cache) { + cache = await setUpCache() + } + if (cache.has(prUrl)) { + return cache.get(prUrl) + } + const milestone = await getPrMilestoneInternal(prUrl) + cache.set(prUrl, milestone) + return milestone +} + +export async function getPrMilestoneInternal(prUrl: string) { + const { data } = await gqlGitHub({ query: getPrMilestoneQuery, variables: { prUrl } }) + return data.resource.milestone.title +} + +let cache: Map + +async function setUpCache() { + process.on('exit', () => { + if (cache.size === 0) { + return + } + fs.writeJsonSync(cacheFilePath, Object.fromEntries(cache), { spaces: 2 }) + }) + + const cacheFilePath = fileURLToPath(new URL('pr_milestone_cache.json', import.meta.url)) + if (!await fs.pathExists(cacheFilePath)) { + return new Map() + } + + const prMilestoneCache = await fs.readJson(cacheFilePath) + return new Map(Object.entries(prMilestoneCache)) +} + +const getPrMilestoneQuery = `\ + query getPrMilestone($prUrl: URI!) { + resource(url: $prUrl) { + ...on PullRequest { + milestone { + title + } + } + } + } +` + export async function getPrsWithMilestone(milestone?: string): Promise { const search = [ 'repo:redwoodjs/redwood', @@ -15,7 +66,7 @@ export async function getPrsWithMilestone(milestone?: string): Promise { search.push(`milestone:${milestone}`) } - const res = await gqlGitHub({ query: prsQuery, variables: { search: search.join(' ') } }) + const res = await gqlGitHub({ query: getPrsWithMilestoneQuery, variables: { search: search.join(' ') } }) const prs = res.data.search.nodes prs.sort((a, b) => { @@ -25,7 +76,7 @@ export async function getPrsWithMilestone(milestone?: string): Promise { return prs } -const prsQuery = `\ +const getPrsWithMilestoneQuery = `\ query ($search: String!) { search( query: $search @@ -48,11 +99,11 @@ const prsQuery = `\ } ` -const milestonesToIds = { - 'chore': 'MDk6TWlsZXN0b25lNjc4MjU1MA==', - 'next-release': 'MI_kwDOC2M2f84Aa82f', - 'next-release-patch': 'MDk6TWlsZXN0b25lNjc1Nzk0MQ==', -} +// const milestonesToIds = { +// 'chore': 'MDk6TWlsZXN0b25lNjc4MjU1MA==', +// 'next-release': 'MI_kwDOC2M2f84Aa82f', +// 'next-release-patch': 'MDk6TWlsZXN0b25lNjc1Nzk0MQ==', +// } export async function assertNoNoMilestonePrs() { const noMilestonePrs = await getPrsWithMilestone() @@ -100,8 +151,12 @@ export async function createMilestone(title: string) { export async function closeMilestone(title: string) { const milestone = await getMilestone(title) + + const url = `https://api.github.com/repos/redwoodjs/redwood/milestones/${milestone.number}` + console.log(`Posting to ${url}`) const headers = await getGitHubFetchHeaders() - const res = await fetch(`https://api.github.com/repos/redwoodjs/redwood/milestones/${milestone.number}`, { + + const res = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ state: 'closed' }), @@ -110,7 +165,7 @@ export async function closeMilestone(title: string) { console.log(json) } -async function getMilestones() { +export async function getMilestones() { const res = await gqlGitHub({ query: getMilestonesQuery }) return res.data.repository.milestones.nodes } @@ -131,11 +186,9 @@ const getMilestonesQuery = `\ export async function getMilestone(title: string) { const milestones = await getMilestones() - let milestone = milestones.find((milestone) => milestone.title === title) - if (milestone) { - return milestone - } - - milestone = await createMilestone(title) + const milestone = milestones.find((milestone) => milestone.title === title) + if (!milestone) { + throw new Error(`Couldn't find an open milestone the the title "${title}"`) + } return milestone } diff --git a/lib/notes.ts b/lib/notes.ts new file mode 100644 index 0000000..cf03721 --- /dev/null +++ b/lib/notes.ts @@ -0,0 +1,15 @@ +import { $ } from 'zx' + +/** + * Fetches notes from the remote. + * We use notes to document commits that we aren't cherry picking. + * + * See https://stackoverflow.com/questions/18268986/git-how-to-push-messages-added-by-git-notes-to-the-central-git-server. + */ +export async function fetchNotes(remote: string) { + await $`git fetch ${remote} 'refs/notes/*:refs/notes/*'` +} + +export async function pushNotes(remote: string) { + await $`git push ${remote} 'refs/notes/*'` +} diff --git a/triage/lib/commit_milestone_cache.json b/lib/pr_milestone_cache.json similarity index 99% rename from triage/lib/commit_milestone_cache.json rename to lib/pr_milestone_cache.json index 043192d..068669b 100755 --- a/triage/lib/commit_milestone_cache.json +++ b/lib/pr_milestone_cache.json @@ -1,5 +1,5 @@ { - "https://github.com/redwoodjs/redwood/pull/10052": "next-release-patch", + "https://github.com/redwoodjs/redwood/pull/10052": "v7.0.2", "https://github.com/redwoodjs/redwood/pull/10050": "RSC", "https://github.com/redwoodjs/redwood/pull/10049": "RSC", "https://github.com/redwoodjs/redwood/pull/10046": "RSC", diff --git a/lib/prompts.test.ts b/lib/prompts.test.ts new file mode 100644 index 0000000..299a33f --- /dev/null +++ b/lib/prompts.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from 'vitest' + +import { resIsOpen, resIsYes, resolveRes } from './prompts.js' + +describe('prompt helpers', () => { + test('resIsYes', () => { + for (const res of ['yes', 'Yes', 'y', 'Y', '']) { + expect(resIsYes(res)).toBe(true) + } + expect(resIsYes('no')).toBe(false) + }) + + test('resIsOpen', () => { + for (const res of ['open', 'Open', 'o', 'O']) { + expect(resIsOpen(res)).toBe(true) + } + expect(resIsOpen('')).toBe(false) + expect(resIsOpen('no')).toBe(false) + }) + + test('resolveRes', () => { + expect(resolveRes('')).toBe('yes') + expect(resolveRes('o')).toBe('open') + expect(resolveRes('abcd')).toBe('no') + }) +}) diff --git a/lib/types.ts b/lib/types.ts index 0741a8f..5b0a091 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -18,3 +18,13 @@ export type Commit = { } export type PrettyCommit = Commit & { pretty?: string } + +export interface PR { + id: string + number: number + title: string + url: string + mergeCommit: { + messageHeadline: string + } +} diff --git a/lib/zx_helpers.ts b/lib/zx_helpers.ts index 60e258e..33ad579 100755 --- a/lib/zx_helpers.ts +++ b/lib/zx_helpers.ts @@ -1,10 +1,12 @@ import type { ProcessOutput } from 'zx' /** - * Helper for getting the trimmed stdout from `zx`'s `ProcessOutput`: + * Helper for getting the trimmed stdout from `zx`'s `ProcessOutput` + * + * @example * * ```ts - * unwrap(await $`...`) + * const stdout = unwrap(await $`...`) * ``` */ export function unwrap(processOutput: ProcessOutput) { diff --git a/package.json b/package.json index 6e1df62..f4f5d6c 100755 --- a/package.json +++ b/package.json @@ -6,13 +6,15 @@ "release": "tsx ./release/run.ts", "reset-next": "tsx ./scripts/resetNext.ts", "test": "vitest run", - "triage": "tsx ./triage/run.ts" + "triage": "tsx ./triage/run.ts", + "update-pr-milestone-cache": "tsx ./scripts/update_pr_milestone_cache.ts" }, "devDependencies": { "@tsconfig/recommended": "1.0.3", "@types/prompts": "2.4.9", "@types/semver": "7.5.8", "boxen": "7.1.1", + "dotenv": "16.4.5", "execa": "8.0.1", "prompts": "2.4.2", "semver": "7.6.0", diff --git a/release/lib/milestones.test.ts b/release/lib/milestones.test.ts deleted file mode 100644 index 970eec1..0000000 --- a/release/lib/milestones.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, test } from 'vitest' - -import { getPrsWithMilestone } from './milestones.js' - -describe('getPrsWithMilestone', () => { - test('it works', async () => { - const prs = await getPrsWithMilestone('v7.0.3') - expect(prs).toMatchInlineSnapshot(` - [ - { - "id": "PR_kwDOC2M2f85nkDDh", - "mergeCommit": { - "messageHeadline": "Update MetaTags to be MetaData in Docs (#10053)", - }, - "mergedAt": "2024-02-22T17:44:49Z", - "number": 10053, - "title": "Update MetaTags to be Metadata in Docs", - "url": "https://github.com/redwoodjs/redwood/pull/10053", - }, - { - "id": "PR_kwDOC2M2f85nszPR", - "mergeCommit": { - "messageHeadline": "fix(render): reduce memory and handle server file (#10055)", - }, - "mergedAt": "2024-02-23T10:04:32Z", - "number": 10055, - "title": "fix(render): reduce memory and handle server file ", - "url": "https://github.com/redwoodjs/redwood/pull/10055", - }, - { - "id": "PR_kwDOC2M2f85nyT1x", - "mergeCommit": { - "messageHeadline": "Update studio.md (#10062)", - }, - "mergedAt": "2024-02-24T01:21:51Z", - "number": 10062, - "title": "Update studio.md", - "url": "https://github.com/redwoodjs/redwood/pull/10062", - }, - ] - `) - }) -}) diff --git a/release/lib/release.ts b/release/lib/release.ts index e852ba4..f580489 100644 --- a/release/lib/release.ts +++ b/release/lib/release.ts @@ -2,15 +2,15 @@ import { execaCommand } from 'execa' import semver from 'semver' import { cd, chalk, fs, path, question, $ } from 'zx' +import { branchExists, pushBranch } from '@lib/branches.js' +import { cherryPickCommits, reportCommitsEligibleForCherryPick } from '@lib/cherry_pick_commits.js' +import { commitIsInRef, getCommitHash } from '@lib/commits.js' import { separator } from '@lib/console_helpers.js' -import { cherryPickCommits, reportCommitsEligibleForCherryPick } from '@lib/cherry_pick.js' import { CustomError } from '@lib/custom_error.js' -import { branchExists, commitIsInRef, getCommitHash } from '@lib/git.js' -import { pushBranch } from '@lib/github.js' +import { closeMilestone, createMilestone, getMilestone, getPrsWithMilestone, updatePrMilestone } from '@lib/milestones.js' import { resIsYes } from '@lib/prompts.js' import { unwrap } from "@lib/zx_helpers.js"; -import { closeMilestone, getMilestone, getPrsWithMilestone, updatePrMilestone } from './milestones.js' import type { ReleaseOptions } from './types.js' export async function assertLoggedInToNpm() { @@ -74,7 +74,7 @@ export async function release(options: ReleaseOptions) { `Ok to ${chalk.underline('version')} docs to ${chalk.magenta(options.nextRelease)}? [Y/n] > ` const okToVersionDocs = resIsYes(await question(message)) if (okToVersionDocs) { - await versionDocs(options) + await versionDocs(options.nextRelease) } console.log(separator) @@ -97,28 +97,31 @@ export async function release(options: ReleaseOptions) { // Temporarily remove `packages/create-redwood-app` from the workspaces field so that we can publish it separately later. const undoRemoveCreateRedwoodAppFromWorkspaces = await removeCreateRedwoodAppFromWorkspaces() await publish() - // Undo the temporary commit and publish CRWA. - await undoRemoveCreateRedwoodAppFromWorkspaces() console.log(separator) + await undoRemoveCreateRedwoodAppFromWorkspaces() await question('Press anything to update create-redwood-app templates > ') await updateCreateRedwoodAppTemplates() await publish() console.log(separator) - await question('Press anything consolidate commits, tag, and push > ') + await question('Press anything consolidate commits and tag > ') // This combines the update package versions commit and update CRWA commit into one. await $`git reset --soft HEAD~2` await $`git commit -m "${options.nextRelease}"` - // Tag and push. await $`git tag -am ${options.nextRelease} "${options.nextRelease}"` + + console.log(separator) + await question('Press anything to push the tag to GitHub > ') await $`git push -u ${options.remote} ${releaseBranch} --follow-tags` + console.log(separator) + await question(`Press anything to close the ${options.nextRelease} milestone > `) await closeMilestone(options.nextRelease) console.log(separator) - await question('Press anything to merge the release branch into next >') - await mergeReleaseBranch(releaseBranch) + await question('Press anything to merge the release branch into next > ') + await mergeReleaseBranch({ ...options, releaseBranch }) } async function switchToReleaseBranch({ releaseBranch, latestRelease }: Pick & { releaseBranch: string }) { @@ -193,7 +196,14 @@ async function updateReleaseBranch(options: ReleaseOptions & { releaseBranch: st } reportCommitsEligibleForCherryPick(prs) - const milestone = await getMilestone(options.nextRelease) + + let milestone + try { + milestone = await getMilestone(options.nextRelease) + } catch (_error) { + milestone = await createMilestone(options.nextRelease) + } + await cherryPickCommits(prs, { range: { from: 'next', to: options.releaseBranch }, afterCherryPick: async (pr) => { @@ -201,13 +211,14 @@ async function updateReleaseBranch(options: ReleaseOptions & { releaseBranch: st } }) console.log(separator) - const okToPushBranch = resIsYes(await question(`Ok to push ${options.releaseBranch}? [Y/n] > `)) + const okToPushBranch = resIsYes(await question(`Ok to push ${chalk.magenta(options.releaseBranch)} to ${chalk.magenta(options.remote)}? [Y/n] > `)) if (okToPushBranch) { await pushBranch(options.releaseBranch, options.remote) + await $`open https://github.com/redwoodjs/redwood/compare/${options.latestRelease}...${options.releaseBranch}` } } -async function versionDocs({ desiredSemver, nextRelease }: Pick) { +async function versionDocs(nextRelease: string) { const nextDocsVersion = nextRelease.slice(1, -2) await cd('./docs') @@ -246,7 +257,7 @@ async function updatePackageVersions({ nextRelease }: Pick ') + } + + console.log(separator) + const okToPushBranch = resIsYes(await question(`Ok to push ${chalk.magenta('next')} to ${chalk.magenta(options.remote)}? [Y/n] > `)) + if (okToPushBranch) { + await pushBranch('next', options.remote) + } + + console.log(separator) + const okToDeleteBranches = resIsYes(await question(`Ok to delete ${chalk.magenta(options.releaseBranch)}? [Y/n] > `)) + if (okToDeleteBranches) { + await $`git switch main` + await $`git branch -d ${options.releaseBranch}` + await $`git push ${options.remote} --delete ${options.releaseBranch}` + } } diff --git a/release/lib/types.ts b/release/lib/types.ts index b3ef496..7a07e2b 100644 --- a/release/lib/types.ts +++ b/release/lib/types.ts @@ -6,13 +6,3 @@ export interface ReleaseOptions { desiredSemver: ReleaseType remote: string } - -export interface PR { - id: string - number: number - title: string - url: string - mergeCommit: { - messageHeadline: string - } -} diff --git a/release/run.ts b/release/run.ts index f5c34d0..702f17b 100644 --- a/release/run.ts +++ b/release/run.ts @@ -1,17 +1,24 @@ +import { assertWorkTreeIsClean } from '@lib/branches.js' import { consoleBoxen, separator } from '@lib/console_helpers.js' -import { setCwd } from '@lib/set_cwd.js' import { CustomError } from '@lib/custom_error.js' -import { assertWorkTreeIsClean, getRedwoodRemote } from '@lib/git.js' +import { assertRwfwPathAndSetCwd } from '@lib/cwd.js' +import { getRedwoodRemote } from '@lib/get_redwood_remote.js' +import { assertNoNoMilestonePrs } from '@lib/milestones.js' import { getDesiredSemver } from '@lib/prompts.js' -import { assertNoNoMilestonePrs } from './lib/milestones.js' -import { assertLoggedInToNpm, assertGitTagDoesntExist, getLatestReleaseOrThrow, getNextReleaseOrThrow, release } from './lib/release.js' +import { + assertLoggedInToNpm, + assertGitTagDoesntExist, + getLatestReleaseOrThrow, + getNextReleaseOrThrow, + release +} from './lib/release.js' try { await assertLoggedInToNpm() console.log(separator) - await setCwd() + await assertRwfwPathAndSetCwd() console.log(separator) await assertWorkTreeIsClean() diff --git a/scripts/reset_next.ts b/scripts/reset_next.ts index da910cd..ea9165a 100644 --- a/scripts/reset_next.ts +++ b/scripts/reset_next.ts @@ -5,12 +5,12 @@ import { question, $ } from 'zx' -import { setCwd } from '@lib/set_cwd.js' -import { getRedwoodRemote } from '@lib/git.js' +import { assertRwfwPathAndSetCwd } from '@lib/cwd.js' +import { getRedwoodRemote } from '@lib/get_redwood_remote.js' import { resIsYes } from '@lib/prompts.js' if (resIsYes(await question('Ok to reset next to origin/next? [Y/n] > '))) { - await setCwd() + await assertRwfwPathAndSetCwd() await $`git switch next` const remote = await getRedwoodRemote() await $`git fetch ${remote}` diff --git a/scripts/update_pr_milestone_cache.ts b/scripts/update_pr_milestone_cache.ts new file mode 100644 index 0000000..e4cfefa --- /dev/null +++ b/scripts/update_pr_milestone_cache.ts @@ -0,0 +1,62 @@ +import { fileURLToPath } from 'node:url' + +import { fs } from 'zx' + +import { getPrMilestoneInternal } from '@lib/milestones.js' + +const cacheFilePath = fileURLToPath(new URL('../lib/pr_milestone_cache.json', import.meta.url)) +const cache = await fs.readJson(cacheFilePath) + +let ok = 0 +const updated: string[] = [] +let lastMessageLineCount = 0; + +function writeMessage() { + // Move cursor back to the start of the message area + if (lastMessageLineCount > 0) { + process.stdout.moveCursor(0, -lastMessageLineCount + 1); + } + + // Clear each line of the previous message + for (let i = 0; i < lastMessageLineCount; i++) { + process.stdout.clearLine(0); + + // Move the cursor down only if it's not the last line + if (i < lastMessageLineCount - 1) { + process.stdout.cursorTo(0); + process.stdout.moveCursor(0, 1); + } + } + + // After clearing everything, move the cursor back to the start of the message area + process.stdout.cursorTo(0); + if (lastMessageLineCount > 0) { + process.stdout.moveCursor(0, -lastMessageLineCount + 1); + } + + // Write the new message + const message = [ + `${ok} ok`, + ...updated + ].join('\n'); + + process.stdout.write(message); + + // Update lastMessageLineCount for the next write + lastMessageLineCount = message.split('\n').length; +} + +for (const [prUrl] of Object.entries(cache)) { + const milestone = await getPrMilestoneInternal(prUrl) + if (milestone === cache[prUrl]) { + ok += 1 + writeMessage() + continue + } + + updated.push(`• updated ${prUrl} from ${cache[prUrl]} to ${milestone}`) + cache[prUrl] = milestone + writeMessage() +} + +await fs.writeJson(cacheFilePath, cache, { spaces: 2 }) diff --git a/triage/lib/options.ts b/triage/lib/get_range.ts similarity index 80% rename from triage/lib/options.ts rename to triage/lib/get_range.ts index 176539d..a8e68f7 100755 --- a/triage/lib/options.ts +++ b/triage/lib/get_range.ts @@ -1,7 +1,7 @@ -import { getReleaseBranches } from "@lib/git.js"; +import { getReleaseBranches } from "@lib/branches.js"; import { prompts } from '@lib/prompts.js' -export async function getOptions() { +export async function getRange() { const releaseBranches = await getReleaseBranches(); console.log() @@ -25,9 +25,7 @@ export async function getOptions() { const [from, to] = rangeRes.range.split("..."); return { - range: { - from, - to, - }, + from, + to, }; } diff --git a/triage/lib/resolve_symmetric_difference.test.ts b/triage/lib/resolve_symmetric_difference.test.ts index 3f5577e..0ef0c5a 100755 --- a/triage/lib/resolve_symmetric_difference.test.ts +++ b/triage/lib/resolve_symmetric_difference.test.ts @@ -2,7 +2,8 @@ import { fs, $ } from 'zx' import { beforeAll, afterAll, describe, expect, it } from 'vitest' -import { setCwd } from '../../lib/set_cwd.js' +import { assertRwfwPathAndSetCwd } from '@lib/cwd.js' + import { resolveSymmetricDifference } from './symmetric_difference.js' $.verbose = false @@ -10,7 +11,7 @@ $.verbose = false describe('resolveSymmetricDifference', () => { let resetCwd: () => void beforeAll(async () => { - resetCwd = await setCwd() + resetCwd = await assertRwfwPathAndSetCwd() }) afterAll(() => { resetCwd() diff --git a/triage/lib/symmetric_difference.test.ts b/triage/lib/symmetric_difference.test.ts new file mode 100755 index 0000000..6b4a9e9 --- /dev/null +++ b/triage/lib/symmetric_difference.test.ts @@ -0,0 +1,355 @@ +import { $ } from 'zx' + +import { beforeAll, afterAll, describe, expect, it, test } from 'vitest' + +import { assertRwfwPathAndSetCwd } from '@lib/cwd.js' + +import { colors } from './colors.js' +import { + getPrettyLine, + getSymmetricDifference, + lineIsAnnotatedTag, + lineIsChore, + lineIsGitLogUi, + PADDING, + resolveLine, +} from './symmetric_difference.js' + +$.verbose = false + +describe('getSymmetricDifference', () => { + let resetCwd: () => void + beforeAll(async () => { + resetCwd = await assertRwfwPathAndSetCwd() + }) + afterAll(() => { + resetCwd() + }) + + it('works', async () => { + const symDiff = await getSymmetricDifference({ from: 'release-tooling/main-test', to: 'release-tooling/next-test' }) + expect(symDiff).toMatchInlineSnapshot(` + [ + "< 9b1dd056d2c33b23a63874f23f4efb5808e0660e Add support for additional env var files (#9961)", + "< ba6934911a242a6e328571493c263970b42174be Update studio.md (#10062)", + "< 2a89267b92d11e7b44c5ff2f18d0427ae0476121 chore(rename): Be consistent with 'for' prefix for babel plugin option (#10059)", + "< 553f4ac7d6bc28bfc93ac59ad6c2746ff5e71ce5 RSC: Remove commented code from worker (#10058)", + "< d5214c92cde6bfdb14c6c1769250c31b23d56328 fix(render): reduce memory and handle server file (#10055)", + "< 1f02ae3cc205cdb2cc98bd6a02a7f264b11cb96b chore(release): update changelog (v7.0.1, v7.0.2)", + "< 9b6047b0e48762959b7f3d382ee3dd3c9237ef5b RSC: Simplify entriesFile loading (#10057)", + "< 965b7ea3901a7e82201bea2090eaaec77e37a210 chore(types): Get rid of TS typecast in babel config (#10056)", + "< 95ba45c24911649fe1e2bcb5df3af6e7276b090a Update MetaTags to be MetaData in Docs (#10053)", + "< 9ad5c7c4bf5cefad0d05fc3a251ea27d1068e8e9 chore(deps): bump ip from 2.0.0 to 2.0.1 (#10043)", + "< a3d955886b61c3a05ff7aea27711adbdb8e1f964 chore(deps): bump ip from 1.1.5 to 1.1.9 in /docs (#10047)", + "< c171208de79866b502a6edcfa8a10b151ed1fcf7 RSC: Transform client components during build (#10048)", + "< e25b51984037d364dfdaf0b038074dbf7b9532c8 RSC: Upgrade test-project-rsa to v8 canary (#10050)", + "< 5e8bc3a1268d486bbbb9e796b3822fcc21a8f0cb RSC: Refactor: extract common code for env vars (#10049)", + "< a59205c826e5e980437a86397d5df8c65c7d5759 RSC: Refactor node-loader and some vite plugins (#10046)", + "< d012c60f9ba64e023837ceda35f0944923c751e6 chore(deps): acorn-loose 8.4.0 (#10042)", + "< 1de0d68a8c04fc8fb05988ca11706f15f68c5c6f chore(ci): update yarn.lock for changelog action (#10039)", + "< 4f612f813f6dd10fe8861c2a5eba21a0011cb1c6 chore: bump TSTyche (#10036)", + "< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'", + "|\\ ", + "< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)", + "< | 4b5f0243354553d59e744574f09803e3f42a17ba fix(server): prefix port/host with api, fix logging (#10035)", + "< | cf3c4211cda0b7d7f68b27948055ff4312c125fe docs: Removes warning within Mailer documentation about Studio being experimental (#10033)", + "< | 369c9e5aa9493ba2806870fadc3f6f16af7e5aa6 docs: Within describeScenario documentation, change optimisation to use American English (#10032)", + "< | 70602deaafdc3481d791900b6ed5e0970eacf0d7 RSC: No basePath arg to serve() (#10030)", + "< | 12d3fc18e6948f6b52065fc9bf96e4cbaba8a942 RSC: Add MultiCellPage to test fixture (#10029)", + "< | e9261ce71687fd6dd579dcc9ea145eda4d039220 RSC chore(tests): Add links to scaffolds in test fixture (#10028)", + "< | ad9bef80715382abc54f0ef09c441e3925b10f95 docs(metadata): Fix spelling/typos (#10027)", + "< | 3289f4593feaf014fb983a55e6ba6e80a0ead227 chore(release): link to previous releases and upgrade guides in the changelog (#10026)", + "< | 43182f14cc5b6755a043bcce2cf158d444e6da78 chore(ci): add changelog check to ci (#9989)", + "< | 7fc06486c8be34ae010294bb4ccf5170311d5ae2 fix(realtime): update logic for including sseLink (#10025)", + "< | 903f8d426d4e62620fbe4c4292943028629e58f1 chore(k6 tests): update entry point (#10024)", + "< | e4a7676cdfbf3c46165ba5185efab07dc7f1f395 feat(server): add docs on the server file (#10019)", + "< | ad493cd79b606776a676961a9c158933cff0a612 fix(types): Fix TS type in createServer.test.ts (#10023)", + "< | 4891360759adbf76bd7b70f9a67623cff47484e2 fix: Handle static assets on the \`rw-serve-fe\` (#10018)", + "< | 0f158c2e7745e3a7900824c32f996ce80e84ee50 fix(server): fix env var loading in \`createServer\` (#10021)", + "< | e2570881db4dc4a9635d94ddf96bfc23488e87cb fix(deps): remove react types packages from \`@redwoodjs/testing\` dependencies (#10020)", + "< | 824ff14955ec30e8a616c8e5bdbce6d530d92689 chore(release): add back \`update-package-versions\` task (#10017)", + "< | 85a0bfa346677518aca4cd80a16a92c663393bf2 chore(renovate): Disable for experimental apollo package (#10016)", + "< | ef015ca4d2737d23dcb74cc5ea39c5e7a4c15d9d RSC: server cells lowercase data function (#10015)", + "< | cfd4c9238269cbace63c988539170a5620dd1821 fix(RSC/SSR): pass CLI options through to apiServerHandler (#10012)", + "< | 8b499f99235a654d14f629e5318368c0e22b743a 7.0 RC: Remove hardcoded check for \`session.id\` (#10013)", + "< | ff138d1d63e3723650b7a2760b3f7628225f5f92 Spelling fix in what-is-redwood.md (#10011)", + "< | 2e3b0f4a5c92b57df74748721998d2b57b3b30a5 Typos in realtime.md (#10010)", + "< | 642238da6aa91c89dce80d9c3c1751d97808a333 RSC: Server cell smoke tests (#10008)", + "< | d44e03afcf617ad90c903706032bda4f03462c95 RSC: test-project EmptyUser 'use client' cell (#10007)", + "< | 0c5d91d9925cbee8b5f5fa15ad5d20e372d1e0d7 RSC: babel-plugin-redwood-cell remove redundant reset (#10006)", + "< | d47bddcbbf61903648e9751af3ab949edb76d948 chore(deps): Upgrade to yarn v4.1.0 (#10002)", + "< | 6132ed5cb4eafc17ee9a11399a33125b2e766dab fix(docs): Spelling of \`data-migrate\` command (#10003)", + "< | fa2887cf9a9069bb0f4bd7249b362ff522e58bcc docs: add aliases fo \`type-check\` command (#10004)", + "< | 0ef0289221781e9d2751507f1a17607694b43255 RSC: Insert 'use client' in scaffolded components (#9998)", + "< | e5f09e49062af04aa030517930fb7e1e90ca5611 fix(telemetry): Fix 'destroy' spelling (#10000)", + "< | dd8efec66891fd855de79d7321a69b75b0465890 chore(jsdocs): Fix jsdoc formatting for hover help (#9999)", + "< | cba4a68547de1a7bf344d28672937e207d2cb196 bug: Update setupHandler.ts firebase version (#9997)", + "< | 7c9dfd688dde35d843fead3135d76dae3126b515 RSC: Keep aligning test project with CRWA template (#9993)", + "< | 7ee21cd87e8f3a8f14216ee0aa4b5e36c5405c92 RSC: Fix babel-plugin-redwood-cell to work wiht more than one cell (#9994)", + "< | 43cb6414e102ba62354f7ae8a3b4af11763ef448 fix(dependencies): Use RW-specific version of apollo ssr package (#9992)", + "< | 44a980bca870f28867a21e3927b983bbb3c5ad08 RSC: chore(test): Update RSC test fixture project (#9990)", + "< | 45e6ce70ccdbbdfb612cc353f25e59e902eb5bce RSC: createServerCell (#9987)", + "< | 85737712fc230bae3f550e6da8001b53141d78cf chore(refactor): Router: Splitting things up into smaller files (#9988)", + "< | ed033ee59eb5ec178fe9e6fb02f0cfbf809d8e53 chore(project-config): make chore changes to trigger ci (#9985)", + "< | 9b009b091b0f8102e05df4bb02c1e3f093ea5cea chore: update rsc fixture (#9986)", + "< | 9df7377db14407ada2419c92b2dd30e1a42f4d1e fix(server): use file extension in import, fix graphql route registering (#9984)", + "< | 50b455f7353baabe9775e264a9f9ee60d98303ca chore(deps): update babel monorepo (#9983)", + "< | 9de6d0e38cf5c15d73f12eb8f72f5701e1253137 fix: unpin react types (#9727)", + "< | 60d031db0612d57c961a205b7f1ee927fb70191d fix(docker): compose dev and prod (#9982)", + "< | 2c15510f94bab59ce7b9173c7323b720b3dee076 fix(deps): update prisma monorepo to v5.9.1 (#9980)", + "< | 180615bed290363e083caf15e2f4d23c29131185 fix(cli): use fetch instead of \`yarn npm info\` (#9975)", + "< | bcf191dfaafcba8d40853078bef2ec9975cbb978 fix(test): Update createServer test to use a different port to normal (#9977)", + "< | 4b06f06539d774937af297923be576c6a2ed582f fix(docker): corepack permissions fix and style updates (#9976)", + "< | cd65bf6ff33675e85fd2f17caa1cfe4d7c8907b9 fix(deps) resolve yarn warnings regarding unmet peer dependencies for Redwood projects (#8874)", + "< | 019361550736f5ed733c37d7f5241583f26d405b chore(build): use \`tsx\` to run build scripts (#9941)", + "< | 3c04b6c3f986f68f25a5532ed74be99cc801464c RSC: Client Cell support smoke-test (#9974)", + "< | 3bbb5f5c9946995d4bb5695ff715df868d742dc1 RSC: Enable babel plugins to get client Cell support (#9973)", + "< | 57c5eca1a15b2cbbc2c600eaf6d8167983f590a3 RSC: Take full control over the env vars (#9972)", + "< | f736662b8552fd998470fd4a684d97228226a108 RSC: Make RWJS_ENV etc available on the server during runtime (#9971)", + "< | 11b678286e97081b65e8a575878016a16072fbb0 chore(cleanup): Remove debug console.log call (#9967)", + "< | 906c143b2feccf8eb28859450888fa606c31df3d fix(studio): Remove unused settings 'endpoint' and 'roles' (#9966)", + "< | f3a865d54b650fb1749e3bb1ad5abf42dc643c2b cli: Remove graphiql setup command (#9964)", + "< | b0d60f3ccf97827fd9cabe8289f8deaac6568999 fix(cell-suspense): pass through variables if passed to refetch (#9965)", + "< | d2b7f7fb4d70243616d9f9dcf7c5bfe1e6a4981f Mention the you need corepack enabled to run this repo (#9960)", + "< | b930777e64276cfcb7a8f340e1dd8f7053e9956f chore(server): improve server tests (#9958)", + "< | 5aa69919a66ca271f19fe82d9111a5583015020e feat(gql): Codemod existing projects to get newest gql config (#9959)", + "< | 1039545a171adee0ace8f6ad9f85b495e08b2fe6 Update Studio docs (#9955)", + "< | 70c1c19d861c0efba94a31cb699bcf530067cf81 fix(gql): Better graphql.config.js template (#9957)", + "< | c6cacf4dfb18bd95ab8a5143466d8c67ff00764d fix(studio): Remove unused setting inMemory (#9956)", + "< | 3fe639eff701625e3c8febf22b6fb8cb19d94df7 docs(typo): Removed \`rw\` from \`yarn install\` command (#9954)", + "< | 2d0100d60e4237bd82b230cc5f7ae3c5fc77a8b0 chore(deps): update dependency firebase to v10.7.0 (#9605)", + "< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock", + "< | 469770d580b015309e26b9a9529044c9cda5a886 fix(deps): update dependency firebase-admin to v11.11.1 (#9953)", + "< | c409341ab3e077ecbc8e84626080c22b215f45fd chore(api-server): switch to vitest (#9929)", + "< | 4773fc8653a10db40d67102561d96490cf5b9cff chore(test): Remove yarn.lock from some test fixtures (#9952)", + "< | 7bce9b04a4e880c5d6deb801a2924150277f9dee fix(deps): update dependency nodemailer to v6.9.9 [security] (#9951)", + "< | c578051190384309b874d573f0ecb24b4df741e6 fix(api-server): Remove duplicate command alias (#9950)", + "< | 80f499e521c501c92959cae715ca80ee92225895 fix(server): spelling, fix deploy handler imports, dedupe server builder (#9949)", + "< | 887dba98722ea48ed97304287e1e9727d46a9577 feat(server): dedupe api server code, make host configurable (#9948)", + "< | 909eeac01f36a1b08a93187338ae3b7f60655ab1 chore(ts): Remove src alias and baseUrl from all tsconfigs (#9944)", + "< | 8247653ea1299ab30f3aa17cc94b5c6c2b594933 @redwoodjs/framework-tools for buildDefaults (#9947)", + "< | a7b1573148970062549c4c4ab9b53c9fa1a4f270 fix(internal): Remove unused import (#9946)", + "< | 21237efcff9fcde4dbdbce9120acd920f204d332 chore(crwa): Use actual filename in seed file template (#9945)", + "< | 2d9c9bb9d459298e4fbaf753908372f579bd745c fix(cli): Prevent caching blank information about plugins (#9942)", + "< | 199fce1c4c339fdc5fa50ca0bcb38d42d4ddf2c7 chore(apollo): Introduce TS type helper to reduce repetition (#9943)", + "< | ff7d79cd0df190656557e922d698a8f69138bef4 feat(middleware): Add support for Middleware to SSR-Streaming server (#9883)", + "< | 7fdf7bb427b6cfcf0d74c1f9f84191b72798efc2 chore(deps): update node.js to v20 (#9936)", + "< | d134f427938b5a2127c67b626902e8043b328844 feat(crwa): Make the seed template idempotent (#9937)", + "< | b31bb6ae4bb681ef6d20235f8dc9d2613f56a345 docs(monitoring): Add Sentry docs (#9934)", + "< | a0cfcc9b5519074bc7c391426e98637487fcd7e5 fix(deps): update prisma monorepo to v5.9.0 (#9935)", + "< | 8d07dc2d4f5a63b02db134ca0942ae94366270d3 fix(docusaurus): Make {jsx,tsx} file extensions switch properly (#9933)", + "< | edfdf99bb852ad17e9ebc48c98bb92f2b15bf628 fix(server): don't mix async and callback styles (#9931)", + "< | 43bc6ebc44880f9a2173fca7a784fbb0526d6bec chore(forms): switch tests to vitest (#9928)", + "< | 472c86bed8f1ee6d7a528ca6d494406553fc072e fix(web): import helmet from node_modules (#9927)", + "< | 63aba23069fb737f63cdb4f48178fd1f73949dce chore(deps): update dependency @testing-library/jest-dom to v6.3.0 (#9926)", + "< | 5227b71c20ba3966d6436d08164712b9453bff8e chore(deps): update dependency @clerk/clerk-react to v4.30.3 (#9922)", + "< | e03108c4c8adaf3ca644b540ec97b0826dee5122 fix(deps): update dependency @testing-library/user-event to v14.5.2 (#9925)", + "< | fb47d08c3d07cfd1e00f47f3475139ba3486b36f chore(deps): update dependency @testing-library/dom to v9.3.4 (#9924)", + "< | 491cef16d97b35d7ab98e22dc2e85794f9c7e62f fix(deps): update dependency @clerk/clerk-sdk-node to v4.13.7 (#9923)", + "< | c67a74d2a8e54a68b04768f70a6964cad1d68b33 fix(deps): update dependency core-js to v3.35.1 (#9919)", + "< | 120ab90e41ce3b8069c176d50d721244ef24c82c fix(deps): update dependency fastify to v4.25.2 (#9920)", + "< | 441e754557ab803ff9628a36da5f94dd08a23593 fix(deps): update dependency graphql-sse to v2.5.2 (#9921)", + "< | 970205bd28b9c7a8093ae806c9d523f2e2a9feb7 chore(deps): update dependency @playwright/test to v1.41.1 (#9918)", + "< | 013f9c2623d408f66315546c58c0f1802731f731 fix(deps): update apollo graphql packages (#9916)", + "< | ce0a064f8787b7440ac292f7faeb2518fee06f02 chore(deps): update dependency vitest to v1.2.2 (#9915)", + "< | b9af37b39591b0004a47c97d34914de5aae22145 fix(deps): update storybook monorepo to v7.6.10 (#9917)", + "< | c63f332164b30261af58d4dde0e8a6743557d96d chore(deps): update dependency lerna to v8.0.2 (#9914)", + "< | fb9f3023bf1387feed25e33282a0178d1b6df260 chore(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#9817)", + "< | d7e343b134a79081a52debf03b895a4cd5029de8 fix(deps): update dependency vite to v4.5.2 [security] (#9852)", + "< | 36d49bc944a3a296eb54c7ad0a3f33eb7c50843c chore(deps): bump @fastify/reply-from from 9.4.0 to 9.6.0 (#9813)", + "< | e4ff05157a462b41443955ec831fa93d00d24c59 chore(deps): bump follow-redirects from 1.15.3 to 1.15.4 in /docs (#9818)", + "< | 5809391d19ca149840df5b9ff8e7823a876cd408 fix(deps): update dependency @graphql-yoga/redis-event-target to v3 (#9909)", + "< | e16e32cfb24361032089a524aa6de5abacafba10 fix(deps): update dependency @graphql-yoga/plugin-graphql-sse to v3 (#9907)", + "< | c3d840d712d23a0cb2614edaac4e6786b300da77 cli-helpers: Don't emit tests to dist/ (#9900)", + "< | 7d12474863b9d931c182e8c48ccb38bf4fabb8d4 fix(deps): update dependency @graphql-yoga/plugin-persisted-operations to v3 (#9908)", + "< | 381850cbf01380a3c9c7bd22ff6223fe4f817d1b fix(deps): update dependency @graphql-yoga/plugin-defer-stream to v3 (#9906)", + "< | d9b87ea51d80fd855175830bc72660879dbf93ba fix(deps): update dependency @graphql-yoga/subscription to v5 (#9912)", + "< | 7b8d35965013f797d605b34a7386dd623f0bdd65 chore(tests): api-server: Improve types (#9896)", + "< | 891bb19442f272f59ada236526a6f09fe3af7057 fix(deps): update dependency graphql-yoga to v5.1.1 (#9913)", + "< | 0ca122430ffb6173d5e4be1712cb1a2d57958110 fix(deps): update dependency react-hook-form to v7.49.3 (#9910)", + "< | ffeb9bbb72f2889d609e858b96ffadc262c7df4e fix(deps): update dependency webpack to v5.90.0 (#9911)", + "< | ffc0fc5de7f2e662b2257287ca65cfbd4025b729 fix(deps): update dependency @envelop/depth-limit to v4 (#9905)", + "< | 15da3edcca0da7cbd7465f6206288a4f4ccbc02f fix(deps): update dependency @envelop/on-resolve to v4 (#9904)", + "< | f01a9441cd69323e82befd236b1927692a2ec7b1 fix(deps): update dependency @envelop/live-query to v7 (#9903)", + "< | cc10a4324f04d8361b42c9b521536295120f1c8d fix(deps): update dependency @envelop/filter-operation-type to v6 (#9902)", + "< | f71f12b8d58840d18541ade7f979c80d258b3d02 chore(deps): update dependency @envelop/testing to v7 (#9898)", + "< | 2710b2bd1542596108f680611f9b726c5d3c1f35 fix(deps): update dependency @envelop/disable-introspection to v6 (#9901)", + "< | 33c2d60e48c3b672ffc2819e20777f9f64e8ef59 chore(deps): update dependency @envelop/types to v5 (#9899)", + "< | 5a320f3b688e5918991bb9f9bc807b6587d4c062 chore(deps): update dependency esbuild to v0.20.0 (#9897)", + "< | f9c9b8f5073e87aef517836e7485d42e05c438d9 chore(deps): update babel monorepo (#9892)", + "< | 27353fef2007eccf484dca6075eb1a30bc4d6ba3 fix(serve): fix server listening logs (#9894)", + "< | 965132eeef08619ce4d221089c826cadef6236d0 fix(studio): Bump minimum RW canary version (#9895)", + "< | cbf13c65599b3b913719d4d739ad9155eaa0b221 fix(deps): update prisma monorepo to v5.8.1 (#9893)", + "< | ca5d00f38ef89ce2f20ba9430045276582a30a6c chore(cli): Move coerceRootPath() (#9891)", + "< | 97296d4c57e6dffb4eee2489397a3486d11e24fd chore(tests): api-server: Suppress logs (#9890)", + "< | e1216e92ee9cd1bc85bdcb04d8c78481b7f6ea9a chore(server): dedupe web serve logic (#9884)", + "< | d1058b3a9591ab513648f0ba863f18236045bcff chore(fixtures): Update gql file content as a result of trusted documents fix (#9889)", + "< | e1e3f35e657743eac545e7592f1a6cb774f27b05 fix(gql): Add back gql global type (#9888)", + "o | 802c2519c6623bf3bd52c9c9628d61d2bbeea0ba Update installation.md (#9887)", + " / ", + "o 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0", + ] + `) + }) +}) + +test('lineIsGitLogUi', () => { + expect(lineIsGitLogUi("|\\")).toEqual(true) + expect(lineIsGitLogUi("/")).toEqual(true) + expect(lineIsGitLogUi("o 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0")).toEqual(true) + expect(lineIsGitLogUi("o | 802c2519c6623bf3bd52c9c9628d61d2bbeea0ba Update installation.md (#9887)")).toEqual(true) + + expect(lineIsGitLogUi("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) + expect(lineIsGitLogUi("< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)")).toEqual(false) +}) + +test('lineIsAnnotatedTag', () => { + expect(lineIsAnnotatedTag("v7.0.0")).toEqual(true) + + expect(lineIsAnnotatedTag("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) + expect(lineIsAnnotatedTag("< c9d225b4a401dd6afe282973fc7646bcbe101344 chore(changelog): add v7 (#10038)")).toEqual(false) + expect(lineIsAnnotatedTag("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toEqual(false) + expect(lineIsAnnotatedTag("< | b9af37b39591b0004a47c97d34914de5aae22145 fix(deps): update storybook monorepo to v7.6.10 (#9917)")).toEqual(false) +}) + +test('lineIsChore', () => { + expect(lineIsChore("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toEqual(true) + expect(lineIsChore("< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock")).toEqual(true) + + expect(lineIsChore("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) + expect(lineIsChore("< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)")).toEqual(false) +}) + +describe('resolveLine', async () => { + let resetCwd: () => void + beforeAll(async () => { + resetCwd = await assertRwfwPathAndSetCwd() + }) + afterAll(() => { + resetCwd() + }) + + const range = { + from: 'main', + to: 'next' + } + + it('works', async () => { + const line = "< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)" + const milestone = 'chore' + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line: [line.padEnd(PADDING), `(${milestone})`].join(' '), + type: "commit", + ref: range.to, + notes: undefined, + + hash: "487548234b49bb93bb79ad89c7ac4a91ed6c0dc9", + message: "chore(deps): update dependency @playwright/test to v1.41.2 (#10040)", + + pr: "10040", + url: "https://github.com/redwoodjs/redwood/pull/10040", + milestone, + }) + }) + + it('ui', async () => { + const line = "|\\" + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line, + type: 'ui', + ref: range.from, + }) + }) + + it('chore', async () => { + const line = "< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'" + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line, + type: 'chore', + ref: range.from, + + hash: "635d6dea677b28993661a2e46659ff8c987b7275", + message: "Merge branch 'release/major/v7.0.0'", + }) + }) + + it('annotated tag', async () => { + const line = "< 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0" + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line, + type: "tag", + ref: "v7.0.0", + + hash: "49965f4db294112458dccabfce2b7044f3134bcb", + message: "v7.0.0", + }) + }) + + + it('no pr', async () => { + const line = "< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock" + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line, + type: 'chore', + ref: range.from, + + hash: "8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e", + message: "chore: update yarn.lock", + }) + }) + + it('ref', async () => { + const line = "< | e1e3f35e657743eac545e7592f1a6cb774f27b05 fix(gql): Add back gql global type (#9888)" + const milestone = 'v7.0.0' + + const commit = await resolveLine(line, { range }) + expect(commit).toEqual({ + line: [line.padEnd(PADDING), `(${milestone})`].join(' '), + type: 'commit', + ref: range.to, + + hash: "e1e3f35e657743eac545e7592f1a6cb774f27b05", + message: 'fix(gql): Add back gql global type (#9888)', + + pr: "9888", + url: "https://github.com/redwoodjs/redwood/pull/9888", + milestone, + }) + }) +}) + +test('getPrettyLine', () => { + const range = { from: 'main', to: 'next' } + const commit = { line: 'line' } + + commit.type = 'ui' + expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) + commit.type = 'tag' + expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) + commit.type = 'chore' + expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) + + commit.type = 'commit' + commit.ref = range.to + expect(getPrettyLine(commit, { range })).toEqual(colors.wasCherryPickedWithChanges(commit.line)) + + commit.ref = range.from + + commit.milestone = 'SSR' + expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) + commit.milestone = 'RSC' + expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) + delete commit.milestone + commit.notes = 'abc' + expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) +}) diff --git a/triage/lib/symmetric_difference.ts b/triage/lib/symmetric_difference.ts index 73cb825..86a626d 100755 --- a/triage/lib/symmetric_difference.ts +++ b/triage/lib/symmetric_difference.ts @@ -1,15 +1,20 @@ -import { fileURLToPath } from 'node:url' - -import { fs, $ } from "zx"; - -import { commitRegExps, commitIsInRef, getCommitHash } from '@lib/git.js' -import { gqlGitHub } from '@lib/github.js' +import { $ } from "zx"; + +import { + commitIsInRef, + commitRegExps, + getCommitHash, + getCommitMessage, + getCommitNotes, + getCommitPr +} from '@lib/commits.js' +import { getPrMilestone } from '@lib/milestones.js' import type { Commit, Range } from "@lib/types.js"; import { unwrap } from "@lib/zx_helpers.js"; import { colors } from './colors.js' -export const defaultGitLogOptions = [ +export const gitLogOptions = [ "--oneline", "--no-abbrev-commit", "--left-right", @@ -19,17 +24,10 @@ export const defaultGitLogOptions = [ "--boundary", ]; -interface GetSymmetricDifferenceOptions { - gitLogOptions?: string[]; -} - /* Get the symmetric difference between two refs. (Basically, what's different about them.) */ export async function getSymmetricDifference( range: Range, - { gitLogOptions }: GetSymmetricDifferenceOptions = {}, ) { - gitLogOptions ??= defaultGitLogOptions; - return unwrap( await $`git log ${gitLogOptions} ${range.from}...${range.to}`, ).split("\n") @@ -84,66 +82,12 @@ export async function resolveLine(line: string, { range }: { range: Range }) { return commit } commit.url = `https://github.com/redwoodjs/redwood/pull/${commit.pr}` - commit.milestone = await getCommitMilestone(commit.url) + commit.milestone = await getPrMilestone(commit.url) commit.line = [commit.line.padEnd(PADDING), `(${commit.milestone})`].join(' ') return commit } -/** Get a commit's message from its 40-character hash */ -export async function getCommitMessage(hash: string) { - return unwrap(await $`git log --format=%s -n 1 ${hash}`) -} - -/** Get a commit's PR (if it has one) */ -export function getCommitPr(message: string) { - return message.match(commitRegExps.pr)?.groups?.pr -} - -let cache: Map - -async function setUpCache() { - const cacheFilePath = fileURLToPath(new URL('commit_milestone_cache.json', import.meta.url)) - - if (!await fs.pathExists(cacheFilePath)) { - return new Map() - } - - const commitMilestoneCache = await fs.readJson(cacheFilePath) - return new Map(Object.entries(commitMilestoneCache)) - - process.on('exit', () => { - fs.writeJsonSync(cacheFilePath, Object.fromEntries(cache)) - }) -} - -const query = `\ - query GetCommitMilestone($prUrl: URI!) { - resource(url: $prUrl) { - ...on PullRequest { - milestone { - title - } - } - } - } -` - -/** Get a commit that has a PR's milestone */ -export async function getCommitMilestone(prUrl: string) { - if (!cache) { - cache = await setUpCache() - } - if (cache.has(prUrl)) { - return cache.get(prUrl) - } - const { data } = await gqlGitHub({ query, variables: { prUrl } }) - - const milestone = data.resource.milestone.title - cache.set(prUrl, milestone) - return milestone -} - const MARKS = ["o", "/", "|\\", "| o", "|\\|", "|/"]; /** Determine if a line from `git log --graph` is just UI */ @@ -194,12 +138,3 @@ export function getPrettyLine(commit: Commit, { range }: { range: Range }) { return commit.line } - -async function getCommitNotes(hash: string) { - try { - const notes = unwrap(await $`git notes show ${hash}`) - return notes - } catch (error) { - return undefined - } -} diff --git a/triage/lib/triage.test.ts b/triage/lib/triage.test.ts index 31fbae7..28ecd2b 100755 --- a/triage/lib/triage.test.ts +++ b/triage/lib/triage.test.ts @@ -1,441 +1,37 @@ -import { chalk, $ } from 'zx' +import { describe, expect, test } from 'vitest' +import { $ } from 'zx' -import { beforeAll, afterAll, describe, expect, it, test } from 'vitest' - -import { getCommitHash } from '@lib/git.js' - -import { setCwd } from '../../lib/set_cwd.js' -import { - defaultGitLogOptions, - getCommitMessage, - getCommitMilestone, - getCommitPr, - getPrettyLine, - getSymmetricDifference, - lineIsAnnotatedTag, - lineIsChore, - lineIsGitLogUi, - PADDING, - resolveLine, -} from './symmetric_difference.js' -import { colors } from './colors.js' import { commitIsEligibleForCherryPick } from './triage.js' $.verbose = false -test('defaultGitLogOptions', () => { - expect(defaultGitLogOptions).toMatchInlineSnapshot(` - [ - "--oneline", - "--no-abbrev-commit", - "--left-right", - "--graph", - "--left-only", - "--cherry-pick", - "--boundary", - ] - `) -}) - -describe('getSymmetricDifference', () => { - let resetCwd: () => void - beforeAll(async () => { - resetCwd = await setCwd() - }) - afterAll(() => { - resetCwd() - }) - - it('works', async () => { - const symDiff = await getSymmetricDifference({ from: 'release-tooling/main-test', to: 'release-tooling/next-test' }) - expect(symDiff).toMatchInlineSnapshot(` - [ - "< 9b1dd056d2c33b23a63874f23f4efb5808e0660e Add support for additional env var files (#9961)", - "< ba6934911a242a6e328571493c263970b42174be Update studio.md (#10062)", - "< 2a89267b92d11e7b44c5ff2f18d0427ae0476121 chore(rename): Be consistent with 'for' prefix for babel plugin option (#10059)", - "< 553f4ac7d6bc28bfc93ac59ad6c2746ff5e71ce5 RSC: Remove commented code from worker (#10058)", - "< d5214c92cde6bfdb14c6c1769250c31b23d56328 fix(render): reduce memory and handle server file (#10055)", - "< 1f02ae3cc205cdb2cc98bd6a02a7f264b11cb96b chore(release): update changelog (v7.0.1, v7.0.2)", - "< 9b6047b0e48762959b7f3d382ee3dd3c9237ef5b RSC: Simplify entriesFile loading (#10057)", - "< 965b7ea3901a7e82201bea2090eaaec77e37a210 chore(types): Get rid of TS typecast in babel config (#10056)", - "< 95ba45c24911649fe1e2bcb5df3af6e7276b090a Update MetaTags to be MetaData in Docs (#10053)", - "< 9ad5c7c4bf5cefad0d05fc3a251ea27d1068e8e9 chore(deps): bump ip from 2.0.0 to 2.0.1 (#10043)", - "< a3d955886b61c3a05ff7aea27711adbdb8e1f964 chore(deps): bump ip from 1.1.5 to 1.1.9 in /docs (#10047)", - "< c171208de79866b502a6edcfa8a10b151ed1fcf7 RSC: Transform client components during build (#10048)", - "< e25b51984037d364dfdaf0b038074dbf7b9532c8 RSC: Upgrade test-project-rsa to v8 canary (#10050)", - "< 5e8bc3a1268d486bbbb9e796b3822fcc21a8f0cb RSC: Refactor: extract common code for env vars (#10049)", - "< a59205c826e5e980437a86397d5df8c65c7d5759 RSC: Refactor node-loader and some vite plugins (#10046)", - "< d012c60f9ba64e023837ceda35f0944923c751e6 chore(deps): acorn-loose 8.4.0 (#10042)", - "< 1de0d68a8c04fc8fb05988ca11706f15f68c5c6f chore(ci): update yarn.lock for changelog action (#10039)", - "< 4f612f813f6dd10fe8861c2a5eba21a0011cb1c6 chore: bump TSTyche (#10036)", - "< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'", - "|\\ ", - "< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)", - "< | 4b5f0243354553d59e744574f09803e3f42a17ba fix(server): prefix port/host with api, fix logging (#10035)", - "< | cf3c4211cda0b7d7f68b27948055ff4312c125fe docs: Removes warning within Mailer documentation about Studio being experimental (#10033)", - "< | 369c9e5aa9493ba2806870fadc3f6f16af7e5aa6 docs: Within describeScenario documentation, change optimisation to use American English (#10032)", - "< | 70602deaafdc3481d791900b6ed5e0970eacf0d7 RSC: No basePath arg to serve() (#10030)", - "< | 12d3fc18e6948f6b52065fc9bf96e4cbaba8a942 RSC: Add MultiCellPage to test fixture (#10029)", - "< | e9261ce71687fd6dd579dcc9ea145eda4d039220 RSC chore(tests): Add links to scaffolds in test fixture (#10028)", - "< | ad9bef80715382abc54f0ef09c441e3925b10f95 docs(metadata): Fix spelling/typos (#10027)", - "< | 3289f4593feaf014fb983a55e6ba6e80a0ead227 chore(release): link to previous releases and upgrade guides in the changelog (#10026)", - "< | 43182f14cc5b6755a043bcce2cf158d444e6da78 chore(ci): add changelog check to ci (#9989)", - "< | 7fc06486c8be34ae010294bb4ccf5170311d5ae2 fix(realtime): update logic for including sseLink (#10025)", - "< | 903f8d426d4e62620fbe4c4292943028629e58f1 chore(k6 tests): update entry point (#10024)", - "< | e4a7676cdfbf3c46165ba5185efab07dc7f1f395 feat(server): add docs on the server file (#10019)", - "< | ad493cd79b606776a676961a9c158933cff0a612 fix(types): Fix TS type in createServer.test.ts (#10023)", - "< | 4891360759adbf76bd7b70f9a67623cff47484e2 fix: Handle static assets on the \`rw-serve-fe\` (#10018)", - "< | 0f158c2e7745e3a7900824c32f996ce80e84ee50 fix(server): fix env var loading in \`createServer\` (#10021)", - "< | e2570881db4dc4a9635d94ddf96bfc23488e87cb fix(deps): remove react types packages from \`@redwoodjs/testing\` dependencies (#10020)", - "< | 824ff14955ec30e8a616c8e5bdbce6d530d92689 chore(release): add back \`update-package-versions\` task (#10017)", - "< | 85a0bfa346677518aca4cd80a16a92c663393bf2 chore(renovate): Disable for experimental apollo package (#10016)", - "< | ef015ca4d2737d23dcb74cc5ea39c5e7a4c15d9d RSC: server cells lowercase data function (#10015)", - "< | cfd4c9238269cbace63c988539170a5620dd1821 fix(RSC/SSR): pass CLI options through to apiServerHandler (#10012)", - "< | 8b499f99235a654d14f629e5318368c0e22b743a 7.0 RC: Remove hardcoded check for \`session.id\` (#10013)", - "< | ff138d1d63e3723650b7a2760b3f7628225f5f92 Spelling fix in what-is-redwood.md (#10011)", - "< | 2e3b0f4a5c92b57df74748721998d2b57b3b30a5 Typos in realtime.md (#10010)", - "< | 642238da6aa91c89dce80d9c3c1751d97808a333 RSC: Server cell smoke tests (#10008)", - "< | d44e03afcf617ad90c903706032bda4f03462c95 RSC: test-project EmptyUser 'use client' cell (#10007)", - "< | 0c5d91d9925cbee8b5f5fa15ad5d20e372d1e0d7 RSC: babel-plugin-redwood-cell remove redundant reset (#10006)", - "< | d47bddcbbf61903648e9751af3ab949edb76d948 chore(deps): Upgrade to yarn v4.1.0 (#10002)", - "< | 6132ed5cb4eafc17ee9a11399a33125b2e766dab fix(docs): Spelling of \`data-migrate\` command (#10003)", - "< | fa2887cf9a9069bb0f4bd7249b362ff522e58bcc docs: add aliases fo \`type-check\` command (#10004)", - "< | 0ef0289221781e9d2751507f1a17607694b43255 RSC: Insert 'use client' in scaffolded components (#9998)", - "< | e5f09e49062af04aa030517930fb7e1e90ca5611 fix(telemetry): Fix 'destroy' spelling (#10000)", - "< | dd8efec66891fd855de79d7321a69b75b0465890 chore(jsdocs): Fix jsdoc formatting for hover help (#9999)", - "< | cba4a68547de1a7bf344d28672937e207d2cb196 bug: Update setupHandler.ts firebase version (#9997)", - "< | 7c9dfd688dde35d843fead3135d76dae3126b515 RSC: Keep aligning test project with CRWA template (#9993)", - "< | 7ee21cd87e8f3a8f14216ee0aa4b5e36c5405c92 RSC: Fix babel-plugin-redwood-cell to work wiht more than one cell (#9994)", - "< | 43cb6414e102ba62354f7ae8a3b4af11763ef448 fix(dependencies): Use RW-specific version of apollo ssr package (#9992)", - "< | 44a980bca870f28867a21e3927b983bbb3c5ad08 RSC: chore(test): Update RSC test fixture project (#9990)", - "< | 45e6ce70ccdbbdfb612cc353f25e59e902eb5bce RSC: createServerCell (#9987)", - "< | 85737712fc230bae3f550e6da8001b53141d78cf chore(refactor): Router: Splitting things up into smaller files (#9988)", - "< | ed033ee59eb5ec178fe9e6fb02f0cfbf809d8e53 chore(project-config): make chore changes to trigger ci (#9985)", - "< | 9b009b091b0f8102e05df4bb02c1e3f093ea5cea chore: update rsc fixture (#9986)", - "< | 9df7377db14407ada2419c92b2dd30e1a42f4d1e fix(server): use file extension in import, fix graphql route registering (#9984)", - "< | 50b455f7353baabe9775e264a9f9ee60d98303ca chore(deps): update babel monorepo (#9983)", - "< | 9de6d0e38cf5c15d73f12eb8f72f5701e1253137 fix: unpin react types (#9727)", - "< | 60d031db0612d57c961a205b7f1ee927fb70191d fix(docker): compose dev and prod (#9982)", - "< | 2c15510f94bab59ce7b9173c7323b720b3dee076 fix(deps): update prisma monorepo to v5.9.1 (#9980)", - "< | 180615bed290363e083caf15e2f4d23c29131185 fix(cli): use fetch instead of \`yarn npm info\` (#9975)", - "< | bcf191dfaafcba8d40853078bef2ec9975cbb978 fix(test): Update createServer test to use a different port to normal (#9977)", - "< | 4b06f06539d774937af297923be576c6a2ed582f fix(docker): corepack permissions fix and style updates (#9976)", - "< | cd65bf6ff33675e85fd2f17caa1cfe4d7c8907b9 fix(deps) resolve yarn warnings regarding unmet peer dependencies for Redwood projects (#8874)", - "< | 019361550736f5ed733c37d7f5241583f26d405b chore(build): use \`tsx\` to run build scripts (#9941)", - "< | 3c04b6c3f986f68f25a5532ed74be99cc801464c RSC: Client Cell support smoke-test (#9974)", - "< | 3bbb5f5c9946995d4bb5695ff715df868d742dc1 RSC: Enable babel plugins to get client Cell support (#9973)", - "< | 57c5eca1a15b2cbbc2c600eaf6d8167983f590a3 RSC: Take full control over the env vars (#9972)", - "< | f736662b8552fd998470fd4a684d97228226a108 RSC: Make RWJS_ENV etc available on the server during runtime (#9971)", - "< | 11b678286e97081b65e8a575878016a16072fbb0 chore(cleanup): Remove debug console.log call (#9967)", - "< | 906c143b2feccf8eb28859450888fa606c31df3d fix(studio): Remove unused settings 'endpoint' and 'roles' (#9966)", - "< | f3a865d54b650fb1749e3bb1ad5abf42dc643c2b cli: Remove graphiql setup command (#9964)", - "< | b0d60f3ccf97827fd9cabe8289f8deaac6568999 fix(cell-suspense): pass through variables if passed to refetch (#9965)", - "< | d2b7f7fb4d70243616d9f9dcf7c5bfe1e6a4981f Mention the you need corepack enabled to run this repo (#9960)", - "< | b930777e64276cfcb7a8f340e1dd8f7053e9956f chore(server): improve server tests (#9958)", - "< | 5aa69919a66ca271f19fe82d9111a5583015020e feat(gql): Codemod existing projects to get newest gql config (#9959)", - "< | 1039545a171adee0ace8f6ad9f85b495e08b2fe6 Update Studio docs (#9955)", - "< | 70c1c19d861c0efba94a31cb699bcf530067cf81 fix(gql): Better graphql.config.js template (#9957)", - "< | c6cacf4dfb18bd95ab8a5143466d8c67ff00764d fix(studio): Remove unused setting inMemory (#9956)", - "< | 3fe639eff701625e3c8febf22b6fb8cb19d94df7 docs(typo): Removed \`rw\` from \`yarn install\` command (#9954)", - "< | 2d0100d60e4237bd82b230cc5f7ae3c5fc77a8b0 chore(deps): update dependency firebase to v10.7.0 (#9605)", - "< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock", - "< | 469770d580b015309e26b9a9529044c9cda5a886 fix(deps): update dependency firebase-admin to v11.11.1 (#9953)", - "< | c409341ab3e077ecbc8e84626080c22b215f45fd chore(api-server): switch to vitest (#9929)", - "< | 4773fc8653a10db40d67102561d96490cf5b9cff chore(test): Remove yarn.lock from some test fixtures (#9952)", - "< | 7bce9b04a4e880c5d6deb801a2924150277f9dee fix(deps): update dependency nodemailer to v6.9.9 [security] (#9951)", - "< | c578051190384309b874d573f0ecb24b4df741e6 fix(api-server): Remove duplicate command alias (#9950)", - "< | 80f499e521c501c92959cae715ca80ee92225895 fix(server): spelling, fix deploy handler imports, dedupe server builder (#9949)", - "< | 887dba98722ea48ed97304287e1e9727d46a9577 feat(server): dedupe api server code, make host configurable (#9948)", - "< | 909eeac01f36a1b08a93187338ae3b7f60655ab1 chore(ts): Remove src alias and baseUrl from all tsconfigs (#9944)", - "< | 8247653ea1299ab30f3aa17cc94b5c6c2b594933 @redwoodjs/framework-tools for buildDefaults (#9947)", - "< | a7b1573148970062549c4c4ab9b53c9fa1a4f270 fix(internal): Remove unused import (#9946)", - "< | 21237efcff9fcde4dbdbce9120acd920f204d332 chore(crwa): Use actual filename in seed file template (#9945)", - "< | 2d9c9bb9d459298e4fbaf753908372f579bd745c fix(cli): Prevent caching blank information about plugins (#9942)", - "< | 199fce1c4c339fdc5fa50ca0bcb38d42d4ddf2c7 chore(apollo): Introduce TS type helper to reduce repetition (#9943)", - "< | ff7d79cd0df190656557e922d698a8f69138bef4 feat(middleware): Add support for Middleware to SSR-Streaming server (#9883)", - "< | 7fdf7bb427b6cfcf0d74c1f9f84191b72798efc2 chore(deps): update node.js to v20 (#9936)", - "< | d134f427938b5a2127c67b626902e8043b328844 feat(crwa): Make the seed template idempotent (#9937)", - "< | b31bb6ae4bb681ef6d20235f8dc9d2613f56a345 docs(monitoring): Add Sentry docs (#9934)", - "< | a0cfcc9b5519074bc7c391426e98637487fcd7e5 fix(deps): update prisma monorepo to v5.9.0 (#9935)", - "< | 8d07dc2d4f5a63b02db134ca0942ae94366270d3 fix(docusaurus): Make {jsx,tsx} file extensions switch properly (#9933)", - "< | edfdf99bb852ad17e9ebc48c98bb92f2b15bf628 fix(server): don't mix async and callback styles (#9931)", - "< | 43bc6ebc44880f9a2173fca7a784fbb0526d6bec chore(forms): switch tests to vitest (#9928)", - "< | 472c86bed8f1ee6d7a528ca6d494406553fc072e fix(web): import helmet from node_modules (#9927)", - "< | 63aba23069fb737f63cdb4f48178fd1f73949dce chore(deps): update dependency @testing-library/jest-dom to v6.3.0 (#9926)", - "< | 5227b71c20ba3966d6436d08164712b9453bff8e chore(deps): update dependency @clerk/clerk-react to v4.30.3 (#9922)", - "< | e03108c4c8adaf3ca644b540ec97b0826dee5122 fix(deps): update dependency @testing-library/user-event to v14.5.2 (#9925)", - "< | fb47d08c3d07cfd1e00f47f3475139ba3486b36f chore(deps): update dependency @testing-library/dom to v9.3.4 (#9924)", - "< | 491cef16d97b35d7ab98e22dc2e85794f9c7e62f fix(deps): update dependency @clerk/clerk-sdk-node to v4.13.7 (#9923)", - "< | c67a74d2a8e54a68b04768f70a6964cad1d68b33 fix(deps): update dependency core-js to v3.35.1 (#9919)", - "< | 120ab90e41ce3b8069c176d50d721244ef24c82c fix(deps): update dependency fastify to v4.25.2 (#9920)", - "< | 441e754557ab803ff9628a36da5f94dd08a23593 fix(deps): update dependency graphql-sse to v2.5.2 (#9921)", - "< | 970205bd28b9c7a8093ae806c9d523f2e2a9feb7 chore(deps): update dependency @playwright/test to v1.41.1 (#9918)", - "< | 013f9c2623d408f66315546c58c0f1802731f731 fix(deps): update apollo graphql packages (#9916)", - "< | ce0a064f8787b7440ac292f7faeb2518fee06f02 chore(deps): update dependency vitest to v1.2.2 (#9915)", - "< | b9af37b39591b0004a47c97d34914de5aae22145 fix(deps): update storybook monorepo to v7.6.10 (#9917)", - "< | c63f332164b30261af58d4dde0e8a6743557d96d chore(deps): update dependency lerna to v8.0.2 (#9914)", - "< | fb9f3023bf1387feed25e33282a0178d1b6df260 chore(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#9817)", - "< | d7e343b134a79081a52debf03b895a4cd5029de8 fix(deps): update dependency vite to v4.5.2 [security] (#9852)", - "< | 36d49bc944a3a296eb54c7ad0a3f33eb7c50843c chore(deps): bump @fastify/reply-from from 9.4.0 to 9.6.0 (#9813)", - "< | e4ff05157a462b41443955ec831fa93d00d24c59 chore(deps): bump follow-redirects from 1.15.3 to 1.15.4 in /docs (#9818)", - "< | 5809391d19ca149840df5b9ff8e7823a876cd408 fix(deps): update dependency @graphql-yoga/redis-event-target to v3 (#9909)", - "< | e16e32cfb24361032089a524aa6de5abacafba10 fix(deps): update dependency @graphql-yoga/plugin-graphql-sse to v3 (#9907)", - "< | c3d840d712d23a0cb2614edaac4e6786b300da77 cli-helpers: Don't emit tests to dist/ (#9900)", - "< | 7d12474863b9d931c182e8c48ccb38bf4fabb8d4 fix(deps): update dependency @graphql-yoga/plugin-persisted-operations to v3 (#9908)", - "< | 381850cbf01380a3c9c7bd22ff6223fe4f817d1b fix(deps): update dependency @graphql-yoga/plugin-defer-stream to v3 (#9906)", - "< | d9b87ea51d80fd855175830bc72660879dbf93ba fix(deps): update dependency @graphql-yoga/subscription to v5 (#9912)", - "< | 7b8d35965013f797d605b34a7386dd623f0bdd65 chore(tests): api-server: Improve types (#9896)", - "< | 891bb19442f272f59ada236526a6f09fe3af7057 fix(deps): update dependency graphql-yoga to v5.1.1 (#9913)", - "< | 0ca122430ffb6173d5e4be1712cb1a2d57958110 fix(deps): update dependency react-hook-form to v7.49.3 (#9910)", - "< | ffeb9bbb72f2889d609e858b96ffadc262c7df4e fix(deps): update dependency webpack to v5.90.0 (#9911)", - "< | ffc0fc5de7f2e662b2257287ca65cfbd4025b729 fix(deps): update dependency @envelop/depth-limit to v4 (#9905)", - "< | 15da3edcca0da7cbd7465f6206288a4f4ccbc02f fix(deps): update dependency @envelop/on-resolve to v4 (#9904)", - "< | f01a9441cd69323e82befd236b1927692a2ec7b1 fix(deps): update dependency @envelop/live-query to v7 (#9903)", - "< | cc10a4324f04d8361b42c9b521536295120f1c8d fix(deps): update dependency @envelop/filter-operation-type to v6 (#9902)", - "< | f71f12b8d58840d18541ade7f979c80d258b3d02 chore(deps): update dependency @envelop/testing to v7 (#9898)", - "< | 2710b2bd1542596108f680611f9b726c5d3c1f35 fix(deps): update dependency @envelop/disable-introspection to v6 (#9901)", - "< | 33c2d60e48c3b672ffc2819e20777f9f64e8ef59 chore(deps): update dependency @envelop/types to v5 (#9899)", - "< | 5a320f3b688e5918991bb9f9bc807b6587d4c062 chore(deps): update dependency esbuild to v0.20.0 (#9897)", - "< | f9c9b8f5073e87aef517836e7485d42e05c438d9 chore(deps): update babel monorepo (#9892)", - "< | 27353fef2007eccf484dca6075eb1a30bc4d6ba3 fix(serve): fix server listening logs (#9894)", - "< | 965132eeef08619ce4d221089c826cadef6236d0 fix(studio): Bump minimum RW canary version (#9895)", - "< | cbf13c65599b3b913719d4d739ad9155eaa0b221 fix(deps): update prisma monorepo to v5.8.1 (#9893)", - "< | ca5d00f38ef89ce2f20ba9430045276582a30a6c chore(cli): Move coerceRootPath() (#9891)", - "< | 97296d4c57e6dffb4eee2489397a3486d11e24fd chore(tests): api-server: Suppress logs (#9890)", - "< | e1216e92ee9cd1bc85bdcb04d8c78481b7f6ea9a chore(server): dedupe web serve logic (#9884)", - "< | d1058b3a9591ab513648f0ba863f18236045bcff chore(fixtures): Update gql file content as a result of trusted documents fix (#9889)", - "< | e1e3f35e657743eac545e7592f1a6cb774f27b05 fix(gql): Add back gql global type (#9888)", - "o | 802c2519c6623bf3bd52c9c9628d61d2bbeea0ba Update installation.md (#9887)", - " / ", - "o 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0", - ] - `) - }) -}) - -describe('getCommitHash', () => { - it('works', () => { - const hash = getCommitHash("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)") - expect(hash).toEqual('487548234b49bb93bb79ad89c7ac4a91ed6c0dc9') - }) - - it('throws if no hash is found', () => { - expect(() => getCommitHash("|\\")).toThrowErrorMatchingInlineSnapshot(` - [Error: Couldn't find a commit hash in the line "|\\" - This most likely means that a line that's UI isn't being identified as such] - `) - }) - - it('works for non left-right lines', () => { - const hash = getCommitHash("487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)") - expect(hash).toEqual('487548234b49bb93bb79ad89c7ac4a91ed6c0dc9') - - }) -}) - -test('getCommitMessage', async () => { - const message = await getCommitMessage('4f4ad5989b794ddd0065d9c96c3091343c2a63c0') - expect(message).toEqual('Initial commit') -}) - -test('getCommitPr', () => { - expect(getCommitPr("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual('10040') - expect(getCommitPr("< 5f89c0176f517b894cb2c3f1ab9cee4c7c207393 Revert \"Revert `@testing-library/jest-dom` v6 upgrade (#9713)\" (#9719)")).toEqual('9719') - expect(getCommitPr("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toBeUndefined() -}) - -test('getCommitMilestone', async () => { - const milestone = await getCommitMilestone("https://github.com/redwoodjs/redwood/pull/9803") - expect(milestone).toEqual('chore') -}) - -test('lineIsGitLogUi', () => { - expect(lineIsGitLogUi("|\\")).toEqual(true) - expect(lineIsGitLogUi("/")).toEqual(true) - expect(lineIsGitLogUi("o 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0")).toEqual(true) - expect(lineIsGitLogUi("o | 802c2519c6623bf3bd52c9c9628d61d2bbeea0ba Update installation.md (#9887)")).toEqual(true) - - expect(lineIsGitLogUi("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) - expect(lineIsGitLogUi("< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)")).toEqual(false) -}) - -test('lineIsAnnotatedTag', () => { - expect(lineIsAnnotatedTag("v7.0.0")).toEqual(true) - - expect(lineIsAnnotatedTag("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) - expect(lineIsAnnotatedTag("< c9d225b4a401dd6afe282973fc7646bcbe101344 chore(changelog): add v7 (#10038)")).toEqual(false) - expect(lineIsAnnotatedTag("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toEqual(false) - expect(lineIsAnnotatedTag("< | b9af37b39591b0004a47c97d34914de5aae22145 fix(deps): update storybook monorepo to v7.6.10 (#9917)")).toEqual(false) -}) - -test('lineIsChore', () => { - expect(lineIsChore("< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'")).toEqual(true) - expect(lineIsChore("< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock")).toEqual(true) - - expect(lineIsChore("< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)")).toEqual(false) - expect(lineIsChore("< | 8b467685a7cdce55f8be6424793bc5e6ad450c0a chore(docs): align v6 docs with the next branch (#10034)")).toEqual(false) -}) - -describe('resolveLine', async () => { - let resetCwd: () => void - beforeAll(async () => { - resetCwd = await setCwd() - }) - afterAll(() => { - resetCwd() - }) - - const range = { - from: 'main', - to: 'next' - } - - it('works', async () => { - const line = "< 487548234b49bb93bb79ad89c7ac4a91ed6c0dc9 chore(deps): update dependency @playwright/test to v1.41.2 (#10040)" - const milestone = 'chore' - - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line: [line.padEnd(PADDING), `(${milestone})`].join(' '), - type: "commit", - ref: range.to, - notes: undefined, - - hash: "487548234b49bb93bb79ad89c7ac4a91ed6c0dc9", - message: "chore(deps): update dependency @playwright/test to v1.41.2 (#10040)", - - pr: "10040", - url: "https://github.com/redwoodjs/redwood/pull/10040", - milestone, - }) - }) - - it('ui', async () => { - const line = "|\\" - - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line, - type: 'ui', - ref: range.from, - }) - }) - - it('chore', async () => { - const line = "< 635d6dea677b28993661a2e46659ff8c987b7275 Merge branch 'release/major/v7.0.0'" +describe('triage', () => { + test('commitIsEligibleForCherryPick', () => { + const range = { from: 'main', to: 'next' } + const commit = {} - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line, - type: 'chore', - ref: range.from, + commit.type = 'ui' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) + commit.type = 'tag' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) + commit.type = 'chore' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - hash: "635d6dea677b28993661a2e46659ff8c987b7275", - message: "Merge branch 'release/major/v7.0.0'", - }) - }) + commit.type = 'commit' + commit.ref = range.to + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - it('annotated tag', async () => { - const line = "< 49965f4db294112458dccabfce2b7044f3134bcb v7.0.0" + commit.ref = range.from - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line, - type: "tag", - ref: "v7.0.0", + commit.milestone = 'SSR' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) + commit.milestone = 'RSC' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) + delete commit.milestone + commit.notes = 'abc' + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - hash: "49965f4db294112458dccabfce2b7044f3134bcb", - message: "v7.0.0", - }) + delete commit.notes + expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(true) }) - - - it('no pr', async () => { - const line = "< | 8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e chore: update yarn.lock" - - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line, - type: 'chore', - ref: range.from, - - hash: "8ae6eb1ab306e9a3a925d9dbe9eb84c57f2cc67e", - message: "chore: update yarn.lock", - }) - }) - - it('ref', async () => { - const line = "< | e1e3f35e657743eac545e7592f1a6cb774f27b05 fix(gql): Add back gql global type (#9888)" - const milestone = 'v7.0.0' - - const commit = await resolveLine(line, { range }) - expect(commit).toEqual({ - line: [line.padEnd(PADDING), `(${milestone})`].join(' '), - type: 'commit', - ref: range.to, - - hash: "e1e3f35e657743eac545e7592f1a6cb774f27b05", - message: 'fix(gql): Add back gql global type (#9888)', - - pr: "9888", - url: "https://github.com/redwoodjs/redwood/pull/9888", - milestone, - }) - }) -}) - - -test('getPrettyLine', () => { - const range = { from: 'main', to: 'next' } - const commit = { line: 'line' } - - commit.type = 'ui' - expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) - commit.type = 'tag' - expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) - commit.type = 'chore' - expect(getPrettyLine(commit, { range })).toEqual(colors.choreOrDecorative(commit.line)) - - commit.type = 'commit' - commit.ref = range.to - expect(getPrettyLine(commit, { range })).toEqual(colors.wasCherryPickedWithChanges(commit.line)) - - commit.ref = range.from - - commit.milestone = 'SSR' - expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) - commit.milestone = 'RSC' - expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) - delete commit.milestone - commit.notes = 'abc' - expect(getPrettyLine(commit, { range })).toEqual(colors.shouldntBeCherryPicked(commit.line)) -}) - -test('commitIsEligibleForCherryPick', () => { - const range = { from: 'main', to: 'next' } - const commit = {} - - commit.type = 'ui' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - commit.type = 'tag' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - commit.type = 'chore' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - - commit.type = 'commit' - commit.ref = range.to - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - - commit.ref = range.from - - commit.milestone = 'SSR' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - commit.milestone = 'RSC' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - delete commit.milestone - commit.notes = 'abc' - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(false) - - delete commit.notes - expect(commitIsEligibleForCherryPick(commit, { range })).toEqual(true) }) diff --git a/triage/lib/triage.ts b/triage/lib/triage.ts index 02a13e3..0bfef5a 100755 --- a/triage/lib/triage.ts +++ b/triage/lib/triage.ts @@ -2,15 +2,16 @@ import { fileURLToPath } from 'node:url' import { chalk, fs, path, question, $ } from 'zx' +import { pushBranch } from '@lib/branches.js' import { consoleBoxen, separator } from '@lib/console_helpers.js' -import { cherryPickCommits, reportCommitsEligibleForCherryPick } from '@lib/cherry_pick.js' -import { pushBranch, pushNotes } from '@lib/github.js' +import { cherryPickCommits, reportCommitsEligibleForCherryPick } from '@lib/cherry_pick_commits.js' +import { pushNotes } from '@lib/notes.js' import { resIsYes } from '@lib/prompts.js' import type { Commit, PrettyCommit, Range } from '@lib/types.js' import { unwrap } from '@lib/zx_helpers.js' -import { getPrettyLine, getSymmetricDifference, resolveSymmetricDifference } from './symmetric_difference.js' import { colors } from './colors.js' +import { getPrettyLine, getSymmetricDifference, resolveSymmetricDifference } from './symmetric_difference.js' export async function triageRange(range: Range, { remote }: { remote: string }) { const key = await cache.getKey(range) @@ -42,11 +43,11 @@ export async function triageRange(range: Range, { remote }: { remote: string }) await cherryPickCommits(commitsEligibleForCherryPick.toReversed(), { range }) console.log(separator) - const okToPushNotes = resIsYes(await question('Ok to push notes? [Y/n] > ')) + const okToPushNotes = resIsYes(await question(`Ok to push notes to ${chalk.magenta('origin')}? [Y/n] > `)) if (okToPushNotes) { await pushNotes(remote) } - const okToPushBranch = resIsYes(await question(`Ok to push ${range.to}? [Y/n] > `)) + const okToPushBranch = resIsYes(await question(`Ok to push ${chalk.magenta(range.to)} to ${chalk.magenta('origin')}? [Y/n] > `)) if (okToPushBranch) { await pushBranch(range.to, remote) } diff --git a/triage/run.ts b/triage/run.ts index cac6fe8..3375d08 100755 --- a/triage/run.ts +++ b/triage/run.ts @@ -1,15 +1,20 @@ +import 'dotenv/config' + +import { assertBranchExistsAndTracksRemote, assertWorkTreeIsClean, pullBranch } from '@lib/branches.js' +import { assertGitHubToken } from '@lib/assert_github_token.js' import { consoleBoxen, separator } from '@lib/console_helpers.js' -import { setCwd } from '@lib/set_cwd.js' import { CustomError } from '@lib/custom_error.js' -import { assertBranchExists, assertWorkTreeIsClean, getRedwoodRemote } from '@lib/git.js' -import { fetchNotes, pullBranch } from '@lib/github.js' +import { assertRwfwPathAndSetCwd } from '@lib/cwd.js' +import { getRedwoodRemote } from '@lib/get_redwood_remote.js' +import { fetchNotes } from '@lib/notes.js' -import { getOptions } from './lib/options.js' +import { getRange } from './lib/get_range.js' import { triageRange } from "./lib/triage.js"; try { - await setCwd() + assertGitHubToken() + await assertRwfwPathAndSetCwd() console.log(separator) await assertWorkTreeIsClean() @@ -17,19 +22,19 @@ try { const remote = await getRedwoodRemote() console.log(separator) - const options = await getOptions() + const range = await getRange() console.log(separator) - await assertBranchExists(options.range.from) - await assertBranchExists(options.range.to) + await assertBranchExistsAndTracksRemote(range.from, remote) + await assertBranchExistsAndTracksRemote(range.to, remote) console.log(separator) - await pullBranch(options.range.from, remote) - await pullBranch(options.range.to, remote) + await pullBranch(range.from, remote) + await pullBranch(range.to, remote) await fetchNotes(remote) console.log(separator) - await triageRange(options.range, { remote }); + await triageRange(range, { remote }); } catch (error) { process.exitCode = 1; diff --git a/scripts/test_triage.sh b/triage/run_test.sh similarity index 93% rename from scripts/test_triage.sh rename to triage/run_test.sh index 64166ae..4250a91 100755 --- a/scripts/test_triage.sh +++ b/triage/run_test.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Small script for testing triage/run.ts. +# Small script for testing ./triage/run.ts. # Comment in the desired test case and run the script. # # - Worktree isn't clean diff --git a/wip.ts b/wip.ts new file mode 100644 index 0000000..2763abc --- /dev/null +++ b/wip.ts @@ -0,0 +1,8 @@ +import { assertRwfwPathAndSetCwd } from './lib/cwd.js' +import { mergeReleaseBranch } from './release/lib/release.js' + +await assertRwfwPathAndSetCwd() +await mergeReleaseBranch({ + releaseBranch: 'release/patch/v7.0.4', + remote: 'origin', +}) diff --git a/yarn.lock b/yarn.lock index ab318d7..23850ec 100755 --- a/yarn.lock +++ b/yarn.lock @@ -786,6 +786,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f + languageName: node + linkType: hard + "duplexer@npm:~0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -1905,6 +1912,7 @@ __metadata: "@types/prompts": "npm:2.4.9" "@types/semver": "npm:7.5.8" boxen: "npm:7.1.1" + dotenv: "npm:16.4.5" execa: "npm:8.0.1" prompts: "npm:2.4.2" semver: "npm:7.6.0"