tools.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. 'use strict';
  2. var dirtyFiles,
  3. ignoreList;
  4. var repositoryRegExp = /^(ckeditor\/[^#]+)(?:#)?(.*)/;
  5. var directoryRegExp = /^ckeditor5/;
  6. module.exports = {
  7. /**
  8. * Check if a task (including its optional target) is in the queue of tasks to be executed by Grunt.
  9. *
  10. * @param grunt {Object} The Grunt object.
  11. * @param task {String} The task name. May optionally include the target (e.g. 'task:target').
  12. * @returns {Boolean} "true" if the task is in the queue.
  13. */
  14. checkTaskInQueue: function( grunt, task ) {
  15. var cliTasks = grunt.cli.tasks;
  16. // Check if the task has been called directly.
  17. var isDirectCall = ( cliTasks.indexOf( task ) > -1 );
  18. // Check if this is a "default" call and that the task is inside "default".
  19. var isDefaultTask = ( cliTasks.indexOf( 'default' ) > -1 ) || !cliTasks.length;
  20. // Hacking Grunt hard.
  21. var isTaskInDefault = isDefaultTask && ( grunt.task._tasks.default.info.indexOf( '"' + task + '"' ) > -1 );
  22. return isDirectCall || isTaskInDefault;
  23. },
  24. /**
  25. * Configures a multi-task and defines targets that are queued to be run by Grunt.
  26. *
  27. * @param grunt {Object} The Grunt object.
  28. * @param options {Object} A list of options for the method. See the jscs and jshint tasks for example.
  29. */
  30. setupMultitaskConfig: function( grunt, options ) {
  31. var task = options.task;
  32. var taskConfig = {};
  33. var config = taskConfig[ task ] = {
  34. options: options.defaultOptions
  35. };
  36. // "all" is the default target to be used if others are not to be run.
  37. var all = options.targets.all;
  38. var isAll = true;
  39. delete options.targets.all;
  40. Object.getOwnPropertyNames( options.targets ).forEach( function( target ) {
  41. if ( this.checkTaskInQueue( grunt, task + ':' + target ) ) {
  42. config[ target ] = options.targets[ target ]();
  43. isAll = false;
  44. }
  45. }, this );
  46. if ( isAll ) {
  47. config.all = all();
  48. }
  49. // Append .gitignore entries to the ignore list.
  50. if ( options.addGitIgnore ) {
  51. var ignoreProp = task + '.options.' + options.addGitIgnore;
  52. var ignores = grunt.config.get( ignoreProp ) || [];
  53. ignores = ignores.concat( this.getGitIgnore( grunt ) );
  54. grunt.config.set( ignoreProp, ignores );
  55. }
  56. // Merge over configurations set in gruntfile.js.
  57. grunt.config.merge( taskConfig );
  58. },
  59. /**
  60. * Gets the list of ignores from `.gitignore`.
  61. *
  62. * @param grunt {Object} The Grunt object.
  63. * @returns {String[]} The list of ignores.
  64. */
  65. getGitIgnore: function( grunt ) {
  66. if ( !ignoreList ) {
  67. ignoreList = grunt.file.read( '.gitignore' );
  68. ignoreList = ignoreList
  69. // Remove comment lines.
  70. .replace( /^#.*$/gm, '' )
  71. // Transform into array.
  72. .split( /\n+/ )
  73. // Remove empty entries.
  74. .filter( function( path ) {
  75. return !!path;
  76. } );
  77. }
  78. return ignoreList;
  79. },
  80. /**
  81. * Gets the list of files that are supposed to be included in the next Git commit.
  82. *
  83. * @returns {String[]} A list of file paths.
  84. */
  85. getGitDirtyFiles: function() {
  86. // Cache it, so it is executed only once when running multiple tasks.
  87. if ( !dirtyFiles ) {
  88. dirtyFiles = this
  89. // Compare the state of index with HEAD.
  90. .shExec( 'git diff-index --name-only HEAD' )
  91. // Remove trailing /n to avoid an empty entry.
  92. .replace( /\s*$/, '' )
  93. // Transform into array.
  94. .split( '\n' );
  95. // If nothing is returned, the array will one one empty string only.
  96. if ( dirtyFiles.length == 1 && !dirtyFiles[ 0 ] ) {
  97. dirtyFiles = [];
  98. }
  99. }
  100. return dirtyFiles;
  101. },
  102. /**
  103. * Executes a shell command.
  104. *
  105. * @param command {String} The command to be executed.
  106. * @returns {String} The command output.
  107. */
  108. shExec: function( command ) {
  109. var sh = require( 'shelljs' );
  110. sh.config.silent = true;
  111. var ret = sh.exec( command );
  112. if ( ret.code ) {
  113. throw new Error(
  114. 'Error while executing `' + command + '`:\n\n' +
  115. ret.output
  116. );
  117. }
  118. return ret.output;
  119. },
  120. /**
  121. * Links repository located in source path to repository located in destination path. Uses npm link.
  122. *
  123. * @param {String} sourcePath
  124. * @param {String} destinationPath
  125. * @param {String} pluginName
  126. */
  127. npmLink: function( sourcePath, destinationPath, pluginName ) {
  128. // Don't use sudo on windows when executing npm link.
  129. var isWin = process.platform == 'win32';
  130. var linkCommands = [
  131. 'cd ' + sourcePath,
  132. ( !isWin ? 'sudo ' : '' ) + 'npm link',
  133. 'cd ' + destinationPath,
  134. 'npm link ' + pluginName
  135. ];
  136. module.exports.shExec( linkCommands.join( ' && ' ) );
  137. },
  138. /**
  139. * Clones repository from provided GitHub URL. Only short GitHub urls are supported that starts with 'ckeditor/'.
  140. * https://docs.npmjs.com/files/package.json#github-urls
  141. *
  142. * @param {String} name Repository name.
  143. * @param {String} gitHubUrl GitHub url to repository.
  144. * @param {String} location Destination path.
  145. */
  146. cloneRepository: function( name, gitHubUrl, location ) {
  147. var match = gitHubUrl.match( repositoryRegExp );
  148. if ( match && match[ 1 ] ) {
  149. var cloneCommands = [
  150. 'cd ' + location,
  151. 'git clone git@github.com:' + match[ 1 ]
  152. ];
  153. // If commit-ish suffix is included - run git checkout.
  154. if ( match[ 2 ] ) {
  155. cloneCommands.push( 'cd ' + name );
  156. cloneCommands.push( 'git checkout ' + match[ 2 ] );
  157. }
  158. module.exports.shExec( cloneCommands.join( ' && ' ) );
  159. }
  160. },
  161. /**
  162. * Returns dependencies that starts with ckeditor5-, and have valid, short GitHub url. Returns null if no
  163. * dependencies are found.
  164. *
  165. * @param {Object} dependencies Dependencies object loaded from package.json file.
  166. * @returns {Object|null}
  167. */
  168. getCKEditorDependencies: function( dependencies ) {
  169. var result = null;
  170. var regexp = /^ckeditor5-/;
  171. if ( dependencies ) {
  172. Object.keys( dependencies ).forEach( function( key ) {
  173. if ( regexp.test( key ) && repositoryRegExp.test( dependencies[ key ] ) ) {
  174. if ( result === null ) {
  175. result = {};
  176. }
  177. result[ key ] = dependencies[ key ];
  178. }
  179. } );
  180. }
  181. return result;
  182. },
  183. /**
  184. * Returns array with all directories under specified path.
  185. *
  186. * @param {String} path
  187. * @returns {Array}
  188. */
  189. getDirectories: function( path ) {
  190. var fs = require( 'fs' );
  191. var pth = require( 'path' );
  192. return fs.readdirSync( path ).filter( function( item ) {
  193. return fs.statSync( pth.join( path, item ) ).isDirectory();
  194. } );
  195. },
  196. /**
  197. * Returns all directories under specified path that match 'ckeditor5' pattern.
  198. *
  199. * @param {String} path
  200. * @returns {Array}
  201. */
  202. getCKE5Directories: function( path ) {
  203. return module.exports.getDirectories( path ).filter( function( dir ) {
  204. return directoryRegExp.test( dir );
  205. } );
  206. },
  207. /**
  208. * Returns git status --porcelain -sb executed under specified path.
  209. *
  210. * @param {String} path Path where git status will be executed.
  211. * @returns {String|null}
  212. */
  213. getGitStatus: function( path ) {
  214. var exec = module.exports.shExec;
  215. try {
  216. return exec( 'cd ' + path + ' && git status --porcelain -sb' ).trim();
  217. } catch ( e ) { }
  218. return null;
  219. },
  220. /**
  221. * Initializes development workspace. Takes CKEditor5 dependencies, clones them and npm links to the main CKEditor5
  222. * repository.
  223. *
  224. * @param {String} workspacePath Absolute path to the workspace where all repositories will be cloned.
  225. * @param {String} ckeditor5Path Absolute path to the CKEditor5 repository where all dependencies will be linked.
  226. * @param {Function} log Log function used to report progress.
  227. */
  228. initDevWorkspace: function( workspacePath, ckeditor5Path, log ) {
  229. var tools = module.exports;
  230. var path = require( 'path' );
  231. var packageJSON = require( path.join( ckeditor5Path, 'package.json' ) );
  232. var pluginPath;
  233. // Get only CKEditor dependencies.
  234. var dependencies = tools.getCKEditorDependencies( packageJSON.dependencies );
  235. if ( dependencies ) {
  236. Object.keys( dependencies ).forEach( function( name ) {
  237. log( 'Clonning repository ' + dependencies[ name ] + '...' );
  238. tools.cloneRepository( name, dependencies[ name ], workspacePath );
  239. pluginPath = path.join( workspacePath, name );
  240. log( 'Linking ' + pluginPath + ' into ' + ckeditor5Path + '...' );
  241. tools.npmLink( pluginPath, ckeditor5Path, name );
  242. } );
  243. }
  244. },
  245. /**
  246. * Returns git status from all CKEditor repositories from workspace.
  247. *
  248. * @param {String} workspacePath Absolute path to the workspace containing repositories.
  249. * @param {Function} log Log function used to output status information.
  250. */
  251. getWorkspaceStatus: function( workspacePath, log ) {
  252. var tools = module.exports;
  253. var path = require( 'path' );
  254. var directories = tools.getCKE5Directories( workspacePath );
  255. directories.forEach( function( directory ) {
  256. var location = path.join( workspacePath, directory );
  257. var data = tools.getGitStatus( location );
  258. if ( data ) {
  259. log( '\x1b[1m' , '\x1b[36m', directory, '\x1b[0m\n', data );
  260. }
  261. } );
  262. }
  263. };