小程序代码命令行自动上传

因为开发环境上线比较多,顾虑手动修改代码过程中错误修改,导致上线后使用错误的环境,因此使用此方式

运行命令

编译文件目录 build/build.js

设定编辑器所在目录、运行环境

    "build": "node build/build.js"

设定编辑器所在目录、运行环境,小程序代码上传

    "build:upload": "node build/build.js upload"

设定编辑器所在目录、运行环境,小程序代码上传,版本号写入

    "build:upload:version": "node build/build.js upload version"

这里也可以仅仅有第一条命令,就需要一段可以选择编译功能的代码,推荐使用 inquirer

代码分解

文件说明

包含两个文件,build.js buildConfig.js。
build.js:编译主文件
buildConfig.js:编译配置文件,包含开发者工具安装目录和当前环境两个变量。因为在小程序内部没有找到读取json文件的方法,所以配置文件使用.js文件

获取开发者工具目录

因为每个人的安装目录都是不一样的,默认的安装目录为空。第一次运行需要指定目录,并把指定的目录写入 buildConfig.js 文件,假如目录不正确就需要手动删掉,重新运行命令;配置成功后再次进入就不需要再次指定。

buildConfig.js 文件默认是不存在的,需要 try catch 包裹起来,在执行 writeFileSync 的时候没有文件会自动创建文件

async function initBuildConfig () {
  try{
    const _buildConfig = require('./buildConfig.js') || {}
    buildConfig.toolsPath = _buildConfig.toolsPath || ''
    buildConfig.env = _buildConfig.env || ''
  } catch (e) {
  }
}

function getToolsPath () {
  return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });

    rl.question('当前未设置开发者工具安装路径,请设置:\n\t -> ', (path) => {

      rl.close();

      fs.writeFileSync(
        `${__dirname}/buildConfig.js`, 
        `module.exports = ${JSON.stringify(Object.assign(buildConfig, {toolsPath: path}), false, 2)}`,
      )

      console.log('\n')

      resolve(path)
    });
  })
}

选择编译环境

使用 inquirer完成环境的选择,并写入 buildConfig.js 文件,下次进入会默认指定上一次选择的环境

当其他文件需要使用环境变量是只需要引用buildConfig.js,并使用env变量即可

async function selectEnv() {
  const{env} = await inquirer.prompt({
    type: 'list',
    name: 'env',
    message: '当前编译环境:\n\t ->',
    default: buildConfig.env,
    choices: ['local', 'qa', 'test', 'v4', 'v8'],
  })

  fs.writeFileSync(
    `${__dirname}/buildConfig.js`, 
    `module.exports = ${JSON.stringify(Object.assign(buildConfig, {env}), false, 2)}`,
  )

  return env
}

小程序代码上传

使用命令模式的初衷是为了不打开微信开发者工具,事实证明我想的太理想了,还是需要打开的。

执行命令的返回值 stdout 在执行成功的时候才有值,stderr无论成功与否都有值,所以这里使用 stdout 判断命令执行失败。

function commitCode (env) {
  return new Promise((resolve, reject) => {
    const version = packageConfig.version
    const projectPath = path.resolve(__dirname, '../')
    console.log("\n项目路径:\n\t ->", projectPath, '\n')
    
    const toolsPath = buildConfig.toolsPath

    const command = `cli ${[
      'upload',
      '--project',
      projectPath,
      '-v',
      version,
      '-d',
      `${env}-${version}`
    ].join(' ')}`

    console.log("小程序上传代码命令:\n\t ->", command, '\n')

    exec(command, {cwd: toolsPath}, (error, stdout, stderr) => {
      if (error) {
        console.error(`执行的错误: ${error}`)

        reject(error)
        return;
      }

      if (!stdout) {
        console.error(`执行的错误: ${stderr}`)

        reject(stderr)
        return;
      }

      console.log(`stdout: ${stdout}`);

      resolve()
    })
  })

}

获取上传版本号

若果没有版本号则指定默认版本号1.0.0

