#!/usr/bin/env node var program = require('commander'); var log = require('colors'); var fs = require("fs"); var path = require("path"); var gulp = require("gulp"); var del = require("del"); var gutil = require('gulp-util'); var gpl = require("gulp-plumber"); var notify = require("gulp-notify"); var grp = require("../lib/gulp-relative-path"); var gzip = require("gulp-zip"); var ignore = require("gulp-ignore"); var revEasy = require("gulp-rev-easy"); var hashCreator = require('../lib/gulp-native-hash'); var lbInclude = require("gulp-lb-include"); var htmlbeautify = require('gulp-html-beautify'); var htmlMin = require("gulp-html-minifier2"); var htmlReplace = require("gulp-html-replace"); var dateformat = require('dateformat'); var svn = require("svn-interface"); var xml = require('xml'); var _ = require("underscore"); program.version('0.0.1') .usage("加油宝H5本地化打包工具"); program.command('zip [projectName]') //.option('-f, --force', '强制打包') .option('-a, --all', '强制打全量包') .option('-d, --deleteFile', "打包完成后删除下载的文件") .option('-l, --local', "是否直接使用本地文件(如果有)") .option('-t, --test', "打测试环境的本地包") .option('-n, --number [value]', "指定打包的版本号") //.option('-p, --patch', '强制打增量包') .description("打包指定名称的应用,命令格式:native zip [projectName] -a\n 如果不指定-a,则程序自动判断打包类型,指定则打全量包") .action(executeZip); function executeZip(projectName, option) { var config = require(process.cwd() + "/configzip.js"); // 判断配置是否存在 if (config[projectName]) { new ZipProject({ config: config, projectName: projectName, local: option.local, deleteFile: option.deleteFile, all: option.all, test: option.test, version: option.number }); } else { console.log("查找不到您输入的项目名,请检查配置文件".yellow.bold); } } /** * 执行压缩命令 */ function ZipProject(option) { this.config = option.config; this.projectName = option.projectName; this.deleteFile = !!option.deleteFile; this.isAll = !!option.all; this.local = !!option.local; this.isTest = !!option.test; this.version = option.version; this.number = 0; this.init(); } ZipProject.prototype = { init: function () { this.module = this.config[this.projectName]; var dependency = this.module.dependencies; if (!dependency) { showLog("没有配置依赖文件,请检查配置", 'error'); return; } this.basePath = "./" + this.module.uid + "/"; this.analyseDependency(); }, analyseDependency: function () { var _this = this, dependency = _this.module.dependencies; if (_this.local) { var exists = fs.existsSync(path.resolve(process.cwd(), _this.basePath)); if (exists) { showLog("使用本地保存的文件", 'info'); _this.getFileComplete(); } else { showLog("本地查找不到您要打包的项目文件", 'error'); } } else { // 读取依赖文件配置信息 _.each(dependency.copyPath, function (value, key) { if (_.isArray(value)) { _.each(value, function (dir) { _this.number++; _this.getSvnFile(_this.module.svnPath + dir, dir, key); }) } else { _this.number++; _this.getSvnFile(_this.module.svnPath + value, value, key); } }) } }, /** * 读取svn文件 * @param filePath * @param dirName * @param type */ getSvnFile: function (filePath, dirName, type) { var _this = this; showLog("开始下载" + type + "文件", 'info'); svn.export(filePath + " " + _this.basePath + dirName, { username: _this.config.svnAccount.username, password: _this.config.svnAccount.password, force: true }, function (err, data) { if (!err) { _this.number--; showLog("下载" + type + "文件完成", 'warn'); _this.getFileComplete(); } else { showLog("读取svn文件出错:" + data, 'error'); } }); }, /** * 获取文件完成,分析依赖文件 * @param type */ getFileComplete: function () { var self = this; if (self.number <= 0) { this.includeHtml(); } }, /** * 执行ssi的include */ includeHtml: function () { var self = this, dependency = self.module.dependencies, htmlPath = []; // 执行include编译动作,拷贝至dist目录 if (_.isArray(dependency.files.html)) { _.each(dependency.copyPath.html, function (value, index) { htmlPath.push(path.join(self.basePath, value, dependency.files.html[index])); }); } else { htmlPath.push(path.join(self.basePath, dependency.copyPath.html, dependency.files.html)); } showLog("编译html include", 'info'); /** * 1、先执行ssi的include生成html文件 * 2、格式化html * 3、压缩html里的js * 4、替换seajs的路径 * 5、替换路径 * 6、放到目标目录下 * 7、触发结束回调 */ gulp.src(htmlPath) .pipe(gpl({errorHandler: notify.onError("Error: <%= error.message %>")})) .pipe(lbInclude({ root: this.basePath })) .pipe(htmlbeautify()) .pipe(htmlReplace({ 'seajs': self.module.seajsTpl + "\n\t"+ (self.isTest ? self.module.testEnv : self.module.idcEnv) })) .pipe(htmlMin({ collapseWhitespace: false, minifyJS: true, minifyCSS: true })) .pipe(grp({ root: self.basePath, matchPath: self.module.staticPath, relativePath: self.module.relativePath, absolutePath: self.module.absolutePath, pattern: [ { reg: /('|")(\/\/[^('|")]*)('|")\s*/g, transformMethod: function (a, b) { return a.replace(/(\/\/)/, self.module.fullLinkProtocol + ":$1"); } } ] })) //.pipe(revEasy({ // //})) .pipe(gulp.dest(function (file) { return path.dirname(file.path); })) .on("end", function () { self.analyseChanged(); }) }, /** * 生成所有文件的hash值,保存成map文件,并找出变更的文件列表 */ analyseChanged: function () { var self = this; // 读取所有文件的hash值,和map文件进行比较 var hc = new hashCreator({ key: self.module.uid, root: self.basePath, output: self.config.mapFile || 'zip-manifest.json', onEnd: function (changed, allFiles, number) { // 强制打全量包,其他条件不做判断 if (self.isAll) { showLog("生成map文件成功,开始打全量包>>>>>>>>>>>>>>>>>>", 'info'); self.startZip(allFiles, number); } else { if (changed.length) { // 有变更的文件,并且只是部分变更,则打分支包,否则就打全量包 if (changed.length != number) { self.packagePatch = true; } showLog("生成map文件成功,开始打包>>>>>>>>>>>>>>>>>>", 'info'); self.startZip(changed, number); } else { showLog("该产品没有更新文件,停止打包>>>>>>>>>>>>>>>", 'info'); } } } }); gulp.src(self.getFilePath(true)).pipe(hc); }, /** * 生成zip文件 * @param files 本次扫描出来的变更的文件列表 **/ startZip: function (files) { var self = this, fileName = self.module.uid + ".zip", fileList = []; if (self.packagePatch) { fileName = self.module.uid + "_patch.zip"; } self.zipFileName = fileName; _.each(files, function (value) { fileList.push(value.path); }); self.fileList = fileList; showLog("*************打包文件如下:**************\n", 'info'); showLog("\n > " + fileList.join("\n > "), "warn"); gulp.src([this.basePath + "**/*"]) .pipe(ignore.include(fileList)) .pipe(gzip(fileName)) .pipe(gulp.dest('dist/'+self.module.uid)) .on("end", function () { showLog("生成打包文件成功" + fileName, 'info'); self.deleteFiles().updateVersion().generateChangeLog(); }); }, /** * 获取要被扫描的所有的文件路径 * @param withTopPath * @returns {Array} */ getFilePath: function (withTopPath) { var filePath = [], self = this, dependency = self.module.dependencies; _.each(dependency.files, function (value, key) { if (_.isArray(value)) { _.each(value, function (globPath, index) { filePath.push(path.join(withTopPath ? self.basePath : "./", dependency.copyPath[key][index], globPath)); }) } else { filePath.push(path.join(withTopPath ? self.basePath : "./", dependency.copyPath[key], value)); } }); return filePath; }, deleteFiles: function () { if (this.deleteFile) { showLog("\n>>>>>>>>>>>>>>>删除文件目录>>>>>>>>>>>>>>>>>>", 'info'); del(this.basePath).then(function () { showLog("删除下载的打包文件目录成功"); }); } return this; }, updateVersion: function () { var self = this, version = self.version; // 未指定版本号 if(!self.version) { version = dateformat(new Date(), (self.module.versionFormat || "yyyymmddHHMM")) } self.version = version; fs.stat("./dist/" + self.zipFileName, function (err, state) { var configXml = [{ package: [ {uid: self.module.uid}, {name: self.module.name}, {version: version}, {description: self.module.name}, {login: self.module.login}, {zip: self.packagePatch ? "" : ((self.isTest ? self.config.testPath : self.config.idcPath) + self.zipFileName)}, {patch: !self.packagePatch ? "" : ((self.isTest ? self.config.testPath : self.config.idcPath) + self.zipFileName)}, {entry: self.module.entry} ] }]; var xmlData = xml(configXml, { indent: " ", declaration: {encoding: 'UTF-8' }}); var xmlPath = "./dist/"+self.module.uid+"/"+self.module.uid+".xml"; fs.writeFile(xmlPath, xmlData, "utf8", function () { showLog("写配置文件成功"+xmlPath); }) }) return this; }, generateChangeLog: function () { var self = this; fs.exists(path.join(process.cwd(), "/changelog.log"), function (exist) { var logContent = ''; if(exist) { fs.readFile(path.join(process.cwd(), "/changelog.log"), "utf8", function (err, data) { if(err) { showLog("读取日志文件失败,本次打包日志丢失", "error"); } else { logContent = data; writeLog(logContent); } }); } else { writeLog(logContent); } }); function writeLog(content) { var logArr = [], date = dateformat(new Date(), "yyyy-mm-dd HH:MM"); logArr.push("["+date + "] 打包"+self.projectName+"产品"); logArr.push("\t打包文件列表") logArr.push("\t\t> "+self.fileList.join("\n\t\t> ")); logArr.push("\t打包文件名:"+self.zipFileName); logArr.push("\t打包版本号:"+ self.version); logArr.push("\t配置文件名:"+ self.module.uid+".xml") logArr.push("\t文件路径:"+ "dist/"+self.module.uid+"/"+self.zipFileName); logArr.push("----------------------------------------------------------------------------------\n"); fs.writeFile(path.join(process.cwd(), "/changelog.log"), content + logArr.join("\n"), "utf8", function () { showLog("写日志文件成功"); }); } } } /** * 显示log日志 * @param msg * @param type */ function showLog(msg, type) { switch (type) { case "warn": gutil.log(msg.magenta.bold); break; case "info": gutil.log(msg.green.bold); break; case "error": gutil.log(msg.yellow.bold); break; default: gutil.log(msg.green.bold); break; } } program.parse(process.argv);