tools.js 8.0 KB

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