## 介绍

miniprogram-ci 是从[微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html)中抽离的关于小程序/小游戏项目代码的编译模块。

使用前需要使用小程序管理员身份访问"[微信公众平台](https://mp.weixin.qq.com)-开发-开发设置"后下载代码上传密钥，并配置 IP 白名单，才能进行上传、预览操作。

miniprogram-ci 从 1.0.28 开始支持第三方平台开发的上传和预览，调用方式与普通开发模式无异。[查看详情](#第三方平台开发)

## 最近变更
#### 2.1.31
 - `fix` 修复 去除 package.json devDependencies
 - `fix` 修复 锁定 less 版本为 4.5.1
#### 2.1.30
 - `new` 新增 允许 CI 构建多端 APK 时使用 beta 版本 SDK
#### 2.1.29
 - `fix` 修复 多端 iOS 构建专业版水印去除失败
#### 2.1.27
 - `new` 优化 cos 上传链路
#### 2.1.20
 - `fix` 修复 onProgressUpdate 未指定时输出多余日志
#### 2.1.19
 - `fix` 修复 当使用多个多端插件时出现的下载失败问题
 - `fix` 修复 分包 entry 被无依赖过滤的问题
#### 2.1.18
 - `fix` 修复 当使用 ssss/less @import时重复上传内容的问题
#### 2.1.17
 - `fix` 修复 多端构建 packOptions 不生效
#### 2.1.16
 - `fix` 修复 COS 上报失败的问题
#### 2.1.15
 - `fix` 修复 部分log不清晰的问题
#### 2.1.14
 - `fix` 修复 缺少 tmp 依赖的问题
#### 2.1.13
 - `fix` 修复 进程不退出的问题
 - `fix` 修复 useCos 情况下 robot失效的问题
#### 2.1.12
 - `fix` 修复 minifyJS 配置影响到 minifyWXSS
#### 2.1.11
 - `new` 新增 支持使用多端 iOS SDK 版本 1.6.x
#### 2.1.10
 - `fix` 修复 packOptions 不生效的问题
#### 2.1.9
 - `fix` 修复 miniprogramRoot 结尾没有 / 会影响babel编译的问题
#### 2.1.7
 - `fix` 修复 同进程构建 Ipa 与 Apk 时条件编译错误
#### 2.1.6
 - `new` 新增 多端资源包上传
#### 2.1.5
 - `fix` 修复 多端构建问题
#### 2.1.4
 - `new` 优化 预览和上传的时候不需要将app.json usingComponents 配置扩散到页面 json 文件中，减少代码包体积
#### 2.1.3
 - `fix` 修复 多端构建条件编译不生效
 - `new` 新增 支持 Linux 环境下的安卓构建
#### 2.1.2
 - `fix` 修复 安卓构建的参数校验
#### 2.1.2
 - `fix` 修复 安卓构建的参数校验
#### 2.1.1
 - `fix` 修复 依赖声明
#### 2.1.0
 - `new` 优化 内部架构
#### 2.0.11
 - `fix` 修复 压缩wxml出现的问题
 - `fix` 修复 独立分包entry文件被过滤的问题
#### 2.0.10
 - `fix` 修复 schema d.ts路径问题
#### 2.0.9
 - `fix` 修复 ts，sass 等文件在某些情况无法编译的问题
#### 2.0.8
 - `fix` 修复 babel 导致的 path.requeueComputedKeyAndDecorators 问题
#### 2.0.7
 - `fix` 修复 Skyline disableScroll 不强制为 true
#### 2.0.6
 - `fix` 修复 未设置 onProgressUpdate 输出日志为[object Object]的问题
#### 2.0.5
 - `fix` 修复 命令行执行完不退出的问题
 - `fix` 修复 输出日志为[object Object]的问题
#### 2.0.4
 - `fix` 修复 代码包体积变大的问题
#### 2.0.3
 - `fix` 优化 目录结构 
#### 2.0.2
 - `fix` 修复 windows 下 cannot find module 'handleerror' 的问题 
#### 2.0.1
 - `fix` 修复 subPackages of undefined 问题
#### 2.0.0
 - `new` 新增 swc 编译模式
 - `new` 新增 useProjectConfig 编译配置
#### 1.9.15
 - `fix` 修复 app.json 的 app-bar 配置未生效的 bug。
#### 1.9.14
 - `new` 更新  `es6` 或者 `es7` 编译时，忽略编译的文件大小阈值由 500kb 改为 2MB，即大小超过 2MB 的 js 文件不会被编译。
#### 1.9.11
 - `new` 新增  `checkCodeQuality` 代码质量检查能力。
#### 1.9.10
  - `new` 新增 支持 compileWorklet 编译选项。
#### 1.9.9
  - `new` 更新 worklet 函数的编译逻辑。
#### 1.9.8
  - `fix` 修复 TypeError: _regeneratorRuntime3 is not a function 报错问题
#### 1.9.7
  - `new` 更新 babel 版本到 7.21.4

>完整变更记录请查看 CHANGELOG.md 文件
## 注意事项

1. 代码上传密钥拥有预览、上传代码的权限
2. 代码上传密钥不会明文存储在微信公众平台上，一旦遗失必须重置，请妥善保管
3. 未配置IP白名单的，将无法使用 miniprogram-ci 进行预览和上传
4. 可选择不对IP进行限制，但务必明白风险

## 功能

miniprogram-ci 目前提供以下能力：
1. 上传代码，对应小程序开发者工具的上传
1. 预览代码，对应小程序开发者工具的预览
1. 构建 npm，对应小程序开发者工具的: 菜单-工具-构建npm
1. 上传云开发云函数代码，对应小程序开发者工具的上传云函数能力
1. 代理，配置 miniprogram-ci 的网络请求代理方式
1. 支持获取最近上传版本的 sourceMap
1. 支持 node 脚本调用方式和 命令行 调用方式


## 脚本调用

```shell script
npm install miniprogram-ci --save
```

### 项目对象

项目对象是本模块主要的入参，可以依据下边的定义自行实现

**项目对象的定义：**

```typescript
interface IProject {
  appid: string
  type: string
  projectPath: string
  privateKey: string
  attr(): Promise<IProjectAttr>
  stat(prefix: string, filePath: string): IStat | undefined
  getFile(prefix: string, filePath: string): Buffer
  getFileList(prefix: string, extName: string): string[]
  updateFiles: () => void
}
```

| 键          | 类型     | 说明                                                                                                               |
| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| appid       | 属性     | 小程序/小游戏项目的 appid                                                                                          |
| type        | 属性     | 项目的类型，有效值 miniProgram/miniProgramPlugin/miniGame/miniGamePlugin                                           |
| projectPath | 属性     | 项目的路径，即 project.config.json 所在的目录                                                    |
| privateKey  | 属性     | 私钥，在获取项目属性和上传时用于鉴权使用，在 [微信公众平台](https://mp.weixin.qq.com) 上使用小程序管理员登录后下载 |
| attr        | 异步方法 | 项目的属性，如指定了 privateKey 则会使用真实的项目属性                                                             |
| stat        | 同步方法 | 特定目录下前缀下（prefix）文件路径 (filePath) 的 stat, 如果不存在则返回 undefined                                  |
| getFile     | 异步方法 | 特定目录下前缀下（prefix）文件路径 (filePath) 的 Buffer                                                            |
| getFileList | 同步方法 | 特定目录下前缀下（prefix）文件路径 (filePath) 下的文件列表                                                         |
| updateFile | 同步方法  | 更新项目文件                                                                                                   |

也可以通过指定项目路径来创建该对象

```javascript
const ci = require('miniprogram-ci')

// 注意： new ci.Project 调用时，请确保项目代码已经是完整的，避免编译过程出现找不到文件的报错。
const project = new ci.Project({
  appid: 'wxsomeappid',
  type: 'miniProgram',
  projectPath: 'the/project/path',
  privateKeyPath: 'the/privatekey/path',
  ignores: ['node_modules/**/*'],
})
```

| 键             | 类型     | 必填 | 说明                                                                                                     |
| -------------- | -------- | ---- | -------------------------------------------------------------------------------------------------------- |
| appid          | string   | 是   | 合法的小程序/小游戏 appid                                                                                |
| projectPath    | string   | 是   | 项目路径，即 project.config.json 所在的目录                                                                                                |
| privateKey     | string   | 否   | 私钥的内容，（创建 Project 对象，需要传入私钥内容或私钥文件路径）                                                  |
| privateKeyPath | string   | 否   | 私钥文件的路径，（创建 Project 对象，需要传入私钥内容或私钥文件路径）                                             |
| type           | string   | 否   | 显示指明当前的项目类型, 默认为 miniProgram，有效值 miniProgram/miniProgramPlugin/miniGame/miniGamePlugin |
| ignores        | string[] | 否   | 指定需要排除的规则                                                                                       |

### 上传

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const uploadResult = await ci.upload({
    project,
    version: '1.1.1',
    desc: 'hello',
    setting: {
      useProjectConfig: true,
    },
    onProgressUpdate: console.log,
  })
  console.log(uploadResult)
})()
```
#### 参数

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| project          | IProject | 是   | #项目对象                              |
| version          | string   | 是   | 自定义版本号                            |
| desc             | string   | 否   | 自定义备注                              |
| setting          | object   | 否   | #编译设置                             |
| onProgressUpdate | function | 否   | 进度更新监听函数                        |
| robot            | number   | 否   | 指定使用哪一个 ci 机器人，可选值：1 ~ 30 |
| threads          | number   | 否   | 指定本地编译过程中开启的线程数 |
| useCOS           | boolean  | 否   | 使用异步方式上传，当代码包大于 5MB 时，默认开启 |
| ~~llowIgnoreUnusedFiles~~  | ~~boolean~~  | ~~否~~   | ~~允许过滤无依赖文件，默认开启~~ (该参数已废弃，请在 project.config.json 中配置此项) |

#### 返回

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| subPackageInfo   | Array<{name:string, size:number}> | 否   | 小程序包信息, `name` 为 `__FULL__` 时表示整个小程序包， `name` 为 `__APP__` 时表示小程序主包，其他情况都表示分包 |
| pluginInfo   | Array<{pluginProviderAppid: string, version: string, size:number}> | 否   | 小程序插件信息 |
| devPluginId      | string | 否   | 插件开发模式下，上传版本的插件 id                  |


### 预览

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const previewResult = await ci.preview({
    project,
    desc: 'hello',
    setting: {
      useProjectConfig: true,
    },
    qrcodeFormat: 'image',
    qrcodeOutputDest: '/path/to/qrcode/file/destination.jpg',
    onProgressUpdate: console.log,
    // pagePath: 'pages/index/index', // 预览页面
    // searchQuery: 'a=1&b=2',  // 预览参数 [注意!]这里的`&`字符在命令行中应写成转义字符`\&`
    // scene: 1011, // 场景值
  })
  console.log(previewResult)
})()
```

