Browse Source

Added early version of task running

Maksymilian Barnaś 9 years ago
parent
commit
a7da23a907

+ 12 - 0
dev/tasks/exec/functions/bump-year.js

@@ -0,0 +1,12 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/**
+ * {String} packagePath
+ * {Minimatch} params
+ */
+module.exports = () => {};

+ 12 - 0
dev/tasks/exec/package.json

@@ -0,0 +1,12 @@
+{
+  "name": "exec",
+  "version": "0.1.0",
+  "description": "",
+  "main": "tasks.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC"
+}

+ 46 - 0
dev/tasks/exec/tasks.js

@@ -0,0 +1,46 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const gulp = require( 'gulp' );
+const minimist = require( 'minimist' );
+
+const exec = require( './tasks/exec' );
+
+const log = require( './utils/log' );
+const gutil = require( 'gulp-util' );
+
+module.exports = ( config ) => {
+	const ckeditor5Path = process.cwd();
+	const packageJSON = require( '../../../package.json' );
+
+	// Configure logging.
+	log.configure(
+		( msg ) => gutil.log( msg ),
+		( msg ) => gutil.log( gutil.colors.red( msg ) )
+	);
+
+	const tasks = {
+		execOnRepositories() {
+			const options = minimist( process.argv.slice( 2 ), {
+				boolean: [ 'dry-run' ],
+				default: {
+					'dry-run': true
+				}
+			} );
+
+			const installTask = () => {};
+
+			return exec( installTask, ckeditor5Path, packageJSON, config.WORKSPACE_DIR, options[ 'dry-run' ] );
+		},
+
+		register() {
+			gulp.task( 'exec', tasks.execOnRepositories );
+		}
+	};
+
+	return tasks;
+};

+ 57 - 0
dev/tasks/exec/tasks/exec.js

@@ -0,0 +1,57 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const tools = require( '../utils/tools' );
+const git = require( '../utils/git' );
+const path = require( 'path' );
+const log = require( '../utils/log' );
+
+/**
+ * @param {Function} installTask Install task to use on each dependency that is missing from workspace.
+ * @param {String} ckeditor5Path Path to main CKEditor5 repository.
+ * @param {Object} packageJSON Parsed package.json file from CKEditor5 repository.
+ * @param {String} workspaceRoot Relative path to workspace root.
+ * @param {Boolean} dryRun
+ */
+module.exports = ( installTask, ckeditor5Path, packageJSON, workspaceRoot, dryRun ) => {
+	const workspaceAbsolutePath = path.join( ckeditor5Path, workspaceRoot );
+
+	// Get all CKEditor dependencies from package.json.
+	const dependencies = tools.getCKEditorDependencies( packageJSON.dependencies );
+
+	if ( dependencies ) {
+		const directories = tools.getCKE5Directories( workspaceAbsolutePath );
+
+		if ( dependencies ) {
+			for ( let dependency in dependencies ) {
+				const repositoryURL = dependencies[ dependency ];
+				const urlInfo = git.parseRepositoryUrl( repositoryURL );
+				// const repositoryAbsolutePath = path.join( workspaceAbsolutePath, dependency );
+
+				// Check if repository's directory already exists.
+				if ( directories.indexOf( urlInfo.name ) > -1 ) {
+					if ( dryRun ) {
+						log.out( `Dry run in ${ urlInfo.name }...` );
+					} else {
+						try {
+							log.out( `Executing task on ${ repositoryURL }...` );
+						} catch ( error ) {
+							log.err( error );
+						}
+					}
+				} else {
+					// Directory does not exits in workspace - install it.
+					// installTask( ckeditor5Path, workspaceRoot, repositoryURL );
+				}
+			}
+		} else {
+			log.out( 'No CKEditor5 plugins in development mode.' );
+		}
+	} else {
+		log.out( 'No CKEditor5 dependencies found in package.json file.' );
+	}
+};

+ 146 - 0
dev/tasks/exec/utils/git.js

