听说你还在用开发者工具手动上传小程序,快来试试 miniprogram-ci 提效摸鱼

本文主要是介绍听说你还在用开发者工具手动上传小程序,快来试试 miniprogram-ci 提效摸鱼,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信进群。

本文是 @NewName 小伙伴参加源码共读活动第30期(@tarojs/plugin-mini-ci 小程序上传代码 ci)的投稿。

原文链接:https://juejin.cn/post/7089819849257385997

此人非同寻常,我发布了多少期源码共读、他就基本写了多少期文章。

Part1学习准备工作

阅读相关学习资料:

微信小程序CI :https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html
taro CI: https://link.juejin.cn/?target=https%3A%2F%2Ftaro-docs.jd.com%2Ftaro%2Fdocs%2Fnext%2Fplugin-mini-ci%2F
coding自动构建微信小程序:https://help.coding.net/docs/ci/practice/quick/wechat-mini-program.html
小打卡小程序自动化构建:https://www.yuque.com/jinxuanzheng/gvhmm5/uy4qu9#mmmx7

clone源码:源码路径如下:

https://github.com/NervJS/taro/tree/next/packages/taro-plugin-mini-ci

我是把taro整个clone下来的。

Part2源码学习

1小程序CI的整体流程

首先看index.js:

module.exports = require('./dist/index.js').default
module.exports.default = module.exports

含义是引入dist文件夹下的index.js文件, 但是刚克隆下来的代码中并没有dist文件夹。很容易想到dist文件夹是打包后产生的,所以执行命令:

npm i
npm run build

注意是在taro/packages/taro-plugin-mini-ci目录下执行 install和build命令:43ca431666abf8d0f3da3fb914fce102.pngbuild之后可以看到有了dist文件夹:1cf0d3eaf792650ab1bcd18d85396b80.png对应目录下也生成了index.js文件,生成的js文件和原来的ts文件也没差太多,再加上最近再自学ts,就看index.ts吧(代码有删减):

import { IPluginContext } from '@tarojs/service'
import * as minimist from 'minimist'
import { CIOptions } from './BaseCi'
import WeappCI from './WeappCI'
import TTCI from './TTCI'
import AlipayCI from './AlipayCI'
import SwanCI from './SwanCI'export { CIOptions } from './BaseCi'
export default (ctx: IPluginContext, pluginOpts: CIOptions) => {const onBuildDone = ctx.onBuildComplete || ctx.onBuildFinishctx.addPluginOptsSchema((joi) => {return joi.object().keys({/** 微信小程序上传配置 */weapp: joi.object({appid: joi.string().required(),projectPath: joi.string(),privateKeyPath: joi.string().required(),type: joi.string().valid('miniProgram', 'miniProgramPlugin', 'miniGame', 'miniGamePlugin'),ignores: joi.array().items(joi.string().required())}),/** 字节跳动小程序上传配置 *//** 阿里小程序上传配置 *//** 百度小程序上传配置 */swan: joi.object({token: joi.string().required(),minSwanVersion: joi.string()}),version: joi.string(),desc: joi.string()}).required()})onBuildDone(async () => {const args = minimist(process.argv.slice(2), {boolean: ['open', 'upload', 'preview']})const { printLog, processTypeEnum } = ctx.helperconst platform = ctx.runOpts.options.platformlet ciswitch (platform) {case 'weapp':ci = new WeappCI(ctx, pluginOpts)breakcase 'tt':ci = new TTCI(ctx, pluginOpts)breakcase 'alipay':case 'iot':ci = new AlipayCI(ctx, pluginOpts)breakcase 'swan':ci = new SwanCI(ctx, pluginOpts)breakdefault:break}if (!ci) {printLog(processTypeEnum.WARNING, `"@tarojs/plugin-mini-ci" 插件暂时不支持 "${platform}" 平台`)return}switch (true) {case args.open:ci.open()breakcase args.upload:ci.upload()breakcase args.preview:ci.preview()breakdefault:break}})
}

代码的整体流程比较简单,判断平台,创建CI实例, 执行对应的CI。6b418ec16b37cd5e54f3d632b2d466b3.png

可以在启动Node.js 程序时直接指定命令行参数,例如:

