瀏覽代碼

Added dev-install task.

Szymon Kupś 10 年之前
父節點
當前提交
8345f906f4
共有 8 個文件被更改,包括 324 次插入20 次删除
  1. 5 0
      dev/tasks/dev.js
  2. 73 0
      dev/tasks/utils/dev-install.js
  3. 15 5
      dev/tasks/utils/git.js
  4. 24 0
      dev/tasks/utils/tools.js
  5. 131 0
      dev/tests/dev-install.js
  6. 30 14
      dev/tests/git.js
  7. 43 0
      dev/tests/tools.js
  8. 3 1
      package.json

+ 5 - 0
dev/tasks/dev.js

@@ -10,6 +10,7 @@ const pluginCreateTask = require( './utils/dev-plugin-create' );
 const pluginInstallTask = require( './utils/dev-plugin-install' );
 const pluginUpdateTask = require( './utils/dev-update' );
 const pluginStatusTask = require( './utils/dev-status' );
+const installTask = require( './utils/dev-install' );
 const relinkTask = require( './utils/dev-relink' );
 const boilerplateUpdateTask = require( './utils/dev-boilerplate-update' );
 const ckeditor5Path = process.cwd();
@@ -51,5 +52,9 @@ module.exports = ( grunt ) => {
 	grunt.registerTask( 'dev-relink', function() {
 		relinkTask( ckeditor5Path, packageJSON, workspaceRoot, grunt.log.writeln, grunt.log.error );
 	} );
+
+	grunt.registerTask( 'dev-install', function( ) {
+		installTask( ckeditor5Path, workspaceRoot, grunt.option( 'plugin' ), grunt.log.writeln );
+	} );
 };
 

+ 73 - 0
dev/tasks/utils/dev-install.js

@@ -0,0 +1,73 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const git = require( './git' );
+const tools = require( './tools' );
+const path = require( 'path' );
+
+/**
+ * This tasks install specified module in development mode. It can be executed by typing:
+ * 		grunt dev-install --plugin <npm_name|git_hub_url>
+ *
+ * It performs follwing steps:
+ * 1. Get GitHub URL from NPM if module name is provided.
+ * 2. Checks if repository is cloned already. If not - clones it.
+ * 3. Checks out plugin repository to provided branch (`master` if no branch is specified).
+ * 4. Links plugin directory into `ckeditor5/node_modules/`.
+ * 5. Adds dependency with local path to `ckeditor5/package.json`.
+ * 6. Runs `npm install` in `ckeditor5/`.
+ *
+ * @param {String} ckeditor5Path Absolute path to `ckeditor5` repository.
+ * @param {String} workspaceRoot Relative path to workspace root directory.
+ * @param {String} name Name of the NPM module or GitHub URL.
+ * @param {Function} writeln Function used to report progress to the console.
+ */
+module.exports = ( ckeditor5Path, workspaceRoot, name, writeln ) => {
+	let urlInfo = git.parseRepositoryUrl( name );
+	const workspaceAbsolutePath = path.join( ckeditor5Path, workspaceRoot );
+	let repositoryPath;
+
+	if ( !urlInfo ) {
+		writeln( `Not a GitHub URL. Trying to get GitHub URL from npm package...` );
+		const url = tools.getGitUrlFromNpm( name );
+
+		if ( url ) {
+			urlInfo = git.parseRepositoryUrl( url );
+		}
+	}
+
+	if ( urlInfo ) {
+		repositoryPath = path.join( workspaceAbsolutePath, urlInfo.name );
+
+		if ( tools.isDirectory( repositoryPath ) ) {
+			writeln( `Directory ${ repositoryPath } already exists.` );
+		} else {
+			writeln( `Cloning ${ urlInfo.name } into ${ repositoryPath }... ` );
+			git.cloneRepository( urlInfo, workspaceAbsolutePath );
+		}
+
+		writeln( `Checking ${ urlInfo.name } to ${ urlInfo.branch }...` );
+		git.checkout( repositoryPath, urlInfo.branch );
+
+		const linkPath = path.join( ckeditor5Path, 'node_modules', urlInfo.name );
+		writeln( `Linking ${ linkPath } to ${ repositoryPath }...` );
+		tools.linkDirectories( repositoryPath, linkPath );
+
+		writeln( `Adding ${ urlInfo.name } dependency to CKEditor5 package.json... ` );
+		tools.updateJSONFile( path.join( ckeditor5Path, 'package.json' ), ( json ) => {
+			json.dependencies = json.dependencies || {};
+			json.dependencies[ urlInfo.name ] = repositoryPath;
+
+			return json;
+		} );
+
+		writeln( 'Running "npm install" in CKEditor5 repository...' );
+		tools.npmInstall( ckeditor5Path );
+	} else {
+		throw new Error( 'Please provide valid GitHub URL or npm module name.' );
+	}
+};