#### 参数

| 键               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project          | IProject | 是   | #项目对象                                                            |
| desc             | string   | 否   | 自定义备注                                                           |
| setting          | object   | 否   | #编译设置                                                            |
| onProgressUpdate | function | 否   | 进度更新监听函数                                                      |
| robot            | number   | 否   | 指定使用哪一个 ci 机器人，可选值：1 ~ 30                                |
| threads          | number   | 否   | 指定本地编译过程中开启的线程数                                           |
| useCOS           | boolean  | 否   | 使用异步方式上传，默认为 false                                          |
| qrcodeFormat     | string   | 否  | 返回二维码文件的格式 `"image"` 或 `"base64"`， 默认值 `"terminal"` 供调试用 |
| qrcodeOutputDest | string   | 是  | 二维码文件保存路径                                                    |
| pagePath:        | string   | 否  | 预览页面路径                                                         |
| searchQuery:     | string   | 否  | 预览页面路径启动参数                                                   |
| scene            | number   | 否  | 默认值 `1011`，具体含义见[场景值列表](https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html)    |
| bigPackageSizeSupport | boolean | 否 | 预览时主包、分包体积上限调整为4M |
| ~~llowIgnoreUnusedFiles~~  | ~~boolean~~  | ~~否~~   | ~~允许过滤无依赖文件，默认开启~~ (该参数已废弃，请在 project.config.json 中配置此项) |


