sync.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. module.exports = function (grunt) {
  2. 'use strict';
  3. // Register the 'sync' task.
  4. grunt.registerTask('sync', 'Syncs necessary libraries for development purposes. Read more in: MAINTAINERS.md.', function () {
  5. var cwd = process.cwd();
  6. var force = grunt.option('force');
  7. var path = require('path');
  8. var pkg = require('../package');
  9. // Internal variables.
  10. var libraries = {};
  11. var librariesCache = path.join(cwd, pkg.caches.libraries);
  12. var librariesPath = path.join(cwd, pkg.paths.libraries);
  13. var exists = grunt.file.exists(librariesCache);
  14. var expired = false;
  15. // Determine the validity of any existing cached libraries file.
  16. if (!force && exists) {
  17. grunt.verbose.write('Cached library information detected, checking...');
  18. var fs = require('fs');
  19. var stat = fs.statSync(librariesCache);
  20. var now = new Date().getTime();
  21. var expire = new Date(stat.mtime);
  22. expire.setDate(expire.getDate() + 7); // 1 week
  23. expired = now > expire.getTime();
  24. grunt.verbose.writeln((expired ? 'EXPIRED' : 'VALID')[expired ? 'red' : 'green']);
  25. }
  26. // Register a private sub-task. Doing this inside the main task prevents
  27. // this private sub-task from being executed directly and also prevents it
  28. // from showing up on the list of available tasks on --help.
  29. grunt.registerTask('sync:api', function () {
  30. var done = this.async();
  31. var request = require('request');
  32. grunt.verbose.write(pkg.urls.jsdelivr + ' ');
  33. request(pkg.urls.jsdelivr, function (error, response, body) {
  34. if (!error && response.statusCode == 200) {
  35. grunt.verbose.ok();
  36. var json;
  37. grunt.verbose.write("\nParsing JSON response...");
  38. try {
  39. json = JSON.parse(body);
  40. grunt.verbose.ok();
  41. } catch (e) {
  42. grunt.verbose.error();
  43. throw grunt.util.error('Unable to parse the response value (' + e.message + ').', e);
  44. }
  45. grunt.verbose.write("\nExtracting versions and themes from libraries...");
  46. libraries = {};
  47. json.forEach(function (library) {
  48. if (library.name === 'bootstrap' || library.name === 'bootswatch') {
  49. library.assets.forEach(function (asset) {
  50. if (asset.version.match(/^3.\d\.\d$/)) {
  51. if (!libraries[library.name]) libraries[library.name] = {};
  52. if (!libraries[library.name][asset.version]) libraries[library.name][asset.version] = {};
  53. asset.files.forEach(function (file) {
  54. if (!file.match(/bootstrap\.min\.css$/)) return;
  55. if (library.name === 'bootstrap') {
  56. libraries[library.name][asset.version]['bootstrap'] = true;
  57. }
  58. else {
  59. libraries[library.name][asset.version][file.split(path.sep)[0]] = true;
  60. }
  61. });
  62. }
  63. });
  64. }
  65. });
  66. grunt.verbose.ok();
  67. // Flatten themes.
  68. for (var library in libraries) {
  69. grunt.verbose.header(library);
  70. if (!libraries.hasOwnProperty(library)) continue;
  71. var versions = Object.keys(libraries[library]);
  72. grunt.verbose.ok('Versions: ' + versions.join(', '));
  73. var themeCount = 0;
  74. for (var version in libraries[library]) {
  75. if (!libraries[library].hasOwnProperty(version)) continue;
  76. var themes = Object.keys(libraries[library][version]).sort();
  77. libraries[library][version] = themes;
  78. if (themes.length > themeCount) {
  79. themeCount = themes.length;
  80. }
  81. }
  82. grunt.verbose.ok(grunt.util.pluralize(themeCount, 'Themes: 1/Themes: ' + themeCount));
  83. }
  84. grunt.verbose.writeln();
  85. grunt.file.write(librariesCache, JSON.stringify(libraries, null, 2));
  86. grunt.log.ok('Synced');
  87. }
  88. else {
  89. grunt.verbose.error();
  90. if (error) grunt.verbose.error(error);
  91. grunt.verbose.error('Request URL: ' + pkg.urls.jsdelivr);
  92. grunt.verbose.error('Status Code: ' + response.statusCode);
  93. grunt.verbose.error('Response Headers: ' + JSON.stringify(response.headers, null, 2));
  94. grunt.verbose.error('Response:');
  95. grunt.verbose.error(body);
  96. grunt.fail.fatal('Unable to establish a connection. Run with --verbose to view the response received.');
  97. }
  98. return done(error);
  99. });
  100. });
  101. // Run API sync sub-task.
  102. if (!exists || force || expired) {
  103. if (!exists) grunt.verbose.writeln('No libraries cache detected, syncing libraries.');
  104. else if (force) grunt.verbose.writeln('Forced option detected, syncing libraries.');
  105. else if (expired) grunt.verbose.writeln('Libraries cache is over a week old, syncing libraries.');
  106. grunt.task.run(['sync:api']);
  107. }
  108. // Register another private sub-task.
  109. grunt.registerTask('sync:libraries', function () {
  110. var bower = require('bower');
  111. var done = this.async();
  112. var inquirer = require('inquirer');
  113. var queue = require('queue')({concurrency: 1, timeout: 60000});
  114. if (!grunt.file.exists(librariesCache)) {
  115. return grunt.fail.fatal('No libraries detected. Please run `grunt sync --force`.');
  116. }
  117. libraries = grunt.file.readJSON(librariesCache);
  118. var bowerJson = path.join(cwd, 'bower.json');
  119. if (!grunt.file.isDir(librariesPath)) grunt.file.mkdir(librariesPath);
  120. // Iterate over libraries.
  121. for (var library in libraries) {
  122. if (!libraries.hasOwnProperty(library)) continue;
  123. // Iterate over versions.
  124. for (var version in libraries[library]) {
  125. if (!libraries[library].hasOwnProperty(version)) continue;
  126. var endpoint = library + '#' + version;
  127. // Check if library is already installed. If so, skip.
  128. var versionPath = path.join(librariesPath, version);
  129. grunt.verbose.write('Checking ' + endpoint + '...');
  130. if (grunt.file.isDir(versionPath) && grunt.file.isDir(versionPath + '/' + library)) {
  131. grunt.verbose.writeln('INSTALLED'.green);
  132. continue;
  133. }
  134. grunt.verbose.writeln('MISSING'.red);
  135. grunt.file.mkdir(versionPath);
  136. grunt.file.copy(bowerJson, path.join(versionPath, 'bower.json'));
  137. var config = {
  138. cwd: versionPath,
  139. directory: '',
  140. interactive: true,
  141. scripts: {
  142. postinstall: 'rm -rf jquery && rm -rf font-awesome'
  143. }
  144. };
  145. // Enqueue bower installations.
  146. (function (endpoint, config) {
  147. queue.push(function (done) {
  148. bower.commands
  149. .install([endpoint], {saveDev: true}, config)
  150. .on('log', function (result) {
  151. if (result.level === 'action' && result.id !== 'validate' && !result.message.match(/(jquery|font-awesome)/)) {
  152. grunt.log.writeln(['bower', result.id.cyan, result.message.green].join(' '));
  153. }
  154. else if (result.level === 'action') {
  155. grunt.verbose.writeln(['bower', result.id.cyan, result.message.green].join(' '));
  156. }
  157. else {
  158. grunt.log.debug(['bower', result.id.cyan, result.message.green].join(' '));
  159. }
  160. })
  161. .on('prompt', function (prompts, callback) {
  162. inquirer.prompt(prompts, callback);
  163. })
  164. .on('end', function () { done() })
  165. ;
  166. });
  167. })(endpoint, config);
  168. }
  169. }
  170. // Start bower queue.
  171. queue.start(function (e) {
  172. return done(e);
  173. });
  174. });
  175. // Run private sub-task.
  176. grunt.task.run(['sync:libraries']);
  177. });
  178. }