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