<commit_before>'use strict';
/*jshint maxlen:false*/

var gulp = require('gulp'),
    gutil = require('gulp-util'),
    mapStream = require('map-stream'),
    mergeStream = require('merge-stream'),
    notifier = require('node-notifier'),
    tildify = require('tildify'),
    commander = require('commander'),
    extend = require('extend'),
    del = require('del'),
    temp = require('temp').track(),
    nodejs = {
        fs: require('fs'),
        path: require('path'),
        os: require('os'),
        childProcess: require('child_process')
    },
    plugins = {
        changed: require('gulp-changed'),
        coffee: require('gulp-coffee'),
        concat: require('gulp-concat'),
        footer: require('gulp-footer'),
        iif: require('gulp-if'),
        inject: require('gulp-inject'),
        jade: require('gulp-jade'),
        jshint: require('gulp-jshint'),
        less: require('gulp-less'),
        plumber: require('gulp-plumber'),
        rename: require('gulp-rename'),
        sourceMaps: require('gulp-sourcemaps'),
        stripDebug: require('gulp-strip-debug'),
        webServer: require('gulp-webserver'),
        coffeeLint: require('gulp-coffeelint')
    },
    buildConfig = {
        // The location to use to write transformed output
        outputPath: './dev',

        // If source maps should be generated (coffee, less, js)
        sourceMaps: false,

        // The version to use for build
        version: '0.0.' + String(Math.round(Math.random() * 1000000)),

        // Whether or not this is a release build
        release: false,

        // Where to copy the release build files
        releaseOutput: './build',

        // If almond should be used instead of require.js
        useAlmond: false,

        // If the current build was started with the watch task
        IS_WATCH: false
    },
    argv = commander
        .option('--output [path]', 'The location to use to output build files [./dev | ./build]')
        .option('--source-maps', 'Generate source maps')
        .option('--release [version]', 'Create a release build [generate]')
        .option('--almond', 'If almond should be used instead of require.js')
        .option('--server', 'Start a livereload server')

        //- Gulp related CLI arguments
        .option('--tasks')
        .option('--color')
        .option('--no-color')
        .option('--gulpfile')
        .parse(process.argv);

if (argv.release) {
    buildConfig.release = true;
    buildConfig.outputPath = mkTmpDir();

    if (typeof argv.release === 'string') {
        buildConfig.version = argv.release;
    }
    if (typeof argv.output === 'string') {
        buildConfig.releaseOutput = argv.output;
    }
    if (argv.almond) {
        buildConfig.useAlmond = argv.almond;
    }
}
else {
    if (typeof argv.output === 'string') {
        buildConfig.outputPath = argv.output;
    }
}
if (argv.sourceMaps) {
    buildConfig.sourceMaps = true;
}

function onTaskError(error) {
    // When doing an optimized build, any task error is considered fatal
    gutil.log(gutil.colors.red(error.stack));

    if (buildConfig.release) {
        process.exit(1);
    }
}

function notify(message) {
    notifier.notify({title: 'SPA-KO', message: message, sound: true, wait: false, time: 10000});
}

function mkTmpDir() {
    return temp.mkdirSync('spa-ko-');
}

gulp.task('clean', function(done) {
    if (buildConfig.IS_WATCH || buildConfig.release) {
        done();
    }
    else {
        gutil.log('Cleaning output directory ' + gutil.colors.magenta(tildify(nodejs.path.resolve(buildConfig.outputPath))));
        del('*', {cwd: buildConfig.outputPath}, function(err) {
            if (err) {
                gutil.log(gutil.colors.red(err.stack));
            }
            done();
        });
    }
});

gulp.task('process-vendor-scripts', ['clean'], function() {
    var amdWrapTransform = function(depMap) {
        return mapStream(function(file, cb) {
            var dependencyNames = Object.keys(depMap);
            var variableNames = [];

            dependencyNames.forEach(function(name) {
                if (depMap[name] === true) {
                    variableNames.push(name);
                }
                else {
                    variableNames.push(depMap[name]);
                }
            });

            var output = [];
            output.push('define([');
            output.push(dependencyNames.map(function(name) { return '\'' + name + '\''; }).join(', '));
            output.push('], function(');
            output.push(variableNames.join(', '));
            output.push(') {\n\n');
            output.push(file.contents.toString('utf8'));
            output.push('\n});\n');

            file.contents = new Buffer(output.join(''));
            cb(null, file);
        });
    };
    var stream = mergeStream(
        gulp.src([
            'transition.js',
            'alert.js',
            'button.js',
            'carousel.js',
            'collapse.js',
            'dropdown.js',
            'modal.js',
            'tooltip.js',
            'popover.js',
            'scrollspy.js',
            'tab.js',
            'affix.js'
        ], {cwd: 'vendor/bootstrap/js'})
            .pipe(plugins.plumber(onTaskError))
            .pipe(plugins.concat('bootstrap.js'))
            .pipe(amdWrapTransform({jquery: 'jQuery'}))
            .pipe(plugins.changed(buildConfig.outputPath, {hasChanged: plugins.changed.compareSha1Digest})),
        gulp.src(buildConfig.release ? 'knockout.debug.js' : 'knockout.js', {cwd: 'vendor/knockout/dist'})
            .pipe(plugins.plumber(onTaskError))
            .pipe(plugins.rename('knockout.js'))
            .pipe(plugins.changed(buildConfig.outputPath, {hasChanged: plugins.changed.compareSha1Digest})),
        gulp.src([
            buildConfig.release && buildConfig.useAlmond ? 'vendor/almond/almond.js' : 'vendor/requirejs/require.js',
            'vendor/jquery/dist/jquery.js',
            'vendor/crossroads/dist/crossroads.js',
            'vendor/hasher/dist/js/hasher.js',
            'vendor/js-signals/dist/signals.js',
            'vendor/q/q.js',
            'vendor/requirejs-text/text.js'])
            .pipe(plugins.changed(buildConfig.outputPath))
    );
    return stream.pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-js', ['clean'], function() {
    gulp.src('src/app/**/*.js', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.iif(buildConfig.release, plugins.stripDebug()))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('jshint-stylish'))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-main', ['clean'], function() {
    var injectDependenciesOptions = {
        transform: function(filePath, file, i, length) {
            var fileName = '\'' + gutil.replaceExtension(file.relative, '') + '\'';
            if (i + 1 < length) {
                fileName += ',';
            }
            return fileName.replace(/\\/g, '/');
        },
        starttag: '//-inject:modules',
        endtag: '//-end-inject',
        addRootSlash: false
    };
    return gulp.src('src/main.js')
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.inject(gulp.src('src/app/**/module-init.{js,coffee}', {read: false, base: 'src'}), injectDependenciesOptions))
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('jshint-stylish'))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-coffee', ['clean'], function() {
    return gulp.src('src/app/**/*.coffee', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.coffeeLint())
        .pipe(plugins.coffeeLint.reporter('default'))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath, ext: '.js'}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.coffee())
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-scripts', ['process-app-js', 'process-app-main', 'process-app-coffee']);

