Compare commits

..

2 Commits

247 changed files with 34585 additions and 13110 deletions

View File

@ -18,7 +18,7 @@ inputs:
default: 'accounts+apple@balena.io' default: 'accounts+apple@balena.io'
NODE_VERSION: NODE_VERSION:
type: string type: string
default: '20.x' default: '18.x'
VERBOSE: VERBOSE:
type: string type: string
default: 'true' default: 'true'
@ -28,7 +28,7 @@ runs:
using: 'composite' using: 'composite'
steps: steps:
- name: Download custom source artifact - name: Download custom source artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 uses: actions/download-artifact@v3
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }} path: ${{ runner.temp }}
@ -39,17 +39,11 @@ runs:
run: tar -xf ${{ runner.temp }}/custom.tgz run: tar -xf ${{ runner.temp }}/custom.tgz
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 uses: actions/setup-node@v3
with: with:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Set up Python 3.11
if: runner.os == 'macOS'
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
with:
python-version: "3.11"
- name: Install additional tools - name: Install additional tools
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: bash shell: bash
@ -66,7 +60,7 @@ runs:
# https://github.com/Apple-Actions/import-codesign-certs # https://github.com/Apple-Actions/import-codesign-certs
- name: Import Apple code signing certificate - name: Import Apple code signing certificate
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2 uses: apple-actions/import-codesign-certs@v1
with: with:
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }} p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
@ -75,11 +69,18 @@ runs:
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: powershell shell: powershell
run: | run: |
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:SM_CLIENT_CERT_FILE_B64 Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12 certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
Remove-Item -path ${{ runner.temp }} -include certificate.base64 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: env:
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }} 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/scripts/tree/master/shared
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml # https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
@ -99,21 +100,12 @@ runs:
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }} CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
elif [[ $runner_os =~ windows|win ]]; then elif [[ $runner_os =~ windows|win ]]; then
SM_HOST=${{ fromJSON(inputs.secrets).SM_HOST }} CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
SM_API_KEY=${{ fromJSON(inputs.secrets).SM_API_KEY }} CSC_LINK='${{ runner.temp }}\certificate.pfx'
SM_CLIENT_CERT_FILE='${{ runner.temp }}\Certificate_pkcs12.p12'
SM_CLIENT_CERT_PASSWORD=${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }}
SM_CODE_SIGNING_CERT_SHA1_HASH=${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }}
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \ # patches/all/oclif.patch
-H "x-api-key:$SM_API_KEY" \ MSYSSHELLPATH="$(which bash)"
-o smtools-windows-x64.msi MSYSTEM=MSYS
msiexec -i smtools-windows-x64.msi -qn
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
smksp_registrar.exe list
smctl.exe keypair ls
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks # (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}" PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
@ -127,17 +119,16 @@ runs:
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks # 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 # 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 CSC_FOR_PULL_REQUEST: true
# https://docs.digicert.com/es/software-trust-manager/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html # https://sectigo.com/resource-library/time-stamping-server
TIMESTAMP_SERVER: http://timestamp.digicert.com TIMESTAMP_SERVER: http://timestamp.sectigo.com
# Apple notarization (automation/build-bin.ts) # Apple notarization (automation/build-bin.ts)
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }} XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }} XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }} XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 uses: actions/upload-artifact@v3
with: with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }} name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
path: dist path: dist
retention-days: 1 retention-days: 1
if-no-files-found: error

View File

@ -15,7 +15,7 @@ inputs:
# --- custom environment # --- custom environment
NODE_VERSION: NODE_VERSION:
type: string type: string
default: '20.x' default: '18.x'
VERBOSE: VERBOSE:
type: string type: string
default: "true" default: "true"
@ -26,14 +26,14 @@ runs:
steps: steps:
# https://github.com/actions/setup-node#caching-global-packages-data # https://github.com/actions/setup-node#caching-global-packages-data
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4 uses: actions/setup-node@v3
with: with:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Set up Python 3.11 - name: Set up Python 3.11
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4 uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
with: with:
python-version: "3.11" python-version: "3.11"
@ -58,7 +58,7 @@ runs:
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz . run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
- name: Upload custom artifact - name: Upload custom artifact
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 uses: actions/upload-artifact@v3
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz path: ${{ runner.temp }}/custom.tgz

View File

@ -1,4 +0,0 @@
{
"extends": ["github>balena-io/renovate-config"],
"postUpdateOptions": ["npmDedupe"]
}

View File

@ -1,4 +1,5 @@
name: Flowzone name: Flowzone
on: on:
pull_request: pull_request:
types: [opened, synchronize, closed] types: [opened, synchronize, closed]
@ -6,6 +7,7 @@ on:
pull_request_target: pull_request_target:
types: [opened, synchronize, closed] types: [opened, synchronize, closed]
branches: [main, master] branches: [main, master]
jobs: jobs:
flowzone: flowzone:
name: Flowzone name: Flowzone
@ -21,25 +23,8 @@ jobs:
) )
secrets: inherit secrets: inherit
with: with:
custom_test_matrix: > custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-12"],["windows-2019"]]'
{ repo_config: true
"os": [ repo_description: "The official balena CLI tool."
["self-hosted", "X64"],
["self-hosted", "ARM64"],
["macos-12"],
["windows-2019"],
["macos-latest-xlarge"]
]
}
custom_publish_matrix: >
{
"os": [
["self-hosted", "X64"],
["self-hosted", "ARM64"],
["macos-12"],
["windows-2019"],
["macos-latest-xlarge"]
]
}
github_prerelease: false github_prerelease: false
restrict_custom_actions: false restrict_custom_actions: false

View File

@ -1 +0,0 @@
node automation/check-npm-version.js && ts-node automation/check-doc.ts

File diff suppressed because it is too large Load Diff

View File

