mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
168 Commits
Author | SHA1 | Date | |
---|---|---|---|
81f4aae7d2 | |||
46ab335407 | |||
07cb0cbfcd | |||
a2392dc580 | |||
8b3235ab2b | |||
15dac6f194 | |||
3c93db8449 | |||
9d8df0b781 | |||
bcadbdbed8 | |||
05a96fa60e | |||
2e37536e7a | |||
025c4ef7f2 | |||
ecbc660bf5 | |||
ba1f17d537 | |||
3ab8f7500e | |||
0a25bec010 | |||
01e765e670 | |||
61844f2386 | |||
46aa08c953 | |||
b6c7fb82c3 | |||
fcda09009a | |||
1a6fe1f3de | |||
98e91c0607 | |||
bed2387d83 | |||
50e852acee | |||
da30623e4e | |||
7a46b367a7 | |||
d9651c7393 | |||
e371b1e759 | |||
77cf4af166 | |||
9d197317ca | |||
9a8b0b4a0d | |||
0c62b9ef08 | |||
83a5e7392a | |||
f0c8c37022 | |||
ba26d3204d | |||
d53542975e | |||
632296a271 | |||
3e089fcdb2 | |||
d61c300750 | |||
a0a97c5f40 | |||
165f3b83ca | |||
5bf95300ee | |||
adb460b270 | |||
ca80bd52fe | |||
281f8abb9a | |||
2cf2918d73 | |||
7dfb7474f5 | |||
6ee0b48c9a | |||
bd01fbf90c | |||
cd19845b6b | |||
5545883c3f | |||
75a380b0ba | |||
35fe7c6a58 | |||
69249b3139 | |||
bf897fd56d | |||
150c6e75f5 | |||
e8bc43dc64 | |||
1213689de2 | |||
c1017e8e27 | |||
7ad9e685f6 | |||
c778aaffaf | |||
b98047cacf | |||
03ace6e4b2 | |||
9b4701bcb7 | |||
174312977a | |||
963d9af817 | |||
af5ec51232 | |||
1cd9fbf6a0 | |||
72639e9e59 | |||
447dcc1480 | |||
564716faa7 | |||
3e5b4457c2 | |||
793e70d909 | |||
5761a306be | |||
adff0f2a0a | |||
4ec45a0c43 | |||
ecf4b046b5 | |||
b0cae93ac9 | |||
53b66678d4 | |||
0b9b65ef88 | |||
8a84d9d792 | |||
c535b8e1ea | |||
234fb6cd39 | |||
8714830b48 | |||
0e07b36691 | |||
ba80d3c38c | |||
e65dc82cfe | |||
bc727521c6 | |||
a8c0c884d3 | |||
b11c7157d3 | |||
578de7bcd4 | |||
cfc6b3ce9e | |||
1c7a354fe7 | |||
40a0941ca3 | |||
0ab4760272 | |||
42b2269e81 | |||
c818d846b3 | |||
3328f40416 | |||
58d10c1908 | |||
2fd0ca6a02 | |||
173028fd0d | |||
62d5bf4436 | |||
63a0d19770 | |||
8244636bf2 | |||
6a01fb361c | |||
ca637b3fb6 | |||
006293bd01 | |||
338b5d79d3 | |||
60dd0daae5 | |||
662b8283a6 | |||
cfc866cf41 | |||
e566badfff | |||
69834c417e | |||
8aa9c62afd | |||
4f29e37fe7 | |||
99e8a36bb5 | |||
669cbe227f | |||
e9156d77f1 | |||
767216c842 | |||
d3018f9061 | |||
37c6ad855b | |||
ca97678358 | |||
3bb0036ba8 | |||
ac9e2a9e7e | |||
52e95e6d0a | |||
c5d2aa7eec | |||
683220e303 | |||
44f09b32fa | |||
d1a0660a3d | |||
ee1987f188 | |||
39e9997d9e | |||
97b8c75043 | |||
7cb8349f29 | |||
6063f4c776 | |||
4899d545f1 | |||
115bf6433d | |||
e5ce1ade89 | |||
9c4174ea8a | |||
cf16957195 | |||
4de369ff95 | |||
ac3ebff8ee | |||
76b01d92d3 | |||
19144163ee | |||
535ffccbad | |||
6f5ada9692 | |||
1c7d9255ae | |||
807e6ea2ad | |||
c76f019fd0 | |||
3c2c925eed | |||
14b54be15e | |||
7fb82f7447 | |||
4a5d44a0f1 | |||
1cba0284df | |||
6e4fe229bf | |||
7033075900 | |||
ded268ff3c | |||
a366f0b7eb | |||
507c8a1bfd | |||
1fb46bfa5d | |||
2e115968d5 | |||
83020797b0 | |||
0c4647e980 | |||
a20d2a04a8 | |||
57b0dccc7d | |||
d1e3bdf29a | |||
bdf7fedd7a | |||
c163662f4a |
133
.github/actions/publish/action.yml
vendored
Normal file
133
.github/actions/publish/action.yml
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
---
|
||||
name: package and draft GitHub release
|
||||
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||
inputs:
|
||||
json:
|
||||
description: "JSON stringified object containing all the inputs from the calling workflow"
|
||||
required: true
|
||||
secrets:
|
||||
description: "JSON stringified object containing all the secrets from the calling workflow"
|
||||
required: true
|
||||
variables:
|
||||
description: "JSON stringified object containing all the variables from the calling workflow"
|
||||
required: true
|
||||
|
||||
# --- custom environment
|
||||
XCODE_APP_LOADER_EMAIL:
|
||||
type: string
|
||||
default: "accounts+apple@balena.io"
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '16.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
|
||||
runs:
|
||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Download custom source artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}
|
||||
|
||||
- name: Extract custom source artifact
|
||||
shell: pwsh
|
||||
working-directory: .
|
||||
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Install additional tools
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
choco install yq
|
||||
|
||||
- name: Install additional tools
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
brew install coreutils
|
||||
|
||||
# https://www.electron.build/code-signing.html
|
||||
# https://github.com/Apple-Actions/import-codesign-certs
|
||||
- name: Import Apple code signing certificate
|
||||
if: runner.os == 'macOS'
|
||||
uses: apple-actions/import-codesign-certs@v1
|
||||
with:
|
||||
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||
|
||||
- name: Import Windows code signing certificate
|
||||
if: runner.os == 'Windows'
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
|
||||
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
|
||||
Remove-Item -path ${{ runner.temp }} -include certificate.base64
|
||||
|
||||
Import-PfxCertificate `
|
||||
-FilePath ${{ runner.temp }}/certificate.pfx `
|
||||
-CertStoreLocation Cert:\CurrentUser\My `
|
||||
-Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
||||
|
||||
env:
|
||||
WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||
|
||||
# https://github.com/product-os/scripts/tree/master/shared
|
||||
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
|
||||
- name: Package release
|
||||
shell: bash
|
||||
run: |
|
||||
set -ea
|
||||
|
||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||
|
||||
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
||||
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [[ $runner_os =~ darwin|macos|osx ]]; then
|
||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||
CSC_KEYCHAIN=signing_temp
|
||||
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
|
||||
elif [[ $runner_os =~ windows|win ]]; then
|
||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||
CSC_LINK='${{ runner.temp }}\certificate.pfx'
|
||||
|
||||
# patches/all/oclif.patch
|
||||
MSYSSHELLPATH="$(which bash)"
|
||||
MSYSTEM=MSYS
|
||||
|
||||
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
|
||||
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
|
||||
fi
|
||||
|
||||
npm run package
|
||||
|
||||
find dist -type f -maxdepth 1
|
||||
|
||||
env:
|
||||
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks
|
||||
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
# https://sectigo.com/resource-library/time-stamping-server
|
||||
TIMESTAMP_SERVER: http://timestamp.sectigo.com
|
||||
# Apple notarization (automation/build-bin.ts)
|
||||
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
|
||||
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
|
||||
path: dist
|
||||
retention-days: 1
|
59
.github/actions/test/action.yml
vendored
Normal file
59
.github/actions/test/action.yml
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
name: test release
|
||||
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||
inputs:
|
||||
json:
|
||||
description: "JSON stringified object containing all the inputs from the calling workflow"
|
||||
required: true
|
||||
secrets:
|
||||
description: "JSON stringified object containing all the secrets from the calling workflow"
|
||||
required: true
|
||||
variables:
|
||||
description: "JSON stringified object containing all the variables from the calling workflow"
|
||||
required: true
|
||||
|
||||
# --- custom environment
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '16.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
|
||||
runs:
|
||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||
using: "composite"
|
||||
steps:
|
||||
# https://github.com/actions/setup-node#caching-global-packages-data
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Test release
|
||||
shell: bash
|
||||
run: |
|
||||
set -ea
|
||||
|
||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||
|
||||
if [[ -e package-lock.json ]] || [[ -e npm-shrinkwrap.json ]]; then
|
||||
npm ci
|
||||
else
|
||||
npm i
|
||||
fi
|
||||
|
||||
npm run build
|
||||
npm run test
|
||||
|
||||
- name: Compress custom source
|
||||
shell: pwsh
|
||||
run: tar -acf ${{ runner.temp }}/custom.tgz .
|
||||
|
||||
- name: Upload custom artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}/custom.tgz
|
||||
retention-days: 1
|
29
.github/workflows/flowzone.yml
vendored
Normal file
29
.github/workflows/flowzone.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Flowzone
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
branches: [main, master]
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, closed]
|
||||
branches: [main, master]
|
||||
|
||||
jobs:
|
||||
flowzone:
|
||||
name: Flowzone
|
||||
uses: product-os/flowzone/.github/workflows/flowzone.yml@v4.7.1
|
||||
# prevent duplicate workflow executions for pull_request and pull_request_target
|
||||
if: |
|
||||
(
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event_name == 'pull_request'
|
||||
) || (
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
github.event_name == 'pull_request_target'
|
||||
)
|
||||
secrets: inherit
|
||||
with:
|
||||
custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-12"],["windows-2019"]]'
|
||||
repo_config: true
|
||||
repo_description: "The official balena CLI tool."
|
||||
github_prerelease: true
|
20
.resinci.yml
20
.resinci.yml
@ -1,20 +0,0 @@
|
||||
---
|
||||
npm:
|
||||
platforms:
|
||||
- name: linux
|
||||
os: ubuntu
|
||||
architecture: x86_64
|
||||
node_versions:
|
||||
- "12"
|
||||
- "14"
|
||||
##
|
||||
## Temporarily skip Alpine tests until the following issues are resolved:
|
||||
## * https://github.com/concourse/concourse/issues/7905
|
||||
## * https://github.com/product-os/balena-concourse/issues/631
|
||||
##
|
||||
# - name: linux
|
||||
# os: alpine
|
||||
# architecture: x86_64
|
||||
# node_versions:
|
||||
# - "12"
|
||||
# - "14"
|
File diff suppressed because it is too large
Load Diff
589
CHANGELOG.md
589
CHANGELOG.md
@ -4,6 +4,595 @@ All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 16.2.6 - 2023-05-24
|
||||
|
||||
* device init: Stop fetching unnecessary app fields [Thodoris Greasidis]
|
||||
* device move: Stop fetching unnecessary device & app fields [Thodoris Greasidis]
|
||||
* device register: Stop fetching unnecessary app fields [Thodoris Greasidis]
|
||||
* config generate: Stop fetching unnecessary app fields [Thodoris Greasidis]
|
||||
* devices: Stop fetching unnecessary device fields [Thodoris Greasidis]
|
||||
* fleet rm,restart,rename,purge: Stop fetching unnecessary app fields [Thodoris Greasidis]
|
||||
* push: Stop unnecessarily fetching the application name [Thodoris Greasidis]
|
||||
* ssh,tunnel: Reduce the amount of application fields fetched [Thodoris Greasidis]
|
||||
* orgs: Stop fetching unnecessary org fields [Thodoris Greasidis]
|
||||
* fleet create: Reduce the amount of org fields fetched [Thodoris Greasidis]
|
||||
* Fetch only the app slug when resolving an app by name [Thodoris Greasidis]
|
||||
|
||||
## 16.2.5 - 2023-05-24
|
||||
|
||||
* Fix device int & os initialize failing to initialize the drive list [Thodoris Greasidis]
|
||||
|
||||
## 16.2.4 - 2023-05-23
|
||||
|
||||
* Remove extra request when filling the application selection list [Thodoris Greasidis]
|
||||
|
||||
## 16.2.3 - 2023-05-23
|
||||
|
||||
* Use stricter typings [Thodoris Greasidis]
|
||||
|
||||
## 16.2.2 - 2023-05-23
|
||||
|
||||
* env add: Fix accepting fleet names when setting service vars [Thodoris Greasidis]
|
||||
|
||||
## 16.2.1 - 2023-05-23
|
||||
|
||||
* Update balena-settings-client to 5.0.2 [Thodoris Greasidis]
|
||||
|
||||
<details>
|
||||
<summary> Update balena-preload & balena-image-manager [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-image-manager-9.0.0 - 2023-05-23
|
||||
>
|
||||
> * Drop .hound.yml [Thodoris Greasidis]
|
||||
> * Update dependencies [Thodoris Greasidis]
|
||||
> * Update README.md [Thodoris Greasidis]
|
||||
> * Update to balena-sdk 17.0.0 [Thodoris Greasidis]
|
||||
> * Convert to typescript an emit type declarations [Thodoris Greasidis]
|
||||
> * Require es2019 capable runtime [Thodoris Greasidis]
|
||||
> * Drop support for Node.js v12 & v14 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-image-manager-8.0.3 - 2022-09-26
|
||||
>
|
||||
> * Delete redundant .resinci.yml [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-image-manager-8.0.2 - 2022-09-22
|
||||
>
|
||||
> * Drop the .travis.yml [Thodoris Greasidis]
|
||||
> * Replace balenaCI with flowzone [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-preload-14.0.0 - 2023-05-23
|
||||
>
|
||||
> * Improve typings & source type safety [Thodoris Greasidis]
|
||||
> * Update dev dependencies [Thodoris Greasidis]
|
||||
> * Update to balena-sdk 17.0.0 [Thodoris Greasidis]
|
||||
> * Emit type declaration files [Thodoris Greasidis]
|
||||
> * Switch to es6 module exports [Thodoris Greasidis]
|
||||
> * Require es2019 capable runtime [Thodoris Greasidis]
|
||||
> * Drop support for Node.js v12 & v14 [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update balena-sdk to 17.0.0 [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-sdk-17.0.0 - 2023-05-22
|
||||
>
|
||||
> * Fully type the result of os.getAllOsVersions when providing pine options [Thodoris Greasidis]
|
||||
> * Change all .create() methods to return fully typed results [Thodoris Greasidis]
|
||||
> * Change the pine.post() method to return fully typed results [Thodoris Greasidis]
|
||||
> * Add the imageType property to the os.download method [Thodoris Greasidis]
|
||||
> * Add OS download custom properties [Otávio Jacobi]
|
||||
> * **BREAKING**: Drop the tags.getAll() method from all models [Thodoris Greasidis]
|
||||
> * **BREAKING**: Drop organization.membership.getAll() [Thodoris Greasidis]
|
||||
> * **BREAKING**: Drop application.membership.getAll() [Thodoris Greasidis]
|
||||
> * **BREAKING**: Drop device.getAll() in favor of getAllByOrganization() [Thodoris Greasidis]
|
||||
> * Replace lodash flatten with native flatMap [Thodoris Greasidis]
|
||||
> * Move the sources under the /src folder [Thodoris Greasidis]
|
||||
> * types: Add type checks for the direction of the string $orderby variant [Thodoris Greasidis]
|
||||
> * types: Require a generic param in pine's associated resource helpers [Thodoris Greasidis]
|
||||
> * tests: Convert some async expectations to asyn-await [Thodoris Greasidis]
|
||||
> * types: Rename ODataOptionsWithSelect to ODataOptionsStrict [Thodoris Greasidis]
|
||||
> * types: Rename PineOptionsWithSelect to PineOptionsStrict [Thodoris Greasidis]
|
||||
> * Types: Rename ParamsObjWithSelect to ParamsObjStrict [Thodoris Greasidis]
|
||||
> * release: Rename the `.note()` method to `.setNote()` [Thodoris Greasidis]
|
||||
> * device: Rename the `.note()` method to `.setNote()` [Thodoris Greasidis]
|
||||
> * os.download: Change to accept a single object parameter [Thodoris Greasidis]
|
||||
> * OsVersion: Make `variant` non-nullable to match the Release field [Thodoris Greasidis]
|
||||
> * OsVersion: Drop the formattedVersion property [Thodoris Greasidis]
|
||||
> * OsVersion: Drop the rawVersion property in favor of raw_version [Thodoris Greasidis]
|
||||
> * Drop device.getManifestByApplication() [Thodoris Greasidis]
|
||||
> * Drop device.getManifestBySlug in favor of config.getDeviceTypeManifestBySlug [Thodoris Greasidis]
|
||||
> * Drop support for callbacks [Thodoris Greasidis]
|
||||
> * getWithServiceDetails: Drop the current_gateway_downloads property [Thodoris Greasidis]
|
||||
> * types: Drop the deprecated public_key property from the JWTUser [Thodoris Greasidis]
|
||||
> * Drop the deprecated needsPasswordReset property from the JWTUser [Thodoris Greasidis]
|
||||
> * Bump mockttp to v3.0.0 [Thodoris Greasidis]
|
||||
>
|
||||
> <details>
|
||||
> <summary> Update balena-hup-action-utils to 5.0.0 [Thodoris Greasidis] </summary>
|
||||
>
|
||||
>> #### balena-hup-action-utils-5.0.0 - 2023-04-28
|
||||
>>
|
||||
>> * Re-enable TS-compatibility checks [Thodoris Greasidis]
|
||||
>> * Update Typescript to 5.0.4 [Thodoris Greasidis]
|
||||
>> * Throw a typed HUPActionError for expected errors [Thodoris Greasidis]
|
||||
>> * Change the build target to es6 [Thodoris Greasidis]
|
||||
>> * Drop support for node < v12 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-hup-action-utils-4.1.3 - 2022-09-26
|
||||
>>
|
||||
>> * Delete redundant .resinci.yml [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-hup-action-utils-4.1.2 - 2022-09-22
|
||||
>>
|
||||
>> * Specify node 10 as the minimum supported node engine in the package.json [Thodoris Greasidis]
|
||||
>> * Replace balenaCI with flowzone [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-hup-action-utils-4.1.1 - 2022-04-09
|
||||
>>
|
||||
>> * Bump karma to v6 [Thodoris Greasidis]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
> * Update balena-settings-client to v5.0.0 [Thodoris Greasidis]
|
||||
> * Bump minimum supported Typescript version to v5.0.2 [Thodoris Greasidis]
|
||||
> * **BREAKING**: Drop device.getAllByParentDevice [Thodoris Greasidis]
|
||||
> * **BREAKING**: Drop support for node 12, require es2019 builtin APIs [Thodoris Greasidis]
|
||||
> * **BREAKING**: Stop publishing an unminified browser bundle [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 16.2.0 - 2023-05-19
|
||||
|
||||
* secureboot: Retrieve the OS release & contract in one request [Thodoris Greasidis]
|
||||
* os configure, config generate: Add '--secureBoot' option to opt-in secure boot [Alex Gonzalez]
|
||||
|
||||
<details>
|
||||
<summary> package.json: Update balena-sdk to 16.44.2 [Alex Gonzalez] </summary>
|
||||
|
||||
> ### balena-sdk-16.44.2 - 2023-05-16
|
||||
>
|
||||
> * Update flowzone's macos runner to v12 [Thodoris Greasidis]
|
||||
> * Add device type yocto properties to typings [Otávio Jacobi]
|
||||
> * Optimize getDeviceUrl request in one query [Otávio Jacobi]
|
||||
>
|
||||
> ### balena-sdk-16.44.1 - 2023-05-09
|
||||
>
|
||||
> * Fix device.getAllByOrganization parameter docs [Otávio Jacobi]
|
||||
>
|
||||
> ### balena-sdk-16.44.0 - 2023-04-27
|
||||
>
|
||||
> * Add device.getAllByOrganization() [Thodoris Greasidis]
|
||||
> * Deprecate Device's is_managed_by__device & manages__device properties [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.43.0 - 2023-04-19
|
||||
>
|
||||
> * Add test case DeviceHistory expandable resources [fisehara]
|
||||
> * Make DeviceHistory referenced resources expandable [fisehara]
|
||||
>
|
||||
> ### balena-sdk-16.42.0 - 2023-04-18
|
||||
>
|
||||
> * Add support for pine queries on Concept Type properties [Thodoris Greasidis]
|
||||
> * Properly type Actor properties on resources [fisehara]
|
||||
>
|
||||
> ### balena-sdk-16.41.0 - 2023-04-07
|
||||
>
|
||||
> * Release model: Add support for getting/patching releases by `application` & `rawVersion` pairs [myarmolinsky]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
* flowzone: update custom runs to use macos-12 [Alex Gonzalez]
|
||||
|
||||
## 16.1.0 - 2023-05-16
|
||||
|
||||
* build linux/arm packages [balenaCI]
|
||||
|
||||
## 16.0.0 - 2023-05-16
|
||||
|
||||
* support: Change the printed support expiry date in ISO 8601 UTC format [Thodoris Greasidis]
|
||||
* logs: Change the timestamp format to ISO 8601 UTC [Thodoris Greasidis]
|
||||
* Pin flowzone to v4.7.1 [Felipe Lalanne]
|
||||
* Update etcher-sdk to v8.5.3 [Felipe Lalanne]
|
||||
* Update vercel/pkg to v5.8.1 [Felipe Lalanne]
|
||||
* Update to Node 16 [Felipe Lalanne]
|
||||
|
||||
## 15.2.3 - 2023-05-03
|
||||
|
||||
* Use valid release uuid for local releases [Felipe Lalanne]
|
||||
|
||||
## 15.2.2 - 2023-04-28
|
||||
|
||||
* Remove nvmrc [Felipe Lalanne]
|
||||
|
||||
## 15.2.1 - 2023-04-28
|
||||
|
||||
* Fix tslib going out of sync causing HUP to fail [Thodoris Greasidis]
|
||||
|
||||
## 15.2.0 - 2023-04-05
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Add support for device restarts in open-balena [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-sdk-16.40.0 - 2023-04-05
|
||||
>
|
||||
> * device.reboot: Fix the typings requiring a second argument [Thodoris Greasidis]
|
||||
> * device.restartApplication: Use the supervisor endpoint to issue restarts [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.39.1 - 2023-04-04
|
||||
>
|
||||
> * patch: Split instruction strings on linebreak [Vipul Gupta (@vipulgupta2048)]
|
||||
>
|
||||
> ### balena-sdk-16.39.0 - Invalid date
|
||||
>
|
||||
> * Add `device history` model [fisehara]
|
||||
>
|
||||
> ### balena-sdk-16.38.2 - 2023-03-28
|
||||
>
|
||||
> * Fix credit-bundle jsdocs [Josh Bowling]
|
||||
>
|
||||
> ### balena-sdk-16.38.1 - 2023-03-27
|
||||
>
|
||||
> * Deprecate the device-type.json's instructions field [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.38.0 - 2023-03-21
|
||||
>
|
||||
> * Add aliases for the DT contrast slugs used in getInstructions [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.37.0 - 2023-03-21
|
||||
>
|
||||
> * device-type/getInstructions: Overload to accept the device type contract [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.36.6 - 2023-03-20
|
||||
>
|
||||
> * Update TypeScript to 5.0.2 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.36.5 - 2023-03-16
|
||||
>
|
||||
> * patch: Improve jetsonFlash provisioning partial [Vipul Gupta (@vipulgupta2048)]
|
||||
>
|
||||
> ### balena-sdk-16.36.4 - 2023-03-15
|
||||
>
|
||||
> * Avoid running write operation tests in parallel to support retries [Thodoris Greasidis]
|
||||
> * Retry failing tests twice [Thodoris Greasidis]
|
||||
> * Fix tests per removal of `microservices-starter` application type [myarmolinsky]
|
||||
>
|
||||
> ### balena-sdk-16.36.3 - 2023-02-28
|
||||
>
|
||||
> * models/device-type: Add test for Radxa Zero instructions [Alexandru Costache]
|
||||
> * lib/models: Add radxaFlash protocol for Radxa boards [Alexandru Costache]
|
||||
>
|
||||
> ### balena-sdk-16.36.2 - 2023-02-24
|
||||
>
|
||||
> * tests: Stop using flowzone internal env vars to for skipping npm test [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.36.1 - 2023-02-20
|
||||
>
|
||||
> * Add plan validity date fields [Josh Bowling]
|
||||
>
|
||||
> ### balena-sdk-16.36.0 - 2023-02-16
|
||||
>
|
||||
> * Add contract partial based instruction generation [Micah Halter]
|
||||
>
|
||||
> ### balena-sdk-16.35.0 - 2023-02-10
|
||||
>
|
||||
> * Add `CreditBundle` model [myarmolinsky]
|
||||
>
|
||||
> ### balena-sdk-16.34.0 - 2023-02-09
|
||||
>
|
||||
> * Add configVarInvalidRegex to Config Var typing [Felipe Lalanne]
|
||||
>
|
||||
> ### balena-sdk-16.33.0 - 2023-02-09
|
||||
>
|
||||
> * CurrentServiceWithCommit: Add release `raw_version` to type [myarmolinsky]
|
||||
>
|
||||
> ### balena-sdk-16.32.3 - 2023-02-07
|
||||
>
|
||||
> * Optimize the device.get method [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.32.2 - 2023-02-02
|
||||
>
|
||||
> * Improve pine typings for public resources without id fields [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.32.1 - 2023-01-16
|
||||
>
|
||||
> * Drop no longer used .travis.yml & .hound.yml [Thodoris Greasidis]
|
||||
> * Rerun prettier [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.32.0 - 2023-01-05
|
||||
>
|
||||
> * typings: Add the device.is_frozen field [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.31.2 - 2022-12-20
|
||||
>
|
||||
> * application.create: Deprecate the `parent` option [Thodoris Greasidis]
|
||||
> * Deprecate the device.getAllByParentDevice() method [Thodoris Greasidis]
|
||||
> * Simplify the device.move() checks [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.31.1 - 2022-12-17
|
||||
>
|
||||
> * Replace appveyor with flowzone [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.31.0 - 2022-12-16
|
||||
>
|
||||
> * Add `updateAccountInfo` method to billing model for updating billing account info [myarmolinsky]
|
||||
>
|
||||
> ### balena-sdk-16.30.2 - 2022-12-13
|
||||
>
|
||||
> * Flowzone: Allow external contributions [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.30.1 - 2022-12-07
|
||||
>
|
||||
> * patch: bump catch-uncommitted from 1.6.2 to 2.0.0 [dependabot[bot]]
|
||||
>
|
||||
> ### balena-sdk-16.30.0 - 2022-11-24
|
||||
>
|
||||
> * Add utils and export mergePineOptions `balena.utils.mergePineOptions()` [JSReds]
|
||||
>
|
||||
> ### balena-sdk-16.29.3 - 2022-11-24
|
||||
>
|
||||
> * device.getWithServiceDetails: Stop auto-expanding the gateway_downloads [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.29.2 - 2022-11-16
|
||||
>
|
||||
> * Update TypeScript to 4.9.3 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.29.1 - 2022-11-12
|
||||
>
|
||||
> * Fix release end_timestamp type [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.29.0 - 2022-11-12
|
||||
>
|
||||
>
|
||||
> <details>
|
||||
> <summary> Support filtered $count operations inside $filter & $orderby [Thodoris Greasidis] </summary>
|
||||
>
|
||||
>> #### pinejs-client-js-6.12.0 - 2022-11-10
|
||||
>>
|
||||
>> * Deprecate the 'a/count' notation in $orderby [Thodoris Greasidis]
|
||||
>> * Deprecate the $count: { $op: number } notation [Thodoris Greasidis]
|
||||
>> * Add support for `$filter: { $op: [{ $count: {} }, number] }` notation [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.11.0 - 2022-11-09
|
||||
>>
|
||||
>> * Deprecate non-$filter props inside `$expand: { a: { $count: {...}}}` [Thodoris Greasidis]
|
||||
>> * Add support for `$orderby: { a: { $count: ... }, $dir: 'asc' }` notation [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.7 - 2022-11-07
|
||||
>>
|
||||
>> * Refactor the deprecation message definitions [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.6 - 2022-11-01
|
||||
>>
|
||||
>> * tests: Support `.only` & `.skip` in the higher level test functions [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.5 - 2022-10-14
|
||||
>>
|
||||
>> * Flowzone: Use inherited secrets [Pagan Gazzard]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.4 - 2022-09-26
|
||||
>>
|
||||
>> * Specify node 10 as the minimum supported node engine in the package.json [Thodoris Greasidis]
|
||||
>> * Replace balenaCI with flowzone [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.3 - 2022-09-15
|
||||
>>
|
||||
>> * Fix $count typings to only allow $filter under it [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.2 - 2022-04-08
|
||||
>>
|
||||
>> * Update dependencies [Pagan Gazzard]
|
||||
>> * Remove circleci [Pagan Gazzard]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.1 - 2022-02-08
|
||||
>>
|
||||
>> * Do not await the _request() result to allow enhanced promises downstream [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### pinejs-client-js-6.10.0 - 2022-01-24
|
||||
>>
|
||||
>> * Add optional retry logic to client [Paul Jonathan Zoulin]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-sdk-16.28.4 - 2022-11-04
|
||||
>
|
||||
> * Use deep imports for date-fns to improve tree-shaking [Thodoris Greasidis]
|
||||
> * Enable esModuleInterop build option [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.28.3 - 2022-11-03
|
||||
>
|
||||
> * Update balena-errors to v4.7.3 [JSReds]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 15.1.3 - 2023-04-05
|
||||
|
||||
|
||||
<details>
|
||||
<summary> devices supported: Fix showing types without a valid & finalized release [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-sdk-16.28.2 - 2022-10-27
|
||||
>
|
||||
> * Update tests to run on node 18 [Thodoris Greasidis]
|
||||
> * deviceType.getAllSupported: Require a valid & final release to exist [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-16.28.1 - 2022-10-14
|
||||
>
|
||||
> * flowzone: Run the node tests using the latest LTS version [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 15.1.2 - 2023-03-27
|
||||
|
||||
* Improve type checking by using the satisfies operator [Thodoris Greasidis]
|
||||
|
||||
## 15.1.1 - 2023-03-17
|
||||
|
||||
* Update TypeScript to 5.0.2 [Thodoris Greasidis]
|
||||
|
||||
## 15.1.0 - 2023-03-14
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update balena-compose to v2.2.1 [Kyle Harding] </summary>
|
||||
|
||||
> ### balena-compose-2.2.1 - 2023-03-14
|
||||
>
|
||||
> * Ignore references to build stages when evaluating manifests [Kyle Harding]
|
||||
>
|
||||
> ### balena-compose-2.2.0 - 2023-03-13
|
||||
>
|
||||
> * OCI Image Index should allow platform opts [Kyle Harding]
|
||||
>
|
||||
> ### balena-compose-2.1.4 - 2023-03-13
|
||||
>
|
||||
> * Write to debug log when using platform option [Kyle Harding]
|
||||
>
|
||||
> ### balena-compose-2.1.3 - 2023-03-01
|
||||
>
|
||||
> * Fixup tests to use recent debian:bullseye-slim images [Kyle Harding]
|
||||
>
|
||||
> ### balena-compose-2.1.2 - 2022-10-17
|
||||
>
|
||||
> * test/multibuild: Use 127.0.0.1 for the extra_hosts test [Ken Bannister]
|
||||
> * Output error text to aid test debugging [Ken Bannister]
|
||||
> * Replace balenaCI & circleCI with flowzone [Thodoris Greasidis]
|
||||
> * Pin dockerode to v3.3.3 to avoid regression [Ken Bannister]
|
||||
> * Prettify fixup [Ken Bannister]
|
||||
> * Fix underspecified generics in release/models [Ken Bannister]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 15.0.6 - 2023-03-13
|
||||
|
||||
* Devices: explicitly fetches only used fields [Otávio Jacobi]
|
||||
|
||||
## 15.0.5 - 2023-03-10
|
||||
|
||||
* Fix application isLegacy check for rename and deploy [JSReds]
|
||||
|
||||
## 15.0.4 - 2023-02-21
|
||||
|
||||
* patch: Clarify update rate of update notifier info [Heath Raftery]
|
||||
|
||||
## 15.0.3 - 2023-01-18
|
||||
|
||||
* Use https for the npm deprecation check, avoiding a redirect [Pagan Gazzard]
|
||||
|
||||
## 15.0.2 - 2023-01-14
|
||||
|
||||
* Fix push --nolive doc typo [Josh Bowling]
|
||||
|
||||
## 15.0.1 - 2023-01-10
|
||||
|
||||
* Process livepush build logs inline [Felipe Lalanne]
|
||||
|
||||
## 15.0.0 - 2023-01-02
|
||||
|
||||
* preload: Drops ability to preload Intel Edison (EOL 2017) Upgrade balena-preload from 12.2.0 to 13.0.0 [JOASSART Edwin]
|
||||
|
||||
## 14.5.18 - 2022-12-29
|
||||
|
||||
* Update flowzone tests to use npm ci [Thodoris Greasidis]
|
||||
|
||||
## 14.5.17 - 2022-12-28
|
||||
|
||||
* Stop using the deprecated balena-sync module [Thodoris Greasidis]
|
||||
|
||||
## 14.5.16 - 2022-12-28
|
||||
|
||||
* Update the npm-shrinkwrap.json dependencies to match the package.json [Thodoris Greasidis]
|
||||
|
||||
## 14.5.15 - 2022-12-12
|
||||
|
||||
* patch: update balena-preload to 12.2.0 [Edwin Joassart]
|
||||
|
||||
## 14.5.14 - 2022-12-11
|
||||
|
||||
* Bump multicast-dns to rebased commit (again) [pipex]
|
||||
|
||||
## 14.5.13 - 2022-12-08
|
||||
|
||||
* Build on macos-11 for library compatibility reasons [Page-]
|
||||
* Build on ubuntu-20.04 for library compatibility reasons [Page-]
|
||||
|
||||
## 14.5.12 - 2022-11-21
|
||||
|
||||
* Move GH publishing to FZ core [ab77]
|
||||
|
||||
## 14.5.11 - 2022-11-17
|
||||
|
||||
* Adding .nvmrc so we can use nvm use instead of hunting for version [zoobot]
|
||||
|
||||
## 14.5.10 - 2022-11-11
|
||||
|
||||
* Fix surfacing incompatible device type errors as not recognized [Thodoris Greasidis]
|
||||
|
||||
## 14.5.9 - 2022-11-11
|
||||
|
||||
* Prevent git from existing with 141 [ab77]
|
||||
|
||||
## 14.5.8 - 2022-11-10
|
||||
|
||||
* Replace missing input [ab77]
|
||||
|
||||
## 14.5.7 - 2022-11-10
|
||||
|
||||
* Just ignore errors during publish [ab77]
|
||||
|
||||
## 14.5.6 - 2022-11-10
|
||||
|
||||
* Ignore PIPE signal [ab77]
|
||||
|
||||
## 14.5.5 - 2022-11-10
|
||||
|
||||
* Don't pipefail [ab77]
|
||||
|
||||
## 14.5.4 - 2022-11-10
|
||||
|
||||
* Error when the device type and image parameters do not match [Thodoris Greasidis]
|
||||
|
||||
## 14.5.3 - 2022-11-10
|
||||
|
||||
* Switch to Flowzone [ab77]
|
||||
|
||||
## 14.5.2 - 2022-10-21
|
||||
|
||||
* Stop waiting for the analytics response [Thodoris Greasidis]
|
||||
|
||||
## 14.5.1 - 2022-10-20
|
||||
|
||||
* Bump parse-link-header from 1.0.1 to 2.0.0 [dependabot[bot]]
|
||||
|
||||
## 14.5.0 - 2022-10-18
|
||||
|
||||
* keeps events loggiging with default message [Otávio Jacobi]
|
||||
* uses amplitude data events format [Otávio Jacobi]
|
||||
* changes analytics endpoint to analytics-backend [Otávio Jacobi]
|
||||
|
||||
## 14.4.4 - 2022-10-18
|
||||
|
||||
* Update simple-git to 3.14.1 [Thodoris Greasidis]
|
||||
|
||||
## 14.4.3 - 2022-10-17
|
||||
|
||||
* config generate: Fix the incompatible arch errors showing as not found [Thodoris Greasidis]
|
||||
|
||||
## 14.4.2 - 2022-10-17
|
||||
|
||||
* Stop relying on device-type.json for resolving the device type aliases [Thodoris Greasidis]
|
||||
* Stop relying on device-type.json for resolving the cpu architecture [Thodoris Greasidis]
|
||||
|
||||
## 14.4.1 - 2022-10-12
|
||||
|
||||
* balena os initialize: Clarify that the process includes flashing [Heath Raftery]
|
||||
|
@ -78,8 +78,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
|
||||
some development tools to be installed first, as follows.
|
||||
|
||||
> **The balena CLI currently requires Node.js version 12 (min 12.8.0).**
|
||||
> **Versions 13 and later are not yet fully supported.**
|
||||
> **The balena CLI currently requires Node.js version 16.**
|
||||
> **Versions 17 and later are not yet fully supported.**
|
||||
|
||||
### Install development tools
|
||||
|
||||
@ -89,7 +89,7 @@ some development tools to be installed first, as follows.
|
||||
$ 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
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 12
|
||||
$ nvm install 16
|
||||
```
|
||||
|
||||
The `curl` command line above uses
|
||||
@ -106,15 +106,15 @@ recommended.
|
||||
```sh
|
||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 12
|
||||
$ nvm install 16
|
||||
```
|
||||
|
||||
#### **Windows** (not WSL)
|
||||
|
||||
Install:
|
||||
|
||||
* Node.js v12 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||
* If you'd like the ability to switch between Node.js versions, install
|
||||
- Node.js v16 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)
|
||||
instead.
|
||||
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:
|
||||
|
@ -45,8 +45,6 @@ const execFileAsync = promisify(execFile);
|
||||
export const packageJSON = loadPackageJson();
|
||||
export const version = 'v' + packageJSON.version;
|
||||
const arch = process.arch;
|
||||
const MSYS2_BASH =
|
||||
process.env.MSYSSHELLPATH || 'C:\\msys64\\usr\\bin\\bash.exe';
|
||||
|
||||
function dPath(...paths: string[]) {
|
||||
return path.join(ROOT, 'dist', ...paths);
|
||||
@ -89,7 +87,7 @@ async function diffPkgOutput(pkgOut: string) {
|
||||
'tests',
|
||||
'test-data',
|
||||
'pkg',
|
||||
`expected-warnings-${process.platform}.txt`,
|
||||
`expected-warnings-${process.platform}-${arch}.txt`,
|
||||
);
|
||||
const absSavedPath = path.join(ROOT, relSavedPath);
|
||||
const ignoreStartsWith = [
|
||||
@ -182,9 +180,18 @@ async function execPkg(...args: any[]) {
|
||||
* to be directly executed from inside another binary executable.)
|
||||
*/
|
||||
async function buildPkg() {
|
||||
// https://github.com/vercel/pkg#targets
|
||||
let targets = `linux-${arch}`;
|
||||
// TBC: not possible to build for macOS or Windows arm64 on x64 nodes
|
||||
if (process.platform === 'darwin') {
|
||||
targets = `macos-x64`;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
targets = `win-x64`;
|
||||
}
|
||||
const args = [
|
||||
'--target',
|
||||
'host',
|
||||
'--targets',
|
||||
targets,
|
||||
'--output',
|
||||
'build-bin/balena',
|
||||
'package.json',
|
||||
@ -425,20 +432,28 @@ async function renameInstallerFiles() {
|
||||
|
||||
/**
|
||||
* If the CSC_LINK and CSC_KEY_PASSWORD env vars are set, digitally sign the
|
||||
* executable installer by running the balena-io/scripts/shared/sign-exe.sh
|
||||
* script (which must be in the PATH) using a MSYS2 bash shell.
|
||||
* executable installer using Microsoft SignTool.exe (Sign Tool)
|
||||
* https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
|
||||
*/
|
||||
async function signWindowsInstaller() {
|
||||
if (process.env.CSC_LINK && process.env.CSC_KEY_PASSWORD) {
|
||||
const exeName = renamedOclifInstallers[process.platform];
|
||||
console.log(`Signing installer "${exeName}"`);
|
||||
await execFileAsync(MSYS2_BASH, [
|
||||
'sign-exe.sh',
|
||||
// trust ...
|
||||
await execFileAsync('signtool.exe', [
|
||||
'sign',
|
||||
'-t',
|
||||
process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com',
|
||||
'-f',
|
||||
exeName,
|
||||
process.env.CSC_LINK,
|
||||
'-p',
|
||||
process.env.CSC_KEY_PASSWORD,
|
||||
'-d',
|
||||
`balena-cli ${version}`,
|
||||
exeName,
|
||||
]);
|
||||
// ... but verify
|
||||
await execFileAsync('signtool.exe', ['verify', '-pa', '-v', exeName]);
|
||||
} else {
|
||||
console.log(
|
||||
'Skipping installer signing step because CSC_* env vars are not set',
|
||||
@ -450,14 +465,21 @@ async function signWindowsInstaller() {
|
||||
* Wait for Apple Installer Notarization to continue
|
||||
*/
|
||||
async function notarizeMacInstaller(): Promise<void> {
|
||||
const appleId = 'accounts+apple@balena.io';
|
||||
const { notarize } = await import('electron-notarize');
|
||||
await notarize({
|
||||
appBundleId: 'io.balena.etcher',
|
||||
appPath: renamedOclifInstallers.darwin,
|
||||
appleId,
|
||||
appleIdPassword: '@keychain:CLI_PASSWORD',
|
||||
});
|
||||
const appleId =
|
||||
process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io';
|
||||
const appBundleId = packageJSON.oclif.macos.identifier || 'io.balena.cli';
|
||||
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD;
|
||||
|
||||
if (appleIdPassword) {
|
||||
const { notarize } = await import('electron-notarize');
|
||||
// https://github.com/electron/notarize/blob/main/README.md
|
||||
await notarize({
|
||||
appBundleId,
|
||||
appPath: renamedOclifInstallers.darwin,
|
||||
appleId,
|
||||
appleIdPassword,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,11 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const stripIndent = require('common-tags/lib/stripIndent');
|
||||
const _ = require('lodash');
|
||||
const { promises: fs } = require('fs');
|
||||
const path = require('path');
|
||||
const simplegit = require('simple-git/promise');
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import { simpleGit } from 'simple-git';
|
||||
|
||||
const ROOT = path.normalize(path.join(__dirname, '..'));
|
||||
|
||||
@ -31,7 +32,7 @@ const ROOT = path.normalize(path.join(__dirname, '..'));
|
||||
* using `touch`.
|
||||
*/
|
||||
async function checkBuildTimestamps() {
|
||||
const git = simplegit(ROOT);
|
||||
const git = simpleGit(ROOT);
|
||||
const docFile = path.join(ROOT, 'docs', 'balena-cli.md');
|
||||
const [docStat, gitStatus] = await Promise.all([
|
||||
fs.stat(docFile),
|
||||
@ -81,4 +82,5 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
run();
|
@ -2160,6 +2160,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
|
||||
being a supervisor feature that allows the "balena push" command to push a user's
|
||||
application directly to a device in the local network.
|
||||
|
||||
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
|
||||
secure boot and disk encryption.
|
||||
|
||||
The --system-connection (-c) option is used to inject NetworkManager connection
|
||||
profiles for additional network interfaces, such as cellular/GSM or additional
|
||||
WiFi or ethernet connections. This option may be passed multiple times in case there
|
||||
@ -2230,6 +2233,10 @@ WiFi SSID (network name) (non-interactive configuration)
|
||||
|
||||
Configure balenaOS to operate in development mode
|
||||
|
||||
#### --secureBoot
|
||||
|
||||
Configure balenaOS installer to opt-in secure boot and disk encryption
|
||||
|
||||
#### -d, --device DEVICE
|
||||
|
||||
device UUID
|
||||
@ -2314,6 +2321,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
|
||||
being a supervisor feature that allows the "balena push" command to push a user's
|
||||
application directly to a device in the local network.
|
||||
|
||||
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
|
||||
secure boot and disk encryption.
|
||||
|
||||
To configure an image for a fleet of mixed device types, use the --fleet option
|
||||
alongside the --deviceType option to specify the target device type.
|
||||
|
||||
@ -2337,6 +2347,7 @@ Examples:
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
|
||||
$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev
|
||||
$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot
|
||||
$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3
|
||||
$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json
|
||||
$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15
|
||||
@ -2355,6 +2366,10 @@ fleet name or slug (preferred)
|
||||
|
||||
Configure balenaOS to operate in development mode
|
||||
|
||||
#### --secureBoot
|
||||
|
||||
Configure balenaOS installer to opt-in secure boot and disk encryption
|
||||
|
||||
#### -d, --device DEVICE
|
||||
|
||||
device UUID
|
||||
@ -2788,7 +2803,7 @@ used (usually $HOME/.balena/secrets.yml|.json)
|
||||
|
||||
Don't run a live session on this push. The filesystem will not be monitored,
|
||||
and changes will not be synchronized to any running containers. Note that both
|
||||
this flag and --detached and required to cause the process to end once the
|
||||
this flag and --detached are required to cause the process to end once the
|
||||
initial build has completed.
|
||||
|
||||
#### -d, --detached
|
||||
|
@ -20,7 +20,7 @@ import Command from '../command';
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import * as compose from '../utils/compose';
|
||||
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
|
||||
import type { ApplicationType, BalenaSDK } from 'balena-sdk';
|
||||
import {
|
||||
buildArgDeprecation,
|
||||
dockerignoreHelp,
|
||||
@ -218,7 +218,9 @@ ${dockerignoreHelp}
|
||||
logger: import('../utils/logger'),
|
||||
composeOpts: ComposeOpts,
|
||||
opts: {
|
||||
app?: Application;
|
||||
app?: {
|
||||
application_type: [Pick<ApplicationType, 'supports_multicontainer'>];
|
||||
};
|
||||
arch: string;
|
||||
deviceType: string;
|
||||
buildEmulated: boolean;
|
||||
@ -234,7 +236,7 @@ ${dockerignoreHelp}
|
||||
opts.buildOpts.t,
|
||||
);
|
||||
|
||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||
const appType = opts.app?.application_type?.[0];
|
||||
if (
|
||||
appType != null &&
|
||||
project.descriptors.length > 1 &&
|
||||
|
@ -19,13 +19,18 @@ import { flags } from '@oclif/command';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
|
||||
import type { PineDeferred } from 'balena-sdk';
|
||||
import {
|
||||
applicationIdInfo,
|
||||
devModeInfo,
|
||||
secureBootInfo,
|
||||
} from '../../utils/messages';
|
||||
import type { BalenaSDK, PineDeferred } from 'balena-sdk';
|
||||
|
||||
interface FlagsDef {
|
||||
version: string; // OS version
|
||||
fleet?: string;
|
||||
dev?: boolean; // balenaOS development variant
|
||||
secureBoot?: boolean;
|
||||
device?: string;
|
||||
deviceApiKey?: string;
|
||||
deviceType?: string;
|
||||
@ -51,6 +56,8 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
${devModeInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
${secureBootInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
To configure an image for a fleet of mixed device types, use the --fleet option
|
||||
alongside the --deviceType option to specify the target device type.
|
||||
|
||||
@ -66,6 +73,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json',
|
||||
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
|
||||
@ -80,6 +88,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
}),
|
||||
fleet: { ...cf.fleet, exclusive: ['device'] },
|
||||
dev: cf.dev,
|
||||
secureBoot: cf.secureBoot,
|
||||
device: {
|
||||
...cf.device,
|
||||
exclusive: [
|
||||
@ -135,17 +144,25 @@ export default class ConfigGenerateCmd extends Command {
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async getApplication(balena: BalenaSDK, fleet: string) {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
return await getApplication(balena, fleet, {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
await this.validateOptions(options);
|
||||
|
||||
let resourceDeviceType: string;
|
||||
let application: ApplicationWithDeviceType | null = null;
|
||||
let application: Awaited<ReturnType<typeof this.getApplication>> | null =
|
||||
null;
|
||||
let device:
|
||||
| (DeviceWithDeviceType & { belongs_to__application: PineDeferred })
|
||||
| null = null;
|
||||
@ -165,36 +182,40 @@ export default class ConfigGenerateCmd extends Command {
|
||||
resourceDeviceType = device.is_of__device_type[0].slug;
|
||||
} else {
|
||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||
application = (await getApplication(balena, options.fleet!, {
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType;
|
||||
application = await this.getApplication(balena, options.fleet!);
|
||||
resourceDeviceType = application.is_for__device_type[0].slug;
|
||||
}
|
||||
|
||||
const deviceType = options.deviceType || resourceDeviceType;
|
||||
|
||||
const deviceManifest = await balena.models.device.getManifestBySlug(
|
||||
deviceType,
|
||||
);
|
||||
|
||||
// Check compatibility if application and deviceType provided
|
||||
if (options.fleet && options.deviceType) {
|
||||
const appDeviceManifest = await balena.models.device.getManifestBySlug(
|
||||
resourceDeviceType,
|
||||
);
|
||||
|
||||
const helpers = await import('../../utils/helpers');
|
||||
if (
|
||||
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
|
||||
!(await helpers.areDeviceTypesCompatible(
|
||||
resourceDeviceType,
|
||||
deviceType,
|
||||
))
|
||||
) {
|
||||
throw new balena.errors.BalenaInvalidDeviceType(
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
`Device type ${options.deviceType} is incompatible with fleet ${options.fleet}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const deviceManifest =
|
||||
await balena.models.config.getDeviceTypeManifestBySlug(deviceType);
|
||||
|
||||
const { validateSecureBootOptionAndWarn } = await import(
|
||||
'../../utils/config'
|
||||
);
|
||||
await validateSecureBootOptionAndWarn(
|
||||
options.secureBoot,
|
||||
deviceType,
|
||||
options.version,
|
||||
);
|
||||
|
||||
// Prompt for values
|
||||
// Pass params as an override: if there is any param with exactly the same name as a
|
||||
// required option, that value is used (and the corresponding question is not asked)
|
||||
@ -203,6 +224,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
});
|
||||
answers.version = options.version;
|
||||
answers.developmentMode = options.dev;
|
||||
answers.secureBoot = options.secureBoot;
|
||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];
|
||||
|
||||
|
@ -43,15 +43,13 @@ import {
|
||||
parseReleaseTagKeysAndValues,
|
||||
} from '../utils/compose_ts';
|
||||
import { dockerCliFlags } from '../utils/docker';
|
||||
import type {
|
||||
Application,
|
||||
ApplicationType,
|
||||
DeviceType,
|
||||
Release,
|
||||
} from 'balena-sdk';
|
||||
import type { ApplicationType, DeviceType, Release } from 'balena-sdk';
|
||||
|
||||
interface ApplicationWithArch extends Application {
|
||||
interface ApplicationWithArch {
|
||||
id: number;
|
||||
arch: string;
|
||||
is_for__device_type: [Pick<DeviceType, 'slug'>];
|
||||
application_type: [Pick<ApplicationType, 'slug' | 'supports_multicontainer'>];
|
||||
}
|
||||
|
||||
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
|
||||
@ -235,7 +233,7 @@ ${dockerignoreHelp}
|
||||
releaseTagValues,
|
||||
);
|
||||
if (options.note) {
|
||||
await sdk.models.release.note(release.id, options.note);
|
||||
await sdk.models.release.setNote(release.id, options.note);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +260,7 @@ ${dockerignoreHelp}
|
||||
'../utils/compose_ts'
|
||||
);
|
||||
|
||||
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
|
||||
const appType = opts.app.application_type[0];
|
||||
|
||||
try {
|
||||
const project = await loadProject(
|
||||
@ -319,7 +317,7 @@ ${dockerignoreHelp}
|
||||
projectName: project.name,
|
||||
composition: compositionToBuild,
|
||||
arch: opts.app.arch,
|
||||
deviceType: (opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
|
||||
deviceType: opts.app.is_for__device_type[0].slug,
|
||||
emulated: opts.buildEmulated,
|
||||
buildOpts: opts.buildOpts,
|
||||
inlineLogs: composeOpts.inlineLogs,
|
||||
@ -340,7 +338,7 @@ ${dockerignoreHelp}
|
||||
);
|
||||
|
||||
let release: Release | ComposeReleaseInfo['release'];
|
||||
if (appType?.is_legacy) {
|
||||
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
|
||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||
|
||||
const msg = getChalk().yellow(
|
||||
|
@ -124,20 +124,21 @@ export default class DeviceInitCmd extends Command {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
// Get application and
|
||||
const application = (await getApplication(
|
||||
const application = await getApplication(
|
||||
balena,
|
||||
options.fleet ||
|
||||
(
|
||||
await (await import('../../utils/patterns')).selectApplication()
|
||||
).slug,
|
||||
{
|
||||
$select: ['id', 'slug'],
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
)) as ApplicationWithDeviceType;
|
||||
);
|
||||
|
||||
// Register new device
|
||||
const deviceUuid = balena.models.device.generateUniqueKey();
|
||||
|
@ -21,6 +21,7 @@ import type {
|
||||
BalenaSDK,
|
||||
Device,
|
||||
DeviceType,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
} from 'balena-sdk';
|
||||
import Command from '../../command';
|
||||
@ -29,13 +30,6 @@ import { ExpectedError } from '../../errors';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
type ExtendedDevice = PineTypedResult<
|
||||
Device,
|
||||
typeof import('../../utils/helpers').expandForAppNameAndCpuArch
|
||||
> & {
|
||||
application_name?: string;
|
||||
};
|
||||
|
||||
interface FlagsDef {
|
||||
fleet?: string;
|
||||
help: void;
|
||||
@ -81,6 +75,33 @@ export default class DeviceMoveCmd extends Command {
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
private async getDevices(balena: BalenaSDK, deviceUuids: string[]) {
|
||||
const deviceOptions = {
|
||||
$select: 'belongs_to__application',
|
||||
$expand: {
|
||||
is_of__device_type: {
|
||||
$select: 'is_of__cpu_architecture',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
// TODO: Refacor once `device.get()` accepts an array of uuids`
|
||||
const devices = await Promise.all(
|
||||
deviceUuids.map(
|
||||
(uuid) =>
|
||||
balena.models.device.get(uuid, deviceOptions) as Promise<
|
||||
PineTypedResult<Device, typeof deviceOptions>
|
||||
>,
|
||||
),
|
||||
);
|
||||
return devices;
|
||||
}
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceMoveCmd,
|
||||
@ -88,36 +109,17 @@ export default class DeviceMoveCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const { expandForAppNameAndCpuArch } = await import('../../utils/helpers');
|
||||
|
||||
// Split uuids string into array of uuids
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
|
||||
// Get devices
|
||||
const devices = await Promise.all(
|
||||
deviceUuids.map(
|
||||
(uuid) =>
|
||||
balena.models.device.get(
|
||||
uuid,
|
||||
expandForAppNameAndCpuArch,
|
||||
) as Promise<ExtendedDevice>,
|
||||
),
|
||||
);
|
||||
|
||||
// Map application name for each device
|
||||
for (const device of devices) {
|
||||
const belongsToApplication = device.belongs_to__application;
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
}
|
||||
const devices = await this.getDevices(balena, deviceUuids);
|
||||
|
||||
// Disambiguate application
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
// Get destination application
|
||||
const application = options.fleet
|
||||
? await getApplication(balena, options.fleet)
|
||||
? await getApplication(balena, options.fleet, { $select: ['id', 'slug'] })
|
||||
: await this.interactivelySelectApplication(balena, devices);
|
||||
|
||||
// Move each device
|
||||
@ -134,7 +136,7 @@ export default class DeviceMoveCmd extends Command {
|
||||
|
||||
async interactivelySelectApplication(
|
||||
balena: BalenaSDK,
|
||||
devices: ExtendedDevice[],
|
||||
devices: Awaited<ReturnType<typeof this.getDevices>>,
|
||||
) {
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
// deduplicate the slugs
|
||||
@ -153,7 +155,7 @@ export default class DeviceMoveCmd extends Command {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
} satisfies PineOptions<DeviceType>;
|
||||
const deviceTypes = (await balena.models.deviceType.getAllSupported(
|
||||
deviceTypeOptions,
|
||||
)) as Array<PineTypedResult<DeviceType, typeof deviceTypeOptions>>;
|
||||
@ -180,7 +182,9 @@ export default class DeviceMoveCmd extends Command {
|
||||
const application = await patterns.selectApplication(
|
||||
(app) =>
|
||||
compatibleDeviceTypeSlugs.has(app.is_for__device_type[0].slug) &&
|
||||
devices.some((device) => device.application_name !== app.app_name),
|
||||
devices.some(
|
||||
(device) => device.belongs_to__application.__id !== app.id,
|
||||
),
|
||||
true,
|
||||
);
|
||||
return application;
|
||||
|
@ -78,7 +78,9 @@ export default class DeviceRegisterCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const application = await getApplication(balena, params.fleet);
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: ['id', 'slug'],
|
||||
});
|
||||
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
|
||||
|
||||
console.info(`Registering to ${application.slug}: ${uuid}`);
|
||||
|
@ -22,7 +22,7 @@ import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
|
||||
|
||||
import type { Application } from 'balena-sdk';
|
||||
import type { Application, Device, PineOptions } from 'balena-sdk';
|
||||
|
||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
dashboard_url?: string;
|
||||
@ -36,6 +36,18 @@ interface FlagsDef {
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
const devicesSelectFields = {
|
||||
$select: [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
||||
'status',
|
||||
'is_online',
|
||||
'supervisor_version',
|
||||
'os_version',
|
||||
],
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
export default class DevicesCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
List all devices.
|
||||
@ -70,20 +82,28 @@ export default class DevicesCmd extends Command {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const devicesOptions = {
|
||||
...devicesSelectFields,
|
||||
...expandForAppName,
|
||||
$orderby: { device_name: 'asc' },
|
||||
} satisfies PineOptions<Device>;
|
||||
|
||||
let devices;
|
||||
|
||||
if (options.fleet != null) {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const application = await getApplication(balena, options.fleet);
|
||||
const application = await getApplication(balena, options.fleet, {
|
||||
$select: 'id',
|
||||
});
|
||||
devices = (await balena.models.device.getAllByApplication(
|
||||
application.id,
|
||||
expandForAppName,
|
||||
devicesOptions,
|
||||
)) as ExtendedDevice[];
|
||||
} else {
|
||||
devices = (await balena.models.device.getAll(
|
||||
expandForAppName,
|
||||
)) as ExtendedDevice[];
|
||||
devices = (await balena.pine.get({
|
||||
resource: 'device',
|
||||
options: devicesOptions,
|
||||
})) as ExtendedDevice[];
|
||||
}
|
||||
|
||||
devices = devices.map(function (device) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { flags } from '@oclif/command';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import * as _ from 'lodash';
|
||||
import Command from '../../command';
|
||||
|
||||
@ -59,36 +60,38 @@ export default class DevicesSupportedCmd extends Command {
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
|
||||
const [dts, configDTs] = await Promise.all([
|
||||
getBalenaSdk().models.deviceType.getAllSupported({
|
||||
$expand: { is_of__cpu_architecture: { $select: 'slug' } },
|
||||
$select: ['slug', 'name'],
|
||||
}),
|
||||
getBalenaSdk().models.config.getDeviceTypes(),
|
||||
]);
|
||||
const dtsBySlug = _.keyBy(dts, (dt) => dt.slug);
|
||||
const configDTsBySlug = _.keyBy(configDTs, (dt) => dt.slug);
|
||||
const pineOptions = {
|
||||
$select: ['slug', 'name'],
|
||||
$expand: {
|
||||
is_of__cpu_architecture: { $select: 'slug' },
|
||||
device_type_alias: {
|
||||
$select: 'is_referenced_by__alias',
|
||||
$orderby: { is_referenced_by__alias: 'asc' },
|
||||
},
|
||||
},
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
|
||||
const dts = (await getBalenaSdk().models.deviceType.getAllSupported(
|
||||
pineOptions,
|
||||
)) as Array<
|
||||
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
|
||||
>;
|
||||
interface DT {
|
||||
slug: string;
|
||||
aliases: string[];
|
||||
arch: string;
|
||||
name: string;
|
||||
}
|
||||
let deviceTypes: DT[] = [];
|
||||
for (const slug of Object.keys(dtsBySlug)) {
|
||||
const configDT: Partial<typeof configDTs[0]> =
|
||||
configDTsBySlug[slug] || {};
|
||||
const aliases = (configDT.aliases || []).filter(
|
||||
(alias) => alias !== slug,
|
||||
);
|
||||
const dt: Partial<typeof dts[0]> = dtsBySlug[slug] || {};
|
||||
deviceTypes.push({
|
||||
slug,
|
||||
let deviceTypes = dts.map((dt): DT => {
|
||||
const aliases = dt.device_type_alias
|
||||
.map((dta) => dta.is_referenced_by__alias)
|
||||
.filter((alias) => alias !== dt.slug);
|
||||
return {
|
||||
slug: dt.slug,
|
||||
aliases: options.json ? aliases : [aliases.join(', ')],
|
||||
arch: (dt.is_of__cpu_architecture as any)?.[0]?.slug || 'n/a',
|
||||
arch: dt.is_of__cpu_architecture[0]?.slug || 'n/a',
|
||||
name: dt.name || 'N/A',
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
const fields = ['slug', 'aliases', 'arch', 'name'];
|
||||
deviceTypes = _.sortBy(deviceTypes, fields);
|
||||
if (options.json) {
|
||||
|
38
lib/commands/env/add.ts
vendored
38
lib/commands/env/add.ts
vendored
@ -151,16 +151,15 @@ export default class EnvAddCmd extends Command {
|
||||
|
||||
const varType = isConfigVar ? 'configVar' : 'envVar';
|
||||
if (options.fleet) {
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
for (const app of options.fleet.split(',')) {
|
||||
for (const appSlug of await resolveFleetSlugs(balena, options.fleet)) {
|
||||
try {
|
||||
await balena.models.application[varType].set(
|
||||
await getFleetSlug(balena, app),
|
||||
appSlug,
|
||||
params.name,
|
||||
params.value,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${app}`);
|
||||
console.error(`${err.message}, fleet: ${appSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@ -181,6 +180,25 @@ export default class EnvAddCmd extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Stop accepting application names in the next major
|
||||
// and just drop this in favor of doing the .split(',') directly.
|
||||
async function resolveFleetSlugs(
|
||||
balena: BalenaSdk.BalenaSDK,
|
||||
fleetOption: string,
|
||||
) {
|
||||
const fleetSlugs: string[] = [];
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
for (const appNameOrSlug of fleetOption.split(',')) {
|
||||
try {
|
||||
fleetSlugs.push(await getFleetSlug(balena, appNameOrSlug));
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${appNameOrSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
return fleetSlugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add service variables for a device or fleet.
|
||||
*/
|
||||
@ -190,17 +208,17 @@ async function setServiceVars(
|
||||
options: FlagsDef,
|
||||
) {
|
||||
if (options.fleet) {
|
||||
for (const app of options.fleet.split(',')) {
|
||||
for (const appSlug of await resolveFleetSlugs(sdk, options.fleet)) {
|
||||
for (const service of options.service!.split(',')) {
|
||||
try {
|
||||
const serviceId = await getServiceIdForApp(sdk, app, service);
|
||||
const serviceId = await getServiceIdForApp(sdk, appSlug, service);
|
||||
await sdk.models.service.var.set(
|
||||
serviceId,
|
||||
params.name,
|
||||
params.value!,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(`${err.message}, fleet: ${app}`);
|
||||
console.error(`${err.message}, fleet: ${appSlug}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@ -245,11 +263,11 @@ async function setServiceVars(
|
||||
*/
|
||||
async function getServiceIdForApp(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
appName: string,
|
||||
appSlug: string,
|
||||
serviceName: string,
|
||||
): Promise<number> {
|
||||
let serviceId: number | undefined;
|
||||
const services = await sdk.models.service.getAllByApplication(appName, {
|
||||
const services = await sdk.models.service.getAllByApplication(appSlug, {
|
||||
$filter: { service_name: serviceName },
|
||||
});
|
||||
if (services.length > 0) {
|
||||
@ -257,7 +275,7 @@ async function getServiceIdForApp(
|
||||
}
|
||||
if (serviceId === undefined) {
|
||||
throw new ExpectedError(
|
||||
`Cannot find service ${serviceName} for fleet ${appName}`,
|
||||
`Cannot find service ${serviceName} for fleet ${appSlug}`,
|
||||
);
|
||||
}
|
||||
return serviceId;
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { Application } from 'balena-sdk';
|
||||
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
@ -101,13 +100,17 @@ export default class FleetCreateCmd extends Command {
|
||||
options.organization?.toLowerCase() || (await this.getOrganization());
|
||||
|
||||
// Create application
|
||||
let application: Application;
|
||||
try {
|
||||
application = await getBalenaSdk().models.application.create({
|
||||
const application = await getBalenaSdk().models.application.create({
|
||||
name: params.name,
|
||||
deviceType,
|
||||
organization,
|
||||
});
|
||||
|
||||
// Output
|
||||
console.log(
|
||||
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
|
||||
);
|
||||
} catch (err) {
|
||||
if ((err.message || '').toLowerCase().includes('unique')) {
|
||||
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
||||
@ -123,16 +126,13 @@ export default class FleetCreateCmd extends Command {
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Output
|
||||
console.log(
|
||||
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
|
||||
);
|
||||
}
|
||||
|
||||
async getOrganization() {
|
||||
const { getOwnOrganizations } = await import('../../utils/sdk');
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk(), {
|
||||
$select: ['name', 'handle'],
|
||||
});
|
||||
|
||||
if (organizations.length === 0) {
|
||||
// User is not a member of any organizations (should not happen).
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
import type { flags as flagsType } from '@oclif/command';
|
||||
import { flags } from '@oclif/command';
|
||||
import type { Release } from 'balena-sdk';
|
||||
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
@ -74,17 +73,12 @@ export default class FleetCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const application = (await getApplication(balena, params.fleet, {
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$expand: {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
should_be_running__release: { $select: 'commit' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType & {
|
||||
should_be_running__release: [Release?];
|
||||
// For display purposes:
|
||||
device_type: string;
|
||||
commit?: string;
|
||||
};
|
||||
});
|
||||
|
||||
if (options.view) {
|
||||
const open = await import('open');
|
||||
@ -95,11 +89,14 @@ export default class FleetCmd extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
application.device_type = application.is_for__device_type[0].slug;
|
||||
application.commit = application.should_be_running__release[0]?.commit;
|
||||
const outputApplication = {
|
||||
...application,
|
||||
device_type: application.is_for__device_type[0].slug,
|
||||
commit: application.should_be_running__release[0]?.commit,
|
||||
};
|
||||
|
||||
await this.outputData(
|
||||
application,
|
||||
outputApplication,
|
||||
['app_name', 'id', 'device_type', 'slug', 'commit'],
|
||||
options,
|
||||
);
|
||||
|
@ -65,7 +65,9 @@ export default class FleetPurgeCmd extends Command {
|
||||
|
||||
// balena.models.application.purge only accepts a numeric id
|
||||
// so we must first fetch the app to get it's id,
|
||||
const application = await getApplication(balena, params.fleet);
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: 'id',
|
||||
});
|
||||
|
||||
try {
|
||||
await balena.models.application.purge(application.id);
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import type { flags } from '@oclif/command';
|
||||
import type { ApplicationType } from 'balena-sdk';
|
||||
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
@ -78,9 +77,10 @@ export default class FleetRenameCmd extends Command {
|
||||
// Disambiguate target application (if params.params is a number, it could either be an ID or a numerical name)
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: ['id', 'app_name', 'slug'],
|
||||
$expand: {
|
||||
application_type: {
|
||||
$select: ['is_legacy'],
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -91,8 +91,8 @@ export default class FleetRenameCmd extends Command {
|
||||
}
|
||||
|
||||
// Check app supports renaming
|
||||
const appType = (application.application_type as ApplicationType[])?.[0];
|
||||
if (appType.is_legacy) {
|
||||
const appType = application.application_type[0];
|
||||
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
|
||||
throw new ExpectedError(
|
||||
`Fleet ${params.fleet} is of 'legacy' type, and cannot be renamed.`,
|
||||
);
|
||||
@ -133,9 +133,9 @@ export default class FleetRenameCmd extends Command {
|
||||
}
|
||||
|
||||
// Get application again, to be sure of results
|
||||
const renamedApplication = await balena.models.application.get(
|
||||
application.id,
|
||||
);
|
||||
const renamedApplication = await getApplication(balena, application.id, {
|
||||
$select: ['app_name', 'slug'],
|
||||
});
|
||||
|
||||
// Output result
|
||||
console.log(`Fleet renamed`);
|
||||
|
@ -63,7 +63,9 @@ export default class FleetRestartCmd extends Command {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
// Disambiguate application
|
||||
const application = await getApplication(balena, params.fleet);
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: 'slug',
|
||||
});
|
||||
|
||||
await balena.models.application.restart(application.slug);
|
||||
}
|
||||
|
@ -76,7 +76,9 @@ export default class FleetRmCmd extends Command {
|
||||
);
|
||||
|
||||
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
||||
const application = await getApplication(balena, params.fleet);
|
||||
const application = await getApplication(balena, params.fleet, {
|
||||
$select: 'slug',
|
||||
});
|
||||
|
||||
// Remove
|
||||
await balena.models.application.remove(application.slug);
|
||||
|
@ -88,6 +88,6 @@ export default class NoteCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
return balena.models.device.note(options.device!, params.note);
|
||||
return balena.models.device.setNote(options.device, params.note);
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ export default class OrgsCmd extends Command {
|
||||
const { getOwnOrganizations } = await import('../utils/sdk');
|
||||
|
||||
// Get organizations
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk(), {
|
||||
$select: ['name', 'handle'],
|
||||
});
|
||||
|
||||
// Display
|
||||
console.log(
|
||||
|
@ -23,7 +23,11 @@ import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
|
||||
import {
|
||||
applicationIdInfo,
|
||||
devModeInfo,
|
||||
secureBootInfo,
|
||||
} from '../../utils/messages';
|
||||
|
||||
const CONNECTIONS_FOLDER = '/system-connections';
|
||||
|
||||
@ -36,6 +40,7 @@ interface FlagsDef {
|
||||
'config-wifi-key'?: string;
|
||||
'config-wifi-ssid'?: string;
|
||||
dev?: boolean; // balenaOS development variant
|
||||
secureBoot?: boolean;
|
||||
device?: string; // device UUID
|
||||
'device-type'?: string;
|
||||
help?: void;
|
||||
@ -53,6 +58,7 @@ interface ArgsDef {
|
||||
interface Answers {
|
||||
appUpdatePollInterval: number; // in minutes
|
||||
developmentMode?: boolean; // balenaOS development variant
|
||||
secureBoot?: boolean;
|
||||
deviceType: string; // e.g. "raspberrypi3"
|
||||
network: 'ethernet' | 'wifi';
|
||||
version: string; // e.g. "2.32.0+rev1"
|
||||
@ -80,6 +86,8 @@ export default class OsConfigureCmd extends Command {
|
||||
|
||||
${devModeInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
${secureBootInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
The --system-connection (-c) option is used to inject NetworkManager connection
|
||||
profiles for additional network interfaces, such as cellular/GSM or additional
|
||||
WiFi or ethernet connections. This option may be passed multiple times in case there
|
||||
@ -140,6 +148,7 @@ export default class OsConfigureCmd extends Command {
|
||||
description: 'WiFi SSID (network name) (non-interactive configuration)',
|
||||
}),
|
||||
dev: cf.dev,
|
||||
secureBoot: cf.secureBoot,
|
||||
device: {
|
||||
...cf.device,
|
||||
exclusive: [
|
||||
@ -215,7 +224,7 @@ export default class OsConfigureCmd extends Command {
|
||||
is_for__device_type: { $select: 'slug' },
|
||||
},
|
||||
})) as ApplicationWithDeviceType;
|
||||
await checkDeviceTypeCompatibility(balena, options, app);
|
||||
await checkDeviceTypeCompatibility(options, app);
|
||||
deviceTypeSlug =
|
||||
options['device-type'] || app.is_for__device_type[0].slug;
|
||||
}
|
||||
@ -238,6 +247,15 @@ export default class OsConfigureCmd extends Command {
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||
await validateDevOptionAndWarn(options.dev, osVersion);
|
||||
|
||||
const { validateSecureBootOptionAndWarn } = await import(
|
||||
'../../utils/config'
|
||||
);
|
||||
await validateSecureBootOptionAndWarn(
|
||||
options.secureBoot,
|
||||
deviceTypeSlug,
|
||||
osVersion,
|
||||
);
|
||||
|
||||
const answers: Answers = await askQuestionsForDeviceType(
|
||||
deviceTypeManifest,
|
||||
options,
|
||||
@ -248,6 +266,7 @@ export default class OsConfigureCmd extends Command {
|
||||
}
|
||||
answers.version = osVersion;
|
||||
answers.developmentMode = options.dev;
|
||||
answers.secureBoot = options.secureBoot;
|
||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];
|
||||
|
||||
@ -361,17 +380,19 @@ async function getOsVersionFromImage(
|
||||
* @param app Balena SDK Application model object
|
||||
*/
|
||||
async function checkDeviceTypeCompatibility(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
options: FlagsDef,
|
||||
app: ApplicationWithDeviceType,
|
||||
app: {
|
||||
is_for__device_type: [Pick<BalenaSdk.DeviceType, 'slug'>];
|
||||
},
|
||||
) {
|
||||
if (options['device-type']) {
|
||||
const [appDeviceType, optionDeviceType] = await Promise.all([
|
||||
sdk.models.device.getManifestBySlug(app.is_for__device_type[0].slug),
|
||||
sdk.models.device.getManifestBySlug(options['device-type']),
|
||||
]);
|
||||
const helpers = await import('../../utils/helpers');
|
||||
if (!helpers.areDeviceTypesCompatible(appDeviceType, optionDeviceType)) {
|
||||
if (
|
||||
!(await helpers.areDeviceTypesCompatible(
|
||||
app.is_for__device_type[0].slug,
|
||||
options['device-type'],
|
||||
))
|
||||
) {
|
||||
throw new ExpectedError(
|
||||
`Device type ${options['device-type']} is incompatible with fleet ${options.fleet}`,
|
||||
);
|
||||
|
@ -31,7 +31,14 @@ import { parseAsInteger } from '../utils/validation';
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import * as _ from 'lodash';
|
||||
import type { Application, BalenaSDK, PineExpand, Release } from 'balena-sdk';
|
||||
import type {
|
||||
Application,
|
||||
BalenaSDK,
|
||||
PineExpand,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
Release,
|
||||
} from 'balena-sdk';
|
||||
import type { Preloader } from 'balena-preload';
|
||||
|
||||
interface FlagsDef extends DockerConnectionCliFlags {
|
||||
@ -187,7 +194,7 @@ Can be repeated to add multiple certificates.\
|
||||
: undefined;
|
||||
|
||||
const progressBars: {
|
||||
[key: string]: ReturnType<typeof getVisuals>['Progress'];
|
||||
[key: string]: InstanceType<ReturnType<typeof getVisuals>['Progress']>;
|
||||
} = {};
|
||||
|
||||
const progressHandler = function (event: {
|
||||
@ -201,7 +208,7 @@ Can be repeated to add multiple certificates.\
|
||||
};
|
||||
|
||||
const spinners: {
|
||||
[key: string]: ReturnType<typeof getVisuals>['Spinner'];
|
||||
[key: string]: InstanceType<ReturnType<typeof getVisuals>['Spinner']>;
|
||||
} = {};
|
||||
|
||||
const spinnerHandler = function (event: { name: string; action: string }) {
|
||||
@ -242,7 +249,7 @@ Can be repeated to add multiple certificates.\
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const docker = await dockerUtils.getDocker(options);
|
||||
const preloader = new balenaPreload.Preloader(
|
||||
null,
|
||||
undefined,
|
||||
docker,
|
||||
fleetSlug,
|
||||
commit,
|
||||
@ -308,7 +315,7 @@ Can be repeated to add multiple certificates.\
|
||||
}
|
||||
}
|
||||
|
||||
readonly applicationExpandOptions: PineExpand<Application> = {
|
||||
readonly applicationExpandOptions = {
|
||||
owns__release: {
|
||||
$select: ['id', 'commit', 'end_timestamp', 'composition'],
|
||||
$expand: {
|
||||
@ -329,7 +336,7 @@ Can be repeated to add multiple certificates.\
|
||||
should_be_running__release: {
|
||||
$select: 'commit',
|
||||
},
|
||||
};
|
||||
} satisfies PineExpand<Application>;
|
||||
|
||||
isCurrentCommit(commit: string) {
|
||||
return commit === 'latest' || commit === 'current';
|
||||
@ -343,7 +350,7 @@ Can be repeated to add multiple certificates.\
|
||||
} catch {
|
||||
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
|
||||
}
|
||||
return (await balena.models.application.getAllDirectlyAccessible({
|
||||
const options = {
|
||||
$select: ['id', 'slug', 'should_track_latest_release'],
|
||||
$expand: this.applicationExpandOptions,
|
||||
$filter: {
|
||||
@ -388,11 +395,10 @@ Can be repeated to add multiple certificates.\
|
||||
},
|
||||
},
|
||||
$orderby: 'slug asc',
|
||||
})) as Array<
|
||||
ApplicationWithDeviceType & {
|
||||
should_be_running__release: [Release?];
|
||||
}
|
||||
>;
|
||||
} satisfies PineOptions<Application>;
|
||||
return (await balena.models.application.getAllDirectlyAccessible(
|
||||
options,
|
||||
)) as Array<PineTypedResult<Application, typeof options>>;
|
||||
}
|
||||
|
||||
async selectApplication(deviceTypeSlug: string) {
|
||||
@ -442,7 +448,7 @@ Can be repeated to add multiple certificates.\
|
||||
}
|
||||
|
||||
async offerToDisableAutomaticUpdates(
|
||||
application: Application,
|
||||
application: Pick<Application, 'id' | 'should_track_latest_release'>,
|
||||
commit: string,
|
||||
pinDevice: boolean,
|
||||
) {
|
||||
@ -494,9 +500,9 @@ Would you like to disable automatic updates for this fleet now?\
|
||||
async getAppWithReleases(balenaSdk: BalenaSDK, slug: string) {
|
||||
const { getApplication } = await import('../utils/sdk');
|
||||
|
||||
return (await getApplication(balenaSdk, slug, {
|
||||
return await getApplication(balenaSdk, slug, {
|
||||
$expand: this.applicationExpandOptions,
|
||||
})) as Application & { should_be_running__release: [Release?] };
|
||||
});
|
||||
}
|
||||
|
||||
async prepareAndPreload(
|
||||
@ -512,7 +518,7 @@ Would you like to disable automatic updates for this fleet now?\
|
||||
|
||||
const application = options.slug
|
||||
? await this.getAppWithReleases(balenaSdk, options.slug)
|
||||
: await this.selectApplication(preloader.config.deviceType);
|
||||
: await this.selectApplication(preloader.config!.deviceType);
|
||||
|
||||
let commit: string; // commit hash or the strings 'latest' or 'current'
|
||||
|
||||
|
@ -178,7 +178,7 @@ export default class PushCmd extends Command {
|
||||
description: stripIndent`
|
||||
Don't run a live session on this push. The filesystem will not be monitored,
|
||||
and changes will not be synchronized to any running containers. Note that both
|
||||
this flag and --detached and required to cause the process to end once the
|
||||
this flag and --detached are required to cause the process to end once the
|
||||
initial build has completed.`,
|
||||
default: false,
|
||||
}),
|
||||
@ -328,7 +328,7 @@ export default class PushCmd extends Command {
|
||||
]);
|
||||
|
||||
const application = await getApplication(sdk, appNameOrSlug, {
|
||||
$select: ['app_name', 'slug'],
|
||||
$select: 'slug',
|
||||
});
|
||||
|
||||
const opts = {
|
||||
@ -358,7 +358,7 @@ export default class PushCmd extends Command {
|
||||
releaseTagValues,
|
||||
);
|
||||
if (options.note) {
|
||||
await sdk.models.release.note(releaseId, options.note);
|
||||
await sdk.models.release.setNote(releaseId, options.note);
|
||||
}
|
||||
} else if (releaseTagKeys.length > 0) {
|
||||
throw new Error(stripIndent`
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { LocalBalenaOsDevice } from 'balena-sync';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getCliUx, stripIndent } from '../utils/lazy';
|
||||
@ -72,7 +71,7 @@ export default class ScanCmd extends Command {
|
||||
|
||||
public async run() {
|
||||
const _ = await import('lodash');
|
||||
const { discover } = await import('balena-sync');
|
||||
const { discoverLocalBalenaOsDevices } = await import('../utils/discover');
|
||||
const prettyjson = await import('prettyjson');
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
|
||||
@ -88,8 +87,7 @@ export default class ScanCmd extends Command {
|
||||
const ux = getCliUx();
|
||||
ux.action.start('Scanning for local balenaOS devices');
|
||||
|
||||
const localDevices: LocalBalenaOsDevice[] =
|
||||
await discover.discoverLocalBalenaOsDevices(discoverTimeout);
|
||||
const localDevices = await discoverLocalBalenaOsDevices(discoverTimeout);
|
||||
const engineReachableDevices: boolean[] = await Promise.all(
|
||||
localDevices.map(async ({ address }: { address: string }) => {
|
||||
const docker = await dockerUtils.createClient({
|
||||
@ -106,7 +104,7 @@ export default class ScanCmd extends Command {
|
||||
}),
|
||||
);
|
||||
|
||||
const developmentDevices: LocalBalenaOsDevice[] = localDevices.filter(
|
||||
const developmentDevices = localDevices.filter(
|
||||
(_localDevice, index) => engineReachableDevices[index],
|
||||
);
|
||||
|
||||
@ -116,18 +114,15 @@ export default class ScanCmd extends Command {
|
||||
_.isEqual,
|
||||
);
|
||||
|
||||
const productionDevicesInfo = _.map(
|
||||
productionDevices,
|
||||
(device: LocalBalenaOsDevice) => {
|
||||
return {
|
||||
host: device.host,
|
||||
address: device.address,
|
||||
osVariant: 'production',
|
||||
dockerInfo: undefined,
|
||||
dockerVersion: undefined,
|
||||
};
|
||||
},
|
||||
);
|
||||
const productionDevicesInfo = productionDevices.map((device) => {
|
||||
return {
|
||||
host: device.host,
|
||||
address: device.address,
|
||||
osVariant: 'production',
|
||||
dockerInfo: undefined,
|
||||
dockerVersion: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
// Query devices for info
|
||||
const devicesInfo = await Promise.all(
|
||||
|
@ -149,7 +149,7 @@ export default class SupportCmd extends Command {
|
||||
console.log(
|
||||
`Access has been granted for ${duration}, expiring ${new Date(
|
||||
expiryTs,
|
||||
).toLocaleString()}`,
|
||||
).toISOString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -153,19 +153,19 @@ export default class TunnelCmd extends Command {
|
||||
try {
|
||||
await handler(client);
|
||||
logConnection(
|
||||
client.remoteAddress || '',
|
||||
client.remotePort || 0,
|
||||
client.localAddress,
|
||||
client.localPort,
|
||||
client.remoteAddress ?? '',
|
||||
client.remotePort ?? 0,
|
||||
client.localAddress ?? '',
|
||||
client.localPort ?? 0,
|
||||
uuid,
|
||||
remotePort,
|
||||
);
|
||||
} catch (err) {
|
||||
logConnection(
|
||||
client.remoteAddress || '',
|
||||
client.remotePort || 0,
|
||||
client.localAddress,
|
||||
client.localPort,
|
||||
client.remoteAddress ?? '',
|
||||
client.remotePort ?? 0,
|
||||
client.localAddress ?? '',
|
||||
client.localPort ?? 0,
|
||||
uuid,
|
||||
remotePort,
|
||||
err,
|
||||
|
@ -86,7 +86,7 @@ export class DeprecationChecker {
|
||||
* @param version Semver without 'v' prefix, e.g. '12.0.0.'
|
||||
*/
|
||||
protected getNpmUrl(version: string) {
|
||||
return `http://registry.npmjs.org/balena-cli/${version}`;
|
||||
return `https://registry.npmjs.org/balena-cli/${version}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,7 +177,16 @@ const messages: {
|
||||
Looks like the session token has expired.
|
||||
Try logging in again with the "balena login" command.`,
|
||||
|
||||
BalenaInvalidDeviceType: (error: Error & { deviceTypeSlug?: string }) => {
|
||||
BalenaInvalidDeviceType: (
|
||||
error: Error & { deviceTypeSlug?: string; type?: string },
|
||||
) => {
|
||||
// TODO: The SDK should be throwing a different Error for this case.
|
||||
if (
|
||||
typeof error.type === 'string' &&
|
||||
error.type.startsWith('Incompatible ')
|
||||
) {
|
||||
return error.type;
|
||||
}
|
||||
const slug = error.deviceTypeSlug ? `"${error.deviceTypeSlug}"` : 'slug';
|
||||
return stripIndent`
|
||||
Device type ${slug} not recognized. Perhaps misspelled?
|
||||
|
@ -73,38 +73,52 @@ export async function trackCommand(commandSignature: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const TIMEOUT = 4000;
|
||||
|
||||
/**
|
||||
* Make the event tracking HTTPS request to balenaCloud's '/mixpanel' endpoint.
|
||||
*/
|
||||
async function sendEvent(balenaUrl: string, event: string, username?: string) {
|
||||
const { default: got } = await import('got');
|
||||
const trackData = {
|
||||
event,
|
||||
properties: {
|
||||
arch: process.arch,
|
||||
balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com'
|
||||
distinct_id: username,
|
||||
mp_lib: 'node',
|
||||
node: process.version,
|
||||
platform: process.platform,
|
||||
token: 'balena-main',
|
||||
version: packageJSON.version,
|
||||
},
|
||||
};
|
||||
const url = `https://api.${balenaUrl}/mixpanel/track`;
|
||||
const searchParams = {
|
||||
ip: 0,
|
||||
verbose: 0,
|
||||
data: Buffer.from(JSON.stringify(trackData)).toString('base64'),
|
||||
api_key: 'balena-main',
|
||||
events: [
|
||||
{
|
||||
event_type: event,
|
||||
user_id: username,
|
||||
version_name: packageJSON.version,
|
||||
event_properties: {
|
||||
balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com'
|
||||
arch: process.arch,
|
||||
platform: process.platform,
|
||||
node: process.version,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const url = `https://data.${balenaUrl}/amplitude/2/httpapi`;
|
||||
|
||||
try {
|
||||
await got(url, { searchParams, retry: 0, timeout: 4000 });
|
||||
await got.post(url, {
|
||||
json: trackData,
|
||||
retry: 0,
|
||||
timeout: {
|
||||
// Starts when the request is initiated.
|
||||
request: TIMEOUT,
|
||||
// Starts when request has been flushed.
|
||||
// Exits the request as soon as it's sent.
|
||||
response: 0,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] Event tracking error: ${e.message || e}`);
|
||||
}
|
||||
|
||||
if (e instanceof got.TimeoutError) {
|
||||
if (
|
||||
e instanceof got.TimeoutError &&
|
||||
TIMEOUT < (e.timings.phases.total ?? 0)
|
||||
) {
|
||||
console.error(stripIndent`
|
||||
Timeout submitting analytics event to balenaCloud/openBalena.
|
||||
If you are using the balena CLI in an air-gapped environment with a filtered
|
||||
|
@ -91,7 +91,7 @@ export default class BalenaHelp extends Help {
|
||||
.map((pc) => {
|
||||
return commands.find((c) => c.id === pc.replace(' ', ':'));
|
||||
})
|
||||
.filter((c): c is typeof commands[0] => !!c);
|
||||
.filter((c): c is (typeof commands)[0] => !!c);
|
||||
|
||||
let usageLength = 0;
|
||||
for (const cmd of primaryCommands) {
|
||||
|
@ -107,16 +107,6 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
|
||||
(_sdk, deviceUUID) => deviceUUID,
|
||||
);
|
||||
|
||||
/** Given a device type alias like 'nuc', return the actual slug like 'intel-nuc'. */
|
||||
export const unaliasDeviceType = _.memoize(async function (
|
||||
sdk: SDK.BalenaSDK,
|
||||
deviceType: string,
|
||||
): Promise<string> {
|
||||
return (
|
||||
(await sdk.models.device.getManifestBySlug(deviceType)).slug || deviceType
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Download balenaOS image for the specified `deviceType`.
|
||||
* `OSVersion` may be one of:
|
||||
@ -255,8 +245,8 @@ export async function getOsVersions(
|
||||
);
|
||||
// if slug is an alias, fetch the real slug
|
||||
if (!versions.length) {
|
||||
// unaliasDeviceType() produces a nice error msg if slug is invalid
|
||||
slug = await unaliasDeviceType(sdk, slug);
|
||||
// unalias device type slug
|
||||
slug = (await sdk.models.deviceType.get(slug, { $select: 'slug' })).slug;
|
||||
if (slug !== deviceType) {
|
||||
versions = await sdk.models.os.getAvailableOsVersions(slug);
|
||||
}
|
||||
|
@ -74,6 +74,12 @@ export const dev: IBooleanFlag<boolean> = flags.boolean({
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const secureBoot: IBooleanFlag<boolean> = flags.boolean({
|
||||
description:
|
||||
'Configure balenaOS installer to opt-in secure boot and disk encryption',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const drive = flags.string({
|
||||
char: 'd',
|
||||
description: stripIndent`
|
||||
|
@ -52,10 +52,14 @@ export interface ImgConfig {
|
||||
os?: {
|
||||
sshKeys?: string[];
|
||||
};
|
||||
|
||||
installer?: {
|
||||
secureboot?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateApplicationConfig(
|
||||
application: BalenaSdk.Application,
|
||||
application: Pick<BalenaSdk.Application, 'slug'>,
|
||||
options: {
|
||||
version: string;
|
||||
appUpdatePollInterval?: number;
|
||||
@ -63,6 +67,7 @@ export async function generateApplicationConfig(
|
||||
os?: {
|
||||
sshKeys?: string[];
|
||||
};
|
||||
secureBoot?: boolean;
|
||||
},
|
||||
): Promise<ImgConfig> {
|
||||
options = {
|
||||
@ -84,6 +89,12 @@ export async function generateApplicationConfig(
|
||||
: options.os.sshKeys;
|
||||
}
|
||||
|
||||
// configure installer secure boot opt-in if specified
|
||||
if (options.secureBoot) {
|
||||
config.installer ??= {};
|
||||
config.installer.secureboot = options.secureBoot;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@ -165,3 +176,54 @@ export async function validateDevOptionAndWarn(
|
||||
and exposes network ports such as 2375 that allows unencrypted access to balenaEngine.
|
||||
Therefore, development mode should only be used in private, trusted local networks.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chech whether the `--secureBoot` option of commands related to OS configuration
|
||||
* such as `os configure` and `config generate` is compatible with a given
|
||||
* OS release, and print a warning regarding the consequences of using that
|
||||
* option.
|
||||
*/
|
||||
export async function validateSecureBootOptionAndWarn(
|
||||
secureBoot?: boolean,
|
||||
slug?: string,
|
||||
version?: string,
|
||||
logger?: import('./logger'),
|
||||
) {
|
||||
if (!secureBoot) {
|
||||
return;
|
||||
}
|
||||
const { ExpectedError } = await import('../errors');
|
||||
if (!version) {
|
||||
throw new ExpectedError(`Error: No version provided`);
|
||||
}
|
||||
if (!slug) {
|
||||
throw new ExpectedError(`Error: No device type provided`);
|
||||
}
|
||||
const sdk = getBalenaSdk();
|
||||
const [osRelease] = await sdk.models.os.getAllOsVersions(slug, {
|
||||
$select: 'contract',
|
||||
$filter: { raw_version: `${version.replace(/^v/, '')}` },
|
||||
});
|
||||
if (!osRelease) {
|
||||
throw new ExpectedError(`Error: No ${version} release for ${slug}`);
|
||||
}
|
||||
|
||||
const contract = osRelease.contract ? JSON.parse(osRelease.contract) : null;
|
||||
if (
|
||||
contract?.provides.some((entry: Dictionary<string>) => {
|
||||
return entry.type === 'sw.feature' && entry.slug === 'secureboot';
|
||||
})
|
||||
) {
|
||||
if (!logger) {
|
||||
const Logger = await import('./logger');
|
||||
logger = Logger.getLogger();
|
||||
}
|
||||
logger.logInfo(stripIndent`
|
||||
The '--secureBoot' option is being used to configure a balenaOS installer image
|
||||
into secure boot and full disk encryption.`);
|
||||
} else {
|
||||
throw new ExpectedError(
|
||||
`Error: The '--secureBoot' option is not supported for ${slug} in ${version}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ import { displayBuildLog } from './logs';
|
||||
import { stripIndent } from '../lazy';
|
||||
|
||||
const LOCAL_APPNAME = 'localapp';
|
||||
const LOCAL_RELEASEHASH = 'localrelease';
|
||||
const LOCAL_RELEASEHASH = '10ca12e1ea5e';
|
||||
const LOCAL_PROJECT_NAME = 'local_image';
|
||||
|
||||
// Define the logger here so the debug output
|
||||
@ -209,9 +209,9 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
globalLogger.logDebug('Fetching device information...');
|
||||
const deviceInfo = await api.getDeviceInformation();
|
||||
|
||||
let buildLogs: Dictionary<string> | undefined;
|
||||
let imageIds: Dictionary<string[]> | undefined;
|
||||
if (!opts.nolive) {
|
||||
buildLogs = {};
|
||||
imageIds = {};
|
||||
}
|
||||
|
||||
const { awaitInterruptibleTask } = await import('../helpers');
|
||||
@ -223,7 +223,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
deviceInfo,
|
||||
globalLogger,
|
||||
opts,
|
||||
buildLogs,
|
||||
imageIds,
|
||||
);
|
||||
|
||||
globalLogger.outputDeferredMessages();
|
||||
@ -265,7 +265,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
docker,
|
||||
logger: globalLogger,
|
||||
composition: project.composition,
|
||||
buildLogs: buildLogs!,
|
||||
imageIds: imageIds!,
|
||||
deployOpts: opts,
|
||||
});
|
||||
promises.push(livepush.init());
|
||||
@ -312,6 +312,14 @@ function connectToDocker(host: string, port: number): Docker {
|
||||
});
|
||||
}
|
||||
|
||||
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
||||
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||
const match = arrowTest.exec(outputLine);
|
||||
if (match != null) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
async function performBuilds(
|
||||
composition: Composition,
|
||||
tarStream: Readable,
|
||||
@ -319,7 +327,7 @@ async function performBuilds(
|
||||
deviceInfo: DeviceInfo,
|
||||
logger: Logger,
|
||||
opts: DeviceDeployOptions,
|
||||
buildLogs?: Dictionary<string>,
|
||||
imageIds?: Dictionary<string[]>,
|
||||
): Promise<BuildTask[]> {
|
||||
const multibuild = await import('@balena/compose/dist/multibuild');
|
||||
|
||||
@ -345,14 +353,29 @@ async function performBuilds(
|
||||
// If we're passed a build logs object make sure to set it
|
||||
// up properly
|
||||
let logHandlers: ((serviceName: string, line: string) => void) | undefined;
|
||||
if (buildLogs != null) {
|
||||
|
||||
const lastArrowMessage: Dictionary<string> = {};
|
||||
|
||||
if (imageIds != null) {
|
||||
for (const task of buildTasks) {
|
||||
if (!task.external) {
|
||||
buildLogs[task.serviceName] = '';
|
||||
imageIds[task.serviceName] = [];
|
||||
}
|
||||
}
|
||||
logHandlers = (serviceName: string, line: string) => {
|
||||
buildLogs[serviceName] += `${line}\n`;
|
||||
// If this was a from line, take the last found
|
||||
// image id and save it
|
||||
if (
|
||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||
lastArrowMessage[serviceName] != null
|
||||
) {
|
||||
imageIds[serviceName].push(lastArrowMessage[serviceName]);
|
||||
} else {
|
||||
const msg = extractDockerArrowMessage(line);
|
||||
if (msg != null) {
|
||||
lastArrowMessage[serviceName] = msg;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -413,12 +436,26 @@ export async function rebuildSingleTask(
|
||||
// the logs, so any calller who wants to keep track of
|
||||
// this should provide the following callback
|
||||
containerIdCb?: (id: string) => void,
|
||||
): Promise<string> {
|
||||
): Promise<string[]> {
|
||||
const multibuild = await import('@balena/compose/dist/multibuild');
|
||||
// First we run the build task, to get the new image id
|
||||
let buildLogs = '';
|
||||
const stageIds = [] as string[];
|
||||
let lastArrowMessage: string | undefined;
|
||||
|
||||
const logHandler = (_s: string, line: string) => {
|
||||
buildLogs += `${line}\n`;
|
||||
// If this was a FROM line, take the last found
|
||||
// image id and save it as a stage id
|
||||
if (
|
||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||
lastArrowMessage != null
|
||||
) {
|
||||
stageIds.push(lastArrowMessage);
|
||||
} else {
|
||||
const msg = extractDockerArrowMessage(line);
|
||||
if (msg != null) {
|
||||
lastArrowMessage = msg;
|
||||
}
|
||||
}
|
||||
|
||||
if (containerIdCb != null) {
|
||||
const match = line.match(/^\s*--->\s*Running\s*in\s*([a-f0-9]*)\s*$/i);
|
||||
@ -477,7 +514,7 @@ export async function rebuildSingleTask(
|
||||
]);
|
||||
}
|
||||
|
||||
return buildLogs;
|
||||
return stageIds;
|
||||
}
|
||||
|
||||
function assignOutputHandlers(
|
||||
|
@ -52,7 +52,6 @@ interface MonitoredContainer {
|
||||
containerId: string;
|
||||
}
|
||||
|
||||
type BuildLogs = Dictionary<string>;
|
||||
type StageImageIDs = Dictionary<string[]>;
|
||||
|
||||
export interface LivepushOpts {
|
||||
@ -62,7 +61,7 @@ export interface LivepushOpts {
|
||||
docker: Dockerode;
|
||||
api: DeviceAPI;
|
||||
logger: Logger;
|
||||
buildLogs: BuildLogs;
|
||||
imageIds: StageImageIDs;
|
||||
deployOpts: DeviceDeployOptions;
|
||||
}
|
||||
|
||||
@ -97,7 +96,7 @@ export class LivepushManager {
|
||||
this.api = opts.api;
|
||||
this.logger = opts.logger;
|
||||
this.deployOpts = opts.deployOpts;
|
||||
this.imageIds = LivepushManager.getMultistageImageIDs(opts.buildLogs);
|
||||
this.imageIds = opts.imageIds;
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
@ -250,7 +249,7 @@ export class LivepushManager {
|
||||
cwd: serviceContext,
|
||||
followSymlinks: true,
|
||||
ignoreInitial: true,
|
||||
ignored: (filePath: string, stats: fs.Stats | undefined) => {
|
||||
ignored: (filePath: string, stats?: fs.Stats) => {
|
||||
if (!stats) {
|
||||
try {
|
||||
// sync because chokidar defines a sync interface
|
||||
@ -297,33 +296,6 @@ export class LivepushManager {
|
||||
return new Dockerfile(content).generateLiveDockerfile();
|
||||
}
|
||||
|
||||
private static getMultistageImageIDs(buildLogs: BuildLogs): StageImageIDs {
|
||||
const stageIds: StageImageIDs = {};
|
||||
_.each(buildLogs, (log, serviceName) => {
|
||||
stageIds[serviceName] = [];
|
||||
|
||||
const lines = log.split(/\r?\n/);
|
||||
let lastArrowMessage: string | undefined;
|
||||
for (const line of lines) {
|
||||
// If this was a from line, take the last found
|
||||
// image id and save it
|
||||
if (
|
||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||
lastArrowMessage != null
|
||||
) {
|
||||
stageIds[serviceName].push(lastArrowMessage);
|
||||
} else {
|
||||
const msg = LivepushManager.extractDockerArrowMessage(line);
|
||||
if (msg != null) {
|
||||
lastArrowMessage = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return stageIds;
|
||||
}
|
||||
|
||||
private async awaitDeviceStateSettle(): Promise<void> {
|
||||
// Cache the state to avoid unnecessary calls
|
||||
this.lastDeviceStatus = await this.api.getStatus();
|
||||
@ -405,9 +377,9 @@ export class LivepushManager {
|
||||
);
|
||||
}
|
||||
|
||||
let buildLog: string;
|
||||
let stageImages: string[];
|
||||
try {
|
||||
buildLog = await rebuildSingleTask(
|
||||
stageImages = await rebuildSingleTask(
|
||||
serviceName,
|
||||
this.docker,
|
||||
this.logger,
|
||||
@ -466,17 +438,13 @@ export class LivepushManager {
|
||||
);
|
||||
}
|
||||
|
||||
const buildLogs: Dictionary<string> = {};
|
||||
buildLogs[serviceName] = buildLog;
|
||||
const stageImages = LivepushManager.getMultistageImageIDs(buildLogs);
|
||||
|
||||
const dockerfile = new Dockerfile(buildTask.dockerfile!);
|
||||
|
||||
instance.livepush = await Livepush.init({
|
||||
dockerfile,
|
||||
context: buildTask.context!,
|
||||
containerId: container.containerId,
|
||||
stageImages: stageImages[serviceName],
|
||||
stageImages,
|
||||
docker: this.docker,
|
||||
});
|
||||
this.assignLivepushOutputHandlers(serviceName, instance.livepush);
|
||||
@ -536,16 +504,6 @@ export class LivepushManager {
|
||||
});
|
||||
}
|
||||
|
||||
private static extractDockerArrowMessage(
|
||||
outputLine: string,
|
||||
): string | undefined {
|
||||
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||
const match = arrowTest.exec(outputLine);
|
||||
if (match != null) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
private getDockerfilePathFromTask(task: BuildTask): string[] {
|
||||
switch (task.projectType) {
|
||||
case 'Standard Dockerfile':
|
||||
|
@ -155,12 +155,8 @@ export function displayLogObject<T extends Log>(
|
||||
system: boolean,
|
||||
filterServices?: string[],
|
||||
): void {
|
||||
let toPrint: string;
|
||||
if (obj.timestamp != null) {
|
||||
toPrint = `[${new Date(obj.timestamp).toLocaleString()}]`;
|
||||
} else {
|
||||
toPrint = `[${new Date().toLocaleString()}]`;
|
||||
}
|
||||
const d = obj.timestamp != null ? new Date(obj.timestamp) : new Date();
|
||||
let toPrint = `[${d.toISOString()}]`;
|
||||
|
||||
if (obj.serviceName != null) {
|
||||
if (filterServices) {
|
||||
|
40
lib/utils/discover.ts
Normal file
40
lib/utils/discover.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { enumerateServices, findServices } from 'resin-discoverable-services';
|
||||
|
||||
interface LocalBalenaOsDevice {
|
||||
address: string;
|
||||
host: string;
|
||||
osVariant?: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
// Although we only check for 'balena-ssh', we know, implicitly, that balenaOS
|
||||
// devices come with 'rsync' installed that can be used over SSH.
|
||||
const avahiBalenaSshTag = 'resin-ssh';
|
||||
|
||||
export async function discoverLocalBalenaOsDevices(
|
||||
timeout = 4000,
|
||||
): Promise<LocalBalenaOsDevice[]> {
|
||||
const availableServices = await enumerateServices();
|
||||
const serviceDefinitions = Array.from(availableServices)
|
||||
.filter((s) => Array.from(s.tags).includes(avahiBalenaSshTag))
|
||||
.map((s) => s.service);
|
||||
|
||||
if (serviceDefinitions.length === 0) {
|
||||
throw new Error(
|
||||
`Could not find any available '${avahiBalenaSshTag}' services`,
|
||||
);
|
||||
}
|
||||
|
||||
const services = await findServices(serviceDefinitions, timeout);
|
||||
return services.map(function (service) {
|
||||
// User referer address to get device IP. This will work fine assuming that
|
||||
// a device only advertises own services.
|
||||
const {
|
||||
referer: { address },
|
||||
host,
|
||||
port,
|
||||
} = service;
|
||||
|
||||
return { address, host, port };
|
||||
});
|
||||
}
|
@ -107,21 +107,53 @@ export async function getManifest(
|
||||
deviceType: string,
|
||||
): Promise<BalenaSdk.DeviceTypeJson.DeviceType> {
|
||||
const init = await import('balena-device-init');
|
||||
const sdk = getBalenaSdk();
|
||||
const manifest = await init.getImageManifest(image);
|
||||
if (manifest != null) {
|
||||
return manifest;
|
||||
if (
|
||||
manifest != null &&
|
||||
manifest.slug !== deviceType &&
|
||||
manifest.slug !== (await sdk.models.deviceType.get(deviceType)).slug
|
||||
) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`The device type of the provided OS image ${manifest.slug}, does not match the expected device type ${deviceType}`,
|
||||
);
|
||||
}
|
||||
return getBalenaSdk().models.device.getManifestBySlug(deviceType);
|
||||
return (
|
||||
manifest ??
|
||||
(await sdk.models.config.getDeviceTypeManifestBySlug(deviceType))
|
||||
);
|
||||
}
|
||||
|
||||
export const areDeviceTypesCompatible = (
|
||||
appDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
|
||||
osDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
|
||||
) =>
|
||||
getBalenaSdk().models.os.isArchitectureCompatibleWith(
|
||||
osDeviceType.arch,
|
||||
appDeviceType.arch,
|
||||
) && !!appDeviceType.isDependent === !!osDeviceType.isDependent;
|
||||
export const areDeviceTypesCompatible = async (
|
||||
appDeviceTypeSlug: string,
|
||||
osDeviceTypeSlug: string,
|
||||
) => {
|
||||
if (appDeviceTypeSlug === osDeviceTypeSlug) {
|
||||
return true;
|
||||
}
|
||||
const sdk = getBalenaSdk();
|
||||
const pineOptions = {
|
||||
$select: 'is_of__cpu_architecture',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
|
||||
const [appDeviceType, osDeviceType] = await Promise.all(
|
||||
[appDeviceTypeSlug, osDeviceTypeSlug].map(
|
||||
(dtSlug) =>
|
||||
sdk.models.deviceType.get(dtSlug, pineOptions) as Promise<
|
||||
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
|
||||
>,
|
||||
),
|
||||
);
|
||||
return sdk.models.os.isArchitectureCompatibleWith(
|
||||
osDeviceType.is_of__cpu_architecture[0].slug,
|
||||
appDeviceType.is_of__cpu_architecture[0].slug,
|
||||
);
|
||||
};
|
||||
|
||||
export async function osProgressHandler(step: InitializeEmitter) {
|
||||
step.on('stdout', process.stdout.write.bind(process.stdout));
|
||||
@ -148,14 +180,13 @@ export async function osProgressHandler(step: InitializeEmitter) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAppWithArch(
|
||||
applicationName: string,
|
||||
): Promise<ApplicationWithDeviceType & { arch: string }> {
|
||||
export async function getAppWithArch(applicationName: string) {
|
||||
const { getApplication } = await import('./sdk');
|
||||
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||
const balena = getBalenaSdk();
|
||||
const app = await getApplication(balena, applicationName, {
|
||||
$expand: {
|
||||
application_type: {
|
||||
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
|
||||
$select: ['name', 'slug', 'supports_multicontainer'],
|
||||
},
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
@ -166,20 +197,10 @@ export async function getAppWithArch(
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const balena = getBalenaSdk();
|
||||
const app = (await getApplication(
|
||||
balena,
|
||||
applicationName,
|
||||
options,
|
||||
)) as ApplicationWithDeviceType;
|
||||
const { getExpanded } = await import('./pine');
|
||||
|
||||
});
|
||||
return {
|
||||
...app,
|
||||
arch: getExpanded(
|
||||
getExpanded(app.is_for__device_type)!.is_of__cpu_architecture,
|
||||
)!.slug,
|
||||
arch: app.is_for__device_type[0].is_of__cpu_architecture[0].slug,
|
||||
};
|
||||
}
|
||||
|
||||
@ -410,25 +431,11 @@ export function getProxyConfig(): ProxyConfig | undefined {
|
||||
|
||||
export const expandForAppName = {
|
||||
$expand: {
|
||||
belongs_to__application: { $select: ['app_name', 'slug'] as any },
|
||||
belongs_to__application: { $select: ['app_name', 'slug'] },
|
||||
is_of__device_type: { $select: 'slug' },
|
||||
is_running__release: { $select: 'commit' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const expandForAppNameAndCpuArch = {
|
||||
$expand: {
|
||||
...expandForAppName.$expand,
|
||||
is_of__device_type: {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.Device>;
|
||||
|
||||
/**
|
||||
* Use the `readline` library on Windows to install SIGINT handlers.
|
||||
|
@ -169,6 +169,10 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
|
||||
being a supervisor feature that allows the "balena push" command to push a user's
|
||||
application directly to a device in the local network.`;
|
||||
|
||||
export const secureBootInfo = `\
|
||||
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
|
||||
secure boot and disk encryption.`;
|
||||
|
||||
export const jsonInfo = `\
|
||||
The --json option is recommended when scripting the output of this command,
|
||||
because field names are less likely to change in JSON format and because it
|
||||
|
@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Application, BalenaSDK, Device, Organization } from 'balena-sdk';
|
||||
import type {
|
||||
Application,
|
||||
BalenaSDK,
|
||||
Device,
|
||||
Organization,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
} from 'balena-sdk';
|
||||
import _ = require('lodash');
|
||||
|
||||
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
|
||||
@ -157,46 +164,55 @@ export async function confirm(
|
||||
}
|
||||
}
|
||||
|
||||
export function selectApplication(
|
||||
filter?: (app: ApplicationWithDeviceType) => boolean,
|
||||
const selectApplicationPineOptions = {
|
||||
$select: ['id', 'slug', 'app_name'],
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
} satisfies PineOptions<Application>;
|
||||
|
||||
type SelectApplicationResult = PineTypedResult<
|
||||
Application,
|
||||
typeof selectApplicationPineOptions
|
||||
>;
|
||||
|
||||
export async function selectApplication(
|
||||
filter?: (app: SelectApplicationResult) => boolean,
|
||||
errorOnEmptySelection = false,
|
||||
) {
|
||||
const balena = getBalenaSdk();
|
||||
return balena.models.application
|
||||
.hasAny()
|
||||
.then(async (hasAnyApplications) => {
|
||||
if (!hasAnyApplications) {
|
||||
throw new ExpectedError('No fleets found');
|
||||
}
|
||||
const apps = (await balena.models.application.getAllDirectlyAccessible(
|
||||
selectApplicationPineOptions,
|
||||
)) as SelectApplicationResult[];
|
||||
|
||||
const apps = (await balena.models.application.getAllDirectlyAccessible({
|
||||
$expand: {
|
||||
is_for__device_type: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
})) as ApplicationWithDeviceType[];
|
||||
return apps.filter(filter || _.constant(true));
|
||||
})
|
||||
.then((applications) => {
|
||||
if (errorOnEmptySelection && applications.length === 0) {
|
||||
throw new ExpectedError('No suitable fleets found for selection');
|
||||
}
|
||||
return getCliForm().ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: _.map(applications, (application) => ({
|
||||
name: `${application.app_name} (${application.slug}) [${application.is_for__device_type[0].slug}]`,
|
||||
value: application,
|
||||
})),
|
||||
});
|
||||
});
|
||||
if (!apps.length) {
|
||||
throw new ExpectedError('No fleets found');
|
||||
}
|
||||
|
||||
const applications = filter ? apps.filter(filter) : apps;
|
||||
|
||||
if (errorOnEmptySelection && applications.length === 0) {
|
||||
throw new ExpectedError('No suitable fleets found for selection');
|
||||
}
|
||||
return getCliForm().ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
choices: _.map(applications, (application) => ({
|
||||
name: `${application.app_name} (${application.slug}) [${application.is_for__device_type[0].slug}]`,
|
||||
value: application,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
export async function selectOrganization(organizations?: Organization[]) {
|
||||
export async function selectOrganization(
|
||||
organizations?: Array<Pick<Organization, 'handle' | 'name'>>,
|
||||
) {
|
||||
// Use either provided orgs (if e.g. already loaded) or load from cloud
|
||||
organizations =
|
||||
organizations || (await getBalenaSdk().models.organization.getAll());
|
||||
organizations ??= await getBalenaSdk().models.organization.getAll({
|
||||
$select: ['name', 'handle'],
|
||||
});
|
||||
return getCliForm().ask({
|
||||
message: 'Select an organization',
|
||||
type: 'list',
|
||||
@ -252,33 +268,6 @@ export async function awaitDeviceOsUpdate(
|
||||
return uuid;
|
||||
}
|
||||
|
||||
export function inferOrSelectDevice(preferredUuid: string) {
|
||||
const balena = getBalenaSdk();
|
||||
return balena.models.device.getAll().then((devices) => {
|
||||
const onlineDevices = devices.filter((device) => device.is_online);
|
||||
if (_.isEmpty(onlineDevices)) {
|
||||
throw new ExpectedError("You don't have any devices online");
|
||||
}
|
||||
|
||||
const defaultUuid = _(onlineDevices).map('uuid').includes(preferredUuid)
|
||||
? preferredUuid
|
||||
: onlineDevices[0].uuid;
|
||||
|
||||
return getCliForm().ask({
|
||||
message: 'Select a device',
|
||||
type: 'list',
|
||||
default: defaultUuid,
|
||||
choices: _.map(onlineDevices, (device) => ({
|
||||
name: `${device.device_name || 'Untitled'} (${device.uuid.slice(
|
||||
0,
|
||||
7,
|
||||
)})`,
|
||||
value: device.uuid,
|
||||
})),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Given fleetOrDevice, which may be
|
||||
* - a fleet name
|
||||
@ -322,19 +311,22 @@ export async function getOnlineTargetDeviceUuid(
|
||||
}
|
||||
|
||||
// Not a device UUID, try application
|
||||
let application: Application;
|
||||
try {
|
||||
logger.logDebug(`Fetching fleet ${fleetOrDevice}`);
|
||||
const { getApplication } = await import('./sdk');
|
||||
application = await getApplication(sdk, fleetOrDevice);
|
||||
} catch (err) {
|
||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
||||
if (instanceOf(err, BalenaApplicationNotFound)) {
|
||||
throw new ExpectedError(`Fleet or Device not found: ${fleetOrDevice}`);
|
||||
} else {
|
||||
throw err;
|
||||
const application = await (async () => {
|
||||
try {
|
||||
logger.logDebug(`Fetching fleet ${fleetOrDevice}`);
|
||||
const { getApplication } = await import('./sdk');
|
||||
return await getApplication(sdk, fleetOrDevice, {
|
||||
$select: ['id', 'slug'],
|
||||
});
|
||||
} catch (err) {
|
||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
||||
if (instanceOf(err, BalenaApplicationNotFound)) {
|
||||
throw new ExpectedError(`Fleet or Device not found: ${fleetOrDevice}`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// App found, load its devices
|
||||
const devices = await sdk.models.device.getAllByApplication(application.id, {
|
||||
|
@ -16,10 +16,10 @@ limitations under the License.
|
||||
|
||||
import type { OptionalNavigationResource } from 'balena-sdk';
|
||||
|
||||
export const getExpanded = <T>(obj: OptionalNavigationResource<T>) =>
|
||||
export const getExpanded = <T extends {}>(obj: OptionalNavigationResource<T>) =>
|
||||
(Array.isArray(obj) && obj[0]) || undefined;
|
||||
|
||||
export const getExpandedProp = <T, K extends keyof T>(
|
||||
export const getExpandedProp = <T extends {}, K extends keyof T>(
|
||||
obj: OptionalNavigationResource<T>,
|
||||
key: K,
|
||||
) => (Array.isArray(obj) && obj[0] && obj[0][key]) || undefined;
|
||||
|
@ -163,12 +163,61 @@ async function getOsVersion(deviceIp: string): Promise<string> {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
const dockerPort = 2375;
|
||||
const dockerTimeout = 2000;
|
||||
|
||||
async function selectLocalBalenaOsDevice(timeout = 4000): Promise<string> {
|
||||
const { discoverLocalBalenaOsDevices } = await import('../utils/discover');
|
||||
const { SpinnerPromise } = getVisuals();
|
||||
const devices = await new SpinnerPromise({
|
||||
promise: discoverLocalBalenaOsDevices(timeout),
|
||||
startMessage: 'Discovering local balenaOS devices..',
|
||||
stopMessage: 'Reporting discovered devices',
|
||||
});
|
||||
|
||||
const responsiveDevices: typeof devices = [];
|
||||
const Docker = await import('docker-toolbelt');
|
||||
await Promise.all(
|
||||
devices.map(async function (device) {
|
||||
const address = device?.address;
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const docker = new Docker({
|
||||
host: address,
|
||||
port: dockerPort,
|
||||
timeout: dockerTimeout,
|
||||
});
|
||||
await docker.ping();
|
||||
responsiveDevices.push(device);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if (!responsiveDevices.length) {
|
||||
throw new Error('Could not find any local balenaOS devices');
|
||||
}
|
||||
|
||||
return getCliForm().ask({
|
||||
message: 'select a device',
|
||||
type: 'list',
|
||||
default: devices[0].address,
|
||||
choices: responsiveDevices.map((device) => ({
|
||||
name: `${device.host || 'untitled'} (${device.address})`,
|
||||
value: device.address,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
async function selectLocalDevice(): Promise<string> {
|
||||
const { forms } = await import('balena-sync');
|
||||
let hostnameOrIp;
|
||||
try {
|
||||
hostnameOrIp = await forms.selectLocalBalenaOsDevice();
|
||||
const hostnameOrIp = await selectLocalBalenaOsDevice();
|
||||
console.error(`==> Selected device: ${hostnameOrIp}`);
|
||||
return hostnameOrIp;
|
||||
} catch (e) {
|
||||
if (e.message.toLowerCase().includes('could not find any')) {
|
||||
throw new ExpectedError(e);
|
||||
@ -176,8 +225,6 @@ async function selectLocalDevice(): Promise<string> {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return hostnameOrIp;
|
||||
}
|
||||
|
||||
async function selectAppFromList(
|
||||
@ -198,31 +245,37 @@ async function selectAppFromList(
|
||||
|
||||
async function getOrSelectApplication(
|
||||
sdk: BalenaSdk.BalenaSDK,
|
||||
deviceType: string,
|
||||
deviceTypeSlug: string,
|
||||
appName?: string,
|
||||
): Promise<ApplicationWithDeviceType> {
|
||||
const _ = await import('lodash');
|
||||
const pineOptions = {
|
||||
$select: 'slug',
|
||||
$expand: {
|
||||
is_of__cpu_architecture: {
|
||||
$select: 'slug',
|
||||
},
|
||||
},
|
||||
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
|
||||
const [deviceType, allDeviceTypes] = await Promise.all([
|
||||
sdk.models.deviceType.get(deviceTypeSlug, pineOptions) as Promise<
|
||||
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
|
||||
>,
|
||||
sdk.models.deviceType.getAllSupported(pineOptions) as Promise<
|
||||
Array<BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>>
|
||||
>,
|
||||
]);
|
||||
|
||||
const allDeviceTypes = await sdk.models.config.getDeviceTypes();
|
||||
const deviceTypeManifest = _.find(allDeviceTypes, { slug: deviceType });
|
||||
if (!deviceTypeManifest) {
|
||||
throw new ExpectedError(`"${deviceType}" is not a valid device type`);
|
||||
}
|
||||
const compatibleDeviceTypes = _(allDeviceTypes)
|
||||
.filter(
|
||||
(dt) =>
|
||||
sdk.models.os.isArchitectureCompatibleWith(
|
||||
deviceTypeManifest.arch,
|
||||
dt.arch,
|
||||
) &&
|
||||
!!dt.isDependent === !!deviceTypeManifest.isDependent &&
|
||||
dt.state !== 'DISCONTINUED',
|
||||
const compatibleDeviceTypes = allDeviceTypes
|
||||
.filter((dt) =>
|
||||
sdk.models.os.isArchitectureCompatibleWith(
|
||||
deviceType.is_of__cpu_architecture[0].slug,
|
||||
dt.is_of__cpu_architecture[0].slug,
|
||||
),
|
||||
)
|
||||
.map((type) => type.slug)
|
||||
.value();
|
||||
.map((type) => type.slug);
|
||||
|
||||
if (!appName) {
|
||||
return createOrSelectApp(sdk, compatibleDeviceTypes, deviceType);
|
||||
return createOrSelectApp(sdk, compatibleDeviceTypes, deviceTypeSlug);
|
||||
}
|
||||
|
||||
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||
@ -257,13 +310,13 @@ async function getOrSelectApplication(
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
return await createApplication(sdk, deviceType, name);
|
||||
return await createApplication(sdk, deviceTypeSlug, name);
|
||||
}
|
||||
|
||||
// We've found at least one fleet with the given name.
|
||||
// Filter out fleets for non-matching device types and see what we're left with.
|
||||
const validApplications = applications.filter((app) =>
|
||||
_.includes(compatibleDeviceTypes, app.is_for__device_type[0].slug),
|
||||
compatibleDeviceTypes.includes(app.is_for__device_type[0].slug),
|
||||
);
|
||||
|
||||
if (validApplications.length === 0) {
|
||||
@ -374,7 +427,7 @@ async function generateApplicationConfig(
|
||||
) {
|
||||
const { generateApplicationConfig: configGen } = await import('./config');
|
||||
|
||||
const manifest = await sdk.models.device.getManifestBySlug(
|
||||
const manifest = await sdk.models.config.getDeviceTypeManifestBySlug(
|
||||
app.is_for__device_type[0].slug,
|
||||
);
|
||||
const opts =
|
||||
|
@ -20,8 +20,18 @@ import type {
|
||||
BalenaSDK,
|
||||
Organization,
|
||||
PineOptions,
|
||||
PineTypedResult,
|
||||
} from 'balena-sdk';
|
||||
|
||||
export async function getApplication(
|
||||
sdk: BalenaSDK,
|
||||
nameOrSlugOrId: string | number,
|
||||
): Promise<Application>;
|
||||
export async function getApplication<TP extends PineOptions<Application>>(
|
||||
sdk: BalenaSDK,
|
||||
nameOrSlugOrId: string | number,
|
||||
options?: TP,
|
||||
): Promise<PineTypedResult<Application, TP>>;
|
||||
/**
|
||||
* Get a fleet object, disambiguating the fleet identifier which may be a
|
||||
* a fleet slug or name.
|
||||
@ -29,21 +39,24 @@ import type {
|
||||
*/
|
||||
export async function getApplication(
|
||||
sdk: BalenaSDK,
|
||||
nameOrSlug: string,
|
||||
nameOrSlugOrId: string | number,
|
||||
options?: PineOptions<Application>,
|
||||
): Promise<Application> {
|
||||
const { looksLikeFleetSlug } = await import('./validation');
|
||||
if (!looksLikeFleetSlug(nameOrSlug)) {
|
||||
if (
|
||||
typeof nameOrSlugOrId === 'string' &&
|
||||
!looksLikeFleetSlug(nameOrSlugOrId)
|
||||
) {
|
||||
// Not a slug: must be an app name.
|
||||
// TODO: revisit this logic when we add support for fleet UUIDs.
|
||||
return await sdk.models.application.getAppByName(
|
||||
nameOrSlug,
|
||||
nameOrSlugOrId,
|
||||
options,
|
||||
'directly_accessible',
|
||||
);
|
||||
}
|
||||
return await sdk.models.application.getDirectlyAccessible(
|
||||
nameOrSlug,
|
||||
nameOrSlugOrId,
|
||||
options,
|
||||
);
|
||||
}
|
||||
@ -63,31 +76,44 @@ export async function getFleetSlug(
|
||||
if (!looksLikeFleetSlug(nameOrSlug)) {
|
||||
// Not a slug: must be an app name.
|
||||
// TODO: revisit this logic when we add support for fleet UUIDs.
|
||||
return (await getApplication(sdk, nameOrSlug)).slug;
|
||||
return (await getApplication(sdk, nameOrSlug, { $select: 'slug' })).slug;
|
||||
}
|
||||
return nameOrSlug.toLowerCase();
|
||||
}
|
||||
|
||||
export async function getOwnOrganizations(
|
||||
sdk: BalenaSDK,
|
||||
): Promise<Organization[]>;
|
||||
export async function getOwnOrganizations<TP extends PineOptions<Organization>>(
|
||||
sdk: BalenaSDK,
|
||||
options: TP,
|
||||
): Promise<Array<PineTypedResult<Organization, TP>>>;
|
||||
/**
|
||||
* Wraps the sdk organization.getAll method,
|
||||
* restricting to those orgs user is a member of
|
||||
*/
|
||||
export async function getOwnOrganizations(
|
||||
sdk: BalenaSDK,
|
||||
options?: PineOptions<Organization>,
|
||||
): Promise<Organization[]> {
|
||||
return await sdk.models.organization.getAll({
|
||||
$filter: {
|
||||
organization_membership: {
|
||||
$any: {
|
||||
$alias: 'orm',
|
||||
$expr: {
|
||||
orm: {
|
||||
user: await sdk.auth.getUserId(),
|
||||
return await sdk.models.organization.getAll(
|
||||
sdk.utils.mergePineOptions(
|
||||
{
|
||||
$filter: {
|
||||
organization_membership: {
|
||||
$any: {
|
||||
$alias: 'orm',
|
||||
$expr: {
|
||||
orm: {
|
||||
user: await sdk.auth.getUserId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
$orderby: 'name asc',
|
||||
},
|
||||
},
|
||||
$orderby: 'name asc',
|
||||
});
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -151,23 +151,23 @@ export async function runRemoteCommand({
|
||||
let exitCode: number | undefined;
|
||||
let exitSignal: NodeJS.Signals | undefined;
|
||||
try {
|
||||
[exitCode, exitSignal] = await new Promise<[number, NodeJS.Signals]>(
|
||||
(resolve, reject) => {
|
||||
const ps = spawn(program, args, { stdio })
|
||||
.on('error', reject)
|
||||
.on('close', (code, signal) => resolve([code, signal]));
|
||||
[exitCode, exitSignal] = await new Promise((resolve, reject) => {
|
||||
const ps = spawn(program, args, { stdio })
|
||||
.on('error', reject)
|
||||
.on('close', (code, signal) =>
|
||||
resolve([code ?? undefined, signal ?? undefined]),
|
||||
);
|
||||
|
||||
if (ps.stdin && stdin && typeof stdin !== 'string') {
|
||||
stdin.pipe(ps.stdin);
|
||||
}
|
||||
if (ps.stdout && stdout && typeof stdout !== 'string') {
|
||||
ps.stdout.pipe(stdout);
|
||||
}
|
||||
if (ps.stderr && stderr && typeof stderr !== 'string') {
|
||||
ps.stderr.pipe(stderr);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (ps.stdin && stdin && typeof stdin !== 'string') {
|
||||
stdin.pipe(ps.stdin);
|
||||
}
|
||||
if (ps.stdout && stdout && typeof stdout !== 'string') {
|
||||
ps.stdout.pipe(stdout);
|
||||
}
|
||||
if (ps.stderr && stderr && typeof stderr !== 'string') {
|
||||
ps.stderr.pipe(stderr);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
const msg = [
|
||||
`ssh failed with exit code=${exitCode} signal=${exitSignal}:`,
|
||||
|
@ -19,8 +19,10 @@ import * as UpdateNotifier from 'update-notifier';
|
||||
|
||||
import packageJSON = require('../../package.json');
|
||||
|
||||
// Check for an update once a day. 1 day granularity should be
|
||||
// enough, rather than every run.
|
||||
// Check for an update at most once a day. 1 day granularity should be
|
||||
// enough, rather than every run. Note because we show the information
|
||||
// from the *last* time we ran, if the cli has not been run for a while
|
||||
// the update info can be out of date.
|
||||
const balenaUpdateInterval = 1000 * 60 * 60 * 24 * 1;
|
||||
|
||||
let notifier: UpdateNotifier.UpdateNotifier;
|
||||
|
28890
npm-shrinkwrap.json
generated
28890
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "14.4.1",
|
||||
"version": "16.2.6",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -27,7 +27,6 @@
|
||||
"scripts": [
|
||||
"build/**/*.js",
|
||||
"node_modules/balena-sdk/es2018/index.js",
|
||||
"node_modules/balena-sync/build/**/*.js",
|
||||
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
|
||||
"node_modules/@balena/compose/dist/parse/schemas/*.json"
|
||||
],
|
||||
@ -90,12 +89,11 @@
|
||||
"author": "Balena Inc. (https://balena.io/)",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.8.0 <13.0.0",
|
||||
"npm": "<7.0.0"
|
||||
"node": ">=16 <18"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "node automation/check-npm-version.js && node automation/check-doc.js"
|
||||
"pre-commit": "node automation/check-npm-version.js && ts-node automation/check-doc.ts"
|
||||
}
|
||||
},
|
||||
"oclif": {
|
||||
@ -115,7 +113,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^6.2.0",
|
||||
"@balena/lint": "^6.2.2",
|
||||
"@oclif/config": "^1.18.2",
|
||||
"@oclif/parser": "^3.8.6",
|
||||
"@octokit/plugin-throttling": "^3.5.1",
|
||||
@ -134,6 +132,7 @@
|
||||
"@types/global-agent": "^2.1.1",
|
||||
"@types/global-tunnel-ng": "^2.1.1",
|
||||
"@types/http-proxy": "^1.17.8",
|
||||
"@types/inquirer": "^7.3.3",
|
||||
"@types/intercept-stdout": "^0.1.0",
|
||||
"@types/is-root": "^2.1.2",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
@ -147,7 +146,7 @@
|
||||
"@types/ndjson": "^2.0.1",
|
||||
"@types/net-keepalive": "^0.4.1",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^12.20.42",
|
||||
"@types/node": "^16.18.25",
|
||||
"@types/node-cleanup": "^2.1.2",
|
||||
"@types/parse-link-header": "^1.0.1",
|
||||
"@types/prettyjson": "^0.0.30",
|
||||
@ -184,17 +183,17 @@
|
||||
"mocha": "^8.4.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"nock": "^13.2.1",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"pkg": "^5.5.1",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"pkg": "^5.8.1",
|
||||
"publish-release": "^1.6.1",
|
||||
"rewire": "^5.0.0",
|
||||
"simple-git": "^2.48.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"sinon": "^11.1.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.6.4"
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@balena/compose": "^2.1.1",
|
||||
"@balena/compose": "^2.2.1",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"@balena/es-version": "^1.0.1",
|
||||
"@oclif/command": "^1.8.16",
|
||||
@ -202,18 +201,16 @@
|
||||
"@sentry/node": "^6.16.1",
|
||||
"@types/fast-levenshtein": "0.0.1",
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"JSONStream": "^1.0.3",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^6.0.0",
|
||||
"balena-errors": "^4.7.1",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-image-manager": "^8.0.0",
|
||||
"balena-preload": "^12.1.0",
|
||||
"balena-sdk": "^16.28.0",
|
||||
"balena-image-manager": "^9.0.0",
|
||||
"balena-preload": "^14.0.0",
|
||||
"balena-sdk": "^17.0.0",
|
||||
"balena-semver": "^2.3.0",
|
||||
"balena-settings-client": "^4.0.7",
|
||||
"balena-settings-client": "^5.0.2",
|
||||
"balena-settings-storage": "^7.0.0",
|
||||
"balena-sync": "^11.0.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.1",
|
||||
"chalk": "^3.0.0",
|
||||
@ -226,9 +223,10 @@
|
||||
"denymount": "^2.3.0",
|
||||
"docker-modem": "3.0.0",
|
||||
"docker-progress": "^5.1.3",
|
||||
"docker-toolbelt": "^3.3.10",
|
||||
"dockerode": "^3.3.1",
|
||||
"ejs": "^3.1.6",
|
||||
"etcher-sdk": "^6.2.1",
|
||||
"etcher-sdk": "^8.5.3",
|
||||
"event-stream": "3.3.4",
|
||||
"express": "^4.17.2",
|
||||
"fast-boot2": "^1.1.0",
|
||||
@ -244,6 +242,7 @@
|
||||
"is-elevated": "^3.0.0",
|
||||
"is-root": "^2.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"JSONStream": "^1.0.3",
|
||||
"klaw": "^3.0.0",
|
||||
"livepush": "^3.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
@ -262,7 +261,8 @@
|
||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||
"request": "^2.88.2",
|
||||
"resin-cli-form": "^2.0.2",
|
||||
"resin-cli-visuals": "^1.8.0",
|
||||
"resin-cli-visuals": "^1.8.3",
|
||||
"resin-discoverable-services": "^2.0.4",
|
||||
"resin-doodles": "^0.2.0",
|
||||
"resin-stream-logger": "^0.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
@ -285,6 +285,6 @@
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2022-10-12T13:55:51.707Z"
|
||||
"publishedAt": "2023-05-24T11:17:29.059Z"
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
diff --git a/node_modules/@oclif/parser/lib/errors.js b/node_modules/@oclif/parser/lib/errors.js
|
||||
index 0c93a81..95d06be 100644
|
||||
index 39936e3..23e3925 100644
|
||||
--- a/node_modules/@oclif/parser/lib/errors.js
|
||||
+++ b/node_modules/@oclif/parser/lib/errors.js
|
||||
@@ -13,7 +13,8 @@ const m = deps_1.default()
|
||||
@@ -14,7 +14,8 @@ const m = deps_1.default()
|
||||
.add('list', () => require('./list'));
|
||||
class CLIParseError extends errors_1.CLIError {
|
||||
constructor(options) {
|
||||
- options.message += '\nSee more help with --help';
|
||||
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
|
||||
+ options.message += `\nSee more help with ${help}`;
|
||||
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
|
||||
+ options.message += `\nSee more help with ${help}`;
|
||||
super(options.message);
|
||||
this.parse = options.parse;
|
||||
}
|
||||
@@ -34,22 +35,24 @@ class InvalidArgsSpecError extends CLIParseError {
|
||||
@@ -35,22 +36,24 @@ class InvalidArgsSpecError extends CLIParseError {
|
||||
exports.InvalidArgsSpecError = InvalidArgsSpecError;
|
||||
class RequiredArgsError extends CLIParseError {
|
||||
constructor({ args, parse }) {
|
||||
- let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}`;
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
|
||||
const namedArgs = args.filter(a => a.name);
|
||||
if (namedArgs.length > 0) {
|
||||
const list = m.list.renderList(namedArgs.map(a => [a.name, a.description]));
|
||||
@ -32,7 +32,7 @@ index 0c93a81..95d06be 100644
|
||||
exports.RequiredArgsError = RequiredArgsError;
|
||||
class RequiredFlagError extends CLIParseError {
|
||||
constructor({ flag, parse }) {
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
const usage = m.list.renderList(m.help.flagUsages([flag], { displayRequired: false }));
|
||||
const message = `Missing required flag:\n${usage}`;
|
||||
- super({ parse, message });
|
||||
@ -41,15 +41,15 @@ index 0c93a81..95d06be 100644
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/@oclif/parser/lib/list.js b/node_modules/@oclif/parser/lib/list.js
|
||||
index 3907cc0..b689ca1 100644
|
||||
index 9d020b7..6ea9eb9 100644
|
||||
--- a/node_modules/@oclif/parser/lib/list.js
|
||||
+++ b/node_modules/@oclif/parser/lib/list.js
|
||||
@@ -21,7 +21,7 @@ function renderList(items) {
|
||||
@@ -22,7 +22,7 @@ function renderList(items) {
|
||||
}
|
||||
left = left.padEnd(maxLength);
|
||||
right = linewrap(maxLength + 2, right);
|
||||
- return `${left} ${right}`;
|
||||
+ return `${left} : ${right}`;
|
||||
+ return `${left} : ${right}`;
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
diff --git a/node_modules/open/index.js b/node_modules/open/index.js
|
||||
index 3bf5373..e042b64 100644
|
||||
index 13147d0..ff161dd 100644
|
||||
--- a/node_modules/open/index.js
|
||||
+++ b/node_modules/open/index.js
|
||||
@@ -11,7 +11,9 @@ const pAccess = promisify(fs.access);
|
||||
const pExecFile = promisify(childProcess.execFile);
|
||||
@@ -10,7 +10,10 @@ const pAccess = promisify(fs.access);
|
||||
const pReadFile = promisify(fs.readFile);
|
||||
|
||||
// Path to included `xdg-open`.
|
||||
-const localXdgOpenPath = path.join(__dirname, 'xdg-open');
|
||||
+const localXdgOpenPath = process.pkg
|
||||
+ ? path.join(path.dirname(process.execPath), 'xdg-open')
|
||||
+ : path.join(__dirname, 'xdg-open');
|
||||
+
|
||||
|
||||
// Convert a path from WSL format to Windows format:
|
||||
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
|
||||
/**
|
||||
Get the mount point for fixed drives in WSL.
|
@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/node-gyp-build/index.js b/node_modules/node-gyp-build/index.js
|
||||
index b5096ed..7cd451a 100644
|
||||
--- a/node_modules/node-gyp-build/index.js
|
||||
+++ b/node_modules/node-gyp-build/index.js
|
||||
@@ -29,6 +29,9 @@ load.path = function (dir) {
|
||||
diff --git a/node_modules/node-gyp-build/node-gyp-build.js b/node_modules/node-gyp-build/node-gyp-build.js
|
||||
index 61b398e..3cc3be8 100644
|
||||
--- a/node_modules/node-gyp-build/node-gyp-build.js
|
||||
+++ b/node_modules/node-gyp-build/node-gyp-build.js
|
||||
@@ -30,6 +30,9 @@ load.resolve = load.path = function (dir) {
|
||||
if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD']
|
||||
} catch (err) {}
|
||||
|
2
repo.yml
2
repo.yml
@ -10,8 +10,6 @@ upstream:
|
||||
url: 'https://github.com/balena-io-modules/balena-image-manager'
|
||||
- repo: 'balena-preload'
|
||||
url: 'https://github.com/balena-io-modules/balena-preload'
|
||||
- repo: 'balena-sync'
|
||||
url: 'https://github.com/balena-io-modules/balena-sync'
|
||||
- repo: 'etcher-sdk'
|
||||
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
||||
- repo: 'balena-compose'
|
||||
|
@ -38,7 +38,7 @@ describe('balena devices', function () {
|
||||
it('should list devices from own and collaborator apps', async () => {
|
||||
api.scope
|
||||
.get(
|
||||
'/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name,slug),is_of__device_type($select=slug),is_running__release($select=commit)',
|
||||
'/v6/device?$orderby=device_name%20asc&$select=id,uuid,device_name,status,is_online,supervisor_version,os_version&$expand=belongs_to__application($select=app_name,slug),is_of__device_type($select=slug),is_running__release($select=commit)',
|
||||
)
|
||||
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
|
||||
'Content-Type': 'application/json',
|
||||
|
@ -44,7 +44,6 @@ describe('balena devices supported', function () {
|
||||
|
||||
it('should list currently supported devices, with correct filtering', async () => {
|
||||
api.expectGetDeviceTypes();
|
||||
api.expectGetConfigDeviceTypes();
|
||||
|
||||
const { out, err } = await runCommand('devices supported');
|
||||
|
||||
@ -54,7 +53,7 @@ describe('balena devices supported', function () {
|
||||
expect(lines).to.have.lengthOf.at.least(2);
|
||||
expect(lines).to.contain('intel-nuc nuc amd64 Intel NUC');
|
||||
expect(lines).to.contain(
|
||||
'odroid-xu4 odroid-ux3, odroid-u3+ armv7hf ODROID-XU4',
|
||||
'odroid-xu4 odroid-u3+, odroid-ux3 armv7hf ODROID-XU4',
|
||||
);
|
||||
expect(err).to.eql([]);
|
||||
});
|
||||
|
47
tests/commands/env/envs.spec.ts
vendored
47
tests/commands/env/envs.spec.ts
vendored
@ -138,6 +138,7 @@ describe('balena envs', function () {
|
||||
});
|
||||
|
||||
it('should successfully list env variables for a test device', async () => {
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceEnvVars();
|
||||
api.expectGetApplication();
|
||||
@ -145,20 +146,19 @@ describe('balena envs', function () {
|
||||
api.expectGetAppServiceVars();
|
||||
api.expectGetDeviceServiceVars();
|
||||
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid}`);
|
||||
const result = await runCommand(`envs -d ${shortUUID}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120110 svar1 svar1-value org/test * service1
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
120120 svar3 svar3-value org/test ${uuid} service1
|
||||
120121 svar4 svar4-value org/test ${uuid} service2
|
||||
120120 svar3 svar3-value org/test ${shortUUID} service1
|
||||
120121 svar4 svar4-value org/test ${shortUUID} service2
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
120203 var3 var3-val org/test ${shortUUID} *
|
||||
120204 var4 44 org/test ${shortUUID} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
@ -168,6 +168,7 @@ describe('balena envs', function () {
|
||||
});
|
||||
|
||||
it('should successfully list env variables for a test device (JSON output)', async () => {
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceEnvVars();
|
||||
api.expectGetApplication();
|
||||
@ -192,6 +193,7 @@ describe('balena envs', function () {
|
||||
});
|
||||
|
||||
it('should successfully list config variables for a test device', async () => {
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceConfigVars();
|
||||
api.expectGetApplication();
|
||||
@ -216,24 +218,24 @@ describe('balena envs', function () {
|
||||
const serviceName = 'service2';
|
||||
api.expectGetService({ serviceName });
|
||||
api.expectGetApplication();
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceServiceVars();
|
||||
api.expectGetAppEnvVars();
|
||||
api.expectGetAppServiceVars();
|
||||
api.expectGetDeviceEnvVars();
|
||||
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
|
||||
const result = await runCommand(`envs -d ${shortUUID} -s ${serviceName}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
120121 svar4 svar4-value org/test ${uuid} service2
|
||||
120121 svar4 svar4-value org/test ${shortUUID} service2
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
120203 var3 var3-val org/test ${shortUUID} *
|
||||
120204 var4 44 org/test ${shortUUID} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
@ -243,20 +245,20 @@ describe('balena envs', function () {
|
||||
});
|
||||
|
||||
it('should successfully list env and service variables for a test device (unknown fleet)', async () => {
|
||||
api.expectGetDevice({ shortUUID, fullUUID, inaccessibleApp: true });
|
||||
api.expectGetDevice({ fullUUID, inaccessibleApp: true });
|
||||
api.expectGetDeviceEnvVars();
|
||||
api.expectGetDeviceServiceVars();
|
||||
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid}`);
|
||||
const result = await runCommand(`envs -d ${shortUUID}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120120 svar3 svar3-value N/A ${uuid} service1
|
||||
120121 svar4 svar4-value N/A ${uuid} service2
|
||||
120203 var3 var3-val N/A ${uuid} *
|
||||
120204 var4 44 N/A ${uuid} *
|
||||
120120 svar3 svar3-value N/A ${shortUUID} service1
|
||||
120121 svar4 svar4-value N/A ${shortUUID} service2
|
||||
120203 var3 var3-val N/A ${shortUUID} *
|
||||
120204 var4 44 N/A ${shortUUID} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
@ -271,22 +273,22 @@ describe('balena envs', function () {
|
||||
api.expectGetApplication();
|
||||
api.expectGetAppEnvVars();
|
||||
api.expectGetAppServiceVars();
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceEnvVars();
|
||||
api.expectGetDeviceServiceVars();
|
||||
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
|
||||
const result = await runCommand(`envs -d ${shortUUID} -s ${serviceName}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120110 svar1 svar1-value org/test * ${serviceName}
|
||||
120120 svar3 svar3-value org/test ${uuid} ${serviceName}
|
||||
120120 svar3 svar3-value org/test ${shortUUID} ${serviceName}
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
120203 var3 var3-val org/test ${shortUUID} *
|
||||
120204 var4 44 org/test ${shortUUID} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
@ -301,6 +303,7 @@ describe('balena envs', function () {
|
||||
api.expectGetApplication();
|
||||
api.expectGetAppEnvVars();
|
||||
api.expectGetAppServiceVars();
|
||||
api.expectGetDevice({ shortUUID, fullUUID });
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetDeviceEnvVars();
|
||||
api.expectGetDeviceServiceVars();
|
||||
|
@ -178,7 +178,7 @@ async function startMockSshServer(): Promise<[Server, number]> {
|
||||
});
|
||||
|
||||
return await new Promise<[Server, number]>((resolve, reject) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
// this callback is called for the 'listening' event
|
||||
|
@ -212,13 +212,20 @@ export class BalenaAPIMock extends NockMock {
|
||||
|
||||
public expectGetDevice(opts: {
|
||||
fullUUID: string;
|
||||
shortUUID?: string;
|
||||
inaccessibleApp?: boolean;
|
||||
isOnline?: boolean;
|
||||
optional?: boolean;
|
||||
persist?: boolean;
|
||||
}) {
|
||||
const id = 7654321;
|
||||
this.optGet(/^\/v\d+\/device($|\?)/, opts).reply(200, {
|
||||
const providedUuid = opts.shortUUID ?? opts.fullUUID;
|
||||
this.optGet(
|
||||
providedUuid.length !== 32
|
||||
? /^\/v\d+\/device($|\?)/
|
||||
: /^\/v\d+\/device\(uuid=%27[0-9a-f]{32}%27\)/,
|
||||
opts,
|
||||
).reply(200, {
|
||||
d: [
|
||||
{
|
||||
id,
|
||||
|
@ -113,7 +113,7 @@ async function createProxyServer(): Promise<[number, number]> {
|
||||
let proxyPort = 0; // TCP port number, 0 means automatic allocation
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
if (err) {
|
||||
@ -197,7 +197,7 @@ async function createInterceptorServer(): Promise<number> {
|
||||
let interceptorPort = 0;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
if (err) {
|
||||
|
@ -6,7 +6,7 @@
|
||||
"name": "Starter",
|
||||
"slug": "microservices-starter",
|
||||
"supports_multicontainer": true,
|
||||
"is_legacy": false,
|
||||
"is_legacy": true,
|
||||
"__metadata": {}
|
||||
}
|
||||
],
|
||||
|
@ -6,7 +6,7 @@
|
||||
"name": "Starter",
|
||||
"slug": "microservices-starter",
|
||||
"supports_multicontainer": true,
|
||||
"is_legacy": false,
|
||||
"is_legacy": true,
|
||||
"__metadata": {}
|
||||
}
|
||||
],
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,20 +3,11 @@
|
||||
{
|
||||
"belongs_to__application": [
|
||||
{
|
||||
"app_name": "test app",
|
||||
"slug": "org/test app",
|
||||
"__metadata": {}
|
||||
}
|
||||
],
|
||||
"id": 1747415,
|
||||
"belongs_to__user": {
|
||||
"__deferred": {
|
||||
"uri": "/resin/user(46272)"
|
||||
},
|
||||
"__id": 46272
|
||||
},
|
||||
"is_managed_by__device": null,
|
||||
"actor": 4180757,
|
||||
"device_name": "sparkling-wood",
|
||||
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
|
||||
"uuid": "fda508c8583011b8466c26abdd5159f2",
|
||||
@ -25,50 +16,10 @@
|
||||
"commit": "18756d3386c25a044db66b89e0409804"
|
||||
}
|
||||
],
|
||||
"note": null,
|
||||
"local_id": null,
|
||||
"status": "Idle",
|
||||
"is_online": false,
|
||||
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
|
||||
"is_connected_to_vpn": false,
|
||||
"last_vpn_event": "2019-11-23T00:26:35.074Z",
|
||||
"ip_address": "192.168.0.112",
|
||||
"vpn_address": null,
|
||||
"public_address": "89.186.29.129",
|
||||
"os_version": "balenaOS 2.44.0+rev3",
|
||||
"os_variant": "dev",
|
||||
"supervisor_version": "10.3.7",
|
||||
"should_be_managed_by__supervisor_release": null,
|
||||
"is_managed_by__service_instance": {
|
||||
"__deferred": {
|
||||
"uri": "/resin/service_instance(124111)"
|
||||
},
|
||||
"__id": 124111
|
||||
},
|
||||
"provisioning_progress": null,
|
||||
"provisioning_state": "",
|
||||
"download_progress": null,
|
||||
"is_web_accessible": false,
|
||||
"longitude": "22.5853",
|
||||
"latitude": "51.2712",
|
||||
"location": "Lublin, Lublin, Poland",
|
||||
"custom_longitude": "",
|
||||
"custom_latitude": "",
|
||||
"logs_channel": null,
|
||||
"is_locked_until__date": null,
|
||||
"is_accessible_by_support_until__date": null,
|
||||
"created_at": "2019-11-18T12:27:37.423Z",
|
||||
"is_active": true,
|
||||
"api_heartbeat_state": "offline",
|
||||
"cpu_usage" : 34,
|
||||
"cpu_temp" : 56.2,
|
||||
"cpu_id" : "some cpu id",
|
||||
"memory_usage" : 1000,
|
||||
"memory_total" : 4000,
|
||||
"storage_block_device" : "/dev/mmcblk0",
|
||||
"storage_usage" : 1000,
|
||||
"storage_total" : 64000,
|
||||
"is_undervolted" : true,
|
||||
"__metadata": {
|
||||
"uri": "/resin/device(@id)?@id=1747415"
|
||||
}
|
||||
@ -76,14 +27,6 @@
|
||||
{
|
||||
"belongs_to__application": [],
|
||||
"id": 1747416,
|
||||
"belongs_to__user": {
|
||||
"__deferred": {
|
||||
"uri": "/resin/user(46272)"
|
||||
},
|
||||
"__id": 46272
|
||||
},
|
||||
"is_managed_by__device": null,
|
||||
"actor": 4180757,
|
||||
"device_name": "dashing-spruce",
|
||||
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
|
||||
"uuid": "fda508c8583011b8466c26abdd5159f3",
|
||||
@ -92,50 +35,10 @@
|
||||
"commit": "18756d3386c25a044db66b89e0409804"
|
||||
}
|
||||
],
|
||||
"note": null,
|
||||
"local_id": null,
|
||||
"status": "Idle",
|
||||
"is_online": false,
|
||||
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
|
||||
"is_connected_to_vpn": false,
|
||||
"last_vpn_event": "2019-11-23T00:26:35.074Z",
|
||||
"ip_address": "192.168.0.112",
|
||||
"vpn_address": null,
|
||||
"public_address": "89.186.29.129",
|
||||
"os_version": "balenaOS 2.44.0+rev3",
|
||||
"os_variant": "dev",
|
||||
"supervisor_version": "10.3.7",
|
||||
"should_be_managed_by__supervisor_release": null,
|
||||
"is_managed_by__service_instance": {
|
||||
"__deferred": {
|
||||
"uri": "/resin/service_instance(124111)"
|
||||
},
|
||||
"__id": 124111
|
||||
},
|
||||
"provisioning_progress": null,
|
||||
"provisioning_state": "",
|
||||
"download_progress": null,
|
||||
"is_web_accessible": false,
|
||||
"longitude": "22.5853",
|
||||
"latitude": "51.2712",
|
||||
"location": "Lublin, Lublin, Poland",
|
||||
"custom_longitude": "",
|
||||
"custom_latitude": "",
|
||||
"logs_channel": null,
|
||||
"is_locked_until__date": null,
|
||||
"is_accessible_by_support_until__date": null,
|
||||
"created_at": "2019-11-18T12:27:37.423Z",
|
||||
"is_active": true,
|
||||
"api_heartbeat_state": "offline",
|
||||
"cpu_usage" : 34,
|
||||
"cpu_temp" : 56.2,
|
||||
"cpu_id" : "some cpu id",
|
||||
"memory_usage" : 1000,
|
||||
"memory_total" : 4000,
|
||||
"storage_block_device" : "/dev/mmcblk0",
|
||||
"storage_usage" : 1000,
|
||||
"storage_total" : 64000,
|
||||
"is_undervolted" : true,
|
||||
"__metadata": {
|
||||
"uri": "/resin/device(@id)?@id=1747415"
|
||||
}
|
||||
|
@ -1,21 +1,3 @@
|
||||
> Warning Cannot resolve 'module'
|
||||
node_modules/balena-sync/build/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules/balena-sync/build/capitano/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules/balena-sync/build/sync/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
@ -56,14 +38,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/patch-package/node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/patch-package/node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot resolve 'path'
|
||||
node_modules/@balena/compose/dist/parse/schemas/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
@ -110,3 +84,8 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
|
@ -1,21 +1,3 @@
|
||||
> Warning Cannot resolve 'module'
|
||||
node_modules/balena-sync/build/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules/balena-sync/build/capitano/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules/balena-sync/build/sync/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
@ -56,14 +38,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/patch-package/node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/patch-package/node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot resolve 'path'
|
||||
node_modules/@balena/compose/dist/parse/schemas/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
@ -110,3 +84,7 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
|
90
tests/test-data/pkg/expected-warnings-linux-x64.txt
Normal file
90
tests/test-data/pkg/expected-warnings-linux-x64.txt
Normal file
@ -0,0 +1,90 @@
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
|
||||
%2: path-to-executable/drivelist.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/darwin.sh
|
||||
%2: path-to-executable/drivelist/darwin.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/linux.sh
|
||||
%2: path-to-executable/drivelist/linux.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
|
||||
%2: path-to-executable/drivelist.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/darwin.sh
|
||||
%2: path-to-executable/drivelist/darwin.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/linux.sh
|
||||
%2: path-to-executable/drivelist/linux.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot resolve 'path'
|
||||
node_modules/@balena/compose/dist/parse/schemas/index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/build/Release/drivelist.node
|
||||
%2: path-to-executable/drivelist.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/darwin.sh
|
||||
%2: path-to-executable/drivelist/darwin.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/linux.sh
|
||||
%2: path-to-executable/drivelist/linux.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/build/Release/drivelist.node
|
||||
%2: path-to-executable/drivelist.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/darwin.sh
|
||||
%2: path-to-executable/drivelist/darwin.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/linux.sh
|
||||
%2: path-to-executable/drivelist/linux.sh
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
|
||||
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
|
@ -1,21 +1,3 @@
|
||||
> Warning Cannot resolve 'module'
|
||||
node_modules\balena-sync\build\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + command'
|
||||
node_modules\balena-sync\build\capitano\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot resolve ''./' + target'
|
||||
node_modules\balena-sync\build\sync\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
||||
is unknown at compilation time and not included into executable.
|
||||
Use a string literal as an argument for 'require', or leave it
|
||||
as is and specify the resolved file name in 'scripts' option.
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\open\xdg-open
|
||||
@ -56,14 +38,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\etcher-sdk\node_modules\drivelist\scripts\win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\patch-package\node_modules\open\xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\patch-package\node_modules\open\xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot resolve 'path'
|
||||
node_modules\@balena\compose\dist\parse\schemas\index.js
|
||||
Dynamic require may fail at run time, because the requested file
|
@ -45,7 +45,7 @@ class MockLivepushManager extends LivepushManager {
|
||||
docker: {} as import('dockerode'),
|
||||
api: {} as import('../../../lib/utils/device/api').DeviceAPI,
|
||||
logger: {} as import('../../../lib/utils/logger'),
|
||||
buildLogs: {},
|
||||
imageIds: {},
|
||||
deployOpts:
|
||||
{} as import('../../../lib/utils/device/deploy').DeviceDeployOptions,
|
||||
});
|
||||
|
@ -63,8 +63,6 @@ describe('detectEncoding() function', function () {
|
||||
it('should correctly detect the encoding of a few selected files', async () => {
|
||||
const sampleBinary = [
|
||||
'drivelist/build/Release/drivelist.node',
|
||||
'@balena.io/usb/build/Release/usb_bindings.node',
|
||||
'xxhash/build/Release/hash.node',
|
||||
'mountutils/build/Release/MountUtils.node',
|
||||
];
|
||||
const sampleText = [
|
||||
|
27
typings/balena-image-manager/index.d.ts
vendored
27
typings/balena-image-manager/index.d.ts
vendored
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'balena-image-manager' {
|
||||
export function cleanCache(): Promise<void>;
|
||||
|
||||
export function get(
|
||||
deviceType: string,
|
||||
versionOrRange: string,
|
||||
): Promise<NodeJS.ReadableStream & { mime: string }>;
|
||||
|
||||
export function isESR(version: string): boolean;
|
||||
}
|
19
typings/balena-preload/index.d.ts
vendored
19
typings/balena-preload/index.d.ts
vendored
@ -1,19 +0,0 @@
|
||||
declare module 'balena-preload' {
|
||||
export class Preloader {
|
||||
constructor(...args: any[]);
|
||||
|
||||
cleanup(): Promise<void>;
|
||||
|
||||
on(...args: any[]): void;
|
||||
|
||||
preload(): Promise<void>;
|
||||
|
||||
prepare(): Promise<void>;
|
||||
|
||||
setAppIdAndCommit(appId: string | number, commit: string): Promise<void>;
|
||||
|
||||
config: any;
|
||||
|
||||
stderr: any;
|
||||
}
|
||||
}
|
41
typings/balena-sync/index.d.ts
vendored
41
typings/balena-sync/index.d.ts
vendored
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'balena-sync' {
|
||||
import { CommandDefinition } from 'capitano';
|
||||
|
||||
export function capitano(tool: 'balena-cli'): CommandDefinition;
|
||||
|
||||
export interface LocalBalenaOsDevice {
|
||||
address: string;
|
||||
host: string;
|
||||
osVariant: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
declare namespace forms {
|
||||
export function selectLocalBalenaOsDevice(
|
||||
timeout?: number,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
declare namespace discover {
|
||||
export function discoverLocalBalenaOsDevices(
|
||||
timeout?: number,
|
||||
): Promise<LocalBalenaOsDevice[]>;
|
||||
}
|
||||
}
|
23
typings/docker-toolbelt/index.d.ts
vendored
Normal file
23
typings/docker-toolbelt/index.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
declare module 'docker-toolbelt' {
|
||||
import * as Docker from 'dockerode';
|
||||
|
||||
interface ImageSpec {
|
||||
registry?: string;
|
||||
imageName: string;
|
||||
tagName: string;
|
||||
digest?: string;
|
||||
}
|
||||
|
||||
type ProgressCallback = (event: any) => void;
|
||||
|
||||
class DockerToolbelt extends Docker {
|
||||
public getRegistryAndName(image: string): Promise<ImageSpec>;
|
||||
public createDeltaAsync(
|
||||
src: string,
|
||||
dest: string,
|
||||
onProgress?: ProgressCallback,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
export = DockerToolbelt;
|
||||
}
|
23
typings/resin-cli-visuals/index.d.ts
vendored
23
typings/resin-cli-visuals/index.d.ts
vendored
@ -15,4 +15,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'resin-cli-visuals';
|
||||
declare module 'resin-cli-visuals' {
|
||||
export const Progress: new (...options: any[]) => any;
|
||||
|
||||
export class Spinner {
|
||||
constructor(message?: string);
|
||||
spinner: any;
|
||||
start(): void;
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
export const SpinnerPromise: new <T>(options: {
|
||||
promise: T;
|
||||
startMessage: string;
|
||||
stopMessage: string;
|
||||
}) => T;
|
||||
|
||||
export const table: {
|
||||
horizontal: (...options: any[]) => any;
|
||||
vertical: (...options: any[]) => any;
|
||||
};
|
||||
export const drive: (...options: any[]) => any;
|
||||
}
|
||||
|
Reference in New Issue
Block a user