@@ -0,0 +1,146 @@
+/**
+ * @license Copyright (c) 2003-2016, 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.user
+	 * @returns {String} urlInfo.name
+	 * @returns {String} urlInfo.branch
+	 */
+	parseRepositoryUrl( url ) {
+		const regexp = /^((?:git@|(?:http[s]?|git):\/\/)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 ] || 'git@github.com:';
+		repository = match[ 2 ];
+		user = match[ 3 ];
+		name = match[ 4 ];
+		branch = match[ 5 ] || 'master';
+
+		name = /\.git$/.test( name ) ? name.slice( 0, -4 ) : name;
+
+		return {
+			server: server,
+			repository: repository,
+			branch: branch,
+			user: user,
+			name: name
+		};
+	},
+
+	/**
+	 * 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( ' && ' ) );
+	},
+
+	/**
+	 * Pulls specified branch from origin.
+	 *
+	 * @param {String} repositoryLocation Absolute path to repository.
+	 * @param {String} branchName Branch name to pull.
+	 */
+	pull( repositoryLocation, branchName ) {
+		const pullCommands = [
+			`cd ${ repositoryLocation }`,
+			`git pull origin ${ branchName }`
+		];
+
+		tools.shExec( pullCommands.join( ' && ' ) );
+	},
+
+	/**
+	 * Fetch all branches from each origin on selected repository.
+	 *
+	 * @param {String} repositoryLocation
+	 */
+	fetchAll( repositoryLocation ) {
+		const fetchCommands = [
+			`cd ${ repositoryLocation }`,
+			`git fetch --all`
+		];
+
+		tools.shExec( fetchCommands.join( ' && ' ) );
+	},
+
+	/**
+	 * Initializes new repository, adds and merges CKEditor5 boilerplate project.
+	 *
+	 * @param {String} repositoryPath Absolute path where repository should be created.
+	 */
+	initializeRepository( repositoryPath ) {
+		tools.shExec( `git init ${ repositoryPath }` );
+	},
+
+	/**
+	 * Returns Git status of repository stored under specified path. It runs `git status --porcelain -sb` command.
+	 *
+	 * @param {String} repositoryPath Absolute path to repository.
+	 * @returns {String} Executed command's result.
+	 */
+	getStatus( repositoryPath ) {
+		return tools.shExec( `cd ${ repositoryPath } && git status --porcelain -sb`, false );
+	},
+
+	/**
+	 * Creates initial commit on repository under specified path.
+	 *
+	 * @param {String} pluginName
+	 * @param {String} repositoryPath
+	 */
+	initialCommit( pluginName, repositoryPath ) {
+		const commitCommands = [
+			`cd ${ repositoryPath }`,
+			`git add .`,
+			`git commit -m "Initial commit for ${ pluginName }."`
+		];
+
+		tools.shExec( commitCommands.join( ' && ' ) );
+	}
+};

+ 56 - 0
dev/tasks/exec/utils/log.js

@@ -0,0 +1,56 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+let logOut;
+let logErr;
+
+module.exports = {
+	/**
+	 * Configure login output functions.
+	 *
+	 * 		log.configure( logOut, logErr );
+	 *
+	 * 		function logOut( message ) {
+	 * 			// Save output to file.
+	 * 			...
+	 * 		}
+	 *
+	 * 		function logErr( message) {
+	 * 			// Save error to file.
+	 * 			...
+	 * 		}
+	 *
+	 * @param {Function} stdout Function to be used to log standard output.
+	 * @param {Function} stderr Function to be used to log standard error.
+	 */
+	configure( stdout, stderr ) {
+		logOut = stdout;
+		logErr = stderr;
+	},
+
+	/**
+	 * Logs output using function provided in {@link configure}.
+	 *
+	 * @param {String} message Message to be logged.
+	 */
+	out( message ) {
+		if ( logOut ) {
+			logOut( message );
+		}
+	},
+
+	/**
+	 * Logs errors using function provided in {@link #configure}.
+	 *
+	 * @param {String} message Message to be logged.
+	 */
+	err( message ) {
+		if ( logErr ) {
+			logErr( message );
+		}
+	}
+};

+ 329 - 0
dev/tasks/exec/utils/tools.js