#### 返回

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| subPackageInfo   | Array<{name:string, size:number}> | 否   | 小程序包信息, `name` 为 `__FULL__` 时表示整个小程序包， `name` 为 `__APP__` 时表示小程序主包，其他情况都表示分包 |
| pluginInfo | Array<{pluginProviderAppid: string, version: string, size:number}> | 否   | 小程序插件信息 |



### 拉取最近上传版本的sourceMap

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  await ci.getDevSourceMap({
    project,
    robot: 1,
    sourceMapSavePath: './sm.zip'
  })
})()
```

#### 入参

| 键               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project          | IProject | 是   | #项目对象                                                            |
| robot            | number   | 是   | 指定使用哪一个 ci 机器人，可选值：1 ~ 30                                |
| sourceMapSavePath        | string   | 是  | 保存的路径 |
| streaming        | boolean   | 否  | 是否使用流式传输 |

### 获取本地编译后的代码包
当怀疑代码大小发生变化，不符合预期时，可以调用这个方法，检查一下最终上传到微信后台服务器时代码包里的文件内容。
```javascript
const ci = require('miniprogram-ci')
const path = require('path')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })

  // zip 文件保存位置
  const saveZipPath = path.join(__dirname, 'compiledResult.zip')

  const compiledResult = await ci.getCompiledResult({
    project,
    desc: 'hello',
    setting: {
      useProjectConfig: true,
    },
    qrcodeFormat: 'image',
    qrcodeOutputDest: '/path/to/qrcode/file/destination.jpg',
    onProgressUpdate: console.log,
    // pagePath: 'pages/index/index', // 预览页面
    // searchQuery: 'a=1&b=2',  // 预览参数 [注意!]这里的`&`字符在命令行中应写成转义字符`\&`
    // scene: 1011, // 场景值
  }, saveZipPath)

  console.log(compiledResult) // compiledResult 为 Record<string, string | Buffer> 类型
})()
```
#### 入参

| 参数              | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| options          | IUploadOptions | 是   | 和 upload 一致                                  |
| savePath            | string   | 否   | 编译结果zip文件保存的位置                                          |


### 构建npm
对应开发者工具[构建npm](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html)功能。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  // 在有需要的时候构建npm
  const warning = await ci.packNpm(project, {
    ignores: ['pack_npm_ignore_list'],
    reporter: (infos) => { console.log(infos) }
  })
  console.warn(warning)
  // 可对warning进行格式化
  /*
    warning.map((it, index) => {
            return `${index + 1}. ${it.msg}
    \t> code: ${it.code}
    \t@ ${it.jsPath}:${it.startLine}-${it.endLine}`
          }).join('---------------\n')
  */
  // 完成构建npm之后，可用ci.preview或者ci.upload
})()
```

| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                                                           |
| options.ignores   | string[] | 否   | 指定构建npm需要排除的规则                                              |
| options.reporter  | function   | 否  | 构建回调信息                                                        |

### 自定义 node_modules 位置的构建 npm
有的时候，需要被构建模块对应的 `node_modules` 可能并不在小程序项目内，所以提供了一个新的接口来支持这个需求。
例如有如下项目结构

```text
├── lib # lib目录存放要被构建的 node_modules
│   ├── node_modules
│   │   └── is-object
│   └── package.json
└── miniprogram-project # 这里是小程序项目路径
    ├── miniprogram # 我们希望最终把 miniprogram_npm 构建在 miniprogram/ 目录之下
    │   ├── app.js
    │   ├── app.json
    │   ├── app.wxss
    │   ├── pages
    │   │   ├── index
    │   │   └── logs
    │   └── sitemap.json
    └── project.config.json
```

于是可以这样调用

```javascript
let packResult = await ci.packNpmManually({
  packageJsonPath: './lib/package.json',
  miniprogramNpmDistDir: './miniprogram-project/miniprogram/',
})

console.log('pack done, packResult:', packResult)
// 输出 pack done, packResult: { miniProgramPackNum: 0, otherNpmPackNum: 1, warnList: [] }
```

得到的最终项目

```text
.
├── lib
│   ├── node_modules
│   │   └── is-object
│   └── package.json
└── miniprogram-project
    ├── miniprogram
    │   ├── app.js
    │   ├── app.json
    │   ├── app.wxss
    │   ├── miniprogram_npm # <--- 这就是构建出来的由 lib/node_modules 里 miniprogram_npm 了
    │   ├── pages
    │   └── sitemap.json
    └── project.config.json
```

| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| options.packageJsonPath   | string   | 是   | 希望被构建的`node_modules` 对应的 `package.json` 的路径       |
| options.miniprogramNpmDistDir  | string | 是   | 被构建 `miniprogram_npm` 的目标位置目标位置                  |
| options.ignores  | string[]   | 否  | 指定需要排除的规则                                                    |

### 上传云开发云函数

对应开发者工具[云开发](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)的[云函数](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/capabilities.html#%E4%BA%91%E5%87%BD%E6%95%B0)上传能力。命令行调用请参考[文末部分](#命令行调用)。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })

  const result = await ci.cloud.uploadFunction({
    project,
    env: '云环境 ID',
    name: '云函数名称',
    path: '云函数代码目录',
    remoteNpmInstall: true, // 是否云端安装依赖
  })
  console.warn(result)
})()
```

| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                                                           |
| env   | string | 是  | 云环境 ID                                              |
| name  | string | 是  | 云函数名称                                                        |
| path  | string | 是  | 云函数代码目录                                                        |
| remoteNpmInstall  | boolean | 否  | 是否云端安装依赖，默认 false                                                        |

`remoteNpmInstall` 额外说明：true 时云端安装依赖，不会上传本地 `node_modules`，false 时全量上传，包括上传 `node_modules`。

### 云函数添加定时触发器

对应开发者工具[定时触发器](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/functions/triggers.html)。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })

  const createResult = await ci.cloud.createTimeTrigger({
    project,
    envId: 'xxx-xxx-xxx-123',
    functionName: 'myFunctionName',
    // triggersConfig 字段是触发器数组，目前仅支持一个触发器，即数组只能填写一个，不可添加多个
    triggersConfig: [{
      // name: 触发器的名字，详细说明见工具文档
      "name": "myTrigger",
      // type: 触发器类型，目前仅支持 timer (即 定时触发器)
      "type": "timer",
      // config: 触发器配置，在定时触发器下，config 格式为 cron 表达式，详细说明见工具文档
      "config": "0 0 2 1 * * *"
    }]
  })
  console.log(createResult)
})()
```

```typescript
// 触发器数组，目前仅支持一个触发器，即数组只能填写一个，不可添加多个
interface ITriggersConfig {
  // name: 触发器的名字，规则见定时触发器说明
  name: string
  // type: 触发器类型，目前仅支持 timer (即 定时触发器)
  type: string
  // config: 触发器配置，在定时触发器下，config 格式为 cron 表达式，规则见定时触发器说明
  config: string
}[]
```
| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                                                           |
| envId   | string | 是  | 云环境 ID                                              |
| functionName  | string | 是  | 云函数名称                                                        |
| triggersConfig  | ITriggersConfig | 是  | 定时触发器配置                                                        |

### 上传云开发静态网站/云存储