node index.js --beep=boop -t -z 12 -n5 foo bar

Node.js 程序启动后可以直接从process.argv中读取到参数列表:

console.log(process.argv);
// ['/bin/node', '/tmp/index.js', '--beep=boop', '-t', '-z', '12', '-n5', 'foo', 'bar']

从上述代码中可以看到,process.argv 变量是一个数组,数组前两项分别是 node 程序位置和js脚本位置,数组中随后的元素都是我们启动Node.js后的参数,这些参数以空格分隔成数组。而minimist 是一个专门用于处理Node.js启动参数的库,可以将 process.argv 中的参数列表转换成更加易于使用的格式:

const argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
// { _: [ 'foo', 'bar' ], beep: 'boop', t: true, z: 12, n: 5 }

具体使用可以参考https://www.npmjs.com/package/minimist, 使用的时候接收参数和配置对象。

2CI抽象类:BaseCI

packages/taro-plugin-mini-ci/src/BaseCi.ts(代码有删减):

import { IPluginContext } from '@tarojs/service'
import * as path from 'path'export type ProjectType = 'miniProgram' | 'miniGame' | 'miniProgramPlugin' | 'miniGamePlugin';/** 微信小程序配置 *//** 头条小程序配置 *//** 支付宝系列小程序配置 *//** 百度小程序配置 */export interface CIOptions {/** 发布版本号,默认取 package.json 文件的 taroConfig.version 字段 */version: string;/** 版本发布描述, 默认取 package.json 文件的 taroConfig.desc 字段 */desc: string;/** 微信小程序CI配置, 官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html */weapp?: WeappConfig;/** 头条小程序配置, 官方文档地址:https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/developer-instrument/development-assistance/ide-order-instrument */tt?: TTConfig;/** 支付宝系列小程序配置,官方文档地址:https://opendocs.alipay.com/mini/miniu/api */alipay?: AlipayConfig;/** 百度小程序配置, 官方文档地址:https://smartprogram.baidu.com/docs/develop/devtools/commandtool/ */swan?: SwanConfig;
}export default abstract class BaseCI {/** taro 插件上下文 */protected ctx: IPluginContext/** 传入的插件选项 */protected pluginOpts: CIOptions/** 当前要发布的版本号 */protected version: string/** 当前发布内容的描述 */protected desc: stringconstructor (ctx: IPluginContext, pluginOpts: CIOptions) {this.ctx = ctxthis.pluginOpts = pluginOptsconst { appPath } = ctx.pathsconst { fs } = ctx.helperconst packageInfo = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json'), {encoding: 'utf8'}))this.version = pluginOpts.version || packageInfo.taroConfig?.version || '1.0.0'this.desc = pluginOpts.desc || packageInfo.taroConfig?.desc || `CI构建自动构建于${new Date().toLocaleTimeString()}`this._init()}/** 初始化函数,会被构造函数调用 */protected abstract _init():void;/** 打开小程序项目 */abstract open();/** 上传小程序 */abstract upload();/** 预览小程序 */abstract preview();
}

在抽象类中定义了一些属性是protected的,意味着可以在本类以及子类中访问;在constructor中对属性进行了初始化,并调用初始化函数。然后是定义了一些CI操作的抽象方法。

3CI子类:AlipayCI

packages/taro-plugin-mini-ci/src/AlipayCI.ts

/* eslint-disable no-console */
import * as miniu from 'miniu'
import * as path from 'path'
import BaseCI from './BaseCi'
import generateQrCode from './QRCode'/** 文档地址:https://opendocs.alipay.com/mini/miniu/api */
export default class AlipayCI extends BaseCI {protected _init (): void {if (this.pluginOpts.alipay == null) {throw new Error('请为"@tarojs/plugin-mini-ci"插件配置 "alipay" 选项')}const { appPath } = this.ctx.pathsconst { fs } = this.ctx.helperconst { toolId, privateKeyPath: _privateKeyPath, proxy } = this.pluginOpts.alipayconst privateKeyPath = path.isAbsolute(_privateKeyPath) ? _privateKeyPath : path.join(appPath, _privateKeyPath)if (!fs.pathExistsSync(privateKeyPath)) {throw new Error(`"alipay.privateKeyPath"选项配置的路径不存在,本次上传终止:${privateKeyPath}`)}miniu.setConfig({toolId,privateKey: fs.readFileSync(privateKeyPath, 'utf-8'),proxy})}open () {const { printLog, processTypeEnum } = this.ctx.helperprintLog(processTypeEnum.WARNING, '阿里小程序不支持 "--open" 参数打开开发者工具')}async upload () {const { chalk, printLog, processTypeEnum } = this.ctx.helperconst clientType = this.pluginOpts.alipay!.clientType || 'alipay'printLog(processTypeEnum.START, '上传代码到阿里小程序后台', clientType)// 上传结果CI库本身有提示,故此不做异常处理// TODO 阿里的CI库上传时不能设置“禁止压缩”,所以上传时被CI二次压缩代码,可能会造成报错,这块暂时无法处理; SDK上传不支持设置描述信息const result = await miniu.miniUpload({project: this.ctx.paths.outputPath,appId: this.pluginOpts.alipay!.appId,packageVersion: this.version,clientType,experience: true,onProgressUpdate (info) {const { status, data } = infoconsole.log(status, data)}})if (result.packages) {const allPackageInfo = result.packages.find(pkg => pkg.type === 'FULL')const mainPackageInfo = result.packages.find((item) => item.type === 'MAIN')const extInfo = `本次上传${allPackageInfo!.size} ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}}async preview () {const previewResult = await miniu.miniPreview({project: this.ctx.paths.outputPath,appId: this.pluginOpts.alipay!.appId,clientType: this.pluginOpts.alipay!.clientType || 'alipay',qrcodeFormat: 'base64'})console.log('预览二维码地址:', previewResult.packageQrcode)generateQrCode(previewResult.packageQrcode!)}
}

支付宝小程序子类的_init()方法主要做参数的验证和设置;open,upload,preview实现了抽象类定义的方法,分别用于打开开发者工具,上传代码,预览二维码。核心功能的实现依赖于miniu。可以查看相应的资料。627ea6eb73df2b00180d6e704d01639b.png这篇文章介绍了使用MiniU完成CI/CD:https://forum.alipay.com/mini-app/post/35101018。生成二维码调用了generateQrCode方法:

/*** 生产二维码输出到控制台* @param url 链接地址*/
export default function generateQrCode (url: string) {require('qrcode-terminal').generate(url, { small: true })
}

generateQrCode实际上是通过三方包qrcode-terminal来实现的。

4CI子类:SwanCI

在SwanCI类中open方法和preview方法的实现与AlipayCI一样,upload实现有所不同:

async upload () {const { outputPath } = this.ctx.pathsconst { chalk, printLog, processTypeEnum } = this.ctx.helperprintLog(processTypeEnum.START, '上传体验版代码到百度后台')printLog(processTypeEnum.REMIND, `本次上传版本号为:"${this.version}",上传描述为:“${this.desc}”`)shell.exec(`${this.swanBin} upload --project-path ${outputPath} --token ${this.pluginOpts.swan!.token} --release-version ${this.version} --min-swan-version ${this.pluginOpts.swan!.minSwanVersion || '3.350.6'} --desc ${this.desc} --json`, (_code, _stdout, stderr) => {if (!stderr) {// stdout = JSON.parse(stdout)console.log(chalk.green(`上传成功 ${new Date().toLocaleString()}`))}})}

上传的时候执行shell脚本,通过shelljs来实现的 。

5CI子类:WeappCI

WeappCI主要是使用了miniprogram-ci ,具体看一下open, upload, preview方法:open方法(代码有删减):

import * as cp from 'child_process'async open () {const { fs, printLog, processTypeEnum, getUserHomeDir } = this.ctx.helperconst { appPath } = this.ctx.paths// 检查安装路径是否存在/** 命令行工具所在路径 */// 检查是否开启了命令行cp.exec(`${cliPath} open --project ${appPath}`, (err) => {if (err) {printLog(processTypeEnum.ERROR, err.message)}})}

open方法用于打开开发者工具,通过node.js child_process的exec执行命令。upload方法:

import * as ci from 'miniprogram-ci'async upload () {const { chalk, printLog, processTypeEnum } = this.ctx.helpertry {printLog(processTypeEnum.START, '上传体验版代码到微信后台')printLog(processTypeEnum.REMIND, `本次上传版本号为:"${this.version}",上传描述为:“${this.desc}”`)const uploadResult = await ci.upload({project: this.instance,version: this.version,desc: this.desc,onProgressUpdate: undefined})if (uploadResult.subPackageInfo) {const allPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__FULL__')const mainPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__APP__')const extInfo = `本次上传${allPackageInfo!.size / 1024}kb ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size + 'kb' : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}}

上传代码的方法使用miniprogram-ci的upload方法,得到结果信息后根据分包信息来提示整体包大小和主包大小。preview方法(代码有删减):

async preview () {const { chalk, printLog, processTypeEnum } = this.ctx.helpertry {printLog(processTypeEnum.START, '上传开发版代码到微信后台并预览')const uploadResult = await ci.preview({project: this.instance,version: this.version,desc: this.desc,onProgressUpdate: undefined})if (uploadResult.subPackageInfo) {const allPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__FULL__')const mainPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__APP__')const extInfo = `本次上传${allPackageInfo!.size / 1024}kb ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size + 'kb' : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}
}

preview方法使用的是miniprogram-ci的preview方法, 得到结果信息后根据分包信息来提示整体包大小和主包大小。

6CI子类:TTCI

TTCI使用tt-ide-cli来完成预览和上传,使用child_process的exec来完成打开开发者工具的功能。open(代码有删除):

open () {if (fs.existsSync(projectPath)) {console.log(chalk.green(`open projectPath: ${projectPath}`))const openPath = `${openCmd}?path=${projectPath}`cp.exec(openPath, (error) => {if (!error) {console.log('打开IDE成功', openPath)} else {console.log(chalk.red('打开IDE失败', error))}})}}

这里open方法也是通过node.js child_process的exec执行命令。upload(代码有删除):

import * as tt from 'tt-ide-cli'
async upload () {try {await tt.upload({entry: outputPath,version: this.version,changeLog: this.desc})} catch (error) {}}

上传代码使用tt-ide-cli的upload方法。preview(代码有删除):

import * as tt from 'tt-ide-cli'async preview () {try {await tt.preview({entry: outputPath,force: true,small: true})} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}}

生成预览二维码使用了tt-ide-cli的upload方法。

Part3总结

1.taro小程序ci的核心代码逻辑是:判断平台,创建CI实例, 执行对应的CI。2.不同平台对应不同的CI类,但都继承了基础的CI抽象类,实现了抽象类声明的open,upload和preview方法。3.实现具体的open,upload和preview方法时根据对应小程序是否提供了命令行工具,有用到miniu,tt-ide-cli,miniprogram-ci,还有的使用shelljs,qrcode-terminal,以及child_process来执行命令。


cf714cd4d9dfebc36933cc7720c8b8ed.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助4000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

4347d3951e26844834b0a0afaa8676cf.png

扫码加我微信 ruochuan02、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

这篇关于听说你还在用开发者工具手动上传小程序,快来试试 miniprogram-ci 提效摸鱼的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/667450

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼?市面上那么多工具,常常让人无从选择。今天,想给大家安利一款神器——PixPin,一款真正解放双手的截图工具。 想象一下,你只需要按下快捷键就能轻松完成多种截图任务,还能快速编辑、标注甚至保存多种格式的图片。这款工具能满足这些需求吗? PixPin不仅支持全屏、窗口、区域截图等基础功能,它还可以进行延时截图,让你捕捉到每个关键画面。不仅如此

如何使用Ansible实现CI/CD流水线的自动化

如何使用Ansible实现CI/CD流水线的自动化 持续集成(CI)和持续交付(CD)是现代软件开发过程中的核心实践,它们帮助团队更快地交付高质量的软件。Ansible,作为一个强大的自动化工具,可以在CI/CD流水线中发挥关键作用。本文将详细介绍如何使用Ansible实现CI/CD流水线的自动化,包括设计流水线的结构、配置管理、自动化测试、部署、以及集成Ansible与CI/CD工具(如Jen

Spring MVC 图片上传

引入需要的包 <dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的