diff --git a/.gitignore b/.gitignore
index 1acd5ea4252b..f942d04cb422 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@ yarn-error.log
**/*.tsbuildinfo
tasks/.verdaccio
tasks/e2e/cypress/fixtures/example.json
-tasks/release-notes/*.md
+tasks/release/*.md
tmp/
blog-test-project/*
.yarn/*
diff --git a/package.json b/package.json
index 321cae72b50a..f9c58fd8698e 100644
--- a/package.json
+++ b/package.json
@@ -22,11 +22,13 @@
"@types/jest": "27.4.0",
"@types/jscodeshift": "0.11.2",
"@types/lodash.template": "4.5.0",
+ "@types/prompts": "2.4.0",
"all-contributors-cli": "6.20.0",
"ansi-colors": "4.1.1",
"babel-jest": "27.4.6",
"babel-plugin-auto-import": "1.1.0",
"babel-plugin-remove-code": "0.0.6",
+ "boxen": "5.1.2",
"core-js": "3.20.3",
"cypress": "9.3.1",
"cypress-wait-until": "1.7.2",
@@ -36,14 +38,17 @@
"jscodeshift": "0.13.0",
"lerna": "4.0.0",
"lodash.template": "4.5.0",
+ "msw": "0.36.7",
"nodemon": "2.0.15",
"npm-packlist": "3.0.0",
"octokit": "1.7.1",
"ora": "5.4.1",
+ "prompts": "2.4.2",
"rimraf": "3.0.2",
"terminal-link": "2.1.1",
"typescript": "4.5.5",
- "typescript-transform-paths": "3.3.1"
+ "typescript-transform-paths": "3.3.1",
+ "zx": "4.3.0"
},
"resolutions": {
"@types/react": "17.0.38",
@@ -63,13 +68,14 @@
"build:clean": "yarn clean:prisma && rimraf packages/**/dist",
"build:watch": "lerna run build:watch --parallel; tsc --build",
"test": "lerna run test --stream -- --colors --maxWorkers=4",
+ "test-release": "NODE_OPTIONS=--experimental-vm-modules ./node_modules/.bin/jest --config ./tasks/release/jest.config.mjs",
"e2e": "node ./tasks/run-e2e",
"clean:prisma": "rimraf node_modules/.prisma/client && node node_modules/@prisma/client/scripts/postinstall.js",
"lint": "RWJS_CWD=packages/create-redwood-app/template eslint --config .eslintrc.js packages",
"lint:fix": "yarn lint --fix",
"build:link": "node ./tasks/build-and-copy",
"build:test-project": "node ./tasks/test-project/test-project",
- "release-notes": "node ./tasks/release-notes/release-notes.mjs",
+ "release": "node ./tasks/release/cli.mjs",
"publish:canary": "lerna publish --force-publish --canary --include-merged-tags --preid canary --dist-tag canary --yes --loglevel verbose",
"project:deps": "node ./tasks/framework-tools/frameworkDepsToProject.mjs",
"project:copy": "node ./tasks/framework-tools/frameworkFilesToProject.mjs",
diff --git a/tasks/release-notes/README.md b/tasks/release-notes/README.md
deleted file mode 100644
index 19e4b3dfb270..000000000000
--- a/tasks/release-notes/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Release Notes
-
-This script builds release notes for a milestone:
-
-```
-yarn release-notes v0.34.0
-# There should now be a new file at ./tasks/release-notes/v0.34.0-release-notes.md
-```
-
-To run this script, you'll need a personal access token.
-Provision one from https://github.com/settings/tokens and set it to `GITHUB_TOKEN` in your env.
diff --git a/tasks/release-notes/release-notes.md.template b/tasks/release-notes/release-notes.md.template
deleted file mode 100644
index 5d0100a916ef..000000000000
--- a/tasks/release-notes/release-notes.md.template
+++ /dev/null
@@ -1,30 +0,0 @@
-# Changelog
-
-Unique contributors: ${uniqueContributors}
-
-PRs merged: ${prsMerged}
-
-## Features
-
-${features}
-
-## Fixed
-
-${fixed}
-
-## Chore
-
-${chore}
-
-### Package Dependencies
-
-
-View all Dependency Version Upgrades
-
-${packageDependencies}
-
-
-
-## Manual
-
-${manual}
diff --git a/tasks/release/__snapshots__/generateReleaseNotes.test.mjs.snap b/tasks/release/__snapshots__/generateReleaseNotes.test.mjs.snap
new file mode 100644
index 000000000000..cb734e370294
--- /dev/null
+++ b/tasks/release/__snapshots__/generateReleaseNotes.test.mjs.snap
@@ -0,0 +1,35 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`generateReleaseNotes works 1`] = `
+"# Changelog
+
+Unique contributors: 3
+
+PRs merged: 3
+
+## Features
+
+- Added Decimal field type to Scaffold #4169 by @BBurnworth
+
+## Fixed
+
+- Type fix for \`mockGraphQL\` data argument #4164 by @callingmedic911
+
+## Chore
+
+- Add storybook ci option to test that Storybook starts \\"ok\\" #3515 by @virtuoushub
+
+### Package Dependencies
+
+
+View all Dependency Version Upgrades
+
+
+
+## Manual
+
+
+"
+`;
diff --git a/tasks/release/cli.mjs b/tasks/release/cli.mjs
new file mode 100644
index 000000000000..2c235867dbf8
--- /dev/null
+++ b/tasks/release/cli.mjs
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+/* eslint-env node, es2021 */
+
+import yargs from 'yargs'
+import { hideBin } from 'yargs/helpers'
+
+import generateReleaseNotes from './generateReleaseNotes.mjs'
+import release from './release.mjs'
+import updateNextReleasePullRequestsMilestone from './updateNextReleasePullRequestsMilestone.mjs'
+
+yargs(hideBin(process.argv))
+ .scriptName('release')
+ .command('$0', 'Release RedwoodJS', {}, release)
+ .command(
+ 'generate-release-notes [milestone]',
+ 'Generates release notes for a given milestone',
+ (yargs) => {
+ yargs.positional('milestone', {
+ describe: 'The milestone to generate release notes for',
+ type: 'string',
+ })
+ },
+ (argv) => generateReleaseNotes(argv.milestone)
+ )
+ .command(
+ 'update-next-release-prs-milestone ',
+ "Update next-release PRs' milestone. Note that this creates the milestone if it doesn't exist",
+ (yargs) => {
+ yargs.positional('milestone', {
+ describe: 'The milestone to update next-release PRs to',
+ type: 'string',
+ })
+ },
+ (argv) => updateNextReleasePullRequestsMilestone(argv.milestone)
+ )
+ .help()
+ .parse()
diff --git a/tasks/release-notes/release-notes.mjs b/tasks/release/generateReleaseNotes.mjs
similarity index 56%
rename from tasks/release-notes/release-notes.mjs
rename to tasks/release/generateReleaseNotes.mjs
index 8d54478c028a..efdbbb66ae77 100755
--- a/tasks/release-notes/release-notes.mjs
+++ b/tasks/release/generateReleaseNotes.mjs
@@ -1,55 +1,78 @@
-#!/usr/bin/env node
-/* eslint-env node, es6*/
+/* eslint-env node, es2021 */
import template from 'lodash.template'
import fs from 'node:fs'
import url from 'node:url'
-import { Octokit } from 'octokit'
-import yargs from 'yargs'
-import { hideBin } from 'yargs/helpers'
+
+import octokit from './octokit.mjs'
/**
- * If the user didn't provide a GitHub token, exit early.
+ * Generates release notes for a milestone.
+ *
+ * @remarks
+ *
+ * If no milestone's given, just fetch the latest version milestone (e.g. `v0.42.0`).
+ *
+ * @param {string} [milestone]
*/
-if (!process.env.GITHUB_TOKEN) {
- console.log()
- console.error(
- ` You have to provide a GitHub personal-access token (PAT) by setting it to an env var named "GITHUB_TOKEN"`
- )
- console.error(
- ` You can provision a PAT here: https://github.com/settings/tokens`
- )
- console.log()
+export default async function generateReleaseNotes(milestone) {
+ // Get the milestone's title, id, and PRs.
+ const { title, id } = await getMilestoneId(milestone)
+ const prs = await getPRsWithMilestone({ milestoneId: id })
+
+ const filename = new URL(`${title}ReleaseNotes.md`, import.meta.url)
+ const filedata = interpolate({
+ uniqueContributors: getNoOfUniqueContributors(prs),
+ prsMerged: prs.filter((pr) => pr.author.login !== 'renovate').length,
+ ...sortPRs(prs),
+ })
+ fs.writeFileSync(filename, filedata)
- /**
- * There's a few ways we could exit,
- * but `process.exit` actually isn't recommended: {@link https://nodejs.dev/learn/how-to-exit-from-a-nodejs-program}.
- */
- process.kill(process.pid)
+ console.log(`Written to ${url.fileURLToPath(filename)}`)
+ console.log('Done')
}
-const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
+// Helpers
/**
- * Yargs bindings.
+ * @typedef {{
+ * repository: {
+ * milestones: {
+ * nodes: Array<{ title: string, id: string }>
+ * }
+ * }
+ * }} GetMilestoneIdsRes
*
- * @param {typeof yargs} yargs
+ * @param {string} [title]
*/
-function builder(yargs) {
- yargs
- .positional('milestone', {
- describe: 'The milestone to generate release notes for',
- type: 'string',
- })
- .example('$0 v0.40.0', 'Build release notes for v0.40.0')
+async function getMilestoneId(title) {
+ const {
+ repository: {
+ milestones: { nodes: milestones },
+ },
+ } = /** @type GetMilestoneIdsRes */ (
+ await octokit.graphql(GET_MILESTONE_IDS, { title })
+ )
+
+ if (!title) {
+ const [latestMilestone] = milestones
+ console.log(
+ `No milestone was provided; using the latest: ${latestMilestone.title}`
+ )
+ return latestMilestone
+ }
+
+ let milestone = milestones.find((milestone) => milestone.title === title)
+
+ return milestone
}
-const GET_MILESTONE_IDS = `
+export const GET_MILESTONE_IDS = `
query GetMilestoneIds($title: String) {
repository(owner: "redwoodjs", name: "redwood") {
milestones(
query: $title
- first: 100
+ first: 3
orderBy: { field: NUMBER, direction: DESC }
) {
nodes {
@@ -62,29 +85,58 @@ const GET_MILESTONE_IDS = `
`
/**
- * @param {string} title
+ * @typedef {{
+ * number: number
+ * title: string
+ * author: {
+ * login: string
+ * };
+ * labels: {
+ * nodes: Array<{
+ * name: string
+ * }>
+ * }
+ * }} PR
+ *
+ * @typedef {{
+ * node: {
+ * pullRequests: {
+ * pageInfo: {
+ * hasNextPage: boolean
+ * endCursor: string
+ * }
+ * nodes: Array
+ * totalCount: number
+ * }
+ * }
+ * }} GetPRsWithMilestoneRes
+ *
+ * @param {{ milestoneId: string, after?: string }}
+ * @returns {Promise>}
*/
-async function getMilestoneId(title) {
+async function getPRsWithMilestone({ milestoneId, after }) {
const {
- repository: { milestones },
- } = await octokit.graphql(GET_MILESTONE_IDS, { title })
-
- let milestone = milestones.nodes.find(
- (milestone) => milestone.title === title
+ node: { pullRequests },
+ } = /** @type GetPRsWithMilestoneRes */ (
+ await octokit.graphql(GET_PRS_WITH_MILESTONE, {
+ milestoneId,
+ after,
+ })
)
- if (!milestone) {
- const [latestMilestone] = milestones.nodes
- console.log(
- `No milestone was provided; using the latest: ${latestMilestone.title}`
- )
- milestone = latestMilestone
+ if (!pullRequests.pageInfo.hasNextPage) {
+ return pullRequests.nodes
}
- return milestone
+ const prs = await getPRsWithMilestone({
+ milestoneId,
+ after: pullRequests.pageInfo.endCursor,
+ })
+
+ return [...pullRequests.nodes, ...prs]
}
-const GET_PRS_WITH_MILESTONE = `
+export const GET_PRS_WITH_MILESTONE = `
query GetPRsWithMilestone($milestoneId: ID!, $after: String) {
node(id: $milestoneId) {
... on Milestone {
@@ -99,7 +151,7 @@ const GET_PRS_WITH_MILESTONE = `
author {
login
}
- labels(first: 100) {
+ labels(first: 10) {
nodes {
name
}
@@ -113,104 +165,10 @@ const GET_PRS_WITH_MILESTONE = `
`
/**
- * @param {{ milestoneId: string, after?: string }}
- */
-async function getPRsWithMilestone({ milestoneId, after }) {
- const {
- node: { pullRequests },
- } = await octokit.graphql(GET_PRS_WITH_MILESTONE, {
- milestoneId,
- after,
- })
-
- if (!pullRequests.pageInfo.hasNextPage) {
- return pullRequests.nodes
- }
-
- const prs = await getPRsWithMilestone({
- milestoneId,
- after: pullRequests.pageInfo.endCursor,
- })
-
- return [...pullRequests.nodes, ...prs]
-}
-
-/**
- * This function does pretty much all the work.
- *
- * @param {{
- * milestone: string,
- * }} argv
- */
-async function handler(argv) {
- /**
- * Get the milestone's title, id, and PRs.
- */
- const { title, id } = await getMilestoneId(argv.milestone)
- const prs = await getPRsWithMilestone({ milestoneId: id })
-
- const filename = new URL(`${title}-release-notes.md`, import.meta.url)
- const filedata = interpolate({
- uniqueContributors: getNoOfUniqueContributors(prs),
- prsMerged: prs.filter((pr) => pr.author.login !== 'renovate').length,
- ...sortPRs(prs),
- })
- fs.writeFileSync(filename, filedata)
-
- console.log(`Written to ${url.fileURLToPath(filename)}`)
- console.log('Done')
-}
-
-yargs(hideBin(process.argv))
- .scriptName('release-notes')
- .usage(
- '$0 [milestone]',
- 'Build release notes for a milestone',
- builder,
- handler
- )
- .help()
- .parse()
-
-/**
- * Helper functions.
- */
-
-/**
- * Interpolate the template and write to `${cwd}/${milestone}-release-notes.md`.
- *
- * @see {@link https://nodejs.org/docs/latest-v15.x/api/esm.html#esm_no_filename_or_dirname}
- */
-const interpolate = template(
- fs.readFileSync(new URL('release-notes.md.template', import.meta.url), 'utf8')
-)
-
-/**
- * A helper function for formatting PRs.
- * A `pr` looks like:
+ * Get the number of unique contributors, excluding renovate bot.
*
- * ```js
- * {
- * "number": 2613,
- * "title": "Scaffold Generator File Organization",
- * "author": {
- * "login": "cjreimer",
- * },
- * }
- * ```
- *
- * @param {{
- * number: number,
- * title: string,
- * author: {
- * login: string,
- * }
- * }} pr
+ * @param {Array} prs
*/
-function formatPR(pr) {
- return `${pr.title} #${pr.number} by @${pr.author.login}`
-}
-
function getNoOfUniqueContributors(prs) {
const logins = prs
.map((pr) => pr.author.login)
@@ -220,12 +178,11 @@ function getNoOfUniqueContributors(prs) {
}
/**
- * @param {Array<{
- * number: number,
- * title: string,
- * author: { login: string }
- * labels: { nodes: Array<{ name: string }> }
- * }>} prs
+ * @remarks
+ *
+ * This could be a little better.
+ *
+ * @param {Array} prs
*/
function sortPRs(prs) {
const features = []
@@ -277,3 +234,50 @@ function sortPRs(prs) {
manual: manual.join('\n'),
}
}
+
+/**
+ * @param {Array} pr
+ */
+function formatPR(pr) {
+ return `${pr.title} #${pr.number} by @${pr.author.login}`
+}
+
+/**
+ * Interpolate the template and write to `${cwd}/${milestone}-releaseNotes.md`.
+ *
+ * @see {@link https://nodejs.org/docs/latest-v15.x/api/esm.html#esm_no_filename_or_dirname}
+ */
+const interpolate = template(
+ [
+ '# Changelog',
+ '',
+ 'Unique contributors: ${uniqueContributors}',
+ '',
+ 'PRs merged: ${prsMerged}',
+ '',
+ '## Features',
+ '',
+ '${features}',
+ '',
+ '## Fixed',
+ '',
+ '${fixed}',
+ '',
+ '## Chore',
+ '',
+ '${chore}',
+ '',
+ '### Package Dependencies',
+ '',
+ '',
+ 'View all Dependency Version Upgrades
',
+ '',
+ '${packageDependencies}',
+ '
',
+ ' ',
+ '',
+ '## Manual',
+ '',
+ '${manual}',
+ ].join('\n')
+)
diff --git a/tasks/release/generateReleaseNotes.test.mjs b/tasks/release/generateReleaseNotes.test.mjs
new file mode 100644
index 000000000000..f2f024b6d712
--- /dev/null
+++ b/tasks/release/generateReleaseNotes.test.mjs
@@ -0,0 +1,189 @@
+import { graphql } from 'msw'
+import { setupServer } from 'msw/node'
+import fs from 'node:fs'
+
+import generateReleaseNotes, {
+ GET_MILESTONE_IDS,
+ GET_PRS_WITH_MILESTONE,
+} from './generateReleaseNotes.mjs'
+
+const handleGetMilestoneIds = graphql.query(
+ 'GetMilestoneIds',
+ (req, res, ctx) => {
+ const { title } = req.variables
+
+ const payload = {
+ repository: {
+ milestones: {
+ nodes: [
+ title
+ ? {
+ title,
+ id: `123-${title}`,
+ }
+ : {
+ title: 'v0.42.1',
+ id: 'MI_kwDOC2M2f84Ac_Ij',
+ },
+ ],
+ },
+ },
+ }
+
+ return res(ctx.data(payload))
+ }
+)
+
+const handleGetPRsWithMilestone = graphql.query(
+ 'GetPRsWithMilestone',
+ (_req, res, ctx) => {
+ const payload = {
+ node: {
+ pullRequests: {
+ pageInfo: {
+ hasNextPage: false,
+ endCursor: 'Y3Vyc29yOnYyOpLAzjEcbpg=',
+ },
+ nodes: [
+ {
+ number: 3515,
+ title:
+ 'Add storybook ci option to test that Storybook starts "ok"',
+ author: {
+ login: 'virtuoushub',
+ },
+ labels: {
+ nodes: [
+ {
+ name: 'topic/storybook',
+ },
+ {
+ name: 'v1/priority',
+ },
+ {
+ name: 'release:chore',
+ },
+ ],
+ },
+ },
+ {
+ number: 4164,
+ title: 'Type fix for `mockGraphQL` data argument',
+ author: {
+ login: 'callingmedic911',
+ },
+ labels: {
+ nodes: [
+ {
+ name: 'topic/testing',
+ },
+ {
+ name: 'v1/priority',
+ },
+ {
+ name: 'release:fix',
+ },
+ ],
+ },
+ },
+ {
+ number: 4169,
+ title: 'Added Decimal field type to Scaffold',
+ author: {
+ login: 'BBurnworth',
+ },
+ labels: {
+ nodes: [
+ {
+ name: 'release:feature',
+ },
+ ],
+ },
+ },
+ ],
+ totalCount: 46,
+ },
+ },
+ }
+
+ return res(ctx.data(payload))
+ }
+)
+
+const server = setupServer(handleGetMilestoneIds, handleGetPRsWithMilestone)
+
+beforeAll(() => server.listen())
+
+afterAll(() => {
+ server.close()
+
+ if (
+ fs.existsSync(new URL('./next-releaseReleaseNotes.md', import.meta.url))
+ ) {
+ fs.rmSync(new URL('./next-releaseReleaseNotes.md', import.meta.url))
+ }
+})
+
+describe('generateReleaseNotes', () => {
+ it('uses the right queries', () => {
+ expect(GET_MILESTONE_IDS).toMatchInlineSnapshot(`
+ "
+ query GetMilestoneIds($title: String) {
+ repository(owner: \\"redwoodjs\\", name: \\"redwood\\") {
+ milestones(
+ query: $title
+ first: 3
+ orderBy: { field: NUMBER, direction: DESC }
+ ) {
+ nodes {
+ title
+ id
+ }
+ }
+ }
+ }
+ "
+ `)
+
+ expect(GET_PRS_WITH_MILESTONE).toMatchInlineSnapshot(`
+ "
+ query GetPRsWithMilestone($milestoneId: ID!, $after: String) {
+ node(id: $milestoneId) {
+ ... on Milestone {
+ pullRequests(first: 100, after: $after) {
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ nodes {
+ number
+ title
+ author {
+ login
+ }
+ labels(first: 10) {
+ nodes {
+ name
+ }
+ }
+ }
+ totalCount
+ }
+ }
+ }
+ }
+ "
+ `)
+ })
+
+ it('MSW unit test', async () => {
+ await generateReleaseNotes('next-release')
+
+ expect(
+ fs.readFileSync(
+ new URL('./next-releaseReleaseNotes.md', import.meta.url),
+ 'utf8'
+ )
+ ).toMatchSnapshot()
+ })
+})
diff --git a/tasks/release/jest.config.mjs b/tasks/release/jest.config.mjs
new file mode 100644
index 000000000000..358dc958c501
--- /dev/null
+++ b/tasks/release/jest.config.mjs
@@ -0,0 +1,8 @@
+export default {
+ verbose: true,
+ setupFiles: ['/jest.setup.mjs'],
+ // ESM-specific settings:
+ transform: {},
+ moduleFileExtensions: ['js', 'mjs'],
+ testMatch: ['/*.test.mjs'],
+}
diff --git a/tasks/release/jest.setup.mjs b/tasks/release/jest.setup.mjs
new file mode 100644
index 000000000000..72231ee8e9d7
--- /dev/null
+++ b/tasks/release/jest.setup.mjs
@@ -0,0 +1,3 @@
+/* eslint-env node, es2021 */
+
+process.env.GITHUB_TOKEN = 'github-token'
diff --git a/tasks/release/octokit.mjs b/tasks/release/octokit.mjs
new file mode 100644
index 000000000000..0bedbb6e23be
--- /dev/null
+++ b/tasks/release/octokit.mjs
@@ -0,0 +1,22 @@
+/* eslint-env node, es2021 */
+
+import { Octokit } from 'octokit'
+
+/**
+ * Exit with a helpful error message if the user didn't provide a GitHub token.
+ */
+if (!process.env.GITHUB_TOKEN) {
+ console.log()
+ console.error(
+ ` You have to provide a GitHub personal-access token (PAT) by setting it to an env var named "GITHUB_TOKEN"`
+ )
+ console.error(
+ ` You can provision a PAT here: https://github.com/settings/tokens`
+ )
+ console.log()
+ process.exit(1)
+}
+
+const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN })
+
+export default octokit
diff --git a/tasks/release/release.mjs b/tasks/release/release.mjs
new file mode 100644
index 000000000000..c1a314b0d401
--- /dev/null
+++ b/tasks/release/release.mjs
@@ -0,0 +1,342 @@
+/* eslint-env node, es2021 */
+/**
+ * Notes
+ * - branch already exists? start on the "update package versions" step
+ * - consider using xstate
+ */
+import c from 'ansi-colors'
+import boxen from 'boxen'
+import prompts from 'prompts'
+import { $ } from 'zx'
+
+import generateReleaseNotes from './generateReleaseNotes.mjs'
+import updateNextReleasePullRequestsMilestone, {
+ closeMilestone,
+} from './updateNextReleasePullRequestsMilestone.mjs'
+
+export const ASK = c.bgBlue(c.black(' ASK '))
+export const CHECK = c.bgYellow(c.black(' CHECK '))
+export const FIX = c.bgRed(c.black(' FIX '))
+
+export default async function release() {
+ const { semver } = await exitOnCancelPrompts({
+ type: 'select',
+ name: 'semver',
+ message: `${ASK} Which semver are you releasing?`,
+ choices: [{ value: 'major' }, { value: 'minor' }, { value: 'patch' }],
+ initial: 2,
+ })
+
+ // Get the most-recent tag and get the next version from it.
+ // `git describe --abbrev=0` should output something like like `v0.42.1`.
+ const gitDescribePO = await $`git describe --abbrev=0`
+ const previousVersion = gitDescribePO.stdout.trim()
+ let nextVersion = getNextVersion(semver, previousVersion)
+
+ // Confirm that we got the next version right; give the user a chance to correct it if we didn't.
+ const nextVersionConfirmed = await confirm(
+ `${CHECK} The next release is ${c.green(nextVersion)}`
+ )
+ if (!nextVersionConfirmed) {
+ const answer = await exitOnCancelPrompts({
+ type: 'text',
+ name: 'nextVersion',
+ message: `${ASK} enter the next version`,
+ })
+ nextVersion = answer.nextVersion
+ }
+
+ // Check that the git tag doesn't already exist.
+ const gitTagPO = await $`git tag -l ${nextVersion}`
+ if (gitTagPO.stdout.trim()) {
+ console.log(
+ c.bold(
+ `${c.red('\u2716')} ${FIX} Git tag ${c.green(
+ nextVersion
+ )} already exists locally. You must resolve this before proceeding`
+ )
+ )
+ return
+ }
+
+ const shouldUpdateNextReleasePRsMilestone = await confirm(
+ `${ASK} Do you want to update next-release PRs' milestone to ${c.green(
+ nextVersion
+ )}?`
+ )
+
+ let milestone
+
+ if (shouldUpdateNextReleasePRsMilestone) {
+ try {
+ milestone = await updateNextReleasePullRequestsMilestone(nextVersion)
+ } catch (e) {
+ console.log(
+ `Couldn't update next-release PRs milestone to ${nextVersion}`
+ )
+ console.log(e)
+ }
+ }
+
+ switch (semver) {
+ case 'major':
+ {
+ console.log(c.yellow('Wait till after v1!'))
+ }
+ break
+ case 'minor':
+ await releaseMinor(nextVersion)
+ break
+ case 'patch':
+ await releasePatch(previousVersion, nextVersion)
+ break
+ }
+
+ console.log(rocketBoxen(`Released ${c.green(nextVersion)}`))
+
+ const shouldGenerateReleaseNotes = await confirm(
+ `${ASK} Do you want to generate release notes?`
+ )
+ if (shouldGenerateReleaseNotes) {
+ try {
+ await generateReleaseNotes(nextVersion)
+ } catch (e) {
+ console.log("Couldn't generate release notes")
+ console.log(e)
+ }
+ }
+
+ if (milestone) {
+ const okToClose = await confirm(
+ `${ASK} Ok to close milestone ${c.green(nextVersion)}?`
+ )
+ if (okToClose) {
+ closeMilestone(milestone.number)
+ }
+ }
+}
+
+// Helpers
+
+/**
+ * Take the output from `git describe --abbrev=0` (which is something like `'v0.42.1'`),
+ * and return an array of numbers ([0, 42, 1]).
+ *
+ * @param {string} version the version string (obtain by running `git describe --abbrev=0`)
+ * @returns [string, string, string]
+ */
+function parseGitTag(version) {
+ if (version.startsWith('v')) {
+ version = version.substring(1)
+ }
+
+ return version.split('.').map(Number)
+}
+
+/**
+ * Bump the version according to the semver we're releasing.
+ *
+ * @typedef {'major' | 'minor' | 'patch'} Semver
+ * @param {Semver} semver
+ * @param {string} previousVersion
+ */
+function getNextVersion(semver, previousVersion) {
+ switch (semver) {
+ case 'major': {
+ const [major] = parseGitTag(previousVersion)
+ return `v${[major + 1, 0, 0].join('.')}`
+ }
+ case 'minor': {
+ const [major, minor] = parseGitTag(previousVersion)
+ return `v${[major, minor + 1, 0].join('.')}`
+ }
+ case 'patch': {
+ const [major, minor, patch] = parseGitTag(previousVersion)
+ return `v${[major, minor, patch + 1].join('.')}`
+ }
+ }
+}
+
+/**
+ * Wrapper around `prompts` to exit on crtl c.
+ *
+ * @template Name
+ * @param {import('prompts').PromptObject} promptsObject
+ * @param {import('prompts').Options} promptsOptions
+ */
+function exitOnCancelPrompts(promptsObject, promptsOptions) {
+ return prompts(promptsObject, {
+ ...promptsOptions,
+ onCancel: () => process.exit(1),
+ })
+}
+
+/**
+ * Wrapper around confirm type `prompts`.
+ *
+ * @param {string} message
+ * @returns {Promise}
+ */
+export async function confirm(message) {
+ const answer = await exitOnCancelPrompts({
+ type: 'confirm',
+ name: 'confirm',
+ message,
+ })
+
+ return answer.confirm
+}
+
+/**
+ * Right now releasing a major is the same as releasing a minor.
+ *
+ * @param {string} nextVersion
+ */
+// function releaseMajor(nextVersion) {
+// return releaseMajorOrMinor('major', nextVersion)
+// }
+
+/**
+ * @param {string} nextVersion
+ */
+function releaseMinor(nextVersion) {
+ return releaseMajorOrMinor('minor', nextVersion)
+}
+
+/**
+ * @param {Semver} semver
+ * @param {string} nextVersion
+ */
+async function releaseMajorOrMinor(semver, nextVersion) {
+ const PO = await $`git branch --show-current`
+ const branch = PO.stdout.trim()
+ if (branch !== 'main') {
+ console.log('Not on main. Checking out main')
+ await $`git checkout main`
+ }
+
+ const releaseBranch = ['release', semver, nextVersion].join('/')
+ const okToCheckout = await confirm(
+ `${ASK} Ok to checkout new branch ${c.green(releaseBranch)}?`
+ )
+ if (!okToCheckout) {
+ return
+ }
+ await $`git checkout -b ${releaseBranch}`
+
+ const okToProceed = await confirm(
+ `${ASK} Checked out new release branch ${c.green(
+ releaseBranch
+ )}.\nIf you want to continue publishing, proceed.\nOtherwise, stop here to publish this branch to GitHub to create an RC`
+ )
+ if (!okToProceed) {
+ return
+ }
+
+ const okToCleanInstallUpdate = await confirm(
+ `${ASK} Ok to clean, install, and update package versions?`
+ )
+ if (!okToCleanInstallUpdate) {
+ return
+ }
+
+ await $`git clean -fxd`
+ await $`yarn install`
+ await $`./tasks/update-package-versions ${nextVersion}`
+ const versionsLookRight = await confirm(
+ `${CHECK} The package versions have been updated. Does everything look right?`
+ )
+ if (!versionsLookRight) {
+ return
+ }
+
+ const commitTagQA = await confirm(
+ `${ASK} Ok to commit, tag, and run through local QA?`
+ )
+ if (!commitTagQA) {
+ return
+ }
+
+ await $`git commit -am "${nextVersion}"`
+ await $`git tag -am ${nextVersion} "${nextVersion}"`
+ // QA
+ await $`yarn build`
+ await $`yarn lint`
+ await $`yarn test`
+
+ const okToRelease = await confirm(
+ `${ASK} Everything passed local QA. Are you ready to push your branch to GitHub and publish to NPM?`
+ )
+ if (!okToRelease) {
+ return
+ }
+ // await $`git push && git push --tags`
+ // await $`yarn lerna publish from-package`
+}
+
+/**
+ * This is a WIP.
+ *
+ * @param {string} nextVersion
+ */
+async function releasePatch(previousVersion, nextVersion) {
+ await $`git checkout tags/${previousVersion} -b release/patch/${nextVersion}`
+
+ await $`git push origin release/patch/${nextVersion}`
+ await `open https://github.com/redwoodjs/redwood/compare/${previousVersion}..release/patch/${nextVersion}`
+
+ const diffLooksGood = await confirm('Does the diff look good?')
+
+ if (!diffLooksGood) {
+ console.log('Resetting...')
+ }
+
+ const proceed = await confirm('Cherry pick and handle the conflicts')
+
+ if (!proceed) {
+ console.log('Resetting...')
+ }
+
+ await $`git push origin release/patch/${nextVersion}`
+ await `open https://github.com/redwoodjs/redwood/compare/${previousVersion}..release/patch/${nextVersion}`
+
+ const diffLooksGood2 = await confirm('Does the diff look good?')
+
+ if (!diffLooksGood2) {
+ console.log('Resetting...')
+ }
+
+ // publish rc
+
+ await $`git clean -fxd`
+ await $`yarn install`
+
+ await $`./tasks/update-package-versions ${nextVersion}`
+
+ await $`yarn build`
+
+ await $`git commit -am "${nextVersion}"`
+ await $`git tag -am ${nextVersion} "${nextVersion}"`
+
+ await $`git checkout main`
+ // merge commit
+ await $`git branch -d release/patch/${nextVersion}`
+}
+
+/**
+ * @param {string} message
+ */
+function rocketBoxen(message) {
+ boxen(message, {
+ padding: 1,
+ margin: 1,
+ borderStyle: {
+ bottomLeft: '🚀',
+ bottomRight: '🚀',
+ horizontal: '—',
+ topLeft: '🚀',
+ topRight: '🚀',
+ vertical: '🚀',
+ },
+ })
+}
diff --git a/tasks/release/updateNextReleasePullRequestsMilestone.mjs b/tasks/release/updateNextReleasePullRequestsMilestone.mjs
new file mode 100644
index 000000000000..5687d983e58a
--- /dev/null
+++ b/tasks/release/updateNextReleasePullRequestsMilestone.mjs
@@ -0,0 +1,276 @@
+/* eslint-env node, es2021 */
+/**
+ * - is:pr is:merged no:milestone -> should empty
+ * - milestone:next-release-patch -> check empty
+ * - close milestone _after_ publish
+ */
+import c from 'ansi-colors'
+
+import octokit from './octokit.mjs'
+import { confirm, ASK, CHECK } from './release.mjs'
+
+/**
+ * @param {string} title
+ */
+export default async function updateNextReleasePullRequestsMilestone(title) {
+ let milestone = await getMilestone(title)
+
+ if (!milestone) {
+ const okToCreate = await confirm(
+ `${ASK} Milestone ${c.green(title)} doesn't exist. Ok to create it?`
+ )
+
+ if (!okToCreate) {
+ return
+ }
+
+ const {
+ data: { node_id: id, number },
+ } = await createMilestone(title)
+
+ milestone = { title, id, number }
+
+ console.log(`Created milestone ${c.green(title)}`)
+ }
+
+ const nextReleaseMilestoneId = await getNextReleaseMilestoneId()
+
+ const pullRequestIds = await getPullRequestIdsWithMilestone(
+ nextReleaseMilestoneId
+ )
+
+ const okToUpdate = await confirm(
+ `${ASK} Ok to update the milestone of ${
+ pullRequestIds.length
+ } PRs from ${c.green('next-release')} to ${c.green(title)}?`
+ )
+
+ if (!okToUpdate) {
+ return
+ }
+
+ await Promise.all(
+ pullRequestIds.map((pullRequestId) =>
+ updatePullRequestMilestone(pullRequestId, milestone.id)
+ )
+ )
+
+ const looksRight = await confirm(
+ `${CHECK} Updated the milestone of ${pullRequestIds.length} PRs:
+ https://github.com/redwoodjs/redwood/pulls?q=is%3Apr+is%3Amerged+milestone%3A${title}\nDoes everything look right?`
+ )
+
+ if (!looksRight) {
+ const undoPRs = await confirm(
+ `${ASK} Do you want to undo the changes to the PRs?`
+ )
+
+ if (undoPRs) {
+ await Promise.all(
+ pullRequestIds.map((pullRequestId) =>
+ updatePullRequestMilestone(pullRequestId, nextReleaseMilestoneId)
+ )
+ )
+ }
+
+ const undoMilestone = await confirm(
+ `${ASK} Do you want to delete the milestone`
+ )
+
+ if (undoMilestone) {
+ await deleteMilestone(milestone.number)
+ }
+
+ return
+ }
+
+ return milestone
+}
+
+// Helpers
+
+/**
+ * @typedef {{
+ * repository: {
+ * milestones: {
+ * nodes: Array<{ title: string, id: string, number: number }>
+ * }
+ * }
+ * }} GetMilestonesRes
+ *
+ * @param {string} [title]
+ */
+async function getMilestone(title) {
+ const {
+ repository: {
+ milestones: { nodes: milestones },
+ },
+ } = /** @type GetMilestonesRes */ (
+ await octokit.graphql(GET_MILESTONES, { title })
+ )
+
+ let milestone = milestones.find((milestone) => milestone.title === title)
+
+ return milestone
+}
+
+export const GET_MILESTONES = `
+ query GetMilestoneIds($title: String) {
+ repository(owner: "redwoodjs", name: "redwood") {
+ milestones(
+ query: $title
+ first: 3
+ orderBy: { field: NUMBER, direction: DESC }
+ ) {
+ nodes {
+ title
+ id
+ number
+ }
+ }
+ }
+ }
+`
+
+/**
+ * @param {string} title
+ * @returns {Promise<{ data: { node_id: string, number: number } }>}
+ */
+function createMilestone(title) {
+ // GitHub doesn't have a GraphQL API for creating milestones, so REST it is.
+ return octokit.request('POST /repos/{owner}/{repo}/milestones', {
+ owner: 'redwoodjs',
+ repo: 'redwood',
+ title,
+ })
+}
+
+/**
+ * @typedef {{
+ * repository: {
+ * milestones: {
+ * nodes: Array<{ title: string, id: string }>
+ * }
+ * }
+ * }} GetNextReleaseMilestoneIdRes
+ */
+async function getNextReleaseMilestoneId() {
+ const {
+ repository: {
+ milestones: { nodes: milestones },
+ },
+ } = /** @type {GetNextReleaseMilestoneIdRes} */ (
+ await octokit.graphql(GET_NEXT_RELEASE_MILESTONE_ID)
+ )
+
+ const { id } = milestones.find(
+ (milestone) => milestone.title === 'next-release'
+ )
+
+ return id
+}
+
+export const GET_NEXT_RELEASE_MILESTONE_ID = `
+ query GetNextReleaseMilestoneId {
+ repository(owner: "redwoodjs", name: "redwood") {
+ milestones(query: "next-release", first: 5) {
+ nodes {
+ title
+ id
+ }
+ }
+ }
+ }
+`
+
+/**
+ * @typedef {{
+ * node: {
+ * pullRequests: {
+ * nodes: Array<{ id: string }>
+ * }
+ * }
+ * }} GetNextReleasePullRequestIdsRes
+ *
+ * @param {string} milestoneId
+ */
+export async function getPullRequestIdsWithMilestone(milestoneId) {
+ /**
+ * Right now we're not handling the case that we merge more than 100 PRs.
+ */
+ const {
+ node: {
+ pullRequests: { nodes: pullRequests },
+ },
+ } = /** @type {GetNextReleasePullRequestIdsRes} */ (
+ await octokit.graphql(GET_NEXT_RELEASE_PULL_REQUEST_IDS, {
+ milestoneId,
+ })
+ )
+
+ return pullRequests.map((pullRequest) => pullRequest.id)
+}
+
+export const GET_NEXT_RELEASE_PULL_REQUEST_IDS = `
+ query GetNextReleasePullRequestIds($milestoneId: ID!) {
+ node(id: $milestoneId) {
+ ... on Milestone {
+ pullRequests(first: 100) {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ }
+`
+
+/**
+ * @param {string} pullRequestId
+ * @param {string} milestoneId
+ */
+function updatePullRequestMilestone(pullRequestId, milestoneId) {
+ return octokit.graphql(UPDATE_NEXT_RELEASE_PULL_REQUEST_MILESTONE, {
+ pullRequestId,
+ milestoneId,
+ })
+}
+
+export const UPDATE_NEXT_RELEASE_PULL_REQUEST_MILESTONE = `
+ mutation UpdatePullRequestMilestone($pullRequestId: ID!, $milestoneId: ID!) {
+ updatePullRequest(
+ input: { pullRequestId: $pullRequestId, milestoneId: $milestoneId }
+ ) {
+ clientMutationId
+ }
+ }
+`
+
+/**
+ * @param {number} milestone_number
+ */
+export function closeMilestone(milestone_number) {
+ return octokit.request(
+ 'POST /repos/{owner}/{repo}/milestones/{milestone_number}',
+ {
+ owner: 'redwoodjs',
+ repo: 'redwood',
+ milestone_number,
+ state: 'closed',
+ }
+ )
+}
+
+/**
+ * @param {number} milestone_number
+ */
+function deleteMilestone(milestone_number) {
+ return octokit.request(
+ 'DELETE /repos/{owner}/{repo}/milestones/{milestone_number}',
+ {
+ owner: 'redwoodjs',
+ repo: 'redwood',
+ milestone_number,
+ }
+ )
+}
diff --git a/tasks/release/updateNextReleasePullRequestsMilestone.test.mjs b/tasks/release/updateNextReleasePullRequestsMilestone.test.mjs
new file mode 100644
index 000000000000..b80e1621c612
--- /dev/null
+++ b/tasks/release/updateNextReleasePullRequestsMilestone.test.mjs
@@ -0,0 +1,237 @@
+/* eslint-env jest, es2021 */
+import { rest, graphql } from 'msw'
+import { setupServer } from 'msw/node'
+
+import updateNextReleasePullRequestsMilestone, {
+ GET_MILESTONES,
+ GET_NEXT_RELEASE_MILESTONE_ID,
+ GET_NEXT_RELEASE_PULL_REQUEST_IDS,
+ UPDATE_NEXT_RELEASE_PULL_REQUEST_MILESTONE,
+} from './updateNextReleasePullRequestsMilestone.mjs'
+
+/**
+ * MSW setup
+ */
+let nextVersionMilestone
+
+const handleCreateMilestone = rest.post(
+ 'https://api.github.com/repos/:owner/:repo/milestones',
+ (req, res, ctx) => {
+ const { title } = req.body
+
+ if (!title) {
+ return res(ctx.status(422))
+ }
+
+ const { owner, repo } = req.params
+
+ if (owner !== 'redwoodjs' || repo !== 'redwood') {
+ return res(ctx.status(404))
+ }
+
+ nextVersionMilestone = {
+ node_id: `123-${title}`,
+ number: 1,
+ }
+
+ return res(ctx.json(nextVersionMilestone))
+ }
+)
+
+const nextReleaseMilestone = {
+ title: 'next-release',
+ id: 'MI_kwDOC2M2f84Aa82f',
+}
+
+const handleGetMilestoneIds = graphql.query(
+ 'GetMilestoneIds',
+ (_req, res, ctx) => {
+ return res(
+ ctx.data({
+ repository: {
+ milestones: {
+ nodes: [
+ {
+ title: 'next-release-patch',
+ id: 'MDk6TWlsZXN0b25lNjc1Nzk0MQ==',
+ },
+ {
+ title: 'next-release-priority',
+ id: 'MDk6TWlsZXN0b25lNjc3MTI3MQ==',
+ },
+ nextReleaseMilestone,
+ ],
+ },
+ },
+ })
+ )
+ }
+)
+
+const pullRequests = [
+ {
+ id: 'PR_kwDOC2M2f84svWtA',
+ },
+ {
+ id: 'PR_kwDOC2M2f84xGIzx',
+ },
+ {
+ id: 'PR_kwDOC2M2f84xHG6Y',
+ },
+]
+
+const handleGetNextReleasePullRequestIds = graphql.query(
+ 'GetNextReleasePullRequestIds',
+ (req, res, ctx) => {
+ const { milestoneId } = req.variables
+
+ if (milestoneId !== nextReleaseMilestone.id) {
+ return res(
+ ctx.data({
+ node: null,
+ })
+ )
+ }
+
+ return res(
+ ctx.data({
+ node: {
+ pullRequests: {
+ nodes: pullRequests,
+ },
+ },
+ })
+ )
+ }
+)
+
+const handleUpdatePullRequestMilestone = graphql.mutation(
+ 'UpdatePullRequestMilestone',
+ (req, res, ctx) => {
+ const { pullRequestId, milestoneId } = req.variables
+
+ if (
+ milestoneId !== nextVersionMilestone.node_id ||
+ !pullRequests.map((pullRequest) => pullRequest.id).includes(pullRequestId)
+ ) {
+ return res(
+ ctx.data({
+ updatePullRequest: null,
+ })
+ )
+ }
+
+ return res(
+ ctx.data({
+ updatePullRequest: {
+ clientMutationId: '123',
+ },
+ })
+ )
+ }
+)
+
+const handleCloseMilestone = rest.post(
+ 'https://api.github.com/repos/:owner/:repo/milestones/:milestone_number',
+ (req, res, ctx) => {
+ const { milestone_number } = req.params
+
+ if (+milestone_number !== nextVersionMilestone.number) {
+ return res(ctx.status(404))
+ }
+
+ const { state } = req.body
+
+ return res(
+ ctx.json({
+ state,
+ })
+ )
+ }
+)
+
+const server = setupServer(
+ handleCreateMilestone,
+ handleGetMilestoneIds,
+ handleGetNextReleasePullRequestIds,
+ handleUpdatePullRequestMilestone,
+ handleCloseMilestone
+)
+
+beforeAll(() => server.listen())
+
+afterAll(() => server.close())
+
+describe('updateNextReleasePullRequestsMilestone', () => {
+ it('uses the right queries', () => {
+ expect(GET_MILESTONES).toMatchInlineSnapshot(`
+ "
+ query GetMilestoneIds($title: String) {
+ repository(owner: \\"redwoodjs\\", name: \\"redwood\\") {
+ milestones(
+ query: $title
+ first: 3
+ orderBy: { field: NUMBER, direction: DESC }
+ ) {
+ nodes {
+ title
+ id
+ number
+ }
+ }
+ }
+ }
+ "
+ `)
+
+ expect(GET_NEXT_RELEASE_MILESTONE_ID).toMatchInlineSnapshot(`
+ "
+ query GetNextReleaseMilestoneId {
+ repository(owner: \\"redwoodjs\\", name: \\"redwood\\") {
+ milestones(query: \\"next-release\\", first: 5) {
+ nodes {
+ title
+ id
+ }
+ }
+ }
+ }
+ "
+ `)
+
+ expect(GET_NEXT_RELEASE_PULL_REQUEST_IDS).toMatchInlineSnapshot(`
+ "
+ query GetNextReleasePullRequestIds($milestoneId: ID!) {
+ node(id: $milestoneId) {
+ ... on Milestone {
+ pullRequests(first: 100) {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ }
+ "
+ `)
+
+ expect(UPDATE_NEXT_RELEASE_PULL_REQUEST_MILESTONE).toMatchInlineSnapshot(`
+ "
+ mutation UpdatePullRequestMilestone($pullRequestId: ID!, $milestoneId: ID!) {
+ updatePullRequest(
+ input: { pullRequestId: $pullRequestId, milestoneId: $milestoneId }
+ ) {
+ clientMutationId
+ }
+ }
+ "
+ `)
+ })
+
+ /**
+ * This needs a few tweaks.
+ */
+ it.skip('MSW unit test', async () => {
+ await updateNextReleasePullRequestsMilestone('v0.42.1')
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index ac8f5363a9ee..b7783868d630 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7335,7 +7335,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/fs-extra@npm:9.0.13":
+"@types/fs-extra@npm:9.0.13, @types/fs-extra@npm:^9.0.12":
version: 9.0.13
resolution: "@types/fs-extra@npm:9.0.13"
dependencies:
@@ -7662,7 +7662,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/minimist@npm:^1.2.0":
+"@types/minimist@npm:^1.2.0, @types/minimist@npm:^1.2.2":
version: 1.2.2
resolution: "@types/minimist@npm:1.2.2"
checksum: f220f57f682bbc3793dab4518f8e2180faa79d8e2589c79614fd777d7182be203ba399020c3a056a115064f5d57a065004a32b522b2737246407621681b24137
@@ -7683,7 +7683,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/node-fetch@npm:2.5.12, @types/node-fetch@npm:^2.5.7":
+"@types/node-fetch@npm:2.5.12, @types/node-fetch@npm:^2.5.12, @types/node-fetch@npm:^2.5.7":
version: 2.5.12
resolution: "@types/node-fetch@npm:2.5.12"
dependencies:
@@ -7693,7 +7693,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/node@npm:*, @types/node@npm:16.11.21, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0":
+"@types/node@npm:*, @types/node@npm:16.11.21, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0, @types/node@npm:^16.6":
version: 16.11.21
resolution: "@types/node@npm:16.11.21"
checksum: e5af56bf8fdfd24a15a427f407f9b0a225fec2e8b91e1fdc365fdd2ab8ca19b9cb4c9e0f37494f97c5dce9983c9589b8e746ecc9592066523ca05949dd9b1fbd
@@ -7779,6 +7779,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/prompts@npm:2.4.0":
+ version: 2.4.0
+ resolution: "@types/prompts@npm:2.4.0"
+ checksum: 24bb6d7021c5f50ac1265a2d0634fff1ccd2b4c40b6a8b55728ce5d9c17d132ea8f57b848d6601002d235d5d29cf100ae902ed60ef4a06091cf4b640b9e411cb
+ languageName: node
+ linkType: hard
+
"@types/prop-types@npm:*":
version: 15.7.4
resolution: "@types/prop-types@npm:15.7.4"
@@ -13474,7 +13481,7 @@ __metadata:
languageName: node
linkType: hard
-"duplexer@npm:^0.1.1, duplexer@npm:^0.1.2":
+"duplexer@npm:^0.1.1, duplexer@npm:^0.1.2, duplexer@npm:~0.1.1":
version: 0.1.2
resolution: "duplexer@npm:0.1.2"
checksum: c57bcd4bdf7e623abab2df43a7b5b23d18152154529d166c1e0da6bee341d84c432d157d7e97b32fecb1bf3a8b8857dd85ed81a915789f550637ed25b8e64fc2
@@ -14559,6 +14566,21 @@ __metadata:
languageName: node
linkType: hard
+"event-stream@npm:=3.3.4":
+ version: 3.3.4
+ resolution: "event-stream@npm:3.3.4"
+ dependencies:
+ duplexer: ~0.1.1
+ from: ~0
+ map-stream: ~0.1.0
+ pause-stream: 0.0.11
+ split: 0.3
+ stream-combiner: ~0.0.4
+ through: ~2.3.1
+ checksum: c3ec4e1efc27ab3e73a98923f0a2fa9a19051b87068fea2f3d53d2e4e8c5cfdadf8c8a115b17f3d90b16a46432d396bad91b6e8d0cceb3e449be717a03b75209
+ languageName: node
+ linkType: hard
+
"event-target-shim@npm:^5.0.0":
version: 5.0.1
resolution: "event-target-shim@npm:5.0.1"
@@ -15670,6 +15692,13 @@ __metadata:
languageName: node
linkType: hard
+"from@npm:~0":
+ version: 0.1.7
+ resolution: "from@npm:0.1.7"
+ checksum: 3aab5aea8fe8e1f12a5dee7f390d46a93431ce691b6222dcd5701c5d34378e51ca59b44967da1105a0f90fcdf5d7629d963d51e7ccd79827d19693bdcfb688d4
+ languageName: node
+ linkType: hard
+
"fromentries@npm:^1.3.1":
version: 1.3.2
resolution: "fromentries@npm:1.3.2"
@@ -15684,7 +15713,7 @@ __metadata:
languageName: node
linkType: hard
-"fs-extra@npm:10.0.0":
+"fs-extra@npm:10.0.0, fs-extra@npm:^10.0.0":
version: 10.0.0
resolution: "fs-extra@npm:10.0.0"
dependencies:
@@ -16284,17 +16313,17 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
-"globby@npm:^12.0.2":
- version: 12.0.2
- resolution: "globby@npm:12.0.2"
+"globby@npm:^12.0.1, globby@npm:^12.0.2":
+ version: 12.2.0
+ resolution: "globby@npm:12.2.0"
dependencies:
array-union: ^3.0.1
dir-glob: ^3.0.1
fast-glob: ^3.2.7
- ignore: ^5.1.8
+ ignore: ^5.1.9
merge2: ^1.4.1
slash: ^4.0.0
- checksum: 61057effcc7edc7ca7ba9e27a435f08e05b2b8d80c13d2a18e14804936efcc7b1fae528536b6e6f85969f0a76fb0c86d65b3003f39e060457a2e524a3016b704
+ checksum: 121fee62bb9a43a35a32731cda9540241003ef578f9cee5ad87b27d3020b94857ff62f8d82cb99dbeedf6f26981c9fa62509d873392642ceb37674f3d6ec4e52
languageName: node
linkType: hard
@@ -17370,7 +17399,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
-"ignore@npm:^5.1.8, ignore@npm:^5.2.0":
+"ignore@npm:^5.1.8, ignore@npm:^5.1.9, ignore@npm:^5.2.0":
version: 5.2.0
resolution: "ignore@npm:5.2.0"
checksum: 7fb7b4c4c52c2555113ff968f8a83b8ac21b076282bfcb3f468c3fb429be69bd56222306c31de95dd452c647fc6ae24339b8047ebe3ef34c02591abfec58da01
@@ -20513,6 +20542,13 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
+"map-stream@npm:~0.1.0":
+ version: 0.1.0
+ resolution: "map-stream@npm:0.1.0"
+ checksum: 7dd6debe511c1b55d9da75e1efa65a28b1252a2d8357938d2e49b412713c478efbaefb0cdf0ee0533540c3bf733e8f9f71e1a15aa0fe74bf71b64e75bf1576bd
+ languageName: node
+ linkType: hard
+
"map-values@npm:^1.0.1":
version: 1.0.1
resolution: "map-values@npm:1.0.1"
@@ -22846,6 +22882,15 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
+"pause-stream@npm:0.0.11":
+ version: 0.0.11
+ resolution: "pause-stream@npm:0.0.11"
+ dependencies:
+ through: ~2.3
+ checksum: 86f12c64cdaaa8e45ebaca4e39a478e1442db8b4beabc280b545bfaf79c0e2f33c51efb554aace5c069cc441c7b924ba484837b345eaa4ba6fc940d62f826802
+ languageName: node
+ linkType: hard
+
"pbkdf2@npm:^3.0.17, pbkdf2@npm:^3.0.3":
version: 3.1.2
resolution: "pbkdf2@npm:3.1.2"
@@ -23902,6 +23947,17 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
+"ps-tree@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "ps-tree@npm:1.2.0"
+ dependencies:
+ event-stream: =3.3.4
+ bin:
+ ps-tree: ./bin/ps-tree.js
+ checksum: 9d1c159e0890db5aa05f84d125193c2190a6c4ecd457596fd25e7611f8f747292a846459dcc0244e27d45529d4cea6d1010c3a2a087fad02624d12fdb7d97c22
+ languageName: node
+ linkType: hard
+
"pseudomap@npm:^1.0.1":
version: 1.0.2
resolution: "pseudomap@npm:1.0.2"
@@ -25428,11 +25484,13 @@ resolve@^2.0.0-next.3:
"@types/jest": 27.4.0
"@types/jscodeshift": 0.11.2
"@types/lodash.template": 4.5.0
+ "@types/prompts": 2.4.0
all-contributors-cli: 6.20.0
ansi-colors: 4.1.1
babel-jest: 27.4.6
babel-plugin-auto-import: 1.1.0
babel-plugin-remove-code: 0.0.6
+ boxen: 5.1.2
core-js: 3.20.3
cypress: 9.3.1
cypress-wait-until: 1.7.2
@@ -25442,14 +25500,17 @@ resolve@^2.0.0-next.3:
jscodeshift: 0.13.0
lerna: 4.0.0
lodash.template: 4.5.0
+ msw: 0.36.7
nodemon: 2.0.15
npm-packlist: 3.0.0
octokit: 1.7.1
ora: 5.4.1
+ prompts: 2.4.2
rimraf: 3.0.2
terminal-link: 2.1.1
typescript: 4.5.5
typescript-transform-paths: 3.3.1
+ zx: 4.3.0
languageName: unknown
linkType: soft
@@ -26503,6 +26564,15 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
+"split@npm:0.3":
+ version: 0.3.3
+ resolution: "split@npm:0.3.3"
+ dependencies:
+ through: 2
+ checksum: 88c09b1b4de84953bf5d6c153123a1fbb20addfea9381f70d27b4eb6b2bfbadf25d313f8f5d3fd727d5679b97bfe54da04766b91010f131635bf49e51d5db3fc
+ languageName: node
+ linkType: hard
+
"split@npm:^1.0.0":
version: 1.0.1
resolution: "split@npm:1.0.1"
@@ -26668,6 +26738,15 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
+"stream-combiner@npm:~0.0.4":
+ version: 0.0.4
+ resolution: "stream-combiner@npm:0.0.4"
+ dependencies:
+ duplexer: ~0.1.1
+ checksum: 8075a94c0eb0f20450a8236cb99d4ce3ea6e6a4b36d8baa7440b1a08cde6ffd227debadffaecd80993bd334282875d0e927ab5b88484625e01970dd251004ff5
+ languageName: node
+ linkType: hard
+
"stream-each@npm:^1.1.0":
version: 1.2.3
resolution: "stream-each@npm:1.2.3"
@@ -27594,7 +27673,7 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
-"through@npm:2, through@npm:>=2.2.7 <3, through@npm:^2.3.4, through@npm:^2.3.6, through@npm:^2.3.8":
+"through@npm:2, through@npm:>=2.2.7 <3, through@npm:^2.3.4, through@npm:^2.3.6, through@npm:^2.3.8, through@npm:~2.3, through@npm:~2.3.1":
version: 2.3.8
resolution: "through@npm:2.3.8"
checksum: 4b09f3774099de0d4df26d95c5821a62faee32c7e96fb1f4ebd54a2d7c11c57fe88b0a0d49cf375de5fee5ae6bf4eb56dbbf29d07366864e2ee805349970d3cc
@@ -30234,3 +30313,24 @@ resolve@^2.0.0-next.3:
checksum: 26dc7d32e5596824b565db1da9650d00d32659c1211195bef50c25c60820f9c942aa7abefe678fc1ed0b97c1755036ac1bde5f97881d7d0e73e04e02aca56957
languageName: node
linkType: hard
+
+"zx@npm:4.3.0":
+ version: 4.3.0
+ resolution: "zx@npm:4.3.0"
+ dependencies:
+ "@types/fs-extra": ^9.0.12
+ "@types/minimist": ^1.2.2
+ "@types/node": ^16.6
+ "@types/node-fetch": ^2.5.12
+ chalk: ^4.1.2
+ fs-extra: ^10.0.0
+ globby: ^12.0.1
+ minimist: ^1.2.5
+ node-fetch: ^2.6.1
+ ps-tree: ^1.2.0
+ which: ^2.0.2
+ bin:
+ zx: zx.mjs
+ checksum: 04f83a76bf0f75c08d36b9590f14e4aeddc6d70533af3cb90af34a26f64e07f3e9b65f95f3fba5fa2e8475cd302514b436c5b77d3af99185593a9e730fe540ec
+ languageName: node
+ linkType: hard