利用工具,解放微信小程序繁琐的代码上传流程

想一想,输入一个命令就可以直接上传小程序的代码,还能做一些前置操作,让整个上传流程自动化,岂不是很爽!

需求背景

原生的小体型的微信小程序项目通常不需要思考这方面的解决方案,因为将代码上传到微信服务器不需要太多的前置操作。

如果是大体型的,或是使用第三方框架开发的微信小程序项目,在上传前就需要一定的前置操作。

如切换分支,切换环境,编译代码,维护日志等。

场景也不仅限一个微信小程序项目,一套代码是有肯能发布多个微信小程序项目的,切换微信开发者工具去一个个上传代码效率非常低下。

同时人工的做这些操作是有可能出现问题的,如依赖的子项目忘记更新就上传了代码。

这些需要人工成本的固定步骤,希望有一套工具可以解放劳动力,同时可以避免因为人工失误带来的问题。

方案

微信开发者工具提供了命令行工具来做基础操作。

可以通过 cli upload 来上传代码。

cd 命令行工具所在目录

./cli upload --project 项目地址 -v 项目版本 -d '版本描述'

基于这个命令行工具,向上扩展内容即可。

需求

希望通过简单的命令就可以达到上传的目的。

假设工具名是 mpup

mpup --config=配置文件

分析配置文件

按照最基础版本来分析

首先是必要的配置项:

  • 项目路径 也就是要上传的代码
  • 小程序开发者工具提供的命令行工具路径 用于定位工具,上传代码
  • 版本号 每次上传代码时候的标志,小程序官方要求的必填项

其他配置项:

根据需求,代码上传前要前置的做一些事情,这些事情其实都是一些命令。

如切换分支,就是执行 git 命令,编译代码,可能会使用 babel 相关工具。

所以需要一个命令集合。

另外应该在各个阶段暴露一些钩子,提供中断操作等。

  • 命令集合 上传前要执行的所有命令
  • 各个阶段的钩子 提供一些额外能力,如记录日志,中断上传

所以一个配置文件大概会是这样的:

module.exports = {
  // 项目路径
  projectPath: '',

  // 小程序工具路径
  mpToolPath: '',

  // 代码版本
  ver: '',

  // 版本描述,这个配置是传递给命令行工具的,微信上传代码前填写的表单中的一项,但不是必填项
  desc: '',

  // 命令集合
  commends: [
    {
      // 要执行的命令
      cmd: '',
      // 执行命令前做的事
      async before() {},
      // 执行命令后做的事
      async after() {},
      // 执行命令失败的时候做的事
      async error() {},
    },
  ],

  // 所有钩子执行前
  async beforeExecAllCommends() {},

  // 所有钩子执行后
  async afterExecAllCommends() {},

  // 上传前
  async beforeUpload() {},

  // 上传后
  async afterUpload() {},
};

实现

获取配置文件

配置文件是通过命令后传入的,所以使用 yargs 来处理命令后传入的参数。

const yargs = require('yargs');

const argv = yargs
  .usage('mpup [options]')
  .option('config', {
    describe: '上传配置',
    type: 'string',
  })
  .help('help')
  .argv;

使用 mpup --config=./mpup.config.js ,就可以从 argv 里取出 config 参数。

获取到参数后就是取到配置文件。

const path = require('path');
const { config } = argv;

const configPath = path.resolve(process.cwd(), config);

const mpupConfig = require(configPath);

通过 process.cwd 获取到执行命令的路径,然后和 config 参数一起处理,得到配置文件路径,再 require 到就得到了配置。

中断方法

只要退出了当前进程,就是中断了上传,所以中断方法很简单。

const abort = () => {
  process.exit();
};

执行所有前置命令

执行命令需要用到 child_process 模块的 exec 或者 execSync 方法,这两个方法作用是相同的,用于在某个工作目录下执行某个命令。

这里使用 exec 并简单封装一下。

const { exec } = require('child_process');

const execCmd = (cmd, path) => {
  return new Promise((resolve, reject) => {
    try {
      exec(cmd, { cwd: path }, (err, stdout, stderr) => {
        if (stdout) {
          resolve(stdout);
          return;
        }
        reject(err || stderr);
      });
    } catch (e) {
      reject(e);
    }
  });
};

随后就是从 mpupConfig 中获取到所有命令,并执行。

const {
  commends,
  beforeExecAllCommends,
  afterExecAllCommends,
} = mpupConfig;

// 开始执行命令前的钩子
await beforeExecAllCommends({ abort });

for (let i = 0, len = commends.length; i < len; i++) {
  const commend = commends[i];
  const {
    cmd,
    before = () => {},
    after = () => {},
    error = () => {},
  } = commend;

  await before({ abort, commend });

  let stdout = '';
  try {
    stdout = await execCmd(cmd);
  } catch (e) {
    await error({
      e,
      abort, // 中断函数
    })
  }

  await after({ abort, commend, stdout });
}

// 结束所有命令后的钩子
await afterExecAllCommends({ abort });

上传代码

这一步就是调用微信团队提供的工具了。

// 判断平台,因为在windows和mac下执行的命令不同
const isMacOS = () => {
  return !(/^win/.test(process.platform));
};

// 获取到 项目路径 、 工具路径 、 版本 、 版本描述
const {
  projectPath,
  mpToolPath,
  desc,
  ver,
  beforeUpload,
  afterUpload,
} = mpupConfig;

// 根据环境和配置拼出命令
const uploadCmd = `${isMacOS() ? './' : ''}cli${isMacOS() ? '' : '.bat'} upload --project=${projectPath} --version=${ver} --desc=${desc}`;

