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',
};