对应开发者工具[云开发](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)的[静态网站托管](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/staticstorage/introduction.html)和[云存储](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage.html)的上传能力。命令行调用请参考[文末部分](#命令行调用)。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  // 静态网站
  const resultStatic = await ci.cloud.uploadStaticStorage({
    project,
    env: '云环境 ID',
    path: '本地文件目录',
    remotePath: '要上传到的远端文件目录',
  })
  // 云存储
  const resultStorage = await ci.cloud.uploadStorage({
    project,
    env: '云环境 ID',
    path: '本地文件目录',
    remotePath: '要上传到的远端文件目录',
  })
  console.warn(resultStatic, resultStorage)
})()
```

| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                                                           |
| env   | string | 是  | 云环境 ID                                              |
| path  | string | 是  | 本地文件目录                                                        |
| remotePath  | boolean | 否  | 要上传到的远端文件目录                                                         |

### 新建云开发云托管版本

对应开发者工具[云开发](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html)的[云托管](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/container/)新建版本能力。命令行调用请参考[文末部分](#命令行调用)。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const result = await ci.cloud.uploadContainer({
    project,
    env: '云环境 ID',
    version: {
      uploadType: 'package', // 上传方式
      flowRatio: 0, // 流量比例
      cpu: 0.25, // CPU 核心数
      mem: 0.5, // 内存大小
      minNum: 0, // 最小副本数
      maxNum: 1, // 最大副本数
      policyType: 'cpu', // 扩缩容条件
      policyThreshold: 60, // 扩缩容阈值
      containerPort: 80, // 容器监听端口
      serverName: 'server', // 服务名称
      versionRemark: 'ci', // 版本备注
      envParams: '{}', // 环境变量
      buildDir: '', // 构建目录
      dockerfilePath: '' // Dockerfile 路径
    },
    containerRoot: 'the/path/to/container' // 需要上传的版本文件目录
  })
  console.warn(result)
})()
```