gulp.task('process-assets', ['clean'], function() {
    return gulp.src('src/assets/**', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('assets', {cwd: buildConfig.outputPath}))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-less', ['clean'], function() {
    return gulp.src(['src/styles/main.less', 'src/app/**/*.less'])
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.concat('main.less'))
        .pipe(plugins.less({ ieCompat: false, compress: buildConfig.release}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(gulp.dest('styles', {cwd: buildConfig.outputPath}));
});

gulp.task('process-app-jade', ['clean'], function() {
    return gulp.src('src/app/**/*.jade', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath, ext: '.html'}))
        .pipe(plugins.jade({
            clientDebug: false,
            client: false,
            pretty: false,
            ext: '.html'
        }))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-index-jade', ['clean'], function() {
    return gulp.src('src/index.jade', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed(buildConfig.outputPath, {ext: '.html'}))
        .pipe(plugins.jade({
            clientDebug: false,
            client: false,
            pretty: false,
            ext: '.html',
            data: {
                APP_VERSION: buildConfig.version,
                ALMOND_VERSION: buildConfig.release && buildConfig.useAlmond ? require('./vendor/almond/bower.json').version : null,
                REQUIRE_VERSION: buildConfig.release && buildConfig.useAlmond ? null : require('./vendor/requirejs/bower.json').version
            }
        }))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-static-content', ['process-assets', 'process-less', 'process-app-jade', 'process-index-jade']);

gulp.task('build', ['process-vendor-scripts', 'process-app-scripts', 'process-static-content'], function(done) {
    if (buildConfig.release) {
        setTimeout(optimize.bind(undefined, done), 1000);
    }
    else {
        notify('Your build is complete');
        done();
    }
});

gulp.task('watch', ['build'], function() {
    gulp.watch('vendor/**/bower.json', ['process-vendor-js']);
    gulp.watch('src/app/**/*.js', ['process-app-js']);
    gulp.watch('src/main.js', ['process-app-main']);
    gulp.watch('src/app/**/*.coffee', ['process-app-coffee']);

    gulp.watch('src/assets/**', ['process-assets']);
    gulp.watch(['src/styles/*.less', 'src/app/**/*.less'], ['process-less']);
    gulp.watch('src/app/**/*.jade', ['process-app-jade']);
    gulp.watch('src/index.jade', ['process-index-jade']);

    if (argv.server) {
        var webServer = require('gulp-webserver'),
            plumber = require('gulp-plumber');

        gulp.src(buildConfig.outputPath)
            .pipe(plumber(onTaskError))
            .pipe(webServer({
                host: 'localhost',
                port: 8000,
                livereload: true,
                directoryListing: false,
                open: true
            }));
    }
});

gulp.task('default', ['build']);

function optimize(done) {
    try {
        var rjsConfig = extend({}, require('./require-config'));
        rjsConfig.appDir = buildConfig.outputPath;
        rjsConfig.dir = mkTmpDir();

        if (buildConfig.useAlmond) {
            rjsConfig.insertRequire = ['main'];
        }

        var globOptions = {read: false, cwd: buildConfig.outputPath, base: buildConfig.outputPath};
        var stream = gulp.src(['*.js', 'app/**/*.js', 'app/**/*.html', buildConfig.useAlmond ? '!almond.js' : '!require.js'], globOptions);

        stream.pipe(mapStream(function(file, callback) {
            switch (nodejs.path.extname(file.path)) {
                case '.js':
                    rjsConfig.include.push(gutil.replaceExtension(file.relative, ''));
                    break;
                case '.html':
                    rjsConfig.include.push('text!' + file.relative);
                    break;
                default:
                    gutil.log(gutil.colors.yellow(file.path));
                    break;
            }
            callback(null, file);
        }));
        stream.on('end', function() {
            var configPath = nodejs.path.join(buildConfig.outputPath, 'require-config.js');
            nodejs.fs.writeFileSync(configPath, '(' + JSON.stringify(rjsConfig) + ')');

            var process = nodejs.childProcess.spawn('./node_modules/.bin/r.js', ['-o', configPath], {stdio: 'inherit'});

            process.on('close', function(exitCode) {
                if (exitCode !== 0) {
                    notifier.notify({
                        title: 'SPA-KO',
                        message: 'The build failed. Exit code: ' + exitCode,
                        sound: true,
                        wait: false,
                        time: 10000
                    });
                    done();
                }
                else {
                    var buildFiles = [
                        'index.html',
                        buildConfig.useAlmond ? 'almond.js' : 'require.js',
                        'main.js',
                        'styles/**',
                        'assets/**',
                        'build.txt'
                    ];
                    var deployStream = gulp.src(buildFiles, {cwd: rjsConfig.dir, base: rjsConfig.dir})
                        .pipe(plugins.plumber(onTaskError))
                        .pipe(gulp.dest(buildConfig.releaseOutput));

                    deployStream.on('end', function() {
                        notifier.notify({
                            title: 'SPA-KO',
                            message: 'The build is complete',
                            sound: true,
                            wait: false,
                            time: 10000
                        });
                        done();
                    });
                }
            });
        });
        stream.on('error', function(err) {
            onTaskError(err);
        });
    }
    catch (err) {
        onTaskError(err);
    }
}
<commit_msg>feat(gulpfile): allow livereload server's port to be configurable through command line arguments<commit_after>'use strict';
/*jshint maxlen:false*/

var gulp = require('gulp'),
    gutil = require('gulp-util'),
    mapStream = require('map-stream'),
    mergeStream = require('merge-stream'),
    notifier = require('node-notifier'),
    tildify = require('tildify'),
    commander = require('commander'),
    extend = require('extend'),
    del = require('del'),
    temp = require('temp').track(),
    nodejs = {
        fs: require('fs'),
        path: require('path'),
        os: require('os'),
        childProcess: require('child_process')
    },
    plugins = {
        changed: require('gulp-changed'),
        coffee: require('gulp-coffee'),
        concat: require('gulp-concat'),
        footer: require('gulp-footer'),
        iif: require('gulp-if'),
        inject: require('gulp-inject'),
        jade: require('gulp-jade'),
        jshint: require('gulp-jshint'),
        less: require('gulp-less'),
        plumber: require('gulp-plumber'),
        rename: require('gulp-rename'),
        sourceMaps: require('gulp-sourcemaps'),
        stripDebug: require('gulp-strip-debug'),
        webServer: require('gulp-webserver'),
        coffeeLint: require('gulp-coffeelint')
    },
    buildConfig = {
        // The location to use to write transformed output
        outputPath: './dev',

        // If source maps should be generated (coffee, less, js)
        sourceMaps: false,

        // The version to use for build
        version: '0.0.' + String(Math.round(Math.random() * 1000000)),

        // Whether or not this is a release build
        release: false,

        // Where to copy the release build files
        releaseOutput: './build',

        // If almond should be used instead of require.js
        useAlmond: false,

        // If the current build was started with the watch task
        IS_WATCH: false
    },
    argv = commander
        .option('--output [path]', 'The location to use to output build files [./dev | ./build]')
        .option('--source-maps', 'Generate source maps')
        .option('--release [version]', 'Create a release build [generate]')
        .option('--almond', 'If almond should be used instead of require.js')
        .option('--server [port]', 'Start a livereload server on the specified port [8000]')

        //- Gulp related CLI arguments
        .option('--tasks')
        .option('--color')
        .option('--no-color')
        .option('--gulpfile')
        .parse(process.argv);

if (argv.release) {
    buildConfig.release = true;
    buildConfig.outputPath = mkTmpDir();

    if (typeof argv.release === 'string') {
        buildConfig.version = argv.release;
    }
    if (typeof argv.output === 'string') {
        buildConfig.releaseOutput = argv.output;
    }
    if (argv.almond) {
        buildConfig.useAlmond = argv.almond;
    }
}
else {
    if (typeof argv.output === 'string') {
        buildConfig.outputPath = argv.output;
    }
}
if (argv.sourceMaps) {
    buildConfig.sourceMaps = true;
}

function onTaskError(error) {
    // When doing an optimized build, any task error is considered fatal
    gutil.log(gutil.colors.red(error.stack));

    if (buildConfig.release) {
        process.exit(1);
    }
}

function notify(message) {
    notifier.notify({title: 'SPA-KO', message: message, sound: true, wait: false, time: 10000});
}

function mkTmpDir() {
    return temp.mkdirSync('spa-ko-');
}

gulp.task('clean', function(done) {
    if (buildConfig.IS_WATCH || buildConfig.release) {
        done();
    }
    else {
        gutil.log('Cleaning output directory ' + gutil.colors.magenta(tildify(nodejs.path.resolve(buildConfig.outputPath))));
        del('*', {cwd: buildConfig.outputPath}, function(err) {
            if (err) {
                gutil.log(gutil.colors.red(err.stack));
            }
            done();
        });
    }
});

gulp.task('process-vendor-scripts', ['clean'], function() {
    var amdWrapTransform = function(depMap) {
        return mapStream(function(file, cb) {
            var dependencyNames = Object.keys(depMap);
            var variableNames = [];

            dependencyNames.forEach(function(name) {
                if (depMap[name] === true) {
                    variableNames.push(name);
                }
                else {
                    variableNames.push(depMap[name]);
                }
            });

            var output = [];
            output.push('define([');
            output.push(dependencyNames.map(function(name) { return '\'' + name + '\''; }).join(', '));
            output.push('], function(');
            output.push(variableNames.join(', '));
            output.push(') {\n\n');
            output.push(file.contents.toString('utf8'));
            output.push('\n});\n');

            file.contents = new Buffer(output.join(''));
            cb(null, file);
        });
    };
    var stream = mergeStream(
        gulp.src([
            'transition.js',
            'alert.js',
            'button.js',
            'carousel.js',
            'collapse.js',
            'dropdown.js',
            'modal.js',
            'tooltip.js',
            'popover.js',
            'scrollspy.js',
            'tab.js',
            'affix.js'
        ], {cwd: 'vendor/bootstrap/js'})
            .pipe(plugins.plumber(onTaskError))
            .pipe(plugins.concat('bootstrap.js'))
            .pipe(amdWrapTransform({jquery: 'jQuery'}))
            .pipe(plugins.changed(buildConfig.outputPath, {hasChanged: plugins.changed.compareSha1Digest})),
        gulp.src(buildConfig.release ? 'knockout.debug.js' : 'knockout.js', {cwd: 'vendor/knockout/dist'})
            .pipe(plugins.plumber(onTaskError))
            .pipe(plugins.rename('knockout.js'))
            .pipe(plugins.changed(buildConfig.outputPath, {hasChanged: plugins.changed.compareSha1Digest})),
        gulp.src([
            buildConfig.release && buildConfig.useAlmond ? 'vendor/almond/almond.js' : 'vendor/requirejs/require.js',
            'vendor/jquery/dist/jquery.js',
            'vendor/crossroads/dist/crossroads.js',
            'vendor/hasher/dist/js/hasher.js',
            'vendor/js-signals/dist/signals.js',
            'vendor/q/q.js',
            'vendor/requirejs-text/text.js'])
            .pipe(plugins.changed(buildConfig.outputPath))
    );
    return stream.pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-js', ['clean'], function() {
    gulp.src('src/app/**/*.js', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.iif(buildConfig.release, plugins.stripDebug()))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('jshint-stylish'))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-main', ['clean'], function() {
    var injectDependenciesOptions = {
        transform: function(filePath, file, i, length) {
            var fileName = '\'' + gutil.replaceExtension(file.relative, '') + '\'';
            if (i + 1 < length) {
                fileName += ',';
            }
            return fileName.replace(/\\/g, '/');
        },
        starttag: '//-inject:modules',
        endtag: '//-end-inject',
        addRootSlash: false
    };
    return gulp.src('src/main.js')
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.inject(gulp.src('src/app/**/module-init.{js,coffee}', {read: false, base: 'src'}), injectDependenciesOptions))
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('jshint-stylish'))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-coffee', ['clean'], function() {
    return gulp.src('src/app/**/*.coffee', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.coffeeLint())
        .pipe(plugins.coffeeLint.reporter('default'))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath, ext: '.js'}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.coffee())
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-app-scripts', ['process-app-js', 'process-app-main', 'process-app-coffee']);

gulp.task('process-assets', ['clean'], function() {
    return gulp.src('src/assets/**', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('assets', {cwd: buildConfig.outputPath}))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-less', ['clean'], function() {
    return gulp.src(['src/styles/main.less', 'src/app/**/*.less'])
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.init()))
        .pipe(plugins.concat('main.less'))
        .pipe(plugins.less({ ieCompat: false, compress: buildConfig.release}))
        .pipe(plugins.iif(buildConfig.sourceMaps, plugins.sourceMaps.write()))
        .pipe(gulp.dest('styles', {cwd: buildConfig.outputPath}));
});