@ -4,710 +4,6 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## 19.5.0 - 2024-10-17
* Add `key list` alias for `keys` command [myarmolinsky]
## 19.4.0 - 2024-10-16
* Add `release list` alias for `releases` command [myarmolinsky]
## 19.3.0 - 2024-10-16
* Add alias `fleet list` for `fleets` command [myarmolinsky]
## 19.2.0 - 2024-10-16
* Add alias `api-key list` for command `api-keys` [myarmolinsky]
## 19.1.3 - 2024-10-16
* Remove custom sorting of OS commands in docs in favor of alphabetizing [myarmolinsky]
## 19.1.2 - 2024-10-16
* Remove no longer needed dependency `get-stdin` [myarmolinsky]
* Remove custom override of oclif Command class in favor of `prerun` hook [myarmolinsky]
## 19.1.1 - 2024-10-14
* Deduplicate dependencies [myarmolinsky]
* Fix changelog entry for v19.1.0 [myarmolinsky]
## 19.1.0 - 2024-10-11
* Docs: Show aliases for commands [myarmolinsky]
* Add alias `device list` for `devices` command [myarmolinsky]
* Deduplicate dependencies [myarmolinsky]
## 19.0.20 - 2024-10-11
* Docs: Generate CLI command references from file names instead of usage [myarmolinsky]
* Use default oclif `USAGE` message for all commands [myarmolinsky]
## 19.0.19 - 2024-10-11
* Deduplicate dependencies [myarmolinsky]
* Fix update notification release notes link [myarmolinsky]
## 19.0.18 - 2024-10-08
* Contributing: No longer request separate folders for plural commands [myarmolinsky]
## 19.0.17 - 2024-10-08
* Remove dev dependency `parse-link-header` [myarmolinsky]
* Remove dev dependency @octokit/rest [myarmolinsky]
* Remove dev dependency @octokit/plugin-throttling [myarmolinsky]
* Remove no longer needed references and tests for mixpanel [myarmolinsky]
* Remove dev dependency `@types/mixpanel` [myarmolinsky]
## 19.0.16 - 2024-10-08
* compose: Reduce the properties updated to only the necessary [Thodoris Greasidis]
## 19.0.15 - 2024-10-08
* Remove unused `mockery` dev dependency [myarmolinsky]
## 19.0.14 - 2024-10-08
* Temporarily skip broken image-manager tests on Windows and Mac [myarmolinsky]
## 19.0.13 - 2024-09-23
* Remove extra line from recent changelog entry [myarmolinsky]
## 19.0.12 - 2024-09-20
* Add `image-manager` tests [myarmolinsky]
* Remove `balena-image-manager` dependency [myarmolinsky]
* Embed `balena-image-manager` instead of having it as a dependency [myarmolinsky]
## 19.0.11 - 2024-09-18
* Remove Bluebird as a direct dependency [Thodoris Greasidis]
## 19.0.10 - 2024-09-12
* Remove package `@resin.io/valid-email` [myarmolinsky]
## 19.0.9 - 2024-09-12
* Update actions/download-artifact action to v4.1.8 [Self-hosted Renovate Bot]
## 19.0.8 - 2024-09-12
* Update actions/upload-artifact digest to 5076954 [Self-hosted Renovate Bot]
## 19.0.7 - 2024-09-12
* Update actions/setup-node digest to 1e60f62 [Self-hosted Renovate Bot]
## 19.0.6 - 2024-09-12
* Remove moment and moment-duration-format in favor of native time parsing [Otavio Jacobi]
## 19.0.5 - 2024-09-10
* Update apple-actions/import-codesign-certs action to v2 [Self-hosted Renovate Bot]
## 19.0.4 - 2024-09-10
* Update TypeScript to 5.6.2 [Thodoris Greasidis]
## 19.0.3 - 2024-09-05
* Reduce use of CJS require() on automation files [Otavio Jacobi]
* Remove the use of CJS require() on test files [Otavio Jacobi]
* Remove not necessary 'import = require' syntax for js-yaml [Otavio Jacobi]
## 19.0.2 - 2024-09-03
* Update devDependency patch-package to v8.0.0 [Otavio Jacobi]
* Update devDependency mkdirp to v3.0.1 [Otavio Jacobi]
* Update devDependency fs-extra(to v11) and @types/fs-extra(to v11) [Otavio Jacobi]
* Update devDependency @types/parse-link-header to v2.0.3 [Otavio Jacobi]
* Remove unused devDependency @types/nock [Otavio Jacobi]
* Update devDependency klaw(to v4) and @types/klaw(to v3.0.6) [Otavio Jacobi]
* Update husky to v9.1.5 [Otavio Jacobi]
* Update devDependency @types/jsonwebtoken to v9.0.6 [Otavio Jacobi]
* Update devDependency archiver(to v7) and @types/archiver(to v6) [Otavio Jacobi]
* Removes unused devDependency @types/net-keepalive [Otavio Jacobi]
* Update devDependency rewire(to v7) and @types/rewire(to v2.5.30) [Otavio Jacobi]
* Update devDependency sinon(to v18) and @types/sinon(to v17) [Otavio Jacobi]
## 19.0.1 - 2024-09-02
* Bump @oclif/core from 3.27.0 to 4.0.18 [Otavio Jacobi]
## 19.0.0 - 2024-08-22
* Update all references of lib to src [Otavio Jacobi]
* Rename the lib folder to src [Otavio Jacobi]
* Update @balena/compose to 4.0.1 Update @balena/compose from 3.2.1 to 4.0.1 [Otavio Jacobi]
* Use standard visuals table component for fleet/s [Otavio Jacobi]
## 18.2.34 - 2024-07-29
* Run npm dedupe commands [Kyle Harding]
* Switch to self-hosted [Anton Belodedenko]
## 18.2.33 - 2024-07-17
* Improve discover balena os across different networks [Otavio Jacobi]
## 18.2.32 - 2024-07-16
* Remove unused code [Otavio Jacobi]
## 18.2.31 - 2024-07-15
* deploy: Use the sdk's pine instance with balena-compose [Thodoris Greasidis]
<details>
<summary> Update balena-sdk to 19.7.3 [Thodoris Greasidis] </summary>
> ### balena-sdk-19.7.3 - 2024-07-12
>
> * pinejs-client-core: Add some missing methods to the custom typings [Thodoris Greasidis]
>
</details>
## 18.2.30 - 2024-07-15
* Omit unicode control character escapes from test logs [Thodoris Greasidis]
## 18.2.29 - 2024-07-12
* Update balena-preload from 15.0.5 to 15.0.6 [Otavio Jacobi]
## 18.2.28 - 2024-07-12
* Downgrade pinejs-client-request to 7.4.2 to unblock the sdk update [Thodoris Greasidis]
<details>
<summary> Update balena-sdk to 19.7.2 [Thodoris Greasidis] </summary>
> ### balena-sdk-19.7.2 - 2024-07-12
>
>
> <details>
> <summary> Update balena-request from 13.3.1 to 13.3.2 [Thodoris Greasidis] </summary>
>
>> #### balena-request-13.3.2 - 2024-07-12
>>
>> * Fix always following redirects when followRedirect = false [Thodoris Greasidis]
>>
>
> </details>
>
>
> ### balena-sdk-19.7.1 - 2024-07-08
>
>
> <details>
> <summary> Limit pinejs-client-core to ~6.14.0, to fix errors in older TypeScript [Thodoris Greasidis] </summary>
>
>> #### pinejs-client-js-6.14.0 - 2023-12-05
>>
>> * Respect the Retry-After header when clients define the getRetryAfterHeader option [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.13.0 - 2023-07-11
>>
>> * Add support for $duration [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.12.4 - 2023-05-09
>>
>> * Avoid an unnecessary function creation on each get() call [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.12.3 - 2022-12-28
>>
>> * CI: Convert tests to TypeScript [Josh Bowling]
>>
>> #### pinejs-client-js-6.12.2 - 2022-11-18
>>
>> * Fix `$orderby: { a: { $count: ... }, $dir: 'asc' }` typings [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.12.1 - 2022-11-15
>>
>> * Update TypeScript to 4.9.3 [Thodoris Greasidis]
>>
>
> </details>
>
> * Fix the TypeScript incompatibility test [Thodoris Greasidis]
>
> ### balena-sdk-19.7.0 - 2024-07-05
>
> * Add identity provider & saml account model typing [Otavio Jacobi]
>
> ### balena-sdk-19.6.1 - 2024-06-20
>
> * Update TypeScript to 5.5.2 [Thodoris Greasidis]
>
> ### balena-sdk-19.6.0 - 2024-06-20
>
> * Add the application.getAllByOrganization() method [Thodoris Greasidis]
> * Deprecate the application.getAppByOwner() method [Thodoris Greasidis]
>
> ### balena-sdk-19.5.11 - 2024-05-28
>
> * tests: Make the cleanups more precise [Thodoris Greasidis]
>
> ### balena-sdk-19.5.10 - 2024-03-29
>
> * Drop the toWritable helper in favor of TypeScript's satisfies [Thodoris Greasidis]
>
> ### balena-sdk-19.5.9 - 2024-03-29
>
> * os: Update the comments on why we still need to be using the release_tags [Thodoris Greasidis]
>
> ### balena-sdk-19.5.8 - 2024-03-18
>
> * Fix `application.create` method being wrongly marked as deprecated [myarmolinsky]
>
> ### balena-sdk-19.5.7 - 2024-03-08
>
> * Fix missing underscore to describes__device property [Andrea Rosci]
>
> ### balena-sdk-19.5.6 - 2024-03-07
>
> * Update TypeScript to 5.4.2 [Thodoris Greasidis]
> * device-type.getInstructions: Convert etcher link to HTTPS [Vipul Gupta (@vipulgupta2048)]
>
</details>
## 18.2.27 - 2024-07-12
<details>
<summary> Update balena-sdk to 19.5.5 [Thodoris Greasidis] </summary>
> ### balena-sdk-19.5.5 - 2024-02-26
>
>
> <details>
> <summary> Update balena-auth to 6.0.1 [Thodoris Greasidis] </summary>
>
>> #### balena-auth-6.0.1 - 2024-02-23
>>
>> * Update jwt-decode to v3 [Thodoris Greasidis]
>>
>> #### balena-auth-6.0.0 - 2024-02-23
>>
>> * Update typescript to 5.3.3 [Thodoris Greasidis]
>> * Move the sources from lib to src [Thodoris Greasidis]
>> * Update @balena/lint to v7 [Thodoris Greasidis]
>> * Stop publishing the lib folder [Thodoris Greasidis]
>> * Drop support for nodejs < 18 [Thodoris Greasidis]
>> * Drop no longer used appveyor.yml [Thodoris Greasidis]
>>
>> #### balena-register-device-9.0.2 - 2024-02-23
>>
>> * Update @balena/lint to v7 [Thodoris Greasidis]
>> * Update balena-request to 13.3.0 [Thodoris Greasidis]
>>
>> #### balena-request-13.3.1 - 2024-02-23
>>
>> * Update balena-auth to 6.0.1 [Thodoris Greasidis]
>>
>
> </details>
>
>
> ### balena-sdk-19.5.4 - 2024-02-14
>
> * Bump balena-request Update balena-request from 13.2.0 to 13.3.0 [Otávio Jacobi]
>
> ### balena-sdk-19.5.3 - 2024-02-14
>
> * Replace deprecated flowzone input tests_run_on [Kyle Harding]
>
> ### balena-sdk-19.5.2 - 2024-02-13
>
> * tests: Reformat describe & it calls to have curly braces [Thodoris Greasidis]
>
> ### balena-sdk-19.5.1 - 2024-02-02
>
> * Update @balena/lint to 7.3.0 [Thodoris Greasidis]
>
> ### balena-sdk-19.5.0 - 2024-01-26
>
> * types: Add the `Organization.is_using__billing_version` property [Thodoris Greasidis]
>
</details>
## 18.2.26 - 2024-07-12
* Drop unused dependencies [Otavio Jacobi]
* Move dependencies that should be dev only as devDependencies [Otavio Jacobi]
## 18.2.25 - 2024-07-11
* Fix complete generation intermitency [Otavio Jacobi]
* Bump oclif to v4 [Otavio Jacobi]
## 18.2.24 - 2024-07-10
* Update mocha from 8.4.0 to 10.6.0 [Otavio Jacobi]
* Override inline-source-cli with non-vulnerable dependency [Otavio Jacobi]
## 18.2.23 - 2024-07-10
* Replace resin-discoverable-services with bonjour-service [Otavio Jacobi]
## 18.2.22 - 2024-07-10
* Remove unused dependency minimatch [Otavio Jacobi]
## 18.2.21 - 2024-07-09
* Bump resin-discoverable-services from 2.0.4 to 2.0.5 [Otavio Jacobi]
## 18.2.20 - 2024-07-05
* Audit fix dependencies [Otavio Jacobi]
## 18.2.19 - 2024-07-05
* Remove unused package `publish-release` [myarmolinsky]
## 18.2.18 - 2024-07-04
* Update actions/setup-node action to v4 [Self-hosted Renovate Bot]
## 18.2.17 - 2024-07-02
<details>
<summary> Update dependency etcher-sdk to v9.1.0 [Self-hosted Renovate Bot] </summary>
> ### etcher-sdk-9.1.0 - 2024-06-13
>
> * patch: etcher-sdk is not yet compatible with node22 [JOASSART Edwin]
> * minor: allow passing custom assets to start SB protected CM4 [Edwin Joassart]
>
</details>
## 18.2.16 - 2024-07-02
<details>
<summary> Update dependency etcher-sdk to v9.0.11 [Self-hosted Renovate Bot] </summary>
> ### etcher-sdk-9.0.11 - 2024-04-26
>
> * patch: use http2 to fix issues with url source [Edwin Joassart]
>
> ### etcher-sdk-9.0.10 - 2024-04-26
>
> * patch: remove CI workaround [Edwin Joassart]
>
> ### etcher-sdk-9.0.9 - 2024-04-24
>
> * patch: add option to allow listing virtual drive on Mac [JOASSART Edwin]
>
</details>
## 18.2.15 - 2024-07-02
* Update dependency event-stream to v3.3.5 [Self-hosted Renovate Bot]
## 18.2.14 - 2024-07-02
* Update dependency jsonwebtoken to v9 [SECURITY] [Self-hosted Renovate Bot]
## 18.2.13 - 2024-07-02
* Update dependency @types/prettyjson to ^0.0.33 [Self-hosted Renovate Bot]
## 18.2.12 - 2024-07-02
* Deduplicate dependencies [Thodoris Greasidis]
## 18.2.11 - 2024-07-01
* Update dependency @types/fast-levenshtein to v0.0.4 [Self-hosted Renovate Bot]
## 18.2.10 - 2024-06-21
* Update actions/download-artifact action to v4.1.7 [Self-hosted Renovate Bot]
## 18.2.9 - 2024-06-21
* Update actions/setup-python digest to 65d7f2d [Self-hosted Renovate Bot]
## 18.2.8 - 2024-06-21
* Update actions/upload-artifact digest to 6546280 [Self-hosted Renovate Bot]
## 18.2.7 - 2024-06-21
* Pin dependencies [Self-hosted Renovate Bot]
## 18.2.6 - 2024-06-21
* Update @oclif/core from 3.26.9 to 3.27.0 [Otavio Jacobi]
## 18.2.5 - 2024-06-21
* Limit @oclif/core to ~3.26 so that npm dedupe doesn't auto-bump it [Thodoris Greasidis]
* Update TypeScript to 5.5.2 [Thodoris Greasidis]
## 18.2.4 - 2024-05-17
* patch: fix outdated doc for "os configure" [Edwin Joassart]
## 18.2.3 - 2024-05-15
* Pluralize command categories in docs [dfunckt]
## 18.2.2 - 2024-04-30
* Upgrade dockerode and docker-modem dependencies [Ken Bannister]
## 18.2.1 - 2024-04-23
* Use Actuated runners for Linux test and publish [Kyle Harding]
## 18.2.0 - 2024-04-17
* build: Auto-resolve the cpu arch when the --deviceType is provided [Thodoris Greasidis]
## 18.1.10 - 2024-04-16
* Mark node 20.6.0 as the minimum working version [Thodoris Greasidis]
## 18.1.9 - 2024-04-10
* Enable npm dedupe as part of Renovate postUpdateOptions [Kyle Harding]
## 18.1.8 - 2024-04-09
* Bump patch-package to 6.5.1 [Thodoris Greasidis]
* npm-shrinkwrap.json: Recreate with lockfileVersion 3 [Thodoris Greasidis]
## 18.1.7 - 2024-04-09
<details>
<summary> Update balena-preload to 15.0.5 [Thodoris Greasidis] </summary>
> ### balena-preload-15.0.5 - 2024-04-09
>
> * Remove unused dependencies [Otavio Jacobi]
>
</details>
## 18.1.6 - 2024-04-09
* Update @oclif/core to 3.26.2 [Thodoris Greasidis]
* Drop the keep-alive package in favor of node's setKeepAlive defaults [Thodoris Greasidis]
* Update balena-preload to v15.0.4 [Thodoris Greasidis]
* Update resin-cli-form to v3 [Thodoris Greasidis]
* Update resin-cli-visuals to v2 [Thodoris Greasidis]
* Update balena-device-init to v7.0.1 [Thodoris Greasidis]
* Update etcher-sdk to v9.0.8 [Thodoris Greasidis]
* Mark bin/dev & bin/run as executable [Thodoris Greasidis]
## 18.1.5 - 2024-03-14
* Move klaw library to dev dependency [Otavio Jacobi]
## 18.1.4 - 2024-03-14
* Update @balena/lint to 8.0.0 [myarmolinsky]
## 18.1.3 - 2024-03-14
* Use standard oclif run.js & dev.js [Otavio Jacobi]
## 18.1.2 - 2024-03-13
* Move macos binary signing to oclif pretarball lifecycle [Otavio Jacobi]
## 18.1.1 - 2024-03-12
* Remove patching tmp for windows runners [Otavio Jacobi]
## 18.1.0 - 2024-03-12
* Add support for macos arm64 builds [Otavio Jacobi]
## 18.0.4 - 2024-03-11
* Update dependencies [Otavio Jacobi]
## 18.0.3 - 2024-03-11
* Removes signing patches [Otavio Jacobi]
## 18.0.2 - 2024-03-07
* Remove no longer needed windows oclif patches [Otavio Jacobi]
## 18.0.1 - 2024-03-07
* Fix windows signing [Otavio Jacobi]
## 18.0.0 - 2024-02-06
* Update to Node 20 [Otávio Jacobi]
## 17.5.1 - 2024-01-31
* Fix target state construction with livepush [Felipe Lalanne]
## 17.5.0 - 2024-01-23
* os versions: Add the --include-draft option [Thodoris Greasidis]
* device os-update: Add option for including pre-release versions in the list [Thodoris Greasidis]
* device os-update: Enable updates to pre-release versions of higher base semver [Thodoris Greasidis]
<details>
<summary> Update balena-sdk to 19.4.0 [Thodoris Greasidis] </summary>
> ### balena-sdk-19.4.0 - 2024-01-23
>
> * Update the deviceType.getInstructions tests [Thodoris Greasidis]
> * os.getSupportedOsUpdateVersions: Add the option to include draft releases [Thodoris Greasidis]
>
> <details>
> <summary> Enable OS Updates to pre-release versions of higher base semver [Thodoris Greasidis] </summary>
>
>> #### balena-hup-action-utils-6.1.0 - 2024-01-04
>>
>> * Enable OS Updates to pre-release versions of higher base semver [Thodoris Greasidis]
>>
>> #### balena-hup-action-utils-6.0.0 - 2023-12-20
>>
>> * Drop support for TypeScript < 5.3.3 [Thodoris Greasidis]
>> * Drop support for node < v18 [Thodoris Greasidis]
>> * Update dependencies [Thodoris Greasidis]
>> * Move the build step from prepare to prepack [Thodoris Greasidis]
>>
>> #### balena-hup-action-utils-5.0.1 - 2023-07-13
>>
>> * patch: Update flowzone.yml [Kyle Harding]
>>
>
> </details>
>
> * os.getAvailableOsVersions: Add the option to include draft releases [Thodoris Greasidis]
>
> ### balena-sdk-19.3.5 - 2023-12-21
>
> * Update date-fns to v3 [Thodoris Greasidis]
>
> ### balena-sdk-19.3.4 - 2023-12-15
>
> * types/Device: Deprecate the non-existent vpn_address property [Otávio Jacobi]
>
> ### balena-sdk-19.3.3 - 2023-12-15
>
> * types/Device: Deprecate the non-existent state & status_sort_index properties [Thodoris Greasidis]
>
> ### balena-sdk-19.3.2 - 2023-12-08
>
> * test:fast: Run the tests ignoring any linting errors [Thodoris Greasidis]
> * tests: Re-enable the explicit error checks for non-tarball DWB requests [Thodoris Greasidis]
>
> ### balena-sdk-19.3.1 - Invalid date
>
> * Update TypeScript to 5.3.2 [Thodoris Greasidis]
>
> ### balena-sdk-19.3.0 - Invalid date
>
> * tests: Remove the explicit error checks for non-tarball DWB requests [Thodoris Greasidis]
> * tests: Properly cleanup the test orgs [Thodoris Greasidis]
> * tests: Reduce the request batching chunk size to speed up tests [Thodoris Greasidis]
> * Add option for configuring the request batching chunk size [Thodoris Greasidis]
>
> ### balena-sdk-19.2.0 - 2023-11-13
>
> * Add organization logo to organization [Otávio Jacobi]
>
> ### balena-sdk-19.1.0 - 2023-11-06
>
> * Add the retryRateLimitedRequests sdk option for retrying after HTTP 429s [Thodoris Greasidis]
>
</details>
## 17.4.12 - 2024-01-18
* Regression described in GitHub Issue 2715; balena push hangs in local mode. [Ken Bannister]
## 17.4.11 - 2024-01-05
* Exclude the oclif package patch from the published files [Thodoris Greasidis]
* Update the @oclif/core patch [Thodoris Greasidis]
## 17.4.10 - 2024-01-02
* Normalize v prefixes in the --version parameter of all commands [Thodoris Greasidis]
## 17.4.9 - 2023-12-19
* Fix publishing artifacts to gh release [Otávio Jacobi]
## 17.4.8 - 2023-12-19
* Remove repo config from flowzone.yml [Kyle Harding]
## 17.4.7 - 2023-12-19
* deploy: Add rate-limiting aware retries for failed requests [Thodoris Greasidis]
<details>
<summary> Update dependencies [Thodoris Greasidis] </summary>
> ### balena-compose-3.2.0 - 2023-12-05
>
> * release/createClient: Allow specifying the "retry" options [Thodoris Greasidis]
>
> ### balena-compose-3.1.3 - 2023-11-29
>
> * Update dockerode to 3.3.5 [Pagan Gazzard]
>
> ### balena-compose-3.1.2 - 2023-11-29
>
> * Use the JSONStream typings from @types/jsonstream [Pagan Gazzard]
>
> ### balena-compose-3.1.1 - 2023-11-27
>
> * Make use of `pipeline` for piping streams together [Pagan Gazzard]
>
> ### balena-compose-3.1.0 - 2023-11-13
>
> * Allow injecting any PinejsClientCore compatible API client [Thodoris Greasidis]
>
> ### balena-compose-3.0.7 - 2023-11-10
>
> * NodeResolver: Refactor the recursion to an async-await loop [Thodoris Greasidis]
> * Drop bluebird & bluebird-lru-cache in favor of memoizee [Thodoris Greasidis]
>
> ### balena-compose-3.0.6 - 2023-11-10
>
> * Fix the remaining linting errors [Thodoris Greasidis]
> * Remove unnecessary regex escaping [Thodoris Greasidis]
> * Replace the {} type with object [Thodoris Greasidis]
> * Update TypeScript to 5.2.2 and @blaena/lint to v7.2.1 [Thodoris Greasidis]
>
</details>
## 17.4.6 - 2023-12-08
* Bump oclif core & use default missing flag handler [Otávio Jacobi]
## 17.4.5 - 2023-12-04 ## 17.4.5 - 2023-12-04
* Stop testing dependency deduplication on the custom test runners [Thodoris Greasidis] * Stop testing dependency deduplication on the custom test runners [Thodoris Greasidis]

View File

@ -115,9 +115,9 @@ The content sources for the auto generation of `docs/balena-cli.md` are:
* [Selected * [Selected
sections](https://github.com/balena-io/balena-cli/blob/v12.23.0/automation/capitanodoc/capitanodoc.ts#L199-L204) sections](https://github.com/balena-io/balena-cli/blob/v12.23.0/automation/capitanodoc/capitanodoc.ts#L199-L204)
of the README file. of the README file.
* The CLI's command documentation in source code (`src/commands/` folder), for example: * The CLI's command documentation in source code (`lib/commands/` folder), for example:
* `src/commands/push.ts` * `lib/commands/push.ts`
* `src/commands/env/add.ts` * `lib/commands/env/add.ts`
The README file is manually edited, but subsections are automatically extracted for inclusion in The README file is manually edited, but subsections are automatically extracted for inclusion in
`docs/balena-cli.md` by the `getCapitanoDoc()` function in `docs/balena-cli.md` by the `getCapitanoDoc()` function in
@ -133,6 +133,7 @@ To add a new command to be documented,
1. Find the resource which it is part of or create a new one. 1. Find the resource which it is part of or create a new one.
2. List the location of the build file 2. List the location of the build file
3. Make sure to add your files in alphabetical order 3. Make sure to add your files in alphabetical order
4. Resources with plural names needs to have 2 sections if they have commands like: "fleet, fleets" or "device, devices" or "tag, tags"
Once added, run the command `npm run build` to generate the documentation Once added, run the command `npm run build` to generate the documentation
@ -223,7 +224,7 @@ command, or by manually editing or copying files to the `node_modules` folder.
Unexpected behavior may then be observed because of the CLI's use of the Unexpected behavior may then be observed because of the CLI's use of the
[fast-boot2](https://www.npmjs.com/package/fast-boot2) package that caches module resolution. [fast-boot2](https://www.npmjs.com/package/fast-boot2) package that caches module resolution.
`fast-boot2` is configured in `src/fast-boot.ts` to automatically invalidate the cache if `fast-boot2` is configured in `lib/fast-boot.ts` to automatically invalidate the cache if
changes are made to the `package.json` or `npm-shrinkwrap.json` files, but the cache won't changes are made to the `package.json` or `npm-shrinkwrap.json` files, but the cache won't
be automatically invalidated if `npm link` is used or if manual modifications are made to the be automatically invalidated if `npm link` is used or if manual modifications are made to the
`node_modules` folder. In this situation: `node_modules` folder. In this situation:

View File

@ -40,7 +40,7 @@ By default, the CLI is installed to the following folders:
OS | Folders OS | Folders
--- | --- --- | ---
Windows: | `C:\Program Files\balena-cli\` Windows: | `C:\Program Files\balena-cli\`
macOS: | `/usr/local/src/balena-cli/` <br> `/usr/local/bin/balena` macOS: | `/usr/local/lib/balena-cli/` <br> `/usr/local/bin/balena`
## Standalone Zip Package ## Standalone Zip Package
@ -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 The npm installation involves building native (platform-specific) binary modules, which require
some development tools to be installed first, as follows. some development tools to be installed first, as follows.
> **The balena CLI currently requires Node.js version ^20.6.0** > **The balena CLI currently requires Node.js version 18.**
> **Versions 21 and later are not yet fully supported.** > **Versions 19 and later are not yet fully supported.**
### Install development tools ### 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++ $ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc $ . ~/.bashrc
$ nvm install 20 $ nvm install 18
``` ```
The `curl` command line above uses The `curl` command line above uses
@ -106,7 +106,7 @@ recommended.
```sh ```sh
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc $ . ~/.bashrc
$ nvm install 20 $ nvm install 18
``` ```
#### **Windows** (not WSL) #### **Windows** (not WSL)
@ -114,7 +114,7 @@ $ nvm install 20
Install: Install:
* If you'd like the ability to switch between Node.js versions, install * If you'd like the ability to switch between Node.js versions, install
- Node.js v20 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/). - Node.js v18 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
instead. instead.
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more: * The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:

View File

@ -27,7 +27,7 @@ To update the balena CLI, repeat the steps above for the new version.
To uninstall it, run the following command on a terminal prompt: To uninstall it, run the following command on a terminal prompt:
```text ```text
sudo /usr/local/src/balena-cli/bin/uninstall sudo /usr/local/lib/balena-cli/bin/uninstall
``` ```
## Additional Dependencies ## Additional Dependencies

View File

@ -15,13 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
import type { JsonVersions } from '../src/commands/version/index'; import type { JsonVersions } from '../lib/commands/version/index';
import { run as oclifRun } from '@oclif/core'; import { run as oclifRun } from '@oclif/core';
import * as archiver from 'archiver'; import * as archiver from 'archiver';
import { exec, execFile } from 'child_process'; import * as Bluebird from 'bluebird';
import { execFile } from 'child_process';
import * as filehound from 'filehound'; import * as filehound from 'filehound';
import type { Stats } from 'fs'; import { Stats } from 'fs';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as klaw from 'klaw'; import * as klaw from 'klaw';
import * as path from 'path'; import * as path from 'path';
@ -40,8 +41,6 @@ import {
} from './utils'; } from './utils';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
const rimrafAsync = promisify(rimraf);
export const packageJSON = loadPackageJson(); export const packageJSON = loadPackageJson();
export const version = 'v' + packageJSON.version; export const version = 'v' + packageJSON.version;
@ -61,13 +60,9 @@ const standaloneZips: PathByPlatform = {
win32: dPath(`balena-cli-${version}-windows-${arch}-standalone.zip`), win32: dPath(`balena-cli-${version}-windows-${arch}-standalone.zip`),
}; };
const getOclifInstallersOriginalNames = async (): Promise<PathByPlatform> => { const oclifInstallers: PathByPlatform = {
const { stdout } = await execAsync('git rev-parse --short HEAD'); darwin: dPath('macos', `balena-${version}.pkg`),
const sha = stdout.trim(); win32: dPath('win32', `balena-${version}-${arch}.exe`),
return {
darwin: dPath('macos', `balena-${version}-${sha}-${arch}.pkg`),
win32: dPath('win32', `balena-${version}-${sha}-${arch}.exe`),
};
}; };
const renamedOclifInstallers: PathByPlatform = { const renamedOclifInstallers: PathByPlatform = {
@ -160,7 +155,7 @@ ${sep}
* messages (stdout and stderr) in order to call diffPkgOutput(). * messages (stdout and stderr) in order to call diffPkgOutput().
*/ */
async function execPkg(...args: any[]) { async function execPkg(...args: any[]) {
const { exec: pkgExec } = await import('@yao-pkg/pkg'); const { exec: pkgExec } = await import('pkg');
const outTap = new StdOutTap(true); const outTap = new StdOutTap(true);
try { try {
outTap.tap(); outTap.tap();
@ -187,10 +182,10 @@ async function execPkg(...args: any[]) {
async function buildPkg() { async function buildPkg() {
// https://github.com/vercel/pkg#targets // https://github.com/vercel/pkg#targets
let targets = `linux-${arch}`; let targets = `linux-${arch}`;
// TBC: not possible to build for macOS or Windows arm64 on x64 nodes
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
targets = `macos-${arch}`; targets = `macos-x64`;
} }
// TBC: not yet possible to build for Windows arm64 on x64 nodes
if (process.platform === 'win32') { if (process.platform === 'win32') {
targets = `win-x64`; targets = `win-x64`;
} }
@ -326,11 +321,7 @@ async function zipPkg() {
}); });
} }
export async function signFilesForNotarization() { async function signFilesForNotarization() {
console.log('Signing files for notarization');
if (process.platform !== 'darwin') {
return;
}
console.log('Deleting unneeded zip files...'); console.log('Deleting unneeded zip files...');
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
klaw('node_modules/') klaw('node_modules/')
@ -430,7 +421,6 @@ export async function buildStandaloneZip() {
} }
async function renameInstallerFiles() { async function renameInstallerFiles() {
const oclifInstallers = await getOclifInstallersOriginalNames();
if (await fs.pathExists(oclifInstallers[process.platform])) { if (await fs.pathExists(oclifInstallers[process.platform])) {
await fs.rename( await fs.rename(
oclifInstallers[process.platform], oclifInstallers[process.platform],
@ -445,20 +435,18 @@ async function renameInstallerFiles() {
* https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe * https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
*/ */
async function signWindowsInstaller() { async function signWindowsInstaller() {
if (process.env.SM_CODE_SIGNING_CERT_SHA1_HASH) { if (process.env.CSC_LINK && process.env.CSC_KEY_PASSWORD) {
const exeName = renamedOclifInstallers[process.platform]; const exeName = renamedOclifInstallers[process.platform];
console.log(`Signing installer "${exeName}"`); console.log(`Signing installer "${exeName}"`);
// trust ... // trust ...
await execFileAsync('signtool.exe', [ await execFileAsync('signtool.exe', [
'sign', 'sign',
'-sha1', '-t',
process.env.SM_CODE_SIGNING_CERT_SHA1_HASH,
'-tr',
process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com', process.env.TIMESTAMP_SERVER || 'http://timestamp.comodoca.com',
'-td', '-f',
'SHA256', process.env.CSC_LINK,
'-fd', '-p',
'SHA256', process.env.CSC_KEY_PASSWORD,
'-d', '-d',
`balena-cli ${version}`, `balena-cli ${version}`,
exeName, exeName,
@ -503,7 +491,7 @@ export async function buildOclifInstaller() {
let packOpts = ['-r', ROOT]; let packOpts = ['-r', ROOT];
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
packOS = 'macos'; packOS = 'macos';
packOpts = packOpts.concat('--targets', `darwin-${arch}`); packOpts = packOpts.concat('--targets', 'darwin-x64');
} else if (process.platform === 'win32') { } else if (process.platform === 'win32') {
packOS = 'win'; packOS = 'win';
packOpts = packOpts.concat('--targets', 'win32-x64'); packOpts = packOpts.concat('--targets', 'win32-x64');
@ -517,7 +505,11 @@ export async function buildOclifInstaller() {
} }
for (const dir of dirs) { for (const dir of dirs) {
console.log(`rimraf(${dir})`); console.log(`rimraf(${dir})`);
await rimrafAsync(dir); await Bluebird.fromCallback((cb) => rimraf(dir, cb));
}
if (process.platform === 'darwin') {
console.log('Signing files for notarization...');
await signFilesForNotarization();
} }
console.log('======================================================='); console.log('=======================================================');
console.log(`oclif ${packCmd} ${packOpts.join(' ')}`); console.log(`oclif ${packCmd} ${packOpts.join(' ')}`);

View File

@ -28,7 +28,7 @@ import { GlobSync } from 'glob';
* *
* IMPORTANT * IMPORTANT
* *
* All commands need to be stored under a folder in src/commands to maintain uniformity * All commands need to be stored under a folder in lib/commands to maintain uniformity
* Generating docs will error out if directive not followed * Generating docs will error out if directive not followed
* To add a custom heading for command docs, add the heading next to the folder name * To add a custom heading for command docs, add the heading next to the folder name
* in the `commandHeadings` dictionary. * in the `commandHeadings` dictionary.
@ -36,6 +36,9 @@ import { GlobSync } from 'glob';
* This dictionary is the source of truth that creates the docs config which is used * This dictionary is the source of truth that creates the docs config which is used
* to generate the CLI documentation. By default, the folder name will be used. * to generate the CLI documentation. By default, the folder name will be used.
* *
* Resources with plural names needs to have 2 sections if they have commands like:
* "fleet, fleets" or "device, devices" or "tag, tags"
*
*/ */
interface Category { interface Category {
@ -51,14 +54,16 @@ interface Documentation {
// Mapping folders names to custom headings in the docs // Mapping folders names to custom headings in the docs
const commandHeadings: { [key: string]: string } = { const commandHeadings: { [key: string]: string } = {
'api-key': 'API Keys', 'api-key': 'API Key',
'api-keys': 'API Keys',
login: 'Authentication', login: 'Authentication',
whoami: 'Authentication', whoami: 'Authentication',
logout: 'Authentication', logout: 'Authentication',
env: 'Environment Variables', env: 'Environment Variable',
envs: 'Environment Variables', envs: 'Environment Variables',
help: 'Help and Version', help: 'Help and Version',
key: 'SSH Keys', key: 'SSH Key',
keys: 'SSH Keys',
orgs: 'Organizations', orgs: 'Organizations',
os: 'OS', os: 'OS',
util: 'Utilities', util: 'Utilities',
@ -68,12 +73,6 @@ const commandHeadings: { [key: string]: string } = {
build: 'Deploy', build: 'Deploy',
join: 'Platform', join: 'Platform',
leave: 'Platform', leave: 'Platform',
app: 'Apps',
block: 'Blocks',
device: 'Devices',
fleet: 'Fleets',
release: 'Releases',
tag: 'Tags',
}; };
// Fetch all available commands // Fetch all available commands

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { Command as OclifCommandClass } from '@oclif/core'; import { Command as OclifCommandClass } from '@oclif/core';
type OclifCommand = typeof OclifCommandClass; type OclifCommand = typeof OclifCommandClass;
@ -26,7 +26,7 @@ export interface Document {
export interface Category { export interface Category {
title: string; title: string;
commands: Array<OclifCommand & { name: string }>; commands: OclifCommand[];
} }
export { OclifCommand }; export { OclifCommand };

View File

@ -16,8 +16,9 @@
*/ */
import * as path from 'path'; import * as path from 'path';
import { getCapitanoDoc } from './capitanodoc'; import { getCapitanoDoc } from './capitanodoc';
import type { Category, Document, OclifCommand } from './doc-types'; import { Category, Document, OclifCommand } from './doc-types';
import * as markdown from './markdown'; import * as markdown from './markdown';
import { stripIndent } from '../../lib/utils/lazy';
/** /**
* Generates the markdown document (as a string) for the CLI documentation * Generates the markdown document (as a string) for the CLI documentation
@ -38,7 +39,7 @@ export async function renderMarkdown(): Promise<string> {
}; };
for (const jsFilename of commandCategory.files) { for (const jsFilename of commandCategory.files) {
category.commands.push(await importOclifCommands(jsFilename)); category.commands.push(...importOclifCommands(jsFilename));
} }
result.categories.push(category); result.categories.push(category);
} }
@ -46,23 +47,48 @@ export async function renderMarkdown(): Promise<string> {
return markdown.render(result); return markdown.render(result);
} }
async function importOclifCommands(jsFilename: string) { // Help is now managed via a plugin
const command = (await import(path.join(process.cwd(), jsFilename))) // This fake command allows capitanodoc to include help in docs
.default as OclifCommand; class FakeHelpCommand {
description = stripIndent`
List balena commands, or get detailed help for a specific command.
return { List balena commands, or get detailed help for a specific command.
...command, `;
// build/commands/device/index.js -> device
// build/commands/device/list.js -> device list examples = [
name: jsFilename '$ balena help',
.split('/') '$ balena help login',
.slice(2) '$ balena help os download',
.join(' ') ];
.split('.')
.slice(0, 1) args = {
.join(' ') command: {
.split(' index')[0], description: 'command to show help for',
} as Category['commands'][0]; },
};
usage = 'help [command]';
flags = {
verbose: {
description: 'show additional commands',
char: '-v',
},
};
}
function importOclifCommands(jsFilename: string): OclifCommand[] {
// TODO: Currently oclif commands with no `usage` overridden will cause
// an error when parsed. This should be improved so that `usage` does not have
// to be overridden if not necessary.
const command: OclifCommand =
jsFilename === 'help'
? (new FakeHelpCommand() as unknown as OclifCommand)
: (require(path.join(process.cwd(), jsFilename)).default as OclifCommand);
return [command];
} }
/** /**

View File

@ -18,20 +18,12 @@ import { Parser } from '@oclif/core';
import * as ent from 'ent'; import * as ent from 'ent';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { capitanoizeOclifUsage } from '../../src/utils/oclif-utils'; import { getManualSortCompareFunction } from '../../lib/utils/helpers';
import type { Category, Document } from './doc-types'; import { capitanoizeOclifUsage } from '../../lib/utils/oclif-utils';
import { Category, Document, OclifCommand } from './doc-types';
function renderOclifCommand(command: Category['commands'][0]): string[] { function renderOclifCommand(command: OclifCommand): string[] {
const result = [`## ${ent.encode(command.name || '')}`]; const result = [`## ${ent.encode(command.usage || '')}`];
if (command.aliases?.length) {
result.push('### Aliases');
result.push(command.aliases.map((alias) => `- \`${alias}\``).join('\n'));
result.push(
`\nTo use one of the aliases, replace \`${command.name}\` with the alias.`,
);
}
result.push('### Description');
const description = (command.description || '') const description = (command.description || '')
.split('\n') .split('\n')
.slice(1) // remove the first line, which oclif uses as help header .slice(1) // remove the first line, which oclif uses as help header
@ -88,7 +80,7 @@ function renderToc(categories: Category[]): string[] {
result.push( result.push(
category.commands category.commands
.map((command) => { .map((command) => {
const signature = capitanoizeOclifUsage(command.name); const signature = capitanoizeOclifUsage(command.usage);
return `\t- [${ent.encode(signature)}](${getAnchor(signature)})`; return `\t- [${ent.encode(signature)}](${getAnchor(signature)})`;
}) })
.join('\n'), .join('\n'),
@ -97,7 +89,33 @@ function renderToc(categories: Category[]): string[] {
return result; return result;
} }
const manualCategorySorting: { [category: string]: string[] } = {
'Environment Variables': ['envs', 'env rm', 'env add', 'env rename'],
OS: [
'os versions',
'os download',
'os build config',
'os configure',
'os initialize',
],
};
function sortCommands(doc: Document): void {
for (const category of doc.categories) {
if (category.title in manualCategorySorting) {
category.commands = category.commands.sort(
getManualSortCompareFunction<OclifCommand, string>(
manualCategorySorting[category.title],
(cmd: OclifCommand, x: string) =>
(cmd.usage || '').toString().replace(/\W+/g, ' ').includes(x),
),
);
}
}
}
export function render(doc: Document) { export function render(doc: Document) {
sortCommands(doc);
const result = [ const result = [
`# ${doc.title}`, `# ${doc.title}`,
doc.introduction, doc.introduction,

View File

@ -15,9 +15,41 @@
* limitations under the License. * limitations under the License.
*/ */
import type { OptionDefinition } from 'capitano';
import * as ent from 'ent';
import * as fs from 'fs'; import * as fs from 'fs';
import * as readline from 'readline'; import * as readline from 'readline';
export function getOptionPrefix(signature: string) {
if (signature.length > 1) {
return '--';
} else {
return '-';
}
}
export function getOptionSignature(signature: string) {
return `${getOptionPrefix(signature)}${signature}`;
}
export function parseCapitanoOption(option: OptionDefinition): string {
let result = getOptionSignature(option.signature);
if (Array.isArray(option.alias)) {
for (const alias of option.alias) {
result += `, ${getOptionSignature(alias)}`;
}
} else if (typeof option.alias === 'string') {
result += `, ${getOptionSignature(option.alias)}`;
}
if (option.parameter) {
result += ` <${option.parameter}>`;
}
return ent.encode(result);
}
export class MarkdownFileParser { export class MarkdownFileParser {
constructor(public mdFilePath: string) {} constructor(public mdFilePath: string) {}

View File

@ -43,8 +43,8 @@ async function checkBuildTimestamps() {
...gitStatus.staged, ...gitStatus.staged,
...gitStatus.renamed.map((o) => o.to), ...gitStatus.renamed.map((o) => o.to),
]) ])
// select only staged files that start with src/ or typings/ // select only staged files that start with lib/ or typings/
.filter((f) => f.match(/^(src|typings)[/\\]/)) .filter((f) => f.match(/^(lib|typings)[/\\]/))
.map((f) => path.join(ROOT, f)); .map((f) => path.join(ROOT, f));
const fStats = await Promise.all(stagedFiles.map((f) => fs.stat(f))); const fStats = await Promise.all(stagedFiles.map((f) => fs.stat(f)));

257
automation/deploy-bin.ts Normal file
View File

@ -0,0 +1,257 @@
/**
* @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.
*/
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as semver from 'semver';
import { finalReleaseAssets, version } from './build-bin';
const { GITHUB_TOKEN } = process.env;
/**
* Create or update a release in GitHub's releases page, uploading the
* installer files (standalone zip + native oclif installers).
*/
export async function createGitHubRelease() {
console.log(`Publishing release ${version} to GitHub`);
const publishRelease = await import('publish-release');
const ghRelease = (await Bluebird.fromCallback(
publishRelease.bind(null, {
token: GITHUB_TOKEN || '',
owner: 'balena-io',
repo: 'balena-cli',
tag: version,
name: `balena-CLI ${version}`,
reuseRelease: true,
assets: finalReleaseAssets[process.platform],
}),
)) as { html_url: any };
console.log(`Release ${version} successful: ${ghRelease.html_url}`);
}
/**
* Top-level function to create a CLI release in GitHub's releases page:
* call zipStandaloneInstaller(), rename the files as we'd like them to
* display on the releases page, and call createGitHubRelease() to upload
* the files.
*/
export async function release() {
try {
await createGitHubRelease();
} catch (err) {
throw new Error(`Error creating GitHub release:\n${err}`);
}
}
/** Return a cached Octokit instance, creating a new one as needed. */
const getOctokit = _.once(function () {
const Octokit = (
require('@octokit/rest') as typeof import('@octokit/rest')
).Octokit.plugin(
(
require('@octokit/plugin-throttling') as typeof import('@octokit/plugin-throttling')
).throttling,
);
return new Octokit({
auth: GITHUB_TOKEN,
throttle: {
onRateLimit: (retryAfter: number, options: any) => {
console.warn(
`Request quota exhausted for request ${options.method} ${options.url}`,
);
// retries 3 times
if (options.request.retryCount < 3) {
console.log(`Retrying after ${retryAfter} seconds!`);
return true;
}
},
onAbuseLimit: (_retryAfter: number, options: any) => {
// does not retry, only logs a warning
console.warn(
`Abuse detected for request ${options.method} ${options.url}`,
);
},
},
});
});
/**
* Extract pagination information (current page, total pages, ordinal number)
* from the 'link' response header (example below), using the parse-link-header
* npm package:
* "link": "<https://api.github.com/repositories/187370853/releases?per_page=2&page=2>; rel=\"next\",
* <https://api.github.com/repositories/187370853/releases?per_page=2&page=3>; rel=\"last\""
*
* @param response Octokit response object (including response.headers.link)
* @param perPageDefault Default per_page pagination value if missing in URL
* @return Object where 'page' is the current page number (1-based),
* 'pages' is the total number of pages, and 'ordinal' is the ordinal number
* (3rd, 4th, 5th...) of the first item in the current page.
*/
function getPageNumbers(
response: any,
perPageDefault: number,
): { page: number; pages: number; ordinal: number } {
const res = { page: 1, pages: 1, ordinal: 1 };
if (!response.headers.link) {
return res;
}
const parse =
require('parse-link-header') as typeof import('parse-link-header');
const parsed = parse(response.headers.link);
if (parsed == null) {
throw new Error(`Failed to parse link header: '${response.headers.link}'`);
}
let perPage = perPageDefault;
if (parsed.next) {
if (parsed.next.per_page) {
perPage = parseInt(parsed.next.per_page, 10);
}
res.page = parseInt(parsed.next.page, 10) - 1;
res.pages = parseInt(parsed.last.page, 10);
} else {
if (parsed.prev.per_page) {
perPage = parseInt(parsed.prev.per_page, 10);
}
res.page = res.pages = parseInt(parsed.prev.page, 10) + 1;
}
res.ordinal = (res.page - 1) * perPage + 1;
return res;
}
/**
* Iterate over every GitHub release in the given owner/repo, check whether
* its tag_name matches against the affectedVersions semver spec, and if so
* replace its release description (body) with the given newDescription value.
* @param owner GitHub repo owner, e.g. 'balena-io' or 'pdcastro'
* @param repo GitHub repo, e.g. 'balena-cli'
* @param affectedVersions Semver spec, e.g. '2.6.1 - 7.10.9 || 8.0.0'
* @param newDescription New release description (body)
* @param editID Short string present in newDescription, e.g. '[AA101]', that
* can be searched to determine whether that release has already been updated.
*/
async function updateGitHubReleaseDescriptions(
owner: string,
repo: string,
affectedVersions: string,
newDescription: string,
editID: string,
) {
const perPage = 30;
const octokit = getOctokit();
const options = octokit.repos.listReleases.endpoint.merge({
owner,
repo,
per_page: perPage,
});
let errCount = 0;
type Release =
import('@octokit/rest').RestEndpointMethodTypes['repos']['listReleases']['response']['data'][0];
for await (const response of octokit.paginate.iterator<Release>(options)) {
const {
page: thisPage,
pages: totalPages,
ordinal,
} = getPageNumbers(response, perPage);
let i = 0;
for (const cliRelease of response.data) {
const prefix = `[#${ordinal + i++} pg ${thisPage}/${totalPages}]`;
if (!cliRelease.id) {
console.error(
`${prefix} Error: missing release ID (errCount=${++errCount})`,
);
continue;
}
const skipMsg = `${prefix} skipping release "${cliRelease.tag_name}" (${cliRelease.id})`;
if (cliRelease.draft === true) {
console.info(`${skipMsg}: draft release`);
continue;
} else if (cliRelease.body && cliRelease.body.includes(editID)) {
console.info(`${skipMsg}: already updated`);
continue;
} else if (!semver.satisfies(cliRelease.tag_name, affectedVersions)) {
console.info(`${skipMsg}: outside version range`);
continue;
} else {
const updatedRelease = {
owner,
repo,
release_id: cliRelease.id,
body: newDescription,
};
let oldBodyPreview = cliRelease.body;
if (oldBodyPreview) {
oldBodyPreview = oldBodyPreview.replace(/\s+/g, ' ').trim();
if (oldBodyPreview.length > 12) {
oldBodyPreview = oldBodyPreview.substring(0, 9) + '...';
}
}
console.info(
`${prefix} updating release "${cliRelease.tag_name}" (${cliRelease.id}) old body="${oldBodyPreview}"`,
);
try {
await octokit.repos.updateRelease(updatedRelease);
} catch (err) {
console.error(
`${skipMsg}: Error: ${err.message} (count=${++errCount})`,
);
continue;
}
}
}
}
}
/**
* Add a warning description to CLI releases affected by a mixpanel tracking
* security issue (#1359). This function can be executed "manually" with the
* following command line:
*
* npx ts-node --type-check -P automation/tsconfig.json automation/run.ts fix1359
*/
export async function updateDescriptionOfReleasesAffectedByIssue1359() {
// Run only on Linux/Node10, instead of all platform/Node combinations.
// (It could have been any other platform, as long as it only runs once.)
if (process.platform !== 'linux' || semver.major(process.version) !== 10) {
return;
}
const owner = 'balena-io';
const repo = 'balena-cli';
const affectedVersions =
'2.6.1 - 7.10.9 || 8.0.0 - 8.1.0 || 9.0.0 - 9.15.6 || 10.0.0 - 10.17.5 || 11.0.0 - 11.7.2';
const editID = '[AA100]';
let newDescription = `
Please note: the "login" command in this release is affected by a
security issue fixed in versions
[7.10.10](https://github.com/balena-io/balena-cli/releases/tag/v7.10.10),
[8.1.1](https://github.com/balena-io/balena-cli/releases/tag/v8.1.1),
[9.15.7](https://github.com/balena-io/balena-cli/releases/tag/v9.15.7),
[10.17.6](https://github.com/balena-io/balena-cli/releases/tag/v10.17.6),
[11.7.3](https://github.com/balena-io/balena-cli/releases/tag/v11.7.3)
and later. If you need to use this version, avoid passing your password,
keys or tokens as command-line arguments. ${editID}`;
// remove line breaks and collapse white space
newDescription = newDescription.replace(/\s+/g, ' ').trim();
await updateGitHubReleaseDescriptions(
owner,
repo,
affectedVersions,
newDescription,
editID,
);
}

View File

@ -21,9 +21,12 @@ import {
buildOclifInstaller, buildOclifInstaller,
buildStandaloneZip, buildStandaloneZip,
catchUncommitted, catchUncommitted,
signFilesForNotarization,
testShrinkwrap, testShrinkwrap,
} from './build-bin'; } from './build-bin';
import {
release,
updateDescriptionOfReleasesAffectedByIssue1359,
} from './deploy-bin';
// DEBUG set to falsy for negative values else is truthy // DEBUG set to falsy for negative values else is truthy
process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes( process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
@ -37,6 +40,7 @@ process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
* of the following strings, then call the appropriate functions: * of the following strings, then call the appropriate functions:
* 'build:installer' (to build a native oclif installer) * 'build:installer' (to build a native oclif installer)
* 'build:standalone' (to build a standalone pkg package) * 'build:standalone' (to build a standalone pkg package)
* 'release' (to create/update a GitHub release)
* *
* @param args Arguments to parse (default is process.argv.slice(2)) * @param args Arguments to parse (default is process.argv.slice(2))
*/ */
@ -50,9 +54,10 @@ async function parse(args?: string[]) {
const commands: { [cmd: string]: () => void | Promise<void> } = { const commands: { [cmd: string]: () => void | Promise<void> } = {
'build:installer': buildOclifInstaller, 'build:installer': buildOclifInstaller,
'build:standalone': buildStandaloneZip, 'build:standalone': buildStandaloneZip,
'sign:binaries': signFilesForNotarization,
'catch-uncommitted': catchUncommitted, 'catch-uncommitted': catchUncommitted,
'test-shrinkwrap': testShrinkwrap, 'test-shrinkwrap': testShrinkwrap,
fix1359: updateDescriptionOfReleasesAffectedByIssue1359,
release,
}; };
for (const arg of args) { for (const arg of args) {
if (!Object.hasOwn(commands, arg)) { if (!Object.hasOwn(commands, arg)) {
@ -60,6 +65,21 @@ async function parse(args?: string[]) {
} }
} }
// The BUILD_TMP env var is used as an alternative location for oclif
// (patched) to copy/extract the CLI files, run npm install and then
// create the NSIS executable installer for Windows. This was necessary
// to avoid issues with a 260-char limit on Windows paths (possibly a
// limitation of some library used by NSIS), as the "current working dir"
// provided by balena CI is a rather long path to start with.
if (process.platform === 'win32' && !process.env.BUILD_TMP) {
const randID = (await import('crypto'))
.randomBytes(6)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_'); // base64url (RFC 4648)
process.env.BUILD_TMP = `C:\\tmp\\${randID}`;
}
for (const arg of args) { for (const arg of args) {
try { try {
const cmdFunc = commands[arg]; const cmdFunc = commands[arg];

View File

@ -107,11 +107,11 @@ async function $main() {
const changeType = process.argv[4] const changeType = process.argv[4]
? // if the caller specified a change type, use that one ? // if the caller specified a change type, use that one
validateChangeType(process.argv[4]) validateChangeType(process.argv[4])
: // use the same change type as in the dependency, but avoid major bumps : // use the same change type as in the dependency, but avoid major bumps
semverChangeType && semverChangeType !== 'major' semverChangeType && semverChangeType !== 'major'
? semverChangeType ? semverChangeType
: 'minor'; : 'minor';
console.log(`Using Change-type: ${changeType}`); console.log(`Using Change-type: ${changeType}`);
let { stdout: currentBranch } = await run('git rev-parse --abbrev-ref HEAD'); let { stdout: currentBranch } = await run('git rev-parse --abbrev-ref HEAD');

View File

@ -17,8 +17,6 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs';
import { diffTrimmedLines } from 'diff';
export const ROOT = path.join(__dirname, '..'); export const ROOT = path.join(__dirname, '..');
@ -66,6 +64,7 @@ export class StdOutTap {
* https://www.npmjs.com/package/diff * https://www.npmjs.com/package/diff
*/ */
export function diffLines(str1: string, str2: string): string { export function diffLines(str1: string, str2: string): string {
const { diffTrimmedLines } = require('diff');
const diffObjs = diffTrimmedLines(str1, str2); const diffObjs = diffTrimmedLines(str1, str2);
const prefix = (chunk: string, char: string) => const prefix = (chunk: string, char: string) =>
chunk chunk
@ -77,18 +76,15 @@ export function diffLines(str1: string, str2: string): string {
return part.added return part.added
? prefix(part.value, '+') ? prefix(part.value, '+')
: part.removed : part.removed
? prefix(part.value, '-') ? prefix(part.value, '-')
: prefix(part.value, ' '); : prefix(part.value, ' ');
}) })
.join('\n'); .join('\n');
return diffStr; return diffStr;
} }
export function loadPackageJson() { export function loadPackageJson() {
const packageJsonPath = path.join(ROOT, 'package.json'); return require(path.join(ROOT, 'package.json'));
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
return JSON.parse(packageJson);
} }
/** /**

View File

@ -1 +0,0 @@
run.js

21
bin/balena Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env node
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Disable oclif registering ts-node
process.env.OCLIF_TS_NODE = 0;
async function run() {
// Use fast-boot to cache require lookups, speeding up startup
await require('../build/fast-boot').start();
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Run the CLI
await require('../build/app').run(undefined, { dir: __dirname });
}
run();

View File

@ -1 +0,0 @@
dev.js

87
bin/balena-dev Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
// ****************************************************************************
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
// Before opening a PR you should build and test your changes using bin/balena
// ****************************************************************************
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
// * fast-boot2's cacheKiller option is configured to include the timestamps of
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
// behavior when changes are made to dependencies during development. This is
// generally a good thing, however, `balena-dev` (a few lines below) edits
// `package.json` to modify oclif paths, and this results in cache
// invalidation and a performance hit rather than speedup.
// * Even if the timestamps are removed from cacheKiller, so that there is no
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
// * `fast-boot` causes unexpected behavior when used with `npm link` or
// when the `node_modules` folder is manually modified (affecting transitive
// dependencies) during development (e.g. bug investigations). A workaround
// is to use `balena-dev` without `fast-boot`. See also notes in
// `CONTRIBUTING.md`.
const path = require('path');
const rootDir = path.join(__dirname, '..');
// Allow balena-dev to work with oclif by temporarily
// pointing oclif config options to lib/ instead of build/
modifyOclifPaths();
// Undo changes on exit
process.on('exit', function () {
modifyOclifPaths(true);
});
// Undo changes in case of ctrl-c
process.on('SIGINT', function () {
modifyOclifPaths(true);
// Note process exit here will interfere with commands that do their own SIGINT handling,
// but without it commands can not be exited.
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
// Ideally a better solution is needed.
process.exit();
});
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
// default option. We upgraded ts-node and found that adding 'transpile-only'
// was necessary to avoid a mysterious 'null' error message. On the plus side,
// it is supposed to run faster. We still benefit from type checking when
// running 'npm run build'.
require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true,
});
require('../lib/app').run(undefined, { dir: __dirname, development: true });
// Modify package.json oclif paths from build/ -> lib/, or vice versa
function modifyOclifPaths(revert) {
const fs = require('fs');
const packageJsonPath = path.join(rootDir, 'package.json');
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
const packageObj = JSON.parse(packageJson);
if (!packageObj.oclif) {
return;
}
let oclifSectionText = JSON.stringify(packageObj.oclif);
if (!revert) {
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/lib/');
} else {
oclifSectionText = oclifSectionText.replace(/\/lib\//g, '/build/');
}
packageObj.oclif = JSON.parse(oclifSectionText);
fs.writeFileSync(
packageJsonPath,
`${JSON.stringify(packageObj, null, 2)}\n`,
'utf8',
);
}

View File

@ -1,3 +0,0 @@
@echo off
node "%~dp0\run" %*

View File

@ -1,87 +0,0 @@
#!/usr/bin/env node
// ****************************************************************************
// THIS IS FOR DEV PURPOSES ONLY AND WILL NOT BE PART OF THE PUBLISHED PACKAGE
// Before opening a PR you should build and test your changes using bin/balena
// ****************************************************************************
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Note on `fast-boot2`: We do not use `fast-boot2` with `balena-dev` because:
// * fast-boot2's cacheKiller option is configured to include the timestamps of
// the package.json and npm-shrinkwrap.json files, to avoid unexpected CLI
// behavior when changes are made to dependencies during development. This is
// generally a good thing, however, `balena-dev` (a few lines below) edits
// `package.json` to modify oclif paths, and this results in cache
// invalidation and a performance hit rather than speedup.
// * Even if the timestamps are removed from cacheKiller, so that there is no
// cache invalidation, fast-boot's speedup is barely noticeable when ts-node
// is used, e.g. 1.43s vs 1.4s when running `balena version`.
// * `fast-boot` causes unexpected behavior when used with `npm link` or
// when the `node_modules` folder is manually modified (affecting transitive
// dependencies) during development (e.g. bug investigations). A workaround
// is to use `balena-dev` without `fast-boot`. See also notes in
// `CONTRIBUTING.md`.
const path = require('path');
const rootDir = path.join(__dirname, '..');
// Allow balena-dev to work with oclif by temporarily
// pointing oclif config options to src/ instead of build/
modifyOclifPaths();
// Undo changes on exit
process.on('exit', function () {
modifyOclifPaths(true);
});
// Undo changes in case of ctrl-c
process.on('SIGINT', function () {
modifyOclifPaths(true);
// Note process exit here will interfere with commands that do their own SIGINT handling,
// but without it commands can not be exited.
// So currently using balena-dev does not guarantee proper exit behaviour when using ctrl-c.
// Ideally a better solution is needed.
process.exit();
});
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Note: before ts-node v6.0.0, 'transpile-only' (no type checking) was the
// default option. We upgraded ts-node and found that adding 'transpile-only'
// was necessary to avoid a mysterious 'null' error message. On the plus side,
// it is supposed to run faster. We still benefit from type checking when
// running 'npm run build'.
require('ts-node').register({
project: path.join(rootDir, 'tsconfig.json'),
transpileOnly: true,
});
require('../src/app').run(undefined, { dir: __dirname, development: true });
// Modify package.json oclif paths from build/ -> src/, or vice versa
function modifyOclifPaths(revert) {
const fs = require('fs');
const packageJsonPath = path.join(rootDir, 'package.json');
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
const packageObj = JSON.parse(packageJson);
if (!packageObj.oclif) {
return;
}
let oclifSectionText = JSON.stringify(packageObj.oclif);
if (!revert) {
oclifSectionText = oclifSectionText.replace(/\/build\//g, '/src/');
} else {
oclifSectionText = oclifSectionText.replace(/\/src\//g, '/build/');
}
packageObj.oclif = JSON.parse(oclifSectionText);
fs.writeFileSync(
packageJsonPath,
`${JSON.stringify(packageObj, null, 2)}\n`,
'utf8',
);
}

View File

@ -1,3 +0,0 @@
@echo off
node "%~dp0\run" %*

View File

@ -1,21 +0,0 @@
#!/usr/bin/env node
// We boost the threadpool size as ext2fs can deadlock with some
// operations otherwise, if the pool runs out.
process.env.UV_THREADPOOL_SIZE = '64';
// Disable oclif registering ts-node
process.env.OCLIF_TS_NODE = 0;
async function run() {
// Use fast-boot to cache require lookups, speeding up startup
await require('../build/fast-boot').start();
// Set the desired es version for downstream modules that support it
require('@balena/es-version').set('es2018');
// Run the CLI
await require('../build/app').run(undefined, { dir: __dirname });
}
run();

View File

@ -8,21 +8,21 @@ _balena() {
local context state line curcontext="$curcontext" local context state line curcontext="$curcontext"
# Valid top-level completions # Valid top-level completions
main_commands=( api-key api-keys app block build config deploy device devices env envs fleet fleets internal join key keys leave local login logout logs notes orgs os preload push release releases scan settings ssh support tag tags tunnel util version whoami ) main_commands=( api-key api-keys app block build config deploy device device devices env envs fleet fleet fleets internal join key key keys leave local login logout logs notes orgs os preload push release release releases scan settings ssh support tag tags tunnel util version whoami )
# Sub-completions # Sub-completions
api_key_cmds=( generate list revoke ) api_key_cmds=( generate revoke )
app_cmds=( create ) app_cmds=( create )
block_cmds=( create ) block_cmds=( create )
config_cmds=( generate inject read reconfigure write ) config_cmds=( generate inject read reconfigure write )
device_cmds=( deactivate identify init list local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet ) device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet )
devices_cmds=( supported ) devices_cmds=( supported )
env_cmds=( add rename rm ) env_cmds=( add rename rm )
fleet_cmds=( create list pin purge rename restart rm track-latest ) fleet_cmds=( create pin purge rename restart rm track-latest )
internal_cmds=( osinit ) internal_cmds=( osinit )
key_cmds=( add list rm ) key_cmds=( add rm )
local_cmds=( configure flash ) local_cmds=( configure flash )
os_cmds=( build-config configure download initialize versions ) os_cmds=( build-config configure download initialize versions )
release_cmds=( finalize invalidate list validate ) release_cmds=( finalize invalidate validate )
tag_cmds=( rm set ) tag_cmds=( rm set )

View File

@ -7,21 +7,21 @@ _balena_complete()
local cur prev local cur prev
# Valid top-level completions # Valid top-level completions
main_commands="api-key api-keys app block build config deploy device devices env envs fleet fleets internal join key keys leave local login logout logs notes orgs os preload push release releases scan settings ssh support tag tags tunnel util version whoami" main_commands="api-key api-keys app block build config deploy device device devices env envs fleet fleet fleets internal join key key keys leave local login logout logs notes orgs os preload push release release releases scan settings ssh support tag tags tunnel util version whoami"
# Sub-completions # Sub-completions
api_key_cmds="generate list revoke" api_key_cmds="generate revoke"
app_cmds="create" app_cmds="create"
block_cmds="create" block_cmds="create"
config_cmds="generate inject read reconfigure write" config_cmds="generate inject read reconfigure write"
device_cmds="deactivate identify init list local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet" device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown start-service stop-service track-fleet"
devices_cmds="supported" devices_cmds="supported"
env_cmds="add rename rm" env_cmds="add rename rm"
fleet_cmds="create list pin purge rename restart rm track-latest" fleet_cmds="create pin purge rename restart rm track-latest"
internal_cmds="osinit" internal_cmds="osinit"
key_cmds="add list rm" key_cmds="add rm"
local_cmds="configure flash" local_cmds="configure flash"
os_cmds="build-config configure download initialize versions" os_cmds="build-config configure download initialize versions"
release_cmds="finalize invalidate list validate" release_cmds="finalize invalidate validate"
tag_cmds="rm set" tag_cmds="rm set"

View File

@ -33,7 +33,7 @@ const commandsJson = JSON.parse(fs.readFileSync(commandsFilePath, 'utf8'));
const mainCommands = []; const mainCommands = [];
const additionalCommands = []; const additionalCommands = [];
for (const [key, { aliases }] of Object.entries(commandsJson.commands).sort()) { for (const key of Object.keys(commandsJson.commands)) {
const cmd = key.split(':'); const cmd = key.split(':');
if (cmd.length > 1) { if (cmd.length > 1) {
additionalCommands.push(cmd); additionalCommands.push(cmd);
@ -43,17 +43,6 @@ for (const [key, { aliases }] of Object.entries(commandsJson.commands).sort()) {
} else { } else {
mainCommands.push(cmd[0]); mainCommands.push(cmd[0]);
} }
for (const alias of aliases) {
const splitAlias = alias.split(' ');
if (splitAlias.length > 1) {
additionalCommands.push(splitAlias);
if (!mainCommands.includes(splitAlias[0])) {
mainCommands.push(splitAlias[0]);
}
} else {
mainCommands.push(splitAlias[0]);
}
}
} }
const mainCommandsStr = mainCommands.join(' '); const mainCommandsStr = mainCommands.join(' ');
@ -84,33 +73,29 @@ fs.readFile(bashFilePathIn, 'utf8', function (err, data) {
'main_commands="' + mainCommandsStr + '"', 'main_commands="' + mainCommandsStr + '"',
); );
let subCommands = []; let subCommands = [];
let prevElement = additionalCommands.sort((cmd1, cmd2) => let prevElement = additionalCommands[0][0];
cmd1[0].localeCompare(cmd2[0]), additionalCommands.forEach(function (element) {
)[0][0]; if (element[0] === prevElement) {
additionalCommands subCommands.push(element[1]);
.sort((cmd1, cmd2) => cmd1[0].localeCompare(cmd2[0])) } else {
.forEach(function (element) { const prevElement2 = prevElement.replace(/-/g, '_') + '_cmds';
if (element[0] === prevElement) { data = data.replace(
subCommands.push(element[1]); /\$sub_cmds\$/g,
} else { ' ' + prevElement2 + '="' + subCommands.join(' ') + '"\n$sub_cmds$',
const prevElement2 = prevElement.replace(/-/g, '_') + '_cmds'; );
data = data.replace( data = data.replace(
/\$sub_cmds\$/g, /\$sub_cmds_prev\$/g,
' ' + prevElement2 + '="' + subCommands.join(' ') + '"\n$sub_cmds$', ' ' +
); prevElement +
data = data.replace( ')\n COMPREPLY=( $(compgen -W "$' +
/\$sub_cmds_prev\$/g, prevElement2 +
' ' + '" -- $cur) )\n ;;\n$sub_cmds_prev$',
prevElement + );
')\n COMPREPLY=( $(compgen -W "$' + prevElement = element[0];
prevElement2 + subCommands = [];
'" -- $cur) )\n ;;\n$sub_cmds_prev$', subCommands.push(element[1]);
); }
prevElement = element[0]; });
subCommands = [];
subCommands.push(element[1]);
}
});
// cleanup placeholders // cleanup placeholders
data = data.replace(/\$sub_cmds\$/g, ''); data = data.replace(/\$sub_cmds\$/g, '');
data = data.replace(/\$sub_cmds_prev\$/g, ''); data = data.replace(/\$sub_cmds_prev\$/g, '');

File diff suppressed because it is too large Load Diff

View File

@ -16,8 +16,8 @@
*/ */
import * as packageJSON from '../package.json'; import * as packageJSON from '../package.json';
import type { AppOptions } from './preparser';
import { import {
AppOptions,
checkDeletedCommand, checkDeletedCommand,
preparseArgs, preparseArgs,
unsupportedFlag, unsupportedFlag,
@ -137,8 +137,7 @@ async function oclifRun(command: string[], options: AppOptions) {
} }
} }
if (shouldFlush) { if (shouldFlush) {
const { flush } = await import('@oclif/core'); await import('@oclif/core/flush');
await flush();
} }
// TODO: figure out why we need to call fast-boot stop() here, in // TODO: figure out why we need to call fast-boot stop() here, in
// addition to calling it in the main `run()` function in this file. // addition to calling it in the main `run()` function in this file.
@ -153,12 +152,12 @@ async function oclifRun(command: string[], options: AppOptions) {
} }
})(!options.noFlush); })(!options.noFlush);
const { trackPromise } = await import('./hooks/prerun'); const { trackPromise } = await import('./hooks/prerun/track');
await Promise.all([trackPromise, deprecationPromise, runPromise]); await Promise.all([trackPromise, deprecationPromise, runPromise]);
} }
/** CLI entrypoint. Called by the `bin/run.js` and `bin/dev.js` scripts. */ /** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` scripts. */
export async function run(cliArgs = process.argv, options: AppOptions) { export async function run(cliArgs = process.argv, options: AppOptions) {
try { try {
const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import( const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import(

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

174
lib/command.ts Normal file
View File

@ -0,0 +1,174 @@
/**
* @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.
*/
import { Command } from '@oclif/core';
import {
InsufficientPrivilegesError,
NotAvailableInOfflineModeError,
} from './errors';
import { stripIndent } from './utils/lazy';
import * as output from './framework/output';
export default abstract class BalenaCommand extends Command {
/**
* When set to true, command will be listed in `help`,
* otherwise listed in `help --verbose` with secondary commands.
*/
public static primary = false;
/**
* Require elevated privileges to run.
* When set to true, command will exit with an error
* if executed without root on Mac/Linux
* or if executed by non-Administrator on Windows.
*/
public static root = false;
/**
* Require authentication to run.
* When set to true, command will exit with an error
* if user is not already logged in.
*/
public static authenticated = false;
/**
* Require an internet connection to run.
* When set to true, command will exit with an error
* if user is running in offline mode (BALENARC_OFFLINE_MODE).
*/
public static offlineCompatible = false;
/**
* Accept piped input.
* When set to true, command will read from stdin during init
* and make contents available on member `stdin`.
*/
public static readStdin = false;
public stdin: string;
/**
* Throw InsufficientPrivilegesError if not root on Mac/Linux
* or non-Administrator on Windows.
*
* Called automatically if `root=true`.
* Can be called explicitly by command implementation, if e.g.:
* - check should only be done conditionally
* - other code needs to execute before check
*/
protected static async checkElevatedPrivileges() {
const isElevated = await (await import('is-elevated'))();
if (!isElevated) {
throw new InsufficientPrivilegesError(
'You need root/admin privileges to run this command',
);
}
}
/**
* Throw NotLoggedInError if not logged in.
*
* Called automatically if `authenticated=true`.
* Can be called explicitly by command implementation, if e.g.:
* - check should only be done conditionally
* - other code needs to execute before check
*
* Note, currently public to allow use outside of derived commands
* (as some command implementations require this. Can be made protected
* if this changes).
*
* @throws {NotLoggedInError}
*/
public static async checkLoggedIn() {
await (await import('./utils/patterns')).checkLoggedIn();
}
/**
* Throw NotLoggedInError if not logged in when condition true.
*
* @param {boolean} doCheck - will check if true.
* @throws {NotLoggedInError}
*/
public static async checkLoggedInIf(doCheck: boolean) {
if (doCheck) {
await this.checkLoggedIn();
}
}
/**
* Throw NotAvailableInOfflineModeError if in offline mode.
*
* Called automatically if `onlineOnly=true`.
* Can be called explicitly by command implementation, if e.g.:
* - check should only be done conditionally
* - other code needs to execute before check
*
* Note, currently public to allow use outside of derived commands
* (as some command implementations require this. Can be made protected
* if this changes).
*
* @throws {NotAvailableInOfflineModeError}
*/
public static checkNotUsingOfflineMode() {
if (process.env.BALENARC_OFFLINE_MODE) {
throw new NotAvailableInOfflineModeError(stripIndent`
This command requires an internet connection, and cannot be used in offline mode.
To leave offline mode, unset the BALENARC_OFFLINE_MODE environment variable.
`);
}
}
/**
* Read stdin contents and make available to command.
*
* This approach could be improved in the future to automatically set argument
* values from stdin based in configuration, minimising command implementation.
*/
protected async getStdin() {
this.stdin = await (await import('get-stdin'))();
}
/**
* Get a logger instance.
*/
protected static async getLogger() {
return (await import('./utils/logger')).getLogger();
}
protected async init() {
const ctr = this.constructor as typeof BalenaCommand;
if (ctr.root) {
await BalenaCommand.checkElevatedPrivileges();
}
if (ctr.authenticated) {
await BalenaCommand.checkLoggedIn();
}
if (!ctr.offlineCompatible) {
BalenaCommand.checkNotUsingOfflineMode();
}
if (ctr.readStdin) {
await this.getStdin();
}
}
protected outputMessage = output.outputMessage;
protected outputData = output.outputData;
}

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -39,6 +40,8 @@ export default class GenerateCmd extends Command {
}), }),
}; };
public static usage = 'api-key generate <name>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -40,6 +41,8 @@ export default class RevokeCmd extends Command {
}), }),
}; };
public static usage = 'api-key revoke <ids>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,13 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
export default class APIKeyListCmd extends Command { export default class ApiKeysCmd extends Command {
public static aliases = ['api-keys'];
public static description = stripIndent` public static description = stripIndent`
Print a list of balenaCloud API keys. Print a list of balenaCloud API keys.
@ -29,7 +28,9 @@ export default class APIKeyListCmd extends Command {
Print a list of balenaCloud API keys for the current user or for a specific fleet with the \`--fleet\` option. Print a list of balenaCloud API keys for the current user or for a specific fleet with the \`--fleet\` option.
`; `;
public static examples = ['$ balena api-key list']; public static examples = ['$ balena api-keys'];
public static usage = 'api-keys';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
@ -43,7 +44,7 @@ export default class APIKeyListCmd extends Command {
public static authenticated = true; public static authenticated = true;
public async run() { public async run() {
const { flags: options } = await this.parse(APIKeyListCmd); const { flags: options } = await this.parse(ApiKeysCmd);
const { getApplication } = await import('../../utils/sdk'); const { getApplication } = await import('../../utils/sdk');
const actorId = options.fleet const actorId = options.fleet
@ -51,7 +52,7 @@ export default class APIKeyListCmd extends Command {
await getApplication(getBalenaSdk(), options.fleet, { await getApplication(getBalenaSdk(), options.fleet, {
$select: 'actor', $select: 'actor',
}) })
).actor ).actor
: await getBalenaSdk().auth.getActorId(); : await getBalenaSdk().auth.getActorId();
const keys = await getBalenaSdk().pine.get({ const keys = await getBalenaSdk().pine.get({
resource: 'api_key', resource: 'api_key',
@ -64,7 +65,7 @@ export default class APIKeyListCmd extends Command {
name: { name: {
$ne: null, $ne: null,
}, },
} }
: {}), : {}),
}, },
$orderby: 'name asc', $orderby: 'name asc',

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
@ -54,6 +56,8 @@ export default class AppCreateCmd extends Command {
}), }),
}; };
public static usage = 'app create <name>';
public static flags = { public static flags = {
organization: Flags.string({ organization: Flags.string({
char: 'o', char: 'o',

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
@ -54,6 +56,8 @@ export default class BlockCreateCmd extends Command {
}), }),
}; };
public static usage = 'block create <name>';
public static flags = { public static flags = {
organization: Flags.string({ organization: Flags.string({
char: 'o', char: 'o',

View File

@ -15,17 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Flags, Command } from '@oclif/core'; import { Args, Flags } from '@oclif/core';
import Command from '../../command';
import { getBalenaSdk } from '../../utils/lazy'; import { getBalenaSdk } from '../../utils/lazy';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as compose from '../../utils/compose'; import * as compose from '../../utils/compose';
import type { import type { ApplicationType, BalenaSDK } from 'balena-sdk';
ApplicationType,
BalenaSDK,
DeviceType,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
import { import {
buildArgDeprecation, buildArgDeprecation,
dockerignoreHelp, dockerignoreHelp,
@ -72,7 +67,6 @@ ${dockerignoreHelp}
public static examples = [ public static examples = [
'$ balena build --fleet myFleet', '$ balena build --fleet myFleet',
'$ balena build ./source/ --fleet myorg/myfleet', '$ balena build ./source/ --fleet myorg/myfleet',
'$ balena build --deviceType raspberrypi3 --emulated',
'$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated', '$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated',
'$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac', '$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac',
'$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows', '$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows',
@ -83,6 +77,8 @@ ${dockerignoreHelp}
source: Args.string({ description: 'path of project source directory' }), source: Args.string({ description: 'path of project source directory' }),
}; };
public static usage = 'build [source]';
public static flags = { public static flags = {
arch: Flags.string({ arch: Flags.string({
description: 'the architecture to build for', description: 'the architecture to build for',
@ -105,24 +101,19 @@ ${dockerignoreHelp}
public async run() { public async run() {
const { args: params, flags: options } = await this.parse(BuildCmd); const { args: params, flags: options } = await this.parse(BuildCmd);
const Logger = await import('../../utils/logger'); await Command.checkLoggedInIf(!!options.fleet);
const { checkLoggedInIf } = await import('../../utils/patterns');
await checkLoggedInIf(!!options.fleet);
(await import('events')).defaultMaxListeners = 1000; (await import('events')).defaultMaxListeners = 1000;
const sdk = getBalenaSdk(); const sdk = getBalenaSdk();
const logger = Logger.getLogger(); const logger = await Command.getLogger();
logger.logDebug('Parsing input...'); logger.logDebug('Parsing input...');
// `build` accepts `source` as a parameter, but compose expects it as an option // `build` accepts `source` as a parameter, but compose expects it as an option
options.source = params.source; options.source = params.source;
delete params.source; delete params.source;
await this.resolveArchFromDeviceType(sdk, options);
await this.validateOptions(options, sdk); await this.validateOptions(options, sdk);
// Build args are under consideration for removal - warn user // Build args are under consideration for removal - warn user
@ -136,7 +127,7 @@ ${dockerignoreHelp}
try { try {
await this.buildProject(docker, logger, composeOpts, { await this.buildProject(docker, logger, composeOpts, {
appType: app?.application_type?.[0], app,
arch: options.arch!, arch: options.arch!,
deviceType: options.deviceType!, deviceType: options.deviceType!,
buildEmulated: options.emulated, buildEmulated: options.emulated,
@ -159,7 +150,7 @@ ${dockerignoreHelp}
) { ) {
const { ExpectedError } = await import('../../errors'); const { ExpectedError } = await import('../../errors');
throw new ExpectedError( throw new ExpectedError(
'You must specify either a fleet (-f), or the device type (-d) and optionally the architecture (-A)', 'You must specify either a fleet (-f), or the device type (-d) and architecture (-A)',
); );
} }
@ -179,39 +170,6 @@ ${dockerignoreHelp}
opts['registry-secrets'] = registrySecrets; opts['registry-secrets'] = registrySecrets;
} }
protected async resolveArchFromDeviceType(sdk: BalenaSDK, opts: FlagsDef) {
if (opts.deviceType != null && opts.arch == null) {
try {
const deviceTypeOpts = {
$select: 'is_of__cpu_architecture',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
} satisfies PineOptions<DeviceType>;
opts.arch = (
(await sdk.models.deviceType.get(
opts.deviceType,
deviceTypeOpts,
)) as PineTypedResult<DeviceType, typeof deviceTypeOpts>
).is_of__cpu_architecture[0].slug;
} catch (err) {
const { ExpectedError } = await import('../../errors');
if (err instanceof sdk.errors.BalenaInvalidDeviceType) {
let message = err.message;
if (!(await sdk.auth.isLoggedIn())) {
message = `${message}. In case you are trying to use a private device type, please try to log in first.`;
}
throw new ExpectedError(message);
}
throw new ExpectedError(
'Failed to resolve the architecture of the provided device type. If you are in an air-gapped environment please also define the architecture (-A) parameter.',
);
}
}
}
protected async getAppAndResolveArch(opts: FlagsDef) { protected async getAppAndResolveArch(opts: FlagsDef) {
if (opts.fleet) { if (opts.fleet) {
const { getAppWithArch } = await import('../../utils/helpers'); const { getAppWithArch } = await import('../../utils/helpers');
@ -254,7 +212,9 @@ ${dockerignoreHelp}
logger: import('../../utils/logger'), logger: import('../../utils/logger'),
composeOpts: ComposeOpts, composeOpts: ComposeOpts,
opts: { opts: {
appType?: Pick<ApplicationType, 'supports_multicontainer'>; app?: {
application_type: [Pick<ApplicationType, 'supports_multicontainer'>];
};
arch: string; arch: string;
deviceType: string; deviceType: string;
buildEmulated: boolean; buildEmulated: boolean;
@ -270,10 +230,11 @@ ${dockerignoreHelp}
opts.buildOpts.t, opts.buildOpts.t,
); );
const appType = opts.app?.application_type?.[0];
if ( if (
opts.appType != null && appType != null &&
project.descriptors.length > 1 && project.descriptors.length > 1 &&
!opts.appType.supports_multicontainer !appType.supports_multicontainer
) { ) {
logger.logWarn( logger.logWarn(
'Target fleet does not support multiple containers.\n' + 'Target fleet does not support multiple containers.\n' +

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import type { Interfaces } from '@oclif/core'; import type { Interfaces } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import { import {
@ -59,6 +60,8 @@ export default class ConfigGenerateCmd extends Command {
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15', '$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
]; ];
public static usage = 'config generate';
public static flags = { public static flags = {
version: Flags.string({ version: Flags.string({
description: 'a balenaOS version', description: 'a balenaOS version',
@ -256,8 +259,6 @@ export default class ConfigGenerateCmd extends Command {
if (!options.fleet && options.deviceType) { if (!options.fleet && options.deviceType) {
throw new ExpectedError(this.deviceTypeNotAllowedMessage); throw new ExpectedError(this.deviceTypeNotAllowedMessage);
} }
const { normalizeOsVersion } = await import('../../utils/normalization');
options.version = normalizeOsVersion(options.version);
const { validateDevOptionAndWarn } = await import('../../utils/config'); const { validateDevOptionAndWarn } = await import('../../utils/config');
await validateDevOptionAndWarn(options.dev, options.version); await validateDevOptionAndWarn(options.dev, options.version);
} }

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy'; import { getVisuals, stripIndent } from '../../utils/lazy';
@ -42,6 +43,8 @@ export default class ConfigInjectCmd extends Command {
}), }),
}; };
public static usage = 'config inject <file>';
public static flags = { public static flags = {
drive: cf.driveOrImg, drive: cf.driveOrImg,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy'; import { getVisuals, stripIndent } from '../../utils/lazy';
@ -36,6 +36,8 @@ export default class ConfigReadCmd extends Command {
'$ balena config read --drive balena.img', '$ balena config read --drive balena.img',
]; ];
public static usage = 'config read';
public static flags = { public static flags = {
drive: cf.driveOrImg, drive: cf.driveOrImg,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy'; import { getVisuals, stripIndent } from '../../utils/lazy';
@ -38,6 +39,8 @@ export default class ConfigReconfigureCmd extends Command {
'$ balena config reconfigure --drive balena.img --advanced', '$ balena config reconfigure --drive balena.img --advanced',
]; ];
public static usage = 'config reconfigure';
public static flags = { public static flags = {
drive: cf.driveOrImg, drive: cf.driveOrImg,
advanced: Flags.boolean({ advanced: Flags.boolean({

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getVisuals, stripIndent } from '../../utils/lazy'; import { getVisuals, stripIndent } from '../../utils/lazy';
@ -47,6 +48,8 @@ export default class ConfigWriteCmd extends Command {
}), }),
}; };
public static usage = 'config write <key> <value>';
public static flags = { public static flags = {
drive: cf.driveOrImg, drive: cf.driveOrImg,
help: cf.help, help: cf.help,

View File

@ -15,8 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Flags, Command } from '@oclif/core'; import { Args, Flags } from '@oclif/core';
import type { ImageDescriptor } from '@balena/compose/dist/parse'; import type { ImageDescriptor } from '@balena/compose/dist/parse';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy';
import { import {
@ -106,6 +108,8 @@ ${dockerignoreHelp}
image: Args.string({ description: 'the image to deploy' }), image: Args.string({ description: 'the image to deploy' }),
}; };
public static usage = 'deploy <fleet> [image]';
public static flags = { public static flags = {
source: Flags.string({ source: Flags.string({
description: description:
@ -153,9 +157,7 @@ ${dockerignoreHelp}
(await import('events')).defaultMaxListeners = 1000; (await import('events')).defaultMaxListeners = 1000;
const Logger = await import('../../utils/logger'); const logger = await Command.getLogger();
const logger = Logger.getLogger();
logger.logDebug('Parsing input...'); logger.logDebug('Parsing input...');
const { fleet, image } = params; const { fleet, image } = params;
@ -362,13 +364,20 @@ ${dockerignoreHelp}
$select: ['commit'], $select: ['commit'],
}); });
} else { } else {
const [{ id: userId }, auth, apiEndpoint] = await Promise.all([
sdk.auth.getUserInfo(),
sdk.auth.getToken(),
sdk.settings.get('apiUrl'),
]);
release = await $deployProject( release = await $deployProject(
docker, docker,
sdk,
logger, logger,
project.composition, project.composition,
images, images,
opts.app.id, opts.app.id,
userId,
`Bearer ${auth}`,
apiEndpoint,
!opts.shouldUploadLogs, !opts.shouldUploadLogs,
composeOpts.projectPath, composeOpts.projectPath,
opts.createAsDraft, opts.createAsDraft,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -40,6 +41,8 @@ export default class DeviceDeactivateCmd extends Command {
}), }),
}; };
public static usage = 'device deactivate <uuid>';
public static flags = { public static flags = {
yes: cf.yes, yes: cf.yes,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
@ -35,6 +36,8 @@ export default class DeviceIdentifyCmd extends Command {
}), }),
}; };
public static usage = 'device identify <uuid>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers'; import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
@ -61,6 +62,8 @@ export default class DeviceCmd extends Command {
}), }),
}; };
public static usage = 'device <uuid>';
public static flags = { public static flags = {
json: cf.json, json: cf.json,
help: cf.help, help: cf.help,
@ -88,7 +91,7 @@ export default class DeviceCmd extends Command {
}, },
...expandForAppName.$expand, ...expandForAppName.$expand,
}, },
} }
: { : {
$select: [ $select: [
'device_name', 'device_name',
@ -115,7 +118,7 @@ export default class DeviceCmd extends Command {
'is_undervolted', 'is_undervolted',
], ],
...expandForAppName, ...expandForAppName,
}, },
)) as ExtendedDevice; )) as ExtendedDevice;
if (options.view) { if (options.view) {

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages'; import { applicationIdInfo } from '../../utils/messages';
@ -42,17 +43,17 @@ export default class DeviceInitCmd extends Command {
This command effectively combines several other balena CLI commands in one, This command effectively combines several other balena CLI commands in one,
namely: namely:
'balena device register' 'balena device register'
'balena os download' 'balena os download'
'balena os build-config' or 'balena config generate' 'balena os build-config' or 'balena config generate'
'balena os configure' 'balena os configure'
'balena os local flash' 'balena os local flash'
Possible arguments for the '--fleet', '--os-version' and '--drive' options can Possible arguments for the '--fleet', '--os-version' and '--drive' options can
be listed respectively with the commands: be listed respectively with the commands:
'balena fleet list' 'balena fleets'
'balena os versions' 'balena os versions'
'balena util available-drives' 'balena util available-drives'
If the '--fleet' or '--drive' options are omitted, interactive menus will be If the '--fleet' or '--drive' options are omitted, interactive menus will be
@ -73,6 +74,8 @@ export default class DeviceInitCmd extends Command {
'$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes', '$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes',
]; ];
public static usage = 'device init';
public static flags = { public static flags = {
fleet: cf.fleet, fleet: cf.fleet,
yes: cf.yes, yes: cf.yes,
@ -116,9 +119,8 @@ export default class DeviceInitCmd extends Command {
tmp.setGracefulCleanup(); tmp.setGracefulCleanup();
const { downloadOSImage } = await import('../../utils/cloud'); const { downloadOSImage } = await import('../../utils/cloud');
const { getApplication } = await import('../../utils/sdk'); const { getApplication } = await import('../../utils/sdk');
const Logger = await import('../../utils/logger');
const logger = Logger.getLogger(); const logger = await Command.getLogger();
const balena = getBalenaSdk(); const balena = getBalenaSdk();
// Get application and // Get application and
@ -130,7 +132,7 @@ export default class DeviceInitCmd extends Command {
$select: 'slug', $select: 'slug',
}, },
}, },
}) })
: await (await import('../../utils/patterns')).selectApplication(); : await (await import('../../utils/patterns')).selectApplication();
// Register new device // Register new device

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -41,6 +42,8 @@ export default class DeviceLocalModeCmd extends Command {
}), }),
}; };
public static usage = 'device local-mode <uuid>';
public static flags = { public static flags = {
enable: Flags.boolean({ enable: Flags.boolean({
description: 'enable local mode', description: 'enable local mode',

View File

@ -15,13 +15,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import type { import type {
BalenaSDK, BalenaSDK,
Device, Device,
PineOptions, PineOptions,
PineTypedResult, PineTypedResult,
} from 'balena-sdk'; } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -53,6 +54,8 @@ export default class DeviceMoveCmd extends Command {
}), }),
}; };
public static usage = 'device move <uuid(s)>';
public static flags = { public static flags = {
fleet: cf.fleet, fleet: cf.fleet,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy'; import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import type { Device } from 'balena-sdk'; import type { Device } from 'balena-sdk';
@ -36,7 +37,6 @@ export default class DeviceOsUpdateCmd extends Command {
'$ balena device os-update 23c73a1', '$ balena device os-update 23c73a1',
'$ balena device os-update 23c73a1 --version 2.101.7', '$ balena device os-update 23c73a1 --version 2.101.7',
'$ balena device os-update 23c73a1 --version 2.31.0+rev1.prod', '$ balena device os-update 23c73a1 --version 2.31.0+rev1.prod',
'$ balena device os-update 23c73a1 --include-draft',
]; ];
public static args = { public static args = {
@ -46,15 +46,11 @@ export default class DeviceOsUpdateCmd extends Command {
}), }),
}; };
public static usage = 'device os-update <uuid>';
public static flags = { public static flags = {
version: Flags.string({ version: Flags.string({
description: 'a balenaOS version', description: 'a balenaOS version',
exclusive: ['include-draft'],
}),
'include-draft': Flags.boolean({
description: 'include pre-release balenaOS versions',
default: false,
exclusive: ['version'],
}), }),
yes: cf.yes, yes: cf.yes,
help: cf.help, help: cf.help,
@ -90,25 +86,10 @@ export default class DeviceOsUpdateCmd extends Command {
); );
} }
let includeDraft = options['include-draft'];
if (!includeDraft && options.version != null) {
const bSemver = await import('balena-semver');
const parsedVersion = bSemver.parse(options.version);
// When the user provides a draft version, we need to pass `includeDraft`
// to the os.getSupportedOsUpdateVersions() since w/o it the results
// will for sure not include the user provided version and the command
// would return a "not in the Host OS update targets" error.
includeDraft =
parsedVersion != null && parsedVersion.prerelease.length > 0;
}
// Get supported OS update versions // Get supported OS update versions
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions( const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
is_of__device_type[0].slug, is_of__device_type[0].slug,
currentOsVersion, currentOsVersion,
{
includeDraft,
},
); );
if (hupVersionInfo.versions.length === 0) { if (hupVersionInfo.versions.length === 0) {
throw new ExpectedError( throw new ExpectedError(
@ -119,8 +100,6 @@ export default class DeviceOsUpdateCmd extends Command {
// Get target OS version // Get target OS version
let targetOsVersion = options.version; let targetOsVersion = options.version;
if (targetOsVersion != null) { if (targetOsVersion != null) {
const { normalizeOsVersion } = await import('../../utils/normalization');
targetOsVersion = normalizeOsVersion(targetOsVersion);
if (!hupVersionInfo.versions.includes(targetOsVersion)) { if (!hupVersionInfo.versions.includes(targetOsVersion)) {
throw new ExpectedError( throw new ExpectedError(
`The provided version ${targetOsVersion} is not in the Host OS update targets for this device`, `The provided version ${targetOsVersion} is not in the Host OS update targets for this device`,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getExpandedProp } from '../../utils/pine'; import { getExpandedProp } from '../../utils/pine';
@ -43,6 +44,8 @@ export default class DevicePinCmd extends Command {
}), }),
}; };
public static usage = 'device pin <uuid> [releaseToPinTo]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };
@ -79,7 +82,7 @@ export default class DevicePinCmd extends Command {
pinnedRelease pinnedRelease
? `This device is currently pinned to ${pinnedRelease}.` ? `This device is currently pinned to ${pinnedRelease}.`
: 'This device is not currently pinned to any release.' : 'This device is not currently pinned to any release.'
} \n\nTo see a list of all releases this device can be pinned to, run \`balena release list ${appSlug}\`.`, } \n\nTo see a list of all releases this device can be pinned to, run \`balena releases ${appSlug}\`.`,
); );
} else { } else {
await balena.models.device.pinToRelease(params.uuid, releaseToPinTo); await balena.models.device.pinToRelease(params.uuid, releaseToPinTo);

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -43,6 +44,8 @@ export default class DevicePublicUrlCmd extends Command {
}), }),
}; };
public static usage = 'device public-url <uuid>';
public static flags = { public static flags = {
enable: Flags.boolean({ enable: Flags.boolean({
description: 'enable the public URL', description: 'enable the public URL',

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
@ -34,6 +35,8 @@ export default class DevicePurgeCmd extends Command {
'$ balena device purge 55d43b3,23c73a1', '$ balena device purge 55d43b3,23c73a1',
]; ];
public static usage = 'device purge <uuid>';
public static args = { public static args = {
uuid: Args.string({ uuid: Args.string({
description: 'comma-separated list (no blank spaces) of device UUIDs', description: 'comma-separated list (no blank spaces) of device UUIDs',

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -34,6 +35,8 @@ export default class DeviceRebootCmd extends Command {
}), }),
}; };
public static usage = 'device reboot <uuid>';
public static flags = { public static flags = {
force: cf.force, force: cf.force,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -43,6 +44,8 @@ export default class DeviceRegisterCmd extends Command {
fleet: ca.fleetRequired, fleet: ca.fleetRequired,
}; };
public static usage = 'device register <fleet>';
public static flags = { public static flags = {
uuid: Flags.string({ uuid: Flags.string({
description: 'custom uuid', description: 'custom uuid',

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy'; import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
@ -42,6 +43,8 @@ export default class DeviceRenameCmd extends Command {
}), }),
}; };
public static usage = 'device rename <uuid> [newName]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { import type {
@ -52,6 +53,8 @@ export default class DeviceRestartCmd extends Command {
}), }),
}; };
public static usage = 'device restart <uuid>';
public static flags = { public static flags = {
service: Flags.string({ service: Flags.string({
description: description:

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -42,6 +43,8 @@ export default class DeviceRmCmd extends Command {
}), }),
}; };
public static usage = 'device rm <uuid(s)>';
public static flags = { public static flags = {
yes: cf.yes, yes: cf.yes,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
@ -35,6 +36,8 @@ export default class DeviceShutdownCmd extends Command {
}), }),
}; };
public static usage = 'device shutdown <uuid>';
public static flags = { public static flags = {
force: cf.force, force: cf.force,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk'; import type { BalenaSDK } from 'balena-sdk';
@ -45,6 +46,8 @@ export default class DeviceStartServiceCmd extends Command {
}), }),
}; };
public static usage = 'device start-service <uuid>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
import type { BalenaSDK } from 'balena-sdk'; import type { BalenaSDK } from 'balena-sdk';
@ -45,6 +46,8 @@ export default class DeviceStopServiceCmd extends Command {
}), }),
}; };
public static usage = 'device stop-service <uuid>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -34,6 +35,8 @@ export default class DeviceTrackFleetCmd extends Command {
}), }),
}; };
public static usage = 'device track-fleet <uuid>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers'; import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
@ -35,9 +35,7 @@ const devicesSelectFields = {
], ],
} satisfies PineOptions<Device>; } satisfies PineOptions<Device>;
export default class DeviceListCmd extends Command { export default class DevicesCmd extends Command {
public static aliases = ['devices'];
public static description = stripIndent` public static description = stripIndent`
List all devices. List all devices.
@ -50,11 +48,13 @@ export default class DeviceListCmd extends Command {
${jsonInfo.split('\n').join('\n\t\t')} ${jsonInfo.split('\n').join('\n\t\t')}
`; `;
public static examples = [ public static examples = [
'$ balena device list', '$ balena devices',
'$ balena device list --fleet MyFleet', '$ balena devices --fleet MyFleet',
'$ balena device list -f myorg/myfleet', '$ balena devices -f myorg/myfleet',
]; ];
public static usage = 'devices';
public static flags = { public static flags = {
fleet: cf.fleet, fleet: cf.fleet,
json: cf.json, json: cf.json,
@ -66,7 +66,7 @@ export default class DeviceListCmd extends Command {
public static authenticated = true; public static authenticated = true;
public async run() { public async run() {
const { flags: options } = await this.parse(DeviceListCmd); const { flags: options } = await this.parse(DevicesCmd);
const balena = getBalenaSdk(); const balena = getBalenaSdk();
const devicesOptions = { const devicesOptions = {

View File

@ -14,11 +14,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import type * as BalenaSdk from 'balena-sdk'; import type * as BalenaSdk from 'balena-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { CommandHelp } from '../../utils/oclif-utils';
export default class DevicesSupportedCmd extends Command { export default class DevicesSupportedCmd extends Command {
public static description = stripIndent` public static description = stripIndent`
@ -37,6 +40,11 @@ export default class DevicesSupportedCmd extends Command {
'$ balena devices supported --json', '$ balena devices supported --json',
]; ];
public static usage = (
'devices supported ' +
new CommandHelp({ args: DevicesSupportedCmd.args }).defaultUsage()
).trim();
public static flags = { public static flags = {
help: cf.help, help: cf.help,
json: Flags.boolean({ json: Flags.boolean({

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import type * as BalenaSdk from 'balena-sdk'; import type * as BalenaSdk from 'balena-sdk';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -91,6 +92,7 @@ export default class EnvAddCmd extends Command {
// Required for supporting empty string ('') `value` args. // Required for supporting empty string ('') `value` args.
public static strict = false; public static strict = false;
public static usage = 'env add <name> [value]';
public static flags = { public static flags = {
fleet: { ...cf.fleet, exclusive: ['device'] }, fleet: { ...cf.fleet, exclusive: ['device'] },
@ -110,9 +112,7 @@ export default class EnvAddCmd extends Command {
); );
} }
const { checkLoggedIn } = await import('../../utils/patterns'); await Command.checkLoggedIn();
await checkLoggedIn();
if (params.value == null) { if (params.value == null) {
params.value = process.env[params.name]; params.value = process.env[params.name];

View File

@ -14,7 +14,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ec from '../../utils/env-common'; import * as ec from '../../utils/env-common';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -51,6 +53,8 @@ export default class EnvRenameCmd extends Command {
}), }),
}; };
public static usage = 'env rename <id> <value>';
public static flags = { public static flags = {
config: ec.booleanConfig, config: ec.booleanConfig,
device: ec.booleanDevice, device: ec.booleanDevice,
@ -61,9 +65,7 @@ export default class EnvRenameCmd extends Command {
public async run() { public async run() {
const { args: params, flags: opt } = await this.parse(EnvRenameCmd); const { args: params, flags: opt } = await this.parse(EnvRenameCmd);
const { checkLoggedIn } = await import('../../utils/patterns'); await Command.checkLoggedIn();
await checkLoggedIn();
await getBalenaSdk().pine.patch({ await getBalenaSdk().pine.patch({
resource: ec.getVarResourceName(opt.config, opt.device, opt.service), resource: ec.getVarResourceName(opt.config, opt.device, opt.service),

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as ec from '../../utils/env-common'; import * as ec from '../../utils/env-common';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation'; import { parseAsInteger } from '../../utils/validation';
@ -50,6 +52,8 @@ export default class EnvRmCmd extends Command {
}), }),
}; };
public static usage = 'env rm <id>';
public static flags = { public static flags = {
config: ec.booleanConfig, config: ec.booleanConfig,
device: ec.booleanDevice, device: ec.booleanDevice,
@ -65,9 +69,7 @@ export default class EnvRmCmd extends Command {
public async run() { public async run() {
const { args: params, flags: opt } = await this.parse(EnvRmCmd); const { args: params, flags: opt } = await this.parse(EnvRmCmd);
const { checkLoggedIn } = await import('../../utils/patterns'); await Command.checkLoggedIn();
await checkLoggedIn();
const { confirm } = await import('../../utils/patterns'); const { confirm } = await import('../../utils/patterns');
await confirm( await confirm(

View File

@ -14,10 +14,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import type { Interfaces } from '@oclif/core'; import type { Interfaces } from '@oclif/core';
import type * as SDK from 'balena-sdk'; import type * as SDK from 'balena-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
@ -93,6 +94,8 @@ export default class EnvsCmd extends Command {
'$ balena envs --device 7cf02a6 --service MyService', '$ balena envs --device 7cf02a6 --service MyService',
]; ];
public static usage = 'envs';
public static flags = { public static flags = {
fleet: { ...cf.fleet, exclusive: ['device'] }, fleet: { ...cf.fleet, exclusive: ['device'] },
config: Flags.boolean({ config: Flags.boolean({
@ -112,9 +115,7 @@ export default class EnvsCmd extends Command {
const variables: EnvironmentVariableInfo[] = []; const variables: EnvironmentVariableInfo[] = [];
const { checkLoggedIn } = await import('../../utils/patterns'); await Command.checkLoggedIn();
await checkLoggedIn();
if (!options.fleet && !options.device) { if (!options.fleet && !options.device) {
throw new ExpectedError('Missing --fleet or --device option'); throw new ExpectedError('Missing --fleet or --device option');
@ -125,7 +126,7 @@ export default class EnvsCmd extends Command {
let fleetSlug: string | undefined = options.fleet let fleetSlug: string | undefined = options.fleet
? await ( ? await (
await import('../../utils/sdk') await import('../../utils/sdk')
).getFleetSlug(balena, options.fleet) ).getFleetSlug(balena, options.fleet)
: undefined; : undefined;
let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
@ -54,6 +56,8 @@ export default class FleetCreateCmd extends Command {
}), }),
}; };
public static usage = 'fleet create <name>';
public static flags = { public static flags = {
organization: Flags.string({ organization: Flags.string({
char: 'o', char: 'o',

View File

@ -15,10 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Command } from '@oclif/core'; import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages'; import { applicationIdInfo } from '../../utils/messages';
export default class FleetCmd extends Command { export default class FleetCmd extends Command {
@ -39,13 +41,15 @@ export default class FleetCmd extends Command {
fleet: ca.fleetRequired, fleet: ca.fleetRequired,
}; };
public static usage = 'fleet <fleet>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
view: Flags.boolean({ view: Flags.boolean({
default: false, default: false,
description: 'open fleet dashboard page', description: 'open fleet dashboard page',
}), }),
json: cf.json, ...cf.dataOutputFlags,
}; };
public static authenticated = true; public static authenticated = true;
@ -74,28 +78,16 @@ export default class FleetCmd extends Command {
return; return;
} }
const applicationToDisplay = { const outputApplication = {
id: application.id, ...application,
app_name: application.app_name,
slug: application.slug,
device_type: application.is_for__device_type[0].slug, device_type: application.is_for__device_type[0].slug,
commit: application.should_be_running__release[0]?.commit, commit: application.should_be_running__release[0]?.commit,
}; };
if (options.json) { await this.outputData(
console.log(JSON.stringify(applicationToDisplay, null, 4)); outputApplication,
return; ['app_name', 'id', 'device_type', 'slug', 'commit'],
} options,
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
console.log(`== ${applicationToDisplay.app_name}`);
console.log(
getVisuals().table.vertical(applicationToDisplay, [
'id',
'device_type',
'slug',
'commit',
]),
); );
} }
} }

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { getExpandedProp } from '../../utils/pine'; import { getExpandedProp } from '../../utils/pine';
@ -43,6 +44,8 @@ export default class FleetPinCmd extends Command {
}), }),
}; };
public static usage = 'fleet pin <slug> [releaseToPinTo]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };
@ -76,7 +79,7 @@ export default class FleetPinCmd extends Command {
pinnedRelease pinnedRelease
? `This fleet is currently pinned to ${pinnedRelease}.` ? `This fleet is currently pinned to ${pinnedRelease}.`
: 'This fleet is not currently pinned to any release.' : 'This fleet is not currently pinned to any release.'
} \n\nTo see a list of all releases this fleet can be pinned to, run \`balena release list ${slug}\`.`, } \n\nTo see a list of all releases this fleet can be pinned to, run \`balena releases ${slug}\`.`,
); );
} else { } else {
await balena.models.application.pinToRelease(slug, releaseToPinTo); await balena.models.application.pinToRelease(slug, releaseToPinTo);

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -40,6 +40,8 @@ export default class FleetPurgeCmd extends Command {
fleet: ca.fleetRequired, fleet: ca.fleetRequired,
}; };
public static usage = 'fleet purge <fleet>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy'; import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
@ -46,6 +48,8 @@ export default class FleetRenameCmd extends Command {
}), }),
}; };
public static usage = 'fleet rename <fleet> [newName]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -39,6 +39,8 @@ export default class FleetRestartCmd extends Command {
fleet: ca.fleetRequired, fleet: ca.fleetRequired,
}; };
public static usage = 'fleet restart <fleet>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import * as ca from '../../utils/common-args'; import * as ca from '../../utils/common-args';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages'; import { applicationIdInfo } from '../../utils/messages';
import { Command } from '@oclif/core';
export default class FleetRmCmd extends Command { export default class FleetRmCmd extends Command {
public static description = stripIndent` public static description = stripIndent`
@ -42,6 +42,8 @@ export default class FleetRmCmd extends Command {
fleet: ca.fleetRequired, fleet: ca.fleetRequired,
}; };
public static usage = 'fleet rm <fleet>';
public static flags = { public static flags = {
yes: cf.yes, yes: cf.yes,
help: cf.help, help: cf.help,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -37,6 +38,8 @@ export default class FleetTrackLatestCmd extends Command {
}), }),
}; };
public static usage = 'fleet track-latest <slug>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -16,9 +16,10 @@
*/ */
import type * as BalenaSdk from 'balena-sdk'; import type * as BalenaSdk from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { Command } from '@oclif/core';
interface ExtendedApplication extends ApplicationWithDeviceTypeSlug { interface ExtendedApplication extends ApplicationWithDeviceTypeSlug {
device_count: number; device_count: number;
@ -26,9 +27,7 @@ interface ExtendedApplication extends ApplicationWithDeviceTypeSlug {
device_type?: string; device_type?: string;
} }
export default class FleetListCmd extends Command { export default class FleetsCmd extends Command {
public static aliases = ['fleets'];
public static description = stripIndent` public static description = stripIndent`
List all fleets. List all fleets.
@ -38,18 +37,20 @@ export default class FleetListCmd extends Command {
\`balena fleet <fleet>\` \`balena fleet <fleet>\`
`; `;
public static examples = ['$ balena fleet list']; public static examples = ['$ balena fleets'];
public static usage = 'fleets';
public static flags = { public static flags = {
...cf.dataSetOutputFlags,
help: cf.help, help: cf.help,
json: cf.json,
}; };
public static authenticated = true; public static authenticated = true;
public static primary = true; public static primary = true;
public async run() { public async run() {
const { flags: options } = await this.parse(FleetListCmd); const { flags: options } = await this.parse(FleetsCmd);
const balena = getBalenaSdk(); const balena = getBalenaSdk();
@ -76,29 +77,17 @@ export default class FleetListCmd extends Command {
application.device_type = application.is_for__device_type[0].slug; application.device_type = application.is_for__device_type[0].slug;
}); });
const applicationsToDisplay = applications.map((application) => ({ await this.outputData(
id: application.id, applications,
app_name: application.app_name, [
slug: application.slug,
device_type: application.device_type,
online_devices: application.online_devices,
device_count: application.device_count,
}));
if (options.json) {
console.log(JSON.stringify(applicationsToDisplay, null, 4));
return;
}
console.log(
getVisuals().table.horizontal(applicationsToDisplay, [
'id', 'id',
'app_name => NAME', 'app_name',
'slug', 'slug',
'device_type', 'device_type',
'device_count', 'device_count',
'online_devices', 'online_devices',
]), ],
options,
); );
} }
} }

View File

@ -15,8 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
import { CommandHelp } from '../../utils/oclif-utils';
// 'Internal' commands are called during the execution of other commands. // 'Internal' commands are called during the execution of other commands.
// `osinit` is called during `os initialize` // `osinit` is called during `os initialize`
@ -46,6 +48,11 @@ export default class OsinitCmd extends Command {
}), }),
}; };
public static usage = (
'internal osinit ' +
new CommandHelp({ args: OsinitCmd.args }).defaultUsage()
).trim();
public static hidden = true; public static hidden = true;
public static root = true; public static root = true;
public static offlineCompatible = true; public static offlineCompatible = true;

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Flags, Command } from '@oclif/core'; import { Args, Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages'; import { applicationIdInfo } from '../../utils/messages';
@ -59,6 +60,9 @@ export default class JoinCmd extends Command {
}), }),
}; };
// Hardcoded to preserve camelcase
public static usage = 'join [deviceIpOrHostname]';
public static flags = { public static flags = {
fleet: cf.fleet, fleet: cf.fleet,
pollInterval: Flags.integer({ pollInterval: Flags.integer({
@ -76,8 +80,7 @@ export default class JoinCmd extends Command {
const promote = await import('../../utils/promote'); const promote = await import('../../utils/promote');
const sdk = getBalenaSdk(); const sdk = getBalenaSdk();
const Logger = await import('../../utils/logger'); const logger = await Command.getLogger();
const logger = Logger.getLogger();
return promote.join( return promote.join(
logger, logger,
sdk, sdk,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
@ -28,7 +29,7 @@ export default class KeyAddCmd extends Command {
If \`path\` is omitted, the command will attempt to read the SSH key from stdin. If \`path\` is omitted, the command will attempt to read the SSH key from stdin.
About SSH keys About SSH keys
An "SSH key" actually consists of a public/private key pair. A typical name An "SSH key" actually consists of a public/private key pair. A typical name
for the private key file is "id_rsa", and a typical name for the public key for the private key file is "id_rsa", and a typical name for the public key
file is "id_rsa.pub". Both key files are saved to your computer (with the file is "id_rsa.pub". Both key files are saved to your computer (with the
@ -36,7 +37,7 @@ export default class KeyAddCmd extends Command {
saved to your balena account. This means that if you change computers or saved to your balena account. This means that if you change computers or
otherwise lose the private key, you cannot recover the private key through otherwise lose the private key, you cannot recover the private key through
your balena account. You can however add new keys, and delete the old ones. your balena account. You can however add new keys, and delete the old ones.
To generate a new SSH key pair, a nice guide can be found in GitHub's docs: To generate a new SSH key pair, a nice guide can be found in GitHub's docs:
https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
Skip the step about adding the key to a GitHub account, and instead add it to Skip the step about adding the key to a GitHub account, and instead add it to
@ -60,12 +61,16 @@ export default class KeyAddCmd extends Command {
}), }),
}; };
public static usage = 'key add <name> [path]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };
public static authenticated = true; public static authenticated = true;
public static readStdin = true;
public async run() { public async run() {
const { args: params } = await this.parse(KeyAddCmd); const { args: params } = await this.parse(KeyAddCmd);
@ -73,6 +78,8 @@ export default class KeyAddCmd extends Command {
if (params.path != null) { if (params.path != null) {
const { readFile } = (await import('fs')).promises; const { readFile } = (await import('fs')).promises;
key = await readFile(params.path, 'utf8'); key = await readFile(params.path, 'utf8');
} else if (this.stdin.length > 0) {
key = this.stdin;
} else { } else {
throw new ExpectedError('No public key file or path provided.'); throw new ExpectedError('No public key file or path provided.');
} }

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation'; import { parseAsInteger } from '../../utils/validation';
@ -37,6 +38,8 @@ export default class KeyCmd extends Command {
}), }),
}; };
public static usage = 'key <id>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { parseAsInteger } from '../../utils/validation'; import { parseAsInteger } from '../../utils/validation';
@ -39,6 +40,8 @@ export default class KeyRmCmd extends Command {
}), }),
}; };
public static usage = 'key rm <id>';
public static flags = { public static flags = {
yes: cf.yes, yes: cf.yes,
help: cf.help, help: cf.help,

View File

@ -15,19 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
export default class KeyListCmd extends Command { export default class KeysCmd extends Command {
public static aliases = ['keys'];
public static description = stripIndent` public static description = stripIndent`
List the SSH keys in balenaCloud. List the SSH keys in balenaCloud.
List all SSH keys registered in balenaCloud for the logged in user. List all SSH keys registered in balenaCloud for the logged in user.
`; `;
public static examples = ['$ balena key list']; public static examples = ['$ balena keys'];
public static usage = 'keys';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
@ -36,7 +36,7 @@ export default class KeyListCmd extends Command {
public static authenticated = true; public static authenticated = true;
public async run() { public async run() {
await this.parse(KeyListCmd); await this.parse(KeysCmd);
const keys = await getBalenaSdk().models.key.getAll(); const keys = await getBalenaSdk().models.key.getAll();

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
import { parseAsLocalHostnameOrIp } from '../../utils/validation'; import { parseAsLocalHostnameOrIp } from '../../utils/validation';
@ -49,6 +50,8 @@ export default class LeaveCmd extends Command {
}), }),
}; };
public static usage = 'leave [deviceIpOrHostname]';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };
@ -60,8 +63,7 @@ export default class LeaveCmd extends Command {
const { args: params } = await this.parse(LeaveCmd); const { args: params } = await this.parse(LeaveCmd);
const promote = await import('../../utils/promote'); const promote = await import('../../utils/promote');
const Logger = await import('../../utils/logger'); const logger = await Command.getLogger();
const logger = Logger.getLogger();
return promote.leave(logger, params.deviceIpOrHostname); return promote.leave(logger, params.deviceIpOrHostname);
} }
} }

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import { promisify } from 'util'; import { promisify } from 'util';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { stripIndent } from '../../utils/lazy'; import { stripIndent } from '../../utils/lazy';
@ -39,6 +40,8 @@ export default class LocalConfigureCmd extends Command {
}), }),
}; };
public static usage = 'local configure <target>';
public static flags = { public static flags = {
help: cf.help, help: cf.help,
}; };

View File

@ -15,8 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Args, Command } from '@oclif/core'; import { Args } from '@oclif/core';
import type { BlockDevice } from 'etcher-sdk/build/source-destination'; import type { BlockDevice } from 'etcher-sdk/build/source-destination';
import Command from '../../command';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getChalk, getVisuals, stripIndent } from '../../utils/lazy'; import { getChalk, getVisuals, stripIndent } from '../../utils/lazy';
@ -45,6 +46,8 @@ export default class LocalFlashCmd extends Command {
}), }),
}; };
public static usage = 'local flash <image>';
public static flags = { public static flags = {
drive: cf.drive, drive: cf.drive,
yes: cf.yes, yes: cf.yes,

View File

@ -15,7 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { Flags, Args, Command } from '@oclif/core'; import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags'; import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy'; import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { ExpectedError } from '../../errors'; import { ExpectedError } from '../../errors';
@ -62,6 +63,8 @@ export default class LoginCmd extends Command {
}), }),
}; };
public static usage = 'login';
public static flags = { public static flags = {
web: Flags.boolean({ web: Flags.boolean({
default: false, default: false,

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Command } from '@oclif/core'; import Command from '../../command';
import { getBalenaSdk, stripIndent } from '../../utils/lazy'; import { getBalenaSdk, stripIndent } from '../../utils/lazy';
export default class LogoutCmd extends Command { export default class LogoutCmd extends Command {
@ -26,6 +26,8 @@ export default class LogoutCmd extends Command {
`; `;
public static examples = ['$ balena logout']; public static examples = ['$ balena logout'];
public static usage = 'logout';
public async run() { public async run() {
await this.parse(LogoutCmd); await this.parse(LogoutCmd);
await getBalenaSdk().auth.logout(); await getBalenaSdk().auth.logout();

Some files were not shown because too many files have changed in this diff Show More