| 参数               | 类型     | 必填 | 说明                                                                  |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                                                           |
| env   | string | 是  | 云环境 ID                                              |
| containerRoot | string | 是 | 本地容器文件目录 |
| version.uploadType  | string | 是  | 上传类型（package/repository/image）                                                        |
| version.flowRatio  | number | 是  | 新建版本后的默认流量比例                                                         |
| version.cpu | number | 是 | CPU 核心数量（需参考 [容器规格](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/container/concept.html#%E5%AE%B9%E5%99%A8%E8%A7%84%E6%A0%BC)） |
| version.mem | number | 是 | 内存大小（需参考 [容器规格](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/container/concept.html#%E5%AE%B9%E5%99%A8%E8%A7%84%E6%A0%BC)） |
| version.minNum | number | 是 | 最小副本数 |
| version.maxNum | number | 是 | 最大副本数 |
| version.policyType | string | 是 | 扩缩容条件，目前只支持：cpu |
| version.policyThreshold | number | 是 | 扩缩容阈值 |
| version.containerPort | number | 是 | 容器监听端口 |
| version.serverName | string | 是 | 服务名称 |
| version.versionRemark | string | 否 | 版本备注 |
| version.dockerfilePath | string | 否 | Dockerfile 路径 |
| version.buildDir | string | 否 | 构建目录 |
| version.envParams | string | 否 | 环境变量 |


### 上传小游戏jsserver代码
对应开发者工具的[上传jsserver代码](https://developers.weixin.qq.com/minigame/dev/guide/open-ability/interactive-data.html)的功能

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniGame',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })

  // 将上传 project.config.json 中 jsserverRoot 字段指定目录的代码文件。
  const result = await ci.uploadJsServer({
    project,
    env: 'test', // test 为测试环境 release 为正式环境
  })
  console.warn('upload Success' + result) // 上传成功时，result 为 true , 否则会抛出异常
})()
```

| 参数               | 类型     | 必填 | 说明                            |
| ---------------- | -------- | ---- | --------------------------------------------------------------------|
| project         | IProject   | 是   | 项目对象                  |
| env   | string | 是  |  test: 测试环境，release：正式环境             |


### 代码静态依赖分析
对应开发者工具的[代码静态依赖分析](https://developers.weixin.qq.com/miniprogram/dev/devtools/codeanalyse.html)的功能，输出分析结果。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
  })

  const result = await ci.analyseCode(project)
  console.warn('analyse result' + result)
})()
```

#### 分析结果结构说明
代码静态依赖分析的过程，也是构建出模块的依赖图谱的过程。模块分为文件模块和结构模块两种类型。每个模块都有唯一的 id 来表示。模块之间的依赖关系则通过 module.parentDeps 和 module.deps 中的信息来表示。
```js
{
  files:  [
    {
      path: 'app.json', // 文件的路径
      ext: '.json', // 文件的后缀名
      size: 771, // 文件的大小
      moduleId: 'MainPackage:app.json', // 对应的模块id，可以从 modules 中根据id 找出对应的模块，如果 moduleId 为 null，则说明依赖关系图谱中没有该文件。
      subPackage: null // 分包的路径，如果是 null 则代表是主包
    }
    ...
  ],
  modules: [
    {
      id: 'Component:components/chatroom/chatroom.json', // 模块的id，由模块类型和路径组成。
      type: 'Component', // 模块的类型，可能的类型取值有 1. 文件类型: 'Js' | 'Wxml' | 'Wxss' | 'Wxs' | 'Config'  2. 结构类型: 'MainPackage' | 'SubPackage' | 'Page' | 'Component' | 'Plugin' | 'Worker' | 'FunctionalPages' | 'ContextModule' | 'PluginPackage' | 'GameMainPackage' | 'GameSubPackage'
      path: 'components/chatroom/chatroom.json', 
      // 哪些模块依赖了此模块
      parentDeps: [
        {
          from: 'json', // 依赖关系的来源, 可能的取值：'json' | 'file' | 'rule'
          type: 'Component',
          request: '/components/chatroom/chatroom',
          path: 'components/chatroom/chatroom.json',
          moduleId: 'Component:components/chatroom/chatroom.json',
          originModuleId: 'Page:pages/im/room/room.json', // 父模块的id
          error: null, // 如果依赖解析过程中有错误，则为错误信息
        }
      ],
      // 此模块依赖了哪些模块
      deps: [
        {
          from: 'rule',
          type: 'Wxml',
          request: 'components/chatroom/chatroom.wxml',
          path: 'components/chatroom/chatroom.wxml',
          moduleId: 'Wxml:components/chatroom/chatroom.wxml', // 子模块的id
          originModuleId: 'Component:components/chatroom/chatroom.json', 
          error: null 
        },
      ],
      errors: [] // 如果模块解析过程中有错误，则错误列表不为空
    }
  ]
}
```

### 代码质量检查
对应开发者工具的[代码质量扫描](https://developers.weixin.qq.com/miniprogram/dev/devtools/quality.html)的功能，输出质量扫描结果。

```javascript
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey', // checkCodeQuality 方法不会读取 private key
  })

  const res = await ci.checkCodeQuality(project)
  console.warn('code quality' + result)
})()
```
#### 质量扫描结果说明
质量扫描结果是一个包含多个检查结果对象的数组。
每个检查项对象包含以下字段：
* name (字符串)：检查项的名称。
* success (布尔值)：代码是否满足此检查。
* text (字符串)：检查结果描述。
* docURL (字符串)：相关文档的 URL。
* detail (对象)：检查项结果详细信息（在此描述中不展开）。

| name - 检查项名字                   | detail                   |
|--------------------------|----------------------------|
| PACKAGE_SIZE_LIMIT       | 包含各个子包的大小信息             |
| JS_COMPRESS_OPEN         | 无                           |
| WXML_COMPRESS_OPEN       | 无                           |
| WXSS_COMPRESS_OPEN       | 无                           |
| LAZYCODE_LOADING_OPEN    | 无                           |
| IMAGE_AND_AUDIO_LIMIT    | 无                           |
| CONTAINS_OTHER_PKG_JS    | 包含主包中仅被其他子包依赖的 JS 文件信息  |
| CONTAINS_OTHER_PKG_COMPONENTS | 包含主包中仅被其他分包依赖的组件信息   |
| CONTAINS_UNUSED_PLUGINS  | 包含未使用的插件信息             |
| CONTAINS_UNUSED_COMPONENTS | 包含未使用的组件信息            |
| CONTAINS_UNUSED_CODES    | 包含无依赖文件的信息             |

示例：
```js
[
  {
    "name": "PACKAGE_SIZE_LIMIT",
    "success": false,
    "text": "Main package size (without plugins) should be less than 1.5 M",
    "docURL": "https://developers.weixin.qq.com/community/develop/doc/00040e5a0846706e893dcc24256009",
    "detail": {...}
  },
  {
    "name": "JS_COMPRESS_OPEN",
    "success": true,
    "text": "JS compression is not turned on",
    "docURL": "https://developers.weixin.qq.com/community/develop/doc/00040e5a0846706e893dcc24256009",
    "detail": {...}
  },
  ...
]
```

### 代理

miniprogram-ci 使用了 [get-proxy](https://www.npmjs.com/package/get-proxy) 模块来自动获取代理地址。
如果不适用`ci.proxy()`方法或者`--proxy`参数来指定代理，那么 miniprogram-ci 将会按照以下顺序去获取 https 代理地址

1. 获取环境变量中的 `HTTPS_PROXY`
2. 获取环境变量中的 `https_proxy`
3. 获取环境变量中的 `HTTP_PROXY`
4. 获取环境变量中的 `http_proxy`
5. 获取npm配置的 `https-proxy`
6. 获取npm配置的 `http-proxy`
7. 获取npm配置的 `proxy`
8. 把 `servicewechat.com` 加入到环境变量中的 `no_proxy`，miniprogram-ci 将不通过代理发送网络请求

```javascript
const ci = require('miniprogram-ci')
ci.proxy('YOUR_PROXY_URL')
```

### 构建 Apk
```js
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const buildResult = await ci.buildApk({
    project,
    keyAlias: 'keyAlias',
    keyPass: 'keyPass',
    keyStore: 'the/path/to/keystore',
    storePass: 'storePass',
    output: '/the/path/to/output',
    desc: 'build apk from ci',
    setting: {
      es6: true,
    },
  })
  console.log(uploadResult)
})()
```

#### 参数

同时支持传入 [ci.upload](#上传) 的参数

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| project          | IProject | 是   | [项目对象](#项目对象)                                  |
| keyStore         | string   | 是   | 证书文件绝对路径                            |
| keyPass          | string   | 是   | 证书密码                              |
| storePass        | string   | 是   | 证书文件密码                             |
| keyAlias         | string   | 是   | 证书别名                        |
| output           | string   | 是   | 构建产物的保存路径，需要为系统绝对路径 |
| useAab           | boolean  | 否   | 默认 false |
| desc             | string   | 否   | 版本描述 |
| isUploadResourceBundle  | boolean  | 否   | 是否上传资源包 |
| resourceBundleVersion   | string   | 否   | 资源包版本号 |
| resourceBundleDesc      | string   | 否   | 资源包项目备注 |
| disableCache      | boolean   | 否   | 是否清除构建缓存 |


#### 返回

| 键         | 类型     | 必填  | 说明                    |
| ----------| -------- | ---- | ----------------------- |
| errmsg    | string   |      |    构建信息   |
| success   | boolean  |      |  构建是否成功 |

### 构建 Ipa

支持本地构建与远程构建。本地构建 Ipa 仅支持在 macOS 上使用 codesign 进行签名,因此开发者需要确保 keychains 中包含自己的证书信息。windows 可以使用远程构建。

```js
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const buildResult = await ci.buildIpa({
    project,
    output: '/the/path/to/output', // 构建产物的保存路径，需要为系统绝对路径
    profilePath: '/the/path/to/profile', // profile 文件地址，文件格式为 mobileprovision，相对项目根路径的相对路径
    certificateName: 'Apple Develop', // 签名证书名
    tpnsProfilePath: '/the/path/to/tnpsprofile', // tpnsProfile 文件路径，相对项目根路径的相对路径
  })
  console.log(uploadResult)
})()

```

#### 参数

同时支持传入 [ci.upload](#上传) 的参数

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| project          | IProject | 是   | [项目对象](#项目对象)                                  |
| output           | string   | 是   | 构建产物的保存路径，需要为系统绝对路径 |
| isDistribute     | string   | 否   | 是否使用分发证书                            |
| profilePath      | string   | 是   | profile 文件地址，文件格式为 mobileprovision，相对项目根路径的相对路径|
| certificateName  | string   | 是   | 证书别名                        |
| tpnsProfilePath  | string   | 否   | tpnsProfile 文件路径，相对项目根路径的相对路径 |
| p12Path          | string   | 否   | p12 文件路径，文件格式为 p12，相对项目根路径的相对路径。使用远程构建时必填 |
| p12Password      | string   | 否   | p12 密码。使用远程构建时必填 |
| desc             | string   | 否   | 版本描述 |
| isUploadResourceBundle  | boolean  | 否   | 是否上传资源包 |
| resourceBundleVersion   | string   | 否   | 资源包版本号 |
| resourceBundleDesc      | string   | 否   | 资源包项目备注 |
| disableCache      | boolean   | 否  | 是否清除构建缓存 |
| isRemoteBuild     | boolean   | 否  | 是否使用远程构建 |

#### 返回
| 键         | 类型     | 必填  | 说明                    |
| ----------| -------- | ---- | ----------------------- |
| errmsg    | string   |      |    构建信息   |
| success   | boolean  |   |  构建是否成功 |

### 上传多端资源包

```js
const ci = require('miniprogram-ci')
;(async () => {
  const project = new ci.Project({
    appid: 'wxsomeappid',
    type: 'miniProgram',
    projectPath: 'the/project/path',
    privateKeyPath: 'the/path/to/privatekey',
    ignores: ['node_modules/**/*'],
  })
  const uploadResult = await ci.miniappCloudUpload({
    project,
    version: '0.1.24',
    androidPlatform: false,
    setting: {
      es6: true,
    },
  })
  console.log(uploadResult)
})()
```

#### 参数

同时支持传入 [ci.upload](#上传) 的参数

| 键               | 类型     | 必填 | 说明                                    |
| ---------------- | -------- | ---- | ------------------------------------- |
| project          | IProject | 是   | [项目对象](#项目对象)                               |
| version          | string   | 是   | 版本号    |
| desc             | string   | 否   | 版本描述  |
| iOSPlatform      | boolean  | 否   | 适用平台 iOS，默认为  true      |
| androidPlatform  | boolean  | 否   | 适用平台 Android，默认为  true  |


#### 返回
| 键         | 类型     | 必填  | 说明                    |
| ----------| -------- | ---- | ----------------------- |
| errmsg    | string   |      |    构建信息   |
| success   | boolean  |   |  构建是否成功 |

## 命令行调用

### 安装
```shell script
npm install -g miniprogram-ci
```

### 查看命令行调用参数
```shell script
miniprogram-ci --help
```

### 示例
```shell script

#preview
miniprogram-ci \
  preview \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  --uv PACKAGE_VERSION \
  -r 1 \
  --use-project-config true \
  --proxy YOUR_PROXY \
  --qrcode-format image \
  --qrcode-output-dest '/tmp/x.jpg' \

#upload

miniprogram-ci \
  upload \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  --uv PACKAGE_VERSION \
  -r 1 \
  --use-project-config true \

#get-compiled-result
miniprogram-ci \
  get-compiled-result \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  --uv PACKAGE_VERSION \
  -r 1 \
  --use-project-config true \
  --save-path ./compiledResult.zip 

#check-code-quality
miniprogram-ci \
  check-code-quality \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  --save-path ./report.json 

#pack-npm
miniprogram-ci \
  pack-npm \
  --pp ./YOUR_PROJECT/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \

#pack-npm-manually
miniprogram-ci \
pack-npm-manually \
--pack-npm-manually-package-json-path PACKAGE_JSON_PATH \
--pack-npm-manually-miniprogram-npm-dist-dir DISTPATH

#cloudbase upload cloudfunction
miniprogram-ci cloud functions upload \
  --pp ./YOUR_PROJECT/ \
  --appid YOUR_APPID \
  --pkp ./private.YOUR_APPID.key \
  --env YOUR_CLOUD_ENV_ID \   
  --name YOUR_CLOUD_FUNCTION_NAME \
  --path ./YOUR_CLOUD_FUNCTION_FOLDER_PATH/ \
  --remote-npm-install true

#cloudbase upload staticstorage
miniprogram-ci cloud staticstorage upload \
  --pp ./YOUR_PROJECT/ \
  --appid YOUR_APPID \
  --pkp ./private.YOUR_APPID.key \
  --env YOUR_CLOUD_ENV_ID \   
  --path ./YOUR_LOCAL_FOLDER_PATH/ \
  --remotePath ./YOUR_REMOTE_FOLDER_PATH/

#cloudbase upload storage
miniprogram-ci cloud storage upload \
  --pp ./YOUR_PROJECT/ \
  --appid YOUR_APPID \
  --pkp ./private.YOUR_APPID.key \
  --env YOUR_CLOUD_ENV_ID \   
  --path ./YOUR_LOCAL_FOLDER_PATH/ \
  --remotePath ./YOUR_REMOTE_FOLDER_PATH/

#cloudbase create cloud container version
miniprogram-ci cloud container create \
  --pp ./YOUR_PROJECT/ \
  --appid YOUR_APPID \
  --pkp ./private.YOUR_APPID.key \
  --env YOUR_CLOUD_ENV_ID \   
  --flow FLOW \
  --cpu CPU_CORES \
  --mem MEM_SIZE \
  --min MINIMUM_COPY \
  --max MAXIMUM_COPY \
  --threshold CPU_SCALING_THRESHOLD \
  --port CONTAINER_PORT \
  --service SERVICE_NAME \
  --remark VERSION_REMARK \
  --containerRoot ./YOUR_VERSION_CODE_PATH

#Proxy
export HTTPS_PROXY = YOUR_PROXY_URL # 可以在shell脚本里声明临时proxy

miniprogram-ci \
  upload \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  --uv PACKAGE_VERSION \
  -r 1 \
  --use-project-config true \
  --proxy YOUR_PROXY_URL # 也可以使用这个参数声明proxy

  #get dev source map
  miniprogram-ci \
  get-dev-source-map \
  --pp ./demo-proj/ \
  --pkp ./private.YOUR_APPID.key \
  --appid YOUR_APPID \
  -r 1 \ # 获取具体哪个robot最近上传的版本的sourceMap
  --source-map-save-path ./sourcemap.zip #保存路径，推荐zip结尾，最后得到的是一个zip包
```


### 编译设置

| 键             | 类型    | 说明                                                        |
| -------------- | ------- | ----------------------------------------------------------- |
| useProjectConfig | boolean | 默认是 false, true 表示默认使用 [project.config.json](https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html) 中的设置，以下字段可以覆盖相应配置     |
| es6            | boolean | 【废弃】和 es7 是一样的效果               |
| es7            | boolean | 对应微信开发者工具 ”将 JS 编译为 es5“     |
| disableUseStrict | boolean | "增强编译" 开启时，是否禁用JS文件严格模式，默认为false          |
| minifyJS       | boolean | 上传时压缩 JS 代码，优先级高于 minify                                          |
| minifyWXML     | boolean | 上传时压缩 WXML 代码，优先级高于 minify                                        |
| minifyWXSS     | boolean | 上传时压缩 WXSS 代码，优先级高于 minify                                        |
| minify         | boolean | 上传时压缩所有代码，对应于微信开发者工具的 "上传时压缩代码" |
| codeProtect    | boolean | 对应于微信开发者工具的 "上传时进行代码保护"                 |
| autoPrefixWXSS | boolean | 对应于微信开发者工具的 "上传时样式自动补全"                 |
| compileWorklet | boolean | 对应于微信开发者工具的 编译 worklet 代码 |

## 第三方平台开发

`miniprogram-ci` 从 `1.0.28` 开始支持第三方平台开发的上传和预览，调用方式与普通开发模式无异。
使用第三方平台开发模式时，应注意：
- 请确保项目中存在正确的 `ext.json`
- 密钥文件是第三方平台绑定的开发小程序 `appid` 的密钥文件
- ip白名单是第三方平台绑定的开发小程序 `appid` 的 ip 白名单
- 调用传入的 `appid` 是第三方平台绑定的开发小程序 `appid`
关于第三方平台开发模式，请参考[这里](https://developers.weixin.qq.com/miniprogram/dev/devtools/ext.html)
