tools.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. const gutil = require( 'gulp-util' );
  7. const path = require( 'path' );
  8. const del = require( 'del' );
  9. module.exports = {
  10. /**
  11. * Executes a shell command.
  12. *
  13. * @param {String} command The command to be executed.
  14. * @param {Boolean} [logOutput] When set to `false` command's output will not be logged. When set to `true`,
  15. * stdout and stderr will be logged. Defaults to `true`.
  16. * @returns {String} The command output.
  17. */
  18. shExec( command, logOutput ) {
  19. const sh = require( 'shelljs' );
  20. sh.config.silent = true;
  21. logOutput = logOutput !== false;
  22. const ret = sh.exec( command );
  23. const logColor = ret.code ? gutil.colors.red : gutil.colors.grey;
  24. if ( logOutput ) {
  25. if ( ret.stdout ) {
  26. console.log( '\n' + logColor( ret.stdout.trim() ) + '\n' );
  27. }
  28. if ( ret.stderr ) {
  29. console.log( '\n' + gutil.colors.grey( ret.stderr.trim() ) + '\n' );
  30. }
  31. }
  32. if ( ret.code ) {
  33. throw new Error( `Error while executing ${ command }: ${ ret.stderr }` );
  34. }
  35. return ret.stdout;
  36. },
  37. /**
  38. * Links directory located in source path to directory located in destination path.
  39. * @param {String} source
  40. * @param {String} destination
  41. */
  42. linkDirectories( source, destination ) {
  43. const fs = require( 'fs' );
  44. // Remove destination directory if exists.
  45. if ( this.isSymlink( destination ) ) {
  46. this.removeSymlink( destination );
  47. } else if ( this.isDirectory( destination ) ) {
  48. this.shExec( `rm -rf ${ destination }` );
  49. }
  50. fs.symlinkSync( source, destination, 'dir' );
  51. },
  52. /**
  53. * Returns array with all directories under specified path.
  54. *
  55. * @param {String} path
  56. * @returns {Array}
  57. */
  58. getDirectories( path ) {
  59. const fs = require( 'fs' );
  60. const pth = require( 'path' );
  61. return fs.readdirSync( path ).filter( item => {
  62. return this.isDirectory( pth.join( path, item ) );
  63. } );
  64. },
  65. /**
  66. * Returns true if path points to existing directory.
  67. *
  68. * @param {String} path
  69. * @returns {Boolean}
  70. */
  71. isDirectory( path ) {
  72. const fs = require( 'fs' );
  73. try {
  74. return fs.statSync( path ).isDirectory();
  75. } catch ( e ) {}
  76. return false;
  77. },
  78. /**
  79. * Returns true if path points to existing file.
  80. *
  81. * @param {String} path
  82. * @returns {Boolean}
  83. */
  84. isFile( path ) {
  85. const fs = require( 'fs' );
  86. try {
  87. return fs.statSync( path ).isFile();
  88. } catch ( e ) {}
  89. return false;
  90. },
  91. /**
  92. * Returns true if path points to symbolic link.
  93. *
  94. * @param {String} path
  95. */
  96. isSymlink( path ) {
  97. const fs = require( 'fs' );
  98. try {
  99. return fs.lstatSync( path ).isSymbolicLink();
  100. } catch ( e ) {}
  101. return false;
  102. },
  103. /**
  104. * Updates JSON file under specified path.
  105. * @param {String} path Path to file on disk.
  106. * @param {Function} updateFunction Function that will be called with parsed JSON object. It should return
  107. * modified JSON object to save.
  108. */
  109. updateJSONFile( path, updateFunction ) {
  110. const fs = require( 'fs' );
  111. const contents = fs.readFileSync( path, 'utf-8' );
  112. let json = JSON.parse( contents );
  113. json = updateFunction( json );
  114. fs.writeFileSync( path, JSON.stringify( json, null, 2 ) + '\n', 'utf-8' );
  115. },
  116. /**
  117. * Reinserts all object's properties in alphabetical order (character's Unicode value).
  118. * Used for JSON.stringify method which takes keys in insertion order.
  119. *
  120. * @param { Object } obj
  121. * @returns { Object } Same object with sorted keys.
  122. */
  123. sortObject( obj ) {
  124. Object.keys( obj ).sort().forEach( key => {
  125. const val = obj[ key ];
  126. delete obj[ key ];
  127. obj[ key ] = val;
  128. } );
  129. return obj;
  130. },
  131. /**
  132. * Returns name of the NPM module located under provided path.
  133. *
  134. * @param {String} modulePath Path to NPM module.
  135. */
  136. readPackageName( modulePath ) {
  137. const fs = require( 'fs' );
  138. const path = require( 'path' );
  139. const packageJSONPath = path.join( modulePath, 'package.json' );
  140. if ( !this.isFile( packageJSONPath ) ) {
  141. return null;
  142. }
  143. const contents = fs.readFileSync( packageJSONPath, 'utf-8' );
  144. const json = JSON.parse( contents );
  145. return json.name || null;
  146. },
  147. /**
  148. * Calls `npm install` command in specified path.
  149. *
  150. * @param {String} path
  151. */
  152. npmInstall( path ) {
  153. this.shExec( `cd ${ path } && npm install` );
  154. },
  155. /**
  156. * Calls `npm uninstall <name>` command in specified path.
  157. *
  158. * @param {String} path
  159. * @param {String} name
  160. */
  161. npmUninstall( path, name ) {
  162. this.shExec( `cd ${ path } && npm uninstall ${ name }` );
  163. },
  164. /**
  165. * Calls `npm update --dev` command in specified path.
  166. *
  167. * @param {String} path
  168. */
  169. npmUpdate( path ) {
  170. this.shExec( `cd ${ path } && npm update --dev` );
  171. },
  172. /**
  173. * Copies source files into destination directory and replaces contents of the file using provided `replace` object.
  174. *
  175. * // Each occurrence of `{{appName}}` inside README.md and CHANGES.md will be changed to `ckeditor5`.
  176. * tools.copyTemplateFiles( [ 'README.md', 'CHANGES.md' ], '/new/path', { '{{AppName}}': 'ckeditor5' } );
  177. *
  178. * @param {Array} sources Source files.
  179. * @param {String} destination Path to destination directory.
  180. * @param {Object} [replace] Object with data to fill template. Method will take object's keys and replace their
  181. * occurrences with value stored under that key.
  182. */
  183. copyTemplateFiles( sources, destination, replace ) {
  184. const path = require( 'path' );
  185. const fs = require( 'fs-extra' );
  186. replace = replace || {};
  187. destination = path.resolve( destination );
  188. const regexps = [];
  189. for ( let variableName in replace ) {
  190. regexps.push( variableName );
  191. }
  192. const regexp = new RegExp( regexps.join( '|' ), 'g' );
  193. const replaceFunction = ( matched ) => replace[ matched ];
  194. fs.ensureDirSync( destination );
  195. sources.forEach( source => {
  196. source = path.resolve( source );
  197. let fileData = fs.readFileSync( source, 'utf8' );
  198. fileData = fileData.replace( regexp, replaceFunction );
  199. fs.writeFileSync( path.join( destination, path.basename( source ) ), fileData, 'utf8' );
  200. } );
  201. },
  202. /**
  203. * Executes 'npm view' command for provided module name and returns Git url if one is found. Returns null if
  204. * module cannot be found.
  205. *
  206. * @param {String} name Name of the module.
  207. * @returns {*}
  208. */
  209. getGitUrlFromNpm( name ) {
  210. try {
  211. const info = JSON.parse( this.shExec( `npm view ${ name } repository --json`, false ) );
  212. if ( info && info.type == 'git' ) {
  213. return info.url;
  214. }
  215. } catch ( error ) {
  216. // Throw error only when different than E404.
  217. if ( error.message.indexOf( 'npm ERR! code E404' ) == -1 ) {
  218. throw error;
  219. }
  220. }
  221. return null;
  222. },
  223. /**
  224. * Unlinks symbolic link under specified path.
  225. *
  226. * @param {String} path
  227. */
  228. removeSymlink( path ) {
  229. const fs = require( 'fs' );
  230. fs.unlinkSync( path );
  231. },
  232. /**
  233. * Removes files and directories specified by `glob` starting from `rootDir`
  234. * and gently informs about deletion.
  235. *
  236. * @param {String} rootDir The path to the root directory (i.e. "dist/").
  237. * @param {String} glob Glob specifying what to clean.
  238. * @returns {Promise}
  239. */
  240. clean( rootDir, glob ) {
  241. return del( path.join( rootDir, glob ) ).then( paths => {
  242. paths.forEach( p => {
  243. gutil.log( `Deleted file '${ gutil.colors.cyan( p ) }'.` );
  244. } );
  245. } );
  246. }
  247. };