有版本号则根据 版本升级、特性更新、修订补丁 的选择修改对应位置的数字,这里没有对版本号做过多的校验,以为错误的版本号是会报错的,没报错就是系统允许的。

async function getVersion () {
  if (!packageConfig.version) {
    const version = '1.0.0'

    console.log("\n当前无版本号,使用默认版本号:\n\t ->", version)

    packageConfig.version = version

    return version
  }

  console.log("\n当前版本号:\n\t ->", packageConfig.version, '\n')

  const versionTypeOpts = ['版本升级', '特性更新', '修订补丁']

  const {versionType} = await inquirer.prompt({
    type: 'list',
    name: 'versionType',
    message: '选择需要更新的版本类型:\n\t ->',
    default: '修订补丁',
    choices: versionTypeOpts,
  })

  const index = versionTypeOpts.findIndex(item => item === versionType)

  const version = packageConfig.version.replace(/(.+)\.(.+)\.(.+)/, (...args) => {
    const _version = args.slice(1,4)
    _version[index] = Number(_version[index] || 0) + 1

    return _version.join('.')
  })

  packageConfig.version = version

  return version

}

未来版本是选择版本号+自定义版本号,比如当前是1.0.0给出如下几种方案进行选择:

  • 1.0.0
  • 1.0.1
  • 1.1.0
  • 2.0.0
  • 自定义

选择自定义需要开发者自己手动输入,需要分三段输入,每次输入都要第一段校验正整数,后面两段检验是非负整数,并写入package.json

这种傻瓜式配置更能让人理解,而且扩展能力也比较好

本地写入版本号

写入 package.json 的 version。

function setVersion () {
  if (process.argv[3] !== 'version') return

  console.log("版本号写入:\n\t ->", packageConfig.version, '\n')
  fs.writeFileSync(
    path.resolve(__dirname, '../package.json'), 
    JSON.stringify(packageConfig, false, 2),
  )
}

代码合集

const readline = require('readline');
const buildConfig = require('./buildConfig.js')
const packageConfig = require('../package.json')
const { exec } = require('child_process')
const fs = require('fs')
const path = require('path')
var inquirer = require('inquirer')

init()

async function init () {
  // console.log(Object.prototype.toString.call(buildConfig))
  console.log("当前编译配置:\n\t ->", buildConfig, '\n')
  // console.log('__dirname : ' + __dirname)

  if (!buildConfig.toolsPath) {
    await getToolsPath()
  }

  console.log("开发者工具安装路径:\n\t ->", buildConfig.toolsPath, '\n')

  const env = await selectEnv()

  if (process.argv[2] !== 'upload') return

  await getVersion()
  
  await commitCode(env)

  await setVersion()

  console.log("上传成功:\n\t ->", env, '\n')
}

/**
 * 获取开发者工具目录
 */
function getToolsPath () {
  return new Promise((resolve, reject) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });

    rl.question('当前未设置开发者工具安装路径,请设置:\n\t -> ', (path) => {

      rl.close();

      fs.writeFileSync(
        `${__dirname}/buildConfig.js`, 
        `module.exports = ${JSON.stringify(Object.assign(buildConfig, {toolsPath: path}), false, 2)}`,
      )

      console.log('\n')

      resolve(path)
    });
  })
}

/**
 * 选择编译环境
 */
async function selectEnv() {
  const{env} = await inquirer.prompt({
    type: 'list',
    name: 'env',
    message: '当前编译环境:\n\t ->',
    default: buildConfig.env,
    choices: ['local', 'qa', 'test', 'v4', 'v8'],
  })

  fs.writeFileSync(
    `${__dirname}/buildConfig.js`, 
    `module.exports = ${JSON.stringify(Object.assign(buildConfig, {env}), false, 2)}`,
  )

  return env
}

/**
 * 小程序代码上传
 * 
 * @param {String} env 环境
 */
