Sfoglia il codice sorgente

Code refractoring, using ES6 features.

Szymon Kupś 10 anni fa
parent
commit
224c90d15c
5 ha cambiato i file con 404 aggiunte e 289 eliminazioni
  1. 52 15
      dev/tasks/dev.js
  2. 126 0
      dev/tasks/test/git.js
  3. 112 149
      dev/tasks/test/tools.js
  4. 75 0
      dev/tasks/utils/git.js
  5. 39 125
      dev/tasks/utils/tools.js

+ 52 - 15
dev/tasks/dev.js

@@ -6,31 +6,68 @@
 'use strict';
 
 var tools = require( './utils/tools' );
+var git = require( './utils/git' );
 var path = require( 'path' );
 var ckeditor5Path = process.cwd();
-var workspaceAbsolutePath;
 
-module.exports = function( grunt ) {
-	grunt.registerTask( 'dev', function( target ) {
-		var	options = {
+module.exports = grunt => {
+	const packageJSON = grunt.config.data.pkg;
+
+	/**
+	 * 1. Get CKEditor5 dependencies from package.json file.
+	 * 2. Check if any of the repositories are already present in the workspace.
+	 * 		2.1. If repository is present in the workspace, check it out to desired branch if one is provided.
+	 * 		2.2. If repository is not present in the workspace, clone it and checkout to desired branch if one is provided.
+	 * 3. Link new repository to node_modules. (do not use npm link, use standard linking instead)
+	 */
+	grunt.registerTask( 'dev-init', function() {
+		// Get workspace root relative path from configuration and convert it to absolute path.
+		let	options = {
 			workspaceRoot: '..'
 		};
 
-		// Get workspace root from configuration.
 		options = this.options( options );
-		workspaceAbsolutePath = path.join( ckeditor5Path, options.workspaceRoot );
+		const workspaceAbsolutePath = path.join( ckeditor5Path, options.workspaceRoot );
+
+		// Get all CKEditor dependencies from package.json.
+		const dependencies = tools.getCKEditorDependencies( packageJSON.dependencies );
+
+		if ( dependencies ) {
+			const directories = tools.getCKE5Directories( workspaceAbsolutePath );
+
+			for ( let dependency in dependencies ) {
+				const repositoryURL = dependencies[ dependency ];
+				const urlInfo = git.parseRepositoryUrl( repositoryURL );
+				const repositoryAbsolutePath = path.join( workspaceAbsolutePath, dependency );
 
-		switch ( target ) {
+				// Check if repository's directory already exists.
+				if ( directories.indexOf( dependency ) === -1 ) {
+					try {
+						grunt.log.writeln( `Clonning ${ repositoryURL }...` );
+						git.cloneRepository( urlInfo, workspaceAbsolutePath );
+					} catch ( error ) {
+						grunt.log.error( error );
+					}
+				}
 
-			// grunt dev:init
-			case 'init':
-				tools.initDevWorkspace( workspaceAbsolutePath, ckeditor5Path, grunt.log.writeln );
-				break;
+				// Check out proper branch.
+				try {
+					grunt.log.writeln( `Checking out ${ repositoryURL } to ${ urlInfo.branch }...` );
+					git.checkout( repositoryAbsolutePath, urlInfo.branch );
+				} catch ( error ) {
+					grunt.log.error( error );
+				}
 
-			// grunt dev:status
-			case 'status':
-				tools.getWorkspaceStatus( workspaceAbsolutePath, grunt.log.writeln );
-				break;
+				// Link plugin.
+				try {
+					grunt.log.writeln( `Linking ${ repositoryURL }...` );
+					tools.linkDirectories( repositoryAbsolutePath, path.join( ckeditor5Path, 'node_modules' , dependency ) );
+				} catch ( error ) {
+					grunt.log.error( error );
+				}
+			}
+		} else {
+			grunt.log.writeln( 'No CKEditor5 dependencies found in package.json file.' );
 		}
 	} );
 };

+ 126 - 0
dev/tasks/test/git.js

@@ -0,0 +1,126 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+/* global describe, it, beforeEach, afterEach */
+
+let toRestore;
+const git = require( '../utils/git' );
+const chai = require( 'chai' );
+const sinon = require( 'sinon' );
+const tools = require( '../utils/tools' );
+const expect = chai.expect;
+
+describe( 'utils', () => {
+	beforeEach( () => toRestore = [] );
+
+	afterEach( () => {
+		toRestore.forEach( item => item.restore() );
+	} );
+
+	describe( 'git', () => {
+		describe( 'parseRepositoryUrl', () => {
+			it( 'should be defined', () => expect( git.parseRepositoryUrl ).to.be.a( 'function' ) );
+
+			it( 'should parse short GitHub URL ', () => {
+				const urlInfo = git.parseRepositoryUrl( 'ckeditor/ckeditor5-core' );
+
+				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.branch ).to.equal( 'master' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse short GitHub URL with provided branch ', () => {
+				const urlInfo = git.parseRepositoryUrl( 'ckeditor/ckeditor5-core#experimental' );
+
+				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.branch ).to.equal( 'experimental' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (http)', () => {
+				const urlInfo = git.parseRepositoryUrl( 'http://github.com/ckeditor/ckeditor5-core' );
+
+				expect( urlInfo.server ).to.equal( 'http://github.com/' );
+				expect( urlInfo.branch ).to.equal( 'master' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (http) with provided branch', () => {
+				const urlInfo = git.parseRepositoryUrl( 'http://github.com/ckeditor/ckeditor5-core#experimental' );
+
+				expect( urlInfo.server ).to.equal( 'http://github.com/' );
+				expect( urlInfo.branch ).to.equal( 'experimental' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (https)', () => {
+				const urlInfo = git.parseRepositoryUrl( 'https://github.com/ckeditor/ckeditor5-core' );
+
+				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.branch ).to.equal( 'master' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (https) with provided branch', () => {
+				const urlInfo = git.parseRepositoryUrl( 'https://github.com/ckeditor/ckeditor5-core#t/122' );
+
+				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.branch ).to.equal( 't/122' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (git)', () => {
+				const urlInfo = git.parseRepositoryUrl( 'git@github.com:ckeditor/ckeditor5-core' );
+
+				expect( urlInfo.server ).to.equal( 'git@github.com:' );
+				expect( urlInfo.branch ).to.equal( 'master' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+
+			it( 'should parse full GitHub URL (git) with provided branch', () => {
+				const urlInfo = git.parseRepositoryUrl( 'git@github.com:ckeditor/ckeditor5-core#new-feature' );
+
+				expect( urlInfo.server ).to.equal( 'git@github.com:' );
+				expect( urlInfo.branch ).to.equal( 'new-feature' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
+			} );
+		} );
+
+		describe( 'cloneRepository', () => {
+			it( 'should be defined', () => expect( git.cloneRepository ).to.be.a( 'function' ) );
+
+			it( 'should call clone commands', () => {
+				const shExecStub = sinon.stub( tools, 'shExec' );
+				const workspacePath = '/path/to/workspace/';
+				const urlInfo = git.parseRepositoryUrl( 'git@github.com:ckeditor/ckeditor5-core#new-feature' );
+				const cloneCommands = `cd ${ workspacePath } && git clone ${ urlInfo.server + urlInfo.repository }`;
+				toRestore.push( shExecStub );
+
+				git.cloneRepository( urlInfo, workspacePath );
+
+				expect( shExecStub.calledOnce ).to.equal( true );
+				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( cloneCommands );
+			} );
+		} );
+
+		describe( 'checkout', () => {
+			it( 'should be defined', () => expect( git.checkout ).to.be.a( 'function' ) );
+
+			it( 'should call checkout commands', () => {
+				const shExecStub = sinon.stub( tools, 'shExec' );
+				const repositoryLocation = 'path/to/repository';
+				const branchName = 'branch-to-checkout';
+				const checkoutCommands = `cd ${ repositoryLocation } && git checkout ${ branchName }`;
+				toRestore.push( shExecStub );
+
+				git.checkout( repositoryLocation, branchName );
+
+				expect( shExecStub.calledOnce ).to.equal( true );
+				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( checkoutCommands );
+			} );
+		} );
+	} );
+} );

+ 112 - 149
dev/tasks/test/tools.js

@@ -6,93 +6,59 @@
 'use strict';
 /* global describe, it, beforeEach, afterEach */
 
-var chai = require( 'chai' );
-var sinon = require( 'sinon' );
-var expect = chai.expect;
-var tools = require( '../utils/tools' );
-var toRestore;
-
-describe( 'utils', function() {
-	beforeEach( function() {
-		toRestore = [];
+const chai = require( 'chai' );
+const sinon = require( 'sinon' );
+const expect = chai.expect;
+const tools = require( '../utils/tools' );
+const path = require( 'path' );
+let toRestore;
+
+describe( 'utils', () => {
+	beforeEach( () => toRestore = [] );
+
+	afterEach( () => {
+		toRestore.forEach( item => item.restore() );
 	} );
 
-	afterEach( function() {
-		toRestore.forEach( function( item ) {
-			item.restore();
-		} );
-	} );
+	describe( 'tools', () => {
+		describe( 'linkDirectories', () => {
+			it( 'should be defined', () => expect( tools.linkDirectories ).to.be.a( 'function' ) );
 
-	describe( 'tools', function() {
-		describe( 'cloneRepository', function() {
-			it( 'should be defined', function() {
-				expect( tools.cloneRepository ).to.be.a( 'function' );
-			} );
+			it( 'should run link commands', () => {
+				const shExecStub = sinon.stub( tools, 'shExec' );
+				const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( false );
+				const source = '/source/dir';
+				const destination = '/destination/dir';
+				toRestore.push( shExecStub, isDirectoryStub );
 
-			it( 'should run clone repository commands', function( ) {
-				var shExecStub = sinon.stub( tools, 'shExec' );
-				var name = 'test';
-				var gitHubUrl = 'ckeditor/test';
-				var destination = '/destination/dir';
-				toRestore.push( shExecStub );
+				tools.linkDirectories( source, destination );
 
-				tools.cloneRepository( name, gitHubUrl, destination );
+				expect( isDirectoryStub.calledOnce ).to.equal( true );
 				expect( shExecStub.calledOnce ).to.equal( true );
-				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( 'cd ' + destination + ' && git clone git@github.com:' + gitHubUrl );
+				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( `ln -s ${ source } ${ destination }` );
 			} );
 
-			it( 'should checkout proper commit/branch if provided', function() {
-				var shExecStub = sinon.stub( tools, 'shExec' );
-				var name = 'test';
-				var url = 'ckeditor/test';
-				var branch = 'branch';
-				var gitHubUrl = url + '#' + branch;
-				var destination = '/destination/dir';
-				toRestore.push( shExecStub );
-
-				tools.cloneRepository( name, gitHubUrl, destination );
-				expect( shExecStub.calledOnce ).to.equal( true );
-				expect( shExecStub.firstCall.args[ 0 ] ).to.equal(
-					'cd ' + destination + ' && ' +
-					'git clone git@github.com:' + url + ' && ' +
-					'cd ' + name + ' && ' +
-					'git checkout ' + branch
-				);
-			} );
-		} );
+			it( 'should remove destination directory before linking', () => {
+				const shExecStub = sinon.stub( tools, 'shExec' );
+				const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( true );
+				const source = '/source/dir';
+				const destination = '/destination/dir';
+				toRestore.push( shExecStub, isDirectoryStub );
 
-		describe( 'npmLink', function() {
-			it( 'should be defined', function() {
-				expect( tools.cloneRepository ).to.be.a( 'function' );
-			} );
+				tools.linkDirectories( source, destination );
 
-			it( 'should run npm link commands', function( ) {
-				var shExecStub = sinon.stub( tools, 'shExec' );
-				var source = '/source/dir';
-				var destination = '/destination/dir';
-				var pluginName = 'ckeditor5-plugin-name';
-				var isWin = process.platform == 'win32';
-				var linkCommands = [
-					'cd ' + source,
-					( !isWin ? 'sudo ' : '' ) + 'npm link',
-					'cd ' + destination,
-					'npm link ' + pluginName
-				];
-				toRestore.push( shExecStub );
-
-				tools.npmLink( source, destination, pluginName );
-				expect( shExecStub.calledOnce ).to.equal( true );
-				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( linkCommands.join( ' && ' ) );
+				expect( isDirectoryStub.calledOnce ).to.equal( true );
+				expect( shExecStub.calledTwice ).to.equal( true );
+				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( `rm -rf ${ destination }` );
+				expect( shExecStub.secondCall.args[ 0 ] ).to.equal( `ln -s ${ source } ${ destination }` );
 			} );
 		} );
 
-		describe( 'getCKEditorDependencies', function() {
-			it( 'should be defined', function() {
-				expect( tools.getCKEditorDependencies ).to.be.a( 'function' );
-			} );
+		describe( 'getCKEditorDependencies', () => {
+			it( 'should be defined', () => expect( tools.getCKEditorDependencies ).to.be.a( 'function' ) );
 
-			it( 'should return null if no CKEditor5 repository is found', function() {
-				var dependencies = {
+			it( 'should return null if no CKEditor5 repository is found', () => {
+				const dependencies = {
 					'plugin1': '',
 					'plugin2': '',
 					'plugin3': ''
@@ -100,15 +66,14 @@ describe( 'utils', function() {
 				expect( tools.getCKEditorDependencies( dependencies ) ).to.equal( null );
 			} );
 
-			it( 'should return only ckeditor5- dependencies', function() {
-				var dependencies = {
+			it( 'should return only ckeditor5- dependencies', () => {
+				const dependencies = {
 					'plugin1': '',
 					'ckeditor5-plugin-image': 'ckeditor/ckeditor5-plugin-image',
 					'plugin2': '',
 					'ckeditor5-core': 'ckeditor/ckeditor5-core'
 				};
-
-				var ckeditorDependencies = tools.getCKEditorDependencies( dependencies );
+				const ckeditorDependencies = tools.getCKEditorDependencies( dependencies );
 
 				expect( ckeditorDependencies ).to.be.an( 'object' );
 				expect( ckeditorDependencies.plugin1 ).to.be.a( 'undefined' );
@@ -118,85 +83,83 @@ describe( 'utils', function() {
 			} );
 		} );
 
-		describe( 'getCKE5Directories', function() {
-			it( 'should return only ckeditor5 directories', function() {
-				var workspacePath = '/workspace/path';
-				var getDirectoriesStub = sinon.stub( tools, 'getDirectories', function() {
-					return [ 'tools', 'ckeditor5', 'ckeditor5-core', '.bin', 'ckeditor5-plugin-image' ];
-				} );
-				toRestore.push( getDirectoriesStub );
-				var directories = tools.getCKE5Directories( workspacePath );
+		describe( 'getDirectories', () => {
+			it( 'should be defined', () => expect( tools.getDirectories ).to.be.a( 'function' ) );
+
+			it( 'should get directories in specified path', () => {
+				const fs = require( 'fs' );
+				const directories = [ 'dir1', 'dir2', 'dir3' ];
+				const readdirSyncStub = sinon.stub( fs, 'readdirSync', () => directories );
+				const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( true );
+				const dirPath = 'path';
+				toRestore.push( readdirSyncStub, isDirectoryStub );
+
+				tools.getDirectories( dirPath );
 
-				expect( directories.length ).equal( 3 );
-				expect( directories[ 0 ] ).equal( 'ckeditor5' );
-				expect( directories[ 1 ] ).equal( 'ckeditor5-core' );
-				expect( directories[ 2 ] ).equal( 'ckeditor5-plugin-image' );
+				expect( readdirSyncStub.calledOnce ).to.equal( true );
+				expect( isDirectoryStub.calledThrice ).to.equal( true );
+				expect( isDirectoryStub.firstCall.args[ 0 ] ).to.equal( path.join( dirPath, directories[ 0 ] ) );
+				expect( isDirectoryStub.secondCall.args[ 0 ] ).to.equal( path.join( dirPath, directories[ 1 ] ) );
+				expect( isDirectoryStub.thirdCall.args[ 0 ] ).to.equal( path.join( dirPath, directories[ 2 ] ) );
 			} );
 		} );
 
-		describe( 'initDevWorkspace', function() {
-			it( 'should get ckeditor5- dependencies, clone repositories and link them', function() {
-				var path = require( 'path' );
-				var getDependenciesSpy = sinon.spy( tools, 'getCKEditorDependencies' );
-				var cloneRepositoryStub = sinon.stub( tools, 'cloneRepository' );
-				var npmLinkStub = sinon.stub( tools, 'npmLink' );
-				var ckeditor5Path = process.cwd();
-				var workspacePath = path.join( ckeditor5Path, '..' );
-				var dependencies, keys;
-				toRestore.push( getDependenciesSpy, cloneRepositoryStub, npmLinkStub );
-
-				tools.initDevWorkspace( workspacePath, ckeditor5Path, function() {} );
-				expect( getDependenciesSpy.calledOnce ).to.equal( true );
-				dependencies = getDependenciesSpy.firstCall.returnValue;
-
-				if ( dependencies ) {
-					keys = Object.keys( dependencies );
-
-					// All repositories were cloned.
-					expect( cloneRepositoryStub.callCount ).to.equal( keys.length );
-
-					// All repositories were linked.
-					expect( npmLinkStub.callCount ).to.equal( keys.length );
-
-					// Check clone and link parameters.
-					keys.forEach( function( key, i ) {
-						expect( cloneRepositoryStub.getCall( i ).args[0] ).equal( key );
-						expect( cloneRepositoryStub.getCall( i ).args[1] ).equal( dependencies[ key ] );
-						expect( cloneRepositoryStub.getCall( i ).args[2] ).equal( workspacePath );
-
-						expect( npmLinkStub.getCall( i ).args[0] ).equal( path.join( workspacePath, key ) );
-						expect( npmLinkStub.getCall( i ).args[1] ).equal( ckeditor5Path );
-						expect( npmLinkStub.getCall( i ).args[2] ).equal( key );
-					} );
-				}
+		describe( 'isDirectory', () => {
+			it( 'should be defined', () => expect( tools.isDirectory ).to.be.a( 'function' ) );
+
+			it( 'should return true if path points to directory', () => {
+				const fs = require( 'fs' );
+				const statSyncStub = sinon.stub( fs, 'statSync', () => ( { isDirectory: () => true } ) );
+				const path = 'path';
+				toRestore.push( statSyncStub );
+
+				const result = tools.isDirectory( path );
+
+				expect( statSyncStub.calledOnce ).to.equal( true );
+				expect( statSyncStub.firstCall.args[ 0 ] ).to.equal( path );
+				expect( result ).to.equal( true );
+			} );
+
+			it( 'should return false if path does not point to directory', () => {
+				const fs = require( 'fs' );
+				const statSyncStub = sinon.stub( fs, 'statSync', () => ( { isDirectory: () => false } ) );
+				const path = 'path';
+				toRestore.push( statSyncStub );
+
+				const result = tools.isDirectory( path );
+
+				expect( statSyncStub.calledOnce ).to.equal( true );
+				expect( statSyncStub.firstCall.args[ 0 ] ).to.equal( path );
+				expect( result ).to.equal( false );
+			} );
+
+			it( 'should return false if statSync method throws', () => {
+				const fs = require( 'fs' );
+				const statSyncStub = sinon.stub( fs, 'statSync' ).throws();
+				const path = 'path';
+				toRestore.push( statSyncStub );
+
+				const result = tools.isDirectory( path );
+
+				expect( statSyncStub.calledOnce ).to.equal( true );
+				expect( statSyncStub.firstCall.args[ 0 ] ).to.equal( path );
+				expect( result ).to.equal( false );
 			} );
 		} );
 
-		describe( 'getWorkspaceStatus', function() {
-			it( 'should get all repositories status', function() {
-				var path = require( 'path' );
-				var workspacePath = '/workspace/path/';
-				var directories = [ 'ckeditor5', 'ckeditor5-core' ];
-				var log = sinon.spy();
-
-				// Stub methods for test purposes.
-				var getCKE5DirectoriesStub = sinon.stub( tools, 'getCKE5Directories', function() {
-					return directories;
-				} );
-				var getGitStatusStub = sinon.stub( tools, 'getGitStatus', function() {
-					return 'status';
-				} );
-				toRestore.push( getCKE5DirectoriesStub, getGitStatusStub );
-
-				tools.getWorkspaceStatus( workspacePath, log );
-				expect( getCKE5DirectoriesStub.calledOnce ).equal( true );
-				expect( log.callCount ).equal( directories.length );
-				expect( getGitStatusStub.callCount ).equal( directories.length );
-
-				// Check if status was called for proper directory.
-				for ( var i = 0; i < getGitStatusStub.callCount; i++ ) {
-					expect( getGitStatusStub.getCall( i ).args[0] ).equals( path.join( workspacePath, directories[ i ] ) );
-				}
+		describe( 'getCKE5Directories', () => {
+			it( 'should be defined', () => expect( tools.getCKE5Directories ).to.be.a( 'function' ) );
+
+			it( 'should return only ckeditor5 directories', () => {
+				const workspacePath = '/workspace/path';
+				const sourceDirectories = [ 'tools', 'ckeditor5', 'ckeditor5-core', '.bin', 'ckeditor5-plugin-image' ];
+				const getDirectoriesStub = sinon.stub( tools, 'getDirectories', () => sourceDirectories );
+				toRestore.push( getDirectoriesStub );
+				const directories = tools.getCKE5Directories( workspacePath );
+
+				expect( directories.length ).equal( 2 );
+				expect( directories[ 0 ] ).equal( 'ckeditor5-core' );
+				expect( directories[ 1 ] ).equal( 'ckeditor5-plugin-image' );
 			} );
 		} );
 	} );

+ 75 - 0
dev/tasks/utils/git.js

@@ -0,0 +1,75 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const tools = require( './tools' );
+
+module.exports = {
+	/**
+	 * Parses GitHub URL. Extracts used server, repository and branch.
+	 *
+	 * @param {String} url GitHub URL from package.json file.
+	 * @returns {Object} urlInfo
+	 * @returns {String} urlInfo.server
+	 * @returns {String} urlInfo.repository
+	 * @returns {String} urlInfo.branch
+	 */
+	parseRepositoryUrl( url ) {
+		const regexp = /^(git@github\.com:|https?:\/\/github.com\/)?([^#]+)(?:#)?(.*)$/;
+		const match = url.match( regexp );
+		let server;
+		let repository;
+		let branch;
+
+		if ( !match ) {
+			return null;
+		}
+
+		server = match[ 1 ] || 'https://github.com/';
+		repository = match[ 2 ] || '';
+		branch = match[ 3 ] || 'master';
+
+		if ( !repository ) {
+			return null;
+		}
+
+		return {
+			server: server,
+			repository: repository,
+			branch: branch
+		};
+	},
+
+	/**
+	 * Clones repository to workspace.
+	 *
+	 * @param {Object} urlInfo Parsed URL object from {@link #parseRepositoryUrl}.
+	 * @param {String} workspacePath Path to the workspace location where repository will be cloned.
+	 */
+	cloneRepository( urlInfo, workspacePath ) {
+		const cloneCommands = [
+			`cd ${ workspacePath }`,
+			`git clone ${ urlInfo.server + urlInfo.repository }`
+		];
+
+		tools.shExec( cloneCommands.join( ' && ' ) );
+	},
+
+	/**
+	 * Checks out branch on selected repository.
+	 *
+	 * @param {String} repositoryLocation Absolute path to repository.
+	 * @param {String} branchName Name of the branch to checkout.
+	 */
+	checkout( repositoryLocation, branchName ) {
+		const checkoutCommands = [
+			`cd ${ repositoryLocation }`,
+			`git checkout ${ branchName }`
+		];
+
+		tools.shExec( checkoutCommands.join( ' && ' ) );
+	}
+};

+ 39 - 125
dev/tasks/utils/tools.js

@@ -1,10 +1,9 @@
 'use strict';
 
-var dirtyFiles,
+let dirtyFiles,
 	ignoreList;
 
-var repositoryRegExp = /^(ckeditor\/[^#]+)(?:#)?(.*)/;
-var directoryRegExp = /^ckeditor5/;
+const dependencyRegExp = /^ckeditor5-/;
 
 module.exports = {
 	/**
@@ -14,7 +13,7 @@ module.exports = {
 	 * @param task {String} The task name. May optionally include the target (e.g. 'task:target').
 	 * @returns {Boolean} "true" if the task is in the queue.
 	 */
-	checkTaskInQueue: function( grunt, task ) {
+	checkTaskInQueue( grunt, task ) {
 		var cliTasks = grunt.cli.tasks;
 
 		// Check if the task has been called directly.
@@ -33,7 +32,7 @@ module.exports = {
 	 * @param grunt {Object} The Grunt object.
 	 * @param options {Object} A list of options for the method. See the jscs and jshint tasks for example.
 	 */
-	setupMultitaskConfig: function( grunt, options ) {
+	setupMultitaskConfig( grunt, options ) {
 		var task = options.task;
 		var taskConfig = {};
 		var config = taskConfig[ task ] = {
@@ -76,7 +75,7 @@ module.exports = {
 	 * @param grunt {Object} The Grunt object.
 	 * @returns {String[]} The list of ignores.
 	 */
-	getGitIgnore: function( grunt ) {
+	getGitIgnore( grunt ) {
 		if ( !ignoreList ) {
 			ignoreList = grunt.file.read( '.gitignore' );
 
@@ -99,7 +98,7 @@ module.exports = {
 	 *
 	 * @returns {String[]} A list of file paths.
 	 */
-	getGitDirtyFiles: function() {
+	getGitDirtyFiles() {
 		// Cache it, so it is executed only once when running multiple tasks.
 		if ( !dirtyFiles ) {
 			dirtyFiles = this
@@ -125,11 +124,11 @@ module.exports = {
 	 * @param command {String} The command to be executed.
 	 * @returns {String} The command output.
 	 */
-	shExec: function( command ) {
-		var sh = require( 'shelljs' );
+	shExec( command ) {
+		const sh = require( 'shelljs' );
 		sh.config.silent = true;
 
-		var ret = sh.exec( command );
+		const ret = sh.exec( command );
 
 		if ( ret.code ) {
 			throw new Error(
@@ -142,50 +141,17 @@ module.exports = {
 	},
 
 	/**
-	 * Links repository located in source path to repository located in destination path. Uses npm link.
-	 *
-	 * @param {String} sourcePath
-	 * @param {String} destinationPath
-	 * @param {String} pluginName
-	 */
-	npmLink: function( sourcePath, destinationPath, pluginName ) {
-		// Don't use sudo on windows when executing npm link.
-		var isWin = process.platform == 'win32';
-		var linkCommands = [
-			'cd ' + sourcePath,
-			( !isWin ? 'sudo ' : '' ) + 'npm link',
-			'cd ' + destinationPath,
-			'npm link ' + pluginName
-		];
-
-		module.exports.shExec( linkCommands.join( ' && ' ) );
-	},
-
-	/**
-	 * Clones repository from provided GitHub URL. Only short GitHub urls are supported that starts with 'ckeditor/'.
-	 * https://docs.npmjs.com/files/package.json#github-urls
-	 *
-	 * @param {String} name Repository name.
-	 * @param {String} gitHubUrl GitHub url to repository.
-	 * @param {String} location Destination path.
+	 * Links directory located in source path to directory located in destination path using `ln -s` command.
+	 * @param {String} source
+	 * @param {String} destination
 	 */
-	cloneRepository: function( name, gitHubUrl, location ) {
-		var match = gitHubUrl.match( repositoryRegExp );
-
-		if ( match && match[ 1 ] )  {
-			var cloneCommands = [
-				'cd ' + location,
-				'git clone git@github.com:' + match[ 1 ]
-			];
-
-			// If commit-ish suffix is included - run git checkout.
-			if ( match[ 2 ] ) {
-				cloneCommands.push( 'cd ' + name );
-				cloneCommands.push( 'git checkout ' + match[ 2 ] );
-			}
-
-			module.exports.shExec( cloneCommands.join( ' && ' ) );
+	linkDirectories( source, destination ) {
+		// Remove destination directory if exists.
+		if ( this.isDirectory( destination ) ) {
+			this.shExec( `rm -rf ${ destination }` );
 		}
+
+		this.shExec( `ln -s ${ source } ${ destination }` );
 	},
 
 	/**
@@ -195,13 +161,12 @@ module.exports = {
 	 * @param {Object} dependencies Dependencies object loaded from package.json file.
 	 * @returns {Object|null}
 	 */
-	getCKEditorDependencies: function( dependencies ) {
-		var result = null;
-		var regexp = /^ckeditor5-/;
+	getCKEditorDependencies( dependencies ) {
+		let result = null;
 
 		if ( dependencies ) {
 			Object.keys( dependencies ).forEach( function( key ) {
-				if ( regexp.test( key ) && repositoryRegExp.test( dependencies[ key ] ) ) {
+				if ( dependencyRegExp.test( key ) ) {
 					if ( result === null ) {
 						result = {};
 					}
@@ -220,90 +185,39 @@ module.exports = {
 	 * @param {String} path
 	 * @returns {Array}
 	 */
-	getDirectories: function( path ) {
-		var fs = require( 'fs' );
-		var pth = require( 'path' );
+	getDirectories( path ) {
+		const fs = require( 'fs' );
+		const pth = require( 'path' );
 
-		return fs.readdirSync( path ).filter( function( item ) {
-			return fs.statSync( pth.join( path, item ) ).isDirectory();
+		return fs.readdirSync( path ).filter( item => {
+			return this.isDirectory( pth.join( path, item ) );
 		} );
 	},
 
 	/**
-	 * Returns all directories under specified path that match 'ckeditor5' pattern.
-	 *
+	 * Returns true if path points to existing directory.
 	 * @param {String} path
-	 * @returns {Array}
-	 */
-	getCKE5Directories: function( path ) {
-		return module.exports.getDirectories( path ).filter( function( dir ) {
-			return directoryRegExp.test( dir );
-		} );
-	},
-
-	/**
-	 * Returns git status --porcelain -sb executed under specified path.
-	 *
-	 * @param {String} path Path where git status will be executed.
-	 * @returns {String|null}
+	 * @returns {Boolean}
 	 */
-	getGitStatus: function( path ) {
-		var exec = module.exports.shExec;
+	isDirectory( path ) {
+		var fs = require( 'fs' );
 
 		try {
-			return exec( 'cd ' + path + ' && git status --porcelain -sb' ).trim();
-		} catch ( e ) {	}
-
-		return null;
-	},
-
-	/**
-	 * Initializes development workspace. Takes CKEditor5 dependencies, clones them and npm links to the main CKEditor5
-	 * repository.
-	 *
-	 * @param {String} workspacePath Absolute path to the workspace where all repositories will be cloned.
-	 * @param {String} ckeditor5Path Absolute path to the CKEditor5 repository where all dependencies will be linked.
-	 * @param {Function} log Log function used to report progress.
-	 */
-	initDevWorkspace: function( workspacePath, ckeditor5Path, log ) {
-		var tools = module.exports;
-		var path = require( 'path' );
-		var packageJSON = require( path.join( ckeditor5Path, 'package.json' ) );
-		var pluginPath;
-
-		// Get only CKEditor dependencies.
-		var dependencies = tools.getCKEditorDependencies( packageJSON.dependencies );
-
-		if ( dependencies ) {
-			Object.keys( dependencies ).forEach( function( name ) {
-				log( 'Clonning repository ' + dependencies[ name ] + '...' );
-				tools.cloneRepository( name, dependencies[ name ], workspacePath );
+			return fs.statSync( path ).isDirectory();
+		} catch ( e ) {}
 
-				pluginPath = path.join( workspacePath, name );
-				log( 'Linking ' + pluginPath + ' into ' + ckeditor5Path + '...' );
-				tools.npmLink( pluginPath, ckeditor5Path, name );
-			} );
-		}
+		return false;
 	},
 
 	/**
-	 * Returns git status from all CKEditor repositories from workspace.
+	 * Returns all directories under specified path that match 'ckeditor5' pattern.
 	 *
-	 * @param {String} workspacePath Absolute path to the workspace containing repositories.
-	 * @param {Function} log Log function used to output status information.
+	 * @param {String} path
+	 * @returns {Array}
 	 */
-	getWorkspaceStatus: function( workspacePath, log ) {
-		var tools = module.exports;
-		var path = require( 'path' );
-		var directories = tools.getCKE5Directories( workspacePath );
-
-		directories.forEach( function( directory ) {
-			var location = path.join( workspacePath, directory );
-			var data = tools.getGitStatus( location );
-
-			if ( data ) {
-				log( '\x1b[1m' , '\x1b[36m', directory, '\x1b[0m\n', data );
-			}
+	getCKE5Directories( path ) {
+		return this.getDirectories( path ).filter( dir => {
+			return dependencyRegExp.test( dir );
 		} );
 	}
 };