PageRenderTime 67ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/Gruntfile.js

https://github.com/cbmi/cilantro
JavaScript | 694 lines | 173 code | 30 blank | 491 comment | 0 complexity | 7058d8a27cfec42691f0c48446a8591d MD5 | raw file
  1. /* global require, module, console */
  2. /*
  3. * All source files are created and managed by type in `src`.
  4. *
  5. * There three output directories:
  6. *
  7. * - `local` - Development and testing
  8. * - `build` - Intermediate directory prior to optimizations and/or
  9. * concatentation
  10. * - `dist` - Final output for release purposes. This carries over to the master
  11. * branch to be copied in the root of the repo.
  12. * - `cdn` - Final output for deployment on CDNs. This includes *only* Cilantro
  13. * files, no source maps, and no third-party libraries.
  14. *
  15. * Each Grunt task has a target named `local` which performs copying,
  16. * compiling or symlinking in the `local` directory for development.
  17. * Additional targets may be defined for `build` or `dist` depending on
  18. * the requirements.
  19. *
  20. * Shared options for a task should be defined first, followed by each
  21. * task entry. Each task can define it's own set of options to override
  22. * the shared ones defined for the task.
  23. */
  24. module.exports = function(grunt) {
  25. var shell = require('shelljs');
  26. var pkg = grunt.file.readJSON('package.json');
  27. var run = function(cmd) {
  28. grunt.log.ok(cmd);
  29. shell.exec(cmd);
  30. };
  31. var vendorModules = [
  32. 'jquery',
  33. 'backbone',
  34. 'underscore',
  35. 'marionette',
  36. 'highcharts',
  37. 'bootstrap',
  38. 'json2',
  39. 'loglevel'
  40. ];
  41. grunt.initConfig({
  42. pkg: pkg,
  43. srcDir: 'src',
  44. specDir: 'spec',
  45. localDir: 'local',
  46. buildDir: 'build',
  47. distDir: 'dist',
  48. cdnDir: 'cdn',
  49. serve: {
  50. forever: {
  51. options: {
  52. keepalive: true,
  53. port: 8125
  54. }
  55. },
  56. jasmine: {
  57. options: {
  58. port: 8126
  59. }
  60. }
  61. },
  62. watch: {
  63. grunt: {
  64. tasks: ['local'],
  65. files: ['Gruntfile.js']
  66. },
  67. copy: {
  68. tasks: ['copy:local'],
  69. files: [
  70. '<%= srcDir %>/js/**/**/**/*',
  71. '<%= srcDir %>/css/**/**/**/*',
  72. '<%= srcDir %>/font/**/**/**/*',
  73. '<%= srcDir %>/img/**/**/**/*'
  74. ]
  75. },
  76. tests: {
  77. tasks: ['jasmine:local:build'],
  78. files: ['<%= specDir %>/**/**/**/*']
  79. },
  80. sass: {
  81. tasks: ['sass:local'],
  82. files: ['<%= srcDir %>/scss/**/**/**/*']
  83. }
  84. },
  85. sass: {
  86. options: {
  87. sourcemap: true
  88. },
  89. local: {
  90. options: {
  91. trace: true,
  92. style: 'expanded'
  93. },
  94. files: {
  95. '<%= localDir %>/css/style.css': '<%= srcDir %>/scss/style.scss'
  96. }
  97. },
  98. dist: {
  99. options: {
  100. quiet: true,
  101. style: 'compressed'
  102. },
  103. files: {
  104. '<%= distDir %>/css/style.css': '<%= srcDir %>/scss/style.scss'
  105. }
  106. },
  107. cdn: {
  108. options: {
  109. quiet: true,
  110. sourcemap: false,
  111. style: 'compressed'
  112. },
  113. files: {
  114. '<%= cdnDir %>/css/style.css': '<%= srcDir %>/scss/style.scss'
  115. }
  116. }
  117. },
  118. copy: {
  119. local: {
  120. files: [{
  121. expand: true,
  122. cwd: '<%= srcDir %>/js',
  123. src: ['**/*'],
  124. dest: '<%= localDir %>/js'
  125. }, {
  126. expand: true,
  127. cwd: '<%= srcDir %>/css',
  128. src: ['**/*'],
  129. dest: '<%= localDir %>/css'
  130. }, {
  131. expand: true,
  132. cwd: '<%= srcDir %>/font',
  133. src: ['**/*'],
  134. dest: '<%= localDir %>/font'
  135. }, {
  136. expand: true,
  137. cwd: '<%= srcDir %>/img',
  138. src: ['**/*'],
  139. dest: '<%= localDir %>/img'
  140. }, {
  141. expand: true,
  142. src: ['bower.json', 'package.json'],
  143. dest: '<%= localDir %>'
  144. }]
  145. },
  146. build: {
  147. files: [{
  148. expand: true,
  149. cwd: '<%= srcDir %>/templates',
  150. src: ['**/*'],
  151. dest: '<%= buildDir %>/js/templates'
  152. }, {
  153. expand: true,
  154. cwd: '<%= srcDir %>/js',
  155. src: ['**/*'],
  156. dest: '<%= buildDir %>/js'
  157. }]
  158. },
  159. dist: {
  160. files: [{
  161. expand: true,
  162. cwd: '<%= srcDir %>/css',
  163. src: ['**/*'],
  164. dest: '<%= distDir %>/css'
  165. }, {
  166. expand: true,
  167. cwd: '<%= srcDir %>/font',
  168. src: ['**/*'],
  169. dest: '<%= distDir %>/font'
  170. }, {
  171. expand: true,
  172. cwd: '<%= srcDir %>/img',
  173. src: ['**/*'],
  174. dest: '<%= distDir %>/img'
  175. }, {
  176. expand: true,
  177. src: ['bower.json', 'package.json'],
  178. dest: '<%= distDir %>'
  179. }]
  180. },
  181. cdn: {
  182. files: [{
  183. expand: true,
  184. cwd: '<%= srcDir %>/img',
  185. src: ['checkmark.png'],
  186. dest: '<%= cdnDir %>/img'
  187. }]
  188. }
  189. },
  190. symlink: {
  191. options: {
  192. type: 'dir'
  193. },
  194. templates: {
  195. relativeSrc: '../../<%= srcDir %>/templates',
  196. dest: '<%= localDir %>/js/templates'
  197. }
  198. },
  199. requirejs: {
  200. options: {
  201. mainConfigFile: '<%= srcDir %>/js/cilantro/main.js',
  202. baseUrl: '.',
  203. inlineText: true,
  204. preserveLicenseComments: false,
  205. wrap: false,
  206. logLevel: 1,
  207. throwWhen: {
  208. optimize: true
  209. },
  210. modules: [{
  211. name: pkg.name,
  212. exclude: vendorModules
  213. }],
  214. done: function(done, output) {
  215. var dups = require('rjs-build-analysis').duplicates(output);
  216. if (dups.length > 0) {
  217. grunt.log.subhead('Duplicates found in RequireJS build:');
  218. grunt.log.warn(dups);
  219. done(new Error('r.js built duplicate modules, please ' +
  220. 'check the `excludes` option.'));
  221. }
  222. done();
  223. }
  224. },
  225. dist: {
  226. options: {
  227. appDir: '<%= buildDir %>/js',
  228. dir: '<%= distDir %>/js',
  229. optimize: 'uglify2',
  230. generateSourceMaps: true,
  231. removeCombined: false
  232. }
  233. },
  234. cdn: {
  235. options: {
  236. appDir: '<%= buildDir %>/js',
  237. dir: '<%= cdnDir %>/js',
  238. optimize: 'uglify2',
  239. generateSourceMaps: false,
  240. removeCombined: true
  241. }
  242. }
  243. },
  244. clean: {
  245. local: ['<%= localDir %>'],
  246. build: ['<%= buildDir %>'],
  247. dist: ['<%= distDir %>'],
  248. cdn: ['<%= cdnDir %>'],
  249. postdist: ['<%= distDir %>/js/templates'],
  250. postcdn: [
  251. '<%= cdnDir %>/js/templates',
  252. '<%= cdnDir %>/js/require.js'
  253. ].concat(vendorModules.map(function(mod) {
  254. return '<%= cdnDir %>/js/' + mod + '.js';})),
  255. release: [
  256. '<%= localDir %>/js/build.txt',
  257. '<%= distDir %>/js/build.txt',
  258. '<%= cdnDir %>/js/build.txt'
  259. ]
  260. },
  261. jasmine: {
  262. options: {
  263. specs: '<%= specDir %>/**/**/**/*.js',
  264. host: 'http://127.0.0.1:8126',
  265. helpers: './specConfig.js',
  266. keepRunner: true,
  267. template: require('grunt-template-jasmine-requirejs'),
  268. templateOptions: {
  269. version: '<%= srcDir %>/js/require.js'
  270. }
  271. },
  272. local: {
  273. options: {
  274. templateOptions: {
  275. requireConfigFile: '<%= localDir %>/js/cilantro/main.js',
  276. requireConfig: {
  277. baseUrl: '<%= localDir %>/js'
  278. }
  279. }
  280. }
  281. },
  282. dist: {
  283. options: {
  284. templateOptions: {
  285. requireConfigFile: '<%= distDir %>/js/cilantro/main.js',
  286. requireConfig: {
  287. baseUrl: '<%= distDir %>/js'
  288. }
  289. }
  290. }
  291. }
  292. },
  293. amdcheck: {
  294. local: {
  295. options: {
  296. removeUnusedDependencies: false
  297. },
  298. files: [{
  299. expand: true,
  300. src: ['<%= localDir %>/js/**/**/**/**/*.js']
  301. }]
  302. }
  303. },
  304. jshint: {
  305. options: {
  306. camelcase: true,
  307. immed: true,
  308. indent: 4,
  309. latedef: true,
  310. noarg: true,
  311. noempty: true,
  312. undef: true,
  313. unused: true,
  314. trailing: true,
  315. maxdepth: 3,
  316. maxlen: 90,
  317. browser: true,
  318. eqeqeq: true,
  319. globals: {
  320. define: true,
  321. require: true
  322. },
  323. reporter: require('jshint-stylish'),
  324. ignores: [
  325. '<%= srcDir %>/js/backbone.js',
  326. '<%= srcDir %>/js/bootstrap.js',
  327. '<%= srcDir %>/js/highcharts.js',
  328. '<%= srcDir %>/js/jquery.js',
  329. '<%= srcDir %>/js/json2.js',
  330. '<%= srcDir %>/js/loglevel.js',
  331. '<%= srcDir %>/js/marionette.js',
  332. '<%= srcDir %>/js/require.js',
  333. '<%= srcDir %>/js/text.js',
  334. '<%= srcDir %>/js/tpl.js',
  335. '<%= srcDir %>/js/underscore.js',
  336. '<%= srcDir %>/js/plugins/bootstrap-datepicker.js',
  337. '<%= srcDir %>/js/plugins/jquery-easing.js',
  338. '<%= srcDir %>/js/plugins/jquery-ui.js'
  339. ],
  340. },
  341. src: ['<%= srcDir %>/js/**/**/**/**/*.js']
  342. }
  343. });
  344. grunt.loadNpmTasks('grunt-contrib-jasmine');
  345. grunt.loadNpmTasks('grunt-contrib-watch');
  346. grunt.loadNpmTasks('grunt-contrib-sass');
  347. grunt.loadNpmTasks('grunt-contrib-jshint');
  348. grunt.loadNpmTasks('grunt-contrib-requirejs');
  349. grunt.loadNpmTasks('grunt-contrib-copy');
  350. grunt.loadNpmTasks('grunt-contrib-clean');
  351. grunt.loadNpmTasks('grunt-symlink');
  352. grunt.loadNpmTasks('grunt-amdcheck');
  353. grunt.registerMultiTask('serve', 'Run a Node server for testing', function() {
  354. var http = require('http');
  355. var path = require('path');
  356. var url = require('url');
  357. var fs = require('fs');
  358. var contentTypes = {
  359. '.js': 'text/javascript',
  360. '.css': 'text/css',
  361. '.html': 'text/html'
  362. };
  363. var options = this.options({
  364. hostname: 'localhost',
  365. base: '.',
  366. port: 8125,
  367. keepalive: false
  368. });
  369. var serveResponse = function(filename, response) {
  370. var extname = path.extname(filename);
  371. var contentType = contentTypes[extname] || 'text/plain';
  372. response.writeHead(200, {'Content-Type': contentType});
  373. var stream = fs.createReadStream(filename);
  374. stream.pipe(response);
  375. };
  376. var serve404 = function(filename, response) {
  377. response.writeHead(404);
  378. response.end();
  379. };
  380. var serve500 = function(filename, response) {
  381. response.writeHead(500);
  382. response.end();
  383. };
  384. var server = http.createServer(function(request, response) {
  385. var uri = url.parse(request.url).pathname;
  386. var filename = path.join(options.base, uri);
  387. fs.exists(filename, function(exists) {
  388. if (exists) {
  389. fs.readFile(filename, function(error) {
  390. if (error) {
  391. if (error.code === 'EISDIR') {
  392. filename += 'index.html';
  393. fs.exists(filename, function(exists) {
  394. if (exists) {
  395. fs.readFile(filename, function(error) {
  396. if (error) {
  397. serve500(filename, response);
  398. }
  399. else {
  400. serveResponse(filename, response);
  401. }
  402. });
  403. }
  404. else {
  405. serve404(filename, response);
  406. }
  407. });
  408. }
  409. else {
  410. serve500(filename, response);
  411. }
  412. }
  413. else {
  414. serveResponse(filename, response);
  415. }
  416. });
  417. }
  418. else {
  419. serve404(filename, response);
  420. }
  421. });
  422. });
  423. if (options.hostname === '*') {
  424. options.hostname = null;
  425. }
  426. if (options.port === '?') {
  427. options.port = 0;
  428. }
  429. var done = this.async();
  430. return server.listen(options.port, options.hostname).on('listening', function() {
  431. var address = server.address();
  432. var hostname = server.hostname || 'localhost';
  433. if (!options.keepalive) {
  434. done();
  435. }
  436. else {
  437. grunt.log.writeln('Listening on ' + hostname + ':' +
  438. address.port + '...');
  439. }
  440. }).on('error', function(error) {
  441. if (error.code === 'EADDRINUSE') {
  442. grunt.fatal('Port ' + options.port + ' is already in use by ' +
  443. 'another process.');
  444. }
  445. else {
  446. grunt.fatal(error);
  447. }
  448. });
  449. });
  450. grunt.registerTask('local', 'Creates a build for local development and testing', [
  451. 'sass:local',
  452. 'copy:local',
  453. 'symlink',
  454. 'jasmine:local:build'
  455. ]);
  456. grunt.registerTask('dist', 'Creates a build for distribution', [
  457. 'clean:build',
  458. 'copy:build',
  459. 'clean:dist',
  460. 'requirejs:dist',
  461. 'sass:dist',
  462. 'copy:dist',
  463. 'clean:postdist'
  464. ]);
  465. grunt.registerTask('cdn', 'Creates a build for CDN distribution', [
  466. 'clean:build',
  467. 'copy:build',
  468. 'clean:cdn',
  469. 'requirejs:cdn',
  470. 'sass:cdn',
  471. 'copy:cdn',
  472. 'clean:postcdn',
  473. ]);
  474. grunt.registerTask(
  475. 'work',
  476. 'Performs the initial local build and starts a watch process',
  477. [
  478. 'local',
  479. 'watch'
  480. ]
  481. );
  482. grunt.registerTask('test', 'Runs the headless test suite', [
  483. 'copy:local',
  484. 'symlink',
  485. 'serve:jasmine',
  486. 'jasmine:local'
  487. ]);
  488. var changeVersion = function(fname, version) {
  489. var contents = grunt.file.readJSON(fname);
  490. var current = contents.version;
  491. contents.version = version;
  492. grunt.file.write(fname, JSON.stringify(contents, null, 2));
  493. grunt.log.ok('' + fname + ': ' + current + ' => ' + version);
  494. };
  495. var replaceVersion = function(fname, current, version) {
  496. var options = {encoding: 'utf8'};
  497. var content = grunt.file.read(fname, options);
  498. var regexp = new RegExp("version: '" + current + "'");
  499. if (!regexp.test(content)) {
  500. grunt.fatal('File contents does not match version');
  501. }
  502. content = content.replace(regexp, "version: '" + version + "'");
  503. grunt.file.write(fname, content, options);
  504. grunt.log.ok('' + fname + ': ' + current + ' => ' + version);
  505. };
  506. // Uses package.json as the canonical version.
  507. grunt.registerTask('bump-final', 'Updates the version to final', function() {
  508. var svutil = require('semver-utils');
  509. var current = pkg.version;
  510. var version = svutil.parse(pkg.version);
  511. // Ensure it is able to be bumped.
  512. if (version.release !== 'beta') {
  513. grunt.fatal('Version ' + current + ' not beta. Is this ready for release?');
  514. }
  515. // Remove release and build strings.
  516. version.release = '';
  517. version.build = '';
  518. pkg.version = svutil.stringify(version);
  519. replaceVersion('src/js/cilantro/core.js', current, pkg.version);
  520. var files = ['package.json', 'bower.json'];
  521. for (var i = 0; i < files.length; i++) {
  522. changeVersion(files[i], pkg.version);
  523. }
  524. });
  525. grunt.registerTask(
  526. 'bump-patch',
  527. 'Updates the version to next patch-release',
  528. function() {
  529. var svutil = require('semver-utils');
  530. var current = pkg.version;
  531. var version = svutil.parse(pkg.version);
  532. // Ensure it is able to be bumped.
  533. console.log(version.release);
  534. if (version.release) {
  535. grunt.fatal('Version ' + current + ' not final. Should this ' +
  536. 'be bumped to a pre-release?');
  537. }
  538. // Remove release and build strings
  539. version.patch = '' + (parseInt(version.patch, 10) + 1);
  540. version.release = 'beta';
  541. version.build = '';
  542. pkg.version = svutil.stringify(version);
  543. replaceVersion('src/js/cilantro/core.js', current, pkg.version);
  544. var files = ['package.json', 'bower.json'];
  545. for (var i = 0; i < files.length; i++) {
  546. changeVersion(files[i], pkg.version);
  547. }
  548. run('git add bower.json package.json src/js/cilantro/core.js');
  549. run("git commit -s -m '" +
  550. [version.major, version.minor, version.patch].join('.') + " Beta'");
  551. }
  552. );
  553. grunt.registerTask('tag-release', 'Create a release on master', function() {
  554. run('git add bower.json package.json src/js/cilantro/core.js cdn/');
  555. run("git commit -s -m '" + pkg.version + " Release'");
  556. run('git tag ' + pkg.version);
  557. });
  558. grunt.registerTask(
  559. 'release-binaries',
  560. 'Create a release binary for upload',
  561. function() {
  562. var releaseDirName = '' + pkg.name + '-' + pkg.version;
  563. run('rm -rf ' + pkg.name);
  564. run('mkdir -p ' + pkg.name);
  565. run('cp -r dist/* ' + pkg.name);
  566. run('zip -r ' + releaseDirName + '.zip ' + pkg.name);
  567. run('tar -Hzcf ' + releaseDirName + '.tar.gz ' + pkg.name);
  568. run('rm -rf ' + pkg.name);
  569. run('mkdir -p ' + pkg.name);
  570. run('cp -r local/* ' + pkg.name);
  571. run('zip -r ' + releaseDirName + '-src.zip ' + pkg.name);
  572. run('tar -Hzcf ' + releaseDirName + '-src.tar.gz ' + pkg.name);
  573. run('rm -rf ' + pkg.name);
  574. }
  575. );
  576. grunt.registerTask(
  577. 'release-help',
  578. 'Prints the post-release steps',
  579. function() {
  580. grunt.log.ok('Push the code and tags: git push && git push --tags');
  581. grunt.log.ok('Go to ' + pkg.homepage + '/releases to update the ' +
  582. 'release descriptions and upload the binaries.');
  583. }
  584. );
  585. grunt.registerTask(
  586. 'release',
  587. 'Builds distribution files, creates release binaries, and creates Git tag',
  588. [
  589. 'bump-final',
  590. 'local',
  591. 'dist',
  592. 'cdn',
  593. 'clean:release',
  594. 'release-binaries',
  595. 'tag-release',
  596. 'release-help',
  597. 'bump-patch'
  598. ]
  599. );
  600. };