diff --git a/bin/plugin/commands/common.js b/bin/plugin/commands/common.js index fc540b073a1ff..59abfa93447ef 100644 --- a/bin/plugin/commands/common.js +++ b/bin/plugin/commands/common.js @@ -1,72 +1,26 @@ /** * External dependencies */ -const fs = require( 'fs' ); -const rimraf = require( 'rimraf' ); const semver = require( 'semver' ); +const SimpleGit = require( 'simple-git' ); /** * Internal dependencies */ -const { log, formats } = require( '../lib/logger' ); -const { runStep, readJSONFile } = require( '../lib/utils' ); -const git = require( '../lib/git' ); -const config = require( '../config' ); - -/** - * Clone the repository and returns the working directory. - * - * @param {string} abortMessage Abort message. - * - * @return {Promise} Repository local path. - */ -async function runGitRepositoryCloneStep( abortMessage ) { - // Cloning the repository. - let gitWorkingDirectoryPath; - await runStep( 'Cloning the Git repository', abortMessage, async () => { - log( '>> Cloning the Git repository' ); - gitWorkingDirectoryPath = await git.clone( config.gitRepositoryURL ); - log( - '>> The Git repository has been successfully cloned in the following temporary folder: ' + - formats.success( gitWorkingDirectoryPath ) - ); - } ); - - return gitWorkingDirectoryPath; -} - -/** - * Clean the working directories. - * - * @param {string[]} folders Folders to clean. - * @param {string} abortMessage Abort message. - */ -async function runCleanLocalFoldersStep( folders, abortMessage ) { - await runStep( 'Cleaning the temporary folders', abortMessage, async () => { - await Promise.all( - folders.map( async ( directoryPath ) => { - if ( fs.existsSync( directoryPath ) ) { - await rimraf( directoryPath, ( err ) => { - if ( err ) { - throw err; - } - } ); - } - } ) - ); - } ); -} +const { readJSONFile } = require( '../lib/utils' ); /** * Finds the name of the current plugin release branch based on the version in - * the package.json file. + * the package.json file and the latest `trunk` branch in `git`. * * @param {string} gitWorkingDirectoryPath Path to the project's working directory. * * @return {string} Name of the plugin release branch. */ async function findPluginReleaseBranchName( gitWorkingDirectoryPath ) { - await git.checkoutRemoteBranch( gitWorkingDirectoryPath, 'trunk' ); + await SimpleGit( gitWorkingDirectoryPath ) + .fetch( 'origin', 'trunk' ) + .checkout( 'trunk' ); const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; const mainPackageJson = readJSONFile( packageJsonPath ); @@ -141,6 +95,4 @@ function calculateVersionBumpFromChangelog( module.exports = { calculateVersionBumpFromChangelog, findPluginReleaseBranchName, - runGitRepositoryCloneStep, - runCleanLocalFoldersStep, }; diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index e715ee69345ad..6557387b0466d 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -6,20 +6,24 @@ const path = require( 'path' ); const glob = require( 'fast-glob' ); const fs = require( 'fs' ); const { inc: semverInc } = require( 'semver' ); +const rimraf = require( 'rimraf' ); const readline = require( 'readline' ); +const { default: git } = require( 'simple-git' ); /** * Internal dependencies */ const { log, formats } = require( '../lib/logger' ); -const { askForConfirmation, runStep, readJSONFile } = require( '../lib/utils' ); +const { + askForConfirmation, + runStep, + readJSONFile, + getRandomTemporaryPath, +} = require( '../lib/utils' ); const { calculateVersionBumpFromChangelog, findPluginReleaseBranchName, - runGitRepositoryCloneStep, - runCleanLocalFoldersStep, } = require( './common' ); -const git = require( '../lib/git' ); const { join } = require( 'path' ); /** @@ -55,6 +59,17 @@ const { join } = require( 'path' ); * @property {ReleaseType} releaseType The selected release type. */ +/** + * Throws if given an error in the node.js callback style. + * + * @param {any|null} error If callback failed, this will hold a value. + */ +const rethrow = ( error ) => { + if ( error ) { + throw error; + } +}; + /** * Checks out the npm release branch. * @@ -65,8 +80,9 @@ async function checkoutNpmReleaseBranch( { npmReleaseBranch, } ) { // Creating the release branch. - await git.checkoutRemoteBranch( gitWorkingDirectoryPath, npmReleaseBranch ); - await git.fetch( gitWorkingDirectoryPath, [ '--depth=100' ] ); + await git( gitWorkingDirectoryPath ) + .fetch( npmReleaseBranch, [ '--depth=100' ] ) + .checkout( npmReleaseBranch ); log( '>> The local npm release branch ' + formats.success( npmReleaseBranch ) + @@ -105,13 +121,13 @@ async function runNpmReleaseBranchSyncStep( pluginReleaseBranch, config ) { `>> Syncing the latest plugin release to "${ pluginReleaseBranch }".` ); - await git.replaceContentFromRemoteBranch( - gitWorkingDirectoryPath, - pluginReleaseBranch - ); + const repo = git( gitWorkingDirectoryPath ); + + await repo + .raw( 'rm', '-r', '.' ) + .raw( 'checkout', `origin/${ pluginReleaseBranch }`, '--', '.' ); - const commitHash = await git.commit( - gitWorkingDirectoryPath, + const { commit: commitHash } = await repo.commit( `Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch` ); @@ -280,11 +296,10 @@ async function updatePackages( config ) { ); } - const commitHash = await git.commit( - gitWorkingDirectoryPath, - 'Update changelog files', - [ './*' ] - ); + const { commit: commitHash } = await git( gitWorkingDirectoryPath ) + .add( [ './*' ] ) + .commit( 'Update changelog files' ); + if ( commitHash ) { await runPushGitChangesStep( config ); } @@ -313,10 +328,7 @@ async function runPushGitChangesStep( { abortMessage ); } - await git.pushBranchToOrigin( - gitWorkingDirectoryPath, - npmReleaseBranch - ); + await git( gitWorkingDirectoryPath ).push( 'origin', npmReleaseBranch ); } ); } @@ -345,9 +357,10 @@ async function publishPackagesToNpm( { stdio: 'inherit', } ); - const beforeCommitHash = await git.getLastCommitHash( - gitWorkingDirectoryPath - ); + const beforeCommitHash = await git( gitWorkingDirectoryPath ).revparse( [ + '--short', + 'HEAD', + ] ); const yesFlag = interactive ? '' : '--yes'; const noVerifyAccessFlag = interactive ? '' : '--no-verify-access'; @@ -403,9 +416,10 @@ async function publishPackagesToNpm( { ); } - const afterCommitHash = await git.getLastCommitHash( - gitWorkingDirectoryPath - ); + const afterCommitHash = await git( gitWorkingDirectoryPath ).revparse( [ + '--short', + 'HEAD', + ] ); if ( afterCommitHash === beforeCommitHash ) { return; } @@ -439,18 +453,15 @@ async function backportCommitsToBranch( log( `>> Backporting commits to "${ branchName }".` ); - await git.resetLocalBranchAgainstOrigin( - gitWorkingDirectoryPath, - branchName - ); + const repo = git( gitWorkingDirectoryPath ); + + await repo.fetch().checkout( branchName ).pull( 'origin', branchName ); + for ( const commitHash of commits ) { - await git.cherrypickCommitIntoBranch( - gitWorkingDirectoryPath, - branchName, - commitHash - ); + await repo.raw( 'cherry-pick', commitHash ); } - await git.pushBranchToOrigin( gitWorkingDirectoryPath, branchName ); + + await repo.push( 'origin', branchName ); log( `>> Backporting successfully finished.` ); } @@ -478,11 +489,20 @@ async function runPackagesRelease( config, customMessages ) { const temporaryFolders = []; if ( ! config.gitWorkingDirectoryPath ) { - // Cloning the Git repository. - config.gitWorkingDirectoryPath = await runGitRepositoryCloneStep( - config.abortMessage + const gitPath = getRandomTemporaryPath(); + config.gitWorkingDirectoryPath = gitPath; + fs.mkdirSync( gitPath, { recursive: true } ); + temporaryFolders.push( gitPath ); + + await runStep( + 'Cloning the Git repository', + config.abortMessage, + async () => { + log( '>> Cloning the Git repository' ); + await git( gitPath ).clone( config.gitRepositoryURL ); + log( ` >> successfully clone into: ${ gitPath }` ); + } ); - temporaryFolders.push( config.gitWorkingDirectoryPath ); } let pluginReleaseBranch; @@ -518,7 +538,16 @@ async function runPackagesRelease( config, customMessages ) { } } - await runCleanLocalFoldersStep( temporaryFolders, 'Cleaning failed.' ); + await runStep( + 'Cleaning the temporary folders', + 'Cleaning failed', + async () => + await Promise.all( + temporaryFolders + .filter( ( tempDir ) => fs.existsSync( tempDir ) ) + .map( ( tempDir ) => rimraf( tempDir, rethrow ) ) + ) + ); log( '\n>> 🎉 WordPress packages are now published!\n\n', diff --git a/bin/plugin/lib/git.js b/bin/plugin/lib/git.js deleted file mode 100644 index 8208efa7164d1..0000000000000 --- a/bin/plugin/lib/git.js +++ /dev/null @@ -1,202 +0,0 @@ -// @ts-nocheck -/** - * External dependencies - */ -const SimpleGit = require( 'simple-git' ); - -/** - * Internal dependencies - */ -const { getRandomTemporaryPath } = require( './utils' ); - -/** - * Clones a GitHub repository. - * - * @param {string} repositoryUrl - * - * @return {Promise} Repository local Path - */ -async function clone( repositoryUrl ) { - const gitWorkingDirectoryPath = getRandomTemporaryPath(); - const simpleGit = SimpleGit(); - await simpleGit.clone( repositoryUrl, gitWorkingDirectoryPath, [ - '--depth=1', - '--no-single-branch', - ] ); - return gitWorkingDirectoryPath; -} - -/** - * Fetches changes from the repository. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string[]|Object} options Git options to apply. - */ -async function fetch( gitWorkingDirectoryPath, options = [] ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.fetch( options ); -} - -/** - * Commits changes to the repository. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} message Commit message. - * @param {string[]} filesToAdd Files to add. - * - * @return {Promise} Commit Hash - */ -async function commit( gitWorkingDirectoryPath, message, filesToAdd = [] ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.add( filesToAdd ); - const commitData = await simpleGit.commit( message ); - const commitHash = commitData.commit; - - return commitHash; -} - -/** - * Creates a local branch. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} branchName Branch Name - */ -async function createLocalBranch( gitWorkingDirectoryPath, branchName ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.checkoutLocalBranch( branchName ); -} - -/** - * Checkout a local branch. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} branchName Branch Name - */ -async function checkoutRemoteBranch( gitWorkingDirectoryPath, branchName ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.fetch( 'origin', branchName ); - await simpleGit.checkout( branchName ); -} - -/** - * Creates a local tag. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} tagName Tag Name - */ -async function createLocalTag( gitWorkingDirectoryPath, tagName ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.addTag( tagName ); -} - -/** - * Pushes a local branch to the origin. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} branchName Branch Name - */ -async function pushBranchToOrigin( gitWorkingDirectoryPath, branchName ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.push( 'origin', branchName ); -} - -/** - * Pushes tags to the origin. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - */ -async function pushTagsToOrigin( gitWorkingDirectoryPath ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.pushTags( 'origin' ); -} - -/** - * Discard local changes. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - */ -async function discardLocalChanges( gitWorkingDirectoryPath ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.reset( 'hard' ); -} - -/** - * Reset local branch against the origin. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} branchName Branch Name - */ -async function resetLocalBranchAgainstOrigin( - gitWorkingDirectoryPath, - branchName -) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.fetch(); - await simpleGit.checkout( branchName ); - await simpleGit.pull( 'origin', branchName ); -} - -/** - * Gets the commit hash for the last commit in the current branch. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * - * @return {string} Commit hash. - */ -async function getLastCommitHash( gitWorkingDirectoryPath ) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - return await simpleGit.revparse( [ '--short', 'HEAD' ] ); -} - -/** - * Cherry-picks a commit into trunk - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} branchName Branch name. - * @param {string} commitHash Commit hash. - */ -async function cherrypickCommitIntoBranch( - gitWorkingDirectoryPath, - branchName, - commitHash -) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.checkout( branchName ); - await simpleGit.raw( [ 'cherry-pick', commitHash ] ); -} - -/** - * Replaces the local branch's content with the content from another branch. - * - * @param {string} gitWorkingDirectoryPath Local repository path. - * @param {string} sourceBranchName Branch Name - */ -async function replaceContentFromRemoteBranch( - gitWorkingDirectoryPath, - sourceBranchName -) { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await simpleGit.raw( [ 'rm', '-r', '.' ] ); - await simpleGit.raw( [ - 'checkout', - `origin/${ sourceBranchName }`, - '--', - '.', - ] ); -} - -module.exports = { - clone, - commit, - checkoutRemoteBranch, - createLocalBranch, - createLocalTag, - fetch, - pushBranchToOrigin, - pushTagsToOrigin, - discardLocalChanges, - resetLocalBranchAgainstOrigin, - getLastCommitHash, - cherrypickCommitIntoBranch, - replaceContentFromRemoteBranch, -};