Home Manual Reference Source Test

src/Gulpfile.babel.js

import { readFileSync } from 'fs';
import { sep, join } from 'path';
import { src, dest, series, parallel } from 'gulp';
import sane from 'sane';
import { log, colors, File } from 'gulp-util';
import { obj as createStream } from 'through2';
import yargs from 'yargs';
import glob from 'glob';
import { create as createBSInstance } from 'browser-sync';
import NodeStream, { Node } from './lib/db/NodeStream';
import RuntimeConfig from './lib/RuntimeConfig';
import { TransformDirection } from './lib/transformers/Transformer';
import upload from './lib/db/upload';
import SyncStream from './lib/db/SyncStream';
import DBWatcher from './lib/db/Watcher';

// Get project config
import Project, { path as configPath } from './lib/config/ProjectConfig';

log('Using project configuration at', colors.magenta(configPath));

function applyTransforms(sourceStream, direction) {
  let stream = sourceStream;

  let transformers = Project.useTransformers;

  if (direction === TransformDirection.FromFilesystem) {
    transformers = transformers.reverse();
  }

  transformers.forEach(transformer => {
    stream = stream.pipe(transformer.transform(direction));
  });

  return stream;
}

/**
 * A gulp task that downloads all nodes from atvise-server.
 * @return {Stream}
 */
export function pull() {
  return applyTransforms(
    NodeStream.forNodes(Project.nodes)
      .pipe(RuntimeConfig.write()),
    TransformDirection.FromDB
  )
    .pipe(dest('src'));
}
pull.description = 'download all nodes from atvise-server';

function pushChanged(changedFile, root) {
  return applyTransforms(
    src(join(root, changedFile), { base: root })
      // Add file dependencies
      .pipe(createStream(function(file, enc, cb) {
        if (file.isDirectory()) { // No need to process directories
          cb();
        } else {
          const outPath = file.relative.match(new RegExp(`^[^\\.]+[^\\${sep}]+`))[0];

          if (file.relative === outPath) { // No directory mapping
            cb(null, file);
          } else {
            // Prevent duplicates
            if (!this._files) { this._files = {}; }

            if (this._files[file.relative]) {
              cb();
            } else {
              glob(`src/${outPath}/*.*`, (err, files) => {
                files.forEach(f => {
                  if (!this._files[f]) {
                    this._files[f] = true;
                    this.push(new File({
                      base: 'src',
                      path: f,
                      contents: readFileSync(f),
                    }));
                  }
                });

                cb();
              });
            }
          }
        }
      }))
      .pipe(RuntimeConfig.load()),
    TransformDirection.FromFilesystem
  )
    .pipe(upload(true));
}

let latestChange = 0;
let browserSync;

function watchForFileChanges() {
  sane('./src', {
    glob: '**/*.*',
    watchman: ['darwin'].indexOf(process.platform) >= 0,
  })
    .on('change', (file, root, stat) => {
      const mtime = Math.floor(stat.mtime / 1000) * 1000;

      if (mtime > latestChange) {
        log('File modified:', colors.cyan(file));
        latestChange = mtime;

        pushChanged(file, root)
          .pipe(createStream((f, enc, cb) => {
            latestChange = Math.max(latestChange, f.stat.mtime.getTime());

            cb();
          }, () => {
            if (browserSync) {
              browserSync.reload();
            }
          }));
      }
    })
    .on('ready', () => log(colors.green('Waiting for file changes...')));
  // TODO: Implement support for added, ... events
}

function watchForDBChanges() {
  (new DBWatcher(Project.nodesToWatch))
    .on('change', e => {
      const mtime = Math.floor(e.data.sourceTimestamp / 1000) * 1000;

      if (mtime > latestChange) {
        log('Node modified: ', colors.cyan(e.node.nodeId.toString()));

        latestChange = mtime;
        const stream = createStream();
        let uploadStream;

        applyTransforms(
          stream,
          // .pipe(RuntimeConfig.write()), // FIXME: Update RuntimeConfig
          TransformDirection.FromDB
        )
          .pipe(uploadStream = dest('src'));

        stream.push(new Node(e.node, e.data));
        stream.end();

        if (browserSync) {
          uploadStream.once('end', () => browserSync.reload());
        }
      }
    })
    .on('ready', () => log(colors.green('Watching for database changes...')));
}

