From 9a2f8a958ae9f0d3cdb936a695ed827d4ab71bab Mon Sep 17 00:00:00 2001 From: Kamil Piechaczek Date: Tue, 31 Jan 2017 10:36:30 +0100 Subject: [PATCH] Feature: Introduced "save-hashes" command for saving the hashes of packages to config file. --- README.md | 11 ++++++ index.js | 7 ++-- lib/commands/bootstrap.js | 6 +-- lib/commands/savehashes.js | 72 +++++++++++++++++++++++++++++++++++ lib/index.js | 10 ++++- lib/utils/log.js | 2 - lib/utils/updatejsonfile.js | 22 +++++++++++ package.json | 3 +- tests/utils/updatejsonfile.js | 43 +++++++++++++++++++++ 9 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 lib/commands/savehashes.js create mode 100644 lib/utils/updatejsonfile.js create mode 100644 tests/utils/updatejsonfile.js diff --git a/README.md b/README.md index 7606bf2..40919ed 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,17 @@ mgit exec 'echo `pwd`' # /home/mgit/packages/organization/repository-2 ``` + +### save-hashes + +Saves hashes of packages in `mgit.json`. It allows to easily fix project to a specific state. + +Example: + +```bash +mgit save-hashes +``` + ## Projects using mgit2 * [CKEditor 5](https://github.com/ckeditor/ckeditor5) diff --git a/index.js b/index.js index b633bd0..cdf5492 100755 --- a/index.js +++ b/index.js @@ -24,9 +24,10 @@ const cli = meow( ` $ mgit [command] Commands: - bootstrap Install packages (i.e. clone dependent repositories). - exec Exec shell command in each package. - update Update packages to the latest versions (i.e. pull changes). + bootstrap Installs packages (i.e. clone dependent repositories). + exec Executes shell command in each package. + update Updates packages to the latest versions (i.e. pull changes). + save-hashes Saves hashes of packages in mgit.json. It allows to easily fix project to a specific state. Options: --recursive Whether to install dependencies recursively. diff --git a/lib/commands/bootstrap.js b/lib/commands/bootstrap.js index 58f98c3..7b4ae67 100644 --- a/lib/commands/bootstrap.js +++ b/lib/commands/bootstrap.js @@ -41,7 +41,7 @@ module.exports = { .then( ( output ) => { log.info( output ); - const response = { + const commandOutput = { logs: log.all() }; @@ -57,10 +57,10 @@ module.exports = { packages = packages.concat( Object.keys( packageJson.devDependencies ) ); } - response.packages = packages; + commandOutput.packages = packages; } - resolve( response ); + resolve( commandOutput ); } ) .catch( ( error ) => { log.error( error ); diff --git a/lib/commands/savehashes.js b/lib/commands/savehashes.js new file mode 100644 index 0000000..0d1beed --- /dev/null +++ b/lib/commands/savehashes.js @@ -0,0 +1,72 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +const path = require( 'path' ); +const updateJsonFile = require( '../utils/updatejsonfile' ); + +module.exports = { + /** + * @param {Object} data + * @param {String} data.packageName Name of current package to process. + * @returns {Promise} + */ + execute( data ) { + const log = require( '../utils/log' )(); + const execCommand = require( './exec' ); + + return new Promise( ( resolve, reject ) => { + execCommand.execute( getExecData( 'git rev-parse HEAD' ) ) + .then( ( execResponse ) => { + const commitHash = execResponse.logs.info[ 0 ]; + + const commandResponse = { + packageName: data.packageName, + commit: commitHash + }; + + log.info( `Commit: ${ commitHash }.` ); + + resolve( { + response: commandResponse, + logs: log.all() + } ); + } ) + .catch( ( error ) => { + log.error( error ); + + reject( { logs: log.all() } ); + } ); + } ); + + function getExecData( command ) { + return Object.assign( {}, data, { + parameters: [ command ] + } ); + } + }, + + /** + * Saves collected hashes to configuration file. + * + * @param {Set} processedPackages Collection of processed packages. + * @param {Set} commandResponses Results of executed command for each package. + */ + afterExecute( processedPackages, commandResponses ) { + const cwd = require( '../utils/getcwd.js' )(); + const mgitJsonPath = path.join( cwd, 'mgit.json' ); + + updateJsonFile( mgitJsonPath, ( json ) => { + for ( const response of commandResponses.values() ) { + const repository = json.dependencies[ response.packageName ].split( '#' )[ 0 ]; + + json.dependencies[ response.packageName ] = `${ repository }#${ response.commit }`; + } + + return json; + } ); + } +}; diff --git a/lib/index.js b/lib/index.js index cd513ca..1ae622c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,6 +22,9 @@ module.exports = function( parameters, options ) { const resolver = require( options.resolverPath ); + // Remove all dashes from command name. + parameters[ 0 ] = parameters[ 0 ].replace( /-/g, '' ); + const commandPath = path.join( __dirname, 'commands', parameters[ 0 ] ); const command = require( commandPath ); @@ -30,6 +33,7 @@ module.exports = function( parameters, options ) { } const processedPackages = new Set(); + const commandResponses = new Set(); const packageNames = Object.keys( options.dependencies ); let allPackagesNumber = packageNames.length; @@ -69,6 +73,10 @@ module.exports = function( parameters, options ) { } ); } + if ( returnedData.response ) { + commandResponses.add( returnedData.response ); + } + if ( returnedData.logs ) { logDisplay( packageName, returnedData.logs, { current: donePackagesNumber, @@ -93,7 +101,7 @@ module.exports = function( parameters, options ) { return forkPool.killAll() .then( () => { if ( typeof command.afterExecute === 'function' ) { - command.afterExecute( processedPackages ); + command.afterExecute( processedPackages, commandResponses ); } const endTime = process.hrtime( startTime ); diff --git a/lib/utils/log.js b/lib/utils/log.js index d2ecc1d..5d427be 100644 --- a/lib/utils/log.js +++ b/lib/utils/log.js @@ -5,8 +5,6 @@ 'use strict'; -/** - */ module.exports = function log() { const logs = new Map( [ [ 'info', [] ], diff --git a/lib/utils/updatejsonfile.js b/lib/utils/updatejsonfile.js new file mode 100644 index 0000000..994fa92 --- /dev/null +++ b/lib/utils/updatejsonfile.js @@ -0,0 +1,22 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +'use strict'; + +const fs = require( 'fs' ); + +/** + * Updates JSON file under specified path. + * @param {String} path Path to file on disk. + * @param {Function} updateFunction Function that will be called with parsed JSON object. It should return + * modified JSON object to save. + */ +module.exports = function updateJsonFile( path, updateFunction ) { + const contents = fs.readFileSync( path, 'utf-8' ); + let json = JSON.parse( contents ); + json = updateFunction( json ); + + fs.writeFileSync( path, JSON.stringify( json, null, 2 ) + '\n', 'utf-8' ); +}; diff --git a/package.json b/package.json index aa1ea8b..f131f58 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "gulp": "^3.9.1", "guppy-pre-commit": "^0.4.0", "istanbul": "^0.4.5", - "mocha": "^3.2.0" + "mocha": "^3.2.0", + "sinon": "^1.17.7" }, "repository": { "type": "git", diff --git a/tests/utils/updatejsonfile.js b/tests/utils/updatejsonfile.js new file mode 100644 index 0000000..fea46d8 --- /dev/null +++ b/tests/utils/updatejsonfile.js @@ -0,0 +1,43 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/* jshint mocha:true */ + +'use strict'; + +const updateJsonFile = require( '../../lib/utils/updatejsonfile' ); +const expect = require( 'chai' ).expect; +const sinon = require( 'sinon' ); + +describe( 'utils', () => { + let sandbox; + + beforeEach( () => { + sandbox = sinon.sandbox.create(); + } ); + + afterEach( () => { + sandbox.restore(); + } ); + describe( 'updateJsonFile()', () => { + it( 'should read, update and save JSON file', () => { + const path = 'path/to/file.json'; + const fs = require( 'fs' ); + const readFileStub = sandbox.stub( fs, 'readFileSync', () => '{}' ); + const modifiedJSON = { modified: true }; + const writeFileStub = sandbox.stub( fs, 'writeFileSync' ); + + updateJsonFile( path, () => { + return modifiedJSON; + } ); + + expect( readFileStub.calledOnce ).to.equal( true ); + expect( readFileStub.firstCall.args[ 0 ] ).to.equal( path ); + expect( writeFileStub.calledOnce ).to.equal( true ); + expect( writeFileStub.firstCall.args[ 0 ] ).to.equal( path ); + expect( writeFileStub.firstCall.args[ 1 ] ).to.equal( JSON.stringify( modifiedJSON, null, 2 ) + '\n' ); + } ); + } ); +} );