@@ -0,0 +1,329 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+const gutil = require( 'gulp-util' );
+
+const dependencyRegExp = /^ckeditor5-/;
+
+module.exports = {
+
+	/**
+	 * Executes a shell command.
+	 *
+	 * @param {String} command The command to be executed.
+	 * @param {Boolean} [logOutput] When set to `false` command's output will not be logged. When set to `true`,
+	 * stdout and stderr will be logged. Defaults to `true`.
+	 * @returns {String} The command output.
+	 */
+	shExec( command, logOutput ) {
+		const sh = require( 'shelljs' );
+
+		sh.config.silent = true;
+		logOutput = logOutput !== false;
+
+		const ret = sh.exec( command );
+		const logColor = ret.code ? gutil.colors.red : gutil.colors.grey;
+
+		if ( logOutput ) {
+			if ( ret.stdout ) {
+				console.log( '\n' + logColor( ret.stdout.trim() ) + '\n' );
+			}
+
+			if ( ret.stderr ) {
+				console.log( '\n' + gutil.colors.grey( ret.stderr.trim() ) + '\n' );
+			}
+		}
+
+		if ( ret.code ) {
+			throw new Error( `Error while executing ${ command }: ${ ret.stderr }` );
+		}
+
+		return ret.stdout;
+	},
+
+	/**
+	 * Links directory located in source path to directory located in destination path.
+	 * @param {String} source
+	 * @param {String} destination
+	 */
+	linkDirectories( source, destination ) {
+		const fs = require( 'fs' );
+		// Remove destination directory if exists.
+		if ( this.isSymlink( destination ) ) {
+			this.removeSymlink( destination );
+		} else if ( this.isDirectory( destination ) ) {
+			this.shExec( `rm -rf ${ destination }` );
+		}
+
+		fs.symlinkSync( source, destination, 'dir' );
+	},
+
+	/**
+	 * Returns dependencies that starts with ckeditor5-, and have valid, short GitHub url. Returns null if no
+	 * dependencies are found.
+	 *
+	 * @param {Object} dependencies Dependencies object loaded from package.json file.
+	 * @returns {Object|null}
+	 */
+	getCKEditorDependencies( dependencies ) {
+		let result = null;
+
+		if ( dependencies ) {
+			Object.keys( dependencies ).forEach( function( key ) {
+				if ( dependencyRegExp.test( key ) ) {
+					if ( result === null ) {
+						result = {};
+					}
+
+					result[ key ] = dependencies[ key ];
+				}
+			} );
+		}
+
+		return result;
+	},
+
+	/**
+	 * Returns array with all directories under specified path.
+	 *
+	 * @param {String} path
+	 * @returns {Array}
+	 */
+	getDirectories( path ) {
+		const fs = require( 'fs' );
+		const pth = require( 'path' );
+
+		return fs.readdirSync( path ).filter( item => {
+			return this.isDirectory( pth.join( path, item ) );
+		} );
+	},
+
+	/**
+	 * Returns true if path points to existing directory.
+	 *
+	 * @param {String} path
+	 * @returns {Boolean}
+	 */
+	isDirectory( path ) {
+		const fs = require( 'fs' );
+
+		try {
+			return fs.statSync( path ).isDirectory();
+		} catch ( e ) {}
+
+		return false;
+	},
+
+	/**
+	 * Returns true if path points to existing file.
+	 *
+	 * @param {String} path
+	 * @returns {Boolean}
+	 */
+	isFile( path ) {
+		const fs = require( 'fs' );
+
+		try {
+			return fs.statSync( path ).isFile();
+		} catch ( e ) {}
+
+		return false;
+	},
+
+	/**
+	 * Returns true if path points to symbolic link.
+	 *
+	 * @param {String} path
+	 */
+	isSymlink( path ) {
+		const fs = require( 'fs' );
+
+		try {
+			return fs.lstatSync( path ).isSymbolicLink();
+		} catch ( e ) {}
+
+		return false;
+	},
+
+	/**
+	 * Returns all directories under specified path that match 'ckeditor5' pattern.
+	 *
+	 * @param {String} path
+	 * @returns {Array}
+	 */
+	getCKE5Directories( path ) {
+		return this.getDirectories( path ).filter( dir => {
+			return dependencyRegExp.test( dir );
+		} );
+	},
+
+	/**
+	 * 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.
+	 */
+	updateJSONFile( path, updateFunction ) {
+		const fs = require( 'fs' );
+
+		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' );
+	},
+
+	/**
+	 * Reinserts all object's properties in alphabetical order (character's Unicode value).
+	 * Used for JSON.stringify method which takes keys in insertion order.
+	 *
+	 * @param { Object } obj
+	 * @returns { Object } Same object with sorted keys.
+	 */
+	sortObject( obj ) {
+		Object.keys( obj ).sort().forEach( key => {
+			const val = obj[ key ];
+			delete obj[ key ];
+			obj[ key ] = val;
+		} );
+
+		return obj;
+	},
+
+	/**
+	 * Returns name of the NPM module located under provided path.
+	 *
+	 * @param {String} modulePath Path to NPM module.
+     */
+	readPackageName( modulePath ) {
+		const fs = require( 'fs' );
+		const path = require( 'path' );
+		const packageJSONPath = path.join( modulePath, 'package.json' );
+
+		if ( !this.isFile( packageJSONPath ) ) {
+			return null;
+		}
+
+		const contents = fs.readFileSync( packageJSONPath, 'utf-8' );
+		const json = JSON.parse( contents );
+
+		return json.name || null;
+	},
+
+	/**
+	 * Calls `npm install` command in specified path.
+	 *
+	 * @param {String} path
+	 */
+	npmInstall( path ) {
+		this.shExec( `cd ${ path } && npm install` );
+	},
+
+	/**
+	 * Calls `npm uninstall <name>` command in specified path.
+	 *
+	 * @param {String} path
+	 * @param {String} name
+	 */
+	npmUninstall( path, name ) {
+		this.shExec( `cd ${ path } && npm uninstall ${ name }` );
+	},
+
+	/**
+	 * Calls `npm update --dev` command in specified path.
+	 *
+	 * @param {String} path
+	 */
+	npmUpdate( path ) {
+		this.shExec( `cd ${ path } && npm update --dev` );
+	},
+
+	/**
+	 * Copies source files into destination directory and replaces contents of the file using provided `replace` object.
+	 *
+	 *		// Each occurrence of `{{appName}}` inside README.md and CHANGES.md will be changed to `ckeditor5`.
+	 * 		tools.copyTemplateFiles( [ 'README.md', 'CHANGES.md' ], '/new/path', { '{{AppName}}': 'ckeditor5' } );
+	 *
+	 * @param {Array} sources Source files.
+	 * @param {String} destination Path to destination directory.
+	 * @param {Object} [replace] Object with data to fill template. Method will take object's keys and replace their
+	 * occurrences with value stored under that key.
+	 */
+	copyTemplateFiles( sources, destination, replace ) {
+		const path = require( 'path' );
+		const fs = require( 'fs-extra' );
+		replace = replace || {};
+		destination = path.resolve( destination );
+		const regexps = [];
+
+		for ( let variableName in replace ) {
+			regexps.push( variableName );
+		}
+		const regexp = new RegExp( regexps.join( '|' ), 'g' );
+		const replaceFunction = ( matched ) => replace[ matched ];
+
+		fs.ensureDirSync( destination );
+
+		sources.forEach( source => {
+			source = path.resolve( source );
+			let fileData = fs.readFileSync( source, 'utf8' );
+			fileData = fileData.replace( regexp, replaceFunction );
+			fs.writeFileSync( path.join( destination, path.basename( source ) ), fileData, 'utf8' );
+		} );
+	},
+
+	/**
+	 * 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`, false ) );
+
+			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;
+	},
+
+	/**
+	 * Returns list of symbolic links to directories with names starting with `ckeditor5-` prefix.
+	 *
+	 * @param {String} path Path to directory,
+	 * @returns {Array} Array with directories names.
+	 */
+	getCKE5Symlinks( path ) {
+		const fs = require( 'fs' );
+		const pth = require( 'path' );
+
+		return fs.readdirSync( path ).filter( item => {
+			const fullPath = pth.join( path, item );
+
+			return dependencyRegExp.test( item ) && this.isSymlink( fullPath );
+		} );
+	},
+
+	/**
+	 * Unlinks symbolic link under specified path.
+	 *
+	 * @param {String} path
+	 */
+	removeSymlink( path ) {
+		const fs = require( 'fs' );
+		fs.unlinkSync( path );
+	}
+};

+ 1 - 0
gulpfile.js

@@ -20,6 +20,7 @@ require( './dev/tasks/dev/tasks' )( config ).register();
 require( './dev/tasks/lint/tasks' )( config ).register();
 require( './dev/tasks/test/tasks' )( config ).register();
 require( './dev/tasks/docs/tasks' )( config ).register();
+require( './dev/tasks/exec/tasks' )( config ).register();
 
 gulp.task( 'default', [ 'build' ] );
 gulp.task( 'pre-commit', [ 'lint-staged' ] );