Gottwik/Enduro

View on GitHub
libs/juicebox/juicebox.js

Summary

Maintainability
B
6 hrs
Test Coverage
// * ———————————————————————————————————————————————————————— * //
// *     juicebox
// *    deals with lack of persistent storage plus adds backup and versioning
// * ———————————————————————————————————————————————————————— * //
const juicebox = function () {}

// * vendor dependencies
const Promise = require('bluebird')
const tar = require('tar')
const path = require('path')
const fs = Promise.promisifyAll(require('fs-extra'))
const rimraf = Promise.promisify(require('rimraf'))

// * enduro dependencies
const logger = require(enduro.enduro_path + '/libs/logger')
const remote_handler = require(enduro.enduro_path + '/libs/remote_tools/remote_handler')
const juice_helpers = require(enduro.enduro_path + '/libs/juicebox/juice_helpers')
const flat_helpers = require(enduro.enduro_path + '/libs/flat_db/flat_helpers')
const log_clusters = require(enduro.enduro_path + '/libs/log_clusters/log_clusters')

const EXTENSION = '.tar.gz'

// packs up the juicebox together with new juice.json
juicebox.prototype.pack = function (user) {
    const self = this

    return self.pull()
        .then(() => {
            return self.force_pack(user)
        })
}

// * ———————————————————————————————————————————————————————— * //
// *     pull
// *
// *     gets the most recent file from the juicebar
// *    @param {bool} force - overwrites newer files on local
// *    @return {promise} - no data
// * ———————————————————————————————————————————————————————— * //
juicebox.prototype.pull = function (force) {
    const self = this

    // if juicebox is not enabled or disabled by flags
    if (!enduro.config.juicebox_enabled) {
        return Promise.resolve()
    }

    logger.init('Juice pull')

    let pull_juice

    if (enduro.flags.force || force) {
        return get_latest_juice()
            .then((juice) => {
                pull_juice = juice
                return get_juicebox_by_name(juice.latest.hash + EXTENSION)
            }, err)
            .then((latest_juicebox) => {
                return spill_the_juice(latest_juicebox)
            }, () => {
                // latest juicebox does not exist
                return self.force_pack('enduro.js')
            })
            .then(() => {
                logger.end()
                return Promise.resolve(pull_juice.latest.hash)
            })
    } else {
        return get_latest_juice()
            .then((juice) => {
                pull_juice = juice
                return get_juicebox_by_name(juice.latest.hash + EXTENSION)
            }, err)

            .then((latest_juicebox) => {
                return spill_the_juice(latest_juicebox, path.join(enduro.project_path, 'juicebox', 'staging', pull_juice.latest.hash))
            }, () => {
                // latest juicebox does not exist
                // we will spill the just created juicebox instead so we have the first
                return self.force_pack('enduro.js')
                    .then(() => {
                        return spill_the_juice(pull_juice.latest.hash + EXTENSION, path.join(enduro.project_path, 'juicebox', 'staging', pull_juice.latest.hash))
                    })
                    .then(() => {
                        throw new Error('abort promise chain')
                    })
            })

            .then(() => {
                return juice_helpers.spill_newer(path.join('juicebox', 'staging', pull_juice.latest.hash))
            }, () => {})

            .then(() => {
                logger.end()
                return Promise.resolve(pull_juice.latest.hash)
            })
    }
}

// packs up the juicebox together with new juice.json
juicebox.prototype.force_pack = function (user) {
    return new Promise(function (resolve, reject) {

        // sets user to developer if juicing is caused by console
        user = user || 'developer'

        // skip juicing if juicing is not enabled or disabled by flags
        if (!enduro.config.juicebox_enabled || enduro.flags.nojuice) {
            return resolve()
        }

        get_latest_juice()
            .then((juice) => {
                juice.history = juice.history || []

                if (juice.latest) {
                    juice.history.push(juice.latest)
                }

                juice.latest = {
                    hash: get_juicebox_hash_by_timestamp(Math.floor(Date.now() / 1000)),
                    timestamp: Math.floor(Date.now() / 1000),
                    user: user,
                }

                write_juicebox(juice.latest.hash + EXTENSION)
                    .then(() => {
                        return write_juicefile(juice)
                    })
                    .then(() => {
                        return remote_handler.upload_to_filesystem_by_filepath('juicebox/juice.json', path.join(enduro.project_path, 'juicebox', 'juice.json'))
                    })
                    .then(() => {
                        return remote_handler.upload_to_filesystem_by_filepath('juicebox/' + juice.latest.hash + EXTENSION, path.join(enduro.project_path, 'juicebox', juice.latest.hash + EXTENSION))
                    })
                    .then(() => {
                        logger.init('Juice pack')
                        logger.log('packed successfully')
                        logger.end()
                        resolve()
                    })
            })
    })
}

// * ———————————————————————————————————————————————————————— * //
// *     diff current to latest juicebox
// *     will compare current cms folder and latest staged juicebox diff
// * ———————————————————————————————————————————————————————— * //
juicebox.prototype.diff_current_to_latest_juicebox = function () {
    const self = this

    return get_latest_local_juice()
        .then((latest_local_juicebox_hash) => {
            return juice_helpers.get_diff_folder_with_cms(path.join('juicebox', 'staging', latest_local_juicebox_hash, 'cms'))
        })
}

