<commit_before>// Copyright 2014-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

const { join } = require('path');
const importOnce = require('node-sass-import-once');
const rimraf = require('rimraf');
const mkdirp = require('mkdirp');
const spectron = require('spectron');
const asar = require('asar');
const fs = require('fs');
const assert = require('assert');
const sass = require('node-sass');
const packageJson = require('./package.json');

/* eslint-disable more/no-then, no-console  */

module.exports = grunt => {
  const bower = grunt.file.readJSON('bower.json');
  const components = [];
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const i in bower.concat.app) {
    components.push(bower.concat.app[i]);
  }

  const libtextsecurecomponents = [];
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const i in bower.concat.libtextsecure) {
    libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
  }

  grunt.loadNpmTasks('grunt-sass');

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      components: {
        src: components,
        dest: 'js/components.js',
      },
      libtextsecurecomponents: {
        src: libtextsecurecomponents,
        dest: 'libtextsecure/components.js',
      },
      test: {
        src: [
          'node_modules/mocha/mocha.js',
          'node_modules/chai/chai.js',
          'test/_test.js',
        ],
        dest: 'test/test.js',
      },
      // TODO: Move errors back down?
      libtextsecure: {
        options: {
          banner: ';(function() {\n',
          footer: '})();\n',
        },
        src: [
          'libtextsecure/libsignal-protocol.js',
          'libtextsecure/protocol_wrapper.js',

          'libtextsecure/storage/user.js',
          'libtextsecure/storage/unprocessed.js',
          'libtextsecure/protobufs.js',
        ],
        dest: 'js/libtextsecure.js',
      },
      libtextsecuretest: {
        src: [
          'node_modules/jquery/dist/jquery.js',
          'components/mock-socket/dist/mock-socket.js',
          'node_modules/mocha/mocha.js',
          'node_modules/chai/chai.js',
          'libtextsecure/test/_test.js',
        ],
        dest: 'libtextsecure/test/test.js',
      },
    },
    sass: {
      options: {
        implementation: sass,
        sourceMap: true,
        importer: importOnce,
      },
      dev: {
        files: {
          'stylesheets/manifest.css': 'stylesheets/manifest.scss',
          'stylesheets/manifest_bridge.css': 'stylesheets/manifest_bridge.scss',
        },
      },
    },
    copy: {
      deps: {
        files: [
          {
            src: 'components/mp3lameencoder/lib/Mp3LameEncoder.js',
            dest: 'js/Mp3LameEncoder.min.js',
          },
          {
            src: 'components/webaudiorecorder/lib/WebAudioRecorderMp3.js',
            dest: 'js/WebAudioRecorderMp3.js',
          },
        ],
      },
    },
    watch: {
      libtextsecure: {
        files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
        tasks: ['concat:libtextsecure'],
      },
      protobuf: {
        files: ['./protos/SignalService.proto'],
        tasks: ['exec:build-protobuf'],
      },
      sass: {
        files: ['./stylesheets/*.scss', './stylesheets/**/*.scss'],
        tasks: ['sass'],
      },
    },
    exec: {
      'tx-pull-new': {
        cmd: 'tx pull -a --minimum-perc=80',
      },
      'tx-pull': {
        cmd: 'tx pull',
      },
      transpile: {
        cmd: 'yarn transpile',
      },
      'build-protobuf': {
        cmd: 'yarn build-protobuf',
      },
    },
    'test-release': {
      osx: {
        archive: `mac/${packageJson.productName}.app/Contents/Resources/app.asar`,
        exe: `mac/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
      },
      mas: {
        archive: 'mas/Signal.app/Contents/Resources/app.asar',
        exe: `mas/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
      },
      linux: {
        archive: 'linux-unpacked/resources/app.asar',
        exe: `linux-unpacked/${packageJson.name}`,
      },
      win: {
        archive: 'win-unpacked/resources/app.asar',
        exe: `win-unpacked/${packageJson.productName}.exe`,
      },
    },
    gitinfo: {}, // to be populated by grunt gitinfo
  });

  Object.keys(grunt.config.get('pkg').devDependencies).forEach(key => {
    if (/^grunt(?!(-cli)?$)/.test(key)) {
      // ignore grunt and grunt-cli
      grunt.loadNpmTasks(key);
    }
  });

  // Transifex does not understand placeholders, so this task patches all non-en
  // locales with missing placeholders
  grunt.registerTask('locale-patch', () => {
    const en = grunt.file.readJSON('_locales/en/messages.json');
    grunt.file.recurse('_locales', (abspath, rootdir, subdir, filename) => {
      if (subdir === 'en' || filename !== 'messages.json') {
        return;
      }
      const messages = grunt.file.readJSON(abspath);

      // eslint-disable-next-line no-restricted-syntax
      for (const key in messages) {
        if (en[key] !== undefined && messages[key] !== undefined) {
          if (
            en[key].placeholders !== undefined &&
            messages[key].placeholders === undefined
          ) {
            messages[key].placeholders = en[key].placeholders;
          }
        }
      }

      grunt.file.write(abspath, `${JSON.stringify(messages, null, 4)}\n`);
    });
  });

  grunt.registerTask('getExpireTime', () => {
    grunt.task.requires('gitinfo');
    const gitinfo = grunt.config.get('gitinfo');
    const committed = gitinfo.local.branch.current.lastCommitTime;
    const time = Date.parse(committed) + 1000 * 60 * 60 * 24 * 90;
    grunt.file.write(
      'config/local-production.json',
      `${JSON.stringify({ buildExpiration: time })}\n`
    );
  });

  grunt.registerTask('clean-release', () => {
    rimraf.sync('release');
    mkdirp.sync('release');
  });

  function runTests(environment, cb) {
    let failure;
    const { Application } = spectron;
    const electronBinary =
      process.platform === 'win32' ? 'electron.cmd' : 'electron';

    const path = join(__dirname, 'node_modules', '.bin', electronBinary);
    const args = [join(__dirname, 'main.js')];
    console.log('Starting path', path, 'with args', args);
    const app = new Application({
      path,
      args,
      env: {
        NODE_ENV: environment,
      },
      requireName: 'unused',
    });

    function getMochaResults() {
      // eslint-disable-next-line no-undef
      return window.mochaResults;
    }

    app
      .start()
      .then(() => {
        console.log('App started. Now waiting for test results...');
        return app.client.waitUntil(
          () =>
            app.client
              .execute(getMochaResults)
              .then(data => Boolean(data.value)),
          25000,
          'Expected to find window.mochaResults set!'
        );
      })
      .then(() => app.client.execute(getMochaResults))
      .then(data => {
        const results = data.value;
        if (!results) {
          failure = () => grunt.fail.fatal("Couldn't extract test results.");
          return app.client.log('browser');
        }
        if (results.failures > 0) {
          console.error(results.reports);
          failure = () =>
            grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
          return app.client.log('browser');
        }
        grunt.log.ok(`${results.passes} tests passed.`);
        return null;
      })
      .then(logs => {
        if (logs) {
          console.error();
          console.error('Because tests failed, printing browser logs:');
          console.error(logs);
        }
      })
      .catch(error => {
        failure = () =>
          grunt.fail.fatal(
            `Something went wrong: ${error.message} ${error.stack}`
          );
      })
      .then(() => {
        // We need to use the failure variable and this early stop to clean up before
        // shutting down. Grunt's fail methods are the only way to set the return value,
        // but they shut the process down immediately!
        if (failure) {
          console.log();
          console.log('Main process logs:');
          return app.client.getMainProcessLogs().then(logs => {
            logs.forEach(log => {
              console.log(log);
            });

            return app.stop();
          });
        }
        return app.stop();
      })
      .then(() => {
        if (failure) {
          failure();
        }
        cb();
      })
      .catch(error => {
        console.error('Second-level error:', error.message, error.stack);
        if (failure) {
          failure();
        }
        cb();
      });
  }

  grunt.registerTask(
    'unit-tests',
    'Run unit tests w/Electron',
    function thisNeeded() {
      const environment = grunt.option('env') || 'test';
      const done = this.async();

      runTests(environment, done);
    }
  );

  grunt.registerTask(
    'lib-unit-tests',
    'Run libtextsecure unit tests w/Electron',
    function thisNeeded() {
      const environment = grunt.option('env') || 'test-lib';
      const done = this.async();

      runTests(environment, done);
    }
  );

  grunt.registerMultiTask(
    'test-release',
    'Test packaged releases',
    function thisNeeded() {
      const dir = grunt.option('dir') || 'release';
      const environment = grunt.option('env') || 'production';
      const config = this.data;
      const archive = [dir, config.archive].join('/');
      const files = [
        'config/default.json',
        `config/${environment}.json`,
        `config/local-${environment}.json`,
      ];

      console.log(this.target, archive);
      const releaseFiles = files.concat(config.files || []);
      releaseFiles.forEach(fileName => {
        console.log(fileName);
        try {
          asar.statFile(archive, fileName);
          return true;
        } catch (e) {
          console.log(e);
          throw new Error(`Missing file ${fileName}`);
        }
      });

      if (config.appUpdateYML) {
        const appUpdateYML = [dir, config.appUpdateYML].join('/');
        if (fs.existsSync(appUpdateYML)) {
          console.log('auto update ok');
        } else {
          throw new Error(`Missing auto update config ${appUpdateYML}`);
        }
      }

      const done = this.async();
      // A simple test to verify a visible window is opened with a title
      const { Application } = spectron;

      const path = [dir, config.exe].join('/');
      console.log('Starting path', path);
      const app = new Application({
        path,
      });

      const sleep = millis =>
        new Promise(resolve => setTimeout(resolve, millis));

      Promise.race([app.start(), sleep(15000)])
        .then(() => {
          if (!app.isRunning()) {
            throw new Error('Application failed to start');
          }

          return app.client.getWindowCount();
        })
        .then(count => {
          assert.equal(count, 1);
          console.log('window opened');
        })
        .then(() =>
          // Get the window's title
          app.client.getTitle()
        )
        .then(title => {
          // Verify the window's title
          assert.equal(title, packageJson.productName);
          console.log('title ok');
        })
        .then(() => {
          assert(
            app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1
          );
          console.log('environment ok');
        })
        .then(
          () =>
            // Successfully completed test
            app.stop(),
          error =>
            // Test failed!
            app.stop().then(() => {
              grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
            })
        )
        .catch(error => {
          console.log('Main process logs:');
          app.client.getMainProcessLogs().then(logs => {
            logs.forEach(log => {
              console.log(log);
            });

            // Test failed!
            grunt.fail.fatal(`Failure! ${error.message} ${error.stack}`);
          });
        })
        .then(done);
    }
  );

  grunt.registerTask('tx', [
    'exec:tx-pull-new',
    'exec:tx-pull',
    'locale-patch',
  ]);
  grunt.registerTask('dev', ['default', 'watch']);
  grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
  grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
  grunt.registerTask('default', [
    'exec:build-protobuf',
    'exec:transpile',
    'concat',
    'copy:deps',
    'sass',
    'date',
  ]);
};
<commit_msg>Attempt to fix flakiness with window title assertion<commit_after>// Copyright 2014-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