// 上传前的钩子
await beforeUpload({ abort });

// 到命令行工具的地址执行上传命令
const stdout = await execCmd(uploadCmd, mpToolPath);

// 上传后的钩子
await afterUpload({ abort, stdout });

创建软连接

因为最终是要作为命令后工具使用,所以需要创建一个软连接。

package.json 中加入 bin 属性。

{
  "bin": {
    "mpup-test": "./mpup.js"
  }
}

上述代码都放在 ./mpup.js 中,为了防止本地已经安装好的 mpup 冲突,这里将名字作为了 mpup-test

随后在 package.json 所在的目录执行 npm link,就会建立软连接,碰到权限问题记得加 sudo 前缀。

mpup.js 文件第一行需要加入 #! /usr/bin/env node,表示使用 node 来执行文件。

成果

先汇总一份代码:

#! /usr/bin/env node
const { exec } = require('child_process');
const yargs = require('yargs');
const path = require('path');

// 用于判断平台,因为在windows和mac下执行的命令不同
const isMacOS = () => {
  return !(/^win/.test(process.platform));
};

const execCmd = (cmd, path) => {
  return new Promise((resolve, reject) => {
    try {
      exec(cmd, { cwd: path }, (err, stdout, stderr) => {
        if (stdout) {
          resolve(stdout);
          return;
        }
        reject(err || stderr);
      });
    } catch (e) {
      reject(e);
    }
  });
};

const abort = () => {
  process.exit();
};

const argv = yargs
  .usage('mpup [options]')
  .option('config', {
    describe: '上传配置',
    type: 'string',
  })
  .help('help')
  .argv;

(async () => {

  const { config } = argv;

  const configPath = path.resolve(process.cwd(), config);

  const mpupConfig = require(configPath);

  const {
    commends,
    beforeExecAllCommends,
    afterExecAllCommends,
  } = mpupConfig;
  
  // 开始执行命令前的钩子
  await beforeExecAllCommends({ abort });

  for (let i = 0, len = commends.length; i < len; i++) {
    const commend = commends[i];
    const {
      cmd,
      before = () => {},
      after = () => {},
      error = () => {},
    } = commend;

    await before({ abort, commend });

    let stdout = '';
    try {
      stdout = await execCmd(cmd);
    } catch (e) {
      await error({
        e,
        abort, // 中断函数
      })
    }

    await after({ abort, commend, stdout });
  }

  // 结束所有命令后的钩子
  await afterExecAllCommends({ abort });

  // 获取到 项目路径 、 工具路径 、 版本 、 版本描述
  const {
    projectPath,
    mpToolPath,
    desc,
    ver,
    beforeUpload,
    afterUpload,
  } = mpupConfig;

  // 根据环境和配置拼出命令
  const uploadCmd = `${isMacOS() ? './' : ''}cli${isMacOS() ? '' : '.bat'} upload --project=${projectPath} --version=${ver} --desc=${desc}`;

  // 上传前的钩子
  await beforeUpload({ abort });

  // 到命令行工具的地址执行上传命令
  const uploadStdout = await execCmd(uploadCmd, mpToolPath);

  // 上传后的钩子
  await afterUpload({ abort, stdout: uploadStdout });
})();

现在可以在工程目录下先安装一下依赖 yargs

npm i yargs -S

随后提供一份配置文件。

// mpup.config.js
module.exports = {
  // 项目路径
  // 记得配置这个哦 不然会失败的
  projectPath: '',

  // 小程序工具路径
  mpToolPath: '/Applications/wechatwebdevtools.app/Contents/MacOS',

  // 代码版本
  ver: '1.3.1',

  // 版本描述,这个配置是传递给命令行工具的,微信上传代码前填写的表单中的一项,但不是必填项
  desc: 'beta版本',

  // 命令集合
  commends: [
    {
      // 要执行的命令
      cmd: 'ls -a',
      // 执行命令前做的事
      async before() {
        console.log('before ls -a');
      },
      // 执行命令后做的事
      async after({ stdout }) {
        console.log('after ls -a');
        console.log(stdout);
      },
    },
  ],

  // 所有钩子执行前
  async beforeExecAllCommends() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log('开始前先停两秒!');
        resolve();
      }, 2000);
    });
  },

  // 所有钩子执行后
  async afterExecAllCommends() {
    console.log('执行完所有钩子啦');
  },

  // 上传前
  async beforeUpload() {
    console.log('开始上传');
  },

  // 上传后
  async afterUpload({ stdout }) {
    console.log(stdout);
    console.log('结束!');
  },
};

配置文件名为 mpup.config.js

接下来就是运行了。

执行

执行前有一些前置工作:

  1. 打开小程序的服务端口,(小程序开发者工具 => 设置 => 安全设置 => 开启服务端口)
  2. 使用较新稳定版的开发者工具,测试中发现老的稳定版本、 RC 版本和 Nightly Build 版本的命令行工具可能无法运行
  3. 登入开发者工具,且登入的账户拥有项目的上传代码权限

执行一遍 🚀!

mpup-test --config=./mpup.config.js

上面这份配置的执行结果是这样的:

扩展

到目前为止实现的都是基础功能,代码也仅仅是为了可以让流程跑通而写的,有许多可以改进的地方。

这也是整个工具最核心的部分。

目前代码已经开源,项目名也是 mpup,还在持续维护迭代中,因为测试场景不足,可能会有一些bug。

项目:https://github.com/hiNISAL/mpup

其配套的服务端工具也在开发中,因为本地上传代码有局限性,所以理想的情况应该是服务端上传。

谢谢各位观众老爷。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354