mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
103 Commits
v17.1.6
...
allow-cust
Author | SHA1 | Date | |
---|---|---|---|
eed4a385a9 | |||
9e4dd3fce2 | |||
b2590136fc | |||
bf5e61a61c | |||
f550d0c596 | |||
54302669b8 | |||
a4a4e33d7b | |||
8d6a621bfb | |||
4b2602676b | |||
b0810c0f85 | |||
97a6013537 | |||
1ba8db1459 | |||
cdada0aec8 | |||
1166533482 | |||
01538728cd | |||
3a7f6d78b0 | |||
dce48c90e9 | |||
fe70d164c1 | |||
09e2550b32 | |||
07854c3d42 | |||
858a455501 | |||
4e5eb4bcee | |||
696bad3ed6 | |||
9a9d0f02ef | |||
f46d00640b | |||
e369bd3599 | |||
75b29112a7 | |||
b7b01ecd53 | |||
801a25995c | |||
8296dea78c | |||
1da5a75c14 | |||
166de57179 | |||
85dece9e95 | |||
bfbc71215c | |||
d243c14d74 | |||
804eb27551 | |||
4266dc6951 | |||
0ba3522584 | |||
19b0e9489d | |||
d9fed9c34c | |||
81ee9f397f | |||
b9722c6796 | |||
29ade0f696 | |||
d5ae612513 | |||
65ba63d1a8 | |||
f5ffa7d84f | |||
dac3ace61d | |||
72459a04d1 | |||
1e83fcf1e3 | |||
b8769bb9e9 | |||
9f52ee8b21 | |||
90b65cd06b | |||
72a924f00e | |||
e4624eda10 | |||
4173cd82e6 | |||
b393f27e1b | |||
1a4a0e2439 | |||
4cd8f4c16e | |||
2de9d526e5 | |||
d9427c3c59 | |||
fc0cfac475 | |||
99094dbfda | |||
0711eefb7c | |||
dc40b0d969 | |||
4b5def0a8a | |||
f44fa38113 | |||
167dfeb269 | |||
a816548bb5 | |||
94001efc81 | |||
8bfafe8ecc | |||
d78045b6ab | |||
11eabc4b96 | |||
bfaa91c752 | |||
1b615e4690 | |||
7954e13154 | |||
45d8872a82 | |||
56cff46408 | |||
47a1a9c6af | |||
a434a5e657 | |||
221c213791 | |||
d2150c5cb7 | |||
70e113152d | |||
4a64102d67 | |||
9bf267166e | |||
f12249bc81 | |||
80d6c71b02 | |||
9ef4117fb8 | |||
25f3bf1fbe | |||
fe65351666 | |||
7d13946c3e | |||
1ffa8d38f1 | |||
6914d39370 | |||
04db9c7a91 | |||
c785d01a1c | |||
87ba364f89 | |||
b7e5915c7a | |||
0cef6b8f87 | |||
71f1dbd80a | |||
36edcf0cb8 | |||
8204dcad93 | |||
03bcb6cff7 | |||
66b6eed57c | |||
99b0f2c022 |
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
/completion/*
|
||||
/bin/*
|
21
.eslintrc.js
Normal file
21
.eslintrc.js
Normal file
@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@balena/lint/config/.eslintrc.js'],
|
||||
parserOptions: {
|
||||
project: 'tsconfig.dev.json',
|
||||
},
|
||||
root: true,
|
||||
rules: {
|
||||
ignoreDefinitionFiles: 0,
|
||||
// to avoid the `warning Forbidden non-null assertion @typescript-eslint/no-non-null-assertion`
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
};
|
9
.github/actions/publish/action.yml
vendored
9
.github/actions/publish/action.yml
vendored
@ -18,7 +18,7 @@ inputs:
|
||||
default: 'accounts+apple@balena.io'
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '18.x'
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: 'true'
|
||||
@ -28,7 +28,7 @@ runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download custom source artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}
|
||||
@ -127,8 +127,9 @@ runs:
|
||||
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
|
||||
with:
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
12
.github/actions/test/action.yml
vendored
12
.github/actions/test/action.yml
vendored
@ -15,7 +15,7 @@ inputs:
|
||||
# --- custom environment
|
||||
NODE_VERSION:
|
||||
type: string
|
||||
default: '18.x'
|
||||
default: '20.x'
|
||||
VERBOSE:
|
||||
type: string
|
||||
default: "true"
|
||||
@ -31,6 +31,12 @@ runs:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
|
||||
- name: Set up Python 3.11
|
||||
if: runner.os == 'macOS'
|
||||
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Test release
|
||||
shell: bash
|
||||
run: |
|
||||
@ -45,14 +51,14 @@ runs:
|
||||
fi
|
||||
|
||||
npm run build
|
||||
npm run test
|
||||
npm run test:core
|
||||
|
||||
- name: Compress custom source
|
||||
shell: pwsh
|
||||
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
|
||||
|
||||
- name: Upload custom artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}/custom.tgz
|
||||
|
4
.github/workflows/flowzone.yml
vendored
4
.github/workflows/flowzone.yml
vendored
@ -1,5 +1,4 @@
|
||||
name: Flowzone
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
@ -7,7 +6,6 @@ on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, closed]
|
||||
branches: [main, master]
|
||||
|
||||
jobs:
|
||||
flowzone:
|
||||
name: Flowzone
|
||||
@ -24,7 +22,5 @@ jobs:
|
||||
secrets: inherit
|
||||
with:
|
||||
custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-12"],["windows-2019"]]'
|
||||
repo_config: true
|
||||
repo_description: "The official balena CLI tool."
|
||||
github_prerelease: false
|
||||
restrict_custom_actions: false
|
||||
|
File diff suppressed because it is too large
Load Diff
538
CHANGELOG.md
538
CHANGELOG.md
@ -4,6 +4,544 @@ All notable changes to this project will be documented in this file
|
||||
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 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
|
||||
|
||||
* Stop testing dependency deduplication on the custom test runners [Thodoris Greasidis]
|
||||
* Temporarily pin oclif-core to ~3.11.0 to deduplicate the dependencies [Thodoris Greasidis]
|
||||
* Update TypeScript to 5.3.2 [Thodoris Greasidis]
|
||||
|
||||
## 17.4.4 - 2023-11-20
|
||||
|
||||
* Fix balena block create to actually create a block [Otávio Jacobi]
|
||||
|
||||
## 17.4.3 - 2023-11-17
|
||||
|
||||
* Bump oclif-core to v3 [Otávio Jacobi]
|
||||
|
||||
## 17.4.2 - 2023-11-15
|
||||
|
||||
* Docs: Automatically generate Capitano configuration [Vipul Gupta (@vipulgupta2048)]
|
||||
|
||||
## 17.4.1 - 2023-11-13
|
||||
|
||||
* Bump shrinkwrap [Otávio Jacobi]
|
||||
|
||||
## 17.4.0 - 2023-11-10
|
||||
|
||||
* device: Add `--json` option for JSON output [Brian Bugh]
|
||||
|
||||
## 17.3.2 - 2023-11-10
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update @balena/compose to 3.0.5 [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-compose-3.0.5 - 2023-11-09
|
||||
>
|
||||
> * builder: Fix unawaited buildDir pack() promise [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-compose-3.0.4 - 2023-11-09
|
||||
>
|
||||
> * api: Restore the accidentally removed concurrency limits [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-compose-3.0.3 - 2023-11-09
|
||||
>
|
||||
> * .mocharc: Use "exit: true" to workaround hanging on completion on node20 [Thodoris Greasidis]
|
||||
> * tests: Fix the expected error message for Dockerfile syntax errors [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 17.3.1 - 2023-11-09
|
||||
|
||||
* Use `pipeline` instead of `.pipe` when downloading OS image [Pagan Gazzard]
|
||||
|
||||
## 17.3.0 - 2023-11-06
|
||||
|
||||
* Add device start-service and stop-service commands [Morgan Larsson]
|
||||
|
||||
## 17.2.4 - 2023-11-06
|
||||
|
||||
* Dedupe shrinkwrap [myarmolinsky]
|
||||
|
||||
## 17.2.3 - 2023-11-03
|
||||
|
||||
* Generate docs for recently supported commands [Vipul Gupta (@vipulgupta2048)]
|
||||
|
||||
## 17.2.2 - 2023-10-30
|
||||
|
||||
* Bump balena-lint to 7.2.1 [myarmolinsky]
|
||||
|
||||
## 17.2.1 - 2023-10-26
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update balena-sdk and deduped dependencies [Otávio Jacobi] </summary>
|
||||
|
||||
> ### balena-sdk-19.0.0 - 2023-10-16
|
||||
>
|
||||
>
|
||||
> <details>
|
||||
> <summary> **BREAKING**: Drop support to node < 18 [Otávio Jacobi] </summary>
|
||||
>
|
||||
>> #### balena-register-device-9.0.1 - 2023-10-11
|
||||
>>
|
||||
>> * Fix balena-request peer dependency [Otávio Jacobi]
|
||||
>>
|
||||
>> #### balena-register-device-9.0.0 - 2023-10-11
|
||||
>>
|
||||
>> * Drop supoport for node 14 & 16 [Otávio Jacobi]
|
||||
>>
|
||||
>> #### balena-request-13.0.0 - 2023-10-11
|
||||
>>
|
||||
>> * Drop support for node 14 & 16 [Otávio Jacobi]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-sdk-18.3.0 - 2023-10-09
|
||||
>
|
||||
> * jwt: Deprecate the profile fields in favor of the user_profile [Thodoris Greasidis]
|
||||
> * jwt: Deprecate the intercom fields [Thodoris Greasidis]
|
||||
> * jwt: Deprecate the features fields [Thodoris Greasidis]
|
||||
> * jwt: Deprecate thw loginAs field [Thodoris Greasidis]
|
||||
> * jwt: Deprecate username & created_at in favor of the user resource [Thodoris Greasidis]
|
||||
> * Add typings for the user_profile resource [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.2.0 - 2023-09-26
|
||||
>
|
||||
> * Update @balena/lint to 7.2.0 [Thodoris Greasidis]
|
||||
> * Deprecate the social_service_account property of the JWTUser [Thodoris Greasidis]
|
||||
> * Add typings for the social_service_account resource [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.1.4 - 2023-08-24
|
||||
>
|
||||
> * Update TypeScript to 5.2.2 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.1.3 - 2023-08-23
|
||||
>
|
||||
> * tests/os: Refactor some promise tests to async await [Thodoris Greasidis]
|
||||
> * Fix os.getSupervisorReleaseByDeviceType test to work on balenaMachine [Thodoris Greasidis]
|
||||
>
|
||||
> <details>
|
||||
> <summary> Update balena-request from 12.0.2 to 12.0.4 [Thodoris Greasidis] </summary>
|
||||
>
|
||||
>> #### balena-request-12.0.4 - 2023-08-23
|
||||
>>
|
||||
>> * Refactor the interceptors to stop using .reduce() [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-request-12.0.3 - 2023-08-09
|
||||
>>
|
||||
>> * Avoid deep imports from balena-auth [Thodoris Greasidis]
|
||||
>> * Update balena-auth to 5.1.0 [Thodoris Greasidis]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-sdk-18.1.2 - 2023-08-23
|
||||
>
|
||||
> * organization-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
> * application-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
> * tests: Fix bugs that linting surfaced [Thodoris Greasidis]
|
||||
> * Update @balena/lint to 7.0.1 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.1.1 - 2023-08-22
|
||||
>
|
||||
> * logs: Emit errors when initializing the SDK with debug: true [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.1.0 - 2023-08-22
|
||||
>
|
||||
> * Improve the auth.getActorId() tests [Thodoris Greasidis]
|
||||
> * auth.getUserInfo: Add the actor id to the returned values [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.0.2 - 2023-08-18
|
||||
>
|
||||
> * patch: bump lint-staged from 13.3.0 to 14.0.0 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-sdk-18.0.1 - 2023-08-18
|
||||
>
|
||||
> * Replace dependabot with renovate [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-image-manager-10.0.1 - 2023-10-26
|
||||
>
|
||||
>
|
||||
> <details>
|
||||
> <summary> Update balena-sdk to 19.0.1 [Otávio Jacobi] </summary>
|
||||
>
|
||||
>> #### balena-sdk-19.0.1 - 2023-10-19
|
||||
>>
|
||||
>> * Fix test workflow to run on node 18 [Otávio Jacobi]
|
||||
>>
|
||||
>> #### balena-sdk-19.0.0 - 2023-10-16
|
||||
>>
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> **BREAKING**: Drop support to node < 18 [Otávio Jacobi] </summary>
|
||||
>>
|
||||
>>> ##### balena-register-device-9.0.1 - 2023-10-11
|
||||
>>>
|
||||
>>> * Fix balena-request peer dependency [Otávio Jacobi]
|
||||
>>>
|
||||
>>> ##### balena-register-device-9.0.0 - 2023-10-11
|
||||
>>>
|
||||
>>> * Drop supoport for node 14 & 16 [Otávio Jacobi]
|
||||
>>>
|
||||
>>> ##### balena-request-13.0.0 - 2023-10-11
|
||||
>>>
|
||||
>>> * Drop support for node 14 & 16 [Otávio Jacobi]
|
||||
>>>
|
||||
>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-18.3.0 - 2023-10-09
|
||||
>>
|
||||
>> * jwt: Deprecate the profile fields in favor of the user_profile [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate the intercom fields [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate the features fields [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate thw loginAs field [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate username & created_at in favor of the user resource [Thodoris Greasidis]
|
||||
>> * Add typings for the user_profile resource [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.2.0 - 2023-09-26
|
||||
>>
|
||||
>> * Update @balena/lint to 7.2.0 [Thodoris Greasidis]
|
||||
>> * Deprecate the social_service_account property of the JWTUser [Thodoris Greasidis]
|
||||
>> * Add typings for the social_service_account resource [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.4 - 2023-08-24
|
||||
>>
|
||||
>> * Update TypeScript to 5.2.2 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.3 - 2023-08-23
|
||||
>>
|
||||
>> * tests/os: Refactor some promise tests to async await [Thodoris Greasidis]
|
||||
>> * Fix os.getSupervisorReleaseByDeviceType test to work on balenaMachine [Thodoris Greasidis]
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> Update balena-request from 12.0.2 to 12.0.4 [Thodoris Greasidis] </summary>
|
||||
>>
|
||||
>>> ##### balena-request-12.0.4 - 2023-08-23
|
||||
>>>
|
||||
>>> * Refactor the interceptors to stop using .reduce() [Thodoris Greasidis]
|
||||
>>>
|
||||
>>> ##### balena-request-12.0.3 - 2023-08-09
|
||||
>>>
|
||||
>>> * Avoid deep imports from balena-auth [Thodoris Greasidis]
|
||||
>>> * Update balena-auth to 5.1.0 [Thodoris Greasidis]
|
||||
>>>
|
||||
>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-18.1.2 - 2023-08-23
|
||||
>>
|
||||
>> * organization-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
>> * application-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
>> * tests: Fix bugs that linting surfaced [Thodoris Greasidis]
|
||||
>> * Update @balena/lint to 7.0.1 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.1 - 2023-08-22
|
||||
>>
|
||||
>> * logs: Emit errors when initializing the SDK with debug: true [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.0 - 2023-08-22
|
||||
>>
|
||||
>> * Improve the auth.getActorId() tests [Thodoris Greasidis]
|
||||
>> * auth.getUserInfo: Add the actor id to the returned values [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.0.2 - 2023-08-18
|
||||
>>
|
||||
>> * patch: bump lint-staged from 13.3.0 to 14.0.0 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.0.1 - 2023-08-18
|
||||
>>
|
||||
>> * Replace dependabot with renovate [Thodoris Greasidis]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-image-manager-10.0.0 - 2023-10-26
|
||||
>
|
||||
> * Drop support for Node.js v16 [Otávio Jacobi]
|
||||
>
|
||||
> ### balena-image-manager-9.0.3 - 2023-10-26
|
||||
>
|
||||
> * Update @balena/lint [Otávio Jacobi]
|
||||
> * Bump rimraf [Otávio Jacobi]
|
||||
>
|
||||
> ### balena-preload-15.0.1 - 2023-10-26
|
||||
>
|
||||
>
|
||||
> <details>
|
||||
> <summary> Update balena-sdk to 19.0.1 [Otávio Jacobi] </summary>
|
||||
>
|
||||
>> #### balena-sdk-19.0.1 - 2023-10-19
|
||||
>>
|
||||
>> * Fix test workflow to run on node 18 [Otávio Jacobi]
|
||||
>>
|
||||
>> #### balena-sdk-19.0.0 - 2023-10-16
|
||||
>>
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> **BREAKING**: Drop support to node < 18 [Otávio Jacobi] </summary>
|
||||
>>
|
||||
>>> ##### balena-register-device-9.0.1 - 2023-10-11
|
||||
>>>
|
||||
>>> * Fix balena-request peer dependency [Otávio Jacobi]
|
||||
>>>
|
||||
>>> ##### balena-register-device-9.0.0 - 2023-10-11
|
||||
>>>
|
||||
>>> * Drop supoport for node 14 & 16 [Otávio Jacobi]
|
||||
>>>
|
||||
>>> ##### balena-request-13.0.0 - 2023-10-11
|
||||
>>>
|
||||
>>> * Drop support for node 14 & 16 [Otávio Jacobi]
|
||||
>>>
|
||||
>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-18.3.0 - 2023-10-09
|
||||
>>
|
||||
>> * jwt: Deprecate the profile fields in favor of the user_profile [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate the intercom fields [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate the features fields [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate thw loginAs field [Thodoris Greasidis]
|
||||
>> * jwt: Deprecate username & created_at in favor of the user resource [Thodoris Greasidis]
|
||||
>> * Add typings for the user_profile resource [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.2.0 - 2023-09-26
|
||||
>>
|
||||
>> * Update @balena/lint to 7.2.0 [Thodoris Greasidis]
|
||||
>> * Deprecate the social_service_account property of the JWTUser [Thodoris Greasidis]
|
||||
>> * Add typings for the social_service_account resource [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.4 - 2023-08-24
|
||||
>>
|
||||
>> * Update TypeScript to 5.2.2 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.3 - 2023-08-23
|
||||
>>
|
||||
>> * tests/os: Refactor some promise tests to async await [Thodoris Greasidis]
|
||||
>> * Fix os.getSupervisorReleaseByDeviceType test to work on balenaMachine [Thodoris Greasidis]
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> Update balena-request from 12.0.2 to 12.0.4 [Thodoris Greasidis] </summary>
|
||||
>>
|
||||
>>> ##### balena-request-12.0.4 - 2023-08-23
|
||||
>>>
|
||||
>>> * Refactor the interceptors to stop using .reduce() [Thodoris Greasidis]
|
||||
>>>
|
||||
>>> ##### balena-request-12.0.3 - 2023-08-09
|
||||
>>>
|
||||
>>> * Avoid deep imports from balena-auth [Thodoris Greasidis]
|
||||
>>> * Update balena-auth to 5.1.0 [Thodoris Greasidis]
|
||||
>>>
|
||||
>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-18.1.2 - 2023-08-23
|
||||
>>
|
||||
>> * organization-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
>> * application-invite: Fix throwing a typed error when passing an unkonwn role [Thodoris Greasidis]
|
||||
>> * tests: Fix bugs that linting surfaced [Thodoris Greasidis]
|
||||
>> * Update @balena/lint to 7.0.1 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.1 - 2023-08-22
|
||||
>>
|
||||
>> * logs: Emit errors when initializing the SDK with debug: true [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.1.0 - 2023-08-22
|
||||
>>
|
||||
>> * Improve the auth.getActorId() tests [Thodoris Greasidis]
|
||||
>> * auth.getUserInfo: Add the actor id to the returned values [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.0.2 - 2023-08-18
|
||||
>>
|
||||
>> * patch: bump lint-staged from 13.3.0 to 14.0.0 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-18.0.1 - 2023-08-18
|
||||
>>
|
||||
>> * Replace dependabot with renovate [Thodoris Greasidis]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-preload-15.0.0 - 2023-10-26
|
||||
>
|
||||
> * Drop support for Node.js v16 [Otávio Jacobi]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 17.2.0 - 2023-10-20
|
||||
|
||||
* release: Add '--json' option for JSON output [Brian Bugh]
|
||||
|
||||
## 17.1.7 - 2023-10-20
|
||||
|
||||
* tag set: Fix using empty string as a value [Thodoris Greasidis]
|
||||
* env add: Fix using empty string as a value [Thodoris Greasidis]
|
||||
* Add tests for empty & non-provided value for the `env add` command [Thodoris Greasidis]
|
||||
* Add tests for the `tag set` command [Thodoris Greasidis]
|
||||
|
||||
## 17.1.6 - 2023-10-18
|
||||
|
||||
* Allow custom actions for external contributors [Kyle Harding]
|
||||
|
@ -123,6 +123,20 @@ The README file is manually edited, but subsections are automatically extracted
|
||||
`docs/balena-cli.md` by the `getCapitanoDoc()` function in
|
||||
[`automation/capitanodoc/capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts).
|
||||
|
||||
**IMPORTANT**
|
||||
|
||||
The file [`capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts) lists
|
||||
commands to generate documentation from. At the moment, it's manually updated and maintained alphabetically.
|
||||
|
||||
To add a new command to be documented,
|
||||
|
||||
1. Find the resource which it is part of or create a new one.
|
||||
2. List the location of the build file
|
||||
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
|
||||
|
||||
The `INSTALL*.md` and `TROUBLESHOOTING.md` files are also manually edited.
|
||||
|
||||
## Patches folder
|
||||
|
@ -78,8 +78,8 @@ If you are a Node.js developer, you may wish to install the balena CLI via [npm]
|
||||
The npm installation involves building native (platform-specific) binary modules, which require
|
||||
some development tools to be installed first, as follows.
|
||||
|
||||
> **The balena CLI currently requires Node.js version 18.**
|
||||
> **Versions 19 and later are not yet fully supported.**
|
||||
> **The balena CLI currently requires Node.js version 20.**
|
||||
> **Versions 21 and later are not yet fully supported.**
|
||||
|
||||
### Install development tools
|
||||
|
||||
@ -89,7 +89,7 @@ some development tools to be installed first, as follows.
|
||||
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
|
||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 18
|
||||
$ nvm install 20
|
||||
```
|
||||
|
||||
The `curl` command line above uses
|
||||
@ -106,7 +106,7 @@ recommended.
|
||||
```sh
|
||||
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
|
||||
$ . ~/.bashrc
|
||||
$ nvm install 18
|
||||
$ nvm install 20
|
||||
```
|
||||
|
||||
#### **Windows** (not WSL)
|
||||
@ -114,7 +114,7 @@ $ nvm install 18
|
||||
Install:
|
||||
|
||||
* If you'd like the ability to switch between Node.js versions, install
|
||||
- Node.js v18 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||
- Node.js v20 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
|
||||
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
|
||||
instead.
|
||||
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:
|
||||
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { JsonVersions } from '../lib/commands/version';
|
||||
import type { JsonVersions } from '../lib/commands/version/index';
|
||||
|
||||
import { run as oclifRun } from '@oclif/core';
|
||||
import * as archiver from 'archiver';
|
||||
@ -25,7 +25,6 @@ import * as filehound from 'filehound';
|
||||
import { Stats } from 'fs';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as klaw from 'klaw';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
import * as semver from 'semver';
|
||||
@ -156,7 +155,7 @@ ${sep}
|
||||
* messages (stdout and stderr) in order to call diffPkgOutput().
|
||||
*/
|
||||
async function execPkg(...args: any[]) {
|
||||
const { exec: pkgExec } = await import('pkg');
|
||||
const { exec: pkgExec } = await import('@yao-pkg/pkg');
|
||||
const outTap = new StdOutTap(true);
|
||||
try {
|
||||
outTap.tap();
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import * as path from 'path';
|
||||
import { MarkdownFileParser } from './utils';
|
||||
import { GlobSync } from 'glob';
|
||||
|
||||
/**
|
||||
* This is the skeleton of CLI documentation/reference web page at:
|
||||
@ -24,173 +25,114 @@ import { MarkdownFileParser } from './utils';
|
||||
*
|
||||
* The `getCapitanoDoc` function in this module parses README.md and adds
|
||||
* some content to this object.
|
||||
*
|
||||
* IMPORTANT
|
||||
*
|
||||
* All commands need to be stored under a folder in lib/commands to maintain uniformity
|
||||
* 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
|
||||
* in the `commandHeadings` dictionary.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Resources with plural names needs to have 2 sections if they have commands like:
|
||||
* "fleet, fleets" or "device, devices" or "tag, tags"
|
||||
*
|
||||
*/
|
||||
const capitanoDoc = {
|
||||
title: 'balena CLI Documentation',
|
||||
introduction: '',
|
||||
categories: [
|
||||
{
|
||||
title: 'API keys',
|
||||
files: ['build/commands/api-key/generate.js'],
|
||||
},
|
||||
{
|
||||
title: 'Fleet',
|
||||
files: [
|
||||
'build/commands/fleets.js',
|
||||
'build/commands/fleet/index.js',
|
||||
'build/commands/fleet/create.js',
|
||||
'build/commands/fleet/purge.js',
|
||||
'build/commands/fleet/rename.js',
|
||||
'build/commands/fleet/restart.js',
|
||||
'build/commands/fleet/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Authentication',
|
||||
files: [
|
||||
'build/commands/login.js',
|
||||
'build/commands/logout.js',
|
||||
'build/commands/whoami.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Device',
|
||||
files: [
|
||||
'build/commands/devices/index.js',
|
||||
'build/commands/devices/supported.js',
|
||||
'build/commands/device/index.js',
|
||||
'build/commands/device/deactivate.js',
|
||||
'build/commands/device/identify.js',
|
||||
'build/commands/device/init.js',
|
||||
'build/commands/device/local-mode.js',
|
||||
'build/commands/device/move.js',
|
||||
'build/commands/device/os-update.js',
|
||||
'build/commands/device/public-url.js',
|
||||
'build/commands/device/purge.js',
|
||||
'build/commands/device/reboot.js',
|
||||
'build/commands/device/register.js',
|
||||
'build/commands/device/rename.js',
|
||||
'build/commands/device/restart.js',
|
||||
'build/commands/device/rm.js',
|
||||
'build/commands/device/shutdown.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Releases',
|
||||
files: [
|
||||
'build/commands/releases.js',
|
||||
'build/commands/release/index.js',
|
||||
'build/commands/release/finalize.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Environment Variables',
|
||||
files: [
|
||||
'build/commands/envs.js',
|
||||
'build/commands/env/add.js',
|
||||
'build/commands/env/rename.js',
|
||||
'build/commands/env/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
files: [
|
||||
'build/commands/tags.js',
|
||||
'build/commands/tag/rm.js',
|
||||
'build/commands/tag/set.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Help and Version',
|
||||
files: ['help', 'build/commands/version.js'],
|
||||
},
|
||||
{
|
||||
title: 'Keys',
|
||||
files: [
|
||||
'build/commands/keys.js',
|
||||
'build/commands/key/index.js',
|
||||
'build/commands/key/add.js',
|
||||
'build/commands/key/rm.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Logs',
|
||||
files: ['build/commands/logs.js'],
|
||||
},
|
||||
{
|
||||
title: 'Network',
|
||||
files: [
|
||||
'build/commands/scan.js',
|
||||
'build/commands/ssh.js',
|
||||
'build/commands/tunnel.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Notes',
|
||||
files: ['build/commands/note.js'],
|
||||
},
|
||||
{
|
||||
title: 'OS',
|
||||
files: [
|
||||
'build/commands/os/build-config.js',
|
||||
'build/commands/os/configure.js',
|
||||
'build/commands/os/versions.js',
|
||||
'build/commands/os/download.js',
|
||||
'build/commands/os/initialize.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Config',
|
||||
files: [
|
||||
'build/commands/config/generate.js',
|
||||
'build/commands/config/inject.js',
|
||||
'build/commands/config/read.js',
|
||||
'build/commands/config/reconfigure.js',
|
||||
'build/commands/config/write.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Preload',
|
||||
files: ['build/commands/preload.js'],
|
||||
},
|
||||
{
|
||||
title: 'Push',
|
||||
files: ['build/commands/push.js'],
|
||||
},
|
||||
{
|
||||
title: 'Settings',
|
||||
files: ['build/commands/settings.js'],
|
||||
},
|
||||
{
|
||||
title: 'Local',
|
||||
files: [
|
||||
'build/commands/local/configure.js',
|
||||
'build/commands/local/flash.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Deploy',
|
||||
files: ['build/commands/build.js', 'build/commands/deploy.js'],
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
files: ['build/commands/join.js', 'build/commands/leave.js'],
|
||||
},
|
||||
{
|
||||
title: 'Utilities',
|
||||
files: ['build/commands/util/available-drives.js'],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
files: ['build/commands/support.js'],
|
||||
},
|
||||
],
|
||||
|
||||
interface Category {
|
||||
title: string;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
interface Documentation {
|
||||
title: string;
|
||||
introduction: string;
|
||||
categories: Category[];
|
||||
}
|
||||
|
||||
// Mapping folders names to custom headings in the docs
|
||||
const commandHeadings: { [key: string]: string } = {
|
||||
'api-key': 'API Key',
|
||||
'api-keys': 'API Keys',
|
||||
login: 'Authentication',
|
||||
whoami: 'Authentication',
|
||||
logout: 'Authentication',
|
||||
env: 'Environment Variable',
|
||||
envs: 'Environment Variables',
|
||||
help: 'Help and Version',
|
||||
key: 'SSH Key',
|
||||
keys: 'SSH Keys',
|
||||
orgs: 'Organizations',
|
||||
os: 'OS',
|
||||
util: 'Utilities',
|
||||
ssh: 'Network',
|
||||
scan: 'Network',
|
||||
tunnel: 'Network',
|
||||
build: 'Deploy',
|
||||
join: 'Platform',
|
||||
leave: 'Platform',
|
||||
};
|
||||
|
||||
// Fetch all available commands
|
||||
const allCommandsPaths = new GlobSync('build/commands/**/*.js', {
|
||||
ignore: 'build/commands/internal/**',
|
||||
}).found;
|
||||
|
||||
// Throw error if any commands found outside of command directories
|
||||
const illegalCommandPaths = allCommandsPaths.filter((commandPath: string) =>
|
||||
/^build\/commands\/[^/]+\.js$/.test(commandPath),
|
||||
);
|
||||
|
||||
if (illegalCommandPaths.length !== 0) {
|
||||
throw new Error(
|
||||
`Found the following commands without a command directory: ${illegalCommandPaths}\n
|
||||
To resolve this error, move the respective commands to their resource directories or create new ones.\n
|
||||
Refer to the automation/capitanodoc/capitanodoc.ts file for more information.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Docs config template
|
||||
const capitanoDoc: Documentation = {
|
||||
title: 'balena CLI Documentation',
|
||||
introduction: '',
|
||||
categories: [],
|
||||
};
|
||||
|
||||
// Helper function to capitalize each word of directory name
|
||||
function formatTitle(dir: string): string {
|
||||
return dir.replace(/(^\w|\s\w)/g, (word) => word.toUpperCase());
|
||||
}
|
||||
|
||||
// Create a map to track the categories for faster lookup
|
||||
const categoriesMap: { [key: string]: Category } = {};
|
||||
|
||||
for (const commandPath of allCommandsPaths) {
|
||||
const commandDir = path.basename(path.dirname(commandPath));
|
||||
const heading = commandHeadings[commandDir] || formatTitle(commandDir);
|
||||
|
||||
if (!categoriesMap[heading]) {
|
||||
categoriesMap[heading] = { title: heading, files: [] };
|
||||
capitanoDoc.categories.push(categoriesMap[heading]);
|
||||
}
|
||||
|
||||
categoriesMap[heading].files.push(commandPath);
|
||||
}
|
||||
|
||||
// Sort Category titles alphabetically
|
||||
capitanoDoc.categories = capitanoDoc.categories.sort((a, b) =>
|
||||
a.title.localeCompare(b.title),
|
||||
);
|
||||
|
||||
// Sort Category file paths alphabetically
|
||||
capitanoDoc.categories.forEach((category) => {
|
||||
category.files.sort((a, b) => a.localeCompare(b));
|
||||
});
|
||||
|
||||
/**
|
||||
* Modify and return the `capitanoDoc` object above in order to render the
|
||||
* CLI documentation/reference web page at:
|
||||
* https://www.balena.io/docs/reference/cli/
|
||||
* Modify and return the `capitanoDoc` object above in order to generate the
|
||||
* CLI documentation at docs/balena-cli.md
|
||||
*
|
||||
* This function parses the README.md file to extract relevant sections
|
||||
* for the documentation web page.
|
||||
|
@ -104,5 +104,5 @@ async function printMarkdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
printMarkdown();
|
||||
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// tslint:disable-next-line:import-blacklist
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
import { promises as fs } from 'fs';
|
||||
@ -82,5 +82,5 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run();
|
||||
|
@ -23,8 +23,8 @@ function parseSemver(version) {
|
||||
* @param {string} v2
|
||||
*/
|
||||
function semverGte(v1, v2) {
|
||||
let v1Array = parseSemver(v1);
|
||||
let v2Array = parseSemver(v2);
|
||||
const v1Array = parseSemver(v1);
|
||||
const v2Array = parseSemver(v2);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (v1Array[i] < v2Array[i]) {
|
||||
return false;
|
||||
|
@ -30,7 +30,7 @@ const { GITHUB_TOKEN } = process.env;
|
||||
export async function createGitHubRelease() {
|
||||
console.log(`Publishing release ${version} to GitHub`);
|
||||
const publishRelease = await import('publish-release');
|
||||
const ghRelease = await Bluebird.fromCallback(
|
||||
const ghRelease = (await Bluebird.fromCallback(
|
||||
publishRelease.bind(null, {
|
||||
token: GITHUB_TOKEN || '',
|
||||
owner: 'balena-io',
|
||||
@ -40,7 +40,7 @@ export async function createGitHubRelease() {
|
||||
reuseRelease: true,
|
||||
assets: finalReleaseAssets[process.platform],
|
||||
}),
|
||||
);
|
||||
)) as { html_url: any };
|
||||
console.log(`Release ${version} successful: ${ghRelease.html_url}`);
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ async function updateGitHubReleaseDescriptions(
|
||||
) {
|
||||
const perPage = 30;
|
||||
const octokit = getOctokit();
|
||||
const options = await octokit.repos.listReleases.endpoint.merge({
|
||||
const options = octokit.repos.listReleases.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
per_page: perPage,
|
||||
|
@ -60,7 +60,7 @@ async function parse(args?: string[]) {
|
||||
release,
|
||||
};
|
||||
for (const arg of args) {
|
||||
if (!commands.hasOwnProperty(arg)) {
|
||||
if (!Object.hasOwn(commands, arg)) {
|
||||
throw new Error(`command unknown: ${arg}`);
|
||||
}
|
||||
}
|
||||
@ -103,5 +103,5 @@ export async function run(args?: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
run();
|
||||
|
@ -136,5 +136,5 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
main();
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
export const ROOT = path.join(__dirname, '..');
|
||||
|
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
@ -17,7 +15,7 @@ async function run() {
|
||||
require('@balena/es-version').set('es2018');
|
||||
|
||||
// Run the CLI
|
||||
await require('../build/app').run();
|
||||
await require('../build/app').run(undefined, { dir: __dirname });
|
||||
}
|
||||
|
||||
run();
|
||||
|
@ -5,8 +5,6 @@
|
||||
// Before opening a PR you should build and test your changes using bin/balena
|
||||
// ****************************************************************************
|
||||
|
||||
// tslint:disable:no-var-requires
|
||||
|
||||
// We boost the threadpool size as ext2fs can deadlock with some
|
||||
// operations otherwise, if the pool runs out.
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
@ -59,7 +57,7 @@ require('ts-node').register({
|
||||
project: path.join(rootDir, 'tsconfig.json'),
|
||||
transpileOnly: true,
|
||||
});
|
||||
require('../lib/app').run();
|
||||
require('../lib/app').run(undefined, { dir: __dirname, development: true });
|
||||
|
||||
// Modify package.json oclif paths from build/ -> lib/, or vice versa
|
||||
function modifyOclifPaths(revert) {
|
||||
|
@ -8,13 +8,13 @@ _balena() {
|
||||
local context state line curcontext="$curcontext"
|
||||
|
||||
# Valid top-level completions
|
||||
main_commands=( build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key api-keys app block config device device devices env fleet fleet internal key key local os release release tag util )
|
||||
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
|
||||
api_key_cmds=( generate revoke )
|
||||
app_cmds=( create )
|
||||
block_cmds=( create )
|
||||
config_cmds=( generate inject read reconfigure write )
|
||||
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown 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 )
|
||||
env_cmds=( add rename rm )
|
||||
fleet_cmds=( create pin purge rename restart rm track-latest )
|
||||
|
@ -7,13 +7,13 @@ _balena_complete()
|
||||
local cur prev
|
||||
|
||||
# Valid top-level completions
|
||||
main_commands="build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key api-keys app block config device device devices env fleet fleet internal key key local os release release tag util"
|
||||
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
|
||||
api_key_cmds="generate revoke"
|
||||
app_cmds="create"
|
||||
block_cmds="create"
|
||||
config_cmds="generate inject read reconfigure write"
|
||||
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown 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"
|
||||
env_cmds="add rename rm"
|
||||
fleet_cmds="create pin purge rename restart rm track-latest"
|
||||
|
@ -31,8 +31,8 @@ if (fs.existsSync(commandsFilePath)) {
|
||||
|
||||
const commandsJson = JSON.parse(fs.readFileSync(commandsFilePath, 'utf8'));
|
||||
|
||||
var mainCommands = [];
|
||||
var additionalCommands = [];
|
||||
const mainCommands = [];
|
||||
const additionalCommands = [];
|
||||
for (const key of Object.keys(commandsJson.commands)) {
|
||||
const cmd = key.split(':');
|
||||
if (cmd.length > 1) {
|
||||
@ -72,8 +72,8 @@ fs.readFile(bashFilePathIn, 'utf8', function (err, data) {
|
||||
/\$main_commands\$/g,
|
||||
'main_commands="' + mainCommandsStr + '"',
|
||||
);
|
||||
var subCommands = [];
|
||||
var prevElement = additionalCommands[0][0];
|
||||
let subCommands = [];
|
||||
let prevElement = additionalCommands[0][0];
|
||||
additionalCommands.forEach(function (element) {
|
||||
if (element[0] === prevElement) {
|
||||
subCommands.push(element[1]);
|
||||
@ -134,8 +134,8 @@ fs.readFile(zshFilePathIn, 'utf8', function (err, data) {
|
||||
/\$main_commands\$/g,
|
||||
'main_commands=( ' + mainCommandsStr + ' )',
|
||||
);
|
||||
var subCommands = [];
|
||||
var prevElement = additionalCommands[0][0];
|
||||
let subCommands = [];
|
||||
let prevElement = additionalCommands[0][0];
|
||||
additionalCommands.forEach(function (element) {
|
||||
if (element[0] === prevElement) {
|
||||
subCommands.push(element[1]);
|
||||
|
3171
docs/balena-cli.md
3171
docs/balena-cli.md
File diff suppressed because it is too large
Load Diff
13
lib/app.ts
13
lib/app.ts
@ -24,7 +24,7 @@ import {
|
||||
} from './preparser';
|
||||
import { CliSettings } from './utils/bootstrap';
|
||||
import { onceAsync } from './utils/lazy';
|
||||
import { run as mainRun } from '@oclif/core';
|
||||
import { run as mainRun, settings } from '@oclif/core';
|
||||
|
||||
/**
|
||||
* Sentry.io setup
|
||||
@ -117,7 +117,14 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
const runPromise = (async function (shouldFlush: boolean) {
|
||||
let isEEXIT = false;
|
||||
try {
|
||||
await mainRun(command, options.configPath);
|
||||
if (options.development) {
|
||||
// In dev mode -> use ts-node and dev plugins
|
||||
process.env.NODE_ENV = 'development';
|
||||
settings.debug = true;
|
||||
}
|
||||
// For posteriority: We can't use default oclif 'execute' as
|
||||
// We customize error handling and flushing
|
||||
await mainRun(command, options.loadOptions ?? options.dir);
|
||||
} catch (error) {
|
||||
// oclif sometimes exits with ExitError code EEXIT 0 (not an error),
|
||||
// for example the `balena help` command.
|
||||
@ -151,7 +158,7 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import(
|
||||
'./utils/bootstrap'
|
||||
|
@ -52,20 +52,16 @@ export default class RevokeCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(RevokeCmd);
|
||||
|
||||
try {
|
||||
const apiKeyIds = params.ids.split(',');
|
||||
if (apiKeyIds.filter((apiKeyId) => !apiKeyId.match(/^\d+$/)).length > 0) {
|
||||
console.log('API key ids must be positive integers');
|
||||
return;
|
||||
}
|
||||
await Promise.all(
|
||||
apiKeyIds.map(
|
||||
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
|
||||
),
|
||||
);
|
||||
console.log('Successfully revoked the given API keys');
|
||||
} catch (e) {
|
||||
throw e;
|
||||
const apiKeyIds = params.ids.split(',');
|
||||
if (apiKeyIds.filter((apiKeyId) => !apiKeyId.match(/^\d+$/)).length > 0) {
|
||||
console.log('API key ids must be positive integers');
|
||||
return;
|
||||
}
|
||||
await Promise.all(
|
||||
apiKeyIds.map(
|
||||
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
|
||||
),
|
||||
);
|
||||
console.log('Successfully revoked the given API keys');
|
||||
}
|
||||
}
|
||||
|
@ -46,42 +46,38 @@ export default class ApiKeysCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = await this.parse(ApiKeysCmd);
|
||||
|
||||
try {
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const actorId = options.fleet
|
||||
? (
|
||||
await getApplication(getBalenaSdk(), options.fleet, {
|
||||
$select: 'actor',
|
||||
})
|
||||
).actor
|
||||
: await getBalenaSdk().auth.getActorId();
|
||||
const keys = await getBalenaSdk().pine.get({
|
||||
resource: 'api_key',
|
||||
options: {
|
||||
$select: ['id', 'created_at', 'name', 'description', 'expiry_date'],
|
||||
$filter: {
|
||||
is_of__actor: actorId,
|
||||
...(options.user
|
||||
? {
|
||||
name: {
|
||||
$ne: null,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
$orderby: 'name asc',
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
const actorId = options.fleet
|
||||
? (
|
||||
await getApplication(getBalenaSdk(), options.fleet, {
|
||||
$select: 'actor',
|
||||
})
|
||||
).actor
|
||||
: await getBalenaSdk().auth.getActorId();
|
||||
const keys = await getBalenaSdk().pine.get({
|
||||
resource: 'api_key',
|
||||
options: {
|
||||
$select: ['id', 'created_at', 'name', 'description', 'expiry_date'],
|
||||
$filter: {
|
||||
is_of__actor: actorId,
|
||||
...(options.user
|
||||
? {
|
||||
name: {
|
||||
$ne: null,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
const fields = ['id', 'name', 'created_at', 'description', 'expiry_date'];
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
keys.map((key) => _.mapValues(key, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
$orderby: 'name asc',
|
||||
},
|
||||
});
|
||||
const fields = ['id', 'name', 'created_at', 'description', 'expiry_date'];
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
keys.map((key) => _.mapValues(key, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,20 +16,20 @@
|
||||
*/
|
||||
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import * as compose from '../utils/compose';
|
||||
import Command from '../../command';
|
||||
import { getBalenaSdk } from '../../utils/lazy';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import * as compose from '../../utils/compose';
|
||||
import type { ApplicationType, BalenaSDK } from 'balena-sdk';
|
||||
import {
|
||||
buildArgDeprecation,
|
||||
dockerignoreHelp,
|
||||
registrySecretsHelp,
|
||||
} from '../utils/messages';
|
||||
import type { ComposeCliFlags, ComposeOpts } from '../utils/compose-types';
|
||||
import { buildProject, composeCliFlags } from '../utils/compose_ts';
|
||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||
import { dockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/messages';
|
||||
import type { ComposeCliFlags, ComposeOpts } from '../../utils/compose-types';
|
||||
import { buildProject, composeCliFlags } from '../../utils/compose_ts';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
|
||||
import { dockerCliFlags } from '../../utils/docker';
|
||||
|
||||
// TODO: For this special one we can't use Interfaces.InferredFlags/InferredArgs
|
||||
// because of the 'registry-secrets' type which is defined in the actual code
|
||||
@ -67,6 +67,7 @@ ${dockerignoreHelp}
|
||||
public static examples = [
|
||||
'$ balena build --fleet myFleet',
|
||||
'$ balena build ./source/ --fleet myorg/myfleet',
|
||||
'$ balena build ./source/ --fleet myorg/myfleet --docker-compose my-custom-compose.yml',
|
||||
'$ balena build --deviceType raspberrypi3 --arch armv7hf --emulated',
|
||||
'$ balena build --docker /var/run/docker.sock --fleet myFleet # Linux, Mac',
|
||||
'$ balena build --docker //./pipe/docker_engine --fleet myFleet # Windows',
|
||||
@ -148,14 +149,14 @@ ${dockerignoreHelp}
|
||||
(opts.fleet == null && (opts.arch == null || opts.deviceType == null)) ||
|
||||
(opts.fleet != null && (opts.arch != null || opts.deviceType != null))
|
||||
) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
'You must specify either a fleet (-f), or the device type (-d) and architecture (-A)',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate project directory
|
||||
const { validateProjectDirectory } = await import('../utils/compose_ts');
|
||||
const { validateProjectDirectory } = await import('../../utils/compose_ts');
|
||||
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
||||
sdk,
|
||||
{
|
||||
@ -172,7 +173,7 @@ ${dockerignoreHelp}
|
||||
|
||||
protected async getAppAndResolveArch(opts: FlagsDef) {
|
||||
if (opts.fleet) {
|
||||
const { getAppWithArch } = await import('../utils/helpers');
|
||||
const { getAppWithArch } = await import('../../utils/helpers');
|
||||
const app = await getAppWithArch(opts.fleet);
|
||||
opts.arch = app.arch;
|
||||
opts.deviceType = app.is_for__device_type[0].slug;
|
||||
@ -181,7 +182,7 @@ ${dockerignoreHelp}
|
||||
}
|
||||
|
||||
protected async prepareBuild(options: FlagsDef) {
|
||||
const { getDocker, generateBuildOpts } = await import('../utils/docker');
|
||||
const { getDocker, generateBuildOpts } = await import('../../utils/docker');
|
||||
const [docker, buildOpts, composeOpts] = await Promise.all([
|
||||
getDocker(options),
|
||||
generateBuildOpts(options),
|
||||
@ -209,7 +210,7 @@ ${dockerignoreHelp}
|
||||
*/
|
||||
protected async buildProject(
|
||||
docker: import('dockerode'),
|
||||
logger: import('../utils/logger'),
|
||||
logger: import('../../utils/logger'),
|
||||
composeOpts: ComposeOpts,
|
||||
opts: {
|
||||
app?: {
|
||||
@ -221,7 +222,7 @@ ${dockerignoreHelp}
|
||||
buildOpts: BuildOpts;
|
||||
},
|
||||
) {
|
||||
const { loadProject } = await import('../utils/compose_ts');
|
||||
const { loadProject } = await import('../../utils/compose_ts');
|
||||
|
||||
const project = await loadProject(
|
||||
logger,
|
@ -259,6 +259,8 @@ export default class ConfigGenerateCmd extends Command {
|
||||
if (!options.fleet && options.deviceType) {
|
||||
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
|
||||
}
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization');
|
||||
options.version = normalizeOsVersion(options.version);
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||
await validateDevOptionAndWarn(options.dev, options.version);
|
||||
}
|
||||
|
@ -18,31 +18,31 @@
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import type { ImageDescriptor } from '@balena/compose/dist/parse';
|
||||
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import { getBalenaSdk, getChalk, stripIndent } from '../../utils/lazy';
|
||||
import {
|
||||
dockerignoreHelp,
|
||||
registrySecretsHelp,
|
||||
buildArgDeprecation,
|
||||
} from '../utils/messages';
|
||||
import * as ca from '../utils/common-args';
|
||||
import * as compose from '../utils/compose';
|
||||
} from '../../utils/messages';
|
||||
import * as ca from '../../utils/common-args';
|
||||
import * as compose from '../../utils/compose';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeCliFlags,
|
||||
ComposeOpts,
|
||||
Release as ComposeReleaseInfo,
|
||||
} from '../utils/compose-types';
|
||||
import type { BuildOpts, DockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/compose-types';
|
||||
import type { BuildOpts, DockerCliFlags } from '../../utils/docker';
|
||||
import {
|
||||
applyReleaseTagKeysAndValues,
|
||||
buildProject,
|
||||
composeCliFlags,
|
||||
isBuildConfig,
|
||||
parseReleaseTagKeysAndValues,
|
||||
} from '../utils/compose_ts';
|
||||
import { dockerCliFlags } from '../utils/docker';
|
||||
} from '../../utils/compose_ts';
|
||||
import { dockerCliFlags } from '../../utils/docker';
|
||||
import type { ApplicationType, DeviceType, Release } from 'balena-sdk';
|
||||
|
||||
interface ApplicationWithArch {
|
||||
@ -98,6 +98,7 @@ ${dockerignoreHelp}
|
||||
public static examples = [
|
||||
'$ balena deploy myFleet',
|
||||
'$ balena deploy myorg/myfleet --build --source myBuildDir/',
|
||||
'$ balena deploy myorg/myfleet --builld --source myBuildDir/ --docker-compose my-custom-compose.yml',
|
||||
'$ balena deploy myorg/myfleet --build --source myBuildDir/ --note "this is the note for this release"',
|
||||
'$ balena deploy myorg/myfleet myRepo/myImage',
|
||||
'$ balena deploy myFleet myRepo/myImage --release-tag key1 "" key2 "value2 with spaces"',
|
||||
@ -175,7 +176,7 @@ ${dockerignoreHelp}
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
const { getRegistrySecrets, validateProjectDirectory } = await import(
|
||||
'../utils/compose_ts'
|
||||
'../../utils/compose_ts'
|
||||
);
|
||||
|
||||
const { releaseTagKeys, releaseTagValues } = parseReleaseTagKeysAndValues(
|
||||
@ -199,10 +200,10 @@ ${dockerignoreHelp}
|
||||
(options as FlagsDef)['registry-secrets'] = registrySecrets;
|
||||
}
|
||||
|
||||
const helpers = await import('../utils/helpers');
|
||||
const helpers = await import('../../utils/helpers');
|
||||
const app = await helpers.getAppWithArch(fleet);
|
||||
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const dockerUtils = await import('../../utils/docker');
|
||||
const [docker, buildOpts, composeOpts] = await Promise.all([
|
||||
dockerUtils.getDocker(options),
|
||||
dockerUtils.generateBuildOpts(options as FlagsDef),
|
||||
@ -232,7 +233,7 @@ ${dockerignoreHelp}
|
||||
|
||||
async deployProject(
|
||||
docker: import('dockerode'),
|
||||
logger: import('../utils/logger'),
|
||||
logger: import('../../utils/logger'),
|
||||
composeOpts: ComposeOpts,
|
||||
opts: {
|
||||
app: ApplicationWithArch; // the application instance to deploy to
|
||||
@ -250,7 +251,7 @@ ${dockerignoreHelp}
|
||||
const doodles = await import('resin-doodles');
|
||||
const sdk = getBalenaSdk();
|
||||
const { deployProject: $deployProject, loadProject } = await import(
|
||||
'../utils/compose_ts'
|
||||
'../../utils/compose_ts'
|
||||
);
|
||||
|
||||
const appType = opts.app.application_type[0];
|
||||
@ -332,7 +333,7 @@ ${dockerignoreHelp}
|
||||
|
||||
let release: Release | ComposeReleaseInfo['release'];
|
||||
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
|
||||
const { deployLegacy } = require('../utils/deploy-legacy');
|
||||
const { deployLegacy } = require('../../utils/deploy-legacy');
|
||||
|
||||
const msg = getChalk().yellow(
|
||||
'Target fleet requires legacy deploy method.',
|
@ -51,9 +51,8 @@ export default class DeviceDeactivateCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DeviceDeactivateCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceDeactivateCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const patterns = await import('../../utils/patterns');
|
||||
|
@ -20,6 +20,7 @@ import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { jsonInfo } from '../../utils/messages';
|
||||
|
||||
import type { Application, Release } from 'balena-sdk';
|
||||
|
||||
@ -45,10 +46,13 @@ export default class DeviceCmd extends Command {
|
||||
Show info about a single device.
|
||||
|
||||
Show information about a single device.
|
||||
|
||||
${jsonInfo.split('\n').join('\n\t\t')}
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device 7cf02a6',
|
||||
'$ balena device 7cf02a6 --view',
|
||||
'$ balena device 7cf02a6 --json',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
@ -61,6 +65,7 @@ export default class DeviceCmd extends Command {
|
||||
public static usage = 'device <uuid>';
|
||||
|
||||
public static flags = {
|
||||
json: cf.json,
|
||||
help: cf.help,
|
||||
view: Flags.boolean({
|
||||
default: false,
|
||||
@ -76,33 +81,45 @@ export default class DeviceCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const device = (await balena.models.device.get(params.uuid, {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
})) as ExtendedDevice;
|
||||
const device = (await balena.models.device.get(
|
||||
params.uuid,
|
||||
options.json
|
||||
? {
|
||||
$expand: {
|
||||
device_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
},
|
||||
...expandForAppName.$expand,
|
||||
},
|
||||
}
|
||||
: {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
},
|
||||
)) as ExtendedDevice;
|
||||
|
||||
if (options.view) {
|
||||
const open = await import('open');
|
||||
@ -166,6 +183,11 @@ export default class DeviceCmd extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(device, null, 4));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
getVisuals().table.vertical(device, [
|
||||
`$${device.device_name}$`,
|
||||
|
@ -63,9 +63,8 @@ export default class DeviceLocalModeCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DeviceLocalModeCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceLocalModeCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
|
@ -37,6 +37,7 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
'$ balena device os-update 23c73a1',
|
||||
'$ 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 --include-draft',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
@ -51,6 +52,12 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
public static flags = {
|
||||
version: Flags.string({
|
||||
description: 'a balenaOS version',
|
||||
exclusive: ['include-draft'],
|
||||
}),
|
||||
'include-draft': Flags.boolean({
|
||||
description: 'include pre-release balenaOS versions',
|
||||
default: false,
|
||||
exclusive: ['version'],
|
||||
}),
|
||||
yes: cf.yes,
|
||||
help: cf.help,
|
||||
@ -59,9 +66,8 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DeviceOsUpdateCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceOsUpdateCmd);
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
@ -87,10 +93,25 @@ 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
|
||||
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
|
||||
is_of__device_type[0].slug,
|
||||
currentOsVersion,
|
||||
{
|
||||
includeDraft,
|
||||
},
|
||||
);
|
||||
if (hupVersionInfo.versions.length === 0) {
|
||||
throw new ExpectedError(
|
||||
@ -101,6 +122,8 @@ export default class DeviceOsUpdateCmd extends Command {
|
||||
// Get target OS version
|
||||
let targetOsVersion = options.version;
|
||||
if (targetOsVersion != null) {
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization');
|
||||
targetOsVersion = normalizeOsVersion(targetOsVersion);
|
||||
if (!hupVersionInfo.versions.includes(targetOsVersion)) {
|
||||
throw new ExpectedError(
|
||||
`The provided version ${targetOsVersion} is not in the Host OS update targets for this device`,
|
||||
|
@ -65,9 +65,8 @@ export default class DevicePublicUrlCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DevicePublicUrlCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DevicePublicUrlCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
|
@ -61,9 +61,8 @@ export default class DeviceRegisterCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DeviceRegisterCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceRegisterCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
|
@ -46,9 +46,8 @@ export default class DeviceShutdownCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(
|
||||
DeviceShutdownCmd,
|
||||
);
|
||||
const { args: params, flags: options } =
|
||||
await this.parse(DeviceShutdownCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
|
139
lib/commands/device/start-service.ts
Normal file
139
lib/commands/device/start-service.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-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 { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
|
||||
export default class DeviceStartServiceCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Start containers on a device.
|
||||
|
||||
Start containers on a device.
|
||||
|
||||
Multiple devices and services may be specified with a comma-separated list
|
||||
of values (no spaces).
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device start-service 23c73a1 myService',
|
||||
'$ balena device start-service 23c73a1 myService1,myService2',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of device UUIDs',
|
||||
required: true,
|
||||
}),
|
||||
service: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of service names',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device start-service <uuid>';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(DeviceStartServiceCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
||||
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
const serviceNames = params.service.split(',');
|
||||
|
||||
// Iterate sequentially through deviceUuids.
|
||||
// We may later want to add a batching feature,
|
||||
// so that n devices are processed in parallel
|
||||
for (const uuid of deviceUuids) {
|
||||
ux.action.start(`Starting services on device ${uuid}`);
|
||||
await this.startServices(balena, uuid, serviceNames);
|
||||
ux.action.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async startServices(
|
||||
balena: BalenaSDK,
|
||||
deviceUuid: string,
|
||||
serviceNames: string[],
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
|
||||
// Get device
|
||||
const device = await balena.models.device.getWithServiceDetails(
|
||||
deviceUuid,
|
||||
{
|
||||
$expand: {
|
||||
is_running__release: { $select: 'commit' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const activeReleaseCommit = getExpandedProp(
|
||||
device.is_running__release,
|
||||
'commit',
|
||||
);
|
||||
|
||||
// Check specified services exist on this device before startinganything
|
||||
serviceNames.forEach((service) => {
|
||||
if (!device.current_services[service]) {
|
||||
throw new ExpectedError(
|
||||
`Service ${service} not found on device ${deviceUuid}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Start services
|
||||
const startPromises: Array<Promise<void>> = [];
|
||||
for (const serviceName of serviceNames) {
|
||||
const service = device.current_services[serviceName];
|
||||
// Each service is an array of `CurrentServiceWithCommit`
|
||||
// because when service is updating, it will actually hold 2 services
|
||||
// Target commit matching `device.is_running__release`
|
||||
const serviceContainer = service.find((s) => {
|
||||
return s.commit === activeReleaseCommit;
|
||||
});
|
||||
|
||||
if (serviceContainer) {
|
||||
startPromises.push(
|
||||
balena.models.device.startService(
|
||||
deviceUuid,
|
||||
serviceContainer.image_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(startPromises);
|
||||
} catch (e) {
|
||||
if (e.message.toLowerCase().includes('no online device')) {
|
||||
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
139
lib/commands/device/stop-service.ts
Normal file
139
lib/commands/device/stop-service.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-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 { Args } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
|
||||
export default class DeviceStopServiceCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Stop containers on a device.
|
||||
|
||||
Stop containers on a device.
|
||||
|
||||
Multiple devices and services may be specified with a comma-separated list
|
||||
of values (no spaces).
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device stop-service 23c73a1 myService',
|
||||
'$ balena device stop-service 23c73a1 myService1,myService2',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
uuid: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of device UUIDs',
|
||||
required: true,
|
||||
}),
|
||||
service: Args.string({
|
||||
description: 'comma-separated list (no blank spaces) of service names',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
public static usage = 'device stop-service <uuid>';
|
||||
|
||||
public static flags = {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(DeviceStopServiceCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const ux = getCliUx();
|
||||
|
||||
const deviceUuids = params.uuid.split(',');
|
||||
const serviceNames = params.service.split(',');
|
||||
|
||||
// Iterate sequentially through deviceUuids.
|
||||
// We may later want to add a batching feature,
|
||||
// so that n devices are processed in parallel
|
||||
for (const uuid of deviceUuids) {
|
||||
ux.action.start(`Stopping services on device ${uuid}`);
|
||||
await this.stopServices(balena, uuid, serviceNames);
|
||||
ux.action.stop();
|
||||
}
|
||||
}
|
||||
|
||||
async stopServices(
|
||||
balena: BalenaSDK,
|
||||
deviceUuid: string,
|
||||
serviceNames: string[],
|
||||
) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
const { getExpandedProp } = await import('../../utils/pine');
|
||||
|
||||
// Get device
|
||||
const device = await balena.models.device.getWithServiceDetails(
|
||||
deviceUuid,
|
||||
{
|
||||
$expand: {
|
||||
is_running__release: { $select: 'commit' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const activeReleaseCommit = getExpandedProp(
|
||||
device.is_running__release,
|
||||
'commit',
|
||||
);
|
||||
|
||||
// Check specified services exist on this device before stoppinganything
|
||||
serviceNames.forEach((service) => {
|
||||
if (!device.current_services[service]) {
|
||||
throw new ExpectedError(
|
||||
`Service ${service} not found on device ${deviceUuid}.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Stop services
|
||||
const stopPromises: Array<Promise<void>> = [];
|
||||
for (const serviceName of serviceNames) {
|
||||
const service = device.current_services[serviceName];
|
||||
// Each service is an array of `CurrentServiceWithCommit`
|
||||
// because when service is updating, it will actually hold 2 services
|
||||
// Target commit matching `device.is_running__release`
|
||||
const serviceContainer = service.find((s) => {
|
||||
return s.commit === activeReleaseCommit;
|
||||
});
|
||||
|
||||
if (serviceContainer) {
|
||||
stopPromises.push(
|
||||
balena.models.device.stopService(
|
||||
deviceUuid,
|
||||
serviceContainer.image_id,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(stopPromises);
|
||||
} catch (e) {
|
||||
if (e.message.toLowerCase().includes('no online device')) {
|
||||
throw new ExpectedError(`Device ${deviceUuid} is not online.`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
lib/commands/env/add.ts
vendored
2
lib/commands/env/add.ts
vendored
@ -90,6 +90,8 @@ export default class EnvAddCmd extends Command {
|
||||
}),
|
||||
};
|
||||
|
||||
// Required for supporting empty string ('') `value` args.
|
||||
public static strict = false;
|
||||
public static usage = 'env add <name> [value]';
|
||||
|
||||
public static flags = {
|
||||
|
@ -18,11 +18,11 @@ import { Flags } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import type * as SDK from 'balena-sdk';
|
||||
import * as _ from 'lodash';
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
type FlagsDef = Interfaces.InferredFlags<typeof EnvsCmd.flags>;
|
||||
|
||||
@ -124,12 +124,16 @@ export default class EnvsCmd extends Command {
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
let fleetSlug: string | undefined = options.fleet
|
||||
? await (await import('../utils/sdk')).getFleetSlug(balena, options.fleet)
|
||||
? await (
|
||||
await import('../../utils/sdk')
|
||||
).getFleetSlug(balena, options.fleet)
|
||||
: undefined;
|
||||
let fullUUID: string | undefined; // as oppposed to the short, 7-char UUID
|
||||
|
||||
if (options.device) {
|
||||
const { getDeviceAndMaybeAppFromUUID } = await import('../utils/cloud');
|
||||
const { getDeviceAndMaybeAppFromUUID } = await import(
|
||||
'../../utils/cloud'
|
||||
);
|
||||
const [device, app] = await getDeviceAndMaybeAppFromUUID(
|
||||
balena,
|
||||
options.device,
|
||||
@ -182,7 +186,7 @@ export default class EnvsCmd extends Command {
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../utils/helpers');
|
||||
const { pickAndRename } = await import('../../utils/helpers');
|
||||
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
||||
this.log(JSON.stringify(mapped, null, 4));
|
||||
} else {
|
||||
@ -228,9 +232,10 @@ async function getAppVars(
|
||||
if (!fleetSlug) {
|
||||
return appVars;
|
||||
}
|
||||
const vars = await sdk.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(fleetSlug);
|
||||
const vars =
|
||||
await sdk.models.application[
|
||||
options.config ? 'configVar' : 'envVar'
|
||||
].getAllByApplication(fleetSlug);
|
||||
fillInInfoFields(vars, fleetSlug);
|
||||
appVars.push(...vars);
|
||||
if (!options.config) {
|
||||
@ -269,9 +274,8 @@ async function getDeviceVars(
|
||||
const printedUUID = options.json ? fullUUID : options.device!;
|
||||
const deviceVars: EnvironmentVariableInfo[] = [];
|
||||
if (options.config) {
|
||||
const deviceConfigVars = await sdk.models.device.configVar.getAllByDevice(
|
||||
fullUUID,
|
||||
);
|
||||
const deviceConfigVars =
|
||||
await sdk.models.device.configVar.getAllByDevice(fullUUID);
|
||||
fillInInfoFields(deviceConfigVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceConfigVars);
|
||||
} else {
|
||||
@ -296,9 +300,8 @@ async function getDeviceVars(
|
||||
fillInInfoFields(deviceServiceVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceServiceVars);
|
||||
|
||||
const deviceEnvVars = await sdk.models.device.envVar.getAllByDevice(
|
||||
fullUUID,
|
||||
);
|
||||
const deviceEnvVars =
|
||||
await sdk.models.device.envVar.getAllByDevice(fullUUID);
|
||||
fillInInfoFields(deviceEnvVars, fleetSlug, printedUUID);
|
||||
deviceVars.push(...deviceEnvVars);
|
||||
}
|
@ -17,9 +17,9 @@
|
||||
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface ExtendedApplication extends ApplicationWithDeviceTypeSlug {
|
||||
device_count: number;
|
@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
import { Args, Flags } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import { parseAsLocalHostnameOrIp } from '../utils/validation';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
import { parseAsLocalHostnameOrIp } from '../../utils/validation';
|
||||
|
||||
export default class JoinCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -78,7 +78,7 @@ export default class JoinCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(JoinCmd);
|
||||
|
||||
const promote = await import('../utils/promote');
|
||||
const promote = await import('../../utils/promote');
|
||||
const sdk = getBalenaSdk();
|
||||
const logger = await Command.getLogger();
|
||||
return promote.join(
|
@ -15,9 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class KeysCmd extends Command {
|
||||
public static description = stripIndent`
|
@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { stripIndent } from '../utils/lazy';
|
||||
import { parseAsLocalHostnameOrIp } from '../utils/validation';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
import { parseAsLocalHostnameOrIp } from '../../utils/validation';
|
||||
|
||||
export default class LeaveCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -62,7 +62,7 @@ export default class LeaveCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(LeaveCmd);
|
||||
|
||||
const promote = await import('../utils/promote');
|
||||
const promote = await import('../../utils/promote');
|
||||
const logger = await Command.getLogger();
|
||||
return promote.leave(logger, params.deviceIpOrHostname);
|
||||
}
|
@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../utils/lazy';
|
||||
import { ExpectedError } from '../errors';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import type { WhoamiResult } from 'balena-sdk';
|
||||
|
||||
interface FlagsDef {
|
||||
@ -123,7 +123,7 @@ export default class LoginCmd extends Command {
|
||||
const { flags: options, args: params } = await this.parse(LoginCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const messages = await import('../utils/messages');
|
||||
const messages = await import('../../utils/messages');
|
||||
const balenaUrl = await balena.settings.get('balenaUrl');
|
||||
|
||||
// Consolidate user/email options
|
||||
@ -202,16 +202,16 @@ ${messages.reachingOut}`);
|
||||
}
|
||||
// Credentials
|
||||
else if (loginOptions.credentials) {
|
||||
const patterns = await import('../utils/patterns');
|
||||
const patterns = await import('../../utils/patterns');
|
||||
return patterns.authenticate(loginOptions);
|
||||
}
|
||||
// Web
|
||||
else if (loginOptions.web) {
|
||||
const auth = await import('../auth');
|
||||
const auth = await import('../../auth');
|
||||
await auth.login({ port: loginOptions.port });
|
||||
return;
|
||||
} else {
|
||||
const patterns = await import('../utils/patterns');
|
||||
const patterns = await import('../../utils/patterns');
|
||||
// User had not selected login preference, prompt interactively
|
||||
const loginType = await patterns.askLoginType();
|
||||
if (loginType === 'register') {
|
@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class LogoutCmd extends Command {
|
||||
public static description = stripIndent`
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { LogMessage } from 'balena-sdk';
|
||||
|
||||
const MAX_RETRY = 1000;
|
||||
@ -96,14 +96,14 @@ export default class LogsCmd extends Command {
|
||||
const { args: params, flags: options } = await this.parse(LogsCmd);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const { serviceIdToName } = await import('../utils/cloud');
|
||||
const { serviceIdToName } = await import('../../utils/cloud');
|
||||
const { connectAndDisplayDeviceLogs, displayLogObject } = await import(
|
||||
'../utils/device/logs'
|
||||
'../../utils/device/logs'
|
||||
);
|
||||
const { validateIPAddress, validateDotLocalUrl } = await import(
|
||||
'../utils/validation'
|
||||
'../../utils/validation'
|
||||
);
|
||||
const Logger = await import('../utils/logger');
|
||||
const Logger = await import('../../utils/logger');
|
||||
|
||||
const logger = Logger.getLogger();
|
||||
|
||||
@ -132,13 +132,13 @@ export default class LogsCmd extends Command {
|
||||
validateDotLocalUrl(params.device)
|
||||
) {
|
||||
// Logs from local device
|
||||
const { DeviceAPI } = await import('../utils/device/api');
|
||||
const { DeviceAPI } = await import('../../utils/device/api');
|
||||
const deviceApi = new DeviceAPI(logger, params.device);
|
||||
logger.logDebug('Checking we can access device');
|
||||
try {
|
||||
await deviceApi.ping();
|
||||
} catch (e) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
`Cannot access device at address ${params.device}. Device may not be in local mode.`,
|
||||
);
|
@ -16,10 +16,10 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class NoteCmd extends Command {
|
||||
public static description = stripIndent`
|
@ -15,9 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class OrgsCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -38,7 +38,7 @@ export default class OrgsCmd extends Command {
|
||||
public async run() {
|
||||
await this.parse(OrgsCmd);
|
||||
|
||||
const { getOwnOrganizations } = await import('../utils/sdk');
|
||||
const { getOwnOrganizations } = await import('../../utils/sdk');
|
||||
|
||||
// Get organizations
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk(), {
|
@ -216,9 +216,15 @@ export default class OsConfigureCmd extends Command {
|
||||
configJson = JSON.parse(rawConfig);
|
||||
}
|
||||
|
||||
const osVersion =
|
||||
const { normalizeOsVersion } = await import('../../utils/normalization');
|
||||
const osVersion = normalizeOsVersion(
|
||||
options.version ||
|
||||
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
|
||||
(await getOsVersionFromImage(
|
||||
params.image,
|
||||
deviceTypeManifest,
|
||||
devInit,
|
||||
)),
|
||||
);
|
||||
|
||||
const { validateDevOptionAndWarn } = await import('../../utils/config');
|
||||
await validateDevOptionAndWarn(options.dev, osVersion);
|
||||
|
@ -48,15 +48,33 @@ export default class OsVersionsCmd extends Command {
|
||||
description: 'select balenaOS ESR versions',
|
||||
default: false,
|
||||
}),
|
||||
'include-draft': Flags.boolean({
|
||||
description: 'include pre-release balenaOS versions',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = await this.parse(OsVersionsCmd);
|
||||
|
||||
if (options['include-draft']) {
|
||||
const { warnify } = await import('../../utils/messages');
|
||||
console.error(
|
||||
warnify(stripIndent`
|
||||
Using pre-release balenaOS versions is only supported for OS updates
|
||||
and not for OS image downloads.
|
||||
`),
|
||||
);
|
||||
}
|
||||
|
||||
const { formatOsVersion, getOsVersions } = await import(
|
||||
'../../utils/cloud'
|
||||
);
|
||||
const vs = await getOsVersions(params.type, !!options.esr);
|
||||
const vs = await getOsVersions(
|
||||
params.type,
|
||||
!!options.esr,
|
||||
options['include-draft'],
|
||||
);
|
||||
|
||||
console.log(vs.map((v) => formatOsVersion(v)).join('\n'));
|
||||
}
|
||||
|
@ -15,18 +15,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import {
|
||||
getBalenaSdk,
|
||||
getCliForm,
|
||||
getVisuals,
|
||||
stripIndent,
|
||||
} from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import { dockerConnectionCliFlags } from '../utils/docker';
|
||||
import { parseAsInteger } from '../utils/validation';
|
||||
} from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
import { dockerConnectionCliFlags } from '../../utils/docker';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import * as _ from 'lodash';
|
||||
@ -148,7 +148,7 @@ Can be repeated to add multiple certificates.\
|
||||
const balenaPreload = await import('balena-preload');
|
||||
const visuals = getVisuals();
|
||||
const nodeCleanup = await import('node-cleanup');
|
||||
const { instanceOf } = await import('../errors');
|
||||
const { instanceOf } = await import('../../errors');
|
||||
|
||||
// Check image file exists
|
||||
try {
|
||||
@ -171,7 +171,9 @@ Can be repeated to add multiple certificates.\
|
||||
// balena-preload currently does not work with numerical app IDs
|
||||
// Load app here, and use app slug from hereon
|
||||
const fleetSlug: string | undefined = options.fleet
|
||||
? await (await import('../utils/sdk')).getFleetSlug(balena, options.fleet)
|
||||
? await (
|
||||
await import('../../utils/sdk')
|
||||
).getFleetSlug(balena, options.fleet)
|
||||
: undefined;
|
||||
|
||||
const progressBars: {
|
||||
@ -227,7 +229,7 @@ Can be repeated to add multiple certificates.\
|
||||
}
|
||||
|
||||
// Get a configured dockerode instance
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const dockerUtils = await import('../../utils/docker');
|
||||
const docker = await dockerUtils.getDocker(options);
|
||||
const preloader = new balenaPreload.Preloader(
|
||||
undefined,
|
||||
@ -390,9 +392,8 @@ Can be repeated to add multiple certificates.\
|
||||
);
|
||||
applicationInfoSpinner.start();
|
||||
|
||||
const applications = await this.getApplicationsWithSuccessfulBuilds(
|
||||
deviceTypeSlug,
|
||||
);
|
||||
const applications =
|
||||
await this.getApplicationsWithSuccessfulBuilds(deviceTypeSlug);
|
||||
applicationInfoSpinner.stop();
|
||||
if (applications.length === 0) {
|
||||
throw new ExpectedError(
|
||||
@ -480,7 +481,7 @@ Would you like to disable automatic updates for this fleet now?\
|
||||
}
|
||||
|
||||
async getAppWithReleases(balenaSdk: BalenaSDK, slug: string) {
|
||||
const { getApplication } = await import('../utils/sdk');
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
return await getApplication(balenaSdk, slug, {
|
||||
$expand: this.applicationExpandOptions,
|
@ -17,18 +17,18 @@
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { dockerignoreHelp, registrySecretsHelp } from '../../utils/messages';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
import { ExpectedError, instanceOf } from '../errors';
|
||||
import { ExpectedError, instanceOf } from '../../errors';
|
||||
import { RegistrySecrets } from '@balena/compose/dist/multibuild';
|
||||
import { lowercaseIfSlug } from '../utils/normalization';
|
||||
import { lowercaseIfSlug } from '../../utils/normalization';
|
||||
import {
|
||||
applyReleaseTagKeysAndValues,
|
||||
parseReleaseTagKeysAndValues,
|
||||
} from '../utils/compose_ts';
|
||||
} from '../../utils/compose_ts';
|
||||
|
||||
enum BuildTarget {
|
||||
Cloud,
|
||||
@ -78,6 +78,7 @@ export default class PushCmd extends Command {
|
||||
'$ balena push myFleet -s <source directory>',
|
||||
'$ balena push myFleet --source <source directory> --note "this is the note for this release"',
|
||||
'$ balena push myFleet --release-tag key1 "" key2 "value2 with spaces"',
|
||||
'$ balena push myorg/myfleet --docker-compose my-custom-compose.yml',
|
||||
'$ balena push myorg/myfleet',
|
||||
'',
|
||||
'$ balena push 10.0.0.1',
|
||||
@ -121,6 +122,10 @@ export default class PushCmd extends Command {
|
||||
description:
|
||||
'Alternative Dockerfile name/path, relative to the source folder',
|
||||
}),
|
||||
'docker-compose': Flags.string({
|
||||
description:
|
||||
'Alternative compose yml file, relative to the source folder',
|
||||
}),
|
||||
nocache: Flags.boolean({
|
||||
description: stripIndent`
|
||||
Don't use cached layers of previously built images for this project. This
|
||||
@ -233,7 +238,7 @@ export default class PushCmd extends Command {
|
||||
logger.logDebug(`Using build source directory: ${options.source} `);
|
||||
|
||||
const sdk = getBalenaSdk();
|
||||
const { validateProjectDirectory } = await import('../utils/compose_ts');
|
||||
const { validateProjectDirectory } = await import('../../utils/compose_ts');
|
||||
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
||||
sdk,
|
||||
{
|
||||
@ -276,8 +281,8 @@ export default class PushCmd extends Command {
|
||||
dockerfilePath: string,
|
||||
registrySecrets: RegistrySecrets,
|
||||
) {
|
||||
const remote = await import('../utils/remote-build');
|
||||
const { getApplication } = await import('../utils/sdk');
|
||||
const remote = await import('../../utils/remote-build');
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
|
||||
// Check for invalid options
|
||||
const localOnlyOptions: Array<keyof FlagsDef> = [
|
||||
@ -356,7 +361,7 @@ export default class PushCmd extends Command {
|
||||
'is only valid when pushing to a fleet',
|
||||
);
|
||||
|
||||
const deviceDeploy = await import('../utils/device/deploy');
|
||||
const deviceDeploy = await import('../../utils/device/deploy');
|
||||
|
||||
try {
|
||||
await deviceDeploy.deployToDevice({
|
||||
@ -365,6 +370,7 @@ export default class PushCmd extends Command {
|
||||
dockerfilePath,
|
||||
registrySecrets,
|
||||
multiDockerignore: options['multi-dockerignore'],
|
||||
composefileName: options['docker-compose'],
|
||||
nocache: options.nocache,
|
||||
pull: options.pull,
|
||||
noParentCheck: options['noparent-check'],
|
||||
@ -376,7 +382,7 @@ export default class PushCmd extends Command {
|
||||
convertEol: !options['noconvert-eol'],
|
||||
});
|
||||
} catch (e) {
|
||||
const { BuildError } = await import('../utils/device/errors');
|
||||
const { BuildError } = await import('../../utils/device/errors');
|
||||
if (instanceOf(e, BuildError)) {
|
||||
throw new ExpectedError(e.toString());
|
||||
} else {
|
||||
@ -386,7 +392,9 @@ export default class PushCmd extends Command {
|
||||
}
|
||||
|
||||
protected async getBuildTarget(appOrDevice: string): Promise<BuildTarget> {
|
||||
const { validateLocalHostnameOrIp } = await import('../utils/validation');
|
||||
const { validateLocalHostnameOrIp } = await import(
|
||||
'../../utils/validation'
|
||||
);
|
||||
|
||||
return validateLocalHostnameOrIp(appOrDevice)
|
||||
? BuildTarget.Device
|
@ -15,30 +15,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import { Flags, Args, type Interfaces } from '@oclif/core';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import jsyaml = require('js-yaml');
|
||||
import { tryAsInteger } from '../../utils/validation';
|
||||
import { jsonInfo } from '../../utils/messages';
|
||||
|
||||
export const commitOrIdArg = Args.custom({
|
||||
parse: async (commitOrId: string) => tryAsInteger(commitOrId),
|
||||
});
|
||||
|
||||
type FlagsDef = Interfaces.InferredFlags<typeof ReleaseCmd.flags>;
|
||||
|
||||
export default class ReleaseCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Get info for a release.
|
||||
|
||||
${jsonInfo.split('\n').join('\n\t\t')}
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena release a777f7345fe3d655c1c981aa642e5555',
|
||||
'$ balena release 1234567',
|
||||
'$ balena release d3f3151f5ad25ca6b070aa4d08296aca --json',
|
||||
];
|
||||
|
||||
public static usage = 'release <commitOrId>';
|
||||
|
||||
public static flags = {
|
||||
json: cf.json,
|
||||
help: cf.help,
|
||||
composition: Flags.boolean({
|
||||
default: false,
|
||||
@ -63,7 +70,7 @@ export default class ReleaseCmd extends Command {
|
||||
if (options.composition) {
|
||||
await this.showComposition(params.commitOrId, balena);
|
||||
} else {
|
||||
await this.showReleaseInfo(params.commitOrId, balena);
|
||||
await this.showReleaseInfo(params.commitOrId, balena, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +88,7 @@ export default class ReleaseCmd extends Command {
|
||||
async showReleaseInfo(
|
||||
commitOrId: string | number,
|
||||
balena: BalenaSdk.BalenaSDK,
|
||||
options: FlagsDef,
|
||||
) {
|
||||
const fields: Array<keyof BalenaSdk.Release> = [
|
||||
'id',
|
||||
@ -95,7 +103,7 @@ export default class ReleaseCmd extends Command {
|
||||
];
|
||||
|
||||
const release = await balena.models.release.get(commitOrId, {
|
||||
$select: fields,
|
||||
...(!options.json && { $select: fields }),
|
||||
$expand: {
|
||||
release_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
@ -103,17 +111,21 @@ export default class ReleaseCmd extends Command {
|
||||
},
|
||||
});
|
||||
|
||||
const tagStr = release
|
||||
.release_tag!.map((t) => `${t.tag_key}=${t.value}`)
|
||||
.join('\n');
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(release, null, 4));
|
||||
} else {
|
||||
const tagStr = release
|
||||
.release_tag!.map((t) => `${t.tag_key}=${t.value}`)
|
||||
.join('\n');
|
||||
|
||||
const _ = await import('lodash');
|
||||
const values = _.mapValues(
|
||||
release,
|
||||
(val) => val ?? 'N/a',
|
||||
) as Dictionary<string>;
|
||||
values['tags'] = tagStr;
|
||||
const _ = await import('lodash');
|
||||
const values = _.mapValues(
|
||||
release,
|
||||
(val) => val ?? 'N/a',
|
||||
) as Dictionary<string>;
|
||||
values['tags'] = tagStr;
|
||||
|
||||
console.log(getVisuals().table.vertical(values, [...fields, 'tags']));
|
||||
console.log(getVisuals().table.vertical(values, [...fields, 'tags']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,12 @@
|
||||
*/
|
||||
|
||||
import { Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationNameNote } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationNameNote } from '../../utils/messages';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import { jsonInfo } from '../../utils/messages';
|
||||
|
||||
export default class ReleasesCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -29,12 +30,18 @@ export default class ReleasesCmd extends Command {
|
||||
List all releases of the given fleet.
|
||||
|
||||
${applicationNameNote.split('\n').join('\n\t\t')}
|
||||
|
||||
${jsonInfo.split('\n').join('\n\t\t')}
|
||||
`;
|
||||
public static examples = ['$ balena releases myorg/myfleet'];
|
||||
public static examples = [
|
||||
'$ balena releases myorg/myfleet',
|
||||
'$ balena releases myorg/myfleet --json',
|
||||
];
|
||||
|
||||
public static usage = 'releases <fleet>';
|
||||
|
||||
public static flags = {
|
||||
json: cf.json,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -48,7 +55,7 @@ export default class ReleasesCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(ReleasesCmd);
|
||||
const { args: params, flags: options } = await this.parse(ReleasesCmd);
|
||||
|
||||
const fields: Array<keyof BalenaSdk.Release> = [
|
||||
'id',
|
||||
@ -60,19 +67,31 @@ export default class ReleasesCmd extends Command {
|
||||
];
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const { getFleetSlug } = await import('../utils/sdk');
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
|
||||
const releases = await balena.models.release.getAllByApplication(
|
||||
await getFleetSlug(balena, params.fleet),
|
||||
{ $select: fields },
|
||||
options.json
|
||||
? {
|
||||
$expand: {
|
||||
release_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
},
|
||||
},
|
||||
}
|
||||
: { $select: fields },
|
||||
);
|
||||
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(releases, null, 4));
|
||||
} else {
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getCliUx, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getCliUx, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class ScanCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -64,9 +64,11 @@ export default class ScanCmd extends Command {
|
||||
|
||||
public async run() {
|
||||
const _ = await import('lodash');
|
||||
const { discoverLocalBalenaOsDevices } = await import('../utils/discover');
|
||||
const { discoverLocalBalenaOsDevices } = await import(
|
||||
'../../utils/discover'
|
||||
);
|
||||
const prettyjson = await import('prettyjson');
|
||||
const dockerUtils = await import('../utils/docker');
|
||||
const dockerUtils = await import('../../utils/docker');
|
||||
|
||||
const dockerPort = 2375;
|
||||
const dockerTimeout = 2000;
|
@ -15,9 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class SettingsCmd extends Command {
|
||||
public static description = stripIndent`
|
@ -16,10 +16,13 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import { parseAsInteger, validateLocalHostnameOrIp } from '../utils/validation';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import {
|
||||
parseAsInteger,
|
||||
validateLocalHostnameOrIp,
|
||||
} from '../../utils/validation';
|
||||
|
||||
export default class SshCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -108,7 +111,7 @@ export default class SshCmd extends Command {
|
||||
|
||||
// Local connection
|
||||
if (validateLocalHostnameOrIp(params.fleetOrDevice)) {
|
||||
const { performLocalDeviceSSH } = await import('../utils/device/ssh');
|
||||
const { performLocalDeviceSSH } = await import('../../utils/device/ssh');
|
||||
return await performLocalDeviceSSH({
|
||||
hostname: params.fleetOrDevice,
|
||||
port: options.port || 'local',
|
||||
@ -119,8 +122,8 @@ export default class SshCmd extends Command {
|
||||
}
|
||||
|
||||
// Remote connection
|
||||
const { getProxyConfig } = await import('../utils/helpers');
|
||||
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
|
||||
const { getProxyConfig } = await import('../../utils/helpers');
|
||||
const { getOnlineTargetDeviceUuid } = await import('../../utils/patterns');
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
const proxyConfig = getProxyConfig();
|
||||
@ -134,7 +137,7 @@ export default class SshCmd extends Command {
|
||||
params.fleetOrDevice,
|
||||
);
|
||||
|
||||
const { which } = await import('../utils/which');
|
||||
const { which } = await import('../../utils/which');
|
||||
|
||||
const [whichProxytunnel, { username }, proxyUrl] = await Promise.all([
|
||||
useProxy ? which('proxytunnel', false) : undefined,
|
||||
@ -185,7 +188,9 @@ export default class SshCmd extends Command {
|
||||
// that we know exists and is accessible
|
||||
let containerId: string | undefined;
|
||||
if (params.service != null) {
|
||||
const { getContainerIdForService } = await import('../utils/device/ssh');
|
||||
const { getContainerIdForService } = await import(
|
||||
'../../utils/device/ssh'
|
||||
);
|
||||
containerId = await getContainerIdForService({
|
||||
deviceUuid,
|
||||
hostname: `ssh.${proxyUrl}`,
|
||||
@ -202,7 +207,7 @@ export default class SshCmd extends Command {
|
||||
} else {
|
||||
accessCommand = `host ${deviceUuid}`;
|
||||
}
|
||||
const { runRemoteCommand } = await import('../utils/ssh');
|
||||
const { runRemoteCommand } = await import('../../utils/ssh');
|
||||
await runRemoteCommand({
|
||||
cmd: accessCommand,
|
||||
hostname: `ssh.${proxyUrl}`,
|
@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getCliUx, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
export default class SupportCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -116,7 +116,7 @@ export default class SupportCmd extends Command {
|
||||
ux.action.stop();
|
||||
}
|
||||
|
||||
const { getFleetSlug } = await import('../utils/sdk');
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
|
||||
// Process applications
|
||||
for (const appName of appNames) {
|
@ -56,6 +56,8 @@ export default class TagSetCmd extends Command {
|
||||
}),
|
||||
};
|
||||
|
||||
// Required for supporting empty string ('') `value` args.
|
||||
public static strict = false;
|
||||
public static usage = 'tag set <tagKey> [value]';
|
||||
|
||||
public static flags = {
|
||||
|
@ -15,11 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import Command from '../../command';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo } from '../../utils/messages';
|
||||
|
||||
export default class TagsCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -71,7 +71,7 @@ export default class TagsCmd extends Command {
|
||||
let tags;
|
||||
|
||||
if (options.fleet) {
|
||||
const { getFleetSlug } = await import('../utils/sdk');
|
||||
const { getFleetSlug } = await import('../../utils/sdk');
|
||||
tags = await balena.models.application.tags.getAllByApplication(
|
||||
await getFleetSlug(balena, options.fleet),
|
||||
);
|
||||
@ -81,7 +81,7 @@ export default class TagsCmd extends Command {
|
||||
}
|
||||
if (options.release) {
|
||||
const { disambiguateReleaseParam } = await import(
|
||||
'../utils/normalization'
|
||||
'../../utils/normalization'
|
||||
);
|
||||
const releaseParam = await disambiguateReleaseParam(
|
||||
balena,
|
@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
import { Flags, Args } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import Command from '../../command';
|
||||
import {
|
||||
NoPortsDefinedError,
|
||||
InvalidPortMappingError,
|
||||
ExpectedError,
|
||||
} from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import { lowercaseIfSlug } from '../utils/normalization';
|
||||
} from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { lowercaseIfSlug } from '../../utils/normalization';
|
||||
|
||||
import type { Server, Socket } from 'net';
|
||||
|
||||
@ -122,7 +122,7 @@ export default class TunnelCmd extends Command {
|
||||
}
|
||||
|
||||
// Ascertain device uuid
|
||||
const { getOnlineTargetDeviceUuid } = await import('../utils/patterns');
|
||||
const { getOnlineTargetDeviceUuid } = await import('../../utils/patterns');
|
||||
const uuid = await getOnlineTargetDeviceUuid(sdk, params.deviceOrFleet);
|
||||
logger.logInfo(`Opening a tunnel to ${uuid}...`);
|
||||
|
||||
@ -133,7 +133,9 @@ export default class TunnelCmd extends Command {
|
||||
})
|
||||
.map(async ({ localPort, localAddress, remotePort }) => {
|
||||
try {
|
||||
const { tunnelConnectionToDevice } = await import('../utils/tunnel');
|
||||
const { tunnelConnectionToDevice } = await import(
|
||||
'../../utils/tunnel'
|
||||
);
|
||||
const handler = await tunnelConnectionToDevice(uuid, remotePort, sdk);
|
||||
|
||||
const { createServer } = await import('net');
|
||||
@ -204,12 +206,16 @@ export default class TunnelCmd extends Command {
|
||||
let localAddress = 'localhost';
|
||||
|
||||
// First element is always remotePort
|
||||
// TODO: figure out why we have explicitly passed `undefined` for the radix parameter
|
||||
// eslint-disable-next-line radix
|
||||
const remotePort = parseInt(mappingElements[0], undefined);
|
||||
let localPort = remotePort;
|
||||
|
||||
if (mappingElements.length === 2) {
|
||||
// [1] could be localAddress or localPort
|
||||
if (/^\d+$/.test(mappingElements[1])) {
|
||||
// TODO: figure out why we have explicitly passed `undefined` for the radix parameter
|
||||
// eslint-disable-next-line radix
|
||||
localPort = parseInt(mappingElements[1], undefined);
|
||||
} else {
|
||||
localAddress = mappingElements[1];
|
||||
@ -217,6 +223,8 @@ export default class TunnelCmd extends Command {
|
||||
} else if (mappingElements.length === 3) {
|
||||
// [1] is localAddress, [2] is localPort
|
||||
localAddress = mappingElements[1];
|
||||
// TODO: figure out why we have explicitly passed `undefined` for the radix parameter
|
||||
// eslint-disable-next-line radix
|
||||
localPort = parseInt(mappingElements[2], undefined);
|
||||
} else if (mappingElements.length > 3) {
|
||||
throw new InvalidPortMappingError(portMapping);
|
@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
import { Flags } from '@oclif/core';
|
||||
import Command from '../command';
|
||||
import { stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
|
||||
export interface JsonVersions {
|
||||
'balena-cli': string;
|
||||
@ -72,7 +72,7 @@ export default class VersionCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = await this.parse(VersionCmd);
|
||||
const versions: JsonVersions = {
|
||||
'balena-cli': (await import('../../package.json')).version,
|
||||
'balena-cli': (await import('../../../package.json')).version,
|
||||
'Node.js':
|
||||
process.version && process.version.startsWith('v')
|
||||
? process.version.slice(1)
|
@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Command from '../command';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import Command from '../../command';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class WhoamiCmd extends Command {
|
||||
public static description = stripIndent`
|
@ -47,6 +47,12 @@ export class NoPortsDefinedError extends ExpectedError {
|
||||
|
||||
export class SIGINTError extends ExpectedError {}
|
||||
|
||||
export class CompositionFileNotFoundError extends ExpectedError {
|
||||
constructor(filePath: string) {
|
||||
super(`Composition file not found at: "${filePath}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* instanceOf is a more reliable implementation of the plain `instanceof`
|
||||
* typescript operator, for use with TypedError errors when the error
|
||||
@ -211,7 +217,6 @@ const EXPECTED_ERROR_REGEXES = [
|
||||
/^BalenaOrganizationNotFound/, // balena-sdk
|
||||
/Request error: Unauthorized$/, // balena-sdk
|
||||
/^Missing \d+ required arg/, // oclif parser: RequiredArgsError
|
||||
/Missing required flag/, // oclif parser: RequiredFlagError
|
||||
/^Unexpected argument/, // oclif parser: UnexpectedArgsError
|
||||
/to be one of/, // oclif parser: FlagInvalidOptionError, ArgInvalidOptionError
|
||||
/must also be provided when using/, // oclif parser (depends-on)
|
||||
|
@ -47,7 +47,7 @@ export function outputMessage(msg: string) {
|
||||
* @param options Output options
|
||||
*/
|
||||
export async function outputData(
|
||||
data: any[] | {},
|
||||
data: any[] | object,
|
||||
fields: string[],
|
||||
options: DataOutputOptions | DataSetOutputOptions,
|
||||
) {
|
||||
|
@ -50,7 +50,7 @@ export default class BalenaHelp extends Help {
|
||||
|
||||
const command = this.config.findCommand(subject);
|
||||
if (command) {
|
||||
await this.showCommandHelp(await command.load());
|
||||
await this.showCommandHelp(command);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
|
||||
export let unsupportedFlag = false;
|
||||
|
||||
export interface AppOptions {
|
||||
// Prevent the default behavior of flushing stdout after running a command
|
||||
noFlush?: boolean;
|
||||
configPath?: string;
|
||||
development?: boolean;
|
||||
dir: string;
|
||||
loadOptions?: Interfaces.LoadOptions;
|
||||
}
|
||||
|
||||
export async function preparseArgs(argv: string[]): Promise<string[]> {
|
||||
|
@ -31,6 +31,7 @@ export async function applicationCreateBase(
|
||||
name: params.name,
|
||||
deviceType,
|
||||
organization,
|
||||
applicationClass: resource,
|
||||
});
|
||||
|
||||
// Output
|
||||
|
@ -185,8 +185,8 @@ export async function downloadOSImage(
|
||||
output = fs.createWriteStream(outputPath);
|
||||
}
|
||||
|
||||
const streamToPromise = await import('stream-to-promise');
|
||||
await streamToPromise(stream.pipe(output));
|
||||
const { pipeline } = await import('node:stream/promises');
|
||||
await pipeline(stream, output);
|
||||
|
||||
console.info(
|
||||
`balenaOS image version ${displayVersion} downloaded successfully`,
|
||||
@ -200,22 +200,23 @@ async function resolveOSVersion(
|
||||
version: string,
|
||||
): Promise<string> {
|
||||
if (['menu', 'menu-esr'].includes(version)) {
|
||||
return await selectOSVersionFromMenu(deviceType, version === 'menu-esr');
|
||||
}
|
||||
// Note that `version` may also be 'latest', 'recommended', 'default'
|
||||
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
||||
if (version[0] === 'v') {
|
||||
version = version.slice(1);
|
||||
}
|
||||
return await selectOSVersionFromMenu(
|
||||
deviceType,
|
||||
version === 'menu-esr',
|
||||
false,
|
||||
);
|
||||
}
|
||||
const { normalizeOsVersion } = await import('./normalization');
|
||||
version = normalizeOsVersion(version);
|
||||
return version;
|
||||
}
|
||||
|
||||
async function selectOSVersionFromMenu(
|
||||
deviceType: string,
|
||||
esr: boolean,
|
||||
includeDraft: boolean,
|
||||
): Promise<string> {
|
||||
const vs = await getOsVersions(deviceType, esr);
|
||||
const vs = await getOsVersions(deviceType, esr, includeDraft);
|
||||
|
||||
const choices = vs.map((v) => ({
|
||||
value: v.raw_version,
|
||||
@ -237,18 +238,22 @@ async function selectOSVersionFromMenu(
|
||||
export async function getOsVersions(
|
||||
deviceType: string,
|
||||
esr: boolean,
|
||||
includeDraft: boolean,
|
||||
): Promise<SDK.OsVersion[]> {
|
||||
const sdk = getBalenaSdk();
|
||||
let slug = deviceType;
|
||||
let versions: SDK.OsVersion[] = await sdk.models.os.getAvailableOsVersions(
|
||||
slug,
|
||||
{ includeDraft },
|
||||
);
|
||||
// if slug is an alias, fetch the real slug
|
||||
if (!versions.length) {
|
||||
// unalias device type slug
|
||||
slug = (await sdk.models.deviceType.get(slug, { $select: 'slug' })).slug;
|
||||
if (slug !== deviceType) {
|
||||
versions = await sdk.models.os.getAvailableOsVersions(slug);
|
||||
versions = await sdk.models.os.getAvailableOsVersions(slug, {
|
||||
includeDraft,
|
||||
});
|
||||
}
|
||||
}
|
||||
versions = versions.filter(
|
||||
|
2
lib/utils/compose-types.d.ts
vendored
2
lib/utils/compose-types.d.ts
vendored
@ -53,6 +53,7 @@ export interface TaggedImage {
|
||||
export interface ComposeOpts {
|
||||
convertEol: boolean;
|
||||
dockerfilePath?: string;
|
||||
composefileName?: string;
|
||||
inlineLogs?: boolean;
|
||||
multiDockerignore: boolean;
|
||||
noParentCheck: boolean;
|
||||
@ -65,6 +66,7 @@ export interface ComposeCliFlags {
|
||||
emulated: boolean;
|
||||
dockerfile?: string;
|
||||
nologs: boolean;
|
||||
'docker-compose'?: string;
|
||||
'multi-dockerignore': boolean;
|
||||
'noparent-check': boolean;
|
||||
'registry-secrets'?: RegistrySecrets;
|
||||
|
@ -20,6 +20,7 @@ import type * as SDK from 'balena-sdk';
|
||||
import type Dockerode = require('dockerode');
|
||||
import * as path from 'path';
|
||||
import type { Composition, ImageDescriptor } from '@balena/compose/dist/parse';
|
||||
import type { RetryParametersObj } from 'pinejs-client-core';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeOpts,
|
||||
@ -39,6 +40,7 @@ export function generateOpts(options: {
|
||||
dockerfile?: string;
|
||||
'multi-dockerignore': boolean;
|
||||
'noparent-check': boolean;
|
||||
'docker-compose'?: string;
|
||||
}): Promise<ComposeOpts> {
|
||||
const { promises: fs } = require('fs') as typeof import('fs');
|
||||
return fs.realpath(options.source || '.').then((projectPath) => ({
|
||||
@ -48,6 +50,7 @@ export function generateOpts(options: {
|
||||
convertEol: !options['noconvert-eol'],
|
||||
dockerfilePath: options.dockerfile,
|
||||
multiDockerignore: !!options['multi-dockerignore'],
|
||||
composefileName: options['docker-compose'],
|
||||
noParentCheck: options['noparent-check'],
|
||||
}));
|
||||
}
|
||||
@ -94,22 +97,62 @@ export function createProject(
|
||||
};
|
||||
}
|
||||
|
||||
const getRequestRetryParameters = (): RetryParametersObj => {
|
||||
if (
|
||||
process.env.BALENA_CLI_TEST_TYPE != null &&
|
||||
process.env.BALENA_CLI_TEST_TYPE !== ''
|
||||
) {
|
||||
// We only read the test env vars when in test mode.
|
||||
const { intVar } =
|
||||
require('@balena/env-parsing') as typeof import('@balena/env-parsing');
|
||||
// We use the BALENARCTEST namespace and only parse the env vars while in test mode
|
||||
// since we plan to switch all pinejs clients with the one of the SDK and might not
|
||||
// want to have to support these env vars.
|
||||
return {
|
||||
minDelayMs: intVar('BALENARCTEST_API_RETRY_MIN_DELAY_MS'),
|
||||
maxDelayMs: intVar('BALENARCTEST_API_RETRY_MAX_DELAY_MS'),
|
||||
maxAttempts: intVar('BALENARCTEST_API_RETRY_MAX_ATTEMPTS'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
minDelayMs: 1000,
|
||||
maxDelayMs: 60000,
|
||||
maxAttempts: 7,
|
||||
};
|
||||
};
|
||||
|
||||
export const createRelease = async function (
|
||||
logger: Logger,
|
||||
apiEndpoint: string,
|
||||
auth: string,
|
||||
userId: number,
|
||||
appId: number,
|
||||
composition: Composition,
|
||||
draft: boolean,
|
||||
semver?: string,
|
||||
contract?: string,
|
||||
semver: string | undefined,
|
||||
contract: string | undefined,
|
||||
): Promise<Release> {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const crypto = require('crypto') as typeof import('crypto');
|
||||
const releaseMod =
|
||||
require('@balena/compose/dist/release') as typeof import('@balena/compose/dist/release');
|
||||
|
||||
const client = releaseMod.createClient({ apiEndpoint, auth });
|
||||
const client = releaseMod.createClient({
|
||||
apiEndpoint,
|
||||
auth,
|
||||
retry: {
|
||||
...getRequestRetryParameters(),
|
||||
onRetry: (err, delayMs, attempt, maxAttempts) => {
|
||||
const code = err?.statusCode ?? 0;
|
||||
logger.logDebug(
|
||||
`API call failed with code ${code}. Attempting retry ${attempt} of ${maxAttempts} in ${
|
||||
delayMs / 1000
|
||||
} seconds`,
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { release, serviceImages } = await releaseMod.create({
|
||||
client,
|
||||
|
@ -31,8 +31,7 @@ import type * as MultiBuild from '@balena/compose/dist/multibuild';
|
||||
import * as semver from 'semver';
|
||||
import type { Duplex, Readable } from 'stream';
|
||||
import type { Pack } from 'tar-stream';
|
||||
|
||||
import { ExpectedError } from '../errors';
|
||||
import { CompositionFileNotFoundError, ExpectedError } from '../errors';
|
||||
import {
|
||||
BuiltImage,
|
||||
ComposeOpts,
|
||||
@ -129,7 +128,11 @@ export async function loadProject(
|
||||
composeStr = compose.defaultComposition(image);
|
||||
} else {
|
||||
logger.logDebug('Resolving project...');
|
||||
[composeName, composeStr] = await resolveProject(logger, opts.projectPath);
|
||||
[composeName, composeStr] = await resolveProject({
|
||||
logger,
|
||||
projectRoot: opts.projectPath,
|
||||
compositionFile: opts.composefileName,
|
||||
});
|
||||
|
||||
if (composeName) {
|
||||
if (opts.dockerfilePath) {
|
||||
@ -199,33 +202,55 @@ async function mergeDevComposeOverlay(
|
||||
return composeStr;
|
||||
}
|
||||
|
||||
async function getDefaultCompositionFileName(
|
||||
projectRoot: string,
|
||||
): Promise<string | undefined> {
|
||||
for (const fname of compositionFileNames) {
|
||||
if (await exists(path.join(projectRoot, fname))) {
|
||||
return fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ResolveProjectParameters {
|
||||
logger: Logger;
|
||||
projectRoot: string;
|
||||
quiet?: boolean;
|
||||
compositionFile?: string;
|
||||
}
|
||||
/**
|
||||
* Look into the given directory for valid compose files and return
|
||||
* the contents of the first one found.
|
||||
*/
|
||||
async function resolveProject(
|
||||
logger: Logger,
|
||||
projectRoot: string,
|
||||
async function resolveProject({
|
||||
logger,
|
||||
projectRoot,
|
||||
quiet = false,
|
||||
): Promise<[string, string]> {
|
||||
let composeFileName = '';
|
||||
compositionFile,
|
||||
}: ResolveProjectParameters): Promise<[string, string]> {
|
||||
logger.logError(`Iam on resolve project and have ${compositionFile}`);
|
||||
const composeFileName =
|
||||
compositionFile ?? (await getDefaultCompositionFileName(projectRoot));
|
||||
let composeFileContents = '';
|
||||
for (const fname of compositionFileNames) {
|
||||
const fpath = path.join(projectRoot, fname);
|
||||
if (await exists(fpath)) {
|
||||
logger.logDebug(`${fname} file found at "${projectRoot}"`);
|
||||
composeFileName = fname;
|
||||
try {
|
||||
composeFileContents = await fs.readFile(fpath, 'utf8');
|
||||
} catch (err) {
|
||||
logger.logError(`Error reading composition file "${fpath}":\n${err}`);
|
||||
throw err;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (composeFileName == null) {
|
||||
throw new CompositionFileNotFoundError(projectRoot);
|
||||
}
|
||||
|
||||
const fpath = path.join(projectRoot, composeFileName);
|
||||
if (!(await exists(fpath))) {
|
||||
throw new CompositionFileNotFoundError(fpath);
|
||||
}
|
||||
|
||||
logger.logDebug(`Using composition file at "${fpath}"`);
|
||||
try {
|
||||
composeFileContents = await fs.readFile(fpath, 'utf8');
|
||||
} catch (err) {
|
||||
logger.logError(`Error reading composition file "${fpath}":\n${err}`);
|
||||
throw err;
|
||||
}
|
||||
if (!quiet && !composeFileName) {
|
||||
logger.logInfo(`No "docker-compose.yml" file found at "${projectRoot}"`);
|
||||
logger.logInfo(`No composition file found at "${projectRoot}"`);
|
||||
}
|
||||
|
||||
return [composeFileName, composeFileContents];
|
||||
@ -681,15 +706,17 @@ async function loadBuildMetatada(
|
||||
export async function getServiceDirsFromComposition(
|
||||
sourceDir: string,
|
||||
composition?: Composition,
|
||||
compositionFile?: string,
|
||||
): Promise<Dictionary<string>> {
|
||||
const { createProject } = await import('./compose');
|
||||
const serviceDirs: Dictionary<string> = {};
|
||||
if (!composition) {
|
||||
const [, composeStr] = await resolveProject(
|
||||
Logger.getLogger(),
|
||||
sourceDir,
|
||||
true,
|
||||
);
|
||||
const [, composeStr] = await resolveProject({
|
||||
logger: Logger.getLogger(),
|
||||
projectRoot: sourceDir,
|
||||
quiet: true,
|
||||
compositionFile,
|
||||
});
|
||||
if (composeStr) {
|
||||
composition = createProject(sourceDir, composeStr).composition;
|
||||
}
|
||||
@ -1244,7 +1271,7 @@ async function pushAndUpdateServiceImages(
|
||||
afterEach: (
|
||||
serviceImage: import('@balena/compose/dist/release/models').ImageModel,
|
||||
props: object,
|
||||
) => void,
|
||||
) => Promise<void>,
|
||||
) {
|
||||
const { DockerProgress } = await import('docker-progress');
|
||||
const { retry } = await import('./helpers');
|
||||
@ -1386,6 +1413,7 @@ export async function deployProject(
|
||||
`${prefix}Creating release...`,
|
||||
() =>
|
||||
createRelease(
|
||||
logger,
|
||||
apiEndpoint,
|
||||
auth,
|
||||
userId,
|
||||
@ -1652,6 +1680,9 @@ export const composeCliFlags = {
|
||||
description:
|
||||
'Alternative Dockerfile name/path, relative to the source folder',
|
||||
}),
|
||||
'docker-compose': Flags.string({
|
||||
description: 'Alternative compose yml file, relative to the source folder',
|
||||
}),
|
||||
nologs: Flags.boolean({
|
||||
description:
|
||||
'Hide the image build log output (produce less verbose output)',
|
||||
|
@ -184,9 +184,9 @@ export async function validateDevOptionAndWarn(
|
||||
* option.
|
||||
*/
|
||||
export async function validateSecureBootOptionAndWarn(
|
||||
secureBoot?: boolean,
|
||||
slug?: string,
|
||||
version?: string,
|
||||
secureBoot: boolean,
|
||||
slug: string,
|
||||
version: string,
|
||||
logger?: import('./logger'),
|
||||
) {
|
||||
if (!secureBoot) {
|
||||
@ -202,7 +202,7 @@ export async function validateSecureBootOptionAndWarn(
|
||||
const sdk = getBalenaSdk();
|
||||
const [osRelease] = await sdk.models.os.getAllOsVersions(slug, {
|
||||
$select: 'contract',
|
||||
$filter: { raw_version: `${version.replace(/^v/, '')}` },
|
||||
$filter: { raw_version: version },
|
||||
});
|
||||
if (!osRelease) {
|
||||
throw new ExpectedError(`Error: No ${version} release for ${slug}`);
|
||||
|
@ -56,6 +56,7 @@ export interface DeviceDeployOptions {
|
||||
deviceHost: string;
|
||||
devicePort?: number;
|
||||
dockerfilePath?: string;
|
||||
composefileName?: string;
|
||||
registrySecrets: RegistrySecrets;
|
||||
multiDockerignore: boolean;
|
||||
nocache: boolean;
|
||||
@ -182,6 +183,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
const project = await loadProject(globalLogger, {
|
||||
convertEol: opts.convertEol,
|
||||
dockerfilePath: opts.dockerfilePath,
|
||||
composefileName: opts.composefileName,
|
||||
multiDockerignore: opts.multiDockerignore,
|
||||
noParentCheck: opts.noParentCheck,
|
||||
projectName: 'local',
|
||||
@ -627,17 +629,10 @@ export function generateTargetState(
|
||||
};
|
||||
|
||||
opts.environment = _.merge(opts.environment, env[name]);
|
||||
// This function can be called with a subset of the
|
||||
// build tasks, when a single dockerfile has changed
|
||||
// when livepushing, so check the build task exists for
|
||||
// this composition entry (everything else in this
|
||||
// function comes from the composition which doesn't
|
||||
// change)
|
||||
let contract;
|
||||
if (name in keyedBuildTasks) {
|
||||
contract = keyedBuildTasks[name].contract;
|
||||
}
|
||||
|
||||
// This function should always be called with all the build tasks
|
||||
// so we can construct the correct target state so we don't really need
|
||||
// to check that the key exists on the `keyedBuildTasks` object
|
||||
const contract = keyedBuildTasks[name].contract;
|
||||
const task = keyedBuildTasks[name];
|
||||
|
||||
services[idx] = {
|
||||
|
@ -194,6 +194,7 @@ export class LivepushManager {
|
||||
);
|
||||
const eventQueue = this.updateEventsWaiting[$serviceName];
|
||||
eventQueue.push(changedPath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.getDebouncedEventHandler($serviceName)();
|
||||
};
|
||||
|
||||
@ -422,7 +423,12 @@ export class LivepushManager {
|
||||
// If we re-apply the target state, the supervisor
|
||||
// should recreate the container
|
||||
await this.api.setTargetState(
|
||||
generateTargetState(currentState, this.composition, [buildTask], {}),
|
||||
generateTargetState(
|
||||
currentState,
|
||||
this.composition,
|
||||
this.buildTasks,
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
await this.awaitDeviceStateSettle();
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* eslint-disable no-restricted-imports */
|
||||
/** the import blacklist is to enforce lazy loading so exempt this file */
|
||||
/*
|
||||
Copyright 2020 Balena
|
||||
|
||||
@ -14,8 +16,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// tslint:disable:import-blacklist - the import blacklist is to enforce lazy loading so exempt this file
|
||||
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import type { Chalk } from 'chalk';
|
||||
import type * as visuals from 'resin-cli-visuals';
|
||||
|
@ -81,3 +81,13 @@ export async function disambiguateReleaseParam(
|
||||
export async function lowercaseIfSlug(s: string) {
|
||||
return s.includes('/') ? s.toLowerCase() : s;
|
||||
}
|
||||
|
||||
export function normalizeOsVersion(version: string) {
|
||||
// Note that `version` may also be 'latest', 'recommended', 'default'
|
||||
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
||||
if (version[0] === 'v') {
|
||||
version = version.slice(1);
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
||||
import validation = require('./validation');
|
||||
import { delay } from './helpers';
|
||||
|
||||
export function authenticate(options: {}): Promise<void> {
|
||||
export function authenticate(options: object): Promise<void> {
|
||||
const balena = getBalenaSdk();
|
||||
return getCliForm()
|
||||
.run(
|
||||
|
@ -16,10 +16,11 @@ limitations under the License.
|
||||
|
||||
import type { OptionalNavigationResource } from 'balena-sdk';
|
||||
|
||||
export const getExpanded = <T extends {}>(obj: OptionalNavigationResource<T>) =>
|
||||
(Array.isArray(obj) && obj[0]) || undefined;
|
||||
export const getExpanded = <T extends object>(
|
||||
obj: OptionalNavigationResource<T>,
|
||||
) => (Array.isArray(obj) && obj[0]) || undefined;
|
||||
|
||||
export const getExpandedProp = <T extends {}, K extends keyof T>(
|
||||
export const getExpandedProp = <T extends object, K extends keyof T>(
|
||||
obj: OptionalNavigationResource<T>,
|
||||
key: K,
|
||||
) => (Array.isArray(obj) && obj[0] && obj[0][key]) || undefined;
|
||||
|
@ -394,7 +394,9 @@ async function createApplication(
|
||||
throw new sdk.errors.BalenaNotLoggedIn();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const applicationName = await new Promise<string>(async (resolve, reject) => {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
const appName = await getCliForm().ask({
|
||||
|
@ -194,7 +194,7 @@ async function handleHeadlessBuildStream(
|
||||
|
||||
function handleBuilderMetadata(obj: BuilderMessage, build: RemoteBuild) {
|
||||
switch (obj.resource) {
|
||||
case 'cursor':
|
||||
case 'cursor': {
|
||||
if (obj.value == null) {
|
||||
return;
|
||||
}
|
||||
@ -228,6 +228,7 @@ function handleBuilderMetadata(obj: BuilderMessage, build: RemoteBuild) {
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'buildLogId':
|
||||
// The name of this resource is slightly dated, but this is the release
|
||||
// id from the API. We need to save this so that if the user ctrl+c's the
|
||||
@ -291,10 +292,10 @@ async function cancelBuildIfNecessary(build: RemoteBuild): Promise<void> {
|
||||
async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
||||
let tarSpinner = {
|
||||
start: () => {
|
||||
/*noop*/
|
||||
/* noop*/
|
||||
},
|
||||
stop: () => {
|
||||
/*noop*/
|
||||
/* noop*/
|
||||
},
|
||||
};
|
||||
if (process.stdout.isTTY) {
|
||||
|
14253
npm-shrinkwrap.json
generated
14253
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "17.1.6",
|
||||
"version": "18.0.0",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -16,6 +16,7 @@
|
||||
"doc/",
|
||||
"lib/",
|
||||
"patches/",
|
||||
"!patches/**/**.dev.patch",
|
||||
"*.md",
|
||||
"npm-shrinkwrap.json",
|
||||
"oclif.manifest.json"
|
||||
@ -39,6 +40,7 @@
|
||||
"node_modules/open/xdg-open",
|
||||
"node_modules/windosu/*.bat",
|
||||
"node_modules/windosu/*.cmd",
|
||||
"node_modules/axios/**/*",
|
||||
"npm-shrinkwrap.json",
|
||||
"oclif.manifest.json"
|
||||
]
|
||||
@ -59,7 +61,8 @@
|
||||
"package": "npm run build:fast && npm run build:standalone && npm run build:installer",
|
||||
"release": "ts-node --transpile-only automation/run.ts release",
|
||||
"pretest": "npm run build",
|
||||
"test": "npm run test:shrinkwrap && npm run test:source && npm run test:standalone",
|
||||
"test": "npm run test:shrinkwrap && npm run test:core",
|
||||
"test:core": "npm run test:source && npm run test:standalone",
|
||||
"test:shrinkwrap": "ts-node --transpile-only automation/run.ts test-shrinkwrap",
|
||||
"test:source": "cross-env BALENA_CLI_TEST_TYPE=source mocha",
|
||||
"test:standalone": "npm run build:standalone && npm run test:standalone:fast",
|
||||
@ -88,7 +91,7 @@
|
||||
"author": "Balena Inc. (https://balena.io/)",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18 <20"
|
||||
"node": ">=20 <21"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@ -112,7 +115,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^6.2.2",
|
||||
"@balena/lint": "^7.2.1",
|
||||
"@electron/notarize": "^2.0.0",
|
||||
"@octokit/plugin-throttling": "^3.5.1",
|
||||
"@octokit/rest": "^18.6.7",
|
||||
@ -123,6 +126,7 @@
|
||||
"@types/chai-as-promised": "^7.1.4",
|
||||
"@types/cli-truncate": "^2.0.0",
|
||||
"@types/common-tags": "^1.8.1",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/dockerode": "^3.3.9",
|
||||
"@types/ejs": "^3.1.0",
|
||||
"@types/express": "^4.17.13",
|
||||
@ -144,7 +148,7 @@
|
||||
"@types/ndjson": "^2.0.1",
|
||||
"@types/net-keepalive": "^0.4.1",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^18.17.6",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-cleanup": "^2.1.2",
|
||||
"@types/parse-link-header": "^1.0.1",
|
||||
"@types/prettyjson": "^0.0.30",
|
||||
@ -161,6 +165,7 @@
|
||||
"@types/through2": "^2.0.36",
|
||||
"@types/tmp": "^0.2.3",
|
||||
"@types/which": "^2.0.1",
|
||||
"@types/window-size": "^1.1.1",
|
||||
"archiver": "^5.3.0",
|
||||
"catch-uncommitted": "^2.0.0",
|
||||
"chai": "^4.3.4",
|
||||
@ -182,30 +187,31 @@
|
||||
"nock": "^13.2.1",
|
||||
"oclif": "^3.17.1",
|
||||
"parse-link-header": "^2.0.0",
|
||||
"pkg": "^5.8.1",
|
||||
"publish-release": "^1.6.1",
|
||||
"rewire": "^5.0.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"sinon": "^11.1.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^5.1.3"
|
||||
"typescript": "^5.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@balena/compose": "^3.0.2",
|
||||
"@balena/compose": "^3.2.0",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"@balena/env-parsing": "^1.1.8",
|
||||
"@balena/es-version": "^1.0.1",
|
||||
"@oclif/core": "^2.15.0",
|
||||
"@oclif/core": "^3.14.1",
|
||||
"@resin.io/valid-email": "^0.1.0",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"@types/fast-levenshtein": "0.0.1",
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"@yao-pkg/pkg": "^5.11.1",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^6.0.0",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-image-manager": "^9.0.2",
|
||||
"balena-preload": "^14.0.3",
|
||||
"balena-sdk": "^18.0.0",
|
||||
"balena-image-manager": "^10.0.1",
|
||||
"balena-preload": "^15.0.1",
|
||||
"balena-sdk": "^19.4.0",
|
||||
"balena-semver": "^2.3.0",
|
||||
"balena-settings-client": "^5.0.2",
|
||||
"balena-settings-storage": "^8.1.0",
|
||||
@ -222,7 +228,7 @@
|
||||
"docker-progress": "^5.1.3",
|
||||
"dockerode": "3.3.3",
|
||||
"ejs": "^3.1.6",
|
||||
"etcher-sdk": "^8.7.0",
|
||||
"etcher-sdk": "9.0.6",
|
||||
"event-stream": "3.3.4",
|
||||
"express": "^4.17.2",
|
||||
"fast-boot2": "^1.1.0",
|
||||
@ -280,6 +286,6 @@
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2023-10-18T18:16:05.977Z"
|
||||
"publishedAt": "2024-02-06T12:19:36.007Z"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
diff --git a/node_modules/@oclif/core/lib/cli-ux/list.js b/node_modules/@oclif/core/lib/cli-ux/list.js
|
||||
index dc6058c..64b2f85 100644
|
||||
index 607d8dc..07ba1f2 100644
|
||||
--- a/node_modules/@oclif/core/lib/cli-ux/list.js
|
||||
+++ b/node_modules/@oclif/core/lib/cli-ux/list.js
|
||||
@@ -22,7 +22,7 @@ function renderList(items) {
|
||||
@ -12,20 +12,20 @@ index dc6058c..64b2f85 100644
|
||||
return lines.join('\n');
|
||||
}
|
||||
diff --git a/node_modules/@oclif/core/lib/help/command.js b/node_modules/@oclif/core/lib/help/command.js
|
||||
index 6de139b..3a13197 100644
|
||||
index 63c0545..7caad4a 100644
|
||||
--- a/node_modules/@oclif/core/lib/help/command.js
|
||||
+++ b/node_modules/@oclif/core/lib/help/command.js
|
||||
@@ -206,7 +206,7 @@ class CommandHelp extends formatter_1.HelpFormatter {
|
||||
if (args.filter(a => a.description).length === 0)
|
||||
@@ -58,7 +58,7 @@ class CommandHelp extends formatter_1.HelpFormatter {
|
||||
if (args.filter((a) => a.description).length === 0)
|
||||
return;
|
||||
return args.map(a => {
|
||||
return args.map((a) => {
|
||||
- const name = a.name.toUpperCase();
|
||||
+ const name = a.required ? `<${a.name}>` : `[${a.name}]`;
|
||||
let description = a.description || '';
|
||||
if (a.default)
|
||||
description = `[default: ${a.default}] ${description}`;
|
||||
@@ -238,14 +238,12 @@ class CommandHelp extends formatter_1.HelpFormatter {
|
||||
label = labels.join(', ');
|
||||
description = `${(0, theme_1.colorize)(this.config?.theme?.flagDefaultValue, `[default: ${a.default}]`)} ${description}`;
|
||||
@@ -153,14 +153,12 @@ class CommandHelp extends formatter_1.HelpFormatter {
|
||||
label = labels.join(flag.char ? (0, theme_1.colorize)(this.config?.theme?.flagSeparator, ', ') : ' ');
|
||||
}
|
||||
if (flag.type === 'option') {
|
||||
- let value = flag.helpValue || (this.opts.showFlagNameInTitle ? flag.name : '<value>');
|
||||
@ -36,16 +36,16 @@ index 6de139b..3a13197 100644
|
||||
if (flag.multiple)
|
||||
- value += '...';
|
||||
- if (!value.includes('|'))
|
||||
- value = underline(value);
|
||||
- value = chalk_1.default.underline(value);
|
||||
+ value += ' ...';
|
||||
label += `=${value}`;
|
||||
}
|
||||
return label;
|
||||
return (0, theme_1.colorize)(this.config.theme?.flag, label);
|
||||
diff --git a/node_modules/@oclif/core/lib/help/index.js b/node_modules/@oclif/core/lib/help/index.js
|
||||
index f9ef7cc..a14c67c 100644
|
||||
index 242538a..efde8ac 100644
|
||||
--- a/node_modules/@oclif/core/lib/help/index.js
|
||||
+++ b/node_modules/@oclif/core/lib/help/index.js
|
||||
@@ -136,11 +136,12 @@ class Help extends HelpBase {
|
||||
@@ -168,11 +168,12 @@ class Help extends HelpBase {
|
||||
}
|
||||
this.log(this.formatCommand(command));
|
||||
this.log('');
|
||||
@ -56,51 +56,40 @@ index f9ef7cc..a14c67c 100644
|
||||
this.log('');
|
||||
}
|
||||
- if (subCommands.length > 0) {
|
||||
+ if (subCommands.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
+ if (subTopics.length > 0 && !SUPPRESS_SUBTOPICS) {
|
||||
const aliases = [];
|
||||
const uniqueSubCommands = subCommands.filter(p => {
|
||||
const uniqueSubCommands = subCommands.filter((p) => {
|
||||
aliases.push(...p.aliases);
|
||||
diff --git a/node_modules/@oclif/core/lib/parser/errors.js b/node_modules/@oclif/core/lib/parser/errors.js
|
||||
index 07ec8e5..a4560ea 100644
|
||||
index 656ec6b..2bbf36b 100644
|
||||
--- a/node_modules/@oclif/core/lib/parser/errors.js
|
||||
+++ b/node_modules/@oclif/core/lib/parser/errors.js
|
||||
@@ -10,7 +10,8 @@ var errors_2 = require("../errors");
|
||||
Object.defineProperty(exports, "CLIError", { enumerable: true, get: function () { return errors_2.CLIError; } });
|
||||
@@ -14,7 +14,8 @@ Object.defineProperty(exports, "CLIError", { enumerable: true, get: function ()
|
||||
class CLIParseError extends errors_1.CLIError {
|
||||
parse;
|
||||
constructor(options) {
|
||||
- options.message += '\nSee more help with --help';
|
||||
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
|
||||
+ options.message += `\nSee more help with ${help}`;
|
||||
super(options.message);
|
||||
super(options.message, { exit: options.exit });
|
||||
this.parse = options.parse;
|
||||
}
|
||||
@@ -31,7 +32,8 @@ class InvalidArgsSpecError extends CLIParseError {
|
||||
exports.InvalidArgsSpecError = InvalidArgsSpecError;
|
||||
@@ -37,7 +38,8 @@ exports.InvalidArgsSpecError = InvalidArgsSpecError;
|
||||
class RequiredArgsError extends CLIParseError {
|
||||
constructor({ args, parse, flagsWithMultiple }) {
|
||||
args;
|
||||
constructor({ args, exit, flagsWithMultiple, parse, }) {
|
||||
- let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}`;
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
|
||||
const namedArgs = args.filter(a => a.name);
|
||||
const namedArgs = args.filter((a) => a.name);
|
||||
if (namedArgs.length > 0) {
|
||||
const list = (0, list_1.renderList)(namedArgs.map(a => [a.name, a.description]));
|
||||
@@ -42,16 +44,17 @@ class RequiredArgsError extends CLIParseError {
|
||||
const list = (0, list_1.renderList)(namedArgs.map((a) => [a.name, a.description]));
|
||||
@@ -48,7 +50,7 @@ class RequiredArgsError extends CLIParseError {
|
||||
message += `\n\nNote: ${flags} allow${flagsWithMultiple.length === 1 ? 's' : ''} multiple values. Because of this you need to provide all arguments before providing ${flagsWithMultiple.length === 1 ? 'that flag' : 'those flags'}.`;
|
||||
message += '\nAlternatively, you can use "--" to signify the end of the flags and the beginning of arguments.';
|
||||
}
|
||||
- super({ parse, message });
|
||||
+ super({ parse, message, command });
|
||||
- super({ exit: cache_1.default.getInstance().get('exitCodes')?.requiredArgs ?? exit, message, parse });
|
||||
+ super({ exit: cache_1.default.getInstance().get('exitCodes')?.requiredArgs ?? exit, message, parse, command });
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
exports.RequiredArgsError = RequiredArgsError;
|
||||
class RequiredFlagError extends CLIParseError {
|
||||
constructor({ flag, parse }) {
|
||||
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
|
||||
const usage = (0, list_1.renderList)((0, help_1.flagUsages)([flag], { displayRequired: false }));
|
||||
const message = `Missing required flag:\n${usage}`;
|
||||
- super({ parse, message });
|
||||
+ super({ parse, message, command });
|
||||
this.flag = flag;
|
||||
}
|
||||
}
|
2
repo.yml
2
repo.yml
@ -12,7 +12,7 @@ upstream:
|
||||
url: 'https://github.com/balena-io-modules/balena-preload'
|
||||
- repo: 'etcher-sdk'
|
||||
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
||||
- repo: 'balena-compose'
|
||||
- repo: '@balena/compose'
|
||||
url: 'https://github.com/balena-io-modules/balena-compose'
|
||||
- repo: 'docker-progress'
|
||||
url: 'https://github.com/balena-io-modules/docker-progress'
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
import { expect } from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import mock = require('mock-require');
|
||||
import * as mock from 'mock-require';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
|
@ -32,6 +32,7 @@ describe('balena config write', function () {
|
||||
[{ a: 'b' }, 'a', 2, { a: 2 }],
|
||||
[{ a: ['b'] }, 'a', 2, { a: 2 }],
|
||||
[{ a: ['b'] }, 'a.1', 'c', { a: ['b', 'c'] }],
|
||||
// eslint-disable-next-line no-sparse-arrays
|
||||
[{ a: ['b'] }, 'a.2', 'c', { a: ['b', , 'c'] }],
|
||||
[{ a: { '1': 'b' } }, 'a.1', 'c', { a: { '1': 'c' } }],
|
||||
[{ a: { '1': 'b' } }, 'a.2', 'c', { a: { '1': 'b', '2': 'c' } }],
|
||||
|
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { intVar } from '@balena/env-parsing';
|
||||
import type { Request as ReleaseRequest } from '@balena/compose/dist/release';
|
||||
import { expect } from 'chai';
|
||||
import { promises as fs } from 'fs';
|
||||
@ -284,16 +285,25 @@ describe('balena deploy', function () {
|
||||
api.expectPostRelease({});
|
||||
docker.expectGetManifestBusybox();
|
||||
|
||||
let failedImagePatchRequests = 0;
|
||||
// Mock this patch HTTP request to return status code 500, in which case
|
||||
// the release status should be saved as "failed" rather than "success"
|
||||
const maxRequestRetries = intVar('BALENARCTEST_API_RETRY_MAX_ATTEMPTS');
|
||||
expect(
|
||||
maxRequestRetries,
|
||||
'BALENARCTEST_API_RETRY_MAX_ATTEMPTS must be >= 2 for this test',
|
||||
).to.be.greaterThanOrEqual(2);
|
||||
api.expectPatchImage({
|
||||
replyBody: errMsg,
|
||||
statusCode: 500,
|
||||
// b/c failed requests are retried
|
||||
times: maxRequestRetries,
|
||||
inspectRequest: (_uri, requestBody) => {
|
||||
const imageBody = requestBody as Partial<
|
||||
import('@balena/compose/dist/release/models').ImageModel
|
||||
>;
|
||||
expect(imageBody.status).to.equal('success');
|
||||
failedImagePatchRequests++;
|
||||
},
|
||||
});
|
||||
// Check that the CLI patches the release with status="failed"
|
||||
@ -324,13 +334,90 @@ describe('balena deploy', function () {
|
||||
responseCode: 200,
|
||||
services: ['main'],
|
||||
});
|
||||
expect(failedImagePatchRequests).to.equal(maxRequestRetries);
|
||||
} finally {
|
||||
await switchSentry(sentryStatus);
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error claims restore does not exist
|
||||
process.exit.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('should create the expected --build tar stream after retrying failing OData requests (single container)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
'src/.dockerignore': { fileSize: 16, type: 'file' },
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
'src/windows-crlf.sh': {
|
||||
fileSize: isWindows ? 68 : 70,
|
||||
testStream: isWindows ? expectStreamNoCRLF : undefined,
|
||||
type: 'file',
|
||||
},
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST.json';
|
||||
const responseBody = await fs.readFile(
|
||||
path.join(dockerResponsePath, responseFilename),
|
||||
'utf8',
|
||||
);
|
||||
const expectedResponseLines = [
|
||||
...commonResponseLines[responseFilename],
|
||||
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
|
||||
`[Info] Creating default composition with source: "${projectPath}"`,
|
||||
...getDockerignoreWarn1(
|
||||
[path.join(projectPath, 'src', '.dockerignore')],
|
||||
'deploy',
|
||||
),
|
||||
];
|
||||
if (isWindows) {
|
||||
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
||||
expectedResponseLines.push(
|
||||
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
|
||||
);
|
||||
}
|
||||
|
||||
api.expectPostRelease({});
|
||||
docker.expectGetManifestBusybox();
|
||||
|
||||
const maxRequestRetries = intVar('BALENARCTEST_API_RETRY_MAX_ATTEMPTS');
|
||||
expect(
|
||||
maxRequestRetries,
|
||||
'BALENARCTEST_API_RETRY_MAX_ATTEMPTS must be >= 2 for this test',
|
||||
).to.be.greaterThanOrEqual(2);
|
||||
let failedImagePatchRequests = 0;
|
||||
let succesfullImagePatchRequests = 0;
|
||||
api
|
||||
.optPatch(/^\/v6\/image($|[(?])/, { times: maxRequestRetries })
|
||||
.reply((_uri, requestBody) => {
|
||||
const imageBody = requestBody as Partial<
|
||||
import('@balena/compose/dist/release/models').ImageModel
|
||||
>;
|
||||
expect(imageBody.status).to.equal('success');
|
||||
if (failedImagePatchRequests < maxRequestRetries - 1) {
|
||||
failedImagePatchRequests++;
|
||||
return [500, 'Patch Image Error'];
|
||||
}
|
||||
succesfullImagePatchRequests++;
|
||||
return [200, 'OK'];
|
||||
});
|
||||
api.expectPatchRelease({});
|
||||
api.expectPostImageLabel();
|
||||
|
||||
await testDockerBuildStream({
|
||||
commandLine: `deploy testApp --build --source ${projectPath}`,
|
||||
dockerMock: docker,
|
||||
expectedFilesByService: { main: expectedFiles },
|
||||
expectedQueryParamsByService: { main: commonQueryParams },
|
||||
expectedResponseLines,
|
||||
projectPath,
|
||||
responseBody,
|
||||
responseCode: 200,
|
||||
services: ['main'],
|
||||
});
|
||||
expect(failedImagePatchRequests).to.equal(maxRequestRetries - 1);
|
||||
expect(succesfullImagePatchRequests).to.equal(1);
|
||||
});
|
||||
|
||||
it('should create the expected tar stream (docker-compose, --multi-dockerignore)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
||||
const service1Dockerfile = (
|
||||
|
@ -107,4 +107,19 @@ describe('balena device', function () {
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
|
||||
});
|
||||
|
||||
it('outputs device as JSON with the -j/--json flag', async () => {
|
||||
api.scope
|
||||
.get(/^\/v6\/device\?.+&\$expand=device_tag\(\$select=tag_key,value\)/)
|
||||
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
const { out, err } = await runCommand('device 27fda508c --json');
|
||||
expect(err).to.be.empty;
|
||||
const json = JSON.parse(out.join(''));
|
||||
expect(json.device_name).to.equal('sparkling-wood');
|
||||
expect(json.belongs_to__application[0].app_name).to.equal('test app');
|
||||
expect(json.device_tag[0].tag_key).to.equal('example');
|
||||
});
|
||||
});
|
||||
|
57
tests/commands/env/add.spec.ts
vendored
57
tests/commands/env/add.spec.ts
vendored
@ -23,6 +23,8 @@ import { runCommand } from '../../helpers';
|
||||
describe('balena env add', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
const fullUUID = 'f63fd7d7812c34c4c14ae023fdff05f5';
|
||||
|
||||
beforeEach(() => {
|
||||
api = new BalenaAPIMock();
|
||||
api.expectGetWhoAmI({ optional: true, persist: true });
|
||||
@ -35,7 +37,6 @@ describe('balena env add', function () {
|
||||
});
|
||||
|
||||
it('should successfully add an environment variable', async () => {
|
||||
const fullUUID = 'f63fd7d7812c34c4c14ae023fdff05f5';
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetConfigVars();
|
||||
api.scope
|
||||
@ -47,4 +48,58 @@ describe('balena env add', function () {
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
it('should reject adding an environment variable w/o specifying a value when the process environment does not have an env var with the same name defined', async () => {
|
||||
// make sure that that there is no such env var defined atm
|
||||
delete process.env.TEST_ENV_VAR_ADD_NO_VALUE_REJECTED;
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`env add TEST_ENV_VAR_ADD_NO_VALUE_REJECTED -d ${fullUUID}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
// Depending the context that the test was running , this was emiting either 1 or 2 '\n'
|
||||
expect(err.join('')).to.be.oneOf([
|
||||
'Value not found for environment variable: TEST_ENV_VAR_ADD_NO_VALUE_REJECTED\n',
|
||||
'Value not found for environment variable: TEST_ENV_VAR_ADD_NO_VALUE_REJECTED\n\n',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should successfully add an environment variable w/o specifying a value when the process environment has an env var with the same name defined', async () => {
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetConfigVars();
|
||||
api.scope
|
||||
.post(/^\/v\d+\/device_environment_variable($|\?)/)
|
||||
.reply(200, 'OK');
|
||||
|
||||
process.env.TEST_ENV_VAR_ADD_NO_VALUE = '4';
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`env add TEST_ENV_VAR_ADD_NO_VALUE -d ${fullUUID}`,
|
||||
);
|
||||
|
||||
delete process.env.TEST_ENV_VAR_ADD_NO_VALUE;
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.be.oneOf([
|
||||
' › Warning: Using TEST_ENV_VAR_ADD_NO_VALUE=4 from CLI process environment\n',
|
||||
// emitted when running on the windows instance
|
||||
' » Warning: Using TEST_ENV_VAR_ADD_NO_VALUE=4 from CLI process environment\n',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should successfully add an environment variable w/ empty string as a value', async () => {
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.expectGetConfigVars();
|
||||
api.scope
|
||||
.post(/^\/v\d+\/device_environment_variable($|\?)/)
|
||||
.reply(200, 'OK');
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`env add TEST_EMPTY_STRING '' -d ${fullUUID}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
});
|
||||
|
@ -56,6 +56,16 @@ describe('balena release', function () {
|
||||
expect(lines[5]).to.be.equal('main:');
|
||||
});
|
||||
|
||||
it('should print version information as JSON with the the -j/--json flag', async () => {
|
||||
api.expectGetRelease();
|
||||
const { err, out } = await runCommand('release 27fda508c --json');
|
||||
expect(err).to.be.empty;
|
||||
const json = JSON.parse(out.join(''));
|
||||
expect(json.commit).to.equal('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
expect(json.release_tag[0].tag_key).to.equal('testtag1');
|
||||
expect(json.composition.services.main.network_mode).to.equal('host');
|
||||
});
|
||||
|
||||
it('should list releases', async () => {
|
||||
api.expectGetRelease();
|
||||
api.expectGetApplication();
|
||||
@ -65,4 +75,17 @@ describe('balena release', function () {
|
||||
expect(lines[1]).to.contain('142334');
|
||||
expect(lines[1]).to.contain('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
});
|
||||
|
||||
it('should list releases as JSON with the -j/--json flag', async () => {
|
||||
api.expectGetRelease();
|
||||
api.expectGetApplication();
|
||||
const { err, out } = await runCommand('releases someapp --json');
|
||||
expect(err).to.be.empty;
|
||||
const json = JSON.parse(out.join(''));
|
||||
expect(json[0].commit).to.equal('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
expect(json[0].contains__image[0].image[0].start_timestamp).to.equal(
|
||||
'2020-01-04T01:13:08.583Z',
|
||||
);
|
||||
expect(json[0].__metadata.uri).to.equal('/resin/release(@id)?@id=142334');
|
||||
});
|
||||
});
|
||||
|
72
tests/commands/tag/set.spec.ts
Normal file
72
tests/commands/tag/set.spec.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019-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 { expect } from 'chai';
|
||||
|
||||
import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
import { runCommand } from '../../helpers';
|
||||
|
||||
describe('balena tag set', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
const fullUUID = 'f63fd7d7812c34c4c14ae023fdff05f5';
|
||||
|
||||
beforeEach(() => {
|
||||
api = new BalenaAPIMock();
|
||||
api.expectGetWhoAmI({ optional: true, persist: true });
|
||||
api.expectGetMixpanel({ optional: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Check all expected api calls have been made and clean up.
|
||||
api.done();
|
||||
});
|
||||
|
||||
it('should successfully set a tag', async () => {
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.scope.post(/^\/v\d+\/device_tag($|\?)/).reply(200, 'OK');
|
||||
|
||||
const { out, err } = await runCommand(`tag set TEST 1 -d ${fullUUID}`);
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
it('should successfully set a tag w/o specifying a value', async () => {
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.scope.post(/^\/v\d+\/device_tag($|\?)/).reply(200, 'OK');
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`tag set TEST_NO_VALUE -d ${fullUUID}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
it('should successfully set a tag w/ empty string as a value', async () => {
|
||||
api.expectGetDevice({ fullUUID });
|
||||
api.scope.post(/^\/v\d+\/device_tag($|\?)/).reply(200, 'OK');
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`tag set TEST_EMPTY_STRING '' -d ${fullUUID}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal('');
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
});
|
@ -26,6 +26,11 @@ process.env.BALENARC_NO_SENTRY = '1';
|
||||
// Like the global `--unsupported` flag
|
||||
process.env.BALENARC_UNSUPPORTED = '1';
|
||||
|
||||
// Reduce the api request retry limits to keep the tests fast.
|
||||
process.env.BALENARCTEST_API_RETRY_MIN_DELAY_MS = '100';
|
||||
process.env.BALENARCTEST_API_RETRY_MAX_DELAY_MS = '1000';
|
||||
process.env.BALENARCTEST_API_RETRY_MAX_ATTEMPTS = '2';
|
||||
|
||||
import * as tmp from 'tmp';
|
||||
tmp.setGracefulCleanup();
|
||||
// Use a temporary dir for tests data
|
||||
|
@ -218,7 +218,7 @@ export async function testDockerBuildStream(o: {
|
||||
}
|
||||
if (o.expectedExitCode != null) {
|
||||
if (process.env.BALENA_CLI_TEST_TYPE !== 'standalone') {
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error claims the typing doesn't match
|
||||
sinon.assert.calledWith(process.exit);
|
||||
}
|
||||
expect(o.expectedExitCode).to.equal(exitCode);
|
||||
|
@ -48,7 +48,7 @@ describe('handleError() function', () => {
|
||||
'printExpectedErrorMessage',
|
||||
);
|
||||
captureException = sinon.stub();
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error TODO: get explanation for why this ts-expect-error is necessary
|
||||
sandbox.stub(ErrorsModule, 'getSentry').resolves({ captureException });
|
||||
processExit = sandbox.stub(process, 'exit');
|
||||
|
||||
@ -131,7 +131,6 @@ describe('handleError() function', () => {
|
||||
const messagesToMatch = [
|
||||
'Missing 1 required argument', // oclif
|
||||
'Missing 2 required arguments', // oclif
|
||||
'Missing required flag', // oclif
|
||||
'Unexpected argument', // oclif
|
||||
'Unexpected arguments', // oclif
|
||||
'to be one of', // oclif
|
||||
|
@ -106,7 +106,7 @@ async function runCommandInProcess(cmd: string): Promise<TestOutput> {
|
||||
|
||||
try {
|
||||
await balenaCLI.run(preArgs.concat(cmd.split(' ').filter((c) => c)), {
|
||||
configPath: path.resolve(__dirname, '..'),
|
||||
dir: path.resolve(__dirname, '..'),
|
||||
noFlush: true,
|
||||
});
|
||||
} finally {
|
||||
@ -268,6 +268,7 @@ export function cleanOutput(
|
||||
* coded from observation of a few samples only, and may not cover all cases.
|
||||
*/
|
||||
export function monochrome(text: string): string {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return text.replace(/\u001b\[\??(\d+;)*\d+[a-zA-Z]\r?/g, '');
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,13 @@ export class BalenaAPIMock extends NockMock {
|
||||
notFound = false,
|
||||
optional = false,
|
||||
persist = false,
|
||||
times = undefined as number | undefined,
|
||||
expandArchitecture = false,
|
||||
} = {}) {
|
||||
const interceptor = this.optGet(/^\/v6\/application($|[(?])/, {
|
||||
optional,
|
||||
persist,
|
||||
times,
|
||||
});
|
||||
if (notFound) {
|
||||
interceptor.reply(200, { d: [] });
|
||||
@ -105,10 +107,12 @@ export class BalenaAPIMock extends NockMock {
|
||||
notFound = false,
|
||||
optional = false,
|
||||
persist = false,
|
||||
times = undefined as number | undefined,
|
||||
} = {}) {
|
||||
const interceptor = this.optGet(/^\/v6\/release($|[(?])/, {
|
||||
persist,
|
||||
optional,
|
||||
times,
|
||||
});
|
||||
if (notFound) {
|
||||
interceptor.reply(200, { d: [] });
|
||||
@ -133,8 +137,9 @@ export class BalenaAPIMock extends NockMock {
|
||||
inspectRequest = this.inspectNoOp,
|
||||
optional = false,
|
||||
persist = false,
|
||||
times = undefined as number | undefined,
|
||||
}) {
|
||||
this.optPatch(/^\/v6\/release($|[(?])/, { optional, persist }).reply(
|
||||
this.optPatch(/^\/v6\/release($|[(?])/, { optional, persist, times }).reply(
|
||||
statusCode,
|
||||
this.getInspectedReplyBodyFunction(inspectRequest, replyBody),
|
||||
);
|
||||
@ -148,8 +153,9 @@ export class BalenaAPIMock extends NockMock {
|
||||
inspectRequest = this.inspectNoOp,
|
||||
optional = false,
|
||||
persist = false,
|
||||
times = undefined as number | undefined,
|
||||
}) {
|
||||
this.optPost(/^\/v6\/release($|[(?])/, { optional, persist }).reply(
|
||||
this.optPost(/^\/v6\/release($|[(?])/, { optional, persist, times }).reply(
|
||||
statusCode,
|
||||
this.getInspectedReplyFileFunction(
|
||||
inspectRequest,
|
||||
@ -167,8 +173,9 @@ export class BalenaAPIMock extends NockMock {
|
||||
inspectRequest = this.inspectNoOp,
|
||||
optional = false,
|
||||
persist = false,
|
||||
times = undefined as number | undefined,
|
||||
}) {
|
||||
this.optPatch(/^\/v6\/image($|[(?])/, { optional, persist }).reply(
|
||||
this.optPatch(/^\/v6\/image($|[(?])/, { optional, persist, times }).reply(
|
||||
statusCode,
|
||||
this.getInspectedReplyBodyFunction(inspectRequest, replyBody),
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user