function commitCode (env) {
  return new Promise((resolve, reject) => {
    const version = packageConfig.version
    const projectPath = path.resolve(__dirname, '../')
    console.log("\n项目路径:\n\t ->", projectPath, '\n')
    
    const toolsPath = buildConfig.toolsPath

    const command = `cli ${[
      'upload',
      '--project',
      projectPath,
      '-v',
      version,
      '-d',
      `${env}-${version}`
    ].join(' ')}`

    console.log("小程序上传代码命令:\n\t ->", command, '\n')
    // const ls = execSync(command, {
    //   cwd: toolsPath,
    // })

    // console.log(ls, ls.toString())

    // console.log(Object.prototype.toString.call(ls))

    // console.log(ls.pid, ls.output, ls.stdout)

    exec(command, {cwd: toolsPath}, (error, stdout, stderr) => {
      // console.log(`error: ${error}`);
      // console.log(`stdout: ${stdout}`);
      // console.error(`stderr: ${stderr}`);
      if (error) {
        console.error(`执行的错误: ${error}`)

        reject(error)
        return;
      }

      if (!stdout) {
        console.error(`执行的错误: ${stderr}`)

        reject(stderr)
        return;
      }

      console.log(`stdout: ${stdout}`);

      resolve()
    })
  })

}

/**
 * 获取上传版本号
 * 
 * 开发者选择需要更新的版本号
 * 
 * 未来版本是选择版本号+自定义版本号,比如当前是1.0.0给出如下几种方案进行选择:
 * 1.0.0
 * 1.0.1
 * 1.1.0
 * 2.0.0
 * 自定义
 * 
 * 选择自定义需要开发者自己手动输入,需要分三段输入,每次输入都要第一段校验正整数,后面两段检验是非负整数,并写入package.json
 * 
 * 这种傻瓜式配置更能让人理解,而且扩展能力也比较好
 */
async function getVersion () {
  if (!packageConfig.version) {
    const version = '1.0.0'

    console.log("\n当前无版本号,使用默认版本号:\n\t ->", version)

    packageConfig.version = version

    return version
  }

  console.log("\n当前版本号:\n\t ->", packageConfig.version, '\n')

  const versionTypeOpts = ['版本升级', '特性更新', '修订补丁']

  const {versionType} = await inquirer.prompt({
    type: 'list',
    name: 'versionType',
    message: '选择需要更新的版本类型:\n\t ->',
    default: '修订补丁',
    choices: versionTypeOpts,
  })

  const index = versionTypeOpts.findIndex(item => item === versionType)

  const version = packageConfig.version.replace(/(.+)\.(.+)\.(.+)/, (...args) => {
    const _version = args.slice(1,4)
    _version[index] = Number(_version[index] || 0) + 1

    return _version.join('.')
  })

  packageConfig.version = version

  return version

}

/**
 * 本地写入版本号
 * 
 * 这里没有考虑使用版本号来区别是为了更好的灵活性
 */
function setVersion () {
  if (process.argv[3] !== 'version') return

  console.log("版本号写入:\n\t ->", packageConfig.version, '\n')
  fs.writeFileSync(
    path.resolve(__dirname, '../package.json'), 
    JSON.stringify(packageConfig, false, 2),
  )
}

注意

各个项目的环境名称各不相同,需要根据需求自己修改。

buildConfig.js 默认配置成安装目录为空,默认环境为线上环境

如果担心文件bei修改就加入.gitignore禁止上传

参考

https://developers.weixin.qq.com/miniprogram/dev/devtools/cli.html
https://developers.weixin.qq.com/miniprogram/dev/devtools/http.html
http://nodejs.cn/api/readline.html#readline_readline_createinterface_options
http://nodejs.cn/api/child_process.html#child_process_child_process_exec_command_options_callback
http://nodejs.cn/api/fs.html#fs_fs_writefilesync_file_data_options
http://nodejs.cn/api/path.html#path_path_relative_from_to
https://github.com/SBoudrias/Inquirer.js

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

推荐阅读更多精彩内容