mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-25 02:47:55 +00:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
3f9288e9d3 | |||
0efa745628 | |||
bddad252f7 | |||
a1a0e4f028 | |||
de74baa2ff | |||
6c12f755c5 | |||
f80b8e63b1 | |||
b32514f5af | |||
935f8d2549 | |||
b2de857ef1 | |||
78f1471bf4 | |||
d47abf072d | |||
8502c4db4b | |||
dd2c5c40d7 | |||
d23b253ac5 | |||
0b0e24c9b2 | |||
f9656cbe91 | |||
e174f7db4c | |||
a7a408a5c7 | |||
e5877c7de9 | |||
ecb8b3ae6b | |||
fd20516f69 | |||
5ccee0e4f1 | |||
7bb13a551c | |||
19d287aefc | |||
8d10c1af2a | |||
ebfabdba6a | |||
b0cbe43708 | |||
b7f1469912 | |||
3396ba5a97 | |||
78ffff83bc |
2
.github/actions/publish/action.yml
vendored
2
.github/actions/publish/action.yml
vendored
@ -18,7 +18,7 @@ inputs:
|
|||||||
default: 'accounts+apple@balena.io'
|
default: 'accounts+apple@balena.io'
|
||||||
NODE_VERSION:
|
NODE_VERSION:
|
||||||
type: string
|
type: string
|
||||||
default: '20.x'
|
default: '22.x'
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: 'true'
|
default: 'true'
|
||||||
|
2
.github/actions/test/action.yml
vendored
2
.github/actions/test/action.yml
vendored
@ -15,7 +15,7 @@ inputs:
|
|||||||
# --- custom environment
|
# --- custom environment
|
||||||
NODE_VERSION:
|
NODE_VERSION:
|
||||||
type: string
|
type: string
|
||||||
default: '20.x'
|
default: '22.x'
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: "true"
|
default: "true"
|
||||||
|
@ -1,3 +1,212 @@
|
|||||||
|
- commits:
|
||||||
|
- subject: "Deploy: Limit the submitted error_message of images that fail to build
|
||||||
|
to 1000 characters"
|
||||||
|
hash: bddad252f7cb412a3d417be1d7bd7e4ed9726b8e
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
See: https://balena.fibery.io/Work/Project/re-pitching-API-Limit-size-of-large-fields-975
|
||||||
|
see: https://balena.fibery.io/Work/Project/re-pitching-API-Limit-size-of-large-fields-975
|
||||||
|
author: Thodoris Greasidis
|
||||||
|
nested: []
|
||||||
|
version: 22.1.1
|
||||||
|
title: ""
|
||||||
|
date: 2025-06-19T09:32:52.976Z
|
||||||
|
- commits:
|
||||||
|
- subject: Add support for node 22
|
||||||
|
hash: f80b8e63b1e5b22dd95b034fe14da0a7e3ab4986
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
- subject: Bump etcher-sdk to v10.0.0
|
||||||
|
hash: b32514f5afb4fdd12e4a9dca5e2a1d53e727b434
|
||||||
|
body: |
|
||||||
|
Update balena-device-init from 8.1.3 to 8.1.1
|
||||||
|
Update etcher-sdk from 9.1.4 to 10.0.0
|
||||||
|
Update resin-cli-form from 3.0.0 to 4.0.0
|
||||||
|
Update resin-cli-visuals from 2.0.1 to 3.0.0
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested:
|
||||||
|
- commits:
|
||||||
|
- subject: Drop support to node18 and add support to node 22 & 24
|
||||||
|
hash: 95e577823f642a6c0e500aa29fc150b7807d84f7
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: major
|
||||||
|
change-type: major
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
version: etcher-sdk-10.0.0
|
||||||
|
title: ""
|
||||||
|
date: 2025-06-02T09:12:32.868Z
|
||||||
|
version: 22.1.0
|
||||||
|
title: ""
|
||||||
|
date: 2025-06-09T20:05:08.020Z
|
||||||
|
- commits:
|
||||||
|
- subject: Remove `request` dependency
|
||||||
|
hash: d47abf072ddf1f6529f3d4a14e07436def58df61
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: myarmolinsky
|
||||||
|
nested: []
|
||||||
|
- subject: Replace `request` usage with `got`
|
||||||
|
hash: 8502c4db4bb211a70c682dbc1b85df56f01f2d93
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: myarmolinsky
|
||||||
|
nested: []
|
||||||
|
version: 22.0.6
|
||||||
|
title: ""
|
||||||
|
date: 2025-06-02T12:27:11.068Z
|
||||||
|
- commits:
|
||||||
|
- subject: Bump etcher-sdk to v9.1.4
|
||||||
|
hash: 0b0e24c9b29ef4bcb6a577ca85708171cc2421c7
|
||||||
|
body: |
|
||||||
|
Update etcher-sdk from 9.1.0 to 9.1.4
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested:
|
||||||
|
- commits:
|
||||||
|
- subject: Run `npm audit fix` which should only do non-breaking changes
|
||||||
|
hash: 22aaacc0744e41989706c968c4efc8767d30b7a8
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
version: etcher-sdk-9.1.4
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-29T08:57:28.785Z
|
||||||
|
- commits:
|
||||||
|
- subject: Embed config.json with a fixed timestamp to enable consistent checksums
|
||||||
|
hash: 83e67a4089ec39023c39fe79fe59021237797c85
|
||||||
|
body: >
|
||||||
|
Previously the timestamp was changing each time which meant the
|
||||||
|
|
||||||
|
checksum would be different every time even if the rest of the
|
||||||
|
contents
|
||||||
|
|
||||||
|
were identical. Changing this to a fixed timestamp avoids that
|
||||||
|
change
|
||||||
|
|
||||||
|
such that only the contents matter.
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Pagan Gazzard
|
||||||
|
nested: []
|
||||||
|
version: etcher-sdk-9.1.3
|
||||||
|
title: ""
|
||||||
|
date: 2025-02-17T12:48:33.911Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependency unzip-stream to v0.3.2 [SECURITY]
|
||||||
|
hash: c243e56e4189bee7391943a3325a3c1465c62fd1
|
||||||
|
body: |
|
||||||
|
Update unzip-stream from 0.3.1 to 0.3.2
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Self-hosted Renovate Bot
|
||||||
|
nested: []
|
||||||
|
version: etcher-sdk-9.1.2
|
||||||
|
title: ""
|
||||||
|
date: 2024-10-09T08:52:13.524Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: add EXLOCK flag for windows"
|
||||||
|
hash: 915feeeceff83249f87a6a0a1656986791206136
|
||||||
|
body: |
|
||||||
|
Signed-off-by: Talha Can Havadar <havadartalha@gmail.com>
|
||||||
|
|
||||||
|
run prettier for changed files
|
||||||
|
footer:
|
||||||
|
Signed-off-by: Talha Can Havadar <havadartalha@gmail.com>
|
||||||
|
signed-off-by: Talha Can Havadar <havadartalha@gmail.com>
|
||||||
|
author: Talha Can Havadar
|
||||||
|
nested: []
|
||||||
|
version: etcher-sdk-9.1.1
|
||||||
|
title: ""
|
||||||
|
date: 2024-10-09T08:24:06.706Z
|
||||||
|
version: 22.0.5
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-29T15:47:31.891Z
|
||||||
|
- commits:
|
||||||
|
- subject: "tests: Replace request with got"
|
||||||
|
hash: a7a408a5c7dcf06b770e8df85e250bfed5a09f75
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
- subject: "deploy-legacy: Replace request with got"
|
||||||
|
hash: e5877c7de917e377082328ee8ab0b502593c9719
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
version: 22.0.4
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-29T13:19:01.228Z
|
||||||
|
- commits:
|
||||||
|
- subject: Bump sentry to v9
|
||||||
|
hash: 5ccee0e4f1ce3bae6963630963cbd72c9c738f77
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
version: 22.0.3
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-29T12:09:56.743Z
|
||||||
|
- commits:
|
||||||
|
- subject: Fix balena build to work with --nologs
|
||||||
|
hash: 8d10c1af2a8eddfa146e2d23161c079127eb5546
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Otavio Jacobi
|
||||||
|
nested: []
|
||||||
|
version: 22.0.2
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-28T19:32:15.230Z
|
||||||
|
- commits:
|
||||||
|
- subject: "DeviceAPI: Move away from `request` in favor of BalenaSdk request"
|
||||||
|
hash: 3396ba5a971d2ae16552576d65dff953031f01ee
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: myarmolinsky
|
||||||
|
nested: []
|
||||||
|
- subject: Update `nock` to 14.0.4
|
||||||
|
hash: 78ffff83bca1d87dff19909d39d3d18815754a0e
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: myarmolinsky
|
||||||
|
nested: []
|
||||||
|
version: 22.0.1
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-28T17:00:45.889Z
|
||||||
- commits:
|
- commits:
|
||||||
- subject: Add migration guide to v22
|
- subject: Add migration guide to v22
|
||||||
hash: 45eb0ad4b145d1eed2c30bcd1f9bc9e1a9a2d719
|
hash: 45eb0ad4b145d1eed2c30bcd1f9bc9e1a9a2d719
|
||||||
|
66
CHANGELOG.md
66
CHANGELOG.md
@ -4,6 +4,72 @@ All notable changes to this project will be documented in this file
|
|||||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## 22.1.1 - 2025-06-19
|
||||||
|
|
||||||
|
* Deploy: Limit the submitted error_message of images that fail to build to 1000 characters [Thodoris Greasidis]
|
||||||
|
|
||||||
|
## 22.1.0 - 2025-06-09
|
||||||
|
|
||||||
|
* Add support for node 22 [Otavio Jacobi]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Bump etcher-sdk to v10.0.0 [Otavio Jacobi] </summary>
|
||||||
|
|
||||||
|
> ### etcher-sdk-10.0.0 - 2025-06-02
|
||||||
|
>
|
||||||
|
> * Drop support to node18 and add support to node 22 & 24 [Otavio Jacobi]
|
||||||
|
>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 22.0.6 - 2025-06-02
|
||||||
|
|
||||||
|
* Remove `request` dependency [myarmolinsky]
|
||||||
|
* Replace `request` usage with `got` [myarmolinsky]
|
||||||
|
|
||||||
|
## 22.0.5 - 2025-05-29
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Bump etcher-sdk to v9.1.4 [Otavio Jacobi] </summary>
|
||||||
|
|
||||||
|
> ### etcher-sdk-9.1.4 - 2025-05-29
|
||||||
|
>
|
||||||
|
> * Run `npm audit fix` which should only do non-breaking changes [Otavio Jacobi]
|
||||||
|
>
|
||||||
|
> ### etcher-sdk-9.1.3 - 2025-02-17
|
||||||
|
>
|
||||||
|
> * Embed config.json with a fixed timestamp to enable consistent checksums [Pagan Gazzard]
|
||||||
|
>
|
||||||
|
> ### etcher-sdk-9.1.2 - 2024-10-09
|
||||||
|
>
|
||||||
|
> * Update dependency unzip-stream to v0.3.2 [SECURITY] [Self-hosted Renovate Bot]
|
||||||
|
>
|
||||||
|
> ### etcher-sdk-9.1.1 - 2024-10-09
|
||||||
|
>
|
||||||
|
> * patch: add EXLOCK flag for windows [Talha Can Havadar]
|
||||||
|
>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## 22.0.4 - 2025-05-29
|
||||||
|
|
||||||
|
* tests: Replace request with got [Otavio Jacobi]
|
||||||
|
* deploy-legacy: Replace request with got [Otavio Jacobi]
|
||||||
|
|
||||||
|
## 22.0.3 - 2025-05-29
|
||||||
|
|
||||||
|
* Bump sentry to v9 [Otavio Jacobi]
|
||||||
|
|
||||||
|
## 22.0.2 - 2025-05-28
|
||||||
|
|
||||||
|
* Fix balena build to work with --nologs [Otavio Jacobi]
|
||||||
|
|
||||||
|
## 22.0.1 - 2025-05-28
|
||||||
|
|
||||||
|
* DeviceAPI: Move away from `request` in favor of BalenaSdk request [myarmolinsky]
|
||||||
|
* Update `nock` to 14.0.4 [myarmolinsky]
|
||||||
|
|
||||||
## 22.0.0 - 2025-05-26
|
## 22.0.0 - 2025-05-26
|
||||||
|
|
||||||
* Add migration guide to v22 [Otavio Jacobi]
|
* Add migration guide to v22 [Otavio Jacobi]
|
||||||
|
@ -77,8 +77,8 @@ If you are a Node.js developer, you may wish to install the balena CLI via [npm]
|
|||||||
The npm installation involves building native (platform-specific) binary modules, which require
|
The npm installation involves building native (platform-specific) binary modules, which require
|
||||||
some development tools to be installed first, as follows.
|
some development tools to be installed first, as follows.
|
||||||
|
|
||||||
> **The balena CLI currently requires Node.js version ^20.6.0**
|
> **The balena CLI currently requires Node.js version >=20.6.0**
|
||||||
> **Versions 21 and later are not yet fully supported.**
|
> **Versions 23 and later are not yet fully supported.**
|
||||||
|
|
||||||
### Install development tools
|
### Install development tools
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ some development tools to be installed first, as follows.
|
|||||||
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
|
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
|
||||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||||
$ . ~/.bashrc
|
$ . ~/.bashrc
|
||||||
$ nvm install 20
|
$ nvm install 22
|
||||||
```
|
```
|
||||||
|
|
||||||
The `curl` command line above uses
|
The `curl` command line above uses
|
||||||
@ -105,7 +105,7 @@ recommended.
|
|||||||
```sh
|
```sh
|
||||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||||
$ . ~/.bashrc
|
$ . ~/.bashrc
|
||||||
$ nvm install 20
|
$ nvm install 22
|
||||||
```
|
```
|
||||||
|
|
||||||
#### **Windows** (not WSL)
|
#### **Windows** (not WSL)
|
||||||
@ -113,7 +113,7 @@ $ nvm install 20
|
|||||||
Install:
|
Install:
|
||||||
|
|
||||||
* If you'd like the ability to switch between Node.js versions, install
|
* If you'd like the ability to switch between Node.js versions, install
|
||||||
- Node.js v20 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
- Node.js v22 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||||
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
|
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
|
||||||
instead.
|
instead.
|
||||||
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:
|
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:
|
||||||
|
2238
npm-shrinkwrap.json
generated
2238
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "balena-cli",
|
"name": "balena-cli",
|
||||||
"version": "22.0.0",
|
"version": "22.1.1",
|
||||||
"description": "The official balena Command Line Interface",
|
"description": "The official balena Command Line Interface",
|
||||||
"main": "./build/app.js",
|
"main": "./build/app.js",
|
||||||
"homepage": "https://github.com/balena-io/balena-cli",
|
"homepage": "https://github.com/balena-io/balena-cli",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
"author": "Balena Inc. (https://balena.io/)",
|
"author": "Balena Inc. (https://balena.io/)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.6.0"
|
"node": ">=20.6.0 <23"
|
||||||
},
|
},
|
||||||
"oclif": {
|
"oclif": {
|
||||||
"bin": "balena",
|
"bin": "balena",
|
||||||
@ -124,7 +124,6 @@
|
|||||||
"@types/node-cleanup": "^2.1.2",
|
"@types/node-cleanup": "^2.1.2",
|
||||||
"@types/prettyjson": "^0.0.33",
|
"@types/prettyjson": "^0.0.33",
|
||||||
"@types/progress-stream": "^2.0.2",
|
"@types/progress-stream": "^2.0.2",
|
||||||
"@types/request": "^2.48.7",
|
|
||||||
"@types/rewire": "^2.5.30",
|
"@types/rewire": "^2.5.30",
|
||||||
"@types/rimraf": "^3.0.2",
|
"@types/rimraf": "^3.0.2",
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.9",
|
||||||
@ -154,7 +153,7 @@
|
|||||||
"mocha": "^10.6.0",
|
"mocha": "^10.6.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"mock-require": "^3.0.3",
|
"mock-require": "^3.0.3",
|
||||||
"nock": "^13.2.1",
|
"nock": "^14.0.4",
|
||||||
"oclif": "^4.17.0",
|
"oclif": "^4.17.0",
|
||||||
"rewire": "^7.0.0",
|
"rewire": "^7.0.0",
|
||||||
"simple-git": "^3.14.1",
|
"simple-git": "^3.14.1",
|
||||||
@ -169,9 +168,9 @@
|
|||||||
"@balena/env-parsing": "^1.1.8",
|
"@balena/env-parsing": "^1.1.8",
|
||||||
"@balena/es-version": "^1.0.1",
|
"@balena/es-version": "^1.0.1",
|
||||||
"@oclif/core": "^4.1.0",
|
"@oclif/core": "^4.1.0",
|
||||||
"@sentry/node": "^6.16.1",
|
"@sentry/node": "^9.0.0",
|
||||||
"balena-config-json": "^4.2.7",
|
"balena-config-json": "^4.2.7",
|
||||||
"balena-device-init": "^8.1.3",
|
"balena-device-init": "^8.1.11",
|
||||||
"balena-errors": "^4.7.3",
|
"balena-errors": "^4.7.3",
|
||||||
"balena-image-fs": "^7.5.2",
|
"balena-image-fs": "^7.5.2",
|
||||||
"balena-preload": "^18.0.4",
|
"balena-preload": "^18.0.4",
|
||||||
@ -192,7 +191,7 @@
|
|||||||
"docker-progress": "^5.1.3",
|
"docker-progress": "^5.1.3",
|
||||||
"dockerode": "^4.0.5",
|
"dockerode": "^4.0.5",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
"etcher-sdk": "9.1.0",
|
"etcher-sdk": "^10.0.0",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"fast-boot2": "^1.1.0",
|
"fast-boot2": "^1.1.0",
|
||||||
"fast-levenshtein": "^3.0.0",
|
"fast-levenshtein": "^3.0.0",
|
||||||
@ -220,9 +219,8 @@
|
|||||||
"prettyjson": "^1.2.5",
|
"prettyjson": "^1.2.5",
|
||||||
"progress-stream": "^2.0.0",
|
"progress-stream": "^2.0.0",
|
||||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||||
"request": "^2.88.2",
|
"resin-cli-form": "^4.0.0",
|
||||||
"resin-cli-form": "^3.0.0",
|
"resin-cli-visuals": "^3.0.0",
|
||||||
"resin-cli-visuals": "^2.0.1",
|
|
||||||
"resin-doodles": "^0.2.0",
|
"resin-doodles": "^0.2.0",
|
||||||
"resin-stream-logger": "^0.1.2",
|
"resin-stream-logger": "^0.1.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@ -250,6 +248,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2025-05-26T13:53:37.362Z"
|
"publishedAt": "2025-06-19T09:32:53.877Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/app.ts
12
src/app.ts
@ -34,18 +34,14 @@ export const setupSentry = onceAsync(async () => {
|
|||||||
const config = await import('./config');
|
const config = await import('./config');
|
||||||
const Sentry = await import('@sentry/node');
|
const Sentry = await import('@sentry/node');
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
autoSessionTracking: false,
|
|
||||||
dsn: config.sentryDsn,
|
dsn: config.sentryDsn,
|
||||||
release: packageJSON.version,
|
release: packageJSON.version,
|
||||||
});
|
});
|
||||||
Sentry.configureScope((scope) => {
|
Sentry.getCurrentScope().setExtras({
|
||||||
scope.setExtras({
|
is_pkg: !!(process as any).pkg,
|
||||||
is_pkg: !!(process as any).pkg,
|
node_version: process.version,
|
||||||
node_version: process.version,
|
platform: process.platform,
|
||||||
platform: process.platform,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return Sentry.getCurrentHub();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
async function checkNodeVersion() {
|
async function checkNodeVersion() {
|
||||||
|
@ -38,11 +38,11 @@ import { stripIndent } from './utils/lazy';
|
|||||||
export async function trackCommand(commandSignature: string) {
|
export async function trackCommand(commandSignature: string) {
|
||||||
try {
|
try {
|
||||||
let Sentry: typeof import('@sentry/node');
|
let Sentry: typeof import('@sentry/node');
|
||||||
|
let scope: import('@sentry/node').Scope;
|
||||||
if (!process.env.BALENARC_NO_SENTRY) {
|
if (!process.env.BALENARC_NO_SENTRY) {
|
||||||
Sentry = await import('@sentry/node');
|
Sentry = await import('@sentry/node');
|
||||||
Sentry.configureScope((scope) => {
|
scope = Sentry.getCurrentScope();
|
||||||
scope.setExtra('command', commandSignature);
|
scope.setExtra('command', commandSignature);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const { getCachedUsername } = await import('./utils/bootstrap');
|
const { getCachedUsername } = await import('./utils/bootstrap');
|
||||||
let username: string | undefined;
|
let username: string | undefined;
|
||||||
@ -52,11 +52,9 @@ export async function trackCommand(commandSignature: string) {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
if (!process.env.BALENARC_NO_SENTRY) {
|
if (!process.env.BALENARC_NO_SENTRY) {
|
||||||
Sentry!.configureScope((scope) => {
|
scope!.setUser({
|
||||||
scope.setUser({
|
id: username,
|
||||||
id: username,
|
username,
|
||||||
username,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -1322,6 +1322,9 @@ async function pushAndUpdateServiceImages(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error messages are limited to 300KB characters in the API, so we truncate longer ones.
|
||||||
|
const MAX_ERROR_MESSAGE_LENGTH = 300_000;
|
||||||
|
|
||||||
async function pushServiceImages(
|
async function pushServiceImages(
|
||||||
docker: Dockerode,
|
docker: Dockerode,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
@ -1344,23 +1347,34 @@ async function pushServiceImages(
|
|||||||
delete serviceImage.build_log;
|
delete serviceImage.build_log;
|
||||||
}
|
}
|
||||||
|
|
||||||
await releaseMod.updateImage(
|
// These are the only update-able image fields in bC atm, and passing
|
||||||
pineClient,
|
// the whole image object in v7+ would result the allowlist to reject the request.
|
||||||
serviceImage.id,
|
const imagePayload = _.pick(serviceImage, [
|
||||||
// These are the only update-able image fields in bC atm, and passing
|
'end_timestamp',
|
||||||
// the whole image object in v7+ would result the allowlist to reject the request.
|
'project_type',
|
||||||
_.pick(serviceImage, [
|
'error_message',
|
||||||
'end_timestamp',
|
'build_log',
|
||||||
'project_type',
|
'push_timestamp',
|
||||||
'error_message',
|
'status',
|
||||||
'build_log',
|
'content_hash',
|
||||||
'push_timestamp',
|
'dockerfile',
|
||||||
'status',
|
'image_size',
|
||||||
'content_hash',
|
]);
|
||||||
'dockerfile',
|
|
||||||
'image_size',
|
if (
|
||||||
]),
|
typeof imagePayload.error_message === 'string' &&
|
||||||
);
|
imagePayload.error_message.length > MAX_ERROR_MESSAGE_LENGTH
|
||||||
|
) {
|
||||||
|
logger.logDebug(
|
||||||
|
`Truncating error message of image ${serviceImage.is_stored_at__image_location} to ${MAX_ERROR_MESSAGE_LENGTH} characters.`,
|
||||||
|
);
|
||||||
|
imagePayload.error_message = imagePayload.error_message.substring(
|
||||||
|
0,
|
||||||
|
MAX_ERROR_MESSAGE_LENGTH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await releaseMod.updateImage(pineClient, serviceImage.id, imagePayload);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1596,7 +1610,9 @@ function buildProgressAdapter(inline: boolean) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!str.startsWith('Successfully tagged ')) {
|
// We want to keep the regex match instead of startsWith as it also works with buffers
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
|
||||||
|
if (!/^Successfully tagged /.test(str)) {
|
||||||
const match = stepRegex.exec(str);
|
const match = stepRegex.exec(str);
|
||||||
if (match) {
|
if (match) {
|
||||||
step = match[1];
|
step = match[1];
|
||||||
|
@ -19,7 +19,7 @@ import { getVisuals } from './lazy';
|
|||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import type * as Dockerode from 'dockerode';
|
import type * as Dockerode from 'dockerode';
|
||||||
import type Logger = require('./logger');
|
import type Logger = require('./logger');
|
||||||
import type { Request } from 'request';
|
import type got from 'got';
|
||||||
|
|
||||||
const getBuilderPushEndpoint = function (
|
const getBuilderPushEndpoint = function (
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
@ -75,7 +75,10 @@ const showPushProgress = function (message: string) {
|
|||||||
return progressBar;
|
return progressBar;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
const uploadToPromise = (
|
||||||
|
uploadRequest: ReturnType<typeof got.stream.post>,
|
||||||
|
logger: Logger,
|
||||||
|
) =>
|
||||||
new Promise<{ buildId: number }>(function (resolve, reject) {
|
new Promise<{ buildId: number }>(function (resolve, reject) {
|
||||||
uploadRequest.on('error', reject).on('data', function handleMessage(data) {
|
uploadRequest.on('error', reject).on('data', function handleMessage(data) {
|
||||||
let obj;
|
let obj;
|
||||||
@ -106,10 +109,7 @@ const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
const uploadImage = async function (
|
||||||
* @returns {Promise<{ buildId: number }>}
|
|
||||||
*/
|
|
||||||
const uploadImage = function (
|
|
||||||
imageStream: NodeJS.ReadableStream & { length: number },
|
imageStream: NodeJS.ReadableStream & { length: number },
|
||||||
token: string,
|
token: string,
|
||||||
username: string,
|
username: string,
|
||||||
@ -117,10 +117,9 @@ const uploadImage = function (
|
|||||||
appName: string,
|
appName: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
): Promise<{ buildId: number }> {
|
): Promise<{ buildId: number }> {
|
||||||
const request = require('request') as typeof import('request');
|
const { default: got } = await import('got');
|
||||||
const progressStream =
|
const progressStream = await import('progress-stream');
|
||||||
require('progress-stream') as typeof import('progress-stream');
|
const zlib = await import('zlib');
|
||||||
const zlib = require('zlib') as typeof import('zlib');
|
|
||||||
|
|
||||||
// Need to strip off the newline
|
// Need to strip off the newline
|
||||||
const progressMessage = logger
|
const progressMessage = logger
|
||||||
@ -141,25 +140,26 @@ const uploadImage = function (
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const uploadRequest = request.post({
|
const uploadRequest = got.stream.post(
|
||||||
url: getBuilderPushEndpoint(url, username, appName),
|
getBuilderPushEndpoint(url, username, appName),
|
||||||
headers: {
|
{
|
||||||
'Content-Encoding': 'gzip',
|
headers: {
|
||||||
|
'Content-Encoding': 'gzip',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: streamWithProgress.pipe(
|
||||||
|
zlib.createGzip({
|
||||||
|
level: 6,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
throwHttpErrors: false,
|
||||||
},
|
},
|
||||||
auth: {
|
);
|
||||||
bearer: token,
|
|
||||||
},
|
|
||||||
body: streamWithProgress.pipe(
|
|
||||||
zlib.createGzip({
|
|
||||||
level: 6,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return uploadToPromise(uploadRequest, logger);
|
return uploadToPromise(uploadRequest, logger);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadLogs = function (
|
const uploadLogs = async function (
|
||||||
logs: string,
|
logs: string,
|
||||||
token: string,
|
token: string,
|
||||||
url: string,
|
url: string,
|
||||||
@ -167,14 +167,14 @@ const uploadLogs = function (
|
|||||||
username: string,
|
username: string,
|
||||||
appName: string,
|
appName: string,
|
||||||
) {
|
) {
|
||||||
const request = require('request') as typeof import('request');
|
const { default: got } = await import('got');
|
||||||
return request.post({
|
return got.post(getBuilderLogPushEndpoint(url, buildId, username, appName), {
|
||||||
json: true,
|
headers: {
|
||||||
url: getBuilderLogPushEndpoint(url, buildId, username, appName),
|
Authorization: `Bearer ${token}`,
|
||||||
auth: {
|
|
||||||
bearer: token,
|
|
||||||
},
|
},
|
||||||
body: Buffer.from(logs),
|
body: Buffer.from(logs),
|
||||||
|
responseType: 'json',
|
||||||
|
throwHttpErrors: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,7 +232,7 @@ export const deployLegacy = async function (
|
|||||||
username,
|
username,
|
||||||
appName,
|
appName,
|
||||||
]);
|
]);
|
||||||
uploadLogs(...args);
|
await uploadLogs(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildId;
|
return buildId;
|
||||||
|
@ -15,12 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as request from 'request';
|
|
||||||
import type * as Stream from 'stream';
|
|
||||||
|
|
||||||
import { retry } from '../helpers';
|
import { retry } from '../helpers';
|
||||||
import Logger = require('../logger');
|
import Logger = require('../logger');
|
||||||
import * as ApiErrors from './errors';
|
import * as ApiErrors from './errors';
|
||||||
|
import { getBalenaSdk } from '../lazy';
|
||||||
|
import type { BalenaSDK } from 'balena-sdk';
|
||||||
|
|
||||||
export interface DeviceResponse {
|
export interface DeviceResponse {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@ -80,9 +80,9 @@ export class DeviceAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Either return nothing, or throw an error with the info
|
// Either return nothing, or throw an error with the info
|
||||||
public async setTargetState(state: any): Promise<void> {
|
public async setTargetState(state: Record<string, any>) {
|
||||||
const url = this.getUrlForAction('setTargetState');
|
const url = this.getUrlForAction('setTargetState');
|
||||||
return DeviceAPI.promisifiedRequest(
|
await DeviceAPI.sendRequest(
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url,
|
url,
|
||||||
@ -96,37 +96,37 @@ export class DeviceAPI {
|
|||||||
public async getTargetState() {
|
public async getTargetState() {
|
||||||
const url = this.getUrlForAction('getTargetState');
|
const url = this.getUrlForAction('getTargetState');
|
||||||
|
|
||||||
return DeviceAPI.promisifiedRequest(
|
return await DeviceAPI.sendRequest(
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
json: true,
|
json: true,
|
||||||
},
|
},
|
||||||
this.logger,
|
this.logger,
|
||||||
).then((body) => {
|
).then(({ state }: { state: Record<string, any> }) => {
|
||||||
return body.state;
|
return state;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDeviceInformation(): Promise<DeviceInfo> {
|
public async getDeviceInformation() {
|
||||||
const url = this.getUrlForAction('getDeviceInformation');
|
const url = this.getUrlForAction('getDeviceInformation');
|
||||||
|
|
||||||
return DeviceAPI.promisifiedRequest(
|
return await DeviceAPI.sendRequest(
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
json: true,
|
json: true,
|
||||||
},
|
},
|
||||||
this.logger,
|
this.logger,
|
||||||
).then((body) => {
|
).then(({ info }: { info: DeviceInfo }) => {
|
||||||
return body.info;
|
return info;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getContainerId(serviceName: string): Promise<string> {
|
public async getContainerId(serviceName: string): Promise<string> {
|
||||||
const url = this.getUrlForAction('containerId');
|
const url = this.getUrlForAction('containerId');
|
||||||
|
|
||||||
const body = await DeviceAPI.promisifiedRequest(
|
const body = await DeviceAPI.sendRequest(
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
@ -146,10 +146,10 @@ export class DeviceAPI {
|
|||||||
return body.containerId;
|
return body.containerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ping(): Promise<void> {
|
public async ping() {
|
||||||
const url = this.getUrlForAction('ping');
|
const url = this.getUrlForAction('ping');
|
||||||
|
|
||||||
return DeviceAPI.promisifiedRequest(
|
await DeviceAPI.sendRequest(
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
@ -158,10 +158,10 @@ export class DeviceAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVersion(): Promise<string> {
|
public async getVersion(): Promise<string> {
|
||||||
const url = this.getUrlForAction('version');
|
const url = this.getUrlForAction('version');
|
||||||
|
|
||||||
return DeviceAPI.promisifiedRequest({
|
return await DeviceAPI.sendRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
json: true,
|
json: true,
|
||||||
@ -176,10 +176,10 @@ export class DeviceAPI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStatus(): Promise<Status> {
|
public async getStatus() {
|
||||||
const url = this.getUrlForAction('status');
|
const url = this.getUrlForAction('status');
|
||||||
|
|
||||||
return DeviceAPI.promisifiedRequest({
|
return await DeviceAPI.sendRequest({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
json: true,
|
json: true,
|
||||||
@ -194,96 +194,60 @@ export class DeviceAPI {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLogStream(): Promise<Stream.Readable> {
|
public async getLogStream() {
|
||||||
const url = this.getUrlForAction('logs');
|
const url = this.getUrlForAction('logs');
|
||||||
|
const sdk = getBalenaSdk();
|
||||||
|
|
||||||
// Don't use the promisified version here as we want to stream the output
|
const stream = await sdk.request.stream({ url });
|
||||||
return new Promise((resolve, reject) => {
|
stream.on('response', (res) => {
|
||||||
const req = request.get(url);
|
if (res.statusCode !== 200) {
|
||||||
|
throw new ApiErrors.DeviceAPIError(
|
||||||
req.on('error', reject).on('response', (res) => {
|
'Non-200 response from log streaming endpoint',
|
||||||
if (res.statusCode !== 200) {
|
);
|
||||||
reject(
|
}
|
||||||
new ApiErrors.DeviceAPIError(
|
res.socket.setKeepAlive(true, 1000);
|
||||||
'Non-200 response from log streaming endpoint',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
res.socket.setKeepAlive(true, 1000);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error as Error);
|
|
||||||
}
|
|
||||||
resolve(res);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUrlForAction(action: keyof typeof deviceEndpoints): string {
|
private getUrlForAction(action: keyof typeof deviceEndpoints) {
|
||||||
return `${this.deviceAddress}${deviceEndpoints[action]}`;
|
return `${this.deviceAddress}${deviceEndpoints[action]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper method for promisifying general (non-streaming) requests. Streaming
|
// A helper method for promisifying general (non-streaming) requests. Streaming
|
||||||
// requests should use a seperate setup
|
// requests should use a seperate setup
|
||||||
private static async promisifiedRequest<
|
private static async sendRequest(
|
||||||
T extends Parameters<typeof request>[0],
|
opts: Parameters<BalenaSDK['request']['send']>[number],
|
||||||
>(opts: T, logger?: Logger): Promise<any> {
|
logger?: Logger,
|
||||||
interface ObjectWithUrl {
|
) {
|
||||||
url?: string;
|
if (logger != null && opts.url != null) {
|
||||||
}
|
logger.logDebug(`Sending request to ${opts.url}`);
|
||||||
|
|
||||||
if (logger != null) {
|
|
||||||
let url: string | null = null;
|
|
||||||
if (_.isObject(opts) && (opts as ObjectWithUrl).url != null) {
|
|
||||||
// the `as string` shouldn't be necessary, but the type system
|
|
||||||
// is getting a little confused
|
|
||||||
url = (opts as ObjectWithUrl).url!;
|
|
||||||
} else if (typeof opts === 'string') {
|
|
||||||
url = opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url != null) {
|
|
||||||
logger.logDebug(`Sending request to ${url}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sdk = getBalenaSdk();
|
||||||
const doRequest = async () => {
|
const doRequest = async () => {
|
||||||
return await new Promise((resolve, reject) => {
|
const response = await sdk.request.send(opts);
|
||||||
return request(opts, (err, response, body) => {
|
const bodyError =
|
||||||
if (err) {
|
typeof response.body === 'string'
|
||||||
reject(err as Error);
|
? response.body
|
||||||
return;
|
: response.body.message;
|
||||||
}
|
switch (response.statusCode) {
|
||||||
switch (response.statusCode) {
|
case 200:
|
||||||
case 200: {
|
return response.body;
|
||||||
resolve(body);
|
case 400:
|
||||||
return;
|
throw new ApiErrors.BadRequestDeviceAPIError(bodyError);
|
||||||
}
|
case 503:
|
||||||
case 400: {
|
throw new ApiErrors.ServiceUnavailableAPIError(bodyError);
|
||||||
reject(new ApiErrors.BadRequestDeviceAPIError(body.message));
|
default:
|
||||||
return;
|
new ApiErrors.DeviceAPIError(bodyError);
|
||||||
}
|
}
|
||||||
case 503: {
|
|
||||||
reject(new ApiErrors.ServiceUnavailableAPIError(body.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
reject(new ApiErrors.DeviceAPIError(body.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return await retry({
|
return await retry({
|
||||||
func: doRequest,
|
func: doRequest,
|
||||||
initialDelayMs: 2000,
|
initialDelayMs: 2000,
|
||||||
maxAttempts: 6,
|
maxAttempts: 6,
|
||||||
label: `Supervisor API (${opts.method} ${(opts as any).url})`,
|
label: `Supervisor API (${opts.method} ${opts.url})`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DeviceAPI;
|
|
||||||
|
@ -603,11 +603,11 @@ function getImageNameFromTask(task: BuildTask): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateTargetState(
|
export function generateTargetState(
|
||||||
currentTargetState: any,
|
currentTargetState: Record<string, any>,
|
||||||
composition: Composition,
|
composition: Composition,
|
||||||
buildTasks: BuildTask[],
|
buildTasks: BuildTask[],
|
||||||
env: ParsedEnvironment,
|
env: ParsedEnvironment,
|
||||||
): any {
|
) {
|
||||||
const keyedBuildTasks = _.keyBy(buildTasks, 'serviceName');
|
const keyedBuildTasks = _.keyBy(buildTasks, 'serviceName');
|
||||||
|
|
||||||
const services: { [serviceId: string]: any } = {};
|
const services: { [serviceId: string]: any } = {};
|
||||||
|
@ -28,7 +28,7 @@ import { instanceOf } from '../../errors';
|
|||||||
import Logger = require('../logger');
|
import Logger = require('../logger');
|
||||||
|
|
||||||
import { Dockerfile } from 'livepush';
|
import { Dockerfile } from 'livepush';
|
||||||
import type DeviceAPI from './api';
|
import type { DeviceAPI } from './api';
|
||||||
import type { DeviceInfo, Status } from './api';
|
import type { DeviceInfo, Status } from './api';
|
||||||
import type { DeviceDeployOptions } from './deploy';
|
import type { DeviceDeployOptions } from './deploy';
|
||||||
import { generateTargetState, rebuildSingleTask } from './deploy';
|
import { generateTargetState, rebuildSingleTask } from './deploy';
|
||||||
|
@ -94,7 +94,7 @@ async function installQemu(arch: string, qemuPath: string) {
|
|||||||
const urlVersion = encodeURIComponent(QEMU_VERSION);
|
const urlVersion = encodeURIComponent(QEMU_VERSION);
|
||||||
const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`;
|
const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`;
|
||||||
|
|
||||||
const request = await import('request');
|
const { default: got } = await import('got');
|
||||||
const fs = await import('fs');
|
const fs = await import('fs');
|
||||||
const zlib = await import('zlib');
|
const zlib = await import('zlib');
|
||||||
const tar = await import('tar-stream');
|
const tar = await import('tar-stream');
|
||||||
@ -117,7 +117,8 @@ async function installQemu(arch: string, qemuPath: string) {
|
|||||||
reject(err as Error);
|
reject(err as Error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
request(qemuUrl)
|
got.stream
|
||||||
|
.get(qemuUrl)
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.pipe(zlib.createGunzip())
|
.pipe(zlib.createGunzip())
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
|
@ -16,7 +16,8 @@ limitations under the License.
|
|||||||
import type { BalenaSDK } from 'balena-sdk';
|
import type { BalenaSDK } from 'balena-sdk';
|
||||||
import * as JSONStream from 'JSONStream';
|
import * as JSONStream from 'JSONStream';
|
||||||
import * as readline from 'readline';
|
import * as readline from 'readline';
|
||||||
import * as request from 'request';
|
import type { PlainResponse } from 'got';
|
||||||
|
import type got from 'got';
|
||||||
import type { RegistrySecrets } from '@balena/compose/dist/multibuild';
|
import type { RegistrySecrets } from '@balena/compose/dist/multibuild';
|
||||||
import type * as Stream from 'stream';
|
import type * as Stream from 'stream';
|
||||||
import streamToPromise = require('stream-to-promise');
|
import streamToPromise = require('stream-to-promise');
|
||||||
@ -119,7 +120,7 @@ export async function startRemoteBuild(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
buildRequest.abort();
|
buildRequest.destroy();
|
||||||
const sigintErr = new SIGINTError('Build aborted on SIGINT signal');
|
const sigintErr = new SIGINTError('Build aborted on SIGINT signal');
|
||||||
sigintErr.code = 'SIGINT';
|
sigintErr.code = 'SIGINT';
|
||||||
stream.emit('error', sigintErr);
|
stream.emit('error', sigintErr);
|
||||||
@ -337,32 +338,29 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
|||||||
/**
|
/**
|
||||||
* Initiate a POST HTTP request to the remote builder and add some event
|
* Initiate a POST HTTP request to the remote builder and add some event
|
||||||
* listeners.
|
* listeners.
|
||||||
*
|
|
||||||
* ¡! Note: this function must be synchronous because of a bug in the `request`
|
|
||||||
* library that requires the following two steps to take place in the same
|
|
||||||
* iteration of Node's event loop: (1) adding a listener for the 'response'
|
|
||||||
* event and (2) calling request.pipe():
|
|
||||||
* https://github.com/request/request/issues/887
|
|
||||||
*/
|
*/
|
||||||
function createRemoteBuildRequest(
|
async function createRemoteBuildRequest(
|
||||||
build: RemoteBuild,
|
build: RemoteBuild,
|
||||||
tarStream: Stream.Readable,
|
tarStream: Stream.Readable,
|
||||||
builderUrl: string,
|
builderUrl: string,
|
||||||
onError: (error: Error) => void,
|
onError: (error: Error) => void,
|
||||||
): request.Request {
|
) {
|
||||||
const zlib = require('zlib') as typeof import('zlib');
|
const { default: got } = await import('got');
|
||||||
|
const zlib = await import('zlib');
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
console.error(`[debug] Connecting to builder at ${builderUrl}`);
|
console.error(`[debug] Connecting to builder at ${builderUrl}`);
|
||||||
}
|
}
|
||||||
return request
|
return got.stream
|
||||||
.post({
|
.post(builderUrl, {
|
||||||
url: builderUrl,
|
headers: {
|
||||||
auth: { bearer: build.auth },
|
Authorization: `Bearer ${build.auth}`,
|
||||||
headers: { 'Content-Encoding': 'gzip' },
|
'Content-Encoding': 'gzip',
|
||||||
|
},
|
||||||
body: tarStream.pipe(zlib.createGzip({ level: 6 })),
|
body: tarStream.pipe(zlib.createGzip({ level: 6 })),
|
||||||
|
throwHttpErrors: false,
|
||||||
})
|
})
|
||||||
.once('error', onError) // `.once` because the handler re-emits
|
.once('error', onError) // `.once` because the handler re-emits
|
||||||
.once('response', (response: request.RequestResponse) => {
|
.once('response', (response: PlainResponse) => {
|
||||||
if (response.statusCode >= 100 && response.statusCode < 400) {
|
if (response.statusCode >= 100 && response.statusCode < 400) {
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
console.error(
|
console.error(
|
||||||
@ -374,8 +372,8 @@ function createRemoteBuildRequest(
|
|||||||
'Remote builder responded with HTTP error:',
|
'Remote builder responded with HTTP error:',
|
||||||
`${response.statusCode} ${response.statusMessage}`,
|
`${response.statusCode} ${response.statusMessage}`,
|
||||||
];
|
];
|
||||||
if (response.body) {
|
if (response.rawBody) {
|
||||||
msgArr.push(response.body);
|
msgArr.push(response.rawBody.toString());
|
||||||
}
|
}
|
||||||
onError(new ExpectedError(msgArr.join('\n')));
|
onError(new ExpectedError(msgArr.join('\n')));
|
||||||
}
|
}
|
||||||
@ -384,7 +382,7 @@ function createRemoteBuildRequest(
|
|||||||
|
|
||||||
async function getRemoteBuildStream(
|
async function getRemoteBuildStream(
|
||||||
build: RemoteBuild,
|
build: RemoteBuild,
|
||||||
): Promise<[request.Request, Stream.Stream]> {
|
): Promise<[ReturnType<typeof got.stream.post>, Stream.Stream]> {
|
||||||
const builderUrl = await getBuilderEndpoint(
|
const builderUrl = await getBuilderEndpoint(
|
||||||
build.baseUrl,
|
build.baseUrl,
|
||||||
build.appSlug,
|
build.appSlug,
|
||||||
@ -412,7 +410,7 @@ async function getRemoteBuildStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tarStream = await getTarStream(build);
|
const tarStream = await getTarStream(build);
|
||||||
const buildRequest = createRemoteBuildRequest(
|
const buildRequest = await createRemoteBuildRequest(
|
||||||
build,
|
build,
|
||||||
tarStream,
|
tarStream,
|
||||||
builderUrl,
|
builderUrl,
|
||||||
|
@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised';
|
|||||||
import * as ejs from 'ejs';
|
import * as ejs from 'ejs';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as request from 'request';
|
import got from 'got';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
|
|
||||||
import { LoginServer } from '../../build/auth/server';
|
import { LoginServer } from '../../build/auth/server';
|
||||||
@ -61,38 +61,30 @@ describe('Login server:', function () {
|
|||||||
server.shutdown();
|
server.shutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function testLogin(opt: {
|
async function testLogin({
|
||||||
|
verb = 'post',
|
||||||
|
...opt
|
||||||
|
}: {
|
||||||
expectedBody: string;
|
expectedBody: string;
|
||||||
expectedErrorMsg?: string;
|
expectedErrorMsg?: string;
|
||||||
expectedStatusCode: number;
|
expectedStatusCode: number;
|
||||||
expectedToken: string;
|
expectedToken: string;
|
||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
verb?: string;
|
verb?: 'post' | 'put';
|
||||||
}) {
|
}) {
|
||||||
opt.urlPath = opt.urlPath ?? addr.urlPath;
|
opt.urlPath = opt.urlPath ?? addr.urlPath;
|
||||||
const post = opt.verb
|
const res = await got[verb](
|
||||||
? ((request as any)[opt.verb] as typeof request.post)
|
`http://${addr.host}:${addr.port}${opt.urlPath}`,
|
||||||
: request.post;
|
{
|
||||||
await new Promise<void>((resolve, reject) => {
|
form: {
|
||||||
post(
|
token: opt.expectedToken,
|
||||||
`http://${addr.host}:${addr.port}${opt.urlPath}`,
|
|
||||||
{
|
|
||||||
form: {
|
|
||||||
token: opt.expectedToken,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
function (error, response, body) {
|
throwHttpErrors: false,
|
||||||
try {
|
},
|
||||||
expect(error).to.not.exist;
|
);
|
||||||
expect(response.statusCode).to.equal(opt.expectedStatusCode);
|
|
||||||
expect(body).to.equal(opt.expectedBody);
|
expect(res.body).to.equal(opt.expectedBody);
|
||||||
resolve();
|
expect(res.statusCode).to.equal(opt.expectedStatusCode);
|
||||||
} catch (err) {
|
|
||||||
reject(err as Error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = await server.awaitForToken();
|
const token = await server.awaitForToken();
|
||||||
@ -127,7 +119,7 @@ describe('Login server:', function () {
|
|||||||
expectedStatusCode: 404,
|
expectedStatusCode: 404,
|
||||||
expectedToken: tokens.johndoe.token,
|
expectedToken: tokens.johndoe.token,
|
||||||
expectedErrorMsg: 'Unknown path or verb',
|
expectedErrorMsg: 'Unknown path or verb',
|
||||||
verb: 'get',
|
verb: 'put',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -442,6 +442,93 @@ describe('balena build', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create the expected tar stream (docker-compose --nologs)', async () => {
|
||||||
|
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
||||||
|
const service1Dockerfile = (
|
||||||
|
await fs.readFile(
|
||||||
|
path.join(projectPath, 'service1', 'Dockerfile.template'),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
).replace('%%BALENA_MACHINE_NAME%%', 'nuc');
|
||||||
|
const expectedFilesByService: ExpectedTarStreamFilesByService = {
|
||||||
|
service1: {
|
||||||
|
Dockerfile: {
|
||||||
|
contents: service1Dockerfile,
|
||||||
|
fileSize: service1Dockerfile.length,
|
||||||
|
type: 'file',
|
||||||
|
},
|
||||||
|
'Dockerfile.template': { fileSize: 144, type: 'file' },
|
||||||
|
'file1.sh': { fileSize: 12, type: 'file' },
|
||||||
|
},
|
||||||
|
service2: {
|
||||||
|
'Dockerfile-alt': { fileSize: 13, type: 'file' },
|
||||||
|
'file2-crlf.sh': {
|
||||||
|
fileSize: isWindows ? 12 : 14,
|
||||||
|
testStream: isWindows ? expectStreamNoCRLF : undefined,
|
||||||
|
type: 'file',
|
||||||
|
},
|
||||||
|
'src/file1.sh': { fileSize: 12, type: 'file' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const responseFilename = 'build-POST.json';
|
||||||
|
const responseBody = await fs.readFile(
|
||||||
|
path.join(dockerResponsePath, responseFilename),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
const expectedQueryParamsByService = {
|
||||||
|
service1: Object.entries(
|
||||||
|
_.merge({}, commonComposeQueryParams, {
|
||||||
|
buildargs: {
|
||||||
|
COMPOSE_ARG: 'A',
|
||||||
|
barg: 'b',
|
||||||
|
SERVICE1_VAR: 'This is a service specific variable',
|
||||||
|
},
|
||||||
|
cachefrom: ['my/img1', 'my/img2'],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
service2: Object.entries(
|
||||||
|
_.merge({}, commonComposeQueryParamsIntel, {
|
||||||
|
buildargs: {
|
||||||
|
COMPOSE_ARG: 'A',
|
||||||
|
barg: 'b',
|
||||||
|
},
|
||||||
|
cachefrom: ['my/img1', 'my/img2'],
|
||||||
|
dockerfile: 'Dockerfile-alt',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const expectedResponseLines: string[] = [
|
||||||
|
...commonResponseLines[responseFilename],
|
||||||
|
...getDockerignoreWarn1(
|
||||||
|
[path.join(projectPath, 'service2', '.dockerignore')],
|
||||||
|
'build',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (isWindows) {
|
||||||
|
expectedResponseLines.push(
|
||||||
|
`[Info] Converting line endings CRLF -> LF for file: ${path.join(
|
||||||
|
projectPath,
|
||||||
|
'service2',
|
||||||
|
'file2-crlf.sh',
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
docker.expectGetInfo({});
|
||||||
|
docker.expectGetManifestNucAlpine();
|
||||||
|
docker.expectGetManifestBusybox();
|
||||||
|
await testDockerBuildStream({
|
||||||
|
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 -B COMPOSE_ARG=A -B barg=b --cache-from my/img1,my/img2 --nologs`,
|
||||||
|
dockerMock: docker,
|
||||||
|
expectedFilesByService,
|
||||||
|
expectedQueryParamsByService,
|
||||||
|
expectedResponseLines,
|
||||||
|
projectPath,
|
||||||
|
responseBody,
|
||||||
|
responseCode: 200,
|
||||||
|
services: ['service1', 'service2'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should create the expected tar stream (docker-compose, --multi-dockerignore)', async () => {
|
it('should create the expected tar stream (docker-compose, --multi-dockerignore)', async () => {
|
||||||
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
||||||
const service1Dockerfile = (
|
const service1Dockerfile = (
|
||||||
|
@ -27,7 +27,7 @@ import * as sinon from 'sinon';
|
|||||||
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
||||||
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
|
||||||
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
import { DockerMock, dockerResponsePath } from '../nock/docker-mock';
|
||||||
import { cleanOutput, runCommand, switchSentry } from '../helpers';
|
import { cleanOutput, runCommand } from '../helpers';
|
||||||
import type {
|
import type {
|
||||||
ExpectedTarStreamFiles,
|
ExpectedTarStreamFiles,
|
||||||
ExpectedTarStreamFilesByService,
|
ExpectedTarStreamFilesByService,
|
||||||
@ -262,7 +262,6 @@ describe('balena deploy', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update a release with status="failed" on error (single container)', async () => {
|
it('should update a release with status="failed" on error (single container)', async () => {
|
||||||
let sentryStatus: boolean | undefined;
|
|
||||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||||
const expectedFiles: ExpectedTarStreamFiles = {
|
const expectedFiles: ExpectedTarStreamFiles = {
|
||||||
'src/.dockerignore': { fileSize: 16, type: 'file' },
|
'src/.dockerignore': { fileSize: 16, type: 'file' },
|
||||||
@ -319,7 +318,6 @@ describe('balena deploy', function () {
|
|||||||
api.expectPostImageLabel();
|
api.expectPostImageLabel();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sentryStatus = await switchSentry(false);
|
|
||||||
sinon.stub(process, 'exit');
|
sinon.stub(process, 'exit');
|
||||||
|
|
||||||
await testDockerBuildStream({
|
await testDockerBuildStream({
|
||||||
@ -337,9 +335,8 @@ describe('balena deploy', function () {
|
|||||||
});
|
});
|
||||||
expect(failedImagePatchRequests).to.equal(maxRequestRetries);
|
expect(failedImagePatchRequests).to.equal(maxRequestRetries);
|
||||||
} finally {
|
} finally {
|
||||||
await switchSentry(sentryStatus);
|
// We mock process.exit and need to force cast it to a SinonStub to restore it
|
||||||
// @ts-expect-error claims restore does not exist
|
(process.exit as unknown as sinon.SinonStub).restore();
|
||||||
process.exit.restore();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,8 +21,6 @@ import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
|||||||
import { cleanOutput, runCommand } from '../../helpers';
|
import { cleanOutput, runCommand } from '../../helpers';
|
||||||
import { SupervisorMock } from '../../nock/supervisor-mock';
|
import { SupervisorMock } from '../../nock/supervisor-mock';
|
||||||
|
|
||||||
const itS = process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? it : it.skip;
|
|
||||||
|
|
||||||
describe('balena device logs', function () {
|
describe('balena device logs', function () {
|
||||||
let api: BalenaAPIMock;
|
let api: BalenaAPIMock;
|
||||||
let supervisor: SupervisorMock;
|
let supervisor: SupervisorMock;
|
||||||
@ -39,10 +37,7 @@ describe('balena device logs', function () {
|
|||||||
supervisor.done();
|
supervisor.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
// skip non-standalone tests because nock's mock socket causes the error:
|
it('should reach the expected endpoints on a local device', async () => {
|
||||||
// "setKeepAliveInterval expects an instance of socket as its first argument"
|
|
||||||
// in utils/device/api.ts: NetKeepalive.setKeepAliveInterval(sock, 5000);
|
|
||||||
itS('should reach the expected endpoints on a local device', async () => {
|
|
||||||
supervisor.expectGetPing();
|
supervisor.expectGetPing();
|
||||||
supervisor.expectGetLogs();
|
supervisor.expectGetLogs();
|
||||||
supervisor.expectGetLogs();
|
supervisor.expectGetLogs();
|
||||||
|
@ -417,15 +417,3 @@ export function deepJsonParse(data: any): any {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function switchSentry(
|
|
||||||
enabled: boolean | undefined,
|
|
||||||
): Promise<boolean | undefined> {
|
|
||||||
const balenaCLI = await import('../build/app');
|
|
||||||
const sentryOpts = (await balenaCLI.setupSentry()).getClient()?.getOptions();
|
|
||||||
if (sentryOpts) {
|
|
||||||
const sentryStatus = sentryOpts.enabled;
|
|
||||||
sentryOpts.enabled = enabled;
|
|
||||||
return sentryStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -41,23 +41,18 @@ export class BuilderMock extends NockMock {
|
|||||||
checkBuildRequestBody: (requestBody: string | Buffer) => Promise<void>;
|
checkBuildRequestBody: (requestBody: string | Buffer) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
this.optPost(/^\/v3\/build($|[(?])/, opts).reply(
|
this.optPost(/^\/v3\/build($|[(?])/, opts).reply(
|
||||||
async function (uri, requestBody, callback) {
|
async function (uri, requestBody) {
|
||||||
let error: Error | null = null;
|
await opts.checkURI(uri);
|
||||||
try {
|
if (typeof requestBody === 'string') {
|
||||||
await opts.checkURI(uri);
|
const gzipped = Buffer.from(requestBody, 'hex');
|
||||||
if (typeof requestBody === 'string') {
|
const gunzipped = await gunzipAsync(gzipped);
|
||||||
const gzipped = Buffer.from(requestBody, 'hex');
|
await opts.checkBuildRequestBody(gunzipped);
|
||||||
const gunzipped = await gunzipAsync(gzipped);
|
return [opts.responseCode, opts.responseBody];
|
||||||
await opts.checkBuildRequestBody(gunzipped);
|
} else {
|
||||||
} else {
|
throw new Error(
|
||||||
throw new Error(
|
`unexpected requestBody type "${typeof requestBody}"`,
|
||||||
`unexpected requestBody type "${typeof requestBody}"`,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error = err;
|
|
||||||
}
|
}
|
||||||
callback(error, [opts.responseCode, opts.responseBody]);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -81,21 +81,14 @@ export class DockerMock extends NockMock {
|
|||||||
this.optPost(
|
this.optPost(
|
||||||
new RegExp(`^/build\\?(|.+&)${qs.stringify({ t: opts.tag })}&`),
|
new RegExp(`^/build\\?(|.+&)${qs.stringify({ t: opts.tag })}&`),
|
||||||
opts,
|
opts,
|
||||||
).reply(async function (uri, requestBody, cb) {
|
).reply(async function (uri, requestBody) {
|
||||||
let error: Error | null = null;
|
await opts.checkURI(uri);
|
||||||
try {
|
if (typeof requestBody === 'string') {
|
||||||
await opts.checkURI(uri);
|
await opts.checkBuildRequestBody(requestBody);
|
||||||
if (typeof requestBody === 'string') {
|
return [opts.responseCode, opts.responseBody];
|
||||||
await opts.checkBuildRequestBody(requestBody);
|
} else {
|
||||||
} else {
|
throw new Error(`unexpected requestBody type "${typeof requestBody}"`);
|
||||||
throw new Error(
|
|
||||||
`unexpected requestBody type "${typeof requestBody}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
error = err;
|
|
||||||
}
|
}
|
||||||
cb(error, [opts.responseCode, opts.responseBody]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ describe('detectEncoding() function', function () {
|
|||||||
it('should correctly detect the encoding of a few selected files', async () => {
|
it('should correctly detect the encoding of a few selected files', async () => {
|
||||||
const sampleBinary = [
|
const sampleBinary = [
|
||||||
'drivelist/build/Release/drivelist.node',
|
'drivelist/build/Release/drivelist.node',
|
||||||
'mountutils/build/Release/MountUtils.node',
|
'mountutils/prebuilds/linux-x64/mountutils.node',
|
||||||
];
|
];
|
||||||
const sampleText = [
|
const sampleText = [
|
||||||
'node_modules/.bin/mocha',
|
'node_modules/.bin/mocha',
|
||||||
|
Reference in New Issue
Block a user