mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
43 Commits
v13.1.0
...
output-fra
Author | SHA1 | Date | |
---|---|---|---|
ab1d8aa6ba | |||
d2330f9ed1 | |||
cc19b00998 | |||
ed5ac75a10 | |||
465b8a1b5e | |||
eccadbdcb9 | |||
31eb734af1 | |||
fa7b59d64f | |||
1e42bfa0d5 | |||
5464e550e7 | |||
c0f27a663d | |||
d1c61c62ab | |||
a9691bff57 | |||
f5d09a43cd | |||
d11e547e11 | |||
bd462aee02 | |||
f633c0468b | |||
e4f61a1242 | |||
96142a002e | |||
6b9a5cd89c | |||
ba2d3d60ec | |||
d1e66bc1a5 | |||
58799915a9 | |||
5f2d55f569 | |||
8d6e51391c | |||
8454b02988 | |||
879d98ef98 | |||
c4e317a290 | |||
7ca4d2d720 | |||
e1e88ec56d | |||
33f7fa3829 | |||
3d516e7c5f | |||
a8507508b7 | |||
008972b3d3 | |||
92b86330a0 | |||
2563c07c6a | |||
1d4b949cf3 | |||
d17e02a930 | |||
a355cbaa79 | |||
bd021c0a2d | |||
a80f676804 | |||
f723c58089 | |||
e27a4e2e31 |
File diff suppressed because it is too large
Load Diff
282
CHANGELOG.md
282
CHANGELOG.md
@ -4,6 +4,288 @@ 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/).
|
||||
|
||||
## 13.1.13 - 2022-02-10
|
||||
|
||||
* Drop unused awaitDevice utility function [Lucian Buzzo]
|
||||
|
||||
## 13.1.12 - 2022-02-09
|
||||
|
||||
|
||||
<details>
|
||||
<summary> preload: Stop using the deprecated /device-types/v1 API endpoints [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-preload-12.0.0 - 2022-01-27
|
||||
>
|
||||
> * Improve types [Thodoris Greasidis]
|
||||
> * Stop relying on the /device-types/v1 endpoints [Thodoris Greasidis]
|
||||
> * Bump TypeScript to v4.5 [Thodoris Greasidis]
|
||||
>
|
||||
> <details>
|
||||
> <summary> Bump balena-sdk to v16 [Thodoris Greasidis] </summary>
|
||||
>
|
||||
>> #### balena-sdk-16.0.0 - 2021-11-28
|
||||
>>
|
||||
>> * **BREAKING**: Merge the hostApp model into the OS model [Thodoris Greasidis]
|
||||
>> * **BREAKING** Drop os.getSupportedVersions() method in favor of hostapp.getAvailableOsVersions() [Thodoris Greasidis]
|
||||
>> * os.getMaxSatisfyingVersion: Add optional param to choose OS line type [Thodoris Greasidis]
|
||||
>> * os.getMaxSatisfyingVersion: Include ESR versions [Thodoris Greasidis]
|
||||
>> * os.getMaxSatisfyingVersion: Switch to use hostApps [Thodoris Greasidis]
|
||||
>> * hostapp.getAvailableOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||
>> * hostapp.getAllOsVersions: Add single device type argument overload [Thodoris Greasidis]
|
||||
>> * models.hostapp: Add a getAvailableOsVersions() convenience method [Thodoris Greasidis]
|
||||
>> * Support optional extra PineOptions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||
>> * **BREAKING** Include invalidated versions in hostapp.getAllOsVersions() [Thodoris Greasidis]
|
||||
>> * models/application: Add getDirectlyAccessible & getAllDirectlyAccessible [Thodoris Greasidis]
|
||||
>> * application.get: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||
>> * application.getAll: Add 'directly_accessible' convenience filter param [Thodoris Greasidis]
|
||||
>> * **BREAKING** Change application.getAll to include public apps [Thodoris Greasidis]
|
||||
>> * **BREAKING** Drop targeting/retrieving apps by name in favor of slugs [Thodoris Greasidis]
|
||||
>> * Bump minimum supported Typescript to v4.5.2 [Thodoris Greasidis]
|
||||
>> * **BREAKING**: Stop actively supporting node 10 [Thodoris Greasidis]
|
||||
>> * **BREAKING** Drop application.getAllWithDeviceServiceDetails() [Thodoris Greasidis]
|
||||
>> * **BREAKING** Change apiKey.getAll() to return all key variants [Thodoris Greasidis]
|
||||
>> * types: Drop is_in_local_mode from the Device model [Thodoris Greasidis]
|
||||
>> * types: Drop user__is_member_of__application in favor of the term form [Thodoris Greasidis]
|
||||
>> * typings: Drop Subscription's discounts__plan_addon property [Thodoris Greasidis]
|
||||
>> * typings: Stop extending the JWTUser type in the User model [Thodoris Greasidis]
|
||||
>> * models/config: Change the BETA device type state to NEW [Thodoris Greasidis]
|
||||
>> * typings: Drop the PineWithSelectOnGet type [Thodoris Greasidis]
|
||||
>> * Remove my_application from the supported resources [Thodoris Greasidis]
|
||||
>> * typings: Properly type some Device properties [Thodoris Greasidis]
|
||||
>> * typings: Drop the DeviceWithImageInstalls type [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.59.2 - 2021-11-28
|
||||
>>
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> Update balena-request to 11.5.0 [Thodoris Greasidis] </summary>
|
||||
>>
|
||||
>>> ##### balena-request-11.5.0 - 2021-11-28
|
||||
>>>
|
||||
>>> * Convert tests to JavaScript and drop coffeescript [Thodoris Greasidis]
|
||||
>>> * Fix the jsdoc generation [Thodoris Greasidis]
|
||||
>>> * Convert to typescript and publish typings [Thodoris Greasidis]
|
||||
>>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-15.59.1 - 2021-11-28
|
||||
>>
|
||||
>> * Fix the typings of the Image contract field [Thodoris Greasidis]
|
||||
>> * Fix the typings for the Release contract field [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.59.0 - 2021-11-24
|
||||
>>
|
||||
>> * Add release setIsInvalidated function [Matthew Yarmolinsky]
|
||||
>>
|
||||
>> #### balena-sdk-15.58.1 - 2021-11-17
|
||||
>>
|
||||
>> * Update typescript to 4.5.2 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.58.0 - 2021-11-16
|
||||
>>
|
||||
>> * models/release: Add note() method [Thodoris Greasidis]
|
||||
>> * typings: Add the release.invalidation_reason property [Thodoris Greasidis]
|
||||
>> * typings: Add the release.note property [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.57.2 - 2021-11-15
|
||||
>>
|
||||
>> * tests/logs: Increase the wait time for retrieving the subscribed logs [Thodoris Greasidis]
|
||||
>> * tests/logs: Refactor to async-await [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.57.1 - 2021-11-11
|
||||
>>
|
||||
>> * typings: Fix $filters for resources with non numeric ids [Thodoris Greasidis]
|
||||
>> * typings: Add application.can_use__application_as_host ReverseNavigation [Thodoris Greasidis]
|
||||
>> * Add missing apiKey.getDeviceApiKeysByDevice docs [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.57.0 - 2021-11-05
|
||||
>>
|
||||
>> * models/api-key: Change update() & revoke() to work with all key variants [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.56.0 - 2021-11-04
|
||||
>>
|
||||
>> * models/apiKey: Add getDeviceApiKeysByDevice() method [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.55.0 - 2021-11-01
|
||||
>>
|
||||
>> * typings: Add the release.raw_version property [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.54.2 - 2021-10-25
|
||||
>>
|
||||
>> * application/create: Rely on the hostApps for detecting discontinued DTs [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.54.1 - 2021-10-22
|
||||
>>
|
||||
>> * tests/device: Async-await conversions & abstraction on multi-field tests [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.54.0 - 2021-10-20
|
||||
>>
|
||||
>> * tests: Register devices in chunks of 10 to avoid uuid conflicts in node [Thodoris Greasidis]
|
||||
>> * Add known issue check on release isReccomanded logic [JSReds]
|
||||
>> * Add known_issue_list to hostApp.getOsVersions() [JSReds]
|
||||
>>
|
||||
>> #### balena-sdk-15.53.0 - 2021-10-07
|
||||
>>
|
||||
>> * Add support for batch device supervisor updates [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.52.0 - 2021-10-06
|
||||
>>
|
||||
>> * Add support for batch device pinning to release [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.51.4 - 2021-09-28
|
||||
>>
|
||||
>> * auth.isLoggedIn: Treat BalenaExpiredToken errors as logged out indicator [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.51.3 - 2021-09-28
|
||||
>>
|
||||
>> * Convert application spec to TypeScript [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.51.2 - 2021-09-28
|
||||
>>
|
||||
>> * application.trackLatestRelease: Fix using draft/invalidated releases [Thodoris Greasidis]
|
||||
>> * application.isTrackingLatestRelease: Exclude draft&invalidated releases [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.51.1 - 2021-09-20
|
||||
>>
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> Update balena-request to v11.4.2 [Kyle Harding] </summary>
|
||||
>>
|
||||
>>> ##### balena-request-11.4.2 - 2021-09-20
|
||||
>>>
|
||||
>>> * Allow overriding the default zlib flush setting [Kyle Harding]
|
||||
>>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-15.51.0 - 2021-09-16
|
||||
>>
|
||||
>> * os.getConfig: Add typings for the provisioningKeyName option [Nitish Agarwal]
|
||||
>>
|
||||
>> #### balena-sdk-15.50.1 - 2021-09-13
|
||||
>>
|
||||
>> * models/os: Always first normalize the device type slug [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.50.0 - 2021-09-10
|
||||
>>
|
||||
>> * Add release.finalize to promote draft releases to final [toochevere]
|
||||
>>
|
||||
>> #### balena-sdk-15.49.1 - 2021-09-10
|
||||
>>
|
||||
>> * typings: Drop the v5-model-only application_type.is_host_os [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.49.0 - 2021-09-06
|
||||
>>
|
||||
>> * os.getSupportedOsUpdateVersions: Use the hostApp releases [Thodoris Greasidis]
|
||||
>> * os.download: Use the hostApp for finding the latest release [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.48.3 - 2021-08-27
|
||||
>>
|
||||
>>
|
||||
>> <details>
|
||||
>> <summary> Update balena-request to 11.4.1 [Kyle Harding] </summary>
|
||||
>>
|
||||
>>> ##### balena-request-11.4.1 - 2021-08-27
|
||||
>>>
|
||||
>>> * Allow more lenient gzip decompression [Kyle Harding]
|
||||
>>>
|
||||
>> </details>
|
||||
>>
|
||||
>>
|
||||
>> #### balena-sdk-15.48.2 - 2021-08-27
|
||||
>>
|
||||
>> * Improve hostapp.getAllOsVersions performance & reduce fetched data [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.48.1 - 2021-08-27
|
||||
>>
|
||||
>> * Update typescript to 4.4.2 [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.48.0 - 2021-08-15
|
||||
>>
|
||||
>> * Deprecate the release.release_version property [Thodoris Greasidis]
|
||||
>> * typings: Add the release versioning properties [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.47.1 - 2021-08-10
|
||||
>>
|
||||
>> * Run browser tests using the minified browser bundle [Thodoris Greasidis]
|
||||
>> * Move to uglify-js to fix const assignment bug in minified build [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.47.0 - 2021-08-09
|
||||
>>
|
||||
>> * typings: Add the release.is_final & is_finalized_at__date properties [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.46.1 - 2021-07-28
|
||||
>>
|
||||
>> * apiKey.getAll: Return only NamedUserApiKeys for backwards compatibility [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-sdk-15.46.0 - 2021-07-27
|
||||
>>
|
||||
>> * Add email verification & email request methods [Nitish Agarwal]
|
||||
>>
|
||||
>> #### balena-sdk-15.45.0 - 2021-07-26
|
||||
>>
|
||||
>> * Update generateProvisioningKey to include keyName [Nitish Agarwal]
|
||||
>>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
</details>
|
||||
|
||||
## 13.1.11 - 2022-01-19
|
||||
|
||||
* chore: lib/auth/utils.ts: Replace deprecated url.resolve, use async/await [Paulo Castro]
|
||||
* chore: Update @types/node to v12.20.42 [Paulo Castro]
|
||||
|
||||
## 13.1.10 - 2022-01-16
|
||||
|
||||
* Update docs and package.json re min Node.js supported version (12.8.0) [Paulo Castro]
|
||||
|
||||
## 13.1.9 - 2022-01-14
|
||||
|
||||
* Update packages in response to `colors` package issues [Scott Lowe]
|
||||
|
||||
## 13.1.8 - 2022-01-11
|
||||
|
||||
* local push: Fix "invalid character '/' looking for beginning of value" [Paulo Castro]
|
||||
* v14 preparations: Fix TypeError produced by 'npx oclif manifest' [Paulo Castro]
|
||||
|
||||
## 13.1.7 - 2022-01-06
|
||||
|
||||
* Update to pkg 5 [Pagan Gazzard]
|
||||
|
||||
## 13.1.6 - 2022-01-04
|
||||
|
||||
* Automation: enforce noImplicitAny for the type-checked javascript [Pagan Gazzard]
|
||||
|
||||
## 13.1.5 - 2022-01-04
|
||||
|
||||
* Build: switch from using inline-source via gulp to using it directly [Pagan Gazzard]
|
||||
|
||||
## 13.1.4 - 2022-01-03
|
||||
|
||||
* Update pkg [Pagan Gazzard]
|
||||
|
||||
## 13.1.3 - 2022-01-03
|
||||
|
||||
* Convert lib/utils/deploy-legacy to typescript [Pagan Gazzard]
|
||||
|
||||
## 13.1.2 - 2022-01-03
|
||||
|
||||
* Convert lib/utils/compose to typescript [Pagan Gazzard]
|
||||
|
||||
## 13.1.1 - 2021-12-30
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update dependencies [Pagan Gazzard] </summary>
|
||||
|
||||
> ### docker-progress-5.0.1 - 2021-09-22
|
||||
>
|
||||
> * Fix for bad progress values from some registries causing a crash [Paul Jonathan Zoulin]
|
||||
>
|
||||
</details>
|
||||
|
||||
## 13.1.0 - 2021-12-29
|
||||
|
||||
* os configure, config generate: Add '--dev' option for OS developmentMode [Paulo Castro]
|
||||
|
@ -78,7 +78,7 @@ 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 10 (min 10.20.0) or 12.**
|
||||
> **The balena CLI currently requires Node.js version 12 (min 12.8.0).**
|
||||
> **Versions 13 and later are not yet fully supported.**
|
||||
|
||||
### Install development tools
|
||||
|
@ -96,6 +96,7 @@ async function diffPkgOutput(pkgOut: string) {
|
||||
'> pkg@',
|
||||
'> Fetching base Node.js binaries',
|
||||
' fetched-',
|
||||
'prebuild-install WARN install No prebuilt binaries found',
|
||||
];
|
||||
const modulesRE =
|
||||
process.platform === 'win32'
|
||||
@ -322,6 +323,10 @@ async function signFilesForNotarization() {
|
||||
if (!item.stats.isFile()) {
|
||||
return;
|
||||
}
|
||||
if (path.basename(item.path).endsWith('.node.bak')) {
|
||||
console.log('Removing pkg .node.bak file', item.path);
|
||||
fs.unlinkSync(item.path);
|
||||
}
|
||||
if (
|
||||
path.basename(item.path).endsWith('.zip') &&
|
||||
path.dirname(item.path).includes('test')
|
||||
|
@ -6,6 +6,8 @@
|
||||
*
|
||||
* We don't `require('semver')` to allow this script to be run as a npm
|
||||
* 'preinstall' hook, at which point no dependencies have been installed.
|
||||
*
|
||||
* @param {string} version
|
||||
*/
|
||||
function parseSemver(version) {
|
||||
const match = /v?(\d+)\.(\d+).(\d+)/.exec(version);
|
||||
@ -16,6 +18,10 @@ function parseSemver(version) {
|
||||
return [parseInt(major, 10), parseInt(minor, 10), parseInt(patch, 10)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} v1
|
||||
* @param {string} v2
|
||||
*/
|
||||
function semverGte(v1, v2) {
|
||||
let v1Array = parseSemver(v1);
|
||||
let v2Array = parseSemver(v2);
|
||||
|
@ -36,8 +36,8 @@ const run = async (cmd: string) => {
|
||||
}
|
||||
resolve({ stdout, stderr });
|
||||
});
|
||||
p.stdout.pipe(process.stdout);
|
||||
p.stderr.pipe(process.stderr);
|
||||
p.stdout?.pipe(process.stdout);
|
||||
p.stderr?.pipe(process.stderr);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -117,7 +117,7 @@ export async function which(program: string): Promise<string> {
|
||||
*/
|
||||
export async function whichSpawn(
|
||||
programName: string,
|
||||
args?: string[],
|
||||
args: string[] = [],
|
||||
): Promise<void> {
|
||||
const program = await which(programName);
|
||||
let error: Error | undefined;
|
||||
|
@ -333,6 +333,30 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
## fleet <fleet>
|
||||
|
||||
Display detailed information about a single fleet.
|
||||
@ -362,6 +386,14 @@ fleet name, slug (preferred), or numeric ID (deprecated)
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
## fleet create <name>
|
||||
|
||||
Create a new balena fleet.
|
||||
@ -648,9 +680,29 @@ Examples:
|
||||
|
||||
fleet name, slug (preferred), or numeric ID (deprecated)
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
produce JSON output instead of tabular output
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
## devices supported
|
||||
|
||||
@ -669,9 +721,29 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
produce JSON output instead of tabular output
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
## device <uuid>
|
||||
|
||||
@ -689,6 +761,14 @@ the device uuid
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
## device deactivate <uuid>
|
||||
|
||||
Deactivate a device.
|
||||
@ -1152,6 +1232,14 @@ fleet name or slug (preferred)
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
## release <commitOrId>
|
||||
|
||||
|
||||
@ -1173,6 +1261,14 @@ the commit or ID of the release to get information
|
||||
|
||||
Return the release composition
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
## release finalize <commitOrId>
|
||||
|
||||
Finalize a release. Releases can be "draft" or "final", and this command
|
||||
@ -1271,9 +1367,29 @@ show configuration variables only
|
||||
|
||||
device UUID
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
produce JSON output instead of tabular output
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
#### -s, --service SERVICE
|
||||
|
||||
@ -1526,6 +1642,30 @@ device UUID
|
||||
|
||||
release id
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
## tag rm <tagKey>
|
||||
|
||||
Remove a tag from a fleet, device or release.
|
||||
@ -1694,6 +1834,30 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
#### --filter FILTER
|
||||
|
||||
filter results by substring matching of a given field, eg: --filter field=foo
|
||||
|
||||
#### --no-header
|
||||
|
||||
hide table header from output
|
||||
|
||||
#### --no-truncate
|
||||
|
||||
do not truncate output to fit screen
|
||||
|
||||
#### --sort SORT
|
||||
|
||||
field to sort by (prepend '-' for descending order)
|
||||
|
||||
## key <id>
|
||||
|
||||
Display a single SSH key registered in balenaCloud for the logged in user.
|
||||
@ -1710,6 +1874,14 @@ balenaCloud ID for the SSH key
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
## key add <name> [path]
|
||||
|
||||
Add an SSH key to the balenaCloud account of the logged in user.
|
||||
@ -2394,10 +2566,6 @@ the path to the config.json file to inject
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
@ -2418,10 +2586,6 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
@ -2449,10 +2613,6 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
@ -2491,10 +2651,6 @@ the value of the config parameter to write
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
@ -2843,6 +2999,14 @@ Examples:
|
||||
|
||||
### Options
|
||||
|
||||
#### --fields FIELDS
|
||||
|
||||
only show provided fields (comma-separated)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
output in json format
|
||||
|
||||
# Local
|
||||
|
||||
## local configure <target>
|
||||
|
15
gulpfile.js
15
gulpfile.js
@ -1,15 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const inlinesource = require('gulp-inline-source');
|
||||
|
||||
const OPTIONS = {
|
||||
files: {
|
||||
pages: 'lib/auth/pages/*.ejs',
|
||||
},
|
||||
};
|
||||
|
||||
gulp.task('pages', () =>
|
||||
gulp
|
||||
.src(OPTIONS.files.pages)
|
||||
.pipe(inlinesource())
|
||||
.pipe(gulp.dest('build/auth/pages')),
|
||||
);
|
@ -14,57 +14,44 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as url from 'url';
|
||||
import { getBalenaSdk } from '../utils/lazy';
|
||||
|
||||
/**
|
||||
* @summary Get dashboard CLI login URL
|
||||
* @function
|
||||
* @protected
|
||||
* Get dashboard CLI login URL
|
||||
*
|
||||
* @param {String} callbackUrl - callback url
|
||||
* @fulfil {String} - dashboard login url
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* utils.getDashboardLoginURL('http://127.0.0.1:3000').then (url) ->
|
||||
* console.log(url)
|
||||
* @param callbackUrl - Callback url, e.g. 'http://127.0.0.1:3000'
|
||||
* @returns Dashboard login URL, e.g.:
|
||||
* 'https://dashboard.balena-cloud.com/login/cli/http%253A%252F%252F127.0.0.1%253A59581%252Fauth'
|
||||
*/
|
||||
export const getDashboardLoginURL = (callbackUrl: string) => {
|
||||
export async function getDashboardLoginURL(
|
||||
callbackUrl: string,
|
||||
): Promise<string> {
|
||||
// Encode percentages signs from the escaped url
|
||||
// characters to avoid angular getting confused.
|
||||
callbackUrl = encodeURIComponent(callbackUrl).replace(/%/g, '%25');
|
||||
|
||||
return getBalenaSdk()
|
||||
.settings.get('dashboardUrl')
|
||||
.then((dashboardUrl) =>
|
||||
url.resolve(dashboardUrl, `/login/cli/${callbackUrl}`),
|
||||
);
|
||||
};
|
||||
const [{ URL }, dashboardUrl] = await Promise.all([
|
||||
import('url'),
|
||||
getBalenaSdk().settings.get('dashboardUrl'),
|
||||
]);
|
||||
return new URL(`/login/cli/${callbackUrl}`, dashboardUrl).href;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Log in using a token, but only if the token is valid
|
||||
* @function
|
||||
* @protected
|
||||
* Log in using a token, but only if the token is valid.
|
||||
*
|
||||
* @description
|
||||
* This function checks that the token is not only well-structured
|
||||
* but that it also authenticates with the server successfully.
|
||||
*
|
||||
* If authenticated, the token is persisted, if not then the previous
|
||||
* login state is restored.
|
||||
*
|
||||
* @param {String} token - session token or api key
|
||||
* @fulfil {Boolean} - whether the login was successful or not
|
||||
* @returns {Promise}
|
||||
*
|
||||
* utils.loginIfTokenValid('...').then (loggedIn) ->
|
||||
* if loggedIn
|
||||
* console.log('Token is valid!')
|
||||
* @param token - session token or api key
|
||||
* @returns whether the login was successful or not
|
||||
*/
|
||||
export const loginIfTokenValid = async (token: string): Promise<boolean> => {
|
||||
if (_.isEmpty(token?.trim())) {
|
||||
export async function loginIfTokenValid(token?: string): Promise<boolean> {
|
||||
token = (token || '').trim();
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
const balena = getBalenaSdk();
|
||||
@ -86,4 +73,4 @@ export const loginIfTokenValid = async (token: string): Promise<boolean> => {
|
||||
}
|
||||
}
|
||||
return isLoggedIn;
|
||||
};
|
||||
}
|
||||
|
@ -171,4 +171,5 @@ export default abstract class BalenaCommand extends Command {
|
||||
|
||||
protected outputMessage = output.outputMessage;
|
||||
protected outputData = output.outputData;
|
||||
protected printTitle = output.printTitle;
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export default class ConfigInjectCmd extends Command {
|
||||
public static usage = 'config inject <file>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceTypeIgnored,
|
||||
...cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
@ -47,7 +47,7 @@ export default class ConfigReadCmd extends Command {
|
||||
public static usage = 'config read';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceTypeIgnored,
|
||||
...cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
json: cf.json,
|
||||
|
@ -50,7 +50,7 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
public static usage = 'config reconfigure';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceTypeIgnored,
|
||||
...cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
advanced: flags.boolean({
|
||||
description: 'show advanced commands',
|
||||
|
@ -64,7 +64,7 @@ export default class ConfigWriteCmd extends Command {
|
||||
public static usage = 'config write <key> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceTypeIgnored,
|
||||
...cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,8 +22,10 @@ import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { tryAsInteger } from '../../utils/validation';
|
||||
|
||||
import type { Application, Release } from 'balena-sdk';
|
||||
import type { DataOutputOptions } from '../../framework';
|
||||
|
||||
import { isV14 } from '../../utils/version';
|
||||
|
||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
dashboard_url?: string;
|
||||
@ -42,7 +44,7 @@ interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
undervoltage_detected?: boolean;
|
||||
}
|
||||
|
||||
interface FlagsDef {
|
||||
interface FlagsDef extends DataOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -71,13 +73,16 @@ export default class DeviceCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
...(isV14() ? cf.dataOutputFlags : {}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
public static primary = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(DeviceCmd);
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
DeviceCmd,
|
||||
);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
@ -163,37 +168,52 @@ export default class DeviceCmd extends Command {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
getVisuals().table.vertical(device, [
|
||||
`$${device.device_name}$`,
|
||||
'id',
|
||||
'device_type',
|
||||
'status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'public_address',
|
||||
'mac_address',
|
||||
'fleet',
|
||||
'last_seen',
|
||||
'uuid',
|
||||
'commit',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'dashboard_url',
|
||||
'cpu_usage_percent',
|
||||
'cpu_temp_c',
|
||||
'cpu_id',
|
||||
'memory_usage_mb',
|
||||
'memory_total_mb',
|
||||
'memory_usage_percent',
|
||||
'storage_block_device',
|
||||
'storage_usage_mb',
|
||||
'storage_total_mb',
|
||||
'storage_usage_percent',
|
||||
'undervoltage_detected',
|
||||
]),
|
||||
);
|
||||
const outputFields = [
|
||||
'device_name',
|
||||
'id',
|
||||
'device_type',
|
||||
'status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'public_address',
|
||||
'mac_address',
|
||||
'fleet',
|
||||
'last_seen',
|
||||
'uuid',
|
||||
'commit',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'dashboard_url',
|
||||
'cpu_usage_percent',
|
||||
'cpu_temp_c',
|
||||
'cpu_id',
|
||||
'memory_usage_mb',
|
||||
'memory_total_mb',
|
||||
'memory_usage_percent',
|
||||
'storage_block_device',
|
||||
'storage_usage_mb',
|
||||
'storage_total_mb',
|
||||
'storage_usage_percent',
|
||||
'undervoltage_detected',
|
||||
];
|
||||
|
||||
if (isV14()) {
|
||||
await this.outputData(device, outputFields, {
|
||||
...options,
|
||||
hideNullOrUndefinedValues: true,
|
||||
titleField: 'device_name',
|
||||
});
|
||||
} else {
|
||||
// Old output implementation
|
||||
outputFields.unshift(`$${device.device_name}$`);
|
||||
console.log(
|
||||
getVisuals().table.vertical(
|
||||
device,
|
||||
outputFields.filter((f) => f !== 'device_name'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,8 +21,10 @@ import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
|
||||
|
||||
import type { Application } from 'balena-sdk';
|
||||
import type { DataSetOutputOptions } from '../../framework';
|
||||
|
||||
import { isV14 } from '../../utils/version';
|
||||
|
||||
interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
dashboard_url?: string;
|
||||
@ -30,10 +32,10 @@ interface ExtendedDevice extends DeviceWithDeviceType {
|
||||
device_type?: string | null;
|
||||
}
|
||||
|
||||
interface FlagsDef {
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
fleet?: string;
|
||||
help: void;
|
||||
json: boolean;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export default class DevicesCmd extends Command {
|
||||
@ -58,12 +60,11 @@ export default class DevicesCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
fleet: cf.fleet,
|
||||
json: cf.json,
|
||||
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static primary = true;
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
@ -99,31 +100,52 @@ export default class DevicesCmd extends Command {
|
||||
return device;
|
||||
});
|
||||
|
||||
const fields = [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
||||
'device_type',
|
||||
'fleet',
|
||||
'status',
|
||||
'is_online',
|
||||
'supervisor_version',
|
||||
'os_version',
|
||||
'dashboard_url',
|
||||
];
|
||||
if (isV14()) {
|
||||
const outputFields = [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
||||
'device_type',
|
||||
'fleet',
|
||||
'status',
|
||||
'is_online',
|
||||
'supervisor_version',
|
||||
'os_version',
|
||||
'dashboard_url',
|
||||
];
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../../utils/helpers');
|
||||
const mapped = devices.map((device) => pickAndRename(device, fields));
|
||||
console.log(JSON.stringify(mapped, null, 4));
|
||||
await this.outputData(devices, outputFields, {
|
||||
...options,
|
||||
displayNullValuesAs: 'N/a',
|
||||
});
|
||||
} else {
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
devices.map((dev) => _.mapValues(dev, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
// Old output implementation
|
||||
const fields = [
|
||||
'id',
|
||||
'uuid',
|
||||
'device_name',
|
||||
'device_type',
|
||||
'fleet',
|
||||
'status',
|
||||
'is_online',
|
||||
'supervisor_version',
|
||||
'os_version',
|
||||
'dashboard_url',
|
||||
];
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../../utils/helpers');
|
||||
const mapped = devices.map((device) => pickAndRename(device, fields));
|
||||
console.log(JSON.stringify(mapped, null, 4));
|
||||
} else {
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
devices.map((dev) => _.mapValues(dev, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,12 +17,14 @@
|
||||
import { flags } from '@oclif/command';
|
||||
import * as _ from 'lodash';
|
||||
import Command from '../../command';
|
||||
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { CommandHelp } from '../../utils/oclif-utils';
|
||||
import type { DataSetOutputOptions } from '../../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
help: void;
|
||||
json?: boolean;
|
||||
}
|
||||
@ -51,10 +53,7 @@ export default class DevicesSupportedCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
json: flags.boolean({
|
||||
char: 'j',
|
||||
description: 'produce JSON output instead of tabular output',
|
||||
}),
|
||||
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||
};
|
||||
|
||||
public async run() {
|
||||
@ -70,7 +69,7 @@ export default class DevicesSupportedCmd extends Command {
|
||||
const configDTsBySlug = _.keyBy(configDTs, (dt) => dt.slug);
|
||||
interface DT {
|
||||
slug: string;
|
||||
aliases: string[];
|
||||
aliases: string[] | string;
|
||||
arch: string;
|
||||
name: string;
|
||||
}
|
||||
@ -84,19 +83,25 @@ export default class DevicesSupportedCmd extends Command {
|
||||
const dt: Partial<typeof dts[0]> = dtsBySlug[slug] || {};
|
||||
deviceTypes.push({
|
||||
slug,
|
||||
aliases: options.json ? aliases : [aliases.join(', ')],
|
||||
aliases: options.json ? aliases : aliases.join(', '),
|
||||
arch: (dt.is_of__cpu_architecture as any)?.[0]?.slug || 'n/a',
|
||||
name: dt.name || 'N/A',
|
||||
});
|
||||
}
|
||||
const fields = ['slug', 'aliases', 'arch', 'name'];
|
||||
deviceTypes = _.sortBy(deviceTypes, fields);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(deviceTypes, null, 4));
|
||||
|
||||
if (isV14()) {
|
||||
await this.outputData(deviceTypes, fields, options);
|
||||
} else {
|
||||
const visuals = getVisuals();
|
||||
const output = await visuals.table.horizontal(deviceTypes, fields);
|
||||
console.log(output);
|
||||
// Old output implementation
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(deviceTypes, null, 4));
|
||||
} else {
|
||||
const visuals = getVisuals();
|
||||
const output = await visuals.table.horizontal(deviceTypes, fields);
|
||||
console.log(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,12 +22,15 @@ import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
fleet?: string;
|
||||
config: boolean;
|
||||
device?: string; // device UUID
|
||||
json: boolean;
|
||||
json?: boolean;
|
||||
help: void;
|
||||
service?: string; // service name
|
||||
}
|
||||
@ -113,7 +116,7 @@ export default class EnvsCmd extends Command {
|
||||
}),
|
||||
device: { ...cf.device, exclusive: ['fleet'] },
|
||||
help: cf.help,
|
||||
json: cf.json,
|
||||
...(isV14() ? cf.dataSetOutputFlags : { json: cf.json }),
|
||||
service: { ...cf.service, exclusive: ['config'] },
|
||||
};
|
||||
|
||||
@ -181,24 +184,59 @@ export default class EnvsCmd extends Command {
|
||||
return i;
|
||||
});
|
||||
|
||||
if (options.device) {
|
||||
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
|
||||
}
|
||||
if (!options.config) {
|
||||
fields.push(options.json ? 'serviceName' : 'serviceName => SERVICE');
|
||||
}
|
||||
if (isV14()) {
|
||||
const results = [...varArray] as any;
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../utils/helpers');
|
||||
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
||||
this.log(JSON.stringify(mapped, null, 4));
|
||||
// Rename fields
|
||||
if (options.device) {
|
||||
if (options.json) {
|
||||
fields.push('deviceUUID');
|
||||
} else {
|
||||
results.forEach((r: any) => {
|
||||
r.device = r.deviceUUID;
|
||||
delete r.deviceUUID;
|
||||
});
|
||||
|
||||
fields.push('device');
|
||||
}
|
||||
}
|
||||
if (!options.config) {
|
||||
if (options.json) {
|
||||
fields.push('serviceName');
|
||||
} else {
|
||||
results.forEach((r: any) => {
|
||||
r.service = r.serviceName;
|
||||
delete r.serviceName;
|
||||
});
|
||||
fields.push('service');
|
||||
}
|
||||
}
|
||||
|
||||
await this.outputData(results, fields, {
|
||||
...options,
|
||||
sort: options.sort || 'name',
|
||||
});
|
||||
} else {
|
||||
this.log(
|
||||
getVisuals().table.horizontal(
|
||||
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
// Old output implementation
|
||||
if (options.device) {
|
||||
fields.push(options.json ? 'deviceUUID' : 'deviceUUID => DEVICE');
|
||||
}
|
||||
if (!options.config) {
|
||||
fields.push(options.json ? 'serviceName' : 'serviceName => SERVICE');
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
const { pickAndRename } = await import('../utils/helpers');
|
||||
const mapped = varArray.map((o) => pickAndRename(o, fields));
|
||||
this.log(JSON.stringify(mapped, null, 4));
|
||||
} else {
|
||||
this.log(
|
||||
getVisuals().table.horizontal(
|
||||
_.sortBy(varArray, (v: SDK.EnvironmentVariableBase) => v.name),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,10 +20,13 @@ import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||
import { parseAsInteger } from '../../utils/validation';
|
||||
import type { DataOutputOptions } from '../../framework';
|
||||
|
||||
import { isV14 } from '../../utils/version';
|
||||
|
||||
type IArg<T> = import('@oclif/parser').args.IArg<T>;
|
||||
|
||||
interface FlagsDef {
|
||||
interface FlagsDef extends DataOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -52,27 +55,52 @@ export default class KeyCmd extends Command {
|
||||
public static usage = 'key <id>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
...(isV14() ? cf.dataOutputFlags : {}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<{}, ArgsDef>(KeyCmd);
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
KeyCmd,
|
||||
);
|
||||
|
||||
const key = await getBalenaSdk().models.key.get(params.id);
|
||||
|
||||
// Use 'name' instead of 'title' to match dashboard.
|
||||
const displayKey = {
|
||||
id: key.id,
|
||||
name: key.title,
|
||||
};
|
||||
if (isV14()) {
|
||||
// Use 'name' instead of 'title' to match dashboard.
|
||||
const displayKey = {
|
||||
id: key.id,
|
||||
name: key.title,
|
||||
public_key: key.public_key,
|
||||
};
|
||||
|
||||
console.log(getVisuals().table.vertical(displayKey, ['id', 'name']));
|
||||
if (!options.json) {
|
||||
// Id is redundant, since user must have provided it in command call
|
||||
this.printTitle(displayKey.name);
|
||||
this.outputMessage(displayKey.public_key);
|
||||
} else {
|
||||
await this.outputData(
|
||||
displayKey,
|
||||
['id', 'name', 'public_key'],
|
||||
options,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Old output implementation
|
||||
// Use 'name' instead of 'title' to match dashboard.
|
||||
const displayKey = {
|
||||
id: key.id,
|
||||
name: key.title,
|
||||
};
|
||||
|
||||
// Since the public key string is long, it might
|
||||
// wrap to lines below, causing the table layout to break.
|
||||
// See https://github.com/balena-io/balena-cli/issues/151
|
||||
console.log('\n' + key.public_key);
|
||||
console.log(getVisuals().table.vertical(displayKey, ['id', 'name']));
|
||||
|
||||
// Since the public key string is long, it might
|
||||
// wrap to lines below, causing the table layout to break.
|
||||
// See https://github.com/balena-io/balena-cli/issues/151
|
||||
console.log('\n' + key.public_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2022 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -35,13 +38,14 @@ export default class KeysCmd extends Command {
|
||||
public static usage = 'keys';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
this.parse<FlagsDef, {}>(KeysCmd);
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(KeysCmd);
|
||||
|
||||
const keys = await getBalenaSdk().models.key.getAll();
|
||||
|
||||
@ -50,6 +54,12 @@ export default class KeysCmd extends Command {
|
||||
return { id: k.id, name: k.title };
|
||||
});
|
||||
|
||||
console.log(getVisuals().table.horizontal(displayKeys, ['id', 'name']));
|
||||
// Display
|
||||
if (isV14()) {
|
||||
await this.outputData(displayKeys, ['id', 'name'], options);
|
||||
} else {
|
||||
// Old output implementation
|
||||
console.log(getVisuals().table.horizontal(displayKeys, ['id', 'name']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2022 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -36,12 +39,13 @@ export default class OrgsCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
this.parse<FlagsDef, {}>(OrgsCmd);
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(OrgsCmd);
|
||||
|
||||
const { getOwnOrganizations } = await import('../utils/sdk');
|
||||
|
||||
@ -49,8 +53,13 @@ export default class OrgsCmd extends Command {
|
||||
const organizations = await getOwnOrganizations(getBalenaSdk());
|
||||
|
||||
// Display
|
||||
console.log(
|
||||
getVisuals().table.horizontal(organizations, ['name', 'handle']),
|
||||
);
|
||||
if (isV14()) {
|
||||
await this.outputData(organizations, ['name', 'handle'], options);
|
||||
} else {
|
||||
// Old output implementation
|
||||
console.log(
|
||||
getVisuals().table.horizontal(organizations, ['name', 'handle']),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,8 +22,11 @@ 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 type { DataOutputOptions } from '../../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../../utils/version';
|
||||
|
||||
interface FlagsDef extends DataOutputOptions {
|
||||
help: void;
|
||||
composition?: boolean;
|
||||
}
|
||||
@ -49,7 +52,9 @@ export default class ReleaseCmd extends Command {
|
||||
default: false,
|
||||
char: 'c',
|
||||
description: 'Return the release composition',
|
||||
exclusive: ['json', 'fields'],
|
||||
}),
|
||||
...(isV14() ? cf.dataOutputFlags : {}),
|
||||
};
|
||||
|
||||
public static args = [
|
||||
@ -68,29 +73,27 @@ export default class ReleaseCmd extends Command {
|
||||
ReleaseCmd,
|
||||
);
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
if (options.composition) {
|
||||
await this.showComposition(params.commitOrId, balena);
|
||||
await this.showComposition(params.commitOrId);
|
||||
} else {
|
||||
await this.showReleaseInfo(params.commitOrId, balena);
|
||||
await this.showReleaseInfo(params.commitOrId, options);
|
||||
}
|
||||
}
|
||||
|
||||
async showComposition(
|
||||
commitOrId: string | number,
|
||||
balena: BalenaSdk.BalenaSDK,
|
||||
) {
|
||||
const release = await balena.models.release.get(commitOrId, {
|
||||
async showComposition(commitOrId: string | number) {
|
||||
const release = await getBalenaSdk().models.release.get(commitOrId, {
|
||||
$select: 'composition',
|
||||
});
|
||||
|
||||
console.log(jsyaml.dump(release.composition));
|
||||
if (isV14()) {
|
||||
this.outputMessage(jsyaml.dump(release.composition));
|
||||
} else {
|
||||
// Old output implementation
|
||||
console.log(jsyaml.dump(release.composition));
|
||||
}
|
||||
}
|
||||
|
||||
async showReleaseInfo(
|
||||
commitOrId: string | number,
|
||||
balena: BalenaSdk.BalenaSDK,
|
||||
) {
|
||||
async showReleaseInfo(commitOrId: string | number, options: FlagsDef) {
|
||||
const fields: Array<keyof BalenaSdk.Release> = [
|
||||
'id',
|
||||
'commit',
|
||||
@ -103,7 +106,7 @@ export default class ReleaseCmd extends Command {
|
||||
'end_timestamp',
|
||||
];
|
||||
|
||||
const release = await balena.models.release.get(commitOrId, {
|
||||
const release = await getBalenaSdk().models.release.get(commitOrId, {
|
||||
$select: fields,
|
||||
$expand: {
|
||||
release_tag: {
|
||||
@ -116,13 +119,28 @@ export default class ReleaseCmd extends Command {
|
||||
.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;
|
||||
if (isV14()) {
|
||||
await this.outputData(
|
||||
{
|
||||
tags: tagStr,
|
||||
...release,
|
||||
},
|
||||
fields,
|
||||
{
|
||||
displayNullValuesAs: 'N/a',
|
||||
...options,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Old output implementation
|
||||
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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,8 +21,11 @@ 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 type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -43,6 +46,7 @@ export default class ReleasesCmd extends Command {
|
||||
public static usage = 'releases <fleet>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
...(isV14() ? cf.dataOutputFlags : {}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -57,7 +61,9 @@ export default class ReleasesCmd extends Command {
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(ReleasesCmd);
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
ReleasesCmd,
|
||||
);
|
||||
|
||||
const fields: Array<keyof BalenaSdk.Release> = [
|
||||
'id',
|
||||
@ -76,12 +82,20 @@ export default class ReleasesCmd extends Command {
|
||||
{ $select: fields },
|
||||
);
|
||||
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
if (isV14()) {
|
||||
await this.outputData(releases, fields, {
|
||||
displayNullValuesAs: 'N/a',
|
||||
...options,
|
||||
});
|
||||
} else {
|
||||
// Old output implementation
|
||||
const _ = await import('lodash');
|
||||
console.log(
|
||||
getVisuals().table.horizontal(
|
||||
releases.map((rel) => _.mapValues(rel, (val) => val ?? 'N/a')),
|
||||
fields,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,8 +19,11 @@ import { flags } from '@oclif/command';
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
||||
import type { DataOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -35,15 +38,27 @@ export default class SettingsCmd extends Command {
|
||||
public static usage = 'settings';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
...(isV14() ? cf.dataOutputFlags : {}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public async run() {
|
||||
this.parse<FlagsDef, {}>(SettingsCmd);
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(SettingsCmd);
|
||||
|
||||
const settings = await getBalenaSdk().settings.getAll();
|
||||
|
||||
const prettyjson = await import('prettyjson');
|
||||
console.log(prettyjson.render(settings));
|
||||
if (isV14()) {
|
||||
// Select all available fields for display
|
||||
const fields = Object.keys(settings);
|
||||
|
||||
await this.outputData(settings, fields, {
|
||||
noCapitalizeKeys: true,
|
||||
...options,
|
||||
});
|
||||
} else {
|
||||
// Old output implementation
|
||||
const prettyjson = await import('prettyjson');
|
||||
console.log(prettyjson.render(settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,8 +21,12 @@ import { ExpectedError } from '../errors';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { applicationIdInfo } from '../utils/messages';
|
||||
import type { ApplicationTag, DeviceTag, ReleaseTag } from 'balena-sdk';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
import { isV14 } from '../utils/version';
|
||||
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
fleet?: string;
|
||||
device?: string;
|
||||
release?: string;
|
||||
@ -61,6 +65,7 @@ export default class TagsCmd extends Command {
|
||||
...cf.release,
|
||||
exclusive: ['fleet', 'device'],
|
||||
},
|
||||
...(isV14() ? cf.dataSetOutputFlags : {}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -78,7 +83,7 @@ export default class TagsCmd extends Command {
|
||||
|
||||
const { tryAsInteger } = await import('../utils/validation');
|
||||
|
||||
let tags;
|
||||
let tags: ApplicationTag[] | DeviceTag[] | ReleaseTag[] = [];
|
||||
|
||||
if (options.fleet) {
|
||||
const { getFleetSlug } = await import('../utils/sdk');
|
||||
@ -103,11 +108,17 @@ export default class TagsCmd extends Command {
|
||||
tags = await balena.models.release.tags.getAllByRelease(releaseParam);
|
||||
}
|
||||
|
||||
if (!tags || tags.length === 0) {
|
||||
if (tags.length === 0 && !options.json) {
|
||||
// TODO: Later change to output message
|
||||
throw new ExpectedError('No tags found');
|
||||
}
|
||||
|
||||
console.log(getVisuals().table.horizontal(tags, ['tag_key', 'value']));
|
||||
if (isV14()) {
|
||||
await this.outputData(tags, ['tag_key', 'value'], options);
|
||||
} else {
|
||||
// Old output implementation
|
||||
console.log(getVisuals().table.horizontal(tags, ['tag_key', 'value']));
|
||||
}
|
||||
}
|
||||
|
||||
protected missingResourceMessage = stripIndent`
|
||||
|
@ -27,14 +27,7 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
// `@types/node` does not know about `options: { bigint?: boolean }`
|
||||
type statT = (
|
||||
fPath: string,
|
||||
options: { bigint?: boolean },
|
||||
) => fs.Stats | Promise<fs.Stats>;
|
||||
|
||||
// async stat does not work with pkg's internal `/snapshot` filesystem
|
||||
const stat: statT = process.pkg ? fs.statSync : fs.promises.stat;
|
||||
const stat = process.pkg ? fs.statSync : fs.promises.stat;
|
||||
|
||||
let fastBootStarted = false;
|
||||
|
||||
|
@ -1,26 +1,35 @@
|
||||
/*
|
||||
Copyright 2020 Balena
|
||||
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getCliUx, getChalk } from '../utils/lazy';
|
||||
|
||||
/**
|
||||
* Used to extend FlagsDef for commands that output single-record data.
|
||||
* Exposed to user in command options.
|
||||
*/
|
||||
export interface DataOutputOptions {
|
||||
fields?: string;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to extend FlagsDef for commands that output multi-record data.
|
||||
* Exposed to user in command options.
|
||||
*/
|
||||
export interface DataSetOutputOptions extends DataOutputOptions {
|
||||
filter?: string;
|
||||
'no-header'?: boolean;
|
||||
@ -28,6 +37,14 @@ export interface DataSetOutputOptions extends DataOutputOptions {
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
// Not exposed to user
|
||||
export interface InternalOutputOptions {
|
||||
displayNullValuesAs?: string;
|
||||
hideNullOrUndefinedValues?: boolean;
|
||||
titleField?: string;
|
||||
noCapitalizeKeys?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output message to STDERR
|
||||
*/
|
||||
@ -49,7 +66,7 @@ export function outputMessage(msg: string) {
|
||||
export async function outputData(
|
||||
data: any[] | {},
|
||||
fields: string[],
|
||||
options: DataOutputOptions | DataSetOutputOptions,
|
||||
options: (DataOutputOptions | DataSetOutputOptions) & InternalOutputOptions,
|
||||
) {
|
||||
if (Array.isArray(data)) {
|
||||
await outputDataSet(data, fields, options as DataSetOutputOptions);
|
||||
@ -68,7 +85,7 @@ export async function outputData(
|
||||
async function outputDataSet(
|
||||
data: any[],
|
||||
fields: string[],
|
||||
options: DataSetOutputOptions,
|
||||
options: DataSetOutputOptions & InternalOutputOptions,
|
||||
) {
|
||||
// Oclif expects fields to be specified in the format used in table headers (though lowercase)
|
||||
// By replacing underscores with spaces here, we can support both header format and actual field name
|
||||
@ -77,6 +94,12 @@ async function outputDataSet(
|
||||
options.filter = options.filter?.replace(/_/g, ' ');
|
||||
options.sort = options.sort?.replace(/_/g, ' ');
|
||||
|
||||
if (!options.json) {
|
||||
data = data.map((d) => {
|
||||
return processNullValues(d, options);
|
||||
});
|
||||
}
|
||||
|
||||
getCliUx().table(
|
||||
data,
|
||||
// Convert fields array to column object keys
|
||||
@ -97,7 +120,7 @@ async function outputDataSet(
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a single data object (like `resin-cli-visuals table.vertical`),
|
||||
* Outputs a single data object (similar to `resin-cli-visuals table.vertical`),
|
||||
* but supporting a subset of options from `cli-ux table` (--json and --fields)
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
@ -107,9 +130,9 @@ async function outputDataSet(
|
||||
async function outputDataItem(
|
||||
data: any,
|
||||
fields: string[],
|
||||
options: DataOutputOptions,
|
||||
options: DataOutputOptions & InternalOutputOptions,
|
||||
) {
|
||||
const outData: typeof data = {};
|
||||
let outData: typeof data = {};
|
||||
|
||||
// Convert comma separated list of fields in `options.fields` to array of correct format.
|
||||
// Note, user may have specified the true field name (e.g. `some_field`),
|
||||
@ -125,30 +148,83 @@ async function outputDataItem(
|
||||
}
|
||||
});
|
||||
|
||||
if (
|
||||
(options.displayNullValuesAs || options.hideNullOrUndefinedValues) &&
|
||||
!options.json
|
||||
) {
|
||||
outData = processNullValues(outData, options);
|
||||
}
|
||||
|
||||
if (options.json) {
|
||||
printLine(JSON.stringify(outData, undefined, 2));
|
||||
} else {
|
||||
const chalk = getChalk();
|
||||
const { capitalize } = await import('lodash');
|
||||
|
||||
// Find longest key, so we can align results
|
||||
const longestKeyLength = getLongestObjectKeyLength(outData);
|
||||
|
||||
if (options.titleField) {
|
||||
printTitle(data[options.titleField as keyof any[]], options);
|
||||
}
|
||||
|
||||
// Output one field per line
|
||||
for (const [k, v] of Object.entries(outData)) {
|
||||
for (let [k, v] of Object.entries(outData)) {
|
||||
const shim = ' '.repeat(longestKeyLength - k.length);
|
||||
const kDisplay = capitalize(k.replace(/_/g, ' '));
|
||||
printLine(`${chalk.bold(kDisplay) + shim} : ${v}`);
|
||||
let kDisplay = k.replace(/_/g, ' ');
|
||||
|
||||
// Start multiline values on the line below the field name
|
||||
if (typeof v === 'string' && v.includes('\n')) {
|
||||
v = `\n${v}`;
|
||||
}
|
||||
|
||||
if (!options.noCapitalizeKeys) {
|
||||
kDisplay = capitalize(kDisplay);
|
||||
}
|
||||
if (k !== options.titleField) {
|
||||
printLine(` ${bold(kDisplay) + shim} : ${v}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLongestObjectKeyLength(o: any): number {
|
||||
return Object.keys(o).length >= 1
|
||||
? Object.keys(o).reduce((a, b) => {
|
||||
return a.length > b.length ? a : b;
|
||||
}).length
|
||||
: 0;
|
||||
/**
|
||||
* Amend null/undefined values in data as per options:
|
||||
* - options.displayNullValuesAs will replace the value with the specified string
|
||||
* - options.hideNullOrUndefinedValues will remove the property from the data
|
||||
*
|
||||
* @param data The data object to process
|
||||
* @param options Output options
|
||||
*
|
||||
* @returns a copy of the data with amended values.
|
||||
*/
|
||||
function processNullValues(data: any, options: InternalOutputOptions) {
|
||||
const dataCopy = { ...data };
|
||||
|
||||
Object.entries(dataCopy).forEach(([k, v]) => {
|
||||
if (v == null) {
|
||||
if (options.displayNullValuesAs) {
|
||||
dataCopy[k] = options.displayNullValuesAs;
|
||||
} else if (options.hideNullOrUndefinedValues) {
|
||||
delete dataCopy[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dataCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a title with underscore
|
||||
*
|
||||
* @param title The title string to print
|
||||
* @param options Output options
|
||||
*/
|
||||
export function printTitle(
|
||||
title: string,
|
||||
options?: InternalOutputOptions & DataSetOutputOptions,
|
||||
) {
|
||||
if (!options?.['no-header']) {
|
||||
printLine(` ${capitalize(bold(title))}`);
|
||||
printLine(` ${bold('─'.repeat(title.length))}`);
|
||||
}
|
||||
}
|
||||
|
||||
function printLine(s: any) {
|
||||
@ -156,3 +232,15 @@ function printLine(s: any) {
|
||||
// but using this one explicitly for ease of testing
|
||||
process.stdout.write(s + '\n');
|
||||
}
|
||||
|
||||
function capitalize(s: string) {
|
||||
return `${s[0].toUpperCase()}${s.slice(1)}`;
|
||||
}
|
||||
|
||||
function bold(s: string) {
|
||||
return getChalk().bold(s);
|
||||
}
|
||||
|
||||
function getLongestObjectKeyLength(o: any): number {
|
||||
return Math.max(0, ...Object.keys(o).map((k) => k.length));
|
||||
}
|
||||
|
@ -53,6 +53,12 @@ export async function preparseArgs(argv: string[]): Promise<string[]> {
|
||||
if (extractBooleanFlag(cmdSlice, '--debug')) {
|
||||
process.env.DEBUG = '1';
|
||||
}
|
||||
// support global --v-next flag
|
||||
if (extractBooleanFlag(cmdSlice, '--v-next')) {
|
||||
const { version } = await import('../package.json');
|
||||
const { inc } = await import('semver');
|
||||
process.env.BALENA_CLI_VERSION_OVERRIDE = inc(version, 'major') || '';
|
||||
}
|
||||
unsupportedFlag = extractBooleanFlag(cmdSlice, '--unsupported');
|
||||
}
|
||||
|
||||
|
@ -97,14 +97,18 @@ export const deviceType = flags.string({
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const deviceTypeIgnored = isV14()
|
||||
? undefined
|
||||
: flags.string({
|
||||
description: 'ignored - no longer required',
|
||||
char: 't',
|
||||
required: false,
|
||||
hidden: true,
|
||||
});
|
||||
export const deviceTypeIgnored = {
|
||||
...(isV14()
|
||||
? {}
|
||||
: {
|
||||
type: flags.string({
|
||||
description: 'ignored - no longer required',
|
||||
char: 't',
|
||||
required: false,
|
||||
hidden: true,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
export const json: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'j',
|
||||
|
9
lib/utils/compose-types.d.ts
vendored
9
lib/utils/compose-types.d.ts
vendored
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ImageModel, ReleaseModel } from 'balena-release/build/models';
|
||||
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||
import type { Pack } from 'tar-stream';
|
||||
|
||||
@ -52,7 +53,7 @@ export interface ComposeOpts {
|
||||
inlineLogs?: boolean;
|
||||
multiDockerignore: boolean;
|
||||
noParentCheck: boolean;
|
||||
projectName: string;
|
||||
projectName?: string;
|
||||
projectPath: string;
|
||||
isLocal?: boolean;
|
||||
}
|
||||
@ -79,7 +80,7 @@ export interface ComposeProject {
|
||||
export interface Release {
|
||||
client: ReturnType<typeof import('balena-release').createClient>;
|
||||
release: Pick<
|
||||
import('balena-release/build/models').ReleaseModel,
|
||||
ReleaseModel,
|
||||
| 'id'
|
||||
| 'status'
|
||||
| 'commit'
|
||||
@ -91,7 +92,9 @@ export interface Release {
|
||||
| 'start_timestamp'
|
||||
| 'end_timestamp'
|
||||
>;
|
||||
serviceImages: Partial<import('balena-release/build/models').ImageModel>;
|
||||
serviceImages: Dictionary<
|
||||
Omit<ImageModel, 'created_at' | 'is_a_build_of__service' | '__metadata'>
|
||||
>;
|
||||
}
|
||||
|
||||
interface TarDirectoryOptions {
|
||||
|
@ -15,14 +15,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Renderer } from './compose_ts';
|
||||
import type * as SDK from 'balena-sdk';
|
||||
import type Dockerode = require('dockerode');
|
||||
import * as path from 'path';
|
||||
import type { Composition, ImageDescriptor } from 'resin-compose-parse';
|
||||
import type {
|
||||
BuiltImage,
|
||||
ComposeOpts,
|
||||
ComposeProject,
|
||||
Release,
|
||||
TaggedImage,
|
||||
} from './compose-types';
|
||||
import { getChalk } from './lazy';
|
||||
import Logger = require('./logger');
|
||||
import { ProgressCallback } from 'docker-progress';
|
||||
|
||||
/**
|
||||
* @returns Promise<{import('./compose-types').ComposeOpts}>
|
||||
*/
|
||||
export function generateOpts(options) {
|
||||
const { promises: fs } = require('fs');
|
||||
export function generateOpts(options: {
|
||||
source?: string;
|
||||
projectName?: string;
|
||||
nologs: boolean;
|
||||
'noconvert-eol': boolean;
|
||||
dockerfile?: string;
|
||||
'multi-dockerignore': boolean;
|
||||
'noparent-check': boolean;
|
||||
}): Promise<ComposeOpts> {
|
||||
const { promises: fs } = require('fs') as typeof import('fs');
|
||||
return fs.realpath(options.source || '.').then((projectPath) => ({
|
||||
projectName: options.projectName,
|
||||
projectPath,
|
||||
@ -34,24 +52,19 @@ export function generateOpts(options) {
|
||||
}));
|
||||
}
|
||||
|
||||
// Parse the given composition and return a structure with info. Input is:
|
||||
// - composePath: the *absolute* path to the directory containing the compose file
|
||||
// - composeStr: the contents of the compose file, as a string
|
||||
/**
|
||||
* @param {string} composePath
|
||||
* @param {string} composeStr
|
||||
* @param {string | undefined} projectName The --projectName flag (build, deploy)
|
||||
* @param {string | undefined} imageTag The --tag flag (build, deploy)
|
||||
* @returns {import('./compose-types').ComposeProject}
|
||||
/** Parse the given composition and return a structure with info. Input is:
|
||||
* - composePath: the *absolute* path to the directory containing the compose file
|
||||
* - composeStr: the contents of the compose file, as a string
|
||||
*/
|
||||
export function createProject(
|
||||
composePath,
|
||||
composeStr,
|
||||
composePath: string,
|
||||
composeStr: string,
|
||||
projectName = '',
|
||||
imageTag = '',
|
||||
) {
|
||||
const yml = require('js-yaml');
|
||||
const compose = require('resin-compose-parse');
|
||||
): ComposeProject {
|
||||
const yml = require('js-yaml') as typeof import('js-yaml');
|
||||
const compose =
|
||||
require('resin-compose-parse') as typeof import('resin-compose-parse');
|
||||
|
||||
// both methods below may throw.
|
||||
const rawComposition = yml.load(composeStr);
|
||||
@ -67,7 +80,8 @@ export function createProject(
|
||||
descr.image.context != null &&
|
||||
descr.image.tag == null
|
||||
) {
|
||||
const { makeImageName } = require('./compose_ts');
|
||||
const { makeImageName } =
|
||||
require('./compose_ts') as typeof import('./compose_ts');
|
||||
descr.image.tag = makeImageName(projectName, descr.serviceName, imageTag);
|
||||
}
|
||||
return descr;
|
||||
@ -80,30 +94,20 @@ export function createProject(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} apiEndpoint
|
||||
* @param {string} auth
|
||||
* @param {number} userId
|
||||
* @param {number} appId
|
||||
* @param {import('resin-compose-parse').Composition} composition
|
||||
* @param {boolean} draft
|
||||
* @param {string|undefined} semver
|
||||
* @param {string|undefined} contract
|
||||
* @returns {Promise<import('./compose-types').Release>}
|
||||
*/
|
||||
export const createRelease = async function (
|
||||
apiEndpoint,
|
||||
auth,
|
||||
userId,
|
||||
appId,
|
||||
composition,
|
||||
draft,
|
||||
semver,
|
||||
contract,
|
||||
) {
|
||||
const _ = require('lodash');
|
||||
const crypto = require('crypto');
|
||||
const releaseMod = require('balena-release');
|
||||
apiEndpoint: string,
|
||||
auth: string,
|
||||
userId: number,
|
||||
appId: number,
|
||||
composition: Composition,
|
||||
draft: boolean,
|
||||
semver?: string,
|
||||
contract?: string,
|
||||
): Promise<Release> {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const crypto = require('crypto') as typeof import('crypto');
|
||||
const releaseMod =
|
||||
require('balena-release') as typeof import('balena-release');
|
||||
|
||||
const client = releaseMod.createClient({ apiEndpoint, auth });
|
||||
|
||||
@ -133,24 +137,26 @@ export const createRelease = async function (
|
||||
'start_timestamp',
|
||||
'end_timestamp',
|
||||
]),
|
||||
serviceImages: _.mapValues(serviceImages, (serviceImage) =>
|
||||
_.omit(serviceImage, [
|
||||
'created_at',
|
||||
'is_a_build_of__service',
|
||||
'__metadata',
|
||||
]),
|
||||
serviceImages: _.mapValues(
|
||||
serviceImages,
|
||||
(serviceImage) =>
|
||||
_.omit(serviceImage, [
|
||||
'created_at',
|
||||
'is_a_build_of__service',
|
||||
'__metadata',
|
||||
]) as Omit<
|
||||
typeof serviceImage,
|
||||
'created_at' | 'is_a_build_of__service' | '__metadata'
|
||||
>,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {Array<import('./compose-types').BuiltImage>} images
|
||||
* @param {Partial<import('balena-release/build/models').ImageModel>} serviceImages
|
||||
* @returns {Promise<Array<import('./compose-types').TaggedImage>>}
|
||||
*/
|
||||
export const tagServiceImages = (docker, images, serviceImages) =>
|
||||
export const tagServiceImages = (
|
||||
docker: Dockerode,
|
||||
images: BuiltImage[],
|
||||
serviceImages: Release['serviceImages'],
|
||||
): Promise<TaggedImage[]> =>
|
||||
Promise.all(
|
||||
images.map(function (d) {
|
||||
const serviceImage = serviceImages[d.serviceName];
|
||||
@ -177,25 +183,24 @@ export const tagServiceImages = (docker, images, serviceImages) =>
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {*} sdk
|
||||
* @param {import('./logger')} logger
|
||||
* @param {number} appID
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
export const getPreviousRepos = (
|
||||
sdk: SDK.BalenaSDK,
|
||||
logger: Logger,
|
||||
appID: number,
|
||||
): Promise<string[]> =>
|
||||
sdk.pine
|
||||
.get({
|
||||
.get<SDK.Release>({
|
||||
resource: 'release',
|
||||
options: {
|
||||
$select: 'id',
|
||||
$filter: {
|
||||
belongs_to__application: appID,
|
||||
status: 'success',
|
||||
},
|
||||
$select: ['id'],
|
||||
$expand: {
|
||||
contains__image: {
|
||||
$expand: 'image',
|
||||
$select: 'image',
|
||||
$expand: { image: { $select: 'is_stored_at__image_location' } },
|
||||
},
|
||||
},
|
||||
$orderby: 'id desc',
|
||||
@ -205,8 +210,11 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
.then(function (release) {
|
||||
// grab all images from the latest release, return all image locations in the registry
|
||||
if (release.length > 0) {
|
||||
const images = release[0].contains__image;
|
||||
const { getRegistryAndName } = require('resin-multibuild');
|
||||
const images = release[0].contains__image as Array<{
|
||||
image: [SDK.Image];
|
||||
}>;
|
||||
const { getRegistryAndName } =
|
||||
require('resin-multibuild') as typeof import('resin-multibuild');
|
||||
return Promise.all(
|
||||
images.map(function (d) {
|
||||
const imageName = d.image[0].is_stored_at__image_location || '';
|
||||
@ -226,21 +234,13 @@ export const getPreviousRepos = (sdk, logger, appID) =>
|
||||
return [];
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {*} sdk
|
||||
* @param {string} tokenAuthEndpoint
|
||||
* @param {string} registry
|
||||
* @param {string[]} images
|
||||
* @param {string[]} previousRepos
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const authorizePush = function (
|
||||
sdk,
|
||||
tokenAuthEndpoint,
|
||||
registry,
|
||||
images,
|
||||
previousRepos,
|
||||
) {
|
||||
sdk: SDK.BalenaSDK,
|
||||
tokenAuthEndpoint: string,
|
||||
registry: string,
|
||||
images: string[],
|
||||
previousRepos: string[],
|
||||
): Promise<string> {
|
||||
if (!Array.isArray(images)) {
|
||||
images = [images];
|
||||
}
|
||||
@ -261,17 +261,20 @@ export const authorizePush = function (
|
||||
|
||||
// utilities
|
||||
|
||||
const renderProgressBar = function (percentage, stepCount) {
|
||||
const _ = require('lodash');
|
||||
const renderProgressBar = function (percentage: number, stepCount: number) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
percentage = _.clamp(percentage, 0, 100);
|
||||
const barCount = Math.floor((stepCount * percentage) / 100);
|
||||
const spaceCount = stepCount - barCount;
|
||||
const bar = `[${_.repeat('=', barCount)}>${_.repeat(' ', spaceCount)}]`;
|
||||
return `${bar} ${_.padStart(percentage, 3)}%`;
|
||||
return `${bar} ${_.padStart(`${percentage}`, 3)}%`;
|
||||
};
|
||||
|
||||
export const pushProgressRenderer = function (tty, prefix) {
|
||||
const fn = function (e) {
|
||||
export const pushProgressRenderer = function (
|
||||
tty: ReturnType<typeof import('./tty')>,
|
||||
prefix: string,
|
||||
): ProgressCallback & { end: () => void } {
|
||||
const fn: ProgressCallback & { end: () => void } = function (e) {
|
||||
const { error, percentage } = e;
|
||||
if (error != null) {
|
||||
throw new Error(error);
|
||||
@ -285,14 +288,39 @@ export const pushProgressRenderer = function (tty, prefix) {
|
||||
return fn;
|
||||
};
|
||||
|
||||
export class BuildProgressUI {
|
||||
constructor(tty, descriptors) {
|
||||
export class BuildProgressUI implements Renderer {
|
||||
public streams;
|
||||
private _prefix;
|
||||
private _prefixWidth;
|
||||
private _tty;
|
||||
private _services;
|
||||
private _startTime: undefined | number;
|
||||
private _ended;
|
||||
private _serviceToDataMap: Dictionary<{
|
||||
status?: string;
|
||||
progress?: number;
|
||||
error?: Error;
|
||||
}> = {};
|
||||
private _cancelled;
|
||||
private _spinner;
|
||||
private _runloop:
|
||||
| undefined
|
||||
| ReturnType<typeof import('./compose_ts').createRunLoop>;
|
||||
|
||||
// these are to handle window wrapping
|
||||
private _maxLineWidth: undefined | number;
|
||||
private _lineWidths: number[] = [];
|
||||
|
||||
constructor(
|
||||
tty: ReturnType<typeof import('./tty')>,
|
||||
descriptors: ImageDescriptor[],
|
||||
) {
|
||||
this._handleEvent = this._handleEvent.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
this.end = this.end.bind(this);
|
||||
this._display = this._display.bind(this);
|
||||
const _ = require('lodash');
|
||||
const through = require('through2');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const through = require('through2') as typeof import('through2');
|
||||
|
||||
const eventHandler = this._handleEvent;
|
||||
const services = _.map(descriptors, 'serviceName');
|
||||
@ -310,7 +338,6 @@ export class BuildProgressUI {
|
||||
.value();
|
||||
|
||||
this._tty = tty;
|
||||
this._serviceToDataMap = {};
|
||||
this._services = services;
|
||||
|
||||
// Logger magically prefixes the log line with [Build] etc., but it doesn't
|
||||
@ -320,22 +347,22 @@ export class BuildProgressUI {
|
||||
|
||||
const offset = 10; // account for escape sequences inserted for colouring
|
||||
this._prefixWidth =
|
||||
offset + prefix.length + _.max(_.map(services, 'length'));
|
||||
offset + prefix.length + _.max(_.map(services, (s) => s.length))!;
|
||||
this._prefix = prefix;
|
||||
|
||||
// these are to handle window wrapping
|
||||
this._maxLineWidth = null;
|
||||
this._lineWidths = [];
|
||||
|
||||
this._startTime = null;
|
||||
this._ended = false;
|
||||
this._cancelled = false;
|
||||
this._spinner = require('./compose_ts').createSpinner();
|
||||
this._spinner = (
|
||||
require('./compose_ts') as typeof import('./compose_ts')
|
||||
).createSpinner();
|
||||
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
_handleEvent(service, event) {
|
||||
_handleEvent(
|
||||
service: string,
|
||||
event: { status?: string; progress?: number; error?: Error },
|
||||
) {
|
||||
this._serviceToDataMap[service] = event;
|
||||
}
|
||||
|
||||
@ -344,20 +371,19 @@ export class BuildProgressUI {
|
||||
this._services.forEach((service) => {
|
||||
this.streams[service].write({ status: 'Preparing...' });
|
||||
});
|
||||
this._runloop = require('./compose_ts').createRunLoop(this._display);
|
||||
this._runloop = (
|
||||
require('./compose_ts') as typeof import('./compose_ts')
|
||||
).createRunLoop(this._display);
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dictionary<string> | undefined} summary
|
||||
*/
|
||||
end(summary) {
|
||||
end(summary?: Dictionary<string>) {
|
||||
if (this._ended) {
|
||||
return;
|
||||
}
|
||||
this._ended = true;
|
||||
this._runloop?.end();
|
||||
this._runloop = null;
|
||||
this._runloop = undefined;
|
||||
|
||||
this._clear();
|
||||
this._renderStatus(true);
|
||||
@ -378,7 +404,7 @@ export class BuildProgressUI {
|
||||
}
|
||||
|
||||
_getServiceSummary() {
|
||||
const _ = require('lodash');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
|
||||
const services = this._services;
|
||||
const serviceToDataMap = this._serviceToDataMap;
|
||||
@ -405,11 +431,11 @@ export class BuildProgressUI {
|
||||
.value();
|
||||
}
|
||||
|
||||
_renderStatus(end) {
|
||||
end ??= false;
|
||||
|
||||
const moment = require('moment');
|
||||
require('moment-duration-format')(moment);
|
||||
_renderStatus(end = false) {
|
||||
const moment = require('moment') as typeof import('moment');
|
||||
(
|
||||
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||
)(moment);
|
||||
|
||||
this._tty.clearLine();
|
||||
this._tty.write(this._prefix);
|
||||
@ -434,11 +460,11 @@ export class BuildProgressUI {
|
||||
}
|
||||
}
|
||||
|
||||
_renderSummary(serviceToStrMap) {
|
||||
const _ = require('lodash');
|
||||
_renderSummary(serviceToStrMap: Dictionary<string>) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const chalk = getChalk();
|
||||
const truncate = require('cli-truncate');
|
||||
const strlen = require('string-width');
|
||||
const truncate = require('cli-truncate') as typeof import('cli-truncate');
|
||||
const strlen = require('string-width') as typeof import('string-width');
|
||||
|
||||
this._services.forEach((service, index) => {
|
||||
let str = _.padEnd(this._prefix + chalk.bold(service), this._prefixWidth);
|
||||
@ -454,13 +480,23 @@ export class BuildProgressUI {
|
||||
}
|
||||
}
|
||||
|
||||
export class BuildProgressInline {
|
||||
constructor(outStream, descriptors) {
|
||||
export class BuildProgressInline implements Renderer {
|
||||
public streams;
|
||||
private _prefixWidth;
|
||||
private _outStream;
|
||||
private _services;
|
||||
private _startTime: number | undefined;
|
||||
private _ended;
|
||||
|
||||
constructor(
|
||||
outStream: NodeJS.ReadWriteStream,
|
||||
descriptors: Array<{ serviceName: string }>,
|
||||
) {
|
||||
this.start = this.start.bind(this);
|
||||
this.end = this.end.bind(this);
|
||||
this._renderEvent = this._renderEvent.bind(this);
|
||||
const _ = require('lodash');
|
||||
const through = require('through2');
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const through = require('through2') as typeof import('through2');
|
||||
|
||||
const services = _.map(descriptors, 'serviceName');
|
||||
const eventHandler = this._renderEvent;
|
||||
@ -477,10 +513,9 @@ export class BuildProgressInline {
|
||||
.value();
|
||||
|
||||
const offset = 10; // account for escape sequences inserted for colouring
|
||||
this._prefixWidth = offset + _.max(_.map(services, 'length'));
|
||||
this._prefixWidth = offset + _.max(_.map(services, (s) => s.length))!;
|
||||
this._outStream = outStream;
|
||||
this._services = services;
|
||||
this._startTime = null;
|
||||
this._ended = false;
|
||||
|
||||
this.streams = streams;
|
||||
@ -494,12 +529,11 @@ export class BuildProgressInline {
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Dictionary<string> | undefined} summary
|
||||
*/
|
||||
end(summary) {
|
||||
const moment = require('moment');
|
||||
require('moment-duration-format')(moment);
|
||||
end(summary?: Dictionary<string>) {
|
||||
const moment = require('moment') as typeof import('moment');
|
||||
(
|
||||
require('moment-duration-format') as typeof import('moment-duration-format')
|
||||
)(moment);
|
||||
|
||||
if (this._ended) {
|
||||
return;
|
||||
@ -527,8 +561,8 @@ export class BuildProgressInline {
|
||||
this._outStream.write(`Built ${serviceStr} in ${durationStr}\n`);
|
||||
}
|
||||
|
||||
_renderEvent(service, event) {
|
||||
const _ = require('lodash');
|
||||
_renderEvent(service: string, event: { status?: string; error?: Error }) {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
|
||||
const str = (function () {
|
||||
const { status, error } = event;
|
@ -235,7 +235,7 @@ interface BuildTaskPlus extends MultiBuild.BuildTask {
|
||||
logBuffer?: string[];
|
||||
}
|
||||
|
||||
interface Renderer {
|
||||
export interface Renderer {
|
||||
start: () => void;
|
||||
end: (buildSummaryByService?: Dictionary<string>) => void;
|
||||
streams: Dictionary<NodeJS.ReadWriteStream>;
|
||||
|
@ -17,15 +17,27 @@
|
||||
|
||||
import { getVisuals } from './lazy';
|
||||
import { promisify } from 'util';
|
||||
import type * as Dockerode from 'dockerode';
|
||||
import type Logger = require('./logger');
|
||||
import type { Request } from 'request';
|
||||
|
||||
const getBuilderPushEndpoint = function (baseUrl, owner, app) {
|
||||
const querystring = require('querystring');
|
||||
const getBuilderPushEndpoint = function (
|
||||
baseUrl: string,
|
||||
owner: string,
|
||||
app: string,
|
||||
) {
|
||||
const querystring = require('querystring') as typeof import('querystring');
|
||||
const args = querystring.stringify({ owner, app });
|
||||
return `https://builder.${baseUrl}/v1/push?${args}`;
|
||||
};
|
||||
|
||||
const getBuilderLogPushEndpoint = function (baseUrl, buildId, owner, app) {
|
||||
const querystring = require('querystring');
|
||||
const getBuilderLogPushEndpoint = function (
|
||||
baseUrl: string,
|
||||
buildId: number,
|
||||
owner: string,
|
||||
app: string,
|
||||
) {
|
||||
const querystring = require('querystring') as typeof import('querystring');
|
||||
const args = querystring.stringify({ owner, app, buildId });
|
||||
return `https://builder.${baseUrl}/v1/pushLogs?${args}`;
|
||||
};
|
||||
@ -35,32 +47,37 @@ const getBuilderLogPushEndpoint = function (baseUrl, buildId, owner, app) {
|
||||
* @param {string} imageId
|
||||
* @param {string} bufferFile
|
||||
*/
|
||||
const bufferImage = function (docker, imageId, bufferFile) {
|
||||
const streamUtils = require('./streams');
|
||||
const bufferImage = function (
|
||||
docker: Dockerode,
|
||||
imageId: string,
|
||||
bufferFile: string,
|
||||
): Promise<NodeJS.ReadableStream & { length: number }> {
|
||||
const streamUtils = require('./streams') as typeof import('./streams');
|
||||
|
||||
const image = docker.getImage(imageId);
|
||||
const sizePromise = image.inspect().then((img) => img.Size);
|
||||
|
||||
return Promise.all([image.get(), sizePromise]).then(
|
||||
([imageStream, imageSize]) =>
|
||||
streamUtils.buffer(imageStream, bufferFile).then((bufferedStream) => {
|
||||
// @ts-ignore adding an extra property
|
||||
bufferedStream.length = imageSize;
|
||||
return bufferedStream;
|
||||
}),
|
||||
streamUtils
|
||||
.buffer(imageStream, bufferFile)
|
||||
.then((bufferedStream: NodeJS.ReadableStream & { length?: number }) => {
|
||||
bufferedStream.length = imageSize;
|
||||
return bufferedStream as NodeJS.ReadableStream & { length: number };
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const showPushProgress = function (message) {
|
||||
const showPushProgress = function (message: string) {
|
||||
const visuals = getVisuals();
|
||||
const progressBar = new visuals.Progress(message);
|
||||
progressBar.update({ percentage: 0 });
|
||||
return progressBar;
|
||||
};
|
||||
|
||||
const uploadToPromise = (uploadRequest, logger) =>
|
||||
new Promise(function (resolve, reject) {
|
||||
const handleMessage = function (data) {
|
||||
const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
||||
new Promise<{ buildId: number }>(function (resolve, reject) {
|
||||
uploadRequest.on('error', reject).on('data', function handleMessage(data) {
|
||||
let obj;
|
||||
data = data.toString();
|
||||
logger.logDebug(`Received data: ${data}`);
|
||||
@ -86,25 +103,24 @@ const uploadToPromise = (uploadRequest, logger) =>
|
||||
default:
|
||||
reject(new Error(`Received unexpected reply from remote: ${data}`));
|
||||
}
|
||||
};
|
||||
|
||||
uploadRequest.on('error', reject).on('data', handleMessage);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns {Promise<{ buildId: number }>}
|
||||
*/
|
||||
const uploadImage = function (
|
||||
imageStream,
|
||||
token,
|
||||
username,
|
||||
url,
|
||||
appName,
|
||||
logger,
|
||||
) {
|
||||
const request = require('request');
|
||||
const progressStream = require('progress-stream');
|
||||
const zlib = require('zlib');
|
||||
imageStream: NodeJS.ReadableStream & { length: number },
|
||||
token: string,
|
||||
username: string,
|
||||
url: string,
|
||||
appName: string,
|
||||
logger: Logger,
|
||||
): Promise<{ buildId: number }> {
|
||||
const request = require('request') as typeof import('request');
|
||||
const progressStream =
|
||||
require('progress-stream') as typeof import('progress-stream');
|
||||
const zlib = require('zlib') as typeof import('zlib');
|
||||
|
||||
// Need to strip off the newline
|
||||
const progressMessage = logger
|
||||
@ -143,8 +159,15 @@ const uploadImage = function (
|
||||
return uploadToPromise(uploadRequest, logger);
|
||||
};
|
||||
|
||||
const uploadLogs = function (logs, token, url, buildId, username, appName) {
|
||||
const request = require('request');
|
||||
const uploadLogs = function (
|
||||
logs: string,
|
||||
token: string,
|
||||
url: string,
|
||||
buildId: number,
|
||||
username: string,
|
||||
appName: string,
|
||||
) {
|
||||
const request = require('request') as typeof import('request');
|
||||
return request.post({
|
||||
json: true,
|
||||
url: getBuilderLogPushEndpoint(url, buildId, username, appName),
|
||||
@ -156,25 +179,24 @@ const uploadLogs = function (logs, token, url, buildId, username, appName) {
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {import('./logger')} logger
|
||||
* @param {string} token
|
||||
* @param {string} username
|
||||
* @param {string} url
|
||||
* @param {{appName: string; imageName: string; buildLogs: string; shouldUploadLogs: boolean}} opts
|
||||
* - appName: the name of the app to deploy to
|
||||
* - imageName: the name of the image to deploy
|
||||
* - buildLogs: a string with build output
|
||||
*/
|
||||
export const deployLegacy = async function (
|
||||
docker,
|
||||
logger,
|
||||
token,
|
||||
username,
|
||||
url,
|
||||
opts,
|
||||
) {
|
||||
const tmp = require('tmp');
|
||||
docker: Dockerode,
|
||||
logger: Logger,
|
||||
token: string,
|
||||
username: string,
|
||||
url: string,
|
||||
opts: {
|
||||
appName: string;
|
||||
imageName: string;
|
||||
buildLogs: string;
|
||||
shouldUploadLogs: boolean;
|
||||
},
|
||||
): Promise<number> {
|
||||
const tmp = require('tmp') as typeof import('tmp');
|
||||
const tmpNameAsync = promisify(tmp.tmpName);
|
||||
|
||||
// Ensure the tmp files gets deleted
|
||||
@ -195,8 +217,8 @@ export const deployLegacy = async function (
|
||||
// has occured before any data was written) this call will throw an
|
||||
// ugly error, just suppress it
|
||||
|
||||
require('fs')
|
||||
.promises.unlink(bufferFile)
|
||||
(require('fs') as typeof import('fs')).promises
|
||||
.unlink(bufferFile)
|
||||
.catch(() => undefined),
|
||||
);
|
||||
|
@ -21,7 +21,6 @@ import type { Chalk } from 'chalk';
|
||||
import type * as visuals from 'resin-cli-visuals';
|
||||
import type * as CliForm from 'resin-cli-form';
|
||||
import type { ux } from 'cli-ux';
|
||||
import type { stripIndent as StripIndent } from 'common-tags';
|
||||
|
||||
// Equivalent of _.once but avoiding the need to import lodash for lazy deps
|
||||
const once = <T>(fn: () => T) => {
|
||||
@ -63,4 +62,4 @@ export const getCliUx = once(() => require('cli-ux').ux as typeof ux);
|
||||
// Directly export stripIndent as we always use it immediately, but importing just `stripIndent` reduces startup time
|
||||
export const stripIndent =
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
require('common-tags/lib/stripIndent') as typeof StripIndent;
|
||||
require('common-tags/lib/stripIndent') as typeof import('common-tags/lib/stripIndent');
|
||||
|
@ -207,35 +207,6 @@ export async function selectOrganization(organizations?: Organization[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function awaitDevice(uuid: string) {
|
||||
const balena = getBalenaSdk();
|
||||
const deviceName = await balena.models.device.getName(uuid);
|
||||
const visuals = getVisuals();
|
||||
const spinner = new visuals.Spinner(
|
||||
`Waiting for ${deviceName} to come online`,
|
||||
);
|
||||
|
||||
const poll = async (): Promise<void> => {
|
||||
const isOnline = await balena.models.device.isOnline(uuid);
|
||||
if (isOnline) {
|
||||
spinner.stop();
|
||||
console.info(`The device **${deviceName}** is online!`);
|
||||
return;
|
||||
} else {
|
||||
// Spinner implementation is smart enough to
|
||||
// not start again if it was already started
|
||||
spinner.start();
|
||||
|
||||
await delay(3000);
|
||||
await poll();
|
||||
}
|
||||
};
|
||||
|
||||
console.info(`Waiting for ${deviceName} to connect to balena...`);
|
||||
await poll();
|
||||
return uuid;
|
||||
}
|
||||
|
||||
export async function awaitDeviceOsUpdate(
|
||||
uuid: string,
|
||||
targetOsVersion: string,
|
||||
|
@ -212,7 +212,7 @@ function handleBuilderMetadata(obj: BuilderMessage, build: RemoteBuild) {
|
||||
}
|
||||
|
||||
const value = match[1];
|
||||
const amount = match[2] || 1;
|
||||
const amount = Number(match[2]) || 1;
|
||||
|
||||
switch (value) {
|
||||
case 'erase':
|
||||
|
@ -77,7 +77,7 @@ export async function exec(
|
||||
.on('error', reject)
|
||||
.on('close', resolve);
|
||||
|
||||
if (stdout) {
|
||||
if (stdout && ps.stdout) {
|
||||
ps.stdout.pipe(stdout);
|
||||
}
|
||||
});
|
||||
|
@ -98,7 +98,7 @@ async function spawnAndPipe(
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if (stderr) {
|
||||
if (stderr && ps.stderr) {
|
||||
ps.stderr.pipe(stderr);
|
||||
}
|
||||
});
|
||||
|
5221
npm-shrinkwrap.json
generated
5221
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
70
package.json
70
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "13.1.0",
|
||||
"version": "13.1.13",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -51,8 +51,9 @@
|
||||
"build": "npm run build:src && npm run catch-uncommitted",
|
||||
"build:t": "npm run lint && npm run build:fast && npm run build:test",
|
||||
"build:src": "npm run lint && npm run build:fast && npm run build:test && npm run build:doc && npm run build:completion",
|
||||
"build:fast": "gulp pages && tsc && npx oclif manifest",
|
||||
"build:test": "tsc -P ./tsconfig.dev.json --noEmit && tsc -P ./tsconfig.js.json --noEmit",
|
||||
"build:pages": "mkdirp ./build/auth/pages/&& inline-source --compress ./lib/auth/pages/error.ejs ./build/auth/pages/error.ejs && inline-source --compress ./lib/auth/pages/success.ejs ./build/auth/pages/success.ejs",
|
||||
"build:fast": "npm run build:pages && tsc && npx oclif manifest",
|
||||
"build:test": "tsc -P ./tsconfig.dev.json --noEmit",
|
||||
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > docs/balena-cli.md",
|
||||
"build:completion": "node completion/generate-completion.js",
|
||||
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
||||
@ -70,10 +71,9 @@
|
||||
"test:only": "npm run build:fast && cross-env BALENA_CLI_TEST_TYPE=source mocha \"tests/**/${npm_config_test}.spec.ts\"",
|
||||
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
||||
"ci": "npm run test && npm run catch-uncommitted",
|
||||
"watch": "gulp watch",
|
||||
"lint": "npm run lint-tsconfig && npm run lint-other",
|
||||
"lint-tsconfig": "balena-lint -e ts -e js -t tsconfig.dev.json --fix automation/ lib/ tests/ typings/",
|
||||
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ gulpfile.js .mocharc.js .mocharc-standalone.js",
|
||||
"lint-other": "balena-lint -e ts -e js --fix bin/balena bin/balena-dev completion/ .mocharc.js .mocharc-standalone.js",
|
||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json",
|
||||
"prepublishOnly": "npm run build"
|
||||
@ -90,7 +90,7 @@
|
||||
"author": "Balena Inc. (https://balena.io/)",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0 <13.0.0",
|
||||
"node": ">=12.8.0 <13.0.0",
|
||||
"npm": "<7.0.0"
|
||||
},
|
||||
"husky": {
|
||||
@ -142,12 +142,12 @@
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/mixpanel": "^2.14.3",
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/mock-require": "^2.0.0",
|
||||
"@types/mock-require": "^2.0.1",
|
||||
"@types/moment-duration-format": "^2.2.3",
|
||||
"@types/ndjson": "^2.0.1",
|
||||
"@types/net-keepalive": "^0.4.1",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^10.17.60",
|
||||
"@types/node": "^12.20.42",
|
||||
"@types/node-cleanup": "^2.1.2",
|
||||
"@types/parse-link-header": "^1.0.1",
|
||||
"@types/prettyjson": "^0.0.30",
|
||||
@ -172,19 +172,19 @@
|
||||
"diff": "^5.0.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"ent": "^2.2.0",
|
||||
"filehound": "^1.17.4",
|
||||
"filehound": "^1.17.5",
|
||||
"fs-extra": "^9.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-inline-source": "^4.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"husky": "^4.3.8",
|
||||
"inline-source-cli": "^2.0.0",
|
||||
"intercept-stdout": "^0.1.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^8.4.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"nock": "^13.2.1",
|
||||
"parse-link-header": "^1.0.1",
|
||||
"pkg": "^4.4.9",
|
||||
"pkg": "^5.5.1",
|
||||
"publish-release": "^1.6.1",
|
||||
"rewire": "^5.0.0",
|
||||
"simple-git": "^2.48.0",
|
||||
@ -197,7 +197,7 @@
|
||||
"@balena/es-version": "^1.0.1",
|
||||
"@oclif/command": "^1.8.16",
|
||||
"@resin.io/valid-email": "^0.1.0",
|
||||
"@sentry/node": "^6.13.2",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"@types/fast-levenshtein": "0.0.1",
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"JSONStream": "^1.0.3",
|
||||
@ -206,7 +206,7 @@
|
||||
"balena-errors": "^4.7.1",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-image-manager": "^7.1.1",
|
||||
"balena-preload": "^11.0.0",
|
||||
"balena-preload": "^12.0.0",
|
||||
"balena-release": "^3.2.0",
|
||||
"balena-sdk": "^16.9.0",
|
||||
"balena-semver": "^2.3.0",
|
||||
@ -214,41 +214,41 @@
|
||||
"balena-settings-storage": "^7.0.0",
|
||||
"balena-sync": "^11.0.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"body-parser": "^1.19.1",
|
||||
"chalk": "^3.0.0",
|
||||
"chokidar": "^3.4.3",
|
||||
"chokidar": "^3.5.2",
|
||||
"cli-truncate": "^2.1.0",
|
||||
"cli-ux": "^5.5.1",
|
||||
"color-hash": "^1.0.3",
|
||||
"color-hash": "^1.1.1",
|
||||
"cli-ux": "^6.0.5",
|
||||
"columnify": "^1.5.2",
|
||||
"common-tags": "^1.7.2",
|
||||
"denymount": "^2.3.0",
|
||||
"docker-modem": "^3.0.0",
|
||||
"docker-progress": "^5.0.0",
|
||||
"docker-modem": "3.0.0",
|
||||
"docker-progress": "^5.0.1",
|
||||
"docker-qemu-transpose": "^1.1.1",
|
||||
"dockerode": "^3.3.0",
|
||||
"ejs": "^3.1.3",
|
||||
"dockerode": "^3.3.1",
|
||||
"ejs": "^3.1.6",
|
||||
"etcher-sdk": "^6.2.1",
|
||||
"event-stream": "3.3.4",
|
||||
"express": "^4.13.3",
|
||||
"express": "^4.17.2",
|
||||
"fast-boot2": "^1.1.0",
|
||||
"fast-levenshtein": "^3.0.0",
|
||||
"filenamify": "^4.3.0",
|
||||
"get-stdin": "^8.0.0",
|
||||
"glob": "^7.1.7",
|
||||
"global-agent": "^2.1.12",
|
||||
"glob": "^7.2.0",
|
||||
"global-agent": "^2.2.0",
|
||||
"global-tunnel-ng": "^2.1.1",
|
||||
"got": "^11.8.2",
|
||||
"got": "^11.8.3",
|
||||
"humanize": "0.0.9",
|
||||
"inquirer": "^7.3.3",
|
||||
"is-elevated": "^3.0.0",
|
||||
"is-root": "^2.1.0",
|
||||
"js-yaml": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"klaw": "^3.0.0",
|
||||
"livepush": "^3.5.0",
|
||||
"livepush": "^3.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^3.0.4",
|
||||
"moment": "^2.27.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment-duration-format": "^2.3.2",
|
||||
"ndjson": "^2.0.0",
|
||||
"net-keepalive": "^3.0.0",
|
||||
@ -257,29 +257,29 @@
|
||||
"oclif": "^1.18.4",
|
||||
"open": "^7.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"prettyjson": "^1.1.3",
|
||||
"prettyjson": "^1.2.5",
|
||||
"progress-stream": "^2.0.0",
|
||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||
"request": "^2.88.2",
|
||||
"resin-cli-form": "^2.0.2",
|
||||
"resin-cli-visuals": "^1.8.0",
|
||||
"resin-compose-parse": "^2.1.3",
|
||||
"resin-doodles": "^0.1.1",
|
||||
"resin-doodles": "^0.2.0",
|
||||
"resin-multibuild": "^4.12.2",
|
||||
"resin-stream-logger": "^0.1.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.2",
|
||||
"semver": "^7.3.5",
|
||||
"shell-escape": "^0.2.0",
|
||||
"split": "^1.0.1",
|
||||
"stream-to-promise": "^2.2.0",
|
||||
"string-width": "^4.2.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi-stream": "^1.0.0",
|
||||
"tar-stream": "^2.1.3",
|
||||
"tar-utils": "^2.1.1",
|
||||
"through2": "^2.0.3",
|
||||
"tmp": "^0.2.1",
|
||||
"typed-error": "^3.2.1",
|
||||
"update-notifier": "^4.1.0",
|
||||
"update-notifier": "^5.1.0",
|
||||
"which": "^2.0.2",
|
||||
"window-size": "^1.1.0"
|
||||
},
|
||||
@ -287,6 +287,6 @@
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2021-12-29T14:22:18.997Z"
|
||||
"publishedAt": "2022-02-10T11:50:34.458Z"
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
diff --git a/node_modules/pkg/lib-es5/packer.js b/node_modules/pkg/lib-es5/packer.js
|
||||
index 7295bb6..76805a3 100644
|
||||
--- a/node_modules/pkg/lib-es5/packer.js
|
||||
+++ b/node_modules/pkg/lib-es5/packer.js
|
||||
@@ -128,6 +128,7 @@ function _default({
|
||||
const newStat = Object.assign({}, value);
|
||||
newStat.isFileValue = value.isFile();
|
||||
newStat.isDirectoryValue = value.isDirectory();
|
||||
+ newStat.isSocketValue = value.isSocket();
|
||||
const buffer = Buffer.from(JSON.stringify(newStat));
|
||||
stripes.push({
|
||||
snap,
|
||||
diff --git a/node_modules/pkg/prelude/bootstrap.js b/node_modules/pkg/prelude/bootstrap.js
|
||||
index 0d19f1d..db69015 100644
|
||||
--- a/node_modules/pkg/prelude/bootstrap.js
|
||||
+++ b/node_modules/pkg/prelude/bootstrap.js
|
||||
@@ -925,8 +925,10 @@ function payloadFileSync (pointer) {
|
||||
|
||||
var isFileValue = s.isFileValue;
|
||||
var isDirectoryValue = s.isDirectoryValue;
|
||||
+ var isSocketValue = s.isSocketValue;
|
||||
delete s.isFileValue;
|
||||
delete s.isDirectoryValue;
|
||||
+ delete s.isSocketValue;
|
||||
|
||||
s.isFile = function () {
|
||||
return isFileValue;
|
||||
@@ -934,6 +936,9 @@ function payloadFileSync (pointer) {
|
||||
s.isDirectory = function () {
|
||||
return isDirectoryValue;
|
||||
};
|
||||
+ s.isSocket = function () {
|
||||
+ return isSocketValue;
|
||||
+ };
|
||||
s.isSymbolicLink = function () {
|
||||
return false;
|
||||
};
|
@ -24,7 +24,10 @@ const { promisify } = require('util');
|
||||
const execFileAsync = promisify(execFile);
|
||||
const patchesDir = 'patches';
|
||||
|
||||
/** Run the patch-package tool in a child process and wait for it to finish */
|
||||
/**
|
||||
* Run the patch-package tool in a child process and wait for it to finish
|
||||
* @param {string} patchDir
|
||||
*/
|
||||
async function patchPackage(patchDir) {
|
||||
// Equivalent to: `npx patch-package --patch-dir $patchDir`
|
||||
const result = await execFileAsync('node', [
|
||||
|
2
repo.yml
2
repo.yml
@ -16,3 +16,5 @@ upstream:
|
||||
url: 'https://github.com/balena-io-modules/etcher-sdk/'
|
||||
- repo: 'resin-compose-parse'
|
||||
url: 'https://github.com/balena-io-modules/resin-compose-parse'
|
||||
- repo: 'docker-progress'
|
||||
url: 'https://github.com/balena-io-modules/docker-progress'
|
||||
|
@ -21,6 +21,8 @@ import * as path from 'path';
|
||||
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../../helpers';
|
||||
|
||||
import { isV14 } from '../../../lib/utils/version';
|
||||
|
||||
describe('balena device', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
@ -57,9 +59,16 @@ describe('balena device', function () {
|
||||
|
||||
const lines = cleanOutput(out);
|
||||
|
||||
expect(lines).to.have.lengthOf(25);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||
if (isV14()) {
|
||||
expect(lines).to.have.lengthOf(26);
|
||||
expect(lines[0]).to.equal('sparkling-wood');
|
||||
expect(lines[2].split(':')[0].trim()).to.equal('Id');
|
||||
expect(lines[2].split(':')[1].trim()).to.equal('1747415');
|
||||
} else {
|
||||
expect(lines).to.have.lengthOf(25);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||
}
|
||||
});
|
||||
|
||||
it.skip('correctly handles devices with missing fields', async () => {
|
||||
@ -79,14 +88,20 @@ describe('balena device', function () {
|
||||
|
||||
const lines = cleanOutput(out);
|
||||
|
||||
expect(lines).to.have.lengthOf(14);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||
if (isV14()) {
|
||||
expect(lines).to.have.lengthOf(15);
|
||||
expect(lines[0]).to.equal('sparkling-wood');
|
||||
expect(lines[7].split(':')[1].trim()).to.equal('org/test app');
|
||||
} else {
|
||||
expect(lines).to.have.lengthOf(14);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('org/test app');
|
||||
}
|
||||
});
|
||||
|
||||
it('correctly handles devices with missing application', async () => {
|
||||
// Devices with missing applications will have application name set to `N/a`.
|
||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||
it.skip('correctly handles devices with missing fleet', async () => {
|
||||
// Devices with missing fleets will have fleet name set to `N/a`.
|
||||
// e.g. When user has a device associated with fleet that user is no longer a collaborator of.
|
||||
api.scope
|
||||
.get(
|
||||
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name,slug\)/,
|
||||
@ -103,8 +118,15 @@ describe('balena device', function () {
|
||||
|
||||
const lines = cleanOutput(out);
|
||||
|
||||
expect(lines).to.have.lengthOf(25);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
|
||||
if (isV14()) {
|
||||
expect(lines).to.have.lengthOf(26);
|
||||
expect(lines[0]).to.equal('sparkling-wood');
|
||||
expect(lines[9].split(':')[0].trim()).to.equal('Fleet');
|
||||
expect(lines[9].split(':')[1].trim()).to.equal('N/a');
|
||||
} else {
|
||||
expect(lines).to.have.lengthOf(25);
|
||||
expect(lines[0]).to.equal('== SPARKLING WOOD');
|
||||
expect(lines[6].split(':')[1].trim()).to.equal('N/a');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,8 @@ import * as path from 'path';
|
||||
import { apiResponsePath, BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../../helpers';
|
||||
|
||||
import { isV14 } from '../../../lib/utils/version';
|
||||
|
||||
describe('balena devices', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
@ -48,15 +50,24 @@ describe('balena devices', function () {
|
||||
|
||||
const lines = cleanOutput(out);
|
||||
|
||||
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
||||
'ID UUID DEVICE NAME DEVICE TYPE FLEET STATUS IS ONLINE SUPERVISOR VERSION OS VERSION DASHBOARD URL',
|
||||
);
|
||||
expect(lines).to.have.lengthOf.at.least(2);
|
||||
|
||||
expect(lines.some((l) => l.includes('org/test app'))).to.be.true;
|
||||
|
||||
// Devices with missing applications will have application name set to `N/a`.
|
||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
||||
if (isV14()) {
|
||||
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
||||
' Id Uuid Device name Device type Fleet Status Is online Supervisor version Os version Dashboard url ',
|
||||
);
|
||||
expect(lines).to.have.lengthOf.at.least(3);
|
||||
expect(lines.some((l) => l.includes('org/test app'))).to.be.true;
|
||||
// Devices with missing applications will have application name set to `N/a`.
|
||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
||||
} else {
|
||||
expect(lines[0].replace(/ +/g, ' ')).to.equal(
|
||||
'ID UUID DEVICE NAME DEVICE TYPE FLEET STATUS IS ONLINE SUPERVISOR VERSION OS VERSION DASHBOARD URL',
|
||||
);
|
||||
expect(lines).to.have.lengthOf.at.least(2);
|
||||
expect(lines.some((l) => l.includes('org/test app'))).to.be.true;
|
||||
// Devices with missing applications will have application name set to `N/a`.
|
||||
// e.g. When user has a device associated with app that user is no longer a collaborator of.
|
||||
expect(lines.some((l) => l.includes('N/a'))).to.be.true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019-2021 Balena Ltd.
|
||||
* Copyright 2019 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,6 +19,7 @@ import { expect } from 'chai';
|
||||
|
||||
import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../../helpers';
|
||||
import { isV14 } from '../../../lib/utils/version';
|
||||
|
||||
describe('balena devices supported', function () {
|
||||
let api: BalenaAPIMock;
|
||||
@ -50,7 +51,10 @@ describe('balena devices supported', function () {
|
||||
|
||||
const lines = cleanOutput(out, true);
|
||||
|
||||
expect(lines[0]).to.equal('SLUG ALIASES ARCH NAME');
|
||||
expect(lines[0]).to.equal(
|
||||
isV14() ? ' Slug Aliases Arch Name ' : 'SLUG ALIASES ARCH NAME',
|
||||
);
|
||||
|
||||
expect(lines).to.have.lengthOf.at.least(2);
|
||||
expect(lines).to.contain('intel-nuc nuc amd64 Intel NUC');
|
||||
expect(lines).to.contain(
|
||||
|
261
tests/commands/env/envs.spec.ts
vendored
261
tests/commands/env/envs.spec.ts
vendored
@ -19,7 +19,9 @@ import { expect } from 'chai';
|
||||
import { stripIndent } from '../../../build/utils/lazy';
|
||||
|
||||
import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
import { runCommand } from '../../helpers';
|
||||
import { runCommand, removeFirstNLines, trimLines } from '../../helpers';
|
||||
|
||||
import { isV14 } from '../../../lib/utils/version';
|
||||
|
||||
describe('balena envs', function () {
|
||||
const appName = 'test';
|
||||
@ -48,15 +50,30 @@ describe('balena envs', function () {
|
||||
|
||||
const { out, err } = await runCommand(`envs -f ${appName}`);
|
||||
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120110 svar1 svar1-value gh_user/testApp service1
|
||||
120111 svar2 svar2-value gh_user/testApp service2
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET SERVICE
|
||||
120110 svar1 svar1-value gh_user/testApp service1
|
||||
120111 svar2 svar2-value gh_user/testApp service2
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n',
|
||||
);
|
||||
);
|
||||
}
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
@ -66,12 +83,24 @@ describe('balena envs', function () {
|
||||
|
||||
const { out, err } = await runCommand(`envs -f ${appName} --config`);
|
||||
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120300 RESIN_SUPERVISOR_NATIVE_LOGGER false gh_user/testApp
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET
|
||||
120300 RESIN_SUPERVISOR_NATIVE_LOGGER false gh_user/testApp
|
||||
` + '\n',
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
@ -82,15 +111,19 @@ describe('balena envs', function () {
|
||||
|
||||
const { out, err } = await runCommand(`envs -cjf ${appName}`);
|
||||
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal([
|
||||
{
|
||||
fleet: 'gh_user/testApp',
|
||||
id: 120300,
|
||||
name: 'RESIN_SUPERVISOR_NATIVE_LOGGER',
|
||||
value: 'false',
|
||||
},
|
||||
]);
|
||||
expect(err.join('')).to.equal('');
|
||||
if (isV14()) {
|
||||
// TODO: Add tests once oclif json issue resolved.
|
||||
} else {
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal([
|
||||
{
|
||||
fleet: 'gh_user/testApp',
|
||||
id: 120300,
|
||||
name: 'RESIN_SUPERVISOR_NATIVE_LOGGER',
|
||||
value: 'false',
|
||||
},
|
||||
]);
|
||||
expect(err.join('')).to.equal('');
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list service variables for a test fleet (-s flag)', async () => {
|
||||
@ -104,14 +137,28 @@ describe('balena envs', function () {
|
||||
`envs -f ${appName} -s ${serviceName}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120111 svar2 svar2-value gh_user/testApp service2
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET SERVICE
|
||||
120111 svar2 svar2-value gh_user/testApp service2
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n',
|
||||
);
|
||||
);
|
||||
}
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
@ -126,14 +173,28 @@ describe('balena envs', function () {
|
||||
`envs -f ${appName} -s ${serviceName}`,
|
||||
);
|
||||
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120110 svar1 svar1-value gh_user/testApp ${serviceName}
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
expect(out.join('')).to.equal(
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET SERVICE
|
||||
120110 svar1 svar1-value gh_user/testApp ${serviceName}
|
||||
120101 var1 var1-val gh_user/testApp *
|
||||
120102 var2 22 gh_user/testApp *
|
||||
` + '\n',
|
||||
);
|
||||
);
|
||||
}
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
@ -148,8 +209,27 @@ describe('balena envs', function () {
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120110 svar1 svar1-value org/test * service1
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
120120 svar3 svar3-value org/test ${uuid} service1
|
||||
120121 svar4 svar4-value org/test ${uuid} service2
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120110 svar1 svar1-value org/test * service1
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
@ -161,10 +241,10 @@ describe('balena envs', function () {
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
|
||||
expect(out.join('')).to.equal(expected);
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
expect(out.join('')).to.equal(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list env variables for a test device (JSON output)', async () => {
|
||||
@ -176,7 +256,11 @@ describe('balena envs', function () {
|
||||
api.expectGetDeviceServiceVars();
|
||||
|
||||
const { out, err } = await runCommand(`envs -jd ${shortUUID}`);
|
||||
const expected = `[
|
||||
|
||||
if (isV14()) {
|
||||
// TODO: Add tests once oclif json issue resolved.
|
||||
} else {
|
||||
const expected = `[
|
||||
{ "id": 120101, "fleet": "org/test", "deviceUUID": "*", "name": "var1", "value": "var1-val", "serviceName": "*" },
|
||||
{ "id": 120102, "fleet": "org/test", "deviceUUID": "*", "name": "var2", "value": "22", "serviceName": "*" },
|
||||
{ "id": 120110, "fleet": "org/test", "deviceUUID": "*", "name": "svar1", "value": "svar1-value", "serviceName": "service1" },
|
||||
@ -187,7 +271,9 @@ describe('balena envs', function () {
|
||||
{ "id": 120204, "fleet": "org/test", "deviceUUID": "${fullUUID}", "name": "var4", "value": "44", "serviceName": "*" }
|
||||
]`;
|
||||
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal(JSON.parse(expected));
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal(JSON.parse(expected));
|
||||
}
|
||||
|
||||
expect(err.join('')).to.equal('');
|
||||
});
|
||||
|
||||
@ -199,17 +285,30 @@ describe('balena envs', function () {
|
||||
|
||||
const result = await runCommand(`envs -d ${shortUUID} --config`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120300 RESIN_SUPERVISOR_NATIVE_LOGGER false org/test *
|
||||
120400 RESIN_SUPERVISOR_POLL_INTERVAL 900900 org/test ${shortUUID}
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE
|
||||
120300 RESIN_SUPERVISOR_NATIVE_LOGGER false org/test *
|
||||
120400 RESIN_SUPERVISOR_POLL_INTERVAL 900900 org/test ${shortUUID}
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
|
||||
expect(out.join('')).to.equal(expected);
|
||||
expect(out.join('')).to.equal(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list service variables for a test device (-s flag)', async () => {
|
||||
@ -225,8 +324,25 @@ describe('balena envs', function () {
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
120121 svar4 svar4-value org/test ${uuid} service2
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120111 svar2 svar2-value org/test * service2
|
||||
120121 svar4 svar4-value org/test ${uuid} service2
|
||||
@ -236,10 +352,11 @@ describe('balena envs', function () {
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
|
||||
expect(out.join('')).to.equal(expected);
|
||||
expect(out.join('')).to.equal(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list env and service variables for a test device (unknown fleet)', async () => {
|
||||
@ -250,8 +367,23 @@ describe('balena envs', function () {
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120120 svar3 svar3-value N/A ${uuid} service1
|
||||
120121 svar4 svar4-value N/A ${uuid} service2
|
||||
120203 var3 var3-val N/A ${uuid} *
|
||||
120204 var4 44 N/A ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120120 svar3 svar3-value N/A ${uuid} service1
|
||||
120121 svar4 svar4-value N/A ${uuid} service2
|
||||
@ -259,10 +391,11 @@ describe('balena envs', function () {
|
||||
120204 var4 44 N/A ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
|
||||
expect(out.join('')).to.equal(expected);
|
||||
expect(out.join('')).to.equal(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list env and service vars for a test device (-s flags)', async () => {
|
||||
@ -278,8 +411,24 @@ describe('balena envs', function () {
|
||||
const uuid = shortUUID;
|
||||
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
|
||||
let { out } = result;
|
||||
let expected =
|
||||
stripIndent`
|
||||
if (isV14()) {
|
||||
let output = out.join('');
|
||||
output = trimLines(removeFirstNLines(output, 2));
|
||||
|
||||
const expected =
|
||||
stripIndent`
|
||||
120110 svar1 svar1-value org/test * ${serviceName}
|
||||
120120 svar3 svar3-value org/test ${uuid} ${serviceName}
|
||||
120101 var1 var1-val org/test * *
|
||||
120102 var2 22 org/test * *
|
||||
120203 var3 var3-val org/test ${uuid} *
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
expect(output).to.equal(expected);
|
||||
} else {
|
||||
let expected =
|
||||
stripIndent`
|
||||
ID NAME VALUE FLEET DEVICE SERVICE
|
||||
120110 svar1 svar1-value org/test * ${serviceName}
|
||||
120120 svar3 svar3-value org/test ${uuid} ${serviceName}
|
||||
@ -289,10 +438,11 @@ describe('balena envs', function () {
|
||||
120204 var4 44 org/test ${uuid} *
|
||||
` + '\n';
|
||||
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
out = out.map((l) => l.replace(/ +/g, ' '));
|
||||
expected = expected.replace(/ +/g, ' ');
|
||||
|
||||
expect(out.join('')).to.equal(expected);
|
||||
expect(out.join('')).to.equal(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('should successfully list env and service vars for a test device (-js flags)', async () => {
|
||||
@ -308,7 +458,11 @@ describe('balena envs', function () {
|
||||
const { out, err } = await runCommand(
|
||||
`envs -d ${shortUUID} -js ${serviceName}`,
|
||||
);
|
||||
const expected = `[
|
||||
|
||||
if (isV14()) {
|
||||
// TODO: Add tests once oclif json issue resolved.
|
||||
} else {
|
||||
const expected = `[
|
||||
{ "id": 120101, "fleet": "org/test", "deviceUUID": "*", "name": "var1", "value": "var1-val", "serviceName": "*" },
|
||||
{ "id": 120102, "fleet": "org/test", "deviceUUID": "*", "name": "var2", "value": "22", "serviceName": "*" },
|
||||
{ "id": 120110, "fleet": "org/test", "deviceUUID": "*", "name": "svar1", "value": "svar1-value", "serviceName": "${serviceName}" },
|
||||
@ -317,7 +471,8 @@ describe('balena envs', function () {
|
||||
{ "id": 120204, "fleet": "org/test", "deviceUUID": "${fullUUID}", "name": "var4", "value": "44", "serviceName": "*" }
|
||||
]`;
|
||||
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal(JSON.parse(expected));
|
||||
expect(err.join('')).to.equal('');
|
||||
expect(JSON.parse(out.join(''))).to.deep.equal(JSON.parse(expected));
|
||||
expect(err.join('')).to.equal('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -20,6 +20,8 @@ import { expect } from 'chai';
|
||||
import { BalenaAPIMock } from '../nock/balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../helpers';
|
||||
|
||||
import { isV14 } from '../../lib/utils/version';
|
||||
|
||||
describe('balena release', function () {
|
||||
let api: BalenaAPIMock;
|
||||
|
||||
@ -34,7 +36,7 @@ describe('balena release', function () {
|
||||
api.done();
|
||||
});
|
||||
|
||||
it('should show release details', async () => {
|
||||
it.skip('should show release details', async () => {
|
||||
api.expectGetRelease();
|
||||
const { out } = await runCommand('release 27fda508c');
|
||||
const lines = cleanOutput(out);
|
||||
@ -44,7 +46,7 @@ describe('balena release', function () {
|
||||
expect(lines[1]).to.contain(' 90247b54de4fa7a0a3cbc85e73c68039');
|
||||
});
|
||||
|
||||
it('should return release composition', async () => {
|
||||
it.skip('should return release composition', async () => {
|
||||
api.expectGetRelease();
|
||||
const { out } = await runCommand('release 27fda508c --composition');
|
||||
const lines = cleanOutput(out);
|
||||
@ -61,8 +63,14 @@ describe('balena release', function () {
|
||||
api.expectGetApplication();
|
||||
const { out } = await runCommand('releases someapp');
|
||||
const lines = cleanOutput(out);
|
||||
expect(lines.length).to.be.equal(2);
|
||||
expect(lines[1]).to.contain('142334');
|
||||
expect(lines[1]).to.contain('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
if (isV14()) {
|
||||
expect(lines.length).to.be.equal(3);
|
||||
expect(lines[2]).to.contain('142334');
|
||||
expect(lines[2]).to.contain('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
} else {
|
||||
expect(lines.length).to.be.equal(2);
|
||||
expect(lines[1]).to.contain('142334');
|
||||
expect(lines[1]).to.contain('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -160,8 +160,9 @@ async function startMockSshServer(): Promise<[Server, number]> {
|
||||
});
|
||||
|
||||
return new Promise<[Server, number]>((resolve, reject) => {
|
||||
// port 0: let the OS allocation any available TCP port number
|
||||
const listener = server.listen(0, '127.0.0.1', (err: Error) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
// this callback is called for the 'listening' event
|
||||
if (err) {
|
||||
console.error(`Error starting mock ssh server:\n${err}`);
|
||||
|
@ -141,10 +141,10 @@ describe('DeprecationChecker', function () {
|
||||
|
||||
getStub.resolves(mockCache);
|
||||
|
||||
// Force isTTY to be false (undefined). It happens to be true when
|
||||
// Force isTTY to be false. It happens to be true when
|
||||
// the tests run on balenaCI on macOS and Linux.
|
||||
const originalIsTTY = process.stderr.isTTY;
|
||||
process.stderr.isTTY = undefined;
|
||||
process.stderr.isTTY = false;
|
||||
let result: TestOutput;
|
||||
try {
|
||||
result = await runCommand('version');
|
||||
|
@ -106,21 +106,6 @@ describe('outputDataSet', function () {
|
||||
expect(splitHeader[1]).to.include('thing');
|
||||
});
|
||||
|
||||
/*
|
||||
it('should output fields in the order specified in `fields` param', async () => {
|
||||
const fields = ['thing_color', 'id', 'name'];
|
||||
const options = {};
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
|
||||
// split header using the `it` column as delimiter
|
||||
const splitHeader = headerLine.split('id');
|
||||
expect(splitHeader[0]).to.include('thing');
|
||||
expect(splitHeader[1]).to.include('name');
|
||||
});
|
||||
*/
|
||||
|
||||
it('should only output fields specified in `options.fields` if present', async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
@ -167,13 +152,41 @@ describe('outputDataSet', function () {
|
||||
expect(printLineSpy.getCall(0).firstArg).to.include('red');
|
||||
});
|
||||
|
||||
it(
|
||||
'should output `null` values using the provided value, ' +
|
||||
'if `options.displayNullValuesAs` is present',
|
||||
async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const nullValue = 'N/a';
|
||||
const options = {
|
||||
'no-header': true,
|
||||
displayNullValuesAs: nullValue,
|
||||
};
|
||||
|
||||
const extendedDataSet = [
|
||||
...dataSet,
|
||||
{
|
||||
name: 'item3',
|
||||
id: 3,
|
||||
thing_color: null,
|
||||
thing_shape: 'round',
|
||||
},
|
||||
];
|
||||
|
||||
await outputDataSet(extendedDataSet, fields, options);
|
||||
|
||||
expect(printLineSpy.callCount).to.equal(3);
|
||||
expect(printLineSpy.getCall(2).firstArg).to.include(nullValue);
|
||||
},
|
||||
);
|
||||
|
||||
it('should output data in json format, if `options.json` true', async () => {
|
||||
const fields = ['name', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
json: true,
|
||||
};
|
||||
|
||||
// TODO: I've run into an oclif cli-ux bug, where numbers are output as strings in json
|
||||
// TODO: I've run into an oclif cli-ux bug, where all types (number. bool etc.) are output as strings in json
|
||||
// (this can be seen by including 'id' in the fields list above).
|
||||
// Issue opened: https://github.com/oclif/cli-ux/issues/309
|
||||
// For now removing id for this test.
|
||||
|
@ -17,8 +17,8 @@
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as packageJSON from '../package.json';
|
||||
import { getChalk } from '../lib/utils/lazy';
|
||||
|
||||
const balenaExe = process.platform === 'win32' ? 'balena.exe' : 'balena';
|
||||
const standalonePath = path.resolve(__dirname, '..', 'build-bin', balenaExe);
|
||||
@ -353,3 +353,65 @@ export async function switchSentry(
|
||||
return sentryStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to an array of character codes
|
||||
* @param text the text to convert.
|
||||
* @returns an array of character codes representing the text.
|
||||
*/
|
||||
export function stringToCharCodes(text: string) {
|
||||
return text.split('').map((c) => {
|
||||
return c.charCodeAt(0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove leaving and trailing whitespace from each lime of a string.
|
||||
* @param text the text to process
|
||||
* @returns a copy of the text with the lines trimmed.
|
||||
*/
|
||||
export function trimLines(text: string) {
|
||||
let lines = text.split('\n');
|
||||
lines = lines.map((l) => l.trim());
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad each line with characters at beginning and end.
|
||||
* @param text the text to pad.
|
||||
* @param startPad the string to prepend each line with.
|
||||
* @param endPad the string to append each line with.
|
||||
* @returns a copy of the text with the specified padding.
|
||||
*/
|
||||
export function padLines(text: string, startPad: string, endPad: string = '') {
|
||||
let lines = text.split('\n');
|
||||
lines = lines.map((l) => {
|
||||
return l === '' ? '' : `${startPad}${l}${endPad}`;
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format first nLines bold.
|
||||
* @param text the text to format
|
||||
* @param nLines number of liens to format (from top)
|
||||
* @returns a copy of the text with the specified number of top lines formatted bold.
|
||||
*/
|
||||
export function boldFirstNLines(text: string, nLines: number) {
|
||||
const chalk = getChalk();
|
||||
let lines = text.split('\n');
|
||||
lines = lines.map((l, i) => {
|
||||
return i < nLines ? chalk.bold(l) : l;
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first nLines bold.
|
||||
* @param text the text to format
|
||||
* @param nLines number of liens to format (from top)
|
||||
* @returns a copy of the text with the first nLines removed.
|
||||
*/
|
||||
export function removeFirstNLines(text: string, nLines: number) {
|
||||
return text.split('\n').slice(nLines).join(`\n`);
|
||||
}
|
||||
|
@ -113,7 +113,9 @@ async function createProxyServer(): Promise<[number, number]> {
|
||||
let proxyPort = 0; // TCP port number, 0 means automatic allocation
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const listener = server.listen(0, '127.0.0.1', (err: Error) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
if (err) {
|
||||
console.error(`Error starting proxy server:\n${err}`);
|
||||
reject(err);
|
||||
@ -195,7 +197,9 @@ async function createInterceptorServer(): Promise<number> {
|
||||
let interceptorPort = 0;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const listener = server.listen(0, '127.0.0.1', (err: Error) => {
|
||||
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
|
||||
// callback type is `() => void`, but our code assumes `(err: Error) => void`
|
||||
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
|
||||
if (err) {
|
||||
console.error(`Error starting interceptor server:\n${err}`);
|
||||
reject(err);
|
||||
|
@ -24,10 +24,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/@ronomon/direct-io/binding.node
|
||||
%2: path-to-executable/binding.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
|
||||
@ -100,18 +96,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/fsevents/fsevents.node
|
||||
%2: path-to-executable/fsevents.node
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/fsevents/fsevents.node
|
||||
%2: path-to-executable/fsevents.node
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/xxhash/build/Release/hash.node
|
||||
%2: path-to-executable/hash.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
|
@ -24,10 +24,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/open/xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/@ronomon/direct-io/binding.node
|
||||
%2: path-to-executable/binding.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
|
||||
@ -100,10 +96,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/drivelist/scripts/win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules/xxhash/build/Release/hash.node
|
||||
%2: path-to-executable/hash.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules/opn/xdg-open
|
||||
|
@ -24,10 +24,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\open\xdg-open
|
||||
%2: path-to-executable/xdg-open
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules\@ronomon\direct-io\binding.node
|
||||
%2: path-to-executable/binding.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\etcher-sdk\node_modules\drivelist\build\Release\drivelist.node
|
||||
@ -100,10 +96,6 @@
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\drivelist\scripts\win32.bat
|
||||
%2: path-to-executable/drivelist/win32.bat
|
||||
> Warning Cannot include addon %1 into executable.
|
||||
The addon must be distributed with executable as %2.
|
||||
%1: node_modules\xxhash\build\Release\hash.node
|
||||
%2: path-to-executable/hash.node
|
||||
> Warning Cannot include file %1 into executable.
|
||||
The file must be distributed with executable as %2.
|
||||
%1: node_modules\opn\xdg-open
|
||||
|
@ -71,7 +71,6 @@ describe('detectEncoding() function', function () {
|
||||
'node_modules/.bin/etcher-image-write',
|
||||
'node_modules/.bin/mocha',
|
||||
'node_modules/.bin/rimraf',
|
||||
'node_modules/.bin/gulp',
|
||||
'node_modules/.bin/tsc',
|
||||
'node_modules/.bin/balena-lint',
|
||||
'node_modules/.bin/catch-uncommitted',
|
||||
|
@ -1,11 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"include": [
|
||||
"./automation/**/*",
|
||||
"./lib/**/*",
|
||||
"./patches/*",
|
||||
"./tests/**/*",
|
||||
"./typings/**/*",
|
||||
"gulpfile.js"
|
||||
"./typings/**/*"
|
||||
]
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.dev.json",
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false,
|
||||
"checkJs": true
|
||||
}
|
||||
}
|
@ -18,8 +18,7 @@
|
||||
"./node_modules/etcher-sdk/typings",
|
||||
"./typings"
|
||||
],
|
||||
"preserveSymlinks": true,
|
||||
"allowJs": true
|
||||
"preserveSymlinks": true
|
||||
},
|
||||
"include": [
|
||||
"./lib/**/*",
|
||||
|
22
typings/common-tags/index.d.ts
vendored
Normal file
22
typings/common-tags/index.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'common-tags/lib/stripIndent' {
|
||||
import { stripIndent } from 'common-tags';
|
||||
|
||||
export = stripIndent;
|
||||
}
|
Reference in New Issue
Block a user