build.js 6.92 KB
/**
 * 本地项目构建
 *  clone >> build >> zip
 */

'use strict';

import sh from 'shelljs';
import moment from 'moment';
import path from 'path';
import fsp from 'fs-promise';
import fs from 'fs';
import config from '../../config/config';
import Tar from './tar';
import ws from '../../lib/ws';
import {
    Building
} from '../models';

class Build {

    constructor(project) {
        this.project = project;
        this.state = 'waiting building';
        this.silent = true;
    }

    build(branch) {
        if (typeof config.buildDir === 'undefined') {
            throw new Error('please config the buildDir');
        }
        this.branch = branch;
        let buildTime = moment().format('YYYYMMDDHHmmss')
        this.buildTime = buildTime;
        this.version = '--';

        return {
            buildTime: buildTime,
            version: this.version,
            distFile: path.join(this.project.name, buildTime, `${this.project.name}.tar.gz`)
        };
    }

    run(bid) {
        this.bid = bid;
        let self = this;
        this._prebuild();
        this.startTime = (new Date()).getTime();
        return this._cloneCode(this.branch).then(() => {
            let pkg = JSON.parse(fs.readFileSync(path.join(self.codePath, 'package.json')));

            self.version = pkg.version;
            self.pkgName = pkg.name;

            return self._installdep();
        }).then(() => {
            return self._buildScript();
        }).then(() => {
            return self._cloneToDeploy();
        }).then(() => {
            self._state('success');
            let diff = (new Date()).getTime() - self.startTime;
            let costTime = moment.duration(diff, 'ms').humanize();
            self._log(`\n\n========== build success. cost: ${costTime} =======================`)
        }).catch((e) => {
            console.error(e);
            self._state('fail');
        });
    }

    get codePath() {
        return path.join(config.codeDir, this.project.name);
    }

    get buildPath() {
        return path.join(config.buildDir, this.project.name, this.buildTime, this.pkgName);
    }

    get rootPath() {
        return path.join(config.buildDir, this.project.name, this.buildTime);
    }

    /**
     * do some folder check
     */
    _prebuild() {
        if (!sh.test('-e', config.buildDir)) {
            sh.mkdir('-p', config.buildDir);
        }

        if (!sh.test('-e', config.codeDir)) {
            sh.mkdir('-p', config.codeDir);
        }

        if (!sh.test('-e', this.rootPath)) {
            sh.mkdir('-p', this.rootPath);
        }

        this.logFile = path.join(this.rootPath, 'building.log');
        sh.touch(this.logFile);
    }

    _cloneCode() {
        var self = this;
        var syncCodeScript;
        var update;


        if(sh.ls(config.codeDir).indexOf(this.project.name) > -1) {
            update = true;
            syncCodeScript = `git fetch && git checkout ${self.branch} && git reset --hard origin/${self.branch} && git pull origin ${self.branch}`;
        } else {
            syncCodeScript = `git clone ${this.project.gitlab}`;
        }

        this._log(`>>>>>>>>> ${syncCodeScript} >>>>>>>>>>>`);

        return new Promise((resolve, reject) => {
            this._state('sync code');

            if (update) {
                sh.cd(self.codePath);
            } else {
                sh.cd(config.codeDir);
            }

            // 回到上级目录更新

            let child = sh.exec(syncCodeScript, {
                // silent: self.silent,
                async: true
            });

            child.stdout.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));

            child.stderr.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));

            child.on('close', (code) => {
                if (code == 0) {
                    console.log('sync code success');
                    resolve();
                } else {
                    reject(new Error(`sync code fail`));
                }
            });
        });
    }

    _installdep() {
        var self = this;
        this._log('>>>>>>>> install dependencies >>>>>>>');
        return new Promise((resolve, reject) => {
            self._state('installing dependencies');
            sh.cd(self.codePath);


            var child = sh.exec('npm i --production=false', {
                silent: self.silent,
                async: true
            });

            child.stdout.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));
            child.stderr.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));

            child.on('close', (code) => {
                if (code == 0) {
                    console.log('install dependencies success');
                } else {
                    console.log('install dependencies with error,rebuild please');
                }
                resolve();
            });
        })
    }

    _buildScript() {
        var self = this;
        this._log(`>>>>>>>>> build static >>>>>>>>>>>`);
        return new Promise((reslove, reject) => {
            self._state('script building');
            sh.cd(self.codePath);

            var child = sh.exec('npm run build -s', {
                silent: self.silent,
                async: true
            });

            child.stdout.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));
            child.stderr.pipe(fs.createWriteStream(self.logFile, {
                flags: 'a'
            }));

            child.on('close', (code) => {
                if (code == 0) {
                    console.log('build success');
                    reslove();
                } else {
                    reject(new Error(`${self.project.name} build code fail`));
                }
            });
        });
    }

    _cloneToDeploy() {
        var self = this;
        this._log('>>>>>>>>> clone to deploy folder >>>>>>>>>>');
        return new Promise((resolve, reject) => {
            let projectRoot = `public/dist/${self.pkgName}/`;

            self._state('clone to deploy');

            // assets folder & version folder
            var child = sh.cp('-r', path.join(self.codePath, projectRoot), self.buildPath);

            if (child.code === 0) {
                console.log('cope to deploy success');
                resolve();
            } else {
                reject(new Error(`copy static fail`));
            }
        });
    }

    async _state(state) {
        ws.broadcast(`/building/${this.project._id}`, {
            bid: this.bid,
            state: state,
            version: this.version
        });
        this._log(`>>>>>>>>> ${state} >>>>>>>>>>>`);
        await Building.updateState(this.bid, state, this.version, this.pkgName);
    }

    _log(line) {
        // ws.broadcast(`/building/${this.project._id}/log`, line);
        fsp.appendFile(this.logFile, line + '\n');
    }
}

export default Build;