import * as webpack from "webpack"; import * as ora from "ora"; import * as chalk from "chalk"; import { genProdConfig, WebpackOptions, genDevConfig } from "./webpackConfig"; import startServer, { DevServerOption } from "./startServer"; import { readCustomEntry, getEntryByKey } from "./utils/entry"; import * as inquirer from "inquirer"; import * as _ from "lodash"; import { printError, printSuccess, isDirectory, printWarn, alertSuccess } from "./utils"; import * as path from "path"; import * as fse from "fs-extra"; import * as vfs from "vinyl-fs"; import Git from "./utils/Git"; export interface PublishOptions { /** * 线上静态文件的仓库(比如 ezstatic)路径 */ onlinePath: string; /** * uat 静态文件的仓库(比如 localezstatic)路径 */ uatPath: string; /** * uat 对应的多套环境的名称,对应多个静态目录,比如 dgamin.65emall.net,dgadmin2.65emall.net */ uatEnv: string[]; /** * uat 对应的多个 map 文件的名称,比如 aspirin 项目的 env1-map.js,env2-map.js */ uatMapName: string[]; /** * 保存上面多个 map 文件的目录名称 */ mapDir: string; } export interface EZPackOptions { /** 项目名称 */ name: string; /** * webpack 相关配置 */ webpack: WebpackOptions; /** * 开发服务器配置 */ devServer: DevServerOption; /** * 发布相关配置 */ publish: PublishOptions; /** * 禁用start/ build 模式下的entry选择,默认使用All entry */ disableEntrySelect?: boolean; } export function validateConfig(config: EZPackOptions) { return config; } async function selectEnv(envList: string[]): Promise { console.log(); envList.forEach((env, index) => { console.log(`${index + 1}: ${env}`); }); console.log(); let answers = await inquirer.prompt([ { type: "input", name: "num", message: "Please input the env number" } ]); if (!_.isFinite(+answers.num)) { printError("Invalid env num."); process.exit(1); } const env = envList[answers.num - 1]; if (env) { return env; } else { printError(`Env ${answers.num} not found.`); process.exit(1); return ""; } } export class EZPack { options: EZPackOptions; constructor(options: EZPackOptions) { this.options = options; } async start() { const { webpack: webpackConfig, name, devServer } = this.options; const config = genDevConfig(webpackConfig, name, devServer.port); startServer(config, devServer); } async build(analyse?: boolean) { const currentRepo = new Git(); const currentBranch = await currentRepo.getCurrentBranch(); const isOnline = currentBranch === "master"; const { webpack: webpackConfig, disableEntrySelect } = this.options; let { entry } = webpackConfig; const entryLen = Object.keys(entry).length; if (entryLen > 1) { entry = await (disableEntrySelect === true ? getEntryByKey(["all"], webpackConfig.entry, webpackConfig.rootPath) : readCustomEntry(webpackConfig.entry, webpackConfig.rootPath, true)); if (!entry) { printWarn("No entry found."); return; } } const config = genProdConfig( { ...webpackConfig, entry }, this.options.name, isOnline, analyse ); const spinner = ora("webpack building..."); spinner.start(); return new Promise((resolve, reject) => { webpack(config, (err, stats) => { spinner.stop(); if (err) { reject(err); } else if (stats.hasErrors()) { reject(stats.toJson().errors.join("\n")); } else { process.stdout.write( stats.toString({ colors: true, modules: false, children: false, chunks: false, chunkModules: false }) + "\n\n" ); console.log(chalk.cyan(" Build complete.\n")); // if (entryLen > 1) { // // only update auto-entry when having multiple entries // updateAutoEntry(); // } alertSuccess("Build successfully."); resolve(); } }); }); } async publish() { const { publish: publishOptions, webpack: webpackOptions, name: projectName } = this.options; const currentRepo = new Git(); const currentBranch = await currentRepo.getCurrentBranch(); const isOnline = currentBranch === "master"; let publishPath = ""; let mapPath = ""; let env = ""; if (isOnline) { const answers = await inquirer.prompt([ { type: "confirm", name: "isPublish", default: false, message: "Are you sure to publish online?" } ]); if (!answers.isPublish) { return; } env = "online"; const { mapDir, onlinePath } = publishOptions; publishPath = onlinePath; if (mapDir) { mapPath = path.join(publishPath, mapDir, "map.js"); } } else { const { uatPath, uatEnv, uatMapName, mapDir } = publishOptions; if (uatEnv && uatEnv.length > 0) { env = await selectEnv(uatEnv); publishPath = path.resolve(uatPath, env); } else { publishPath = uatPath; if (uatMapName) { env = await selectEnv(uatMapName); mapPath = path.join(uatPath, mapDir, env); } } } if (!await isDirectory(publishPath)) { printError(`publish fail! ${publishPath} not exists.`); return; } const publishRepo = new Git(publishPath); // make sure publishPath is a clean git repository try { const stats = await publishRepo.status(); if (stats.files.length !== 0) { printError(`${publishPath} is not clean, check it.`); return; } } catch (e) { printError(`${publishPath} is not a git repository.`); return; } publishRepo.pull("origin", "master"); try { await fse.emptyDir(webpackOptions.outputPath); await this.build(); } catch (e) { printError(e); return; } if (mapPath) { await fse.move(path.join(webpackOptions.outputPath, "map.js"), mapPath, { overwrite: true }); } vfs.src(path.join(webpackOptions.outputPath, "**/*")).pipe(vfs.dest(publishPath)); const answers = await inquirer.prompt([ { type: "confirm", name: "isUpload", default: false, message: "Auto publish:" } ]); if (answers.isUpload) { try { await publishRepo.add("./*"); await publishRepo.commit(`Auto publish ${projectName} ${currentBranch}.`); await publishRepo.pull("origin", "master", { "--rebase": "true" }); await publishRepo.push("master"); printSuccess(`Publish ${env} successfully.`); } catch (e) { printWarn("Build successfully, but auto publish fail."); return; } } } }