tools.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. 'use strict';
  2. let dirtyFiles,
  3. ignoreList;
  4. const dependencyRegExp = /^ckeditor5-/;
  5. const TEMPLATE_PATH = './dev/tasks/templates';
  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( grunt, task ) {
  15. const cliTasks = grunt.cli.tasks;
  16. // Check if the task has been called directly.
  17. const isDirectCall = ( cliTasks.indexOf( task ) > -1 );
  18. // Check if this is a "default" call and that the task is inside "default".
  19. const isDefaultTask = ( cliTasks.indexOf( 'default' ) > -1 ) || !cliTasks.length;
  20. // Hacking Grunt hard.
  21. const 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( grunt, options ) {
  31. const task = options.task;
  32. const taskConfig = {};
  33. const 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. const all = options.targets.all;
  38. let 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. let ignoreProp = task + '.options.' + options.addGitIgnore;
  52. let 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( 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() {
  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( command ) {
  109. const sh = require( 'shelljs' );
  110. sh.config.silent = true;
  111. const 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 directory located in source path to directory located in destination path.
  122. * @param {String} source
  123. * @param {String} destination
  124. */
  125. linkDirectories( source, destination ) {
  126. const fs = require( 'fs' );
  127. // Remove destination directory if exists.
  128. if ( this.isDirectory( destination ) ) {
  129. this.shExec( `rm -rf ${ destination }` );
  130. }
  131. fs.symlinkSync( source, destination, 'dir' );
  132. },
  133. /**
  134. * Returns dependencies that starts with ckeditor5-, and have valid, short GitHub url. Returns null if no
  135. * dependencies are found.
  136. *
  137. * @param {Object} dependencies Dependencies object loaded from package.json file.
  138. * @returns {Object|null}
  139. */
  140. getCKEditorDependencies( dependencies ) {
  141. let result = null;
  142. if ( dependencies ) {
  143. Object.keys( dependencies ).forEach( function( key ) {
  144. if ( dependencyRegExp.test( key ) ) {
  145. if ( result === null ) {
  146. result = {};
  147. }
  148. result[ key ] = dependencies[ key ];
  149. }
  150. } );
  151. }
  152. return result;
  153. },
  154. /**
  155. * Returns array with all directories under specified path.
  156. *
  157. * @param {String} path
  158. * @returns {Array}
  159. */
  160. getDirectories( path ) {
  161. const fs = require( 'fs' );
  162. const pth = require( 'path' );
  163. return fs.readdirSync( path ).filter( item => {
  164. return this.isDirectory( pth.join( path, item ) );
  165. } );
  166. },
  167. /**
  168. * Returns true if path points to existing directory.
  169. *
  170. * @param {String} path
  171. * @returns {Boolean}
  172. */
  173. isDirectory( path ) {
  174. const fs = require( 'fs' );
  175. try {
  176. return fs.statSync( path ).isDirectory();
  177. } catch ( e ) {}
  178. return false;
  179. },
  180. /**
  181. * Returns true if path points to existing file.
  182. *
  183. * @param {String} path
  184. * @returns {Boolean}
  185. */
  186. isFile( path ) {
  187. const fs = require( 'fs' );
  188. try {
  189. return fs.statSync( path ).isFile();
  190. } catch ( e ) {}
  191. return false;
  192. },
  193. /**
  194. * Returns all directories under specified path that match 'ckeditor5' pattern.
  195. *
  196. * @param {String} path
  197. * @returns {Array}
  198. */
  199. getCKE5Directories( path ) {
  200. return this.getDirectories( path ).filter( dir => {
  201. return dependencyRegExp.test( dir );
  202. } );
  203. },
  204. /**
  205. * Updates JSON file under specified path.
  206. * @param {String} path Path to file on disk.
  207. * @param {Function} updateFunction Function that will be called with parsed JSON object. It should return
  208. * modified JSON object to save.
  209. */
  210. updateJSONFile( path, updateFunction ) {
  211. const fs = require( 'fs' );
  212. const contents = fs.readFileSync( path, 'utf-8' );
  213. let json = JSON.parse( contents );
  214. json = updateFunction( json );
  215. fs.writeFileSync( path, JSON.stringify( json, null, 2 ), 'utf-8' );
  216. },
  217. /**
  218. * Returns name of the NPM module located under provided path.
  219. *
  220. * @param {String} modulePath Path to NPM module.
  221. */
  222. readPackageName( modulePath ) {
  223. const fs = require( 'fs' );
  224. const path = require( 'path' );
  225. const packageJSONPath = path.join( modulePath, 'package.json' );
  226. if ( !this.isFile( packageJSONPath ) ) {
  227. return null;
  228. }
  229. const contents = fs.readFileSync( packageJSONPath, 'utf-8' );
  230. const json = JSON.parse( contents );
  231. return json.name || null;
  232. },
  233. /**
  234. * Calls `npm install` command in specified path.
  235. *
  236. * @param {String} path
  237. */
  238. npmInstall( path ) {
  239. this.shExec( `cd ${ path } && npm install` );
  240. },
  241. /**
  242. * Calls `npm uninstall <name>` command in specified path.
  243. *
  244. * @param {String} path
  245. */
  246. npmUninstall( path, name ) {
  247. this.shExec( `cd ${ path } && npm uninstall ${ name }` );
  248. },
  249. /**
  250. * Calls `npm update` command in specified path.
  251. *
  252. * @param {String} path
  253. */
  254. npmUpdate( path ) {
  255. this.shExec( `cd ${ path } && npm update` );
  256. },
  257. /**
  258. * Installs Git hooks in specified repository.
  259. *
  260. * @param {String} path
  261. */
  262. installGitHooks( path ) {
  263. this.shExec( `cd ${ path } && grunt githooks` );
  264. },
  265. /**
  266. * Copies template files to specified destination.
  267. *
  268. * @param {String} destination
  269. */
  270. copyTemplateFiles( destination ) {
  271. const path = require( 'path' );
  272. const templatesPath = path.resolve( TEMPLATE_PATH );
  273. this.shExec( `cp ${ path.join( templatesPath, '*.md' ) } ${ destination }` );
  274. },
  275. /**
  276. * Executes 'npm view' command for provided module name and returns Git url if one is found. Returns null if
  277. * module cannot be found.
  278. *
  279. * @param {String} name Name of the module.
  280. * @returns {*}
  281. */
  282. getGitUrlFromNpm( name ) {
  283. try {
  284. const info = JSON.parse( this.shExec( `npm view ${ name } repository --json` ) );
  285. if ( info && info.type == 'git' ) {
  286. return info.url;
  287. }
  288. } catch ( error ) {
  289. // Throw error only when different than E404.
  290. if ( error.message.indexOf( 'npm ERR! code E404' ) == -1 ) {
  291. throw error;
  292. }
  293. }
  294. return null;
  295. }
  296. };