+ 15 - 5
dev/tasks/utils/git.js

@@ -33,31 +33,41 @@ module.exports = {
 	 * @returns {Object} urlInfo
 	 * @returns {String} urlInfo.server
 	 * @returns {String} urlInfo.repository
+	 * @returns {String} urlInfo.user
+	 * @returns {String} urlInfo.name
 	 * @returns {String} urlInfo.branch
 	 */
 	parseRepositoryUrl( url ) {
-		const regexp = /^(git@github\.com:|https?:\/\/github\.com\/)?([^#]+)(?:#)?(.*)$/;
+		const regexp = /^((?:git@|http[s]?:\/\/)github\.com(?:\/|:))?(([\w-]+)\/([\w-]+(?:\.git)?))(?:#([\w-/]+))?$/;
 		const match = url.match( regexp );
 		let server;
 		let repository;
 		let branch;
+		let name;
+		let user;
 
 		if ( !match ) {
 			return null;
 		}
 
 		server = match[ 1 ] || 'https://github.com/';
-		repository = match[ 2 ] || '';
-		branch = match[ 3 ] || 'master';
+		repository = match[ 2 ];
+		user = match[ 3 ] || '';
+		name = match[ 4 ] || '';
+		branch = match[ 5 ] || 'master';
 
-		if ( !repository ) {
+		if ( !repository || !user || !name ) {
 			return null;
 		}
 
+		name = /\.git$/.test( name ) ? name.slice( 0, -4 ) : name;
+
 		return {
 			server: server,
 			repository: repository,
-			branch: branch
+			branch: branch,
+			user: user,
+			name: name
 		};
 	},
 

+ 24 - 0
dev/tasks/utils/tools.js

@@ -266,5 +266,29 @@ module.exports = {
 		const path = require( 'path' );
 		const templatesPath = path.resolve( TEMPLATE_PATH );
 		this.shExec( `cp ${ path.join( templatesPath, '*.md' ) } ${ destination }` );
+	},
+
+	/**
+	 * Executes 'npm view' command for provided module name and returns Git url if one is found. Returns null if
+	 * module cannot be found.
+	 *
+	 * @param {String} name Name of the module.
+	 * @returns {*}
+     */
+	getGitUrlFromNpm( name ) {
+		try {
+			const info = JSON.parse( this.shExec( `npm view ${ name } repository --json` ) );
+
+			if ( info && info.type == 'git' ) {
+				return info.url;
+			}
+		} catch ( error ) {
+			// Throw error only when different than E404.
+			if ( error.message.indexOf( 'npm ERR! code E404' ) == -1 ) {
+				throw error;
+			}
+		}
+
+		return null;
 	}
 };

+ 131 - 0
dev/tests/dev-install.js

@@ -0,0 +1,131 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global describe, it, beforeEach, afterEach */
+
+'use strict';
+
+const chai = require( 'chai' );
+const sinon = require( 'sinon' );
+const git = require( '../tasks/utils/git' );
+const tools = require( '../tasks/utils/tools' );
+const installTask = require( '../tasks/utils/dev-install' );
+const expect = chai.expect;
+const path = require( 'path' );
+
+describe( 'dev-install', () => {
+	const moduleName = 'ckeditor5-core';
+	const repositoryUrl = 'git@github.com:ckeditor/ckeditor5-core';
+	const ckeditor5Path = '/path/to/ckeditor';
+	const workspacePath = '..';
+	const workspaceAbsolutePath = path.join( ckeditor5Path, workspacePath );
+
+	let toRestore;
+	beforeEach( () => toRestore = [] );
+
+	afterEach( () => {
+		toRestore.forEach( item => item.restore() );
+	} );
+
+	it( 'should use GitHub url if provided', () => {
+		const parseUrlSpy = sinon.spy( git, 'parseRepositoryUrl' );
+		const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( false );
+		const cloneRepositoryStub = sinon.stub( git, 'cloneRepository' );
+		const linkDirectoriesStub = sinon.stub( tools, 'linkDirectories' );
+		const updateJSONstub = sinon.stub( tools, 'updateJSONFile' );
+		const npmInstallStub = sinon.stub( tools, 'npmInstall' );
+		const checkoutStub = sinon.stub( git, 'checkout' );
+
+		toRestore.push( parseUrlSpy, isDirectoryStub, cloneRepositoryStub, linkDirectoriesStub, updateJSONstub,
+						npmInstallStub, checkoutStub );
+
+		installTask( ckeditor5Path, workspacePath, repositoryUrl, () => {}, () => {} );
+
+		sinon.assert.calledOnce( parseUrlSpy );
+		sinon.assert.calledWithExactly( parseUrlSpy, repositoryUrl );
+
+		const urlInfo = parseUrlSpy.firstCall.returnValue;
+		const repositoryPath = path.join( workspaceAbsolutePath, urlInfo.name );
+
+		sinon.assert.calledOnce( isDirectoryStub );
+		sinon.assert.calledWithExactly( isDirectoryStub, repositoryPath );
+
+		sinon.assert.calledOnce( cloneRepositoryStub );
+		sinon.assert.calledWithExactly( cloneRepositoryStub, urlInfo, workspaceAbsolutePath );
+
+		sinon.assert.calledOnce( checkoutStub );
+		sinon.assert.calledWithExactly( checkoutStub, repositoryPath, urlInfo.branch );
+
+		const linkPath = path.join( ckeditor5Path, 'node_modules', urlInfo.name );
+
+		sinon.assert.calledOnce( linkDirectoriesStub );
+		sinon.assert.calledWithExactly( linkDirectoriesStub, repositoryPath, linkPath );
+
+		const packageJsonPath = path.join( ckeditor5Path, 'package.json' );
+		sinon.assert.calledOnce( updateJSONstub );
+		expect( updateJSONstub.firstCall.args[ 0 ] ).to.equal( packageJsonPath );
+		const updateFn = updateJSONstub.firstCall.args[ 1 ];
+		const json = updateFn( {} );
+		expect( json.dependencies ).to.be.a( 'object' );
+		expect( json.dependencies[ urlInfo.name ] ).to.equal( repositoryPath );
+
+		sinon.assert.calledOnce( npmInstallStub );
+		sinon.assert.calledWithExactly( npmInstallStub, ckeditor5Path );
+	} );
+
+	it( 'should use npm module name if provided', () => {
+		const parseUrlSpy = sinon.spy( git, 'parseRepositoryUrl' );
+		const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( true );
+		const getUrlFromNpmSpy = sinon.stub( tools, 'getGitUrlFromNpm' ).returns( repositoryUrl );
+		const cloneRepositoryStub = sinon.stub( git, 'cloneRepository' );
+		const linkDirectoriesStub = sinon.stub( tools, 'linkDirectories' );
+		const updateJSONstub = sinon.stub( tools, 'updateJSONFile' );
+		const npmInstallStub = sinon.stub( tools, 'npmInstall' );
+		const checkoutStub = sinon.stub( git, 'checkout' );
+
+		toRestore.push( parseUrlSpy, isDirectoryStub, getUrlFromNpmSpy, cloneRepositoryStub, linkDirectoriesStub,
+						updateJSONstub, npmInstallStub, checkoutStub );
+
+		installTask( ckeditor5Path, workspacePath, moduleName, () => {}, () => {} );
+
+		sinon.assert.calledTwice( parseUrlSpy );
+		sinon.assert.calledWithExactly( parseUrlSpy.firstCall, moduleName );
+		expect( parseUrlSpy.firstCall.returnValue ).to.equal( null );
+
+		sinon.assert.calledOnce( getUrlFromNpmSpy );
+		sinon.assert.calledWithExactly( getUrlFromNpmSpy, moduleName );
+
+		sinon.assert.calledWithExactly( parseUrlSpy.secondCall, repositoryUrl );
+		const urlInfo = parseUrlSpy.secondCall.returnValue;
+		const repositoryPath = path.join( workspaceAbsolutePath, urlInfo.name );
+
+		sinon.assert.calledOnce( isDirectoryStub );
+		sinon.assert.calledWithExactly( isDirectoryStub, repositoryPath );
+
+		sinon.assert.notCalled( cloneRepositoryStub );
+	} );
+
+	it( 'should throw an exception when invalid name is provided', () => {
+		const parseUrlSpy = sinon.spy( git, 'parseRepositoryUrl' );
+		const isDirectoryStub = sinon.stub( tools, 'isDirectory' ).returns( true );
+		const getUrlFromNpmSpy = sinon.stub( tools, 'getGitUrlFromNpm' ).returns( null );
+		const cloneRepositoryStub = sinon.stub( git, 'cloneRepository' );
+		const linkDirectoriesStub = sinon.stub( tools, 'linkDirectories' );
+		const updateJSONstub = sinon.stub( tools, 'updateJSONFile' );
+		const npmInstallStub = sinon.stub( tools, 'npmInstall' );
+		const checkoutStub = sinon.stub( git, 'checkout' );
+
+		toRestore.push( parseUrlSpy, isDirectoryStub, getUrlFromNpmSpy, cloneRepositoryStub, linkDirectoriesStub,
+			updateJSONstub, npmInstallStub, checkoutStub );
+
+		expect( () => {
+			installTask( ckeditor5Path, workspacePath, moduleName, () => {}, () => {} );
+		} ).to.throw();
+
+		sinon.assert.calledOnce( parseUrlSpy );
+		sinon.assert.calledWithExactly( parseUrlSpy.firstCall, moduleName );
+		expect( parseUrlSpy.firstCall.returnValue ).to.equal( null );
+	} );
+} );

+ 30 - 14
dev/tests/git.js

@@ -29,64 +29,80 @@ describe( 'utils', () => {
 				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' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
+				expect( urlInfo.branch ).to.equal( 'master' );
 			} );
 
 			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' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
+				expect( urlInfo.branch ).to.equal( 'experimental' );
 			} );
 
 			it( 'should parse full GitHub URL (http)', () => {
-				const urlInfo = git.parseRepositoryUrl( 'http://github.com/ckeditor/ckeditor5-core' );
+				const urlInfo = git.parseRepositoryUrl( 'http://github.com/ckeditor/ckeditor5-core.git' );
 
 				expect( urlInfo.server ).to.equal( 'http://github.com/' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				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' );
+				const urlInfo = git.parseRepositoryUrl( 'http://github.com/ckeditor/ckeditor5-core.git#experimental' );
 
 				expect( urlInfo.server ).to.equal( 'http://github.com/' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				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' );
+				const urlInfo = git.parseRepositoryUrl( 'https://github.com/ckeditor/ckeditor5-core.git' );
 
 				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				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' );
+				const urlInfo = git.parseRepositoryUrl( 'https://github.com/ckeditor/ckeditor5-core.git#t/122' );
 
 				expect( urlInfo.server ).to.equal( 'https://github.com/' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				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' );
+				const urlInfo = git.parseRepositoryUrl( 'git@github.com:ckeditor/ckeditor5-core.git' );
 
 				expect( urlInfo.server ).to.equal( 'git@github.com:' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				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' );
+				const urlInfo = git.parseRepositoryUrl( 'git@github.com:ckeditor/ckeditor5-core.git#new-feature' );
 
 				expect( urlInfo.server ).to.equal( 'git@github.com:' );
+				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core.git' );
+				expect( urlInfo.user ).to.equal( 'ckeditor' );
+				expect( urlInfo.name ).to.equal( 'ckeditor5-core' );
 				expect( urlInfo.branch ).to.equal( 'new-feature' );
-				expect( urlInfo.repository ).to.equal( 'ckeditor/ckeditor5-core' );
 			} );
 		} );
 

+ 43 - 0
dev/tests/tools.js

@@ -233,5 +233,48 @@ describe( 'utils', () => {
 				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( `cp ${ path.join( templatesPath, '*.md' ) } ${ repositoryPath }` );
 			} );
 		} );
+
+		describe( 'getGitUrlFromNpm', () => {
+			const repository = {
+				type: 'git',
+				url: 'git@github.com:ckeditor/ckeditor5-core'
+			};
+			const moduleName = 'ckeditor5-core';
+
+			it( 'should be defined', () => expect( tools.getGitUrlFromNpm ).to.be.a( 'function' ) );
+			it( 'should call npm view command', () => {
+				const shExecStub = sinon.stub( tools, 'shExec', () => {
+					return JSON.stringify( repository );
+				} );
+				toRestore.push( shExecStub );
+				const url = tools.getGitUrlFromNpm( moduleName );
+
+				expect( shExecStub.calledOnce ).to.equal( true );
+				expect( shExecStub.firstCall.args[ 0 ] ).to.equal( `npm view ${ moduleName } repository --json` );
+				expect( url ).to.equal( repository.url );
+			} );
+
+			it( 'should return null if module is not found', () => {
+				const shExecStub = sinon.stub( tools, 'shExec' ).throws( new Error( 'npm ERR! code E404' ) );
+				toRestore.push( shExecStub );
+
+				const url = tools.getGitUrlFromNpm( moduleName );
+				expect( url ).to.equal( null );
+			} );
+
+			it( 'should throw on other errors', () => {
+				const error = new Error( 'Random error.' );
+				const shExecStub = sinon.stub( tools, 'shExec' ).throws( error );
+				const getUrlSpy = sinon.spy( tools, 'getGitUrlFromNpm' );
+				toRestore.push( shExecStub );
+				toRestore.push( getUrlSpy );
+
+				try {
+					tools.getGitUrlFromNpm( moduleName );
+				} catch ( e ) {}
+
+				expect( getUrlSpy.threw( error ) ).to.equal( true );
+			} );
+		} );
 	} );
 } );

+ 3 - 1
package.json

@@ -28,6 +28,7 @@
     "grunt-jscs": "^2.0.0",
     "grunt-text-replace": "^0.4.0",
     "inquirer": "^0.11.0",
+    "istanbul": "^0.4.1",
     "mocha": "^2.2.5",
     "ncp": "^2.0.0",
     "replace": "^0.3.0",
@@ -43,6 +44,7 @@
     "url": "https://github.com/ckeditor/ckeditor5.git"
   },
   "scripts": {
-    "tests": "mocha dev/tests"
+    "tests": "mocha dev/tests",
+    "coverage": "istanbul cover _mocha dev/tests/"
   }
 }