juicebox.prototype.diff = function (version_hash, file) {

    // will store the specified juicebox hash
    let juicebox_hash_to_diff

    return get_latest_juice()
        .then((juice) => {
            // if user provided specified version
            if (version_hash) {
                juicebox_hash_to_diff = get_juicebox_hash_by_timestamp(version_hash)
            } else {
                juicebox_hash_to_diff = juice.latest.hash
            }

            return get_juicebox_by_name(juicebox_hash_to_diff + EXTENSION)
        })
        .then((specified_juicebox) => {
            return spill_the_juice(specified_juicebox, path.join('juicebox', 'staging', juicebox_hash_to_diff))
        }, (e) => {
            logger.err(e)
        })
        .then(() => {
            if (file) {
                return juice_helpers.diff_file_with_cms(juicebox_hash_to_diff, file)
            } else {
                return juice_helpers.get_diff_folder_with_cms(path.join('juicebox', 'staging', juicebox_hash_to_diff, 'cms'))
            }
        })
}

juicebox.prototype.log = function (nojuice) {
    return get_latest_juice()
        .then((juice) => {
            juice_helpers.nice_log(juice)
        })
}

juicebox.prototype.is_juicebox_enabled = function () {
    const juicefile_path = path.join(enduro.project_path, 'juicebox', 'juice.json')
    return !flat_helpers.file_exists_sync(juicefile_path)
}

function write_juicebox (juicebox_name) {
    return tar.create({
        gzip: true,
        file: path.join(enduro.project_path, 'juicebox', juicebox_name),
        cwd: enduro.project_path
    },
    [ 'cms' ]
    )
}

function write_juicefile (juice) {
    return new Promise(function (resolve, reject) {
        const destination_juicefile_path = path.join(enduro.project_path, 'juicebox', 'juice.json')
        flat_helpers.ensure_directory_existence(destination_juicefile_path)
            .then(() => {
                fs.writeFile(destination_juicefile_path, JSON.stringify(juice), function (err) {
                    if (err) { reject(err) }
                    resolve(juice)
                })
            })
    })
}

function read_juicefile () {
    const local_juicefile_path = path.join(enduro.project_path, 'juicebox', 'juice.json')

    return fs.readJson(local_juicefile_path)
}

function get_latest_juice () {
    return remote_handler.request_file(remote_handler.get_remote_url('juicebox/juice.json', true))
        .catch(() => {
            throw new Error('latest juice does not exist')
        })
        .spread((body, response) => {

            if (body.indexOf('<?xml') + 1 && body.indexOf('<Error>') + 1) {

                // juicefile doesn't exist yet - let's create a new juicefile
                if (body.indexOf('AccessDenied') + 1) {
                    log_clusters.log('bucket_access_denied')
                // bucket was not created
                } else if (body.indexOf('NoSuchBucket') + 1) {
                    log_clusters.log('nonexistent_bucket')
                } else {
                    logger.raw_err(body)
                }
                process.exit()
            }

            if (response.statusCode != 200) { reject('couldnt read juice file') }

            // check if we got xml or json - xml means there is something wrong
            const juicefile_in_json = JSON.parse(body)

            return write_juicefile(juicefile_in_json)
        })
        .catch(() => {
            return write_juicefile(get_new_juicefile())
        })
}

// gets latest juice from the local juice.json
function get_latest_local_juice () {
    return read_juicefile()
        .then((latest_juicebox_data) => {
            return latest_juicebox_data.latest.hash
        })
}

function get_juicebox_hash_by_timestamp (timestamp) {
    return enduro.config.project_name + '_' + timestamp
}

function get_juicebox_by_name (juicebox_name) {
    const source_path = remote_handler.get_remote_url('juicebox/' + juicebox_name, true)
    const destination_path = path.join(enduro.project_path, 'juicebox', juicebox_name)

    if (source_path == destination_path) {
        return new Promise.resolve(juicebox_name)
    }

    return new Promise(function (resolve, reject) {
        const juicebox_read_stream = remote_handler.request_stream(source_path)

        juicebox_read_stream
            .on('error', () => {
                return reject()
            })

        juicebox_read_stream.pipe(fs.createWriteStream(destination_path)
            .on('close', function () {
                resolve(juicebox_name)
            })
        )
    })
}

function spill_the_juice (juicebox_name, destination) {
    // default destination is the project's root (juicebox has cms folder)
    destination = destination || path.join(enduro.project_path)

    // yea, we need the juicebox name
    if (!juicebox_name) {
        return Promise.resolve()
    }

    const tarball_location = path.join(enduro.project_path, 'juicebox', juicebox_name)

    // delete the folder if it exists
    return rimraf(path.join(destination, 'cms'))
        .then(() => {
            return flat_helpers.file_exists(tarball_location)
        })
        .then(() => {
            return flat_helpers.ensure_directory_existence(path.join(destination, 'fake.txt'))
        })
        .then(() => {
            return tar.extract({
                file: tarball_location,
                cwd: destination,
            })
        })
}

// provides default context of a fresh juicefile
function get_new_juicefile () {

    const timestamp = Math.floor(Date.now() / 1000)

    return {
        history: [],
        latest: {
            hash: get_juicebox_hash_by_timestamp(timestamp),
            timestamp: Math.floor(Date.now() / 1000),
            user: 'first_juicebox',
        }
    }
}

// handles errors
function err (err) {
    throw new Error('abort promise chain', err)
}

module.exports = new juicebox()