build.js 5.83 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;

        return {
            buildTime: buildTime,
            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(() => {
            return self._buildScript();
        }).then(() => {
            return self._ignoreFile();
        }).then(() => {
            self._zipBuild();
            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 buildPath() {
        return path.join(config.buildDir, this.project.name, this.buildTime, this.project.name);
    }

    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', this.rootPath)) {
            sh.mkdir('-p', this.rootPath);
        }
        this.logFile = path.join(this.rootPath, 'building.log');
        sh.touch(this.logFile);
    }

    _cloneCode(branch) {
        var self = this;
        this._state('cloning_code');
        let clone_script = `git clone --depth 1 -b ${branch} ${this.project.gitlab}`;
        this._log(`>>>>>>>>> ${clone_script} >>>>>>>>>>>`);

        return new Promise((reslove, reject) => {
            sh.cd(self.rootPath);
            let child = sh.exec(clone_script, {
                silent: self.silent,
                async: true
            });

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

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

            // child.stderr.on('data', (data) => {
            //     self._log(data);
            // });

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

    _buildScript() {
        var self = this;

        if (this.project.scripts && this.project.scripts.build) {
            this._log(`>>>>>>>>> ${this.project.scripts.build} >>>>>>>>>>>`);
            return new Promise((reslove, reject) => {
                self._state('script_building');
                sh.cd(self.buildPath);
                var child = sh.exec(self.project.scripts.build, {
                    silent: self.silent,
                    async: true
                });

                // child.stdout.on('data', (data) => {
                //     self._log(data);
                // });

                // child.stderr.on('data', (data) => {
                //     self._log(data);
                // });
                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(`build code fail`));
                    }
                });
            });
        } else {
            this._log(`>>>>>>>>> no build script >>>>>>>>>>>`);
            return Promise.resolve();
        }
    }

    _ignoreFile() {
        if (this.project.ignore) {
            let files = this.project.ignore.split(';');

            sh.cd(this.buildPath);
            
            files.forEach(f => {
                if (f) {
                    let fDir = path.join(this.buildPath, f);

                    if (fDir.indexOf(this.buildPath) === 0) {
                        sh.rm('-rf', fDir);
                    } else {
                        this.log(`Illegal ignore path: ${f}`);
                    }
                } 
            });
        }
    }

    _zipBuild() {
        this._state('gziping');
        let target = this.buildPath;
        let dist = path.join(this.rootPath, `${this.project.name}.tar.gz`);
        return Tar.gzip(target, dist);
    }

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

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

export default Build;