gulp.task('process-app-jade', ['clean'], function() {
    return gulp.src('src/app/**/*.jade', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed('app', {cwd: buildConfig.outputPath, ext: '.html'}))
        .pipe(plugins.jade({
            clientDebug: false,
            client: false,
            pretty: false,
            ext: '.html'
        }))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-index-jade', ['clean'], function() {
    return gulp.src('src/index.jade', {base: 'src'})
        .pipe(plugins.plumber(onTaskError))
        .pipe(plugins.changed(buildConfig.outputPath, {ext: '.html'}))
        .pipe(plugins.jade({
            clientDebug: false,
            client: false,
            pretty: false,
            ext: '.html',
            data: {
                APP_VERSION: buildConfig.version,
                ALMOND_VERSION: buildConfig.release && buildConfig.useAlmond ? require('./vendor/almond/bower.json').version : null,
                REQUIRE_VERSION: buildConfig.release && buildConfig.useAlmond ? null : require('./vendor/requirejs/bower.json').version
            }
        }))
        .pipe(gulp.dest(buildConfig.outputPath));
});

gulp.task('process-static-content', ['process-assets', 'process-less', 'process-app-jade', 'process-index-jade']);

gulp.task('build', ['process-vendor-scripts', 'process-app-scripts', 'process-static-content'], function(done) {
    if (buildConfig.release) {
        setTimeout(optimize.bind(undefined, done), 1000);
    }
    else {
        notify('Your build is complete');
        done();
    }
});

gulp.task('watch', ['build'], function() {
    gulp.watch('vendor/**/bower.json', ['process-vendor-js']);
    gulp.watch('src/app/**/*.js', ['process-app-js']);
    gulp.watch('src/main.js', ['process-app-main']);
    gulp.watch('src/app/**/*.coffee', ['process-app-coffee']);

    gulp.watch('src/assets/**', ['process-assets']);
    gulp.watch(['src/styles/*.less', 'src/app/**/*.less'], ['process-less']);
    gulp.watch('src/app/**/*.jade', ['process-app-jade']);
    gulp.watch('src/index.jade', ['process-index-jade']);

    if (argv.server) {
        var webServer = require('gulp-webserver'),
            plumber = require('gulp-plumber');

        gulp.src(buildConfig.outputPath)
            .pipe(plumber(onTaskError))
            .pipe(webServer({
                host: 'localhost',
                port: Number(arg.server) || 8000,
                livereload: true,
                directoryListing: false,
                open: true
            }));
    }
});

gulp.task('default', ['build']);

function optimize(done) {
    try {
        var rjsConfig = extend({}, require('./require-config'));
        rjsConfig.appDir = buildConfig.outputPath;
        rjsConfig.dir = mkTmpDir();

        if (buildConfig.useAlmond) {
            rjsConfig.insertRequire = ['main'];
        }

        var globOptions = {read: false, cwd: buildConfig.outputPath, base: buildConfig.outputPath};
        var stream = gulp.src(['*.js', 'app/**/*.js', 'app/**/*.html', buildConfig.useAlmond ? '!almond.js' : '!require.js'], globOptions);

        stream.pipe(mapStream(function(file, callback) {
            switch (nodejs.path.extname(file.path)) {
                case '.js':
                    rjsConfig.include.push(gutil.replaceExtension(file.relative, ''));
                    break;
                case '.html':
                    rjsConfig.include.push('text!' + file.relative);
                    break;
                default:
                    gutil.log(gutil.colors.yellow(file.path));
                    break;
            }
            callback(null, file);
        }));
        stream.on('end', function() {
            var configPath = nodejs.path.join(buildConfig.outputPath, 'require-config.js');
            nodejs.fs.writeFileSync(configPath, '(' + JSON.stringify(rjsConfig) + ')');

            var process = nodejs.childProcess.spawn('./node_modules/.bin/r.js', ['-o', configPath], {stdio: 'inherit'});

            process.on('close', function(exitCode) {
                if (exitCode !== 0) {
                    notifier.notify({
                        title: 'SPA-KO',
                        message: 'The build failed. Exit code: ' + exitCode,
                        sound: true,
                        wait: false,
                        time: 10000
                    });
                    done();
                }
                else {
                    var buildFiles = [
                        'index.html',
                        buildConfig.useAlmond ? 'almond.js' : 'require.js',
                        'main.js',
                        'styles/**',
                        'assets/**',
                        'build.txt'
                    ];
                    var deployStream = gulp.src(buildFiles, {cwd: rjsConfig.dir, base: rjsConfig.dir})
                        .pipe(plugins.plumber(onTaskError))
                        .pipe(gulp.dest(buildConfig.releaseOutput));

                    deployStream.on('end', function() {
                        notifier.notify({
                            title: 'SPA-KO',
                            message: 'The build is complete',
                            sound: true,
                            wait: false,
                            time: 10000
                        });
                        done();
                    });
                }
            });
        });
        stream.on('error', function(err) {
            onTaskError(err);
        });
    }
    catch (err) {
        onTaskError(err);
    }
}
<|endoftext|>


<commit_before>var browserify = require('browserify'),
	shim = require('browserify-shim'),
	chalk = require('chalk'),
	del = require('del'),
	gulp = require('gulp'),
	bump = require('gulp-bump'),
	connect = require('gulp-connect'),
	deploy = require("gulp-gh-pages"),
	git = require("gulp-git"),
	less = require('gulp-less'),
	rename = require('gulp-rename'),
	streamify = require('gulp-streamify'),
	uglify = require('gulp-uglify'),
	gutil = require('gulp-util'),
	merge = require('merge-stream'),
	reactify = require('reactify'),
	source = require('vinyl-source-stream'),
	watchify = require('watchify');

/**
 * Constants
 */

var SRC_PATH = 'src';
var DIST_PATH = 'dist';

var PACKAGE_FILE = 'Select.js';
var PACKAGE_NAME = 'react-select';
var COMPONENT_NAME = 'Select';

var DEPENDENCIES = [
	'react',
	'react-input-autosize',
	'underscore'
];

var EXAMPLE_SRC_PATH = 'examples/src';
var EXAMPLE_DIST_PATH = 'examples/dist';

var EXAMPLE_APP = 'app.js';
var EXAMPLE_LESS = 'example.less';
var EXAMPLE_FILES = [
	'.gitignore',
	'index.html',
	'standalone.html'
];

/**
 * Bundle helpers
 */

function doBundle(target, name, dest) {
	return target.bundle()
		.on('error', function(e) {
			gutil.log('Browserify Error', e);
		})
		.pipe(source(name))
		.pipe(gulp.dest(dest))
		.pipe(connect.reload());
}

function watchBundle(target, name, dest) {
	return watchify(target)
		.on('update', function (scriptIds) {
			scriptIds = scriptIds
				.filter(function(i) { return i.substr(0,2) !== './' })
				.map(function(i) { return chalk.blue(i.replace(__dirname, '')) });
			if (scriptIds.length > 1) {
				gutil.log(scriptIds.length + ' Scripts updated:\n* ' + scriptIds.join('\n* ') + '\nrebuilding...');
			} else {
				gutil.log(scriptIds[0] + ' updated, rebuilding...');
			}
			doBundle(target, name, dest);
		})
		.on('time', function (time) {
			gutil.log(chalk.green(name + ' built in ' + (Math.round(time / 10) / 100) + 's'));
		});
}

/**
 * Prepare task for examples
 */

gulp.task('prepare:examples', function(done) {
	del([EXAMPLE_DIST_PATH], done);
});

/**
 * Build example files
 */

function buildExampleFiles() {
	return gulp.src(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }))
		.pipe(gulp.dest(EXAMPLE_DIST_PATH))
		.pipe(connect.reload());
}

gulp.task('dev:build:example:files', buildExampleFiles);
gulp.task('build:example:files', ['prepare:examples'], buildExampleFiles);

/**
 * Build example css from less
 */

function buildExampleCSS() {
	return gulp.src(EXAMPLE_SRC_PATH + '/' + EXAMPLE_LESS)
		.pipe(less())
		.pipe(gulp.dest(EXAMPLE_DIST_PATH))
		.pipe(connect.reload());
}

gulp.task('dev:build:example:styles', buildExampleCSS);
gulp.task('build:example:styles', ['prepare:examples'], buildExampleCSS);

/**
 * Build example scripts
 *
 * Returns a gulp task with watchify when in development mode
 */

function buildExampleScripts(dev) {

	var dest = EXAMPLE_DIST_PATH;

	var opts = dev ? watchify.args : {};
	opts.debug = dev ? true : false;
	opts.hasExports = true;

	return function() {

		var common = browserify(opts),
			bundle = browserify(opts).require('./' + SRC_PATH + '/' + PACKAGE_FILE, { expose: PACKAGE_NAME }),
			example = browserify(opts).exclude(PACKAGE_NAME).add('./' + EXAMPLE_SRC_PATH + '/' + EXAMPLE_APP),
			standalone = browserify('./' + SRC_PATH + '/' + PACKAGE_FILE, { standalone: COMPONENT_NAME })
				.transform(reactify)
				.transform(shim);

		DEPENDENCIES.forEach(function(pkg) {
			common.require(pkg);
			bundle.exclude(pkg);
			example.exclude(pkg);
			standalone.exclude(pkg);
		});

		if (dev) {
			watchBundle(common, 'common.js', dest);
			watchBundle(bundle, 'bundle.js', dest);
			watchBundle(example, 'app.js', dest);
			watchBundle(standalone, 'standalone.js', dest);
		}

		return merge(
			doBundle(common, 'common.js', dest),
			doBundle(bundle, 'bundle.js', dest),
			doBundle(example, 'app.js', dest),
			doBundle(standalone, 'standalone.js', dest)
		);

	}

};

gulp.task('dev:build:example:scripts', buildExampleScripts(true));
gulp.task('build:example:scripts', ['prepare:examples'], buildExampleScripts());

/**
 * Build examples
 */

gulp.task('build:examples', [
	'build:example:files',
	'build:example:styles',
	'build:example:scripts'
]);

gulp.task('watch:examples', [
	'dev:build:example:files',
	'dev:build:example:styles',
	'dev:build:example:scripts'
], function() {
	gulp.watch(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }), ['dev:build:example:files']);
	gulp.watch([EXAMPLE_SRC_PATH + './' + EXAMPLE_LESS], ['dev:build:example:styles']);
});

/**
 * Serve task for local development
 */

gulp.task('dev:server', function() {
	connect.server({
		root: EXAMPLE_DIST_PATH,
		port: 8000,
		livereload: true
	});
});

/**
 * Development task
 */

gulp.task('dev', [
	'dev:server',
	'watch:examples'
]);

/**
 * Build task
 */

gulp.task('prepare:dist', function(done) {
	del([DIST_PATH], done);
});

gulp.task('build:styles', ['prepare:dist'], function() {
	return gulp.src('less/default.less')
		.pipe(less())
		.pipe(gulp.dest('dist'));
})

gulp.task('build:scripts', ['prepare:dist'], function() {

	var standalone = browserify('./' + SRC_PATH + '/' + PACKAGE_FILE, {
			standalone: COMPONENT_NAME
		})
		.transform(reactify)
		.transform(shim);

	DEPENDENCIES.forEach(function(pkg) {
		standalone.exclude(pkg);
	});

	return standalone.bundle()
		.on('error', function(e) {
			gutil.log('Browserify Error', e);
		})
		.pipe(source(PACKAGE_NAME + '.js'))
		.pipe(gulp.dest(DIST_PATH))
		.pipe(rename(PACKAGE_NAME + '.min.js'))
		.pipe(streamify(uglify()))
		.pipe(gulp.dest(DIST_PATH));

});

gulp.task('build', [
	'build:styles',
	'build:scripts',
	'build:examples'
]);

/**
 * Version bump tasks
 */

function getBumpTask(type) {
	return function() {
		return gulp.src(['./package.json', './bower.json'])
			.pipe(bump({ type: type }))
			.pipe(gulp.dest('./'));
	};
}

gulp.task('bump', getBumpTask('patch'));
gulp.task('bump:minor', getBumpTask('minor'));
gulp.task('bump:major', getBumpTask('major'));

/**
 * Git tag task
 * (version *must* be bumped first)
 */

gulp.task('publish:tag', function(done) {
	var pkg = require('./package.json');
	var v = 'v' + pkg.version;
	var message = 'Release ' + v;

	git.tag(v, message, function (err) {
		if (err) throw err;
		git.push('origin', v, function (err) {
			if (err) throw err;
			done();
		});
	});
});

/**
 * npm publish task
 * * (version *must* be bumped first)
 */

gulp.task('publish:npm', function(done) {
	require('child_process')
		.spawn('npm', ['publish'], { stdio: 'inherit' })
		.on('close', done);
});

/**
 * Deploy tasks
 */

gulp.task('publish:examples', ['build:examples'], function() {
	return gulp.src(EXAMPLE_DIST_PATH + '/**/*').pipe(deploy());
});

gulp.task('release', ['publish:tag', 'publish:npm', 'publish:examples']);
<commit_msg>Fixing watch task for example stylesheet<commit_after>var browserify = require('browserify'),
	shim = require('browserify-shim'),
	chalk = require('chalk'),
	del = require('del'),
	gulp = require('gulp'),
	bump = require('gulp-bump'),
	connect = require('gulp-connect'),
	deploy = require("gulp-gh-pages"),
	git = require("gulp-git"),
	less = require('gulp-less'),
	rename = require('gulp-rename'),
	streamify = require('gulp-streamify'),
	uglify = require('gulp-uglify'),
	gutil = require('gulp-util'),
	merge = require('merge-stream'),
	reactify = require('reactify'),
	source = require('vinyl-source-stream'),
	watchify = require('watchify');

/**
 * Constants
 */

var SRC_PATH = 'src';
var DIST_PATH = 'dist';

var PACKAGE_FILE = 'Select.js';
var PACKAGE_NAME = 'react-select';
var COMPONENT_NAME = 'Select';

var DEPENDENCIES = [
	'react',
	'react-input-autosize',
	'underscore'
];

var EXAMPLE_SRC_PATH = 'examples/src';
var EXAMPLE_DIST_PATH = 'examples/dist';

var EXAMPLE_APP = 'app.js';
var EXAMPLE_LESS = 'example.less';
var EXAMPLE_FILES = [
	'.gitignore',
	'index.html',
	'standalone.html'
];

/**
 * Bundle helpers
 */

function doBundle(target, name, dest) {
	return target.bundle()
		.on('error', function(e) {
			gutil.log('Browserify Error', e);
		})
		.pipe(source(name))
		.pipe(gulp.dest(dest))
		.pipe(connect.reload());
}

function watchBundle(target, name, dest) {
	return watchify(target)
		.on('update', function (scriptIds) {
			scriptIds = scriptIds
				.filter(function(i) { return i.substr(0,2) !== './' })
				.map(function(i) { return chalk.blue(i.replace(__dirname, '')) });
			if (scriptIds.length > 1) {
				gutil.log(scriptIds.length + ' Scripts updated:\n* ' + scriptIds.join('\n* ') + '\nrebuilding...');
			} else {
				gutil.log(scriptIds[0] + ' updated, rebuilding...');
			}
			doBundle(target, name, dest);
		})
		.on('time', function (time) {
			gutil.log(chalk.green(name + ' built in ' + (Math.round(time / 10) / 100) + 's'));
		});
}

/**
 * Prepare task for examples
 */

gulp.task('prepare:examples', function(done) {
	del([EXAMPLE_DIST_PATH], done);
});

/**
 * Build example files
 */

function buildExampleFiles() {
	return gulp.src(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }))
		.pipe(gulp.dest(EXAMPLE_DIST_PATH))
		.pipe(connect.reload());
}

gulp.task('dev:build:example:files', buildExampleFiles);
gulp.task('build:example:files', ['prepare:examples'], buildExampleFiles);

/**
 * Build example css from less
 */

function buildExampleCSS() {
	return gulp.src(EXAMPLE_SRC_PATH + '/' + EXAMPLE_LESS)
		.pipe(less())
		.pipe(gulp.dest(EXAMPLE_DIST_PATH))
		.pipe(connect.reload());
}

gulp.task('dev:build:example:styles', buildExampleCSS);
gulp.task('build:example:styles', ['prepare:examples'], buildExampleCSS);

/**
 * Build example scripts
 *
 * Returns a gulp task with watchify when in development mode
 */

function buildExampleScripts(dev) {

	var dest = EXAMPLE_DIST_PATH;

	var opts = dev ? watchify.args : {};
	opts.debug = dev ? true : false;
	opts.hasExports = true;

	return function() {

		var common = browserify(opts),
			bundle = browserify(opts).require('./' + SRC_PATH + '/' + PACKAGE_FILE, { expose: PACKAGE_NAME }),
			example = browserify(opts).exclude(PACKAGE_NAME).add('./' + EXAMPLE_SRC_PATH + '/' + EXAMPLE_APP),
			standalone = browserify('./' + SRC_PATH + '/' + PACKAGE_FILE, { standalone: COMPONENT_NAME })
				.transform(reactify)
				.transform(shim);

		DEPENDENCIES.forEach(function(pkg) {
			common.require(pkg);
			bundle.exclude(pkg);
			example.exclude(pkg);
			standalone.exclude(pkg);
		});

		if (dev) {
			watchBundle(common, 'common.js', dest);
			watchBundle(bundle, 'bundle.js', dest);
			watchBundle(example, 'app.js', dest);
			watchBundle(standalone, 'standalone.js', dest);
		}

		return merge(
			doBundle(common, 'common.js', dest),
			doBundle(bundle, 'bundle.js', dest),
			doBundle(example, 'app.js', dest),
			doBundle(standalone, 'standalone.js', dest)
		);

	}

};

gulp.task('dev:build:example:scripts', buildExampleScripts(true));
gulp.task('build:example:scripts', ['prepare:examples'], buildExampleScripts());

/**
 * Build examples
 */

gulp.task('build:examples', [
	'build:example:files',
	'build:example:styles',
	'build:example:scripts'
]);

gulp.task('watch:examples', [
	'dev:build:example:files',
	'dev:build:example:styles',
	'dev:build:example:scripts'
], function() {
	gulp.watch(EXAMPLE_FILES.map(function(i) { return EXAMPLE_SRC_PATH + '/' + i }), ['dev:build:example:files']);
	gulp.watch([EXAMPLE_SRC_PATH + '/' + EXAMPLE_LESS], ['dev:build:example:styles']);
});

/**
 * Serve task for local development
 */

gulp.task('dev:server', function() {
	connect.server({
		root: EXAMPLE_DIST_PATH,
		port: 8000,
		livereload: true
	});
});

/**
 * Development task
 */

gulp.task('dev', [
	'dev:server',
	'watch:examples'
]);

/**
 * Build task
 */

gulp.task('prepare:dist', function(done) {
	del([DIST_PATH], done);
});

gulp.task('build:styles', ['prepare:dist'], function() {
	return gulp.src('less/default.less')
		.pipe(less())
		.pipe(gulp.dest('dist'));
})

gulp.task('build:scripts', ['prepare:dist'], function() {

	var standalone = browserify('./' + SRC_PATH + '/' + PACKAGE_FILE, {
			standalone: COMPONENT_NAME
		})
		.transform(reactify)
		.transform(shim);

	DEPENDENCIES.forEach(function(pkg) {
		standalone.exclude(pkg);
	});

	return standalone.bundle()
		.on('error', function(e) {
			gutil.log('Browserify Error', e);
		})
		.pipe(source(PACKAGE_NAME + '.js'))
		.pipe(gulp.dest(DIST_PATH))
		.pipe(rename(PACKAGE_NAME + '.min.js'))
		.pipe(streamify(uglify()))
		.pipe(gulp.dest(DIST_PATH));

});

gulp.task('build', [
	'build:styles',
	'build:scripts',
	'build:examples'
]);

/**
 * Version bump tasks
 */

function getBumpTask(type) {
	return function() {
		return gulp.src(['./package.json', './bower.json'])
			.pipe(bump({ type: type }))
			.pipe(gulp.dest('./'));
	};
}

gulp.task('bump', getBumpTask('patch'));
gulp.task('bump:minor', getBumpTask('minor'));
gulp.task('bump:major', getBumpTask('major'));

/**
 * Git tag task
 * (version *must* be bumped first)
 */

gulp.task('publish:tag', function(done) {
	var pkg = require('./package.json');
	var v = 'v' + pkg.version;
	var message = 'Release ' + v;

	git.tag(v, message, function (err) {
		if (err) throw err;
		git.push('origin', v, function (err) {
			if (err) throw err;
			done();
		});
	});
});

/**
 * npm publish task
 * * (version *must* be bumped first)
 */

gulp.task('publish:npm', function(done) {
	require('child_process')
		.spawn('npm', ['publish'], { stdio: 'inherit' })
		.on('close', done);
});

/**
 * Deploy tasks
 */

gulp.task('publish:examples', ['build:examples'], function() {
	return gulp.src(EXAMPLE_DIST_PATH + '/**/*').pipe(deploy());
});

gulp.task('release', ['publish:tag', 'publish:npm', 'publish:examples']);
<|endoftext|>


<commit_before>
////////////////////////////////
		//Setup//
////////////////////////////////

// Plugins
var gulp = require('gulp'),
      pjson = require('./package.json'),
      gutil = require('gulp-util'),
      sass = require('gulp-sass'),
      autoprefixer = require('gulp-autoprefixer'),
      cssnano = require('gulp-cssnano'),
      rename = require('gulp-rename'),
      del = require('del'),
      plumber = require('gulp-plumber'),
      pixrem = require('gulp-pixrem'),
      uglify = require('gulp-uglify'),
      imagemin = require('gulp-imagemin'),
      spawn = require('child_process').spawn,
      runSequence = require('run-sequence'),
      browserSync = require('browser-sync').create(),
      ts = require('gulp-typescript'),
      reload = browserSync.reload,
      browserify = require("browserify"),
      tsify = require("tsify"),
      source = require('vinyl-source-stream');

// Relative paths function
var pathsConfig = function (appName) {
  this.app = "./" + (appName || pjson.name);

  return {
    app: this.app,
    templates: this.app + '/templates',
    css: this.app + '/static/css',
    sass: this.app + '/static/sass',
    fonts: this.app + '/static/fonts',
    images: this.app + '/static/images',
    typescript: this.app + '/static/typescript',
    js: this.app + '/static/js',
  }
};

var paths = pathsConfig();

////////////////////////////////
		//Tasks//
////////////////////////////////