/**
 * A gulp task that uploads all files to atvise-server.
 * @return {Stream}
 */
export function push() {
  const flags = yargs.argv;

  if (flags.watch) {
    return series(
      /*
      TODO: Uncomment once push performance allows an initial complete push
      function firstPush() {
        return pushChanged();
      },
      */
      watchForFileChanges
    )();
  }

  return applyTransforms(
    src('src/**/*.*')
      .pipe(RuntimeConfig.load()),
    TransformDirection.FromFilesystem
  )
    .pipe(upload());
}
push.description = 'upload all files to atvise-server';
push.flags = {
  '--watch': 'automatically upload changes',
};

/**
 * A gulp task that synchronizes files and atvise-server.
 * @return {Stream}
 */
export function sync() {
  const flags = yargs.argv;

  if (flags.watch) {
    return parallel(
      watchForFileChanges,
      watchForDBChanges
    )();
  }

  log(colors.yellow('The `sync` task has been deprecated starting with v0.4.0.'));
  log(colors.yellow.bold('Use `push --watch` instead.'));

  return SyncStream.from(
    src('src/**/*.*')
      .pipe(RuntimeConfig.load())
      .pipe(applyTransforms(TransformDirection.FromFilesystem))
  )
    .pull(stream => stream
      .pipe(applyTransforms(TransformDirection.FromDB))
      .pipe(dest('src'))
    )
    .push(stream => stream
      .pipe(upload())
    )
    .merged;
}
sync.description = '[DEPRECATED] synchronize files and atvise-server';
sync.flags = {
  '--watch': 'automatically up- and download changes',
};

function initBrowserSync(cb) {
  browserSync = createBSInstance();

  browserSync.init({
    proxy: `${Project.host}:${Project.port.http}`,
  }, cb);
}

export function watch() {
  return parallel(
    watchForFileChanges,
    watchForDBChanges,
    initBrowserSync
  )();
}

// Utility tasks
// TODO: Move to atvise-scm-cli

function logObject(obj, inset = '') {
  let r = '';

  if (obj) {
    if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
      // Just log the value of primitive types

      return colors.cyan(obj);
    } else if (obj instanceof Array) {
      // log all items of an Array

      for (let i = 0; i < obj.length; i++) {
        r += `\r\n${inset}- ${logObject(obj[i], `${inset}  `)}`;
      }
    } else if (
      obj.constructor.name !== 'Object' &&
      obj.constructor.prototype.toString !== Object.prototype.toString
    ) {
      // For Object subclasses that implement Object#toString call that method

      return colors.cyan(obj.toString());
    } else {
      // otherwise log all properties
      for (const prop in obj) {
        r += `\r\n${inset}${prop}: ${logObject(obj[prop], `${inset}  `)}`;
      }
    }
  }

  return r;
}

/**
 * A gulp task that prints the project configuration.
 * @param {Function(err: Error)} cb Called when task is complete, optionally with an error.
 */
function config(cb) {
  console.log(colors.bold('Project configuration for'), colors.bold.cyan(Project.name));
  console.log(colors.gray('Using project configuration at', configPath));

  const obj = {};
  ['host', 'port', 'useTransformers', 'nodes', 'ignoreNodes']
    .forEach(prop => (obj[prop] = Project[prop]));

  console.log(
    logObject(obj, '  ')
      .replace('\r\n', '') // Remove first newline
  );

  cb();
}
config.description = 'print the project configuration';

/**
 * The default gulp task. Currently does nothing unless a flag is passed.
 * @param cb
 */
export default function defaultTask(cb) {
  const flags = yargs.argv;

  if (flags.config) {
    config(cb);
  } else {
    cb();
  }
}

defaultTask.flags = {
  '--tasks': 'list available tasks',
  '--config': 'print project configuration',
};