const { join } = require('path');
const importOnce = require('node-sass-import-once');
const rimraf = require('rimraf');
const mkdirp = require('mkdirp');
const spectron = require('spectron');
const asar = require('asar');
const fs = require('fs');
const assert = require('assert');
const sass = require('node-sass');
const packageJson = require('./package.json');

/* eslint-disable more/no-then, no-console  */

module.exports = grunt => {
  const bower = grunt.file.readJSON('bower.json');
  const components = [];
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const i in bower.concat.app) {
    components.push(bower.concat.app[i]);
  }

  const libtextsecurecomponents = [];
  // eslint-disable-next-line guard-for-in, no-restricted-syntax
  for (const i in bower.concat.libtextsecure) {
    libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
  }

  grunt.loadNpmTasks('grunt-sass');

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      components: {
        src: components,
        dest: 'js/components.js',
      },
      libtextsecurecomponents: {
        src: libtextsecurecomponents,
        dest: 'libtextsecure/components.js',
      },
      test: {
        src: [
          'node_modules/mocha/mocha.js',
          'node_modules/chai/chai.js',
          'test/_test.js',
        ],
        dest: 'test/test.js',
      },
      // TODO: Move errors back down?
      libtextsecure: {
        options: {
          banner: ';(function() {\n',
          footer: '})();\n',
        },
        src: [
          'libtextsecure/libsignal-protocol.js',
          'libtextsecure/protocol_wrapper.js',

          'libtextsecure/storage/user.js',
          'libtextsecure/storage/unprocessed.js',
          'libtextsecure/protobufs.js',
        ],
        dest: 'js/libtextsecure.js',
      },
      libtextsecuretest: {
        src: [
          'node_modules/jquery/dist/jquery.js',
          'components/mock-socket/dist/mock-socket.js',
          'node_modules/mocha/mocha.js',
          'node_modules/chai/chai.js',
          'libtextsecure/test/_test.js',
        ],
        dest: 'libtextsecure/test/test.js',
      },
    },
    sass: {
      options: {
        implementation: sass,
        sourceMap: true,
        importer: importOnce,
      },
      dev: {
        files: {
          'stylesheets/manifest.css': 'stylesheets/manifest.scss',
          'stylesheets/manifest_bridge.css': 'stylesheets/manifest_bridge.scss',
        },
      },
    },
    copy: {
      deps: {
        files: [
          {
            src: 'components/mp3lameencoder/lib/Mp3LameEncoder.js',
            dest: 'js/Mp3LameEncoder.min.js',
          },
          {
            src: 'components/webaudiorecorder/lib/WebAudioRecorderMp3.js',
            dest: 'js/WebAudioRecorderMp3.js',
          },
        ],
      },
    },
    watch: {
      libtextsecure: {
        files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'],
        tasks: ['concat:libtextsecure'],
      },
      protobuf: {
        files: ['./protos/SignalService.proto'],
        tasks: ['exec:build-protobuf'],
      },
      sass: {
        files: ['./stylesheets/*.scss', './stylesheets/**/*.scss'],
        tasks: ['sass'],
      },
    },
    exec: {
      'tx-pull-new': {
        cmd: 'tx pull -a --minimum-perc=80',
      },
      'tx-pull': {
        cmd: 'tx pull',
      },
      transpile: {
        cmd: 'yarn transpile',
      },
      'build-protobuf': {
        cmd: 'yarn build-protobuf',
      },
    },
    'test-release': {
      osx: {
        archive: `mac/${packageJson.productName}.app/Contents/Resources/app.asar`,
        exe: `mac/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
      },
      mas: {
        archive: 'mas/Signal.app/Contents/Resources/app.asar',
        exe: `mas/${packageJson.productName}.app/Contents/MacOS/${packageJson.productName}`,
      },
      linux: {
        archive: 'linux-unpacked/resources/app.asar',
        exe: `linux-unpacked/${packageJson.name}`,
      },
      win: {
        archive: 'win-unpacked/resources/app.asar',
        exe: `win-unpacked/${packageJson.productName}.exe`,
      },
    },
    gitinfo: {}, // to be populated by grunt gitinfo
  });

  Object.keys(grunt.config.get('pkg').devDependencies).forEach(key => {
    if (/^grunt(?!(-cli)?$)/.test(key)) {
      // ignore grunt and grunt-cli
      grunt.loadNpmTasks(key);
    }
  });

  // Transifex does not understand placeholders, so this task patches all non-en
  // locales with missing placeholders
  grunt.registerTask('locale-patch', () => {
    const en = grunt.file.readJSON('_locales/en/messages.json');
    grunt.file.recurse('_locales', (abspath, rootdir, subdir, filename) => {
      if (subdir === 'en' || filename !== 'messages.json') {
        return;
      }
      const messages = grunt.file.readJSON(abspath);

      // eslint-disable-next-line no-restricted-syntax
      for (const key in messages) {
        if (en[key] !== undefined && messages[key] !== undefined) {
          if (
            en[key].placeholders !== undefined &&
            messages[key].placeholders === undefined
          ) {
            messages[key].placeholders = en[key].placeholders;
          }
        }
      }

      grunt.file.write(abspath, `${JSON.stringify(messages, null, 4)}\n`);
    });
  });

  grunt.registerTask('getExpireTime', () => {
    grunt.task.requires('gitinfo');
    const gitinfo = grunt.config.get('gitinfo');
    const committed = gitinfo.local.branch.current.lastCommitTime;
    const time = Date.parse(committed) + 1000 * 60 * 60 * 24 * 90;
    grunt.file.write(
      'config/local-production.json',
      `${JSON.stringify({ buildExpiration: time })}\n`
    );
  });

  grunt.registerTask('clean-release', () => {
    rimraf.sync('release');
    mkdirp.sync('release');
  });

  function runTests(environment, cb) {
    let failure;
    const { Application } = spectron;
    const electronBinary =
      process.platform === 'win32' ? 'electron.cmd' : 'electron';

    const path = join(__dirname, 'node_modules', '.bin', electronBinary);
    const args = [join(__dirname, 'main.js')];
    console.log('Starting path', path, 'with args', args);
    const app = new Application({
      path,
      args,
      env: {
        NODE_ENV: environment,
      },
      requireName: 'unused',
    });

    function getMochaResults() {
      // eslint-disable-next-line no-undef
      return window.mochaResults;
    }

    app
      .start()
      .then(() => {
        console.log('App started. Now waiting for test results...');
        return app.client.waitUntil(
          () =>
            app.client
              .execute(getMochaResults)
              .then(data => Boolean(data.value)),
          25000,
          'Expected to find window.mochaResults set!'
        );
      })
      .then(() => app.client.execute(getMochaResults))
      .then(data => {
        const results = data.value;
        if (!results) {
          failure = () => grunt.fail.fatal("Couldn't extract test results.");
          return app.client.log('browser');
        }
        if (results.failures > 0) {
          console.error(results.reports);
          failure = () =>
            grunt.fail.fatal(`Found ${results.failures} failing unit tests.`);
          return app.client.log('browser');
        }
        grunt.log.ok(`${results.passes} tests passed.`);
        return null;
      })
      .then(logs => {
        if (logs) {
          console.error();
          console.error('Because tests failed, printing browser logs:');
          console.error(logs);
        }
      })
      .catch(error => {
        failure = () =>
          grunt.fail.fatal(
            `Something went wrong: ${error.message} ${error.stack}`
          );
      })
      .then(() => {
        // We need to use the failure variable and this early stop to clean up before
        // shutting down. Grunt's fail methods are the only way to set the return value,
        // but they shut the process down immediately!
        if (failure) {
          console.log();
          console.log('Main process logs:');
          return app.client.getMainProcessLogs().then(logs => {
            logs.forEach(log => {
              console.log(log);
            });

            return app.stop();
          });
        }
        return app.stop();
      })
      .then(() => {
        if (failure) {
          failure();
        }
        cb();
      })
      .catch(error => {
        console.error('Second-level error:', error.message, error.stack);
        if (failure) {
          failure();
        }
        cb();
      });
  }

  grunt.registerTask(
    'unit-tests',
    'Run unit tests w/Electron',
    function thisNeeded() {
      const environment = grunt.option('env') || 'test';
      const done = this.async();

      runTests(environment, done);
    }
  );

  grunt.registerTask(
    'lib-unit-tests',
    'Run libtextsecure unit tests w/Electron',
    function thisNeeded() {
      const environment = grunt.option('env') || 'test-lib';
      const done = this.async();

      runTests(environment, done);
    }
  );

  grunt.registerMultiTask(
    'test-release',
    'Test packaged releases',
    function thisNeeded() {
      const dir = grunt.option('dir') || 'release';
      const environment = grunt.option('env') || 'production';
      const config = this.data;
      const archive = [dir, config.archive].join('/');
      const files = [
        'config/default.json',
        `config/${environment}.json`,
        `config/local-${environment}.json`,
      ];

      console.log(this.target, archive);
      const releaseFiles = files.concat(config.files || []);
      releaseFiles.forEach(fileName => {
        console.log(fileName);
        try {
          asar.statFile(archive, fileName);
          return true;
        } catch (e) {
          console.log(e);
          throw new Error(`Missing file ${fileName}`);
        }
      });

      if (config.appUpdateYML) {
        const appUpdateYML = [dir, config.appUpdateYML].join('/');
        if (fs.existsSync(appUpdateYML)) {
          console.log('auto update ok');
        } else {
          throw new Error(`Missing auto update config ${appUpdateYML}`);
        }
      }

      const done = this.async();
      // A simple test to verify a visible window is opened with a title
      const { Application } = spectron;

      const path = [dir, config.exe].join('/');
      console.log('Starting path', path);
      const app = new Application({
        path,
      });

      const sleep = millis =>
        new Promise(resolve => setTimeout(resolve, millis));

      Promise.race([app.start(), sleep(15000)])
        .then(() => {
          if (!app.isRunning()) {
            throw new Error('Application failed to start');
          }

          return app.client.getWindowCount();
        })
        .then(count => {
          assert.equal(count, 1);
          console.log('window opened');
        })
        .then(() =>
          // Verify the window's title
          app.client.waitUntil(
            async () =>
              (await app.client.getTitle()) === packageJson.productName,
            {
              timeoutMsg: `Expected window title to be ${JSON.stringify(
                packageJson.productName
              )}`,
            }
          )
        )
        .then(() => {
          console.log('title ok');
        })
        .then(() => {
          assert(
            app.chromeDriver.logLines.indexOf(`NODE_ENV ${environment}`) > -1
          );
          console.log('environment ok');
        })
        .then(
          () =>
            // Successfully completed test
            app.stop(),
          error =>
            // Test failed!
            app.stop().then(() => {
              grunt.fail.fatal(`Test failed: ${error.message} ${error.stack}`);
            })
        )
        .catch(error => {
          console.log('Main process logs:');
          app.client.getMainProcessLogs().then(logs => {
            logs.forEach(log => {
              console.log(log);
            });

            // Test failed!
            grunt.fail.fatal(`Failure! ${error.message} ${error.stack}`);
          });
        })
        .then(done);
    }
  );

  grunt.registerTask('tx', [
    'exec:tx-pull-new',
    'exec:tx-pull',
    'locale-patch',
  ]);
  grunt.registerTask('dev', ['default', 'watch']);
  grunt.registerTask('test', ['unit-tests', 'lib-unit-tests']);
  grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
  grunt.registerTask('default', [
    'exec:build-protobuf',
    'exec:transpile',
    'concat',
    'copy:deps',
    'sass',
    'date',
  ]);
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014-2022 SAP SE
 */

'use strict';

var path = require('path');
var moment = require('moment');
var semver = require('semver');

module.exports = function(grunt) {

	// Force unix linefeeds (see https://github.com/gruntjs/grunt/issues/1123)
	// (grunt uses grunt.util.normalizelf when processing templates)
	grunt.util.linefeed = '\n';

	// Log time how long tasks take
	require('grunt-timer').init(grunt, {
		deferLogs: true,
		color: 'cyan'
	});

	// Check for valid required Node.js version from package.json
	// npm does not validate this within the project itself; only if this project would be installed as a dependency (which is not the case as of now)
	var pkg = grunt.file.readJSON(__dirname + "/package.json");
	if (pkg.engines && pkg.engines.node && !semver.satisfies(process.version, pkg.engines.node)) {
		grunt.log.error('!!! WARNING !!!');
		grunt.log.error('Unsupported Node.js version: wanted "' + pkg.engines.node + '" (current: "' + process.version + '")');
		grunt.log.error('Please update your Node.js installation!');
	}

	// Load all custom tasks from grunt/tasks dir
	grunt.loadTasks(path.join(process.cwd(), 'grunt/tasks'));

	// set default options
	grunt.option('production', grunt.option('production') || false);
	if (typeof grunt.option('minify-css') === 'undefined') {
		grunt.option('minify-css', grunt.option('production'));
	}
	if (typeof grunt.option('include-test-resources') === 'undefined') {
		grunt.option('include-test-resources', grunt.option('no-production'));
	}

	// set of libraries to use (e.g. --libs=sap.ui.core)
	var libsOption = grunt.option('libs');
	var libs = libsOption ? libsOption.split(',') : null;

	// the gruntdata contains the configuration for the build part
	// we distinguish here between a testsuite which is an application
	// and the libraries which are the re-use modules
	var gruntData = {

		buildtime: moment().utc().format('YYYYMMDDHHmmss'),
		lastchange: '',

		testsuite: {
			name: 'testsuite',
			path: 'src/testsuite'
		},
		allLibraries: [
			{
				name: 'sap.ui.core',
				path: 'src/sap.ui.core',
				preload: {
					src: [

						'**/*.js',

						// configuration copied from sap/ui/core/.library

						// files are already part of sap-ui-core.js
						'!ui5loader*.js',

						// exclude all merged files as well as the top level jQuery copy (not visible in OpenUI5)
						'!jquery-*.js',
						'!sap-*',
						// CorePlugin, shouldn't be installed by default
						'!sap/ui/core/plugin/DeclarativeSupport.js',
						'!sap/ui/core/plugin/LessSupport.js',
						// exclude non-productive code
						'!sap/ui/debug/**',
						'!sap/ui/core/support/**',
						'!sap/ui/qunit/**',
						'!sap/ui/test/**',
						'!testsuite/**',
						// Ignore substitutes for moved third party libs
						'!jquery-ui-core.js',
						'!jquery-ui-datepicker.js',
						'!jquery-ui-position.js',
						'!sap/ui/model/odata/datajs.js',
						// Third party libs which should NOT be part of the all-in-one file
						'!sap/ui/thirdparty/blanket.js',
						// '!sap/ui/thirdparty/crossroads.js'
						// '!sap/ui/thirdparty/caja-html-sanitizer.js'
						'!sap/ui/thirdparty/d3.js',
						'!sap/ui/thirdparty/datajs.js',
						'!sap/ui/thirdparty/handlebars.js',
						// '!sap/ui/thirdparty/hasher.js'
						'!sap/ui/thirdparty/IPv6.js',
						'!sap/ui/thirdparty/iscroll.js',
						'!sap/ui/thirdparty/iscroll-lite.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-widget.js',
						'!sap/ui/thirdparty/jqueryui/jquery-effect*.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-blind.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-bounce.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-clip.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-drop.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-explode.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-fade.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-fold.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-highlight.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-pulsate.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-scale.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-shake.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-slide.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-transfer.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-mouse.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-draggable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-resizable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-selectable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-sortable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-droppable.js',
						'!sap/ui/thirdparty/jszip.js',
						'!sap/ui/thirdparty/klay.js',
						'!sap/ui/thirdparty/less.js',
						'!sap/ui/thirdparty/mobify-carousel.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-core.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-scroller.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-datetime.js',
						'!sap/ui/thirdparty/punycode.js',
						'!sap/ui/thirdparty/qunit-2.js',
						'!sap/ui/thirdparty/qunit-composite.js',
						'!sap/ui/thirdparty/qunit-reporter-junit.js',
						'!sap/ui/thirdparty/qunit.js',
						'!sap/ui/thirdparty/RequestRecorder.js',
						'!sap/ui/thirdparty/require.js',
						'!sap/ui/thirdparty/SecondLevelDomains.js',
						// '!sap/ui/thirdparty/signals.js'
						'!sap/ui/thirdparty/sinon-4.js',
						'!sap/ui/thirdparty/sinon-ie.js',
						'!sap/ui/thirdparty/sinon-qunit.js',
						'!sap/ui/thirdparty/sinon-server.js',
						'!sap/ui/thirdparty/sinon.js',
						'!sap/ui/thirdparty/swipe-view.js',
						// URI is no longer excluded because it's needed in jquery.sap.global and others
						// '!sap/ui/thirdparty/URI.js'
						'!sap/ui/thirdparty/URITemplate.js',
						'!sap/ui/thirdparty/vkbeautify.js',
						'!sap/ui/thirdparty/zyngascroll.js',
						// INCLUDED (because not mentioned in the list above) are the following libs:
						// jquery.sap.global.js
						// sap/ui/thirdparty/jquery-mobile-custom.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-core.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-position.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-datepicker.js

						// exclude CLDR and messagebundles
						'!sap/ui/core/cldr/**',
						'!sap/ui/core/messagebundle*'
					]
				},
				jsdoc: {
					exclude: [ 'sap/ui/qunit', 'sap/ui/thirdparty' ]
				}
			},
			{
				name: 'sap.ui.unified',
				path: 'src/sap.ui.unified'
			},
			{
				name: 'sap.ui.layout',
				path: 'src/sap.ui.layout'
			},
			{
				name: 'sap.m',
				path: 'src/sap.m'
			},
			{
				name: 'sap.tnt',
				path: 'src/sap.tnt'
			},
			{
				name: 'sap.f',
				path: 'src/sap.f'
			},
			{
				name: 'sap.ui.commons',
				path: 'src/sap.ui.commons'
			},
			{
				name: 'sap.ui.table',
				path: 'src/sap.ui.table'
			},
			{
				name: 'sap.ui.ux3',
				path: 'src/sap.ui.ux3'
			},
			{
				name: 'sap.ui.suite',
				path: 'src/sap.ui.suite'
			},
			{
				name: 'sap.ui.documentation',
				path: 'src/sap.ui.documentation',
				jsdoc: {
					exclude: [ 'sap/ui/documentation/sdk/thirdparty' ]
				}
			},
			{
				name: 'sap.ui.dt',
				path: 'src/sap.ui.dt'
			},
			{
				name: 'sap.uxap',
				path: 'src/sap.uxap'
			},
			{
				name: 'sap.ui.fl',
				path: 'src/sap.ui.fl'
			},
			{
				name: 'sap.ui.mdc',
				path: 'src/sap.ui.mdc'
			},
			{
				name: 'sap.ui.codeeditor',
				path: 'src/sap.ui.codeeditor',
				preload: {
					src: [
						'**/*.js',
						'!sap/ui/codeeditor/js/**'
					]
				},
				jsdoc: {
					exclude: [ 'sap/ui/codeeditor/js' ]
				}
			},
			{
				name: 'sap.ui.support',
				path: 'src/sap.ui.support'
			},
			{
				name: 'sap.ui.rta',
				path: 'src/sap.ui.rta'
			},
			{
				name: 'sap.ui.integration',
				path: 'src/sap.ui.integration',
				preload: {
					src: [
						'**/*.js',
						'!sap/ui/integration/thirdparty/**',
						'!sap/ui/integration/designtime/thirdparty/**'
					]
				},
				jsdoc: {
					exclude: [
						'sap/ui/integration/thirdparty',
						'sap/ui/integration/designtime/thirdparty'
					]
				}
			},
			{
				name: 'sap.ui.testrecorder',
				path: 'src/sap.ui.testrecorder'
			},
			{
				name: 'themelib_sap_bluecrystal',
				path: 'src/themelib_sap_bluecrystal',
				type: 'theme'
			},
			{
				name: 'themelib_sap_belize',
				path: 'src/themelib_sap_belize',
				type: 'theme'
			},
			{
				name: 'themelib_sap_fiori_3',
				path: 'src/themelib_sap_fiori_3',
				type: 'theme'
			},
			{
				name: 'themelib_sap_horizon',
				path: 'src/themelib_sap_horizon',
				type: 'theme'
			}
		]

	};

	// Load config extension script to allow overrides to "grunt" and "gruntData"
	var configExtensionFile = grunt.option("config-extension");
	if (configExtensionFile) {
		configExtensionFile.split(',').forEach(file => require(path.resolve(file))(grunt, gruntData));
	}

	// Normalize all library 'path' to individual 'src' and 'test' paths
	gruntData.allLibraries.forEach(function(library) {
		library.src = library.src || library.path + "/src";
		library.test = library.test || library.path + "/test";
	});

	// determine set of libraries to use (specified by --libs option)
	gruntData.libraries = !libs ? gruntData.allLibraries : gruntData.allLibraries.filter(function(library) {
		return libs.indexOf(library.name) > -1;
	});

	// Load all grunt config files (in grunt subfolder) and all tasks installed via npm
	require('load-grunt-config')(grunt, {

		configPath: path.join(process.cwd(), 'grunt/config'),

		// loads grunt plugins just-in-time (faster than using load-grunt-tasks)
		jitGrunt: {
			staticMappings: {
				'replace': 'grunt-text-replace',
				'openui5_connect': 'grunt-openui5',
				'openui5_theme': 'grunt-openui5',
				'openui5_preload': 'grunt-openui5',
				'gitclone': 'grunt-git',
				'gitadd': 'grunt-git',
				'gitcommit': 'grunt-git',
				'gittag': 'grunt-git',
				'gitpush': 'grunt-git',
				'jsdoc': 'grunt-jsdoc'
			}
		},

		data: gruntData

	});

};
<commit_msg>[INTERNAL] Grunt: add web components libraries<commit_after>/*
 * Copyright (c) 2014-2022 SAP SE
 */

'use strict';

var path = require('path');
var moment = require('moment');
var semver = require('semver');

module.exports = function(grunt) {

	// Force unix linefeeds (see https://github.com/gruntjs/grunt/issues/1123)
	// (grunt uses grunt.util.normalizelf when processing templates)
	grunt.util.linefeed = '\n';

	// Log time how long tasks take
	require('grunt-timer').init(grunt, {
		deferLogs: true,
		color: 'cyan'
	});

	// Check for valid required Node.js version from package.json
	// npm does not validate this within the project itself; only if this project would be installed as a dependency (which is not the case as of now)
	var pkg = grunt.file.readJSON(__dirname + "/package.json");
	if (pkg.engines && pkg.engines.node && !semver.satisfies(process.version, pkg.engines.node)) {
		grunt.log.error('!!! WARNING !!!');
		grunt.log.error('Unsupported Node.js version: wanted "' + pkg.engines.node + '" (current: "' + process.version + '")');
		grunt.log.error('Please update your Node.js installation!');
	}

	// Load all custom tasks from grunt/tasks dir
	grunt.loadTasks(path.join(process.cwd(), 'grunt/tasks'));

	// set default options
	grunt.option('production', grunt.option('production') || false);
	if (typeof grunt.option('minify-css') === 'undefined') {
		grunt.option('minify-css', grunt.option('production'));
	}
	if (typeof grunt.option('include-test-resources') === 'undefined') {
		grunt.option('include-test-resources', grunt.option('no-production'));
	}

	// set of libraries to use (e.g. --libs=sap.ui.core)
	var libsOption = grunt.option('libs');
	var libs = libsOption ? libsOption.split(',') : null;

	// the gruntdata contains the configuration for the build part
	// we distinguish here between a testsuite which is an application
	// and the libraries which are the re-use modules
	var gruntData = {

		buildtime: moment().utc().format('YYYYMMDDHHmmss'),
		lastchange: '',

		testsuite: {
			name: 'testsuite',
			path: 'src/testsuite'
		},
		allLibraries: [
			{
				name: 'sap.ui.core',
				path: 'src/sap.ui.core',
				preload: {
					src: [

						'**/*.js',

						// configuration copied from sap/ui/core/.library

						// files are already part of sap-ui-core.js
						'!ui5loader*.js',

						// exclude all merged files as well as the top level jQuery copy (not visible in OpenUI5)
						'!jquery-*.js',
						'!sap-*',
						// CorePlugin, shouldn't be installed by default
						'!sap/ui/core/plugin/DeclarativeSupport.js',
						'!sap/ui/core/plugin/LessSupport.js',
						// exclude non-productive code
						'!sap/ui/debug/**',
						'!sap/ui/core/support/**',
						'!sap/ui/qunit/**',
						'!sap/ui/test/**',
						'!testsuite/**',
						// Ignore substitutes for moved third party libs
						'!jquery-ui-core.js',
						'!jquery-ui-datepicker.js',
						'!jquery-ui-position.js',
						'!sap/ui/model/odata/datajs.js',
						// Third party libs which should NOT be part of the all-in-one file
						'!sap/ui/thirdparty/blanket.js',
						// '!sap/ui/thirdparty/crossroads.js'
						// '!sap/ui/thirdparty/caja-html-sanitizer.js'
						'!sap/ui/thirdparty/d3.js',
						'!sap/ui/thirdparty/datajs.js',
						'!sap/ui/thirdparty/handlebars.js',
						// '!sap/ui/thirdparty/hasher.js'
						'!sap/ui/thirdparty/IPv6.js',
						'!sap/ui/thirdparty/iscroll.js',
						'!sap/ui/thirdparty/iscroll-lite.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-widget.js',
						'!sap/ui/thirdparty/jqueryui/jquery-effect*.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-blind.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-bounce.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-clip.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-drop.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-explode.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-fade.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-fold.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-highlight.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-pulsate.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-scale.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-shake.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-slide.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-effect-transfer.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-mouse.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-draggable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-resizable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-selectable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-sortable.js',
						'!sap/ui/thirdparty/jqueryui/jquery-ui-droppable.js',
						'!sap/ui/thirdparty/jszip.js',
						'!sap/ui/thirdparty/klay.js',
						'!sap/ui/thirdparty/less.js',
						'!sap/ui/thirdparty/mobify-carousel.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-core.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-scroller.js',
						'!sap/ui/thirdparty/mobiscroll/js/mobiscroll-datetime.js',
						'!sap/ui/thirdparty/punycode.js',
						'!sap/ui/thirdparty/qunit-2.js',
						'!sap/ui/thirdparty/qunit-composite.js',
						'!sap/ui/thirdparty/qunit-reporter-junit.js',
						'!sap/ui/thirdparty/qunit.js',
						'!sap/ui/thirdparty/RequestRecorder.js',
						'!sap/ui/thirdparty/require.js',
						'!sap/ui/thirdparty/SecondLevelDomains.js',
						// '!sap/ui/thirdparty/signals.js'
						'!sap/ui/thirdparty/sinon-4.js',
						'!sap/ui/thirdparty/sinon-ie.js',
						'!sap/ui/thirdparty/sinon-qunit.js',
						'!sap/ui/thirdparty/sinon-server.js',
						'!sap/ui/thirdparty/sinon.js',
						'!sap/ui/thirdparty/swipe-view.js',
						// URI is no longer excluded because it's needed in jquery.sap.global and others
						// '!sap/ui/thirdparty/URI.js'
						'!sap/ui/thirdparty/URITemplate.js',
						'!sap/ui/thirdparty/vkbeautify.js',
						'!sap/ui/thirdparty/zyngascroll.js',
						// INCLUDED (because not mentioned in the list above) are the following libs:
						// jquery.sap.global.js
						// sap/ui/thirdparty/jquery-mobile-custom.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-core.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-position.js
						// sap/ui/thirdparty/jqueryui/jquery-ui-datepicker.js

						// exclude CLDR and messagebundles
						'!sap/ui/core/cldr/**',
						'!sap/ui/core/messagebundle*'
					]
				},
				jsdoc: {
					exclude: [ 'sap/ui/qunit', 'sap/ui/thirdparty' ]
				}
			},
			{
				name: 'sap.ui.unified',
				path: 'src/sap.ui.unified'
			},
			{
				name: 'sap.ui.layout',
				path: 'src/sap.ui.layout'
			},
			{
				name: 'sap.m',
				path: 'src/sap.m'
			},
			{
				name: 'sap.tnt',
				path: 'src/sap.tnt'
			},
			{
				name: 'sap.f',
				path: 'src/sap.f'
			},
			{
				name: 'sap.ui.commons',
				path: 'src/sap.ui.commons'
			},
			{
				name: 'sap.ui.table',
				path: 'src/sap.ui.table'
			},
			{
				name: 'sap.ui.ux3',
				path: 'src/sap.ui.ux3'
			},
			{
				name: 'sap.ui.suite',
				path: 'src/sap.ui.suite'
			},
			{
				name: 'sap.ui.documentation',
				path: 'src/sap.ui.documentation',
				jsdoc: {
					exclude: [ 'sap/ui/documentation/sdk/thirdparty' ]
				}
			},
			{
				name: 'sap.ui.dt',
				path: 'src/sap.ui.dt'
			},
			{
				name: 'sap.uxap',
				path: 'src/sap.uxap'
			},
			{
				name: 'sap.ui.fl',
				path: 'src/sap.ui.fl'
			},
			{
				name: 'sap.ui.mdc',
				path: 'src/sap.ui.mdc'
			},
			{
				name: 'sap.ui.codeeditor',
				path: 'src/sap.ui.codeeditor',
				preload: {
					src: [
						'**/*.js',
						'!sap/ui/codeeditor/js/**'
					]
				},
				jsdoc: {
					exclude: [ 'sap/ui/codeeditor/js' ]
				}
			},
			{
				name: 'sap.ui.support',
				path: 'src/sap.ui.support'
			},
			{
				name: 'sap.ui.rta',
				path: 'src/sap.ui.rta'
			},
			{
				name: 'sap.ui.integration',
				path: 'src/sap.ui.integration',
				preload: {
					src: [
						'**/*.js',
						'!sap/ui/integration/thirdparty/**',
						'!sap/ui/integration/designtime/thirdparty/**'
					]
				},
				jsdoc: {
					exclude: [
						'sap/ui/integration/thirdparty',
						'sap/ui/integration/designtime/thirdparty'
					]
				}
			},
			{
				name: 'sap.ui.testrecorder',
				path: 'src/sap.ui.testrecorder'
			},
			{
				name: 'themelib_sap_bluecrystal',
				path: 'src/themelib_sap_bluecrystal',
				type: 'theme'
			},
			{
				name: 'themelib_sap_belize',
				path: 'src/themelib_sap_belize',
				type: 'theme'
			},
			{
				name: 'themelib_sap_fiori_3',
				path: 'src/themelib_sap_fiori_3',
				type: 'theme'
			},
			{
				name: 'themelib_sap_horizon',
				path: 'src/themelib_sap_horizon',
				type: 'theme'
			},
			{
				name: 'sap.ui.webc.common',
				path: 'src/sap.ui.webc.common'
			},
			{
				name: 'sap.ui.webc.main',
				path: 'src/sap.ui.webc.main'
			},
			{
				name: 'sap.ui.webc.fiori',
				path: 'src/sap.ui.webc.fiori'
			},
		]

	};

	// Load config extension script to allow overrides to "grunt" and "gruntData"
	var configExtensionFile = grunt.option("config-extension");
	if (configExtensionFile) {
		configExtensionFile.split(',').forEach(file => require(path.resolve(file))(grunt, gruntData));
	}

	// Normalize all library 'path' to individual 'src' and 'test' paths
	gruntData.allLibraries.forEach(function(library) {
		library.src = library.src || library.path + "/src";
		library.test = library.test || library.path + "/test";
	});

	// determine set of libraries to use (specified by --libs option)
	gruntData.libraries = !libs ? gruntData.allLibraries : gruntData.allLibraries.filter(function(library) {
		return libs.indexOf(library.name) > -1;
	});

	// Load all grunt config files (in grunt subfolder) and all tasks installed via npm
	require('load-grunt-config')(grunt, {

		configPath: path.join(process.cwd(), 'grunt/config'),

		// loads grunt plugins just-in-time (faster than using load-grunt-tasks)
		jitGrunt: {
			staticMappings: {
				'replace': 'grunt-text-replace',
				'openui5_connect': 'grunt-openui5',
				'openui5_theme': 'grunt-openui5',
				'openui5_preload': 'grunt-openui5',
				'gitclone': 'grunt-git',
				'gitadd': 'grunt-git',
				'gitcommit': 'grunt-git',
				'gittag': 'grunt-git',
				'gitpush': 'grunt-git',
				'jsdoc': 'grunt-jsdoc'
			}
		},

		data: gruntData

	});

};
<|endoftext|>


<commit_before>'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};

module.exports = function (grunt) {
  // load all grunt tasks
  require('matchdep').filterDev('grunt-*').concat(['gruntacular']).forEach(grunt.loadNpmTasks);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./component.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    watch: {
      coffee: {
        files: ['app/scripts/{,*/}*.coffee'],
        tasks: ['coffee:dist']
      },
      coffeeTest: {
        files: ['test/spec/{,*/}*.coffee'],
        tasks: ['coffee:test']
      },
      compass: {
        files: ['app/styles/{,*/}*.{scss,sass}', 'component/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass']
      },
      livereload: {
        files: [
          'app/{,*/}*.html',
          '{.tmp,app/styles/{,*/}*.css',
          '{.tmp,app/scripts/{,*/}*.js','component/scripts/{,*/}*.js',
          'app/images/{,*/}*.{png,jpg,jpeg}'
        ],
        tasks: ['livereload']
      }
    },
    connect: {
      livereload: {
        options: {
          port: 9000,
          // Change this to '0.0.0.0' to access the server from outside.
          hostname: '0.0.0.0',
          middleware: function (connect) {
            return [
              lrSnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, 'component'),
              mountFolder(connect, yeomanConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9000,
          middleware: function (connect) {
            return [
              mountFolder(connect, '.tmp'),
              mountFolder(connect, 'test')
            ];
          }
        }
      }
    },
    open: {
      server: {
        url: 'http://localhost:3501'
      }
    },
    clean: {
      dist: ['.tmp', 'dist/*'],
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        'app/scripts/{,*/}*.js',
        'component/scripts/{,*/}*.js'
      ]
    },
    testacular: {
      unit: {
        configFile: 'testacular.conf.js',
        singleRun: true
      }
    },
    coffee: {
      dist: {
        files: {
          '.tmp/scripts/coffee.js': 'app/scripts/*.coffee'
        }
      },
      test: {
        files: [{
          expand: true,
          cwd: '.tmp/spec',
          src: '*.coffee',
          dest: 'test/spec'
        }]
      }
    },
    compass: {
      options: {
        sassDir: 'app/styles',
        cssDir: '.tmp/styles',
        imagesDir: 'app/images',
        javascriptsDir: 'app/scripts',
        fontsDir: 'app/styles/fonts',
        importPath: ['app/components', 'component/styles'],
        relativeAssets: true
      },
      dist: {},
      server: {
        options: {
          debugInfo: true
        }
      }
    },
    concat: {
      dist: {
        files: {
          'dist/scripts/scripts.js': [
            '.tmp/scripts/*.js',
            'app/scripts/*.js',
            'component/scripts/*.js'
          ]
        }
      }
    },
    useminPrepare: {
      html: 'app/index.html',
      options: {
        dest: 'dist'
      }
    },
    usemin: {
      html: ['dist/{,*/}*.html'],
      css: ['dist/styles/{,*/}*.css'],
      options: {
        dirs: ['dist']
      }
    },
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: 'app/images',
          src: '{,*/}*.{png,jpg,jpeg}',
          dest: 'dist/images'
        }]
      }
    },
    cssmin: {
      dist: {
        files: {
          'dist/styles/main.css': [
            '.tmp/styles/{,*/}*.css',
            'app/styles/{,*/}*.css'
          ]
        }
      }
    },
    htmlmin: {
      dist: {
        options: {
          /*removeCommentsFromCDATA: true,
          // https://github.com/yeoman/grunt-usemin/issues/44
          //collapseWhitespace: true,
          collapseBooleanAttributes: true,
          removeAttributeQuotes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true*/
        },
        files: [{
          expand: true,
          cwd: 'app',
          src: ['*.html', 'views/*.html'],
          dest: 'dist'
        }]
      }
    },
    cdnify: {
      dist: {
        html: ['dist/*.html']
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: 'dist/scripts',
          src: '*.js',
          dest: 'dist/scripts'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/scripts/scripts.js': [
            'dist/scripts/scripts.js'
          ],
        }
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: 'app',
          dest: 'dist',
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'components/**/*'
          ]
        }]
      }
    }
  });

  grunt.renameTask('regarde', 'watch');
  // remove when mincss task is renamed
  grunt.renameTask('mincss', 'cssmin');

  grunt.registerTask('server', [
    'clean:server',
    'coffee:dist',
    'compass:server',
    'livereload-start',
    'connect:livereload',
    'open',
    'watch'
  ]);

  grunt.registerTask('test', [
    'clean:server',
    'coffee',
    'compass',
    'connect:test',
    'testacular'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'jshint',
    'test',
    'coffee',
    'compass:dist',
    'useminPrepare',
    'imagemin',
    'cssmin',
    'htmlmin',
    'concat',
    'copy',
    'cdnify',
    'usemin',
    'ngmin',
    'uglify'
  ]);

  grunt.registerTask('default', ['build']);
};
<commit_msg>Changing test port to be different than server port<commit_after>'use strict';
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};

module.exports = function (grunt) {
  // load all grunt tasks
  require('matchdep').filterDev('grunt-*').concat(['gruntacular']).forEach(grunt.loadNpmTasks);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./component.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    watch: {
      coffee: {
        files: ['app/scripts/{,*/}*.coffee'],
        tasks: ['coffee:dist']
      },
      coffeeTest: {
        files: ['test/spec/{,*/}*.coffee'],
        tasks: ['coffee:test']
      },
      compass: {
        files: ['app/styles/{,*/}*.{scss,sass}', 'component/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass']
      },
      livereload: {
        files: [
          'app/{,*/}*.html',
          '{.tmp,app/styles/{,*/}*.css',
          '{.tmp,app/scripts/{,*/}*.js','component/scripts/{,*/}*.js',
          'app/images/{,*/}*.{png,jpg,jpeg}'
        ],
        tasks: ['livereload']
      }
    },
    connect: {
      livereload: {
        options: {
          port: 9000,
          // Change this to '0.0.0.0' to access the server from outside.
          hostname: '0.0.0.0',
          middleware: function (connect) {
            return [
              lrSnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, 'component'),
              mountFolder(connect, yeomanConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              mountFolder(connect, '.tmp'),
              mountFolder(connect, 'test')
            ];
          }
        }
      }
    },
    open: {
      server: {
        url: 'http://localhost:3501'
      }
    },
    clean: {
      dist: ['.tmp', 'dist/*'],
      server: '.tmp'
    },
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      all: [
        'Gruntfile.js',
        'app/scripts/{,*/}*.js',
        'component/scripts/{,*/}*.js'
      ]
    },
    testacular: {
      unit: {
        configFile: 'testacular.conf.js',
        singleRun: true
      }
    },
    coffee: {
      dist: {
        files: {
          '.tmp/scripts/coffee.js': 'app/scripts/*.coffee'
        }
      },
      test: {
        files: [{
          expand: true,
          cwd: '.tmp/spec',
          src: '*.coffee',
          dest: 'test/spec'
        }]
      }
    },
    compass: {
      options: {
        sassDir: 'app/styles',
        cssDir: '.tmp/styles',
        imagesDir: 'app/images',
        javascriptsDir: 'app/scripts',
        fontsDir: 'app/styles/fonts',
        importPath: ['app/components', 'component/styles'],
        relativeAssets: true
      },
      dist: {},
      server: {
        options: {
          debugInfo: true
        }
      }
    },
    concat: {
      dist: {
        files: {
          'dist/scripts/scripts.js': [
            '.tmp/scripts/*.js',
            'app/scripts/*.js',
            'component/scripts/*.js'
          ]
        }
      }
    },
    useminPrepare: {
      html: 'app/index.html',
      options: {
        dest: 'dist'
      }
    },
    usemin: {
      html: ['dist/{,*/}*.html'],
      css: ['dist/styles/{,*/}*.css'],
      options: {
        dirs: ['dist']
      }
    },
    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: 'app/images',
          src: '{,*/}*.{png,jpg,jpeg}',
          dest: 'dist/images'
        }]
      }
    },
    cssmin: {
      dist: {
        files: {
          'dist/styles/main.css': [
            '.tmp/styles/{,*/}*.css',
            'app/styles/{,*/}*.css'
          ]
        }
      }
    },
    htmlmin: {
      dist: {
        options: {
          /*removeCommentsFromCDATA: true,
          // https://github.com/yeoman/grunt-usemin/issues/44
          //collapseWhitespace: true,
          collapseBooleanAttributes: true,
          removeAttributeQuotes: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeOptionalTags: true*/
        },
        files: [{
          expand: true,
          cwd: 'app',
          src: ['*.html', 'views/*.html'],
          dest: 'dist'
        }]
      }
    },
    cdnify: {
      dist: {
        html: ['dist/*.html']
      }
    },
    ngmin: {
      dist: {
        files: [{
          expand: true,
          cwd: 'dist/scripts',
          src: '*.js',
          dest: 'dist/scripts'
        }]
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/scripts/scripts.js': [
            'dist/scripts/scripts.js'
          ],
        }
      }
    },
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: 'app',
          dest: 'dist',
          src: [
            '*.{ico,txt}',
            '.htaccess',
            'components/**/*'
          ]
        }]
      }
    }
  });

  grunt.renameTask('regarde', 'watch');
  // remove when mincss task is renamed
  grunt.renameTask('mincss', 'cssmin');

  grunt.registerTask('server', [
    'clean:server',
    'coffee:dist',
    'compass:server',
    'livereload-start',
    'connect:livereload',
    'open',
    'watch'
  ]);

  grunt.registerTask('test', [
    'clean:server',
    'coffee',
    'compass',
    'connect:test',
    'testacular'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'jshint',
    'test',
    'coffee',
    'compass:dist',
    'useminPrepare',
    'imagemin',
    'cssmin',
    'htmlmin',
    'concat',
    'copy',
    'cdnify',
    'usemin',
    'ngmin',
    'uglify'
  ]);

  grunt.registerTask('default', ['build']);
};
<|endoftext|>


<commit_before>'use strict';

var paths = {
  js: ['*.js', 'test/**/*.js', '!test/coverage/**', '!bower_components/**', 'packages/**/*.js', '!packages/**/node_modules/**', '!packages/contrib/**/*.js', '!packages/contrib/**/node_modules/**'],
  html: ['packages/**/public/**/views/**', 'packages/**/server/views/**'],
  css: ['!bower_components/**', 'packages/**/public/**/css/*.css', '!packages/contrib/**/public/**/css/*.css']
};

module.exports = function(grunt) {

  if (process.env.NODE_ENV !== 'production') {
    require('time-grunt')(grunt);
  }

  // Project Configuration
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    assets: grunt.file.readJSON('config/assets.json'),
    clean: ['bower_components/build'],
    watch: {
      js: {
        files: paths.js,
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      html: {
        files: paths.html,
        options: {
          livereload: true,
          interval: 500
        }
      },
      css: {
        files: paths.css,
        tasks: ['csslint'],
        options: {
          livereload: true
        }
      }
    },
    jshint: {
      all: {
        src: paths.js,
        options: {
          jshintrc: true
        }
      }
    },
    uglify: {
      core: {
        options: {
          mangle: false
        },
        files: '<%= assets.core.js %>'
      }
    },
    csslint: {
      options: {
        csslintrc: '.csslintrc'
      },
      src: paths.css
    },
    cssmin: {
      core: {
        files: '<%= assets.core.css %>'
      }
    },
    nodemon: {
      dev: {
        script: 'server.js',
        options: {
          args: [],
          ignore: ['node_modules/**'],
          ext: 'js,html',
          nodeArgs: ['--debug'],
          delayTime: 1,
          cwd: __dirname
        }
      }
    },
    concurrent: {
      tasks: ['nodemon', 'watch'],
      options: {
        logConcurrentOutput: true
      }
    },
    mochaTest: {
      options: {
        reporter: 'spec',
        require: [
          'server.js',
          function() {
            require('meanio/lib/util').preload(__dirname + '/packages/**/server', 'model');
          }
        ]
      },
      src: ['packages/**/server/tests/**/*.js']
    },
    env: {
      test: {
        NODE_ENV: 'test'
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js'
      }
    }
  });

  //Load NPM tasks
  require('load-grunt-tasks')(grunt);

  //Default task(s).
  if (process.env.NODE_ENV === 'production') {
    grunt.registerTask('default', ['clean', 'cssmin', 'uglify', 'concurrent']);
  } else {
    grunt.registerTask('default', ['clean', 'jshint', 'csslint', 'concurrent']);
  }

  //Test task.
  grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']);

  // For Heroku users only.
  // Docs: https://github.com/linnovate/mean/wiki/Deploying-on-Heroku
  grunt.registerTask('heroku:production', ['cssmin', 'uglify']);
};
<commit_msg>watch changed specs and run the tests when changed<commit_after>'use strict';

var paths = {
  js: ['*.js', 'test/**/*.js', 'spec/**/*.js', '!test/coverage/**', '!bower_components/**', 'packages/**/*.js', '!packages/**/node_modules/**', '!packages/contrib/**/*.js', '!packages/contrib/**/node_modules/**'],
  html: ['packages/**/public/**/views/**', 'packages/**/server/views/**'],
  css: ['!bower_components/**', 'packages/**/public/**/css/*.css', '!packages/contrib/**/public/**/css/*.css'],
  server_specs: ['packages/**/server/tests/**/*.js', 'packages/**/server/specs/**/*.js']
};

var specs_require = [
  'server.js',
  function() {
    require('meanio/lib/util').preload(__dirname + '/packages/**/server', 'model');
  }
];

module.exports = function(grunt) {

  if (process.env.NODE_ENV !== 'production') {
    require('time-grunt')(grunt);
  }

  // Project Configuration
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    assets: grunt.file.readJSON('config/assets.json'),
    clean: ['bower_components/build'],
    watch: {
      js: {
        files: paths.js,
        tasks: ['jshint'],
        options: {
          livereload: true
        }
      },
      html: {
        files: paths.html,
        options: {
          livereload: true,
          interval: 500
        }
      },
      css: {
        files: paths.css,
        tasks: ['csslint'],
        options: {
          livereload: true
        }
      },
      test: {
        files: paths.js,
        tasks: ['env:test', 'mochaTest']
      }
    },
    jshint: {
      all: {
        src: paths.js,
        options: {
          jshintrc: true
        }
      }
    },
    uglify: {
      core: {
        options: {
          mangle: false
        },
        files: '<%= assets.core.js %>'
      }
    },
    csslint: {
      options: {
        csslintrc: '.csslintrc'
      },
      src: paths.css
    },
    cssmin: {
      core: {
        files: '<%= assets.core.css %>'
      }
    },
    nodemon: {
      dev: {
        script: 'server.js',
        options: {
          args: [],
          ignore: ['node_modules/**'],
          ext: 'js,html',
          nodeArgs: ['--debug'],
          delayTime: 1,
          cwd: __dirname
        }
      }
    },
    concurrent: {
      tasks: ['nodemon', 'watch'],
      options: {
        logConcurrentOutput: true
      }
    },
    mochaTest: {
      options: {
        reporter: 'dot',
        require: specs_require
      },
      src: paths.server_specs
    },
    env: {
      test: {
        NODE_ENV: 'test'
      }
    },
    karma: {
      unit: {
        configFile: 'karma.conf.js'
      }
    }
  });

  //Load NPM tasks
  require('load-grunt-tasks')(grunt);

  //Default task(s).
  if (process.env.NODE_ENV === 'production') {
    grunt.registerTask('default', ['clean', 'cssmin', 'uglify', 'concurrent']);
  }
  else {
    grunt.registerTask('default', ['clean', 'jshint', 'csslint', 'concurrent']);
  }

  var defaultTestSrc = grunt.config('mochaTest.test.src');

  grunt.event.on('watch', function(action, filepath) {
    grunt.config('jshint.all.src', filepath);
    grunt.config('mochaTest.test.src', defaultTestSrc);

    if(filepath.match('test/') || filepath.match('specs/'))
      grunt.config('mochaTest.test.src', filepath);
  });

  //Test task.
  grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']);

  // For Heroku users only.
  // Docs: https://github.com/linnovate/mean/wiki/Deploying-on-Heroku
  grunt.registerTask('heroku:production', ['cssmin', 'uglify']);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      files: [
        'app/**/*.js',
        'admin/**/*.js',
        'test/**/*.js',
        'shared/**/*.js',
        'server/**/*.js'
      ],
      options: {
        globals: {
          window: true,
          document: true,
          navigator: true,
          console: true,
          module: true,
          require: true,
          FastClick: true,
          $: true,
          Backbone: true,
          Parse: true,
          Handlebars: true,
          _: true,
          google: true,
          config: true,
          ga: true,
          test: true,
          ok: true,
          equal: true,
          deepEqual: true
        },
        undef: true,
        debug: true,
        '-W030': true
      }
    },

    simplemocha: {
      options: {
        timeout: 3000,
        ignoreLeaks: false,
        ui: 'bdd',
        reporter: 'dot'
      },

      all: {src: 'test/unit/**/*.js'}
    },

    watch: {
      files: [
        'Gruntfile.js',
        'app/**/*',
        'admin/**/*',
        'shared/**/*',
        'server/**/*',
        'vendor/**/*',
        '!vendor/**/*.min.*',
        'test/**/*'
      ],
      tasks: ['default']
    },

    sass: {
      options: {includePaths: ['.']},
      app: {src: 'app/css/app.scss', dest: 'tmp/linksf.css'},
      admin: {src: 'admin/css/admin.scss', dest: 'tmp/linksf_admin.css'}
    },

    browserify: {
      options: {
        // We want `.hbs` files to be `require`able. We use the hbsfy transform
        // to precompile Handlebars templates and let us require the compiled
        // JavaScript functions.
        transform: ['hbsfy']
      },
      app: {
        // The entry point where Browserify will begin searching the AST for `require` calls
        src: 'app/js/app.js',

        // The built file
        dest: 'tmp/app.js',

        options: {
          // We want alias mappings because we'd rather
          // `require('views/index_view')` than `require('./views/index_view.js')`
          //
          // There is a tradeoff for convenience here - all aliased files are exported
          // into the built app.js file, even if they're never explicitly
          // `require`d from the entry point tree.
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'app/js/lib',            src: '*.js',  dest: 'lib' },
            { cwd: 'app/js/models',         src: '*.js',  dest: 'models' },
            { cwd: 'app/js/collections',    src: '*.js',  dest: 'collections' },
            { cwd: 'app/js/routers',        src: '*.js',  dest: 'routers' },
            { cwd: 'app/js/templates',      src: '*.hbs', dest: 'templates' },
            { cwd: 'app/js/views',          src: '*.js',  dest: 'views' }
          ]
        }
      },

      admin: {
        src: 'admin/js/admin.js',
        dest: 'tmp/admin.js',
        options: {
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'admin/js/lib',          src: '*.js',  dest: 'lib' },
            { cwd: 'admin/js/models',       src: '*.js',  dest: 'models' },
            { cwd: 'admin/js/collections',  src: '*.js',  dest: 'collections' },
            { cwd: 'admin/js/routers',      src: '*.js',  dest: 'routers' },
            { cwd: 'admin/js/templates',    src: '*.hbs', dest: 'templates' },
            { cwd: 'admin/js/views',        src: '*.js',  dest: 'views' }
          ]
        }
      }
    },

    cssmin: {
      app: {src: 'tmp/linksf.css', dest: 'tmp/linksf.css'},
      admin: {src: 'tmp/linksf_admin.css', dest: 'tmp/linksf_admin.css'}
    },

    concat: {
      // Not a target, just a variable that we can interpolate in elsewhere.
      shared_js: [
        'vendor/js/jquery-2.0.3.js',
        'vendor/js/jquery.serialize-object.js',
        'vendor/js/underscore.js',
        'vendor/js/backbone-1.0.0.js',
        'vendor/js/parse-1.2.12.js'
      ],

      shared_js_minified: [
        'vendor/js/jquery-2.0.3.min.js',
        'vendor/js/jquery.serialize-object.min.js',
        'vendor/js/underscore.min.js',
        'vendor/js/backbone-1.0.0.min.js',
        'vendor/js/parse-1.2.12.min.js'
      ],

      app: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.switch.js',
          'vendor/js/bootstrap-button.js',
          'vendor/js/fastclick.js',
          'tmp/app.js'
        ],
        dest: 'tmp/linksf.js'
      },

      app_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.switch.min.js',
          'vendor/js/bootstrap-button.min.js',
          'vendor/js/fastclick.min.js',
          'tmp/app.min.js'
        ],
        dest: 'tmp/linksf.js'
      },

      admin: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.autosize.js',
          'vendor/js/bootstrap.js',
          'tmp/admin.js'
        ],
        dest: 'tmp/linksf_admin.js'
      },

      admin_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.autosize.min.js',
          'vendor/js/bootstrap.min.js',
          'tmp/admin.min.js'
        ],
        dest: 'tmp/linksf_admin.js'
      }
    },

    uglify: {
      options: {
        mangle: false,
        preserveComments: false,
        report: 'min'
      },

      vendor: {
        files: {
          'vendor/js/jquery.serialize-object.min.js': 'vendor/js/jquery.serialize-object.js',
          'vendor/js/backbone_filters.min.js': 'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js': 'vendor/js/bootstrap-button.js'
        }
      },
      app: {files: {'tmp/app.min.js': 'tmp/app.js'}},
      admin: {files: {'tmp/admin.min.js': 'tmp/admin.js'}}
    },

    clean: {
      build: {src: 'build/*', filter: function(filepath) {return filepath !== 'build/.gitkeep';}},
      tmp: {src: 'tmp/*', filter: function(filepath) {return filepath !== 'tmp/.gitkeep'; }},
      test: {src: 'test/acceptance/app.html'}
    },

    cachebuster: {
      all: {
        files: {src: [
          'tmp/linksf.js',
          'tmp/linksf.css',
          'tmp/linksf_admin.js',
          'tmp/linksf_admin.css'
        ]},
        options: {
          complete: function(hashes) {
            var keyMap = {
              'tmp/linksf.js':        'appJs',
              'tmp/linksf.css':       'appCss',
              'tmp/linksf_admin.js':  'adminJs',
              'tmp/linksf_admin.css': 'adminCss'
            };

            var config = {
              parseAppId: process.env.PARSE_APP_ID,
              parseJsKey: process.env.PARSE_JS_KEY,
              gaToken:    process.env.GOOGLE_ANALYTICS_TOKEN,
              gaHost:     process.env.GOOGLE_ANALYTICS_HOST
            };

            Object.keys(hashes).forEach(function(key) {
              var matches = key.match(/^tmp\/(.*)(\..*)$/); // tmp/(filename)(.js)
              var outputFile = matches[1] + '-' + hashes[key] + matches[2];

              grunt.file.copy(key, 'build/' + outputFile);
              config[keyMap[key]] = outputFile;
            });

            grunt.file.write('build/index.html',
              grunt.template.process(grunt.file.read('app/index.html'), {data: config})
            );

            grunt.file.write('build/admin.html',
              grunt.template.process(grunt.file.read('admin/index.html'), {data: config})
            );

            grunt.file.write('test/acceptance/app.html',
              grunt.template.process(grunt.file.read('test/acceptance/app.html.template'), {data: config})
            );
          }
        }
      }
    },

    qunit: {all: ['test/acceptance/**/*.html']},

    env: {
      dev: {src: '.env.dev'},
      prod: {src: '.env.prod'}
    },

    autoprefixer: {
      options: {
        browsers: [
          'android >= 2.3',
          'last 3 versions'
        ]
      },
      all: {
        src: ['tmp/linksf.css', 'tmp/linksf_admin.css']
      }
    },

    s3: grunt.file.exists('s3.json') && grunt.file.readJSON('s3.json') || {},
    aws_s3: {
      options: {
        accessKeyId: '<%= s3.accessKeyId %>',
        secretAccessKey: '<%= s3.secretAccessKey %>',
        region: '<%= s3.region %>',
        uploadConcurrency: 4
      },
      dev: {
        options: { bucket: '<%= s3.devBucket %>' },
        files: [
          {expand: true, src: ['vendor/font/**', 'img/**']},
          {expand: true, src: 'build/*', dest: ''}
        ]
      },
      prod: {
        options: { bucket: '<%= s3.prodBucket %>' },
        files: [
          {expand: true, src: ['vendor/font/**', 'img/**']},
          {expand: true, src: 'build/*', dest: ''}
        ]
      }
    },

    shell: {
      parse: {
        command: 'parse deploy',
        options: {
          execOptions: {
            cwd: 'server'
          }
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-cachebuster');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-env');
  grunt.loadNpmTasks('grunt-autoprefixer');
  grunt.loadNpmTasks('grunt-aws-s3');
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadTasks('tasks');

  grunt.registerTask('build:common', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'autoprefixer',
    'browserify'
  ]);

  grunt.registerTask('build:dev', [
    'env:dev',
    'build:common',
    'concat:app', 'concat:admin',
    'cachebuster',
    'qunit'
  ]);

  grunt.registerTask('build:production', [
    'env:prod',
    'build:common',
    'cssmin', 'uglify',
    'concat:app_min', 'concat:admin_min',
    'cachebuster',
    'qunit'
  ]);

  grunt.registerTask('parse:deploy', ['parse:config', 'shell:parse']);
  grunt.registerTask('parse:deploy:dev', ['env:dev', 'parse:deploy']);
  grunt.registerTask('parse:deploy:prod', ['env:prod', 'parse:deploy']);

  grunt.registerTask('default', 'build:dev');

  grunt.registerTask('deploy:dev', [
    'build:dev',
    'aws_s3:dev',
    'parse:deploy:dev',
    'clean'
  ]);

  grunt.registerTask('deploy:prod', [
    'build:prod',
    'aws_s3:prod',
    'parse:deploy:prod',
    'clean'
  ]);
};
<commit_msg>rename build:production to build:prod<commit_after>module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      files: [
        'app/**/*.js',
        'admin/**/*.js',
        'test/**/*.js',
        'shared/**/*.js',
        'server/**/*.js'
      ],
      options: {
        globals: {
          window: true,
          document: true,
          navigator: true,
          console: true,
          module: true,
          require: true,
          FastClick: true,
          $: true,
          Backbone: true,
          Parse: true,
          Handlebars: true,
          _: true,
          google: true,
          config: true,
          ga: true,
          test: true,
          ok: true,
          equal: true,
          deepEqual: true
        },
        undef: true,
        debug: true,
        '-W030': true
      }
    },

    simplemocha: {
      options: {
        timeout: 3000,
        ignoreLeaks: false,
        ui: 'bdd',
        reporter: 'dot'
      },

      all: {src: 'test/unit/**/*.js'}
    },

    watch: {
      files: [
        'Gruntfile.js',
        'app/**/*',
        'admin/**/*',
        'shared/**/*',
        'server/**/*',
        'vendor/**/*',
        '!vendor/**/*.min.*',
        'test/**/*'
      ],
      tasks: ['default']
    },

    sass: {
      options: {includePaths: ['.']},
      app: {src: 'app/css/app.scss', dest: 'tmp/linksf.css'},
      admin: {src: 'admin/css/admin.scss', dest: 'tmp/linksf_admin.css'}
    },

    browserify: {
      options: {
        // We want `.hbs` files to be `require`able. We use the hbsfy transform
        // to precompile Handlebars templates and let us require the compiled
        // JavaScript functions.
        transform: ['hbsfy']
      },
      app: {
        // The entry point where Browserify will begin searching the AST for `require` calls
        src: 'app/js/app.js',

        // The built file
        dest: 'tmp/app.js',

        options: {
          // We want alias mappings because we'd rather
          // `require('views/index_view')` than `require('./views/index_view.js')`
          //
          // There is a tradeoff for convenience here - all aliased files are exported
          // into the built app.js file, even if they're never explicitly
          // `require`d from the entry point tree.
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'app/js/lib',            src: '*.js',  dest: 'lib' },
            { cwd: 'app/js/models',         src: '*.js',  dest: 'models' },
            { cwd: 'app/js/collections',    src: '*.js',  dest: 'collections' },
            { cwd: 'app/js/routers',        src: '*.js',  dest: 'routers' },
            { cwd: 'app/js/templates',      src: '*.hbs', dest: 'templates' },
            { cwd: 'app/js/views',          src: '*.js',  dest: 'views' }
          ]
        }
      },

      admin: {
        src: 'admin/js/admin.js',
        dest: 'tmp/admin.js',
        options: {
          aliasMappings: [
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'shared/lib' },
            { cwd: 'shared/js/lib',         src: '*.js',  dest: 'cloud/lib' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'shared/models' },
            { cwd: 'shared/js/models',      src: '*.js',  dest: 'cloud/models' },
            { cwd: 'shared/js/collections', src: '*.js',  dest: 'shared/collections' },
            { cwd: 'shared/js/views',       src: '*.js',  dest: 'shared/views' },
            { cwd: 'shared/js/templates',   src: '*.hbs', dest: 'shared/templates' },
            { cwd: 'admin/js/lib',          src: '*.js',  dest: 'lib' },
            { cwd: 'admin/js/models',       src: '*.js',  dest: 'models' },
            { cwd: 'admin/js/collections',  src: '*.js',  dest: 'collections' },
            { cwd: 'admin/js/routers',      src: '*.js',  dest: 'routers' },
            { cwd: 'admin/js/templates',    src: '*.hbs', dest: 'templates' },
            { cwd: 'admin/js/views',        src: '*.js',  dest: 'views' }
          ]
        }
      }
    },

    cssmin: {
      app: {src: 'tmp/linksf.css', dest: 'tmp/linksf.css'},
      admin: {src: 'tmp/linksf_admin.css', dest: 'tmp/linksf_admin.css'}
    },

    concat: {
      // Not a target, just a variable that we can interpolate in elsewhere.
      shared_js: [
        'vendor/js/jquery-2.0.3.js',
        'vendor/js/jquery.serialize-object.js',
        'vendor/js/underscore.js',
        'vendor/js/backbone-1.0.0.js',
        'vendor/js/parse-1.2.12.js'
      ],

      shared_js_minified: [
        'vendor/js/jquery-2.0.3.min.js',
        'vendor/js/jquery.serialize-object.min.js',
        'vendor/js/underscore.min.js',
        'vendor/js/backbone-1.0.0.min.js',
        'vendor/js/parse-1.2.12.min.js'
      ],

      app: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.switch.js',
          'vendor/js/bootstrap-button.js',
          'vendor/js/fastclick.js',
          'tmp/app.js'
        ],
        dest: 'tmp/linksf.js'
      },

      app_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.switch.min.js',
          'vendor/js/bootstrap-button.min.js',
          'vendor/js/fastclick.min.js',
          'tmp/app.min.js'
        ],
        dest: 'tmp/linksf.js'
      },

      admin: {
        src: [
          '<%= concat.shared_js %>',
          'vendor/js/backbone_filters.js',
          'vendor/js/jquery.autosize.js',
          'vendor/js/bootstrap.js',
          'tmp/admin.js'
        ],
        dest: 'tmp/linksf_admin.js'
      },

      admin_min: {
        src: [
          '<%= concat.shared_js_minified %>',
          'vendor/js/backbone_filters.min.js',
          'vendor/js/jquery.autosize.min.js',
          'vendor/js/bootstrap.min.js',
          'tmp/admin.min.js'
        ],
        dest: 'tmp/linksf_admin.js'
      }
    },

    uglify: {
      options: {
        mangle: false,
        preserveComments: false,
        report: 'min'
      },

      vendor: {
        files: {
          'vendor/js/jquery.serialize-object.min.js': 'vendor/js/jquery.serialize-object.js',
          'vendor/js/backbone_filters.min.js': 'vendor/js/backbone_filters.js',
          'vendor/js/bootstrap-button.min.js': 'vendor/js/bootstrap-button.js'
        }
      },
      app: {files: {'tmp/app.min.js': 'tmp/app.js'}},
      admin: {files: {'tmp/admin.min.js': 'tmp/admin.js'}}
    },

    clean: {
      build: {src: 'build/*', filter: function(filepath) {return filepath !== 'build/.gitkeep';}},
      tmp: {src: 'tmp/*', filter: function(filepath) {return filepath !== 'tmp/.gitkeep'; }},
      test: {src: 'test/acceptance/app.html'}
    },

    cachebuster: {
      all: {
        files: {src: [
          'tmp/linksf.js',
          'tmp/linksf.css',
          'tmp/linksf_admin.js',
          'tmp/linksf_admin.css'
        ]},
        options: {
          complete: function(hashes) {
            var keyMap = {
              'tmp/linksf.js':        'appJs',
              'tmp/linksf.css':       'appCss',
              'tmp/linksf_admin.js':  'adminJs',
              'tmp/linksf_admin.css': 'adminCss'
            };

            var config = {
              parseAppId: process.env.PARSE_APP_ID,
              parseJsKey: process.env.PARSE_JS_KEY,
              gaToken:    process.env.GOOGLE_ANALYTICS_TOKEN,
              gaHost:     process.env.GOOGLE_ANALYTICS_HOST
            };

            Object.keys(hashes).forEach(function(key) {
              var matches = key.match(/^tmp\/(.*)(\..*)$/); // tmp/(filename)(.js)
              var outputFile = matches[1] + '-' + hashes[key] + matches[2];

              grunt.file.copy(key, 'build/' + outputFile);
              config[keyMap[key]] = outputFile;
            });

            grunt.file.write('build/index.html',
              grunt.template.process(grunt.file.read('app/index.html'), {data: config})
            );

            grunt.file.write('build/admin.html',
              grunt.template.process(grunt.file.read('admin/index.html'), {data: config})
            );

            grunt.file.write('test/acceptance/app.html',
              grunt.template.process(grunt.file.read('test/acceptance/app.html.template'), {data: config})
            );
          }
        }
      }
    },

    qunit: {all: ['test/acceptance/**/*.html']},

    env: {
      dev: {src: '.env.dev'},
      prod: {src: '.env.prod'}
    },

    autoprefixer: {
      options: {
        browsers: [
          'android >= 2.3',
          'last 3 versions'
        ]
      },
      all: {
        src: ['tmp/linksf.css', 'tmp/linksf_admin.css']
      }
    },

    s3: grunt.file.exists('s3.json') && grunt.file.readJSON('s3.json') || {},
    aws_s3: {
      options: {
        accessKeyId: '<%= s3.accessKeyId %>',
        secretAccessKey: '<%= s3.secretAccessKey %>',
        region: '<%= s3.region %>',
        uploadConcurrency: 4
      },
      dev: {
        options: { bucket: '<%= s3.devBucket %>' },
        files: [
          {expand: true, src: ['vendor/font/**', 'img/**']},
          {expand: true, src: 'build/*', dest: ''}
        ]
      },
      prod: {
        options: { bucket: '<%= s3.prodBucket %>' },
        files: [
          {expand: true, src: ['vendor/font/**', 'img/**']},
          {expand: true, src: 'build/*', dest: ''}
        ]
      }
    },

    shell: {
      parse: {
        command: 'parse deploy',
        options: {
          execOptions: {
            cwd: 'server'
          }
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-sass');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-cachebuster');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-env');
  grunt.loadNpmTasks('grunt-autoprefixer');
  grunt.loadNpmTasks('grunt-aws-s3');
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadTasks('tasks');

  grunt.registerTask('build:common', [
    'clean',
    'jshint',
    'simplemocha',
    'sass',
    'autoprefixer',
    'browserify'
  ]);

  grunt.registerTask('build:dev', [
    'env:dev',
    'build:common',
    'concat:app', 'concat:admin',
    'cachebuster',
    'qunit'
  ]);

  grunt.registerTask('build:prod', [
    'env:prod',
    'build:common',
    'cssmin', 'uglify',
    'concat:app_min', 'concat:admin_min',
    'cachebuster',
    'qunit'
  ]);

  grunt.registerTask('parse:deploy', ['parse:config', 'shell:parse']);
  grunt.registerTask('parse:deploy:dev', ['env:dev', 'parse:deploy']);
  grunt.registerTask('parse:deploy:prod', ['env:prod', 'parse:deploy']);

  grunt.registerTask('default', 'build:dev');

  grunt.registerTask('deploy:dev', [
    'build:dev',
    'aws_s3:dev',
    'parse:deploy:dev',
    'clean'
  ]);

  grunt.registerTask('deploy:prod', [
    'build:prod',
    'aws_s3:prod',
    'parse:deploy:prod',
    'clean'
  ]);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      all: ['index.js', 'lib/**/*.js', 'spec/**/*.js']
    },

    jasmine_node: {
      options: {
        forceExit: true,
        match: '.',
        matchall: false,
        useHelpers: true,
        extensions: 'js',
        specNameMatcher: 'spec',
        helperNameMatcher: 'helpers'
      },
      all: [ 'spec' ],
      unit: [ 'spec/unit' ],
      integration: [ 'spec/integration' ]
    },

    docker: {
      options: {
        inDir: 'lib',
        colourScheme: 'native'
      },
      doc: {
        src: [ 'lib' ],
        dest: 'doc/annotated'
      }
      // ghp: {
      //   src: [ 'lib' ],
      //   dest: 'tmp/gh-pages/annotated'
      // }
    },

    // 'gh-pages': {
    //   options: {
    //     base: 'tmp/gh-pages',
    //     add: true,
    //     message: 'Updated annotated source'
    //   },
    //   all: {
    //     src: '**'
    //   }
    // },

    clean: {
      doc: [ 'doc' ]
      // ghp: [ 'tmp/gh-pages' ]
    }
  });

  grunt.loadNpmTasks('grunt-bump');
  grunt.loadNpmTasks('grunt-docker');
  grunt.loadNpmTasks('grunt-jasmine-node');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-gh-pages');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.registerTask('default', [ 'jshint', 'jasmine_node:all' ]);
  grunt.registerTask('unit', [ 'jshint', 'jasmine_node:unit' ]);
  grunt.registerTask('int', [ 'jshint', 'jasmine_node:integration' ]);
  grunt.registerTask('doc', [ 'clean:doc', 'docker:doc' ]);
//  grunt.registerTask('ghp', [ 'clean:ghp', 'docker:ghp', 'gh-pages' ]);
};
<commit_msg>Added github pages<commit_after>module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      all: ['index.js', 'lib/**/*.js', 'spec/**/*.js']
    },

    jasmine_node: {
      options: {
        forceExit: true,
        match: '.',
        matchall: false,
        useHelpers: true,
        extensions: 'js',
        specNameMatcher: 'spec',
        helperNameMatcher: 'helpers'
      },
      all: [ 'spec' ],
      unit: [ 'spec/unit' ],
      integration: [ 'spec/integration' ]
    },

    docker: {
      options: {
        inDir: 'lib',
        colourScheme: 'native'
      },
      doc: {
        src: [ 'lib' ],
        dest: 'doc/annotated'
      },
      ghp: {
        src: [ 'lib' ],
        dest: 'tmp/gh-pages/annotated'
      }
    },

    'gh-pages': {
      options: {
        base: 'tmp/gh-pages',
        add: true,
        message: 'Updated annotated source'
      },
      all: {
        src: '**'
      }
    },

    clean: {
      doc: [ 'doc' ],
      ghp: [ 'tmp/gh-pages' ]
    }
  });

  grunt.loadNpmTasks('grunt-bump');
  grunt.loadNpmTasks('grunt-docker');
  grunt.loadNpmTasks('grunt-jasmine-node');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-gh-pages');
  grunt.loadNpmTasks('grunt-contrib-clean');

  grunt.registerTask('default', [ 'jshint', 'jasmine_node:all' ]);
  grunt.registerTask('unit', [ 'jshint', 'jasmine_node:unit' ]);
  grunt.registerTask('int', [ 'jshint', 'jasmine_node:integration' ]);
  grunt.registerTask('doc', [ 'clean:doc', 'docker:doc' ]);
  grunt.registerTask('ghp', [ 'clean:ghp', 'docker:ghp', 'gh-pages' ]);
};
<|endoftext|>