// Styles autoprefixing and minification
gulp.task('styles', function() {
  return gulp.src(paths.sass + '/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(plumber()) // Checks for errors
    .pipe(autoprefixer({browsers: ['last 2 versions']})) // Adds vendor prefixes
    .pipe(pixrem())  // add fallbacks for rem units
    .pipe(gulp.dest(paths.css));
    // .pipe(rename({ suffix: '.min' }))
    // .pipe(cssnano()) // Minifies the result
    // .pipe(gulp.dest(paths.css));
});

// typescript project

// Javascript minification
gulp.task('scripts', function() {
    return browserify({
      'basedir': '.',
      'debug': true,
      'entries': [`${paths.typescript}/project.tsx`],
      'cache': {},
      'packageCache': {}
    })
    .plugin(tsify)
    .bundle()
    .pipe(source("project.js"))
    // .pipe(plumber()) // Checks for errors
    // .pipe(uglify()) // Minifies the js
    // .pipe(rename({ suffix: '.js' }))
    .pipe(gulp.dest(paths.js));
});

// Image compression
gulp.task('imgCompression', function(){
  return gulp.src(paths.images + '/*')
    .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images
    .pipe(gulp.dest(paths.images))
});

// Run django server
gulp.task('runServer', function(cb) {
  var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
  cmd.on('close', function(code) {
    console.log('runServer exited with code ' + code);
    cb(code);
  });
});

// Browser sync server for live reload
gulp.task('browserSync', function() {
    browserSync.init(
      [paths.css + "/*.css", paths.js + "*.js", paths.templates + '*.html'], {
        proxy:  "localhost:8000"
    });
});

// Watch
gulp.task('watch', function() {

  gulp.watch(paths.sass + '/*.scss', ['styles']);
  gulp.watch(paths.typescript + '/*', ['scripts']); // .on("change", reload);
  // gulp.watch(paths.images + '/*', ['imgCompression']);
  // gulp.watch(paths.templates + '/**/*.html').on("change", reload);

});

// Default task
gulp.task('default', function() {
    runSequence(['styles', 'scripts'], 'watch');
});

// gulp.task('default', function() {
//     runSequence(['styles', 'scripts', 'imgCompression'], 'runServer', 'browserSync', 'watch');
// });
<commit_msg>better detection for component modifications<commit_after>
////////////////////////////////
		//Setup//
////////////////////////////////

// Plugins
var gulp = require('gulp'),
      pjson = require('./package.json'),
      gutil = require('gulp-util'),
      sass = require('gulp-sass'),
      autoprefixer = require('gulp-autoprefixer'),
      cssnano = require('gulp-cssnano'),
      rename = require('gulp-rename'),
      del = require('del'),
      plumber = require('gulp-plumber'),
      pixrem = require('gulp-pixrem'),
      uglify = require('gulp-uglify'),
      imagemin = require('gulp-imagemin'),
      spawn = require('child_process').spawn,
      runSequence = require('run-sequence'),
      browserSync = require('browser-sync').create(),
      ts = require('gulp-typescript'),
      reload = browserSync.reload,
      browserify = require("browserify"),
      tsify = require("tsify"),
      source = require('vinyl-source-stream');

// Relative paths function
var pathsConfig = function (appName) {
  this.app = "./" + (appName || pjson.name);

  return {
    app: this.app,
    templates: this.app + '/templates',
    css: this.app + '/static/css',
    sass: this.app + '/static/sass',
    fonts: this.app + '/static/fonts',
    images: this.app + '/static/images',
    typescript: this.app + '/static/typescript',
    js: this.app + '/static/js',
  }
};

var paths = pathsConfig();

////////////////////////////////
		//Tasks//
////////////////////////////////

// Styles autoprefixing and minification
gulp.task('styles', function() {
  return gulp.src(paths.sass + '/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(plumber()) // Checks for errors
    .pipe(autoprefixer({browsers: ['last 2 versions']})) // Adds vendor prefixes
    .pipe(pixrem())  // add fallbacks for rem units
    .pipe(gulp.dest(paths.css));
    // .pipe(rename({ suffix: '.min' }))
    // .pipe(cssnano()) // Minifies the result
    // .pipe(gulp.dest(paths.css));
});

// typescript project

// Javascript minification
gulp.task('scripts', function() {
    return browserify({
      'basedir': '.',
      'debug': true,
      'entries': [`${paths.typescript}/project.tsx`],
      'cache': {},
      'packageCache': {}
    })
    .plugin(tsify)
    .bundle()
    .pipe(source("project.js"))
    // .pipe(plumber()) // Checks for errors
    // .pipe(uglify()) // Minifies the js
    // .pipe(rename({ suffix: '.js' }))
    .pipe(gulp.dest(paths.js));
});

// Image compression
gulp.task('imgCompression', function(){
  return gulp.src(paths.images + '/*')
    .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images
    .pipe(gulp.dest(paths.images))
});

// Run django server
gulp.task('runServer', function(cb) {
  var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'});
  cmd.on('close', function(code) {
    console.log('runServer exited with code ' + code);
    cb(code);
  });
});

// Browser sync server for live reload
gulp.task('browserSync', function() {
    browserSync.init(
      [paths.css + "/*.css", paths.js + "*.js", paths.templates + '*.html'], {
        proxy:  "localhost:8000"
    });
});

// Watch
gulp.task('watch', function() {

  gulp.watch(paths.sass + '/*.scss', ['styles']);
  gulp.watch(paths.typescript + '/**', ['scripts']); // .on("change", reload);
  // gulp.watch(paths.images + '/*', ['imgCompression']);
  // gulp.watch(paths.templates + '/**/*.html').on("change", reload);

});

// Default task
gulp.task('default', function() {
    runSequence(['styles', 'scripts'], 'watch');
});

// gulp.task('default', function() {
//     runSequence(['styles', 'scripts', 'imgCompression'], 'runServer', 'browserSync', 'watch');
// });
<|endoftext|>


<commit_before>const gulp = require('gulp')

const paths = {
  src: [
    'etc/**/*'
  ],
  dest: 'build'
}

gulp.task('default', ['install'])

gulp.task('install', () => (
  gulp.src(paths.src)
    .pipe(gulp.dest(paths.dest))
))
<commit_msg>Remove gulpfile<commit_after><|endoftext|>


<commit_before>var gulp = require('gulp-help')(require('gulp'));
var tslint = require('gulp-tslint');
var eslint = require('gulp-eslint');
var beautify = require('gulp-beautify');
var sourcemaps = require("gulp-sourcemaps");
var ts = require('gulp-typescript');
var concatcss = require('gulp-concat-css');
var watch = require('gulp-watch');
var browsersync = require('browser-sync').create();
var del = require('del');
var tape = require('gulp-tape');
var tapcolorize = require('tap-colorize');

gulp.task('test', function() {
    return gulp.src('src/test/**/*.test.js')
        .pipe(tape({
            reporter: tapcolorize()
        }));
});

// lint javascript and typescript
gulp.task('eslint', 'Lints javascript files with eslint', function() {
    return gulp.src(['./src/lib/**/*.js',
                     './src/test/**/*.js',
                     '!node_modules/**',
                     '!bower_components/**'])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
})

gulp.task('tslint',
    'Lints typescript with tslint according to tslint.json present',
    function() {
        return gulp.src('./src/**/*.ts')
            .pipe(tslint())
            .pipe(tslint.report('verbose'));
    });

// cleanup files
gulp.task('beautify-js', false, function() {
    gulp.src(['./src/lib/**/*.js',
              './src/test/**/*.js'])
        .pipe(beautify())
        .pipe(gulp.dest('./'));
});

// compile javascript from typescript
var tsProject = ts.createProject('tsconfig.json');
// compile typescript
gulp.task('ts',
    'Compiles typescript to javascript according to tsconfig.json', ['tslint'],
    function() {
        var tsResult = tsProject.src()
            .pipe(sourcemaps.init())
            .pipe(ts(tsProject));

        return tsResult.js
            .pipe(sourcemaps.write())
            .pipe(gulp.dest("./"));
    });

//concatenate css files
gulp.task('concat-css',
    'Concatenates css files',
    function() {
        return gulp.src('src/lib/**/*.css')
            .pipe(concatcss('punchcard.css'))
            .pipe(gulp.dest('build/styles/'));
    });

// copy html and css files to build
gulp.task('copy-build',
    'Copies html to build directory',
    function() {
        gulp.src('./src/lib/*.html').pipe(gulp.dest('./build/'))
    });

// watch and build on change
gulp.task('watch', false, ['ts'], function() {
    gulp.watch(['./src/lib/**/*.ts'], ['ts']);
    gulp.watch(['./src/lib/*.html'], ['copy-build']);
    gulp.watch(['./src/lib/styles/*.css'], ['concat-css']);
});

// run BrowserSync
gulp.task('browser-sync',
    'Serves files with BrowserSync, starts and restarts browser on changes',
    function() {
        browsersync.init({
            server: {
                baseDir: ['./', 'build'],
                index: 'build/index.html'
            },
            files: ['build/**/*.js', 'build/**/*.css', 'build/**/*.html'],
            browser: []
        });

        gulp.watch(['build/**/*.js', 'build/**/*.css', 'build/**/*.html'])
            .on('change', browsersync.reload);
    });

gulp.task('clean',
    'Remove files generated in build process',
    function(cb) {
        rimraf('./build', cb);
    });

gulp.task('purge',
    'Remove ./node_modules/, ./bower_components/, and ./typings/, as well as files generated in build process ',
    function () {
        return del([
            './node_modules',
            './bower_components',
            './typings',
            './build',
    ]);
});

gulp.task('dev-watch',
    'Watches files for development', ['ts', 'concat-css', 'copy-build', 'watch', 'browser-sync']);

<commit_msg>changed rimraf in 'clean' task to use del (issue #11)<commit_after>var gulp = require('gulp-help')(require('gulp'));
var tslint = require('gulp-tslint');
var eslint = require('gulp-eslint');
var beautify = require('gulp-beautify');
var sourcemaps = require("gulp-sourcemaps");
var ts = require('gulp-typescript');
var concatcss = require('gulp-concat-css');
var watch = require('gulp-watch');
var browsersync = require('browser-sync').create();
var del = require('del');
var tape = require('gulp-tape');
var tapcolorize = require('tap-colorize');

gulp.task('test', function() {
    return gulp.src('src/test/**/*.test.js')
        .pipe(tape({
            reporter: tapcolorize()
        }));
});

// lint javascript and typescript
gulp.task('eslint', 'Lints javascript files with eslint', function() {
    return gulp.src(['./src/lib/**/*.js',
                     './src/test/**/*.js',
                     '!node_modules/**',
                     '!bower_components/**'])
        .pipe(eslint())
        .pipe(eslint.format())
        .pipe(eslint.failAfterError());
})

gulp.task('tslint',
    'Lints typescript with tslint according to tslint.json present',
    function() {
        return gulp.src('./src/**/*.ts')
            .pipe(tslint())
            .pipe(tslint.report('verbose'));
    });

// cleanup files
gulp.task('beautify-js', false, function() {
    gulp.src(['./src/lib/**/*.js',
              './src/test/**/*.js'])
        .pipe(beautify())
        .pipe(gulp.dest('./'));
});

// compile javascript from typescript
var tsProject = ts.createProject('tsconfig.json');
// compile typescript
gulp.task('ts',
    'Compiles typescript to javascript according to tsconfig.json', ['tslint'],
    function() {
        var tsResult = tsProject.src()
            .pipe(sourcemaps.init())
            .pipe(ts(tsProject));

        return tsResult.js
            .pipe(sourcemaps.write())
            .pipe(gulp.dest("./"));
    });

//concatenate css files
gulp.task('concat-css',
    'Concatenates css files',
    function() {
        return gulp.src('src/lib/**/*.css')
            .pipe(concatcss('punchcard.css'))
            .pipe(gulp.dest('build/styles/'));
    });

// copy html and css files to build
gulp.task('copy-build',
    'Copies html to build directory',
    function() {
        gulp.src('./src/lib/*.html').pipe(gulp.dest('./build/'))
    });

// watch and build on change
gulp.task('watch', false, ['ts'], function() {
    gulp.watch(['./src/lib/**/*.ts'], ['ts']);
    gulp.watch(['./src/lib/*.html'], ['copy-build']);
    gulp.watch(['./src/lib/styles/*.css'], ['concat-css']);
});

// run BrowserSync
gulp.task('browser-sync',
    'Serves files with BrowserSync, starts and restarts browser on changes',
    function() {
        browsersync.init({
            server: {
                baseDir: ['./', 'build'],
                index: 'build/index.html'
            },
            files: ['build/**/*.js', 'build/**/*.css', 'build/**/*.html'],
            browser: []
        });

        gulp.watch(['build/**/*.js', 'build/**/*.css', 'build/**/*.html'])
            .on('change', browsersync.reload);
    });

gulp.task('clean',
    'Remove files generated in build process',
    function () {
        return del([
            './build',
    ]);
});

gulp.task('purge',
    'Remove ./node_modules/, ./bower_components/, and ./typings/, as well as files generated in build process ',
    function () {
        return del([
            './node_modules',
            './bower_components',
            './typings',
            './build',
    ]);
});

gulp.task('dev-watch',
    'Watches files for development', ['ts', 'concat-css', 'copy-build', 'watch', 'browser-sync']);

<|endoftext|>


<commit_before>'use strict'
const gulp = require('gulp')

gulp.task('generate-service-worker', (callback) => {
  let path = require('path')
  let swPrecache = require('sw-precache')
  let rootDir = 'app'

  swPrecache.write(path.join(rootDir, 'service-worker.js'), {
    staticFileGlobs: [ rootDir + '/*.{html, png}', rootDir + '/elements/*.{js,html,png}', rootDir + '/bower_components/*/*.{js,html,png}', '/bower_components/*/!(Gruntfile.js)' ],
    stripPrefix: rootDir,
    replacePrefix: '/playbulb-polymer/app/'
  }, callback)
})
<commit_msg>update gulpfile<commit_after>'use strict'
const gulp = require('gulp')

gulp.task('generate-service-worker', (callback) => {
  let path = require('path')
  let swPrecache = require('sw-precache')
  let rootDir = 'app'

  swPrecache.write(path.join(rootDir, 'service-worker.js'), {
    staticFileGlobs: [
      rootDir + '/*.{html, png}',
      rootDir + '/elements/*.{js,html,png}',
      rootDir + '/bower_components/*/*.{js,html,png}',
      '/bower_components/*/!(Gruntfile.js)' ],
    stripPrefix: rootDir,
    replacePrefix: '/playbulb-polymer/app'
  }, callback)
})
<|endoftext|>


<commit_before>var gulp = require('gulp'),
  uglify = require('gulp-uglify'),
  connect = require('gulp-connect'),
  karma = require('gulp-karma'),
  jshint = require('gulp-jshint'),
  protractor = require("gulp-protractor").protractor,
  concat = require('gulp-concat'),
  rename = require('gulp-rename'),
  program = require('commander'),
  stylish = require('jshint-stylish'),
  debug = false,
  WATCH_MODE = 'watch',
  RUN_MODE = 'run';

var mode = RUN_MODE;

function list(val) {
  return val.split(',');
}

program
  .version('0.0.1')
  .option('-t, --tests [glob]', 'Specify which tests to run')
  .option('-b, --browsers <items>', 'Specify which browsers to run on', list)
  .option('-r, --reporters <items>', 'Specify which reporters to use', list)
  .parse(process.argv);

gulp.task('js', function() {
  var jsTask = gulp.src('src/**/*.js')
    .pipe(concat('ng-breadcrumbs.js'))
    .pipe(gulp.dest('dist'));
  if (!debug) {
    jsTask.pipe(uglify());
  }
  jsTask
    .pipe(rename('ng-breadcrumbs.min.js'))
    .pipe(gulp.dest('dist'));
});

gulp.task('lint', function() {
  gulp.src(['src/**/*.js', 'test/**/*.js'])
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter(stylish));
});

gulp.task('connect', function() {
  if (mode === WATCH_MODE) {
    gulp.watch(['public/**/*', 'index.html'], function() {
      gulp.src(['public/**/*', 'index.html'])
        .pipe(connect.reload());
    });
  }

  connect.server({
    livereload: mode === WATCH_MODE
  });
});

gulp.task('karma', ['js'], function() {
  // undefined.js: unfortunately necessary for now
  gulp.src(['undefined.js'])
    .pipe(karma({
      configFile: 'karma.conf.js',
      action: mode,
      tests: program.tests,
      reporters: program.reporters || ['progress'],
      browsers: program.browsers || ['PhantomJS']
    }))
    .on('error', function() {});
});

gulp.task('protractor', function(done) {
  gulp.src(["./src/tests/*.js"])
    .pipe(protractor({
      configFile: 'protractor.conf.js',
      args: [
        '--baseUrl', 'http://127.0.0.1:8080',
        '--browser', program.browsers ? program.browsers[0] : 'phantomjs'
      ]
    }))
    .on('end', function() {
      if (mode === RUN_MODE) {
        connect.serverClose();
      }
      done();
    })
    .on('error', function() { done(); });
});

gulp.task('debug', function() {
  debug = true;
});

gulp.task('watch-mode', function() {
  mode = WATCH_MODE;

  var jsWatcher = gulp.watch(['src/**/*.js'], ['js']),
      jshintWatcher = gulp.watch(['src/**/*.js', 'test/**/*.js'], ['lint']),
      karmaWatcher = gulp.watch('test/unit/**/*.js', ['karma']),
      protractorWatcher = gulp.watch('test/ui/**/*.js', ['protractor']);

  function changeNotification(event) {
    console.log('File', event.path, 'was', event.type, ', running tasks...');
  }

  jsWatcher.on('change', changeNotification);
  jshintWatcher.on('change', changeNotification);
  karmaWatcher.on('change', changeNotification);
  protractorWatcher.on('change', changeNotification);
});

gulp.task('all', ['js', 'lint', 'karma', 'protractor']);
gulp.task('default', ['watch-mode', 'all']);
gulp.task('server', ['connect', 'default']);
gulp.task('test', ['connect', 'all']);<commit_msg>Closing connect server if test error and RUN_MODE<commit_after>var gulp = require('gulp'),
  uglify = require('gulp-uglify'),
  connect = require('gulp-connect'),
  karma = require('gulp-karma'),
  jshint = require('gulp-jshint'),
  protractor = require("gulp-protractor").protractor,
  concat = require('gulp-concat'),
  rename = require('gulp-rename'),
  program = require('commander'),
  stylish = require('jshint-stylish'),
  debug = false,
  WATCH_MODE = 'watch',
  RUN_MODE = 'run';

var mode = RUN_MODE;

function list(val) {
  return val.split(',');
}

program
  .version('0.0.1')
  .option('-t, --tests [glob]', 'Specify which tests to run')
  .option('-b, --browsers <items>', 'Specify which browsers to run on', list)
  .option('-r, --reporters <items>', 'Specify which reporters to use', list)
  .parse(process.argv);

gulp.task('js', function() {
  var jsTask = gulp.src('src/**/*.js')
    .pipe(concat('ng-breadcrumbs.js'))
    .pipe(gulp.dest('dist'));
  if (!debug) {
    jsTask.pipe(uglify());
  }
  jsTask
    .pipe(rename('ng-breadcrumbs.min.js'))
    .pipe(gulp.dest('dist'));
});

gulp.task('lint', function() {
  gulp.src(['src/**/*.js', 'test/**/*.js'])
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter(stylish));
});

gulp.task('connect', function() {
  if (mode === WATCH_MODE) {
    gulp.watch(['public/**/*', 'index.html'], function() {
      gulp.src(['public/**/*', 'index.html'])
        .pipe(connect.reload());
    });
  }

  connect.server({
    livereload: mode === WATCH_MODE
  });
});

gulp.task('karma', ['js'], function() {
  // undefined.js: unfortunately necessary for now
  gulp.src(['undefined.js'])
    .pipe(karma({
      configFile: 'karma.conf.js',
      action: mode,
      tests: program.tests,
      reporters: program.reporters || ['progress'],
      browsers: program.browsers || ['PhantomJS']
    }))
    .on('error', function() {});
});

gulp.task('protractor', function(done) {
  gulp.src(["./src/tests/*.js"])
    .pipe(protractor({
      configFile: 'protractor.conf.js',
      args: [
        '--baseUrl', 'http://127.0.0.1:8080',
        '--browser', program.browsers ? program.browsers[0] : 'phantomjs'
      ]
    }))
    .on('end', function() {
      if (mode === RUN_MODE) {
        connect.serverClose();
      }
      done();
    })
    .on('error', function() {
      if (mode === RUN_MODE) {
        connect.serverClose();
      }
      done();
    });
});

gulp.task('debug', function() {
  debug = true;
});

gulp.task('watch-mode', function() {
  mode = WATCH_MODE;

  var jsWatcher = gulp.watch(['src/**/*.js'], ['js']),
      jshintWatcher = gulp.watch(['src/**/*.js', 'test/**/*.js'], ['lint']),
      karmaWatcher = gulp.watch('test/unit/**/*.js', ['karma']),
      protractorWatcher = gulp.watch('test/ui/**/*.js', ['protractor']);

  function changeNotification(event) {
    console.log('File', event.path, 'was', event.type, ', running tasks...');
  }

  jsWatcher.on('change', changeNotification);
  jshintWatcher.on('change', changeNotification);
  karmaWatcher.on('change', changeNotification);
  protractorWatcher.on('change', changeNotification);
});

gulp.task('all', ['js', 'lint', 'karma', 'protractor']);
gulp.task('default', ['watch-mode', 'all']);
gulp.task('server', ['connect', 'default']);
gulp.task('test', ['connect', 'all']);<|endoftext|>


<commit_before>'use strict'
var pull = require('pull-stream')
var para = require('pull-paramap')
var Notify = require('pull-notify')
var Cat = require('pull-cat')
var Debounce = require('observ-debounce')
var mdm = require('mdmanifest')
var apidoc = require('scuttlebot/lib/apidocs').replicate
var MutantToPull = require('./mutant-to-pull')
var {Struct, Dict} = require('mutant')

var Pushable = require('pull-pushable')

// compatibility function for old implementations of `latestSequence`
function toSeq (s) {
  return typeof s === 'number' ? s : s.sequence
}

module.exports = {
  name: 'replicate',
  version: '2.0.0',
  manifest: mdm.manifest(apidoc),
  init: function (sbot, config) {
    var debounce = Debounce(200)
    var listeners = {}
    var newPeer = Notify()

    // keep track of sync progress and provide to client

    var start = null
    var count = 0
    var rate = 0
    var toSend = {}
    var peerHas = {}
    var pendingPeer = {}

    window.pendingPeer = pendingPeer

    var syncStatus = Struct({
      type: 'global',
      incomplete: 0,
      pendingCount: 0,
      pendingPeers: Dict({}, {fixedIndexing: true}),
      feeds: null,
      rate: 0
    })

    window.syncStatus = syncStatus

    debounce(function () {
      var incomplete = 0
      var totalPending = 0
      var feeds = Object.keys(toSend).length
      var peers = {}

      Object.keys(pendingPeer).forEach(function (peerId) {
        if (pendingPeer[peerId]) {
          totalPending += 1

          if (Object.keys(toSend).some(function (feedId) {
            if (peerHas[peerId] && peerHas[peerId][feedId]) {
              return peerHas[peerId][feedId] > toSend[feedId]
            }
          })) {
            incomplete += 1
          }

          peers[peerId] = pendingPeer[peerId]
        }
      })

      syncStatus.set({
        incomplete: incomplete,
        feeds: syncStatus.loadedFriends ? feeds : null,
        pendingPeers: peers,
        pending: totalPending,
        rate: rate
      }, {merge: true})
    })

    pull(
      sbot.createLogStream({old: false, live: true, sync: false, keys: false}),
      pull.drain(function (e) {
        // track writes per second, mainly used for developing initial sync.
        if (!start) start = Date.now()
        var time = (Date.now() - start) / 1000
        if (time >= 1) {
          rate = count / time
          start = Date.now()
          count = 0
        }
        var pushable = listeners[e.author]

        if (pushable && pushable.sequence === e.sequence) {
          pushable.sequence++
          pushable.forEach(function (p) {
            p.push(e)
          })
        }
        count++
        addPeer({id: e.author, sequence: e.sequence})
      })
    )

    sbot.createHistoryStream.hook(function (fn, args) {
      var upto = args[0] || {}
      var seq = upto.sequence || upto.seq

      if (this._emit) this._emit('call:createHistoryStream', args[0])

      // if we are calling this locally, skip cleverness
      if (this === sbot) return fn.call(this, upto)

      // keep track of each requested value, per feed / per peer.
      peerHas[this.id] = peerHas[this.id] || {}
      peerHas[this.id][upto.id] = seq - 1

      debounce.set()

      // handle creating lots of history streams efficiently.
      // maybe this could be optimized in map-filter-reduce queries instead?
      if (toSend[upto.id] == null || (seq > toSend[upto.id])) {
        upto.old = false
        if (!upto.live) return pull.empty()
        var pushable = listeners[upto.id] = listeners[upto.id] || []
        var p = Pushable(function () {
          var i = pushable.indexOf(p)
          pushable.splice(i, 1)
        })
        pushable.push(p)
        pushable.sequence = upto.sequence
        return p
      }
      return fn.call(this, upto)
    })

    // collect the IDs of feeds we want to request
    var opts = config.replication || {}
    opts.hops = opts.hops || 3
    opts.dunbar = opts.dunbar || 150
    opts.live = true
    opts.meta = true

    function localPeers () {
      if (!sbot.gossip) return
      sbot.gossip.peers().forEach(function (e) {
        if (e.source === 'local' && !toSend[e.key]) {
          sbot.latestSequence(e.key, function (err, seq) {
            addPeer({id: e.key, sequence: err ? 0 : toSeq(seq)})
          })
        }
      })
    }

    // also request local peers.
    if (sbot.gossip) {
      // if we have the gossip plugin active, then include new local peers
      // so that you can put a name to someone on your local network.
      var int = setInterval(localPeers, 1000)
      if (int.unref) int.unref()
      localPeers()
    }

    function loadedFriends () {
      syncStatus.loadedFriends = true
      debounce.set()
    }

    function addPeer (upto) {
      if (upto.sync) return loadedFriends()
      if (!upto.id) return console.log('invalid', upto)

      if (toSend[upto.id] == null) {
        toSend[upto.id] = Math.max(toSend[upto.id] || 0, upto.sequence || upto.seq || 0)
        newPeer({ id: upto.id, sequence: toSend[upto.id], type: 'new' })
        debounce.set()
      } else {
        toSend[upto.id] = Math.max(toSend[upto.id] || 0, upto.sequence || upto.seq || 0)
      }

      debounce.set()
    }

    // create read-streams for the desired feeds
    pull(
      sbot.friends.createFriendStream(opts),
      // filter out duplicates, and also keep track of what we expect to receive
      // lookup the latest sequence from each user
      para(function (data, cb) {
        if (data.sync) return cb(null, data)
        var id = data.id || data
        sbot.latestSequence(id, function (err, seq) {
          cb(null, {
            id: id, sequence: err ? 0 : toSeq(seq)
          })
        })
      }, 32),
      pull.drain(addPeer, loadedFriends)
    )

    function upto (opts) {
      opts = opts || {}
      var ary = Object.keys(toSend).map(function (k) {
        return { id: k, sequence: toSend[k] }
      })
      if (opts.live) {
        return Cat([pull.values(ary), pull.once({sync: true}), newPeer.listen()])
      }

      return pull.values(ary)
    }

    sbot.on('rpc:connect', function (rpc) {
      // this is the cli client, just ignore.
      if (rpc.id === sbot.id) return
      // check for local peers, or manual connections.
      localPeers()
      sbot.emit('replicate:start', rpc)
      rpc.on('closed', function () {
        sbot.emit('replicate:finish', toSend)
      })
      pull(
        upto({live: opts.live}),
        pull.drain(function (upto) {
          if (upto.sync) return
          var last = (upto.sequence || upto.seq || 0)
          pendingPeer[rpc.id] = (pendingPeer[rpc.id] || 0) + 1
          debounce.set()

          pull(
            rpc.createHistoryStream({
              id: upto.id,
              seq: last + 1,
              live: false,
              keys: false
            }),
            pull.through((msg) => {
              start = Math.max(start, msg.sequence)
            }),
            sbot.createWriteStream(function () {
              // TODO: do something with the error
              // this seems to be thrown fairly regularly whenever something weird happens to the stream

              pendingPeer[rpc.id] -= 1
              debounce.set()

              // all synched, now lets keep watching for live changes
              // need to handle this separately because there is no {sync: true} event with HistoryStream
              // and we want to notify the client that sync has completed

              pull(
                rpc.createHistoryStream({
                  id: upto.id,
                  seq: last + 1,
                  live: true,
                  keys: false
                }),
                sbot.createWriteStream(function () {
                  // TODO: handle error
                })
              )
            })
          )
        }, function (err) {
          if (err) {
            sbot.emit('log:error', ['replication', rpc.id, 'error', err])
          }
        })
      )
    })

    return {
      changes: function () {
        return MutantToPull(syncStatus)
      },
      upto: upto
    }
  }
}
<commit_msg>whoops fix check<commit_after>'use strict'
var pull = require('pull-stream')
var para = require('pull-paramap')
var Notify = require('pull-notify')
var Cat = require('pull-cat')
var Debounce = require('observ-debounce')
var mdm = require('mdmanifest')
var apidoc = require('scuttlebot/lib/apidocs').replicate
var MutantToPull = require('./mutant-to-pull')
var {Struct, Dict} = require('mutant')

var Pushable = require('pull-pushable')

// compatibility function for old implementations of `latestSequence`
function toSeq (s) {
  return typeof s === 'number' ? s : s.sequence
}

module.exports = {
  name: 'replicate',
  version: '2.0.0',
  manifest: mdm.manifest(apidoc),
  init: function (sbot, config) {
    var debounce = Debounce(200)
    var listeners = {}
    var newPeer = Notify()

    // keep track of sync progress and provide to client

    var start = null
    var count = 0
    var rate = 0
    var toSend = {}
    var peerHas = {}
    var pendingPeer = {}

    window.pendingPeer = pendingPeer

    var syncStatus = Struct({
      type: 'global',
      incomplete: 0,
      pendingCount: 0,
      pendingPeers: Dict({}, {fixedIndexing: true}),
      feeds: null,
      rate: 0
    })

    window.syncStatus = syncStatus

    debounce(function () {
      var incomplete = 0
      var totalPending = 0
      var feeds = Object.keys(toSend).length
      var peers = {}

      Object.keys(pendingPeer).forEach(function (peerId) {
        if (pendingPeer[peerId]) {
          totalPending += 1

          if (Object.keys(toSend).some(function (feedId) {
            if (peerHas[peerId] && peerHas[peerId][feedId]) {
              return peerHas[peerId][feedId] > toSend[feedId]
            }
          })) {
            incomplete += 1
          }

          peers[peerId] = pendingPeer[peerId]
        }
      })

      syncStatus.set({
        incomplete: incomplete,
        feeds: syncStatus.loadedFriends ? feeds : null,
        pendingPeers: peers,
        pending: totalPending,
        rate: rate
      }, {merge: true})
    })

    pull(
      sbot.createLogStream({old: false, live: true, sync: false, keys: false}),
      pull.drain(function (e) {
        // track writes per second, mainly used for developing initial sync.
        if (!start) start = Date.now()
        var time = (Date.now() - start) / 1000
        if (time >= 1) {
          rate = count / time
          start = Date.now()
          count = 0
        }
        var pushable = listeners[e.author]

        if (pushable && pushable.sequence === e.sequence) {
          pushable.sequence++
          pushable.forEach(function (p) {
            p.push(e)
          })
        }
        count++
        addPeer({id: e.author, sequence: e.sequence})
      })
    )

    sbot.createHistoryStream.hook(function (fn, args) {
      var upto = args[0] || {}
      var seq = upto.sequence || upto.seq

      if (this._emit) this._emit('call:createHistoryStream', args[0])

      // if we are calling this locally, skip cleverness
      if (this === sbot) return fn.call(this, upto)

      // keep track of each requested value, per feed / per peer.
      peerHas[this.id] = peerHas[this.id] || {}
      peerHas[this.id][upto.id] = seq - 1

      debounce.set()

      // handle creating lots of history streams efficiently.
      // maybe this could be optimized in map-filter-reduce queries instead?
      if (toSend[upto.id] == null || (seq > toSend[upto.id])) {
        upto.old = false
        if (!upto.live) return pull.empty()
        var pushable = listeners[upto.id] = listeners[upto.id] || []
        var p = Pushable(function () {
          var i = pushable.indexOf(p)
          pushable.splice(i, 1)
        })
        pushable.push(p)
        pushable.sequence = upto.sequence
        return p
      }
      return fn.call(this, upto)
    })

    // collect the IDs of feeds we want to request
    var opts = config.replication || {}
    opts.hops = opts.hops || 3
    opts.dunbar = opts.dunbar || 150
    opts.live = true
    opts.meta = true

    function localPeers () {
      if (!sbot.gossip) return
      sbot.gossip.peers().forEach(function (e) {
        if (e.source === 'local' && toSend[e.key] != null) {
          sbot.latestSequence(e.key, function (err, seq) {
            addPeer({id: e.key, sequence: err ? 0 : toSeq(seq)})
          })
        }
      })
    }

    // also request local peers.
    if (sbot.gossip) {
      // if we have the gossip plugin active, then include new local peers
      // so that you can put a name to someone on your local network.
      var int = setInterval(localPeers, 1000)
      if (int.unref) int.unref()
      localPeers()
    }

    function loadedFriends () {
      syncStatus.loadedFriends = true
      debounce.set()
    }

    function addPeer (upto) {
      if (upto.sync) return loadedFriends()
      if (!upto.id) return console.log('invalid', upto)

      if (toSend[upto.id] == null) {
        toSend[upto.id] = Math.max(toSend[upto.id] || 0, upto.sequence || upto.seq || 0)
        newPeer({ id: upto.id, sequence: toSend[upto.id], type: 'new' })
        debounce.set()
      } else {
        toSend[upto.id] = Math.max(toSend[upto.id] || 0, upto.sequence || upto.seq || 0)
      }

      debounce.set()
    }

    // create read-streams for the desired feeds
    pull(
      sbot.friends.createFriendStream(opts),
      // filter out duplicates, and also keep track of what we expect to receive
      // lookup the latest sequence from each user
      para(function (data, cb) {
        if (data.sync) return cb(null, data)
        var id = data.id || data
        sbot.latestSequence(id, function (err, seq) {
          cb(null, {
            id: id, sequence: err ? 0 : toSeq(seq)
          })
        })
      }, 32),
      pull.drain(addPeer, loadedFriends)
    )

    function upto (opts) {
      opts = opts || {}
      var ary = Object.keys(toSend).map(function (k) {
        return { id: k, sequence: toSend[k] }
      })
      if (opts.live) {
        return Cat([pull.values(ary), pull.once({sync: true}), newPeer.listen()])
      }

      return pull.values(ary)
    }

    sbot.on('rpc:connect', function (rpc) {
      // this is the cli client, just ignore.
      if (rpc.id === sbot.id) return
      // check for local peers, or manual connections.
      localPeers()
      sbot.emit('replicate:start', rpc)
      rpc.on('closed', function () {
        sbot.emit('replicate:finish', toSend)
      })
      pull(
        upto({live: opts.live}),
        pull.drain(function (upto) {
          if (upto.sync) return
          var last = (upto.sequence || upto.seq || 0)
          pendingPeer[rpc.id] = (pendingPeer[rpc.id] || 0) + 1
          debounce.set()

          pull(
            rpc.createHistoryStream({
              id: upto.id,
              seq: last + 1,
              live: false,
              keys: false
            }),
            pull.through((msg) => {
              start = Math.max(start, msg.sequence)
            }),
            sbot.createWriteStream(function () {
              // TODO: do something with the error
              // this seems to be thrown fairly regularly whenever something weird happens to the stream

              pendingPeer[rpc.id] -= 1
              debounce.set()

              // all synched, now lets keep watching for live changes
              // need to handle this separately because there is no {sync: true} event with HistoryStream
              // and we want to notify the client that sync has completed

              pull(
                rpc.createHistoryStream({
                  id: upto.id,
                  seq: last + 1,
                  live: true,
                  keys: false
                }),
                sbot.createWriteStream(function () {
                  // TODO: handle error
                })
              )
            })
          )
        }, function (err) {
          if (err) {
            sbot.emit('log:error', ['replication', rpc.id, 'error', err])
          }
        })
      )
    })

    return {
      changes: function () {
        return MutantToPull(syncStatus)
      },
      upto: upto
    }
  }
}
<|endoftext|>