Code Freeze CLI: Add branch command (#37914)
This commit is contained in:
parent
6f7eeeaf49
commit
34bd5e1bf0
|
@ -3296,6 +3296,9 @@ importers:
|
|||
ora:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1
|
||||
promptly:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
semver:
|
||||
specifier: ^7.3.2
|
||||
version: 7.3.5
|
||||
|
@ -8733,9 +8736,9 @@ packages:
|
|||
'@babel/core': 7.21.3
|
||||
'@babel/helper-annotate-as-pure': 7.16.7
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/plugin-syntax-jsx': 7.16.7(@babel/core@7.21.3)
|
||||
'@babel/types': 7.17.0
|
||||
'@babel/types': 7.21.3
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-react-jsx@7.19.0(@babel/core@7.12.9):
|
||||
|
@ -10178,7 +10181,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.17.8
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.17.8)
|
||||
'@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.17.8)
|
||||
'@babel/types': 7.21.3
|
||||
|
@ -10191,7 +10194,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.21.3
|
||||
'@babel/helper-plugin-utils': 7.20.2
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3)
|
||||
'@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3)
|
||||
'@babel/types': 7.21.3
|
||||
|
@ -20863,8 +20866,8 @@ packages:
|
|||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.20.2
|
||||
caniuse-lite: 1.0.30001352
|
||||
browserslist: 4.21.4
|
||||
caniuse-lite: 1.0.30001418
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
|
@ -22406,6 +22409,7 @@ packages:
|
|||
escalade: 3.1.1
|
||||
node-releases: 2.0.6
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/browserslist@4.20.4:
|
||||
resolution: {integrity: sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==}
|
||||
|
@ -27384,12 +27388,12 @@ packages:
|
|||
vue-template-compiler:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.18.6
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@types/json-schema': 7.0.9
|
||||
chalk: 4.1.2
|
||||
chokidar: 3.5.3
|
||||
cosmiconfig: 6.0.0
|
||||
deepmerge: 4.3.0
|
||||
deepmerge: 4.2.2
|
||||
eslint: 8.32.0
|
||||
fs-extra: 9.1.0
|
||||
glob: 7.2.3
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"graphql": "^16.6.0",
|
||||
"octokit": "^2.0.14",
|
||||
"ora": "^5.4.1",
|
||||
"promptly": "^3.2.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Command } from '@commander-js/extra-typings';
|
||||
import { parse } from 'semver';
|
||||
import { confirm } from 'promptly';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import { setOutput } from '@actions/core';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
getLatestReleaseVersion,
|
||||
doesGithubBranchExist,
|
||||
getRefFromGithubBranch,
|
||||
createGithubBranch,
|
||||
deleteGithubBranch,
|
||||
} from '../../../github/repo';
|
||||
import { WPIncrement } from '../milestone/utils';
|
||||
import { Options } from './types';
|
||||
|
||||
const getNextReleaseBranch = async ( options: Options ) => {
|
||||
const latestReleaseVersion = await getLatestReleaseVersion( options );
|
||||
const nextReleaseVersion = WPIncrement( latestReleaseVersion );
|
||||
const parsedNextReleaseVersion = parse( nextReleaseVersion );
|
||||
const nextReleaseMajorMinor = `${ parsedNextReleaseVersion.major }.${ parsedNextReleaseVersion.minor }`;
|
||||
return `release/${ nextReleaseMajorMinor }`;
|
||||
};
|
||||
|
||||
export const branchCommand = new Command( 'branch' )
|
||||
.description( 'Create a new release branch' )
|
||||
.option(
|
||||
'-g --github',
|
||||
'CLI command is used in the Github Actions context.'
|
||||
)
|
||||
.option( '-d --dryRun', 'Prepare the branch but do not create it.' )
|
||||
.option(
|
||||
'-o --owner <owner>',
|
||||
'Repository owner. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-n --name <name>',
|
||||
'Repository name. Default: woocommerce',
|
||||
'woocommerce'
|
||||
)
|
||||
.option(
|
||||
'-b --branch <branch>',
|
||||
'Release branch to create. The branch will be determined from Github if none is supplied'
|
||||
)
|
||||
.option(
|
||||
'-s --source <source>',
|
||||
'Branch to create the release branch from. Default: trunk',
|
||||
'trunk'
|
||||
)
|
||||
.action( async ( options: Options ) => {
|
||||
const { github, source, branch, owner, name, dryRun } = options;
|
||||
let nextReleaseBranch;
|
||||
|
||||
if ( ! branch ) {
|
||||
const versionSpinner = ora(
|
||||
chalk.yellow(
|
||||
'No branch supplied, going off the latest release version'
|
||||
)
|
||||
).start();
|
||||
nextReleaseBranch = await getNextReleaseBranch( options );
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`The next release branch is ${ nextReleaseBranch }`
|
||||
)
|
||||
);
|
||||
versionSpinner.succeed();
|
||||
} else {
|
||||
nextReleaseBranch = branch;
|
||||
}
|
||||
|
||||
const branchSpinner = ora(
|
||||
chalk.yellow(
|
||||
`Check to see if branch ${ nextReleaseBranch } exists on ${ owner }/${ name }`
|
||||
)
|
||||
).start();
|
||||
|
||||
const branchExists = await doesGithubBranchExist(
|
||||
options,
|
||||
nextReleaseBranch
|
||||
);
|
||||
branchSpinner.succeed();
|
||||
|
||||
if ( branchExists ) {
|
||||
if ( github ) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
`Release branch ${ nextReleaseBranch } already exists`
|
||||
)
|
||||
);
|
||||
// When in Github Actions, we don't want to prompt the user for input.
|
||||
process.exit( 0 );
|
||||
}
|
||||
const deleteExistingReleaseBranch = await confirm(
|
||||
chalk.yellow(
|
||||
`Release branch ${ nextReleaseBranch } already exists on ${ owner }/${ name }, do you want to delete it and create a new one from ${ source }? [y/n]`
|
||||
)
|
||||
);
|
||||
if ( deleteExistingReleaseBranch ) {
|
||||
const deleteBranchSpinner = ora(
|
||||
chalk.yellow(
|
||||
`Delete branch ${ nextReleaseBranch } on ${ owner }/${ name } and create new one from ${ source }`
|
||||
)
|
||||
).start();
|
||||
await deleteGithubBranch( options, nextReleaseBranch );
|
||||
deleteBranchSpinner.succeed();
|
||||
} else {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Branch ${ nextReleaseBranch } already exist on ${ owner }/${ name }, no action taken.`
|
||||
)
|
||||
);
|
||||
process.exit( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
const createBranchSpinner = ora(
|
||||
chalk.yellow( `Create branch ${ nextReleaseBranch }` )
|
||||
).start();
|
||||
|
||||
if ( dryRun ) {
|
||||
createBranchSpinner.succeed();
|
||||
console.log(
|
||||
chalk.green(
|
||||
`DRY RUN: Skipping actual creation of branch ${ nextReleaseBranch } on ${ owner }/${ name }`
|
||||
)
|
||||
);
|
||||
|
||||
process.exit( 0 );
|
||||
}
|
||||
|
||||
const ref = await getRefFromGithubBranch( options, source );
|
||||
await createGithubBranch( options, nextReleaseBranch, ref );
|
||||
createBranchSpinner.succeed();
|
||||
|
||||
if ( github ) {
|
||||
setOutput( 'nextReleaseBranch', nextReleaseBranch );
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
`Branch ${ nextReleaseBranch } successfully created on ${ owner }/${ name }`
|
||||
)
|
||||
);
|
||||
} );
|
|
@ -0,0 +1,8 @@
|
|||
export type Options = {
|
||||
github?: boolean;
|
||||
dryRun?: boolean;
|
||||
owner?: string;
|
||||
name?: string;
|
||||
branch?: string;
|
||||
source?: string;
|
||||
};
|
|
@ -7,11 +7,13 @@ import { Command } from '@commander-js/extra-typings';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { verifyDayCommand } from './verify-day';
|
||||
import { milesStoneCommand } from './milestone';
|
||||
import { milestoneCommand } from './milestone';
|
||||
import { branchCommand } from './branch';
|
||||
|
||||
const program = new Command( 'code-freeze' )
|
||||
.description( 'Code freeze utilities' )
|
||||
.addCommand( verifyDayCommand )
|
||||
.addCommand( milesStoneCommand );
|
||||
.addCommand( milestoneCommand )
|
||||
.addCommand( branchCommand );
|
||||
|
||||
export default program;
|
||||
|
|
|
@ -10,10 +10,10 @@ import ora from 'ora';
|
|||
*/
|
||||
import { getLatestReleaseVersion } from '../../../github/repo';
|
||||
import { octokitWithAuth } from '../../../github/api';
|
||||
import { WPIncrement, setGithubMilestoneOutputs } from './utils';
|
||||
import { setGithubMilestoneOutputs, WPIncrement } from './utils';
|
||||
import { Options } from './types';
|
||||
|
||||
export const milesStoneCommand = new Command( 'milestone' )
|
||||
export const milestoneCommand = new Command( 'milestone' )
|
||||
.description( 'Create a milestone' )
|
||||
.option(
|
||||
'-g --github',
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Repository } from '@octokit/graphql-schema';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { graphqlWithAuth } from './api';
|
||||
import { graphqlWithAuth, octokitWithAuth } from './api';
|
||||
|
||||
export const getLatestReleaseVersion = async ( options: {
|
||||
owner?: string;
|
||||
|
@ -34,3 +34,97 @@ export const getLatestReleaseVersion = async ( options: {
|
|||
( tagName ) => tagName.isLatest
|
||||
).tagName;
|
||||
};
|
||||
|
||||
export const doesGithubBranchExist = async (
|
||||
options: {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
},
|
||||
nextReleaseBranch: string
|
||||
): Promise< boolean > => {
|
||||
const { owner, name } = options;
|
||||
try {
|
||||
const branchOnGithub = await octokitWithAuth.request(
|
||||
'GET /repos/{owner}/{repo}/branches/{branch}',
|
||||
{
|
||||
owner,
|
||||
repo: name,
|
||||
branch: nextReleaseBranch,
|
||||
}
|
||||
);
|
||||
return branchOnGithub.data.name === nextReleaseBranch;
|
||||
} catch ( e ) {
|
||||
if (
|
||||
e.status === 404 &&
|
||||
e.response.data.message === 'Branch not found'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
throw new Error( e );
|
||||
}
|
||||
};
|
||||
|
||||
export const getRefFromGithubBranch = async (
|
||||
options: {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
},
|
||||
source: string
|
||||
): Promise< string > => {
|
||||
const { owner, name } = options;
|
||||
const { repository } = await graphqlWithAuth< {
|
||||
repository: Repository;
|
||||
} >( `
|
||||
{
|
||||
repository(owner:"${ owner }", name:"${ name }") {
|
||||
ref(qualifiedName: "refs/heads/${ source }") {
|
||||
target {
|
||||
... on Commit {
|
||||
history(first: 1) {
|
||||
edges{ node{ oid } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
` );
|
||||
|
||||
// @ts-ignore: The graphql query is typed, but the response is not.
|
||||
return repository.ref.target.history.edges.shift().node.oid;
|
||||
};
|
||||
|
||||
export const createGithubBranch = async (
|
||||
options: {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
},
|
||||
branch: string,
|
||||
ref: string
|
||||
): Promise< void > => {
|
||||
const { owner, name } = options;
|
||||
await octokitWithAuth.request( 'POST /repos/{owner}/{repo}/git/refs', {
|
||||
owner,
|
||||
repo: name,
|
||||
ref: `refs/heads/${ branch }`,
|
||||
sha: ref,
|
||||
} );
|
||||
};
|
||||
|
||||
export const deleteGithubBranch = async (
|
||||
options: {
|
||||
owner?: string;
|
||||
name?: string;
|
||||
},
|
||||
branch: string
|
||||
): Promise< void > => {
|
||||
const { owner, name } = options;
|
||||
await octokitWithAuth.request(
|
||||
'DELETE /repos/{owner}/{repo}/git/refs/heads/{ref}',
|
||||
{
|
||||
owner,
|
||||
repo: name,
|
||||
ref: branch,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue