mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
76 Commits
developmen
...
v12.55.5
Author | SHA1 | Date | |
---|---|---|---|
aa286cc0e7 | |||
8abeb6aed7 | |||
f285880135 | |||
2b5c387313 | |||
8babf4c908 | |||
bfc995e948 | |||
c6a0bc0fba | |||
ae69accf0f | |||
cfcace4c99 | |||
6e07db0813 | |||
5c40c8d51f | |||
d827005154 | |||
76081343cc | |||
f3fb9b6bdf | |||
c125e0b38d | |||
73b2f6b4b1 | |||
fdc0d08e96 | |||
e431a59af7 | |||
41a2dbe60c | |||
6ba67eefdb | |||
3b885ad906 | |||
5574dc0318 | |||
fcea91bfb6 | |||
7316c4e075 | |||
389b7a1463 | |||
09d004423c | |||
97978ff812 | |||
498e21f0ab | |||
257dd514ed | |||
85cbdd4947 | |||
73625611da | |||
d2a5a9ba86 | |||
1cd78215e0 | |||
6d744d0b07 | |||
9d312bcd12 | |||
e22aa847e3 | |||
0d1ca67d5b | |||
c4a5a25f03 | |||
b183d88400 | |||
2b6a2142eb | |||
58b29bf4bb | |||
fc0903a414 | |||
cea23f5d5e | |||
5a9b5e3b08 | |||
52138d41eb | |||
5acdc63068 | |||
b546e4dd97 | |||
e4870916e2 | |||
3ca93448cd | |||
f66395e2d5 | |||
952d782e90 | |||
d53c9b3c50 | |||
2f706c0200 | |||
d64b6deb81 | |||
55fc9b2ade | |||
6c29d0ae27 | |||
f46452f6de | |||
c166ec7597 | |||
7325c79888 | |||
2a29b386eb | |||
23b07f8a41 | |||
6d641b4841 | |||
7b498149b1 | |||
ae5ea0f4e8 | |||
f635f648da | |||
3d4e2cf823 | |||
ef3b630887 | |||
19040ccb6c | |||
8e712ac910 | |||
c401ed35ac | |||
94be97313b | |||
48053ecefc | |||
cc60e86507 | |||
bd774e8553 | |||
c493c33e38 | |||
112a7b8194 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -6,7 +6,7 @@
|
||||
*.sh text eol=lf
|
||||
|
||||
# lf for the docs as it's auto-generated and will otherwise trigger an uncommited error on windows
|
||||
doc/cli.markdown text eol=lf
|
||||
docs/balena-cli.md text eol=lf
|
||||
# crlf for the eol conversion test files
|
||||
tests/test-data/projects/docker-compose/basic/service2/file2-crlf.sh eol=crlf
|
||||
tests/test-data/projects/no-docker-compose/basic/src/windows-crlf.sh eol=crlf
|
||||
|
@ -3,5 +3,8 @@ module.exports = {
|
||||
require: 'ts-node/register/transpile-only',
|
||||
file: './tests/config-tests',
|
||||
timeout: 12000,
|
||||
// To test only, say, 'push.spec.ts', do it as follows so that
|
||||
// requests are authenticated:
|
||||
// spec: ['tests/auth/*.spec.ts', 'tests/**/deploy.spec.ts'],
|
||||
spec: 'tests/**/*.spec.ts',
|
||||
};
|
||||
|
@ -5,13 +5,11 @@ npm:
|
||||
os: ubuntu
|
||||
architecture: x86_64
|
||||
node_versions:
|
||||
- "10"
|
||||
- "12"
|
||||
- "14"
|
||||
- name: linux
|
||||
os: alpine
|
||||
architecture: x86_64
|
||||
node_versions:
|
||||
- "10"
|
||||
- "12"
|
||||
- "14"
|
||||
|
@ -1,3 +1,454 @@
|
||||
- commits:
|
||||
- subject: Drop unnecessary directory list created during balena deploy & push
|
||||
hash: f2858801350077305a92a6ba32659a46dd3e1e39
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
|
||||
signed-off-by: Thodoris Greasidis <thodoris@balena.io>
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 12.55.5
|
||||
date: 2021-12-13T23:29:59.103Z
|
||||
- commits:
|
||||
- subject: 'os download, os versions: Accept device type aliases'
|
||||
hash: bfc995e9486024628f3599f52b9c0fb13d815311
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.55.4
|
||||
date: 2021-12-10T00:56:34.562Z
|
||||
- commits:
|
||||
- subject: >-
|
||||
os download: Improve error message when not logged in (balenaOS ESR
|
||||
versions)
|
||||
hash: 6e07db08131a59622d06459ecb62b47adeb52826
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: 'os download: Assume ''.prod'' suffix by default for all balenaOS versions'
|
||||
hash: 5c40c8d51f16db9f9e28e4286f4c2c277a993f37
|
||||
body: ''
|
||||
footer:
|
||||
Resolves: '#2387'
|
||||
resolves: '#2387'
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.55.3
|
||||
date: 2021-12-09T18:51:23.706Z
|
||||
- commits:
|
||||
- subject: 'v13 preparations: Standardize command data output'
|
||||
hash: f3fb9b6bdffa1bf65c90f8061bf83663a5ad7b85
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Scott Lowe <scott@balena.io>
|
||||
signed-off-by: Scott Lowe <scott@balena.io>
|
||||
author: Scott Lowe
|
||||
nested: []
|
||||
version: 12.55.2
|
||||
date: 2021-12-08T11:22:59.156Z
|
||||
- commits:
|
||||
- subject: 'chore: Bump multicast-dns to rebased commit'
|
||||
hash: fdc0d08e963daf9bccdf8150962c365ff4ab32e3
|
||||
body: >
|
||||
Otherwise npm install fails due to the missing commit in
|
||||
npm-shrinkwrap.json
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
See: 'https://github.com/balena-io-modules/multicast-dns/pull/1'
|
||||
see: 'https://github.com/balena-io-modules/multicast-dns/pull/1'
|
||||
author: Kyle Harding
|
||||
nested: []
|
||||
version: 12.55.1
|
||||
date: 2021-11-30T16:10:20.410Z
|
||||
- commits:
|
||||
- subject: Add provisioning key name option to config generate options
|
||||
hash: 6ba67eefdbb47e1aa901be022b497e028260ac0b
|
||||
body: ''
|
||||
footer:
|
||||
Change-Type: minor
|
||||
change-type: minor
|
||||
Signed-off-by: Nitish Agarwal 1592163+nitishagar@users.noreply.github.com
|
||||
signed-off-by: Nitish Agarwal 1592163+nitishagar@users.noreply.github.com
|
||||
author: Nitish Agarwal
|
||||
nested: []
|
||||
version: 12.55.0
|
||||
date: 2021-11-29T10:48:16.077Z
|
||||
- commits:
|
||||
- subject: 'os configure, local configure: Reuse disk partition scanning logic'
|
||||
hash: fcea91bfb6cbcc32117d7242460f1e58476339ac
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.54.5
|
||||
date: 2021-11-27T01:19:18.487Z
|
||||
- commits:
|
||||
- subject: Bump 'balena-lint' and fix 'no-floating-promises' warnings
|
||||
hash: 09d004423cae2bb699aa344e17164ce392e1108b
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.54.4
|
||||
date: 2021-11-26T16:03:03.934Z
|
||||
- commits:
|
||||
- subject: Improve directory scan speed prior to tarballing
|
||||
hash: 257dd514ed7c0f6988b8a47219991cc4f61b4529
|
||||
body: >
|
||||
This changes improves the speed that the project is tarballed by
|
||||
switching from
|
||||
|
||||
`klaw` to `recursive-fs` and not running `lstat` on files that are
|
||||
ignored.
|
||||
|
||||
Whilst testing with the Jellyfish repository, which contains a number of
|
||||
|
||||
sub directories, each with their own node_modules folder, I was able to
|
||||
|
||||
reduce the time taken to scan and tarball the project from 70s to 11s,
|
||||
|
||||
which is a massive improvement.
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
author: Lucian Buzzo
|
||||
nested: []
|
||||
version: 12.54.3
|
||||
date: 2021-11-26T13:59:56.032Z
|
||||
- commits:
|
||||
- subject: Set the correct target state when using Compose "image" field
|
||||
hash: d2a5a9ba8638e712a4b286ce6775158c89615b08
|
||||
body: >
|
||||
Fixes #2394
|
||||
|
||||
|
||||
When pushing to a device in local mode, if a service is not external,
|
||||
and uses
|
||||
|
||||
an `image` field, that value should be used for tags and target state,
|
||||
otherwise
|
||||
|
||||
it won't match the image name generated on the device by balenaEngine.
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
signed-off-by: Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
author: Lucian Buzzo
|
||||
nested: []
|
||||
version: 12.54.2
|
||||
date: 2021-11-26T10:14:56.289Z
|
||||
- commits:
|
||||
- subject: Fix mistake in `config generate` examples
|
||||
hash: b183d884007365d1240fd825e65ed8d95b7ce34e
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Scott Lowe <scott@balena.io>
|
||||
signed-off-by: Scott Lowe <scott@balena.io>
|
||||
author: Scott Lowe
|
||||
nested: []
|
||||
version: 12.54.1
|
||||
date: 2021-11-25T22:35:08.652Z
|
||||
- commits:
|
||||
- subject: Improve UX for offline usage
|
||||
hash: 2b6a2142eb1929694e0eb14cf26bac4ae9b6dca1
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
Resolves: '#2372'
|
||||
resolves: '#2372'
|
||||
Signed-off-by: Scott Lowe <scott@balena.io>
|
||||
signed-off-by: Scott Lowe <scott@balena.io>
|
||||
author: Scott Lowe
|
||||
nested: []
|
||||
version: 12.54.0
|
||||
date: 2021-11-25T20:45:24.329Z
|
||||
- commits:
|
||||
- subject: >-
|
||||
Avoid creating an extra provisioning API key in os configure & config
|
||||
generate
|
||||
hash: 58b29bf4bbd96a57804b7be86aaac1d3cd8cc473
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Changelog-entry: >-
|
||||
Avoid creating an extra provisioning API key in os configure & config
|
||||
generate
|
||||
changelog-entry: >-
|
||||
Avoid creating an extra provisioning API key in os configure & config
|
||||
generate
|
||||
See: >-
|
||||
https://github.com/balena-io/balena-cli/pull/2294#discussion_r756499196
|
||||
see: >-
|
||||
https://github.com/balena-io/balena-cli/pull/2294#discussion_r756499196
|
||||
Signed-off-by: Thodoris Greasidis <thodoris@balena.io>
|
||||
signed-off-by: Thodoris Greasidis <thodoris@balena.io>
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 12.53.2
|
||||
date: 2021-11-25T14:45:34.458Z
|
||||
- commits:
|
||||
- subject: Transitional changes to doc files for landr implementation
|
||||
hash: 5a9b5e3b080eccded0aa7d5dfe9276df9637ef83
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Scott Lowe <scott@balena.io>
|
||||
signed-off-by: Scott Lowe <scott@balena.io>
|
||||
author: Scott Lowe
|
||||
nested: []
|
||||
version: 12.53.1
|
||||
date: 2021-11-25T10:07:50.877Z
|
||||
- commits:
|
||||
- subject: 'config read/write/inject: Avoid need for internet access'
|
||||
hash: b546e4dd97caf4a3da4c3eea140bc201fb6e59ef
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: 'config read: Add ''--json'' option for JSON output'
|
||||
hash: e4870916e233e1540bc4b25d5bde13b8e4e3bed7
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.53.0
|
||||
date: 2021-11-25T02:11:13.469Z
|
||||
- commits:
|
||||
- subject: Delete 'doc/automated-init.md' and improve 'balena help device init'
|
||||
hash: 952d782e905c733f7d98fc4524850d399adbefdc
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.52.2
|
||||
date: 2021-11-24T18:31:20.275Z
|
||||
- commits:
|
||||
- subject: 'push/build: Add test cases for .dockerignore filtering corner cases'
|
||||
hash: d64b6deb81d32fe16c79d97ab5a4699512b74387
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.52.1
|
||||
date: 2021-11-22T01:53:27.126Z
|
||||
- commits:
|
||||
- subject: >-
|
||||
os download: Display OS version actually downloaded (range or
|
||||
'recommended')
|
||||
hash: f46452f6de7bbfa00e073986d47a85608e5a9fea
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: 'os versions, os download: Add support for balenaOS ESR versions'
|
||||
hash: c166ec75979fcaa8bd123a1bf84f379279050fd4
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.52.0
|
||||
date: 2021-11-20T00:51:09.369Z
|
||||
- commits:
|
||||
- subject: 'deploy: Ensure the release fails if an image''s digest (hash) is missing'
|
||||
hash: 23b07f8a41c82c0b23a38bac55cbd3874300dd58
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.51.3
|
||||
date: 2021-11-16T11:58:31.895Z
|
||||
- commits:
|
||||
- subject: Update balena CI configuration (remove Node v10 from npm pipeline list)
|
||||
hash: ae5ea0f4e85008624ff46fe10998fadbd8a48b15
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
version: 12.51.2
|
||||
date: 2021-11-16T00:07:56.053Z
|
||||
- commits:
|
||||
- subject: Fix forums support link in README.md
|
||||
hash: 112a7b8194d098e9c66754eb256590e13f54fe29
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Scott Lowe <scott@balena.io>
|
||||
signed-off-by: Scott Lowe <scott@balena.io>
|
||||
author: Scott Lowe
|
||||
nested: []
|
||||
version: 12.51.1
|
||||
date: 2021-10-25T17:29:36.975Z
|
||||
- commits:
|
||||
- subject: Add support for YAML anchors and aliases in 'docker-compose.yml'
|
||||
hash: 8e712ac91055c4efde885854488000a27c6b483d
|
||||
body: >
|
||||
This allows project files to define services from generic fragments by
|
||||
leveraging YAML's anchors and aliases. See here for an example:
|
||||
https://github.com/compose-spec/compose-spec/blob/43f6537b2c8f01b6d3f0e184d13a0f3cb93d38d7/spec.md#fragments
|
||||
|
||||
|
||||
Removing the FAILSAFE_SCHEMA flag is not expected to break existing
|
||||
project files, since the default behaviour is more liberal, or cause
|
||||
problems down the road given we perform validation immediately after.
|
||||
Docs for the flag:
|
||||
https://github.com/nodeca/js-yaml#load-string---options-
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: dfunckt
|
||||
nested: []
|
||||
version: 12.51.0
|
||||
date: 2021-10-22T16:50:50.004Z
|
||||
- commits:
|
||||
- subject: 'preload: Avoid possible ValueError when parsing storage driver'
|
||||
hash: 48053ecefc00e451f3ee5c9b82b2b398978ec229
|
||||
body: |
|
||||
Update balena-preload from 10.5.0 to 11.0.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
author: Kyle Harding
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Avoid creating multiple preload containers
|
||||
hash: 6b5b6428833ce2cd5c53c2051d6f515f1b3e4c37
|
||||
body: |
|
||||
This was only caught when we started correctly naming
|
||||
the preload container by switching from `Name` to `name` in
|
||||
our createContainer options.
|
||||
|
||||
Previously we were creating two containers but they had unique
|
||||
random names so we never saw the conflict.
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
author: Kyle Harding
|
||||
nested: []
|
||||
- subject: 'major: Remove balena-preload script in favor of use with CLI'
|
||||
hash: 7de06434155913b199024683e62c85e94f1d1cb1
|
||||
body: ''
|
||||
footer:
|
||||
Signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
author: Lorenzo Alberto Maria Ambrosi
|
||||
nested: []
|
||||
- subject: Fix missing 'await' for getEdisonPartitions()
|
||||
hash: 9a2ebfdb2a5375304ee5cf2ba6321b93f93886ed
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: Add extra type information (refactor bind mount array)
|
||||
hash: 5b8d21e68d9da902f7304a210cb1abe22420d1d0
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: Run linter
|
||||
hash: 456c727e6518317588b31e28ffb734377fb85e76
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Paulo Castro
|
||||
nested: []
|
||||
- subject: 'major: Convert to typescript'
|
||||
hash: ce241be0a780bdff87f4513e4d5a0ec63d72ac7e
|
||||
body: ''
|
||||
footer:
|
||||
Signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
author: Lorenzo Alberto Maria Ambrosi
|
||||
nested: []
|
||||
- subject: 'patch: Fix incorrect python List index check'
|
||||
hash: 85d404000ac6e9b4ac83ce1feed33789deca182b
|
||||
body: ''
|
||||
footer:
|
||||
Signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
signed-off-by: >-
|
||||
Lorenzo Alberto Maria Ambrosi
|
||||
<lorenzothunder.ambrosi@gmail.com>
|
||||
author: Lorenzo Alberto Maria Ambrosi
|
||||
nested: []
|
||||
version: balena-preload-11.0.0
|
||||
date: 2021-10-13T18:20:43.867Z
|
||||
version: 12.50.3
|
||||
date: 2021-10-20T15:30:14.694Z
|
||||
- commits:
|
||||
- subject: Error message when renaming a fleet now mentions the target name.
|
||||
hash: c493c33e3896784ee60c9d4ac79721ca6b96a778
|
||||
body: ''
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
|
||||
signed-off-by: Carlo Miguel F. Cruz <carloc@balena.io>
|
||||
author: Carlo Miguel F. Cruz
|
||||
nested: []
|
||||
version: 12.50.2
|
||||
date: 2021-10-05T09:04:40.995Z
|
||||
- commits:
|
||||
- subject: Update dependencies (@sentry/node error reporting)
|
||||
hash: 08dfc945f3ed1c518a7d1830a5a37d72fd5739fd
|
||||
|
113
CHANGELOG.md
113
CHANGELOG.md
@ -4,6 +4,119 @@ 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/).
|
||||
|
||||
## 12.55.5 - 2021-12-13
|
||||
|
||||
* Drop unnecessary directory list created during balena deploy & push [Thodoris Greasidis]
|
||||
|
||||
## 12.55.4 - 2021-12-10
|
||||
|
||||
* os download, os versions: Accept device type aliases [Paulo Castro]
|
||||
|
||||
## 12.55.3 - 2021-12-09
|
||||
|
||||
* os download: Improve error message when not logged in (balenaOS ESR versions) [Paulo Castro]
|
||||
* os download: Assume '.prod' suffix by default for all balenaOS versions [Paulo Castro]
|
||||
|
||||
## 12.55.2 - 2021-12-08
|
||||
|
||||
* v13 preparations: Standardize command data output [Scott Lowe]
|
||||
|
||||
## 12.55.1 - 2021-11-30
|
||||
|
||||
* chore: Bump multicast-dns to rebased commit [Kyle Harding]
|
||||
|
||||
## 12.55.0 - 2021-11-29
|
||||
|
||||
* Add provisioning key name option to config generate options [Nitish Agarwal]
|
||||
|
||||
## 12.54.5 - 2021-11-27
|
||||
|
||||
* os configure, local configure: Reuse disk partition scanning logic [Paulo Castro]
|
||||
|
||||
## 12.54.4 - 2021-11-26
|
||||
|
||||
* Bump 'balena-lint' and fix 'no-floating-promises' warnings [Paulo Castro]
|
||||
|
||||
## 12.54.3 - 2021-11-26
|
||||
|
||||
* Improve directory scan speed prior to tarballing [Lucian Buzzo]
|
||||
|
||||
## 12.54.2 - 2021-11-26
|
||||
|
||||
* Set the correct target state when using Compose "image" field [Lucian Buzzo]
|
||||
|
||||
## 12.54.1 - 2021-11-25
|
||||
|
||||
* Fix mistake in `config generate` examples [Scott Lowe]
|
||||
|
||||
## 12.54.0 - 2021-11-25
|
||||
|
||||
* Improve UX for offline usage [Scott Lowe]
|
||||
|
||||
## 12.53.2 - 2021-11-25
|
||||
|
||||
* Avoid creating an extra provisioning API key in os configure & config generate [Thodoris Greasidis]
|
||||
|
||||
## 12.53.1 - 2021-11-25
|
||||
|
||||
* Transitional changes to doc files for landr implementation [Scott Lowe]
|
||||
|
||||
## 12.53.0 - 2021-11-25
|
||||
|
||||
* config read/write/inject: Avoid need for internet access [Paulo Castro]
|
||||
* config read: Add '--json' option for JSON output [Paulo Castro]
|
||||
|
||||
## 12.52.2 - 2021-11-24
|
||||
|
||||
* Delete 'doc/automated-init.md' and improve 'balena help device init' [Paulo Castro]
|
||||
|
||||
## 12.52.1 - 2021-11-22
|
||||
|
||||
* push/build: Add test cases for .dockerignore filtering corner cases [Paulo Castro]
|
||||
|
||||
## 12.52.0 - 2021-11-20
|
||||
|
||||
* os download: Display OS version actually downloaded (range or 'recommended') [Paulo Castro]
|
||||
* os versions, os download: Add support for balenaOS ESR versions [Paulo Castro]
|
||||
|
||||
## 12.51.3 - 2021-11-16
|
||||
|
||||
* deploy: Ensure the release fails if an image's digest (hash) is missing [Paulo Castro]
|
||||
|
||||
## 12.51.2 - 2021-11-16
|
||||
|
||||
* Update balena CI configuration (remove Node v10 from npm pipeline list) [Paulo Castro]
|
||||
|
||||
## 12.51.1 - 2021-10-25
|
||||
|
||||
* Fix forums support link in README.md [Scott Lowe]
|
||||
|
||||
## 12.51.0 - 2021-10-22
|
||||
|
||||
* Add support for YAML anchors and aliases in 'docker-compose.yml' [dfunckt]
|
||||
|
||||
## 12.50.3 - 2021-10-20
|
||||
|
||||
|
||||
<details>
|
||||
<summary> preload: Avoid possible ValueError when parsing storage driver [Kyle Harding] </summary>
|
||||
|
||||
> ### balena-preload-11.0.0 - 2021-10-13
|
||||
>
|
||||
> * Avoid creating multiple preload containers [Kyle Harding]
|
||||
> * major: Remove balena-preload script in favor of use with CLI [Lorenzo Alberto Maria Ambrosi]
|
||||
> * Fix missing 'await' for getEdisonPartitions() [Paulo Castro]
|
||||
> * Add extra type information (refactor bind mount array) [Paulo Castro]
|
||||
> * Run linter [Paulo Castro]
|
||||
> * major: Convert to typescript [Lorenzo Alberto Maria Ambrosi]
|
||||
> * patch: Fix incorrect python List index check [Lorenzo Alberto Maria Ambrosi]
|
||||
>
|
||||
</details>
|
||||
|
||||
## 12.50.2 - 2021-10-05
|
||||
|
||||
* Error message when renaming a fleet now mentions the target name. [Carlo Miguel F. Cruz]
|
||||
|
||||
## 12.50.1 - 2021-09-30
|
||||
|
||||
* Update dependencies (@sentry/node error reporting) [Paulo Castro]
|
||||
|
@ -105,12 +105,12 @@ npm run update balena-sdk ^13.0.0 major
|
||||
|
||||
## Editing documentation files (README, INSTALL, Reference website...)
|
||||
|
||||
The `doc/cli.markdown` file is automatically generated by running `npm run build:doc` (which also
|
||||
The `docs/balena-cli.md` file is automatically generated by running `npm run build:doc` (which also
|
||||
runs as part of `npm run build`). That file is then pulled by scripts in the
|
||||
[balena-io/docs](https://github.com/balena-io/docs/) GitHub repo for publishing at the [CLI
|
||||
Documentation page](https://www.balena.io/docs/reference/cli/).
|
||||
|
||||
The content sources for the auto generation of `doc/cli.markdown` are:
|
||||
The content sources for the auto generation of `docs/balena-cli.md` are:
|
||||
|
||||
* [Selected
|
||||
sections](https://github.com/balena-io/balena-cli/blob/v12.23.0/automation/capitanodoc/capitanodoc.ts#L199-L204)
|
||||
@ -120,7 +120,7 @@ The content sources for the auto generation of `doc/cli.markdown` are:
|
||||
* `lib/commands/env/add.ts`
|
||||
|
||||
The README file is manually edited, but subsections are automatically extracted for inclusion in
|
||||
`doc/cli.markdown` by the `getCapitanoDoc()` function in
|
||||
`docs/balena-cli.md` by the `getCapitanoDoc()` function in
|
||||
[`automation/capitanodoc/capitanodoc.ts`](https://github.com/balena-io/balena-cli/blob/master/automation/capitanodoc/capitanodoc.ts).
|
||||
|
||||
The `INSTALL*.md` and `TROUBLESHOOTING.md` files are also manually edited.
|
||||
|
@ -144,7 +144,7 @@ To learn more, troubleshoot issues, or to contact us for support:
|
||||
|
||||
* Check the [masterclass tutorials](https://www.balena.io/docs/learn/more/masterclasses/overview/)
|
||||
* Check our [FAQ / troubleshooting document](https://github.com/balena-io/balena-cli/blob/master/TROUBLESHOOTING.md)
|
||||
* Ask us a question through the [balenaCloud forum](https://forums.balena.io/c/balena-cloud)
|
||||
* Ask us a question in the [balena forums](https://forums.balena.io/c/product-support)
|
||||
|
||||
For CLI bug reports or feature requests, check the
|
||||
[CLI GitHub issues](https://github.com/balena-io/balena-cli/issues/).
|
||||
|
@ -310,7 +310,7 @@ async function zipPkg() {
|
||||
archive.on('warning', console.warn);
|
||||
|
||||
archive.pipe(outputStream);
|
||||
archive.finalize();
|
||||
archive.finalize().catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,4 +105,5 @@ async function printMarkdown() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
printMarkdown();
|
||||
|
@ -24,15 +24,15 @@ const simplegit = require('simple-git/promise');
|
||||
const ROOT = path.normalize(path.join(__dirname, '..'));
|
||||
|
||||
/**
|
||||
* Compare the timestamp of cli.markdown with the timestamp of staged files,
|
||||
* issuing an error if cli.markdown is older.
|
||||
* If cli.markdown does not require updating and the developer cannot run
|
||||
* Compare the timestamp of balena-cli.md with the timestamp of staged files,
|
||||
* issuing an error if balena-cli.md is older.
|
||||
* If balena-cli.md does not require updating and the developer cannot run
|
||||
* `npm run build` on their laptop, the error message suggests a workaround
|
||||
* using `touch`.
|
||||
*/
|
||||
async function checkBuildTimestamps() {
|
||||
const git = simplegit(ROOT);
|
||||
const docFile = path.join(ROOT, 'doc', 'cli.markdown');
|
||||
const docFile = path.join(ROOT, 'docs', 'balena-cli.md');
|
||||
const [docStat, gitStatus] = await Promise.all([
|
||||
fs.stat(docFile),
|
||||
git.status(),
|
||||
|
@ -103,4 +103,5 @@ export async function run(args?: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
run();
|
||||
|
@ -136,4 +136,5 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
main();
|
||||
|
@ -1,112 +0,0 @@
|
||||
# Provisioning balena devices in automated (non-interactive) mode
|
||||
|
||||
This document describes how to run the `device init` command in non-interactive mode.
|
||||
|
||||
It requires collecting some preliminary information _once_.
|
||||
|
||||
The final command to provision the device looks like this:
|
||||
|
||||
```bash
|
||||
balena device init --fleet FLEET_ID --os-version OS_VERSION --drive DRIVE --config CONFIG_FILE --yes
|
||||
|
||||
```
|
||||
|
||||
You can run this command as many times as you need, putting the new medium (SD card / USB stick) each time.
|
||||
|
||||
But before you can run it you need to collect the parameters and build the configuration file. Keep reading to figure out how to do it.
|
||||
|
||||
|
||||
## Collect all the required parameters.
|
||||
|
||||
1. `DEVICE_TYPE`. Run
|
||||
```bash
|
||||
balena devices supported
|
||||
```
|
||||
and find the _slug_ for your target device type, like _raspberrypi3_.
|
||||
|
||||
1. `FLEET_ID`. Create a fleet (`balena fleet create FLEET_NAME --type DEVICE_TYPE`) or find an existing one (`balena fleets`) and notice its ID.
|
||||
|
||||
1. `OS_VERSION`. Run
|
||||
```bash
|
||||
balena os versions DEVICE_TYPE
|
||||
```
|
||||
and pick the version that you need, like _v2.0.6+rev1.prod_.
|
||||
_Note_ that even though we support _semver ranges_ it's recommended to use the exact version when doing the automated provisioning as it
|
||||
guarantees full compatibility between the steps.
|
||||
|
||||
1. `DRIVE`. Plug in your target medium (SD card or the USB stick, depending on your device type) and run
|
||||
```bash
|
||||
balena util available-drives
|
||||
```
|
||||
and get the drive name, like _/dev/sdb_ or _/dev/mmcblk0_.
|
||||
The balena CLI will not display the system drives to protect you,
|
||||
but still please check very carefully that you've picked the correct drive as it will be erased during the provisioning process.
|
||||
|
||||
Now we have all the parameters -- time to build the config file.
|
||||
|
||||
## Build the config file
|
||||
|
||||
Interactive device provisioning process often includes collecting some extra device configuration, like the networking mode and wifi credentials.
|
||||
|
||||
To skip this interactive step we need to buid this configuration once and save it to the JSON file for later reuse.
|
||||
|
||||
Let's say we will place it into the `CONFIG_FILE` path, like _./balena-os/raspberrypi3-config.json_.
|
||||
|
||||
We also need to put the OS image somewhere, let's call this path `OS_IMAGE_PATH`, it can be something like _./balena-os/raspberrypi3-v2.0.6+rev1.prod.img_.
|
||||
|
||||
1. First we need to download the OS image once. That's needed for building the config, and will speedup the subsequent operations as the downloaded OS image is placed into the local cache.
|
||||
|
||||
Run:
|
||||
```bash
|
||||
balena os download DEVICE_TYPE --output OS_IMAGE_PATH --version OS_VERSION
|
||||
```
|
||||
|
||||
1. Now we're ready to build the config:
|
||||
|
||||
```bash
|
||||
balena os build-config OS_IMAGE_PATH DEVICE_TYPE --output CONFIG_FILE
|
||||
```
|
||||
|
||||
This will run you through the interactive configuration wizard and in the end save the generated config as `CONFIG_FILE`. You can then verify it's not empty:
|
||||
|
||||
```bash
|
||||
cat CONFIG_FILE
|
||||
```
|
||||
|
||||
## Done
|
||||
|
||||
Now you're ready to run the command in the beginning of this guide.
|
||||
|
||||
Please note again that all of these steps only need to be done once (unless you need to change something), and once all the parameters are collected the main init command can be run unchanged.
|
||||
|
||||
But there are still some nuances to cover, please read below.
|
||||
|
||||
## Nuances
|
||||
|
||||
### `sudo` password on *nix systems
|
||||
|
||||
In order to write the image to the raw device we need the root permissions, this is unavoidable.
|
||||
|
||||
To improve the security we only run the minimal subcommand with `sudo`.
|
||||
|
||||
This means that with the default setup you're interrupted closer to the end of the device init process to enter your sudo password for this subcommand to work.
|
||||
|
||||
There are several ways to eliminate it and make the process fully non-interactive.
|
||||
|
||||
#### Option 1: make passwordless sudo.
|
||||
|
||||
Obviously you shouldn't do that if the machine you're working on has access to any sensitive resources or information.
|
||||
|
||||
But if you're using a machine dedicated to balena provisioning this can be fine, and also the simplest thing to do.
|
||||
|
||||
#### Option 2: `NOPASSWD` directive
|
||||
|
||||
You can configure the `balena` CLI command to be sudo-runnable without the password. Check [this post](https://askubuntu.com/questions/159007/how-do-i-run-specific-sudo-commands-without-a-password) for an example.
|
||||
|
||||
### Extra initialization config
|
||||
|
||||
As of June 2017 all the supported devices should not require any other interactive configuration.
|
||||
|
||||
But by the design of our system it is _possible_ (though it doesn't look very likely it's going to happen any time soon) that some extra initialization options may be requested for the specific device types.
|
||||
|
||||
If that is the case please raise the issue in the balena CLI repository and the maintainers will add the necessary options to build the similar JSON config for this step.
|
@ -132,7 +132,7 @@ To learn more, troubleshoot issues, or to contact us for support:
|
||||
|
||||
* Check the [masterclass tutorials](https://www.balena.io/docs/learn/more/masterclasses/overview/)
|
||||
* Check our [FAQ / troubleshooting document](https://github.com/balena-io/balena-cli/blob/master/TROUBLESHOOTING.md)
|
||||
* Ask us a question through the [balenaCloud forum](https://forums.balena.io/c/balena-cloud)
|
||||
* Ask us a question in the [balena forums](https://forums.balena.io/c/product-support)
|
||||
|
||||
For CLI bug reports or feature requests, check the
|
||||
[CLI GitHub issues](https://github.com/balena-io/balena-cli/issues/).
|
||||
@ -915,10 +915,27 @@ the uuid of the device to identify
|
||||
|
||||
## device init
|
||||
|
||||
Initialize a device by downloading the OS image of the specified fleet
|
||||
and writing it to an SD Card.
|
||||
Register a new device in the selected fleet, download the OS image for the
|
||||
fleet's default device type, configure the image and write it to an SD card.
|
||||
This command effectively combines several other balena CLI commands in one,
|
||||
namely:
|
||||
|
||||
If the --fleet option is omitted, it will be prompted for interactively.
|
||||
'balena device register'
|
||||
'balena os download'
|
||||
'balena os build-config' or 'balena config generate'
|
||||
'balena os configure'
|
||||
'balena os local flash'
|
||||
|
||||
Possible arguments for the '--fleet', '--os-version' and '--drive' options can
|
||||
be listed respectively with the commands:
|
||||
|
||||
'balena fleets'
|
||||
'balena os versions'
|
||||
'balena util available-drives'
|
||||
|
||||
If the '--fleet' or '--drive' options are omitted, interactive menus will be
|
||||
presented with values to choose from. If the '--os-version' option is omitted,
|
||||
the latest released OS version for the fleet's default device type will be used.
|
||||
|
||||
Fleets may be specified by fleet name, slug, or numeric ID. Fleet slugs are
|
||||
the recommended option, as they are unique and unambiguous. Slugs can be
|
||||
@ -932,11 +949,15 @@ environments). Numeric fleet IDs are deprecated because they consist of an
|
||||
implementation detail of the balena backend. We intend to remove support for
|
||||
numeric IDs at some point in the future.
|
||||
|
||||
Image configuration questions will be asked interactively unless a pre-configured
|
||||
'config.json' file is provided with the '--config' option. The file can be
|
||||
generated with the 'balena config generate' or 'balena os build-config' commands.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena device init
|
||||
$ balena device init --fleet MyFleet
|
||||
$ balena device init -f myorg/myfleet
|
||||
$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes
|
||||
|
||||
### Options
|
||||
|
||||
@ -978,6 +999,10 @@ Check `balena util available-drives` for available options.
|
||||
|
||||
path to the config JSON file, see `balena os build-config`
|
||||
|
||||
#### --provisioning-key-name PROVISIONING-KEY-NAME
|
||||
|
||||
custom key name assigned to generated provisioning api key
|
||||
|
||||
## device local-mode <uuid>
|
||||
|
||||
Output current local mode status, or enable/disable local mode
|
||||
@ -2240,6 +2265,9 @@ device UUID
|
||||
Show the available balenaOS versions for the given device type.
|
||||
Check available types with `balena devices supported`.
|
||||
|
||||
balenaOS ESR versions can be listed with the '--esr' option. See also:
|
||||
https://www.balena.io/docs/reference/OS/extended-support-release/
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena os versions raspberrypi3
|
||||
@ -2252,23 +2280,28 @@ device type
|
||||
|
||||
### Options
|
||||
|
||||
#### --esr
|
||||
|
||||
select balenaOS ESR versions
|
||||
|
||||
## os download <type>
|
||||
|
||||
Download an unconfigured OS image for a certain device type.
|
||||
Check available types with `balena devices supported`
|
||||
Download an unconfigured OS image for the specified device type.
|
||||
Check available device types with 'balena devices supported'.
|
||||
|
||||
Note: Currently this command only works with balenaCloud, not openBalena.
|
||||
If using openBalena, please download the OS from: https://www.balena.io/os/
|
||||
|
||||
If version is not specified the newest stable (non-pre-release) version of OS
|
||||
is downloaded (if available), otherwise the newest version (if all existing
|
||||
versions for the given device type are pre-release).
|
||||
The '--version' option is used to select the balenaOS version. If omitted,
|
||||
the latest released version is downloaded (and if only pre-release versions
|
||||
exist, the latest pre-release version is downloaded).
|
||||
|
||||
You can pass `--version menu` to pick the OS version from the interactive menu
|
||||
of all available versions.
|
||||
Use '--version menu' or '--version menu-esr' to interactively select the
|
||||
OS version. The latter lists ESR versions which are only available for
|
||||
download on Production and Enterprise plans. See also:
|
||||
https://www.balena.io/docs/reference/OS/extended-support-release/
|
||||
|
||||
To download a development image append `.dev` to the version or select from
|
||||
the interactive menu.
|
||||
Development images can be selected by appending `.dev` to the version.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -2276,9 +2309,11 @@ Examples:
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.1
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu
|
||||
$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu-esr
|
||||
|
||||
### Arguments
|
||||
|
||||
@ -2294,11 +2329,13 @@ output path
|
||||
|
||||
#### --version VERSION
|
||||
|
||||
exact version number, or a valid semver range,
|
||||
version number (ESR or non-ESR versions),
|
||||
or semver range (non-ESR versions only),
|
||||
or 'latest' (includes pre-releases),
|
||||
or 'default' (excludes pre-releases if at least one stable version is available),
|
||||
or 'default' (excludes pre-releases if at least one released version is available),
|
||||
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
|
||||
or 'menu' (will show the interactive menu)
|
||||
or 'menu' (interactive menu, non-ESR versions),
|
||||
or 'menu-esr' (interactive menu, ESR versions)
|
||||
|
||||
## os build-config <image> <device-type>
|
||||
|
||||
@ -2448,6 +2485,10 @@ balenaOS version, for example "2.32.0" or "2.44.0+rev1"
|
||||
|
||||
paths to local files to place into the 'system-connections' directory
|
||||
|
||||
#### --provisioning-key-name PROVISIONING-KEY-NAME
|
||||
|
||||
custom key name assigned to generated provisioning api key
|
||||
|
||||
## os initialize <image>
|
||||
|
||||
Initialize an os image for a device with a previously
|
||||
@ -2513,7 +2554,7 @@ Examples:
|
||||
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>
|
||||
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
|
||||
$ balena config generate --fleet MyFleet --version 2.12.7
|
||||
$ balena config generate --fleet myorg/myfleet --version 2.12.7
|
||||
@ -2575,18 +2616,22 @@ the wifi ssid to use (used only if --network is set to wifi)
|
||||
|
||||
the wifi key to use (used only if --network is set to wifi)
|
||||
|
||||
#### --provisioning-key-name PROVISIONING-KEY-NAME
|
||||
|
||||
custom key name assigned to generated provisioning api key
|
||||
|
||||
## config inject <file>
|
||||
|
||||
Inject a config.json file to a mounted filesystem, e.g. the SD card of a
|
||||
provisioned device or balenaOS image.
|
||||
Inject a 'config.json' file to a balenaOS image file or attached SD card or
|
||||
USB stick.
|
||||
|
||||
Note: if using a private/custom device type, please ensure you are logged in
|
||||
('balena login' command). Public device types do not require logging in.
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena config inject my/config.json --type raspberrypi3
|
||||
$ balena config inject my/config.json --type raspberrypi3 --drive /dev/disk2
|
||||
$ balena config inject my/config.json
|
||||
$ balena config inject my/config.json --drive /dev/disk2
|
||||
|
||||
### Arguments
|
||||
|
||||
@ -2598,7 +2643,7 @@ the path to the config.json file to inject
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
device type (Check available types with `balena devices supported`)
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
@ -2606,39 +2651,54 @@ path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
## config read
|
||||
|
||||
Read the config.json file from the mounted filesystem,
|
||||
e.g. the SD card of a provisioned device or balenaOS image.
|
||||
Read the 'config.json' file of a balenaOS image file or attached SD card or
|
||||
USB stick.
|
||||
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena config read --type raspberrypi3
|
||||
$ balena config read --type raspberrypi3 --drive /dev/disk2
|
||||
$ balena config read
|
||||
$ balena config read --drive /dev/disk2
|
||||
$ balena config read --drive balena.img
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
device type (Check available types with `balena devices supported`)
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
#### -j, --json
|
||||
|
||||
produce JSON output instead of tabular output
|
||||
|
||||
## config reconfigure
|
||||
|
||||
Interactively reconfigure a provisioned device or OS image.
|
||||
Interactively reconfigure a balenaOS image file or attached media.
|
||||
|
||||
This command extracts the device UUID from the 'config.json' file of the
|
||||
chosen balenaOS image file or attached media, and then passes the UUID as
|
||||
the '--device' argument to the 'balena os configure' command.
|
||||
|
||||
For finer-grained or scripted control of the operation, use the
|
||||
'balena config read' and 'balena os configure' commands separately.
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena config reconfigure --type raspberrypi3
|
||||
$ balena config reconfigure --type raspberrypi3 --advanced
|
||||
$ balena config reconfigure --type raspberrypi3 --drive /dev/disk2
|
||||
$ balena config reconfigure
|
||||
$ balena config reconfigure --drive /dev/disk3
|
||||
$ balena config reconfigure --drive balena.img --advanced
|
||||
|
||||
### Options
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
device type (Check available types with `balena devices supported`)
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
@ -2648,16 +2708,23 @@ path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
show advanced commands
|
||||
|
||||
#### --version VERSION
|
||||
|
||||
balenaOS version, for example "2.32.0" or "2.44.0+rev1"
|
||||
|
||||
## config write <key> <value>
|
||||
|
||||
Write a key-value pair to the config.json file on the mounted filesystem,
|
||||
e.g. the SD card of a provisioned device or balenaOS image.
|
||||
Write a key-value pair to the 'config.json' file of a balenaOS image file or
|
||||
attached SD card or USB stick.
|
||||
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
|
||||
Examples:
|
||||
|
||||
$ balena config write --type raspberrypi3 username johndoe
|
||||
$ balena config write --type raspberrypi3 --drive /dev/disk2 username johndoe
|
||||
$ balena config write --type raspberrypi3 files.network/settings "..."
|
||||
$ balena config write ntpServers "0.resinio.pool.ntp.org 1.resinio.pool.ntp.org"
|
||||
$ balena config write --drive /dev/disk2 hostname custom-hostname
|
||||
$ balena config write --drive balena.img os.network.connectivity.interval 300
|
||||
|
||||
### Arguments
|
||||
|
||||
@ -2673,7 +2740,7 @@ the value of the config parameter to write
|
||||
|
||||
#### -t, --type TYPE
|
||||
|
||||
device type (Check available types with `balena devices supported`)
|
||||
ignored - no longer required
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
15
lib/app.ts
15
lib/app.ts
@ -77,11 +77,13 @@ export function setMaxListeners(maxListeners: number) {
|
||||
/** Selected CLI initialization steps */
|
||||
async function init() {
|
||||
if (process.env.BALENARC_NO_SENTRY) {
|
||||
console.error(`WARN: disabling Sentry.io error reporting`);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`WARN: disabling Sentry.io error reporting`);
|
||||
}
|
||||
} else {
|
||||
await setupSentry();
|
||||
}
|
||||
checkNodeVersion();
|
||||
await checkNodeVersion();
|
||||
|
||||
const settings = new CliSettings();
|
||||
|
||||
@ -91,7 +93,9 @@ async function init() {
|
||||
setupBalenaSdkSharedOptions(settings);
|
||||
|
||||
// check for CLI updates once a day
|
||||
(await import('./utils/update')).notify();
|
||||
if (!process.env.BALENARC_OFFLINE_MODE) {
|
||||
(await import('./utils/update')).notify();
|
||||
}
|
||||
}
|
||||
|
||||
/** Execute the oclif parser and the CLI command. */
|
||||
@ -149,7 +153,10 @@ async function oclifRun(command: string[], options: AppOptions) {
|
||||
/** CLI entrypoint. Called by the `bin/balena` and `bin/balena-dev` scripts. */
|
||||
export async function run(cliArgs = process.argv, options: AppOptions = {}) {
|
||||
try {
|
||||
const { normalizeEnvVars, pkgExec } = await import('./utils/bootstrap');
|
||||
const { setOfflineModeEnvVars, normalizeEnvVars, pkgExec } = await import(
|
||||
'./utils/bootstrap'
|
||||
);
|
||||
setOfflineModeEnvVars();
|
||||
normalizeEnvVars();
|
||||
|
||||
// The 'pkgExec' special/internal command provides a Node.js interpreter
|
||||
|
@ -56,7 +56,7 @@ export async function login({ host = '127.0.0.1', port = 0 }) {
|
||||
|
||||
console.info(`Opening web browser for URL:\n${loginUrl}`);
|
||||
const open = await import('open');
|
||||
open(loginUrl, { wait: false });
|
||||
await open(loginUrl, { wait: false });
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const token = await loginServer.awaitForToken();
|
||||
|
@ -16,7 +16,12 @@
|
||||
*/
|
||||
|
||||
import Command from '@oclif/command';
|
||||
import { InsufficientPrivilegesError } from './errors';
|
||||
import {
|
||||
InsufficientPrivilegesError,
|
||||
NotAvailableInOfflineModeError,
|
||||
} from './errors';
|
||||
import { stripIndent } from './utils/lazy';
|
||||
import * as output from './framework/output';
|
||||
|
||||
export default abstract class BalenaCommand extends Command {
|
||||
/**
|
||||
@ -40,6 +45,13 @@ export default abstract class BalenaCommand extends Command {
|
||||
*/
|
||||
public static authenticated = false;
|
||||
|
||||
/**
|
||||
* Require an internet connection to run.
|
||||
* When set to true, command will exit with an error
|
||||
* if user is running in offline mode (BALENARC_OFFLINE_MODE).
|
||||
*/
|
||||
public static offlineCompatible = false;
|
||||
|
||||
/**
|
||||
* Accept piped input.
|
||||
* When set to true, command will read from stdin during init
|
||||
@ -97,6 +109,29 @@ export default abstract class BalenaCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw NotAvailableInOfflineModeError if in offline mode.
|
||||
*
|
||||
* Called automatically if `onlineOnly=true`.
|
||||
* Can be called explicitly by command implementation, if e.g.:
|
||||
* - check should only be done conditionally
|
||||
* - other code needs to execute before check
|
||||
*
|
||||
* Note, currently public to allow use outside of derived commands
|
||||
* (as some command implementations require this. Can be made protected
|
||||
* if this changes).
|
||||
*
|
||||
* @throws {NotAvailableInOfflineModeError}
|
||||
*/
|
||||
public static checkNotUsingOfflineMode() {
|
||||
if (process.env.BALENARC_OFFLINE_MODE) {
|
||||
throw new NotAvailableInOfflineModeError(stripIndent`
|
||||
This command requires an internet connection, and cannot be used in offline mode.
|
||||
To leave offline mode, unset the BALENARC_OFFLINE_MODE environment variable.
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read stdin contents and make available to command.
|
||||
*
|
||||
@ -125,8 +160,15 @@ export default abstract class BalenaCommand extends Command {
|
||||
await BalenaCommand.checkLoggedIn();
|
||||
}
|
||||
|
||||
if (!ctr.offlineCompatible) {
|
||||
BalenaCommand.checkNotUsingOfflineMode();
|
||||
}
|
||||
|
||||
if (ctr.readStdin) {
|
||||
await this.getStdin();
|
||||
}
|
||||
}
|
||||
|
||||
protected outputMessage = output.outputMessage;
|
||||
protected outputData = output.outputData;
|
||||
}
|
||||
|
@ -28,8 +28,10 @@ import {
|
||||
appToFleetCmdMsg,
|
||||
warnify,
|
||||
} from '../../utils/messages';
|
||||
import { isV13 } from '../../utils/version';
|
||||
import type { DataOutputOptions } from '../../framework';
|
||||
|
||||
interface FlagsDef {
|
||||
interface FlagsDef extends DataOutputOptions {
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -56,13 +58,14 @@ export class FleetCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
...(isV13() ? cf.dataOutputFlags : {}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
public static primary = true;
|
||||
|
||||
public async run(parserOutput?: ParserOutput<FlagsDef, ArgsDef>) {
|
||||
const { args: params } =
|
||||
const { args: params, flags: options } =
|
||||
parserOutput || this.parse<FlagsDef, ArgsDef>(FleetCmd);
|
||||
|
||||
const { getApplication } = await import('../../utils/sdk');
|
||||
@ -82,16 +85,24 @@ export class FleetCmd extends Command {
|
||||
application.device_type = application.is_for__device_type[0].slug;
|
||||
application.commit = application.should_be_running__release[0]?.commit;
|
||||
|
||||
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
|
||||
console.log(`== ${application.app_name}`);
|
||||
console.log(
|
||||
getVisuals().table.vertical(application, [
|
||||
'id',
|
||||
'device_type',
|
||||
'slug',
|
||||
'commit',
|
||||
]),
|
||||
);
|
||||
if (isV13()) {
|
||||
await this.outputData(
|
||||
application,
|
||||
['app_name', 'id', 'device_type', 'slug', 'commit'],
|
||||
options,
|
||||
);
|
||||
} else {
|
||||
// Emulate table.vertical title output, but avoid uppercasing and inserting spaces
|
||||
console.log(`== ${application.app_name}`);
|
||||
console.log(
|
||||
getVisuals().table.vertical(application, [
|
||||
'id',
|
||||
'device_type',
|
||||
'slug',
|
||||
'commit',
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ export class FleetRenameCmd extends Command {
|
||||
} catch (e) {
|
||||
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
|
||||
if ((e.message || '').toLowerCase().includes('unique')) {
|
||||
throw new ExpectedError(`Error: fleet ${params.fleet} already exists.`);
|
||||
throw new ExpectedError(`Error: fleet ${newName} already exists.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
@ -17,18 +17,20 @@
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
import type { Output as ParserOutput } from '@oclif/parser';
|
||||
|
||||
import Command from '../command';
|
||||
import * as cf from '../utils/common-flags';
|
||||
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
|
||||
import { appToFleetCmdMsg, warnify } from '../utils/messages';
|
||||
import { isV13 } from '../utils/version';
|
||||
import type { DataSetOutputOptions } from '../framework';
|
||||
|
||||
interface ExtendedApplication extends ApplicationWithDeviceType {
|
||||
device_count?: number;
|
||||
online_devices?: number;
|
||||
device_count: number;
|
||||
online_devices: number;
|
||||
device_type?: string;
|
||||
}
|
||||
|
||||
interface FlagsDef {
|
||||
interface FlagsDef extends DataSetOutputOptions {
|
||||
help: void;
|
||||
verbose?: boolean;
|
||||
}
|
||||
@ -48,12 +50,17 @@ export class FleetsCmd extends Command {
|
||||
public static usage = 'fleets';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
...(isV13()
|
||||
? {}
|
||||
: {
|
||||
verbose: flags.boolean({
|
||||
default: false,
|
||||
char: 'v',
|
||||
description: 'No-op since release v12.0.0',
|
||||
}),
|
||||
}),
|
||||
...(isV13() ? cf.dataSetOutputFlags : {}),
|
||||
help: cf.help,
|
||||
verbose: flags.boolean({
|
||||
default: false,
|
||||
char: 'v',
|
||||
description: 'No-op since release v12.0.0',
|
||||
}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
@ -75,28 +82,39 @@ export class FleetsCmd extends Command {
|
||||
},
|
||||
})) as ExtendedApplication[];
|
||||
|
||||
const _ = await import('lodash');
|
||||
// Add extended properties
|
||||
applications.forEach((application) => {
|
||||
application.device_count = application.owns__device?.length ?? 0;
|
||||
application.online_devices = _.sumBy(application.owns__device, (d) =>
|
||||
d.is_online === true ? 1 : 0,
|
||||
);
|
||||
// @ts-expect-error
|
||||
application.online_devices =
|
||||
application.owns__device?.filter((d) => d.is_online).length || 0;
|
||||
application.device_type = application.is_for__device_type[0].slug;
|
||||
});
|
||||
|
||||
// Display
|
||||
console.log(
|
||||
getVisuals().table.horizontal(applications, [
|
||||
'id',
|
||||
this.useAppWord ? 'app_name' : 'app_name => NAME',
|
||||
'slug',
|
||||
'device_type',
|
||||
'online_devices',
|
||||
'device_count',
|
||||
]),
|
||||
);
|
||||
if (isV13()) {
|
||||
await this.outputData(
|
||||
applications,
|
||||
[
|
||||
'id',
|
||||
'app_name',
|
||||
'slug',
|
||||
'device_type',
|
||||
'device_count',
|
||||
'online_devices',
|
||||
],
|
||||
_parserOutput.flags,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
getVisuals().table.horizontal(applications, [
|
||||
'id',
|
||||
this.useAppWord ? 'app_name' : 'app_name => NAME',
|
||||
'slug',
|
||||
'device_type',
|
||||
'online_devices',
|
||||
'device_count',
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ interface FlagsDef {
|
||||
wifiSsid?: string;
|
||||
wifiKey?: string;
|
||||
appUpdatePollInterval?: string;
|
||||
'provisioning-key-name'?: string;
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -65,7 +66,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
public static examples = [
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
|
||||
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
|
||||
'$ balena config generate --fleet MyFleet --version 2.12.7',
|
||||
'$ balena config generate --fleet myorg/myfleet --version 2.12.7',
|
||||
@ -94,7 +95,10 @@ export default class ConfigGenerateCmd extends Command {
|
||||
}),
|
||||
}),
|
||||
fleet: { ...cf.fleet, exclusive: ['application', 'app', 'device'] },
|
||||
device: { ...cf.device, exclusive: ['application', 'app', 'fleet'] },
|
||||
device: {
|
||||
...cf.device,
|
||||
exclusive: ['application', 'app', 'fleet', 'provisioning-key-name'],
|
||||
},
|
||||
deviceApiKey: flags.string({
|
||||
description:
|
||||
'custom device key - note that this is only supported on balenaOS 2.0.3+',
|
||||
@ -128,6 +132,10 @@ export default class ConfigGenerateCmd extends Command {
|
||||
description:
|
||||
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
|
||||
}),
|
||||
'provisioning-key-name': flags.string({
|
||||
description: 'custom key name assigned to generated provisioning api key',
|
||||
exclusive: ['device'],
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -202,6 +210,7 @@ export default class ConfigGenerateCmd extends Command {
|
||||
override: options,
|
||||
});
|
||||
answers.version = options.version;
|
||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||
|
||||
// Generate config
|
||||
const { generateDeviceConfig, generateApplicationConfig } = await import(
|
||||
|
@ -21,7 +21,7 @@ import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type: string;
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
}
|
||||
@ -32,18 +32,18 @@ interface ArgsDef {
|
||||
|
||||
export default class ConfigInjectCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Inject a configuration file into a device or OS image.
|
||||
Inject a config.json file to a balenaOS image or attached media.
|
||||
|
||||
Inject a config.json file to a mounted filesystem, e.g. the SD card of a
|
||||
provisioned device or balenaOS image.
|
||||
Inject a 'config.json' file to a balenaOS image file or attached SD card or
|
||||
USB stick.
|
||||
|
||||
Note: if using a private/custom device type, please ensure you are logged in
|
||||
('balena login' command). Public device types do not require logging in.
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena config inject my/config.json --type raspberrypi3',
|
||||
'$ balena config inject my/config.json --type raspberrypi3 --drive /dev/disk2',
|
||||
'$ balena config inject my/config.json',
|
||||
'$ balena config inject my/config.json --drive /dev/disk2',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
@ -57,12 +57,13 @@ export default class ConfigInjectCmd extends Command {
|
||||
public static usage = 'config inject <file>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceType,
|
||||
type: cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
@ -81,7 +82,7 @@ export default class ConfigInjectCmd extends Command {
|
||||
);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
await config.write(drive, options.type, configJSON);
|
||||
await config.write(drive, '', configJSON);
|
||||
|
||||
console.info('Done');
|
||||
}
|
||||
|
@ -21,35 +21,40 @@ import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type: string;
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
export default class ConfigReadCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Read the configuration of a device or OS image.
|
||||
Read the config.json file of a balenaOS image or attached media.
|
||||
|
||||
Read the config.json file from the mounted filesystem,
|
||||
e.g. the SD card of a provisioned device or balenaOS image.
|
||||
Read the 'config.json' file of a balenaOS image file or attached SD card or
|
||||
USB stick.
|
||||
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena config read --type raspberrypi3',
|
||||
'$ balena config read --type raspberrypi3 --drive /dev/disk2',
|
||||
'$ balena config read',
|
||||
'$ balena config read --drive /dev/disk2',
|
||||
'$ balena config read --drive balena.img',
|
||||
];
|
||||
|
||||
public static usage = 'config read';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceType,
|
||||
type: cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
json: cf.json,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
|
||||
@ -61,9 +66,13 @@ export default class ConfigReadCmd extends Command {
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const configJSON = await config.read(drive, options.type);
|
||||
const configJSON = await config.read(drive, '');
|
||||
|
||||
const prettyjson = await import('prettyjson');
|
||||
console.info(prettyjson.render(configJSON));
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(configJSON, null, 4));
|
||||
} else {
|
||||
const prettyjson = await import('prettyjson');
|
||||
console.log(prettyjson.render(configJSON));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,38 +21,48 @@ import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type: string;
|
||||
type?: string;
|
||||
drive?: string;
|
||||
advanced: boolean;
|
||||
help: void;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export default class ConfigReconfigureCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Interactively reconfigure a device or OS image.
|
||||
Interactively reconfigure a balenaOS image file or attached media.
|
||||
|
||||
Interactively reconfigure a provisioned device or OS image.
|
||||
Interactively reconfigure a balenaOS image file or attached media.
|
||||
|
||||
This command extracts the device UUID from the 'config.json' file of the
|
||||
chosen balenaOS image file or attached media, and then passes the UUID as
|
||||
the '--device' argument to the 'balena os configure' command.
|
||||
|
||||
For finer-grained or scripted control of the operation, use the
|
||||
'balena config read' and 'balena os configure' commands separately.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena config reconfigure --type raspberrypi3',
|
||||
'$ balena config reconfigure --type raspberrypi3 --advanced',
|
||||
'$ balena config reconfigure --type raspberrypi3 --drive /dev/disk2',
|
||||
'$ balena config reconfigure',
|
||||
'$ balena config reconfigure --drive /dev/disk3',
|
||||
'$ balena config reconfigure --drive balena.img --advanced',
|
||||
];
|
||||
|
||||
public static usage = 'config reconfigure';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceType,
|
||||
type: cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
advanced: flags.boolean({
|
||||
description: 'show advanced commands',
|
||||
char: 'v',
|
||||
}),
|
||||
help: cf.help,
|
||||
version: flags.string({
|
||||
description: 'balenaOS version, for example "2.32.0" or "2.44.0+rev1"',
|
||||
}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public static root = true;
|
||||
|
||||
public async run() {
|
||||
@ -65,10 +75,20 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const { uuid } = await config.read(drive, options.type);
|
||||
const { uuid } = await config.read(drive, '');
|
||||
await safeUmount(drive);
|
||||
|
||||
if (!uuid) {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
`Error: UUID not found in 'config.json' file for '${drive}'`,
|
||||
);
|
||||
}
|
||||
|
||||
const configureCommand = ['os', 'configure', drive, '--device', uuid];
|
||||
if (options.version) {
|
||||
configureCommand.push('--version', options.version);
|
||||
}
|
||||
if (options.advanced) {
|
||||
configureCommand.push('--advanced');
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import * as cf from '../../utils/common-flags';
|
||||
import { getVisuals, stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
type: string;
|
||||
type?: string;
|
||||
drive?: string;
|
||||
help: void;
|
||||
}
|
||||
@ -33,16 +33,19 @@ interface ArgsDef {
|
||||
|
||||
export default class ConfigWriteCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Write a key-value pair to configuration of a device or OS image.
|
||||
Write a key-value pair to the config.json file of an OS image or attached media.
|
||||
|
||||
Write a key-value pair to the config.json file on the mounted filesystem,
|
||||
e.g. the SD card of a provisioned device or balenaOS image.
|
||||
Write a key-value pair to the 'config.json' file of a balenaOS image file or
|
||||
attached SD card or USB stick.
|
||||
|
||||
Documentation for the balenaOS 'config.json' file can be found at:
|
||||
https://www.balena.io/docs/reference/OS/configuration/
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena config write --type raspberrypi3 username johndoe',
|
||||
'$ balena config write --type raspberrypi3 --drive /dev/disk2 username johndoe',
|
||||
'$ balena config write --type raspberrypi3 files.network/settings "..."',
|
||||
'$ balena config write ntpServers "0.resinio.pool.ntp.org 1.resinio.pool.ntp.org"',
|
||||
'$ balena config write --drive /dev/disk2 hostname custom-hostname',
|
||||
'$ balena config write --drive balena.img os.network.connectivity.interval 300',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
@ -61,14 +64,13 @@ export default class ConfigWriteCmd extends Command {
|
||||
public static usage = 'config write <key> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: cf.deviceType,
|
||||
type: cf.deviceTypeIgnored,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
@ -82,14 +84,14 @@ export default class ConfigWriteCmd extends Command {
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const configJSON = await config.read(drive, options.type);
|
||||
const configJSON = await config.read(drive, '');
|
||||
|
||||
console.info(`Setting ${params.key} to ${params.value}`);
|
||||
ConfigWriteCmd.updateConfigJson(configJSON, params.key, params.value);
|
||||
|
||||
await denyMount(drive, async () => {
|
||||
await safeUmount(drive);
|
||||
await config.write(drive, options.type, configJSON);
|
||||
await config.write(drive, '', configJSON);
|
||||
});
|
||||
|
||||
console.info('Done');
|
||||
|
@ -37,24 +37,46 @@ interface FlagsDef {
|
||||
drive?: string;
|
||||
config?: string;
|
||||
help: void;
|
||||
'provisioning-key-name'?: string;
|
||||
}
|
||||
|
||||
export default class DeviceInitCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Initialize a device with balenaOS.
|
||||
|
||||
Initialize a device by downloading the OS image of the specified fleet
|
||||
and writing it to an SD Card.
|
||||
Register a new device in the selected fleet, download the OS image for the
|
||||
fleet's default device type, configure the image and write it to an SD card.
|
||||
This command effectively combines several other balena CLI commands in one,
|
||||
namely:
|
||||
|
||||
If the --fleet option is omitted, it will be prompted for interactively.
|
||||
'balena device register'
|
||||
'balena os download'
|
||||
'balena os build-config' or 'balena config generate'
|
||||
'balena os configure'
|
||||
'balena os local flash'
|
||||
|
||||
Possible arguments for the '--fleet', '--os-version' and '--drive' options can
|
||||
be listed respectively with the commands:
|
||||
|
||||
'balena fleets'
|
||||
'balena os versions'
|
||||
'balena util available-drives'
|
||||
|
||||
If the '--fleet' or '--drive' options are omitted, interactive menus will be
|
||||
presented with values to choose from. If the '--os-version' option is omitted,
|
||||
the latest released OS version for the fleet's default device type will be used.
|
||||
|
||||
${applicationIdInfo.split('\n').join('\n\t\t')}
|
||||
|
||||
Image configuration questions will be asked interactively unless a pre-configured
|
||||
'config.json' file is provided with the '--config' option. The file can be
|
||||
generated with the 'balena config generate' or 'balena os build-config' commands.
|
||||
`;
|
||||
|
||||
public static examples = [
|
||||
'$ balena device init',
|
||||
'$ balena device init --fleet MyFleet',
|
||||
'$ balena device init -f myorg/myfleet',
|
||||
'$ balena device init --fleet myFleet --os-version 2.83.21+rev1.prod --drive /dev/disk5 --config config.json --yes',
|
||||
];
|
||||
|
||||
public static usage = 'device init';
|
||||
@ -85,6 +107,9 @@ export default class DeviceInitCmd extends Command {
|
||||
config: flags.string({
|
||||
description: 'path to the config JSON file, see `balena os build-config`',
|
||||
}),
|
||||
'provisioning-key-name': flags.string({
|
||||
description: 'custom key name assigned to generated provisioning api key',
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -173,6 +198,13 @@ export default class DeviceInitCmd extends Command {
|
||||
} else if (options.advanced) {
|
||||
configureCommand.push('--advanced');
|
||||
}
|
||||
|
||||
if (options['provisioning-key-name']) {
|
||||
configureCommand.push(
|
||||
'--provisioning-key-name',
|
||||
options['provisioning-key-name'],
|
||||
);
|
||||
}
|
||||
await runCommand(configureCommand);
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ export default class OsinitCmd extends Command {
|
||||
|
||||
public static hidden = true;
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<{}, ArgsDef>(OsinitCmd);
|
||||
|
@ -56,6 +56,7 @@ export default class LocalConfigureCmd extends Command {
|
||||
};
|
||||
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(LocalConfigureCmd);
|
||||
@ -96,7 +97,7 @@ export default class LocalConfigureCmd extends Command {
|
||||
|
||||
readonly CONNECTIONS_FOLDER = '/system-connections';
|
||||
|
||||
getConfigurationSchema(bootPartition: number, connectionFileName?: string) {
|
||||
getConfigurationSchema(bootPartition?: number, connectionFileName?: string) {
|
||||
connectionFileName ??= 'resin-wifi';
|
||||
return {
|
||||
mapper: [
|
||||
@ -235,9 +236,9 @@ export default class LocalConfigureCmd extends Command {
|
||||
async prepareConnectionFile(target: string) {
|
||||
const _ = await import('lodash');
|
||||
const imagefs = await import('balena-image-fs');
|
||||
const helpers = await import('../../utils/helpers');
|
||||
const { getBootPartition } = await import('balena-config-json');
|
||||
|
||||
const bootPartition = await helpers.getBootPartition(target);
|
||||
const bootPartition = await getBootPartition(target);
|
||||
|
||||
const files = await imagefs.interact(target, bootPartition, async (_fs) => {
|
||||
return await promisify(_fs.readdir)(this.CONNECTIONS_FOLDER);
|
||||
|
@ -65,6 +65,8 @@ export default class LocalFlashCmd extends Command {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
LocalFlashCmd,
|
||||
|
@ -187,7 +187,7 @@ ${messages.reachingOut}`);
|
||||
if (loginType === 'register') {
|
||||
const open = await import('open');
|
||||
const signupUrl = `https://dashboard.${balenaUrl}/signup`;
|
||||
open(signupUrl, { wait: false });
|
||||
await open(signupUrl, { wait: false });
|
||||
throw new ExpectedError(`Please sign up at ${signupUrl}`);
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ interface FlagsDef {
|
||||
version?: string;
|
||||
'system-connection': string[];
|
||||
'initial-device-name'?: string;
|
||||
'provisioning-key-name'?: string;
|
||||
}
|
||||
|
||||
interface ArgsDef {
|
||||
@ -62,6 +63,7 @@ interface Answers {
|
||||
version: string; // e.g. "2.32.0+rev1"
|
||||
wifiSsid?: string;
|
||||
wifiKey?: string;
|
||||
provisioningKeyName?: string;
|
||||
}
|
||||
|
||||
const deviceApiKeyDeprecationMsg = stripIndent`
|
||||
@ -145,6 +147,7 @@ export default class OsConfigureCmd extends Command {
|
||||
config: flags.string({
|
||||
description:
|
||||
'path to a pre-generated config.json file to be injected in the OS image',
|
||||
exclusive: ['provisioning-key-name'],
|
||||
}),
|
||||
'config-app-update-poll-interval': flags.integer({
|
||||
description:
|
||||
@ -160,7 +163,10 @@ export default class OsConfigureCmd extends Command {
|
||||
'config-wifi-ssid': flags.string({
|
||||
description: 'WiFi SSID (network name) (non-interactive configuration)',
|
||||
}),
|
||||
device: { exclusive: ['app', 'application', 'fleet'], ...cf.device },
|
||||
device: {
|
||||
exclusive: ['app', 'application', 'fleet', 'provisioning-key-name'],
|
||||
...cf.device,
|
||||
},
|
||||
'device-api-key': flags.string({
|
||||
char: 'k',
|
||||
description:
|
||||
@ -184,9 +190,15 @@ export default class OsConfigureCmd extends Command {
|
||||
description:
|
||||
"paths to local files to place into the 'system-connections' directory",
|
||||
}),
|
||||
'provisioning-key-name': flags.string({
|
||||
description: 'custom key name assigned to generated provisioning api key',
|
||||
exclusive: ['config', 'device'],
|
||||
}),
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
OsConfigureCmd,
|
||||
@ -254,6 +266,8 @@ export default class OsConfigureCmd extends Command {
|
||||
options.version ||
|
||||
(await getOsVersionFromImage(params.image, deviceTypeManifest, devInit));
|
||||
|
||||
answers.provisioningKeyName = options['provisioning-key-name'];
|
||||
|
||||
if (_.isEmpty(configJson)) {
|
||||
if (device) {
|
||||
configJson = await generateDeviceConfig(
|
||||
@ -300,7 +314,8 @@ export default class OsConfigureCmd extends Command {
|
||||
}),
|
||||
);
|
||||
|
||||
const bootPartition = await helpers.getBootPartition(params.image);
|
||||
const { getBootPartition } = await import('balena-config-json');
|
||||
const bootPartition = await getBootPartition(params.image);
|
||||
|
||||
const imagefs = await import('balena-image-fs');
|
||||
|
||||
|
@ -34,30 +34,33 @@ export default class OsDownloadCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Download an unconfigured OS image.
|
||||
|
||||
Download an unconfigured OS image for a certain device type.
|
||||
Check available types with \`balena devices supported\`
|
||||
Download an unconfigured OS image for the specified device type.
|
||||
Check available device types with 'balena devices supported'.
|
||||
|
||||
Note: Currently this command only works with balenaCloud, not openBalena.
|
||||
If using openBalena, please download the OS from: https://www.balena.io/os/
|
||||
|
||||
If version is not specified the newest stable (non-pre-release) version of OS
|
||||
is downloaded (if available), otherwise the newest version (if all existing
|
||||
versions for the given device type are pre-release).
|
||||
The '--version' option is used to select the balenaOS version. If omitted,
|
||||
the latest released version is downloaded (and if only pre-release versions
|
||||
exist, the latest pre-release version is downloaded).
|
||||
|
||||
You can pass \`--version menu\` to pick the OS version from the interactive menu
|
||||
of all available versions.
|
||||
Use '--version menu' or '--version menu-esr' to interactively select the
|
||||
OS version. The latter lists ESR versions which are only available for
|
||||
download on Production and Enterprise plans. See also:
|
||||
https://www.balena.io/docs/reference/OS/extended-support-release/
|
||||
|
||||
To download a development image append \`.dev\` to the version or select from
|
||||
the interactive menu.
|
||||
Development images can be selected by appending \`.dev\` to the version.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2.60.1+rev1.dev',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version ^2.60.0',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version 2021.10.1',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version latest',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version default',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu',
|
||||
'$ balena os download raspberrypi3 -o ../foo/bar/raspberry-pi.img --version menu-esr',
|
||||
];
|
||||
|
||||
public static args = [
|
||||
@ -78,11 +81,13 @@ export default class OsDownloadCmd extends Command {
|
||||
}),
|
||||
version: flags.string({
|
||||
description: stripIndent`
|
||||
exact version number, or a valid semver range,
|
||||
version number (ESR or non-ESR versions),
|
||||
or semver range (non-ESR versions only),
|
||||
or 'latest' (includes pre-releases),
|
||||
or 'default' (excludes pre-releases if at least one stable version is available),
|
||||
or 'default' (excludes pre-releases if at least one released version is available),
|
||||
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
|
||||
or 'menu' (will show the interactive menu)
|
||||
or 'menu' (interactive menu, non-ESR versions),
|
||||
or 'menu-esr' (interactive menu, ESR versions)
|
||||
`,
|
||||
}),
|
||||
help: cf.help,
|
||||
@ -93,6 +98,26 @@ export default class OsDownloadCmd extends Command {
|
||||
OsDownloadCmd,
|
||||
);
|
||||
|
||||
// balenaOS ESR versions require user authentication
|
||||
if (options.version) {
|
||||
const { isESR } = await import('balena-image-manager');
|
||||
if (options.version === 'menu-esr' || isESR(options.version)) {
|
||||
try {
|
||||
await OsDownloadCmd.checkLoggedIn();
|
||||
} catch (e) {
|
||||
const { ExpectedError, NotLoggedInError } = await import(
|
||||
'../../errors'
|
||||
);
|
||||
if (e instanceof NotLoggedInError) {
|
||||
throw new ExpectedError(stripIndent`
|
||||
${e.message}
|
||||
User authentication is required to download balenaOS ESR versions.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { downloadOSImage } = await import('../../utils/cloud');
|
||||
|
||||
try {
|
||||
|
@ -18,9 +18,10 @@
|
||||
import { flags } from '@oclif/command';
|
||||
import Command from '../../command';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
|
||||
interface FlagsDef {
|
||||
esr?: boolean;
|
||||
help: void;
|
||||
}
|
||||
|
||||
@ -34,6 +35,9 @@ export default class OsVersionsCmd extends Command {
|
||||
|
||||
Show the available balenaOS versions for the given device type.
|
||||
Check available types with \`balena devices supported\`.
|
||||
|
||||
balenaOS ESR versions can be listed with the '--esr' option. See also:
|
||||
https://www.balena.io/docs/reference/OS/extended-support-release/
|
||||
`;
|
||||
|
||||
public static examples = ['$ balena os versions raspberrypi3'];
|
||||
@ -50,16 +54,20 @@ export default class OsVersionsCmd extends Command {
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
help: cf.help,
|
||||
esr: flags.boolean({
|
||||
description: 'select balenaOS ESR versions',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
public async run() {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(OsVersionsCmd);
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
OsVersionsCmd,
|
||||
);
|
||||
|
||||
const { versions: vs, recommended } =
|
||||
await getBalenaSdk().models.os.getSupportedVersions(params.type);
|
||||
const { getFormattedOsVersions } = await import('../../utils/cloud');
|
||||
const vs = await getFormattedOsVersions(params.type, !!options.esr);
|
||||
|
||||
vs.forEach((v) => {
|
||||
console.log(`v${v}` + (v === recommended ? ' (recommended)' : ''));
|
||||
});
|
||||
console.log(vs.map((v) => v.formattedVersion).join('\n'));
|
||||
}
|
||||
}
|
||||
|
@ -282,10 +282,17 @@ Can be repeated to add multiple certificates.\
|
||||
if (signal) {
|
||||
gotSignal = true;
|
||||
nodeCleanup.uninstall(); // don't call cleanup handler again
|
||||
preloader.cleanup().then(() => {
|
||||
// calling process.exit() won't inform parent process of signal
|
||||
process.kill(process.pid, signal);
|
||||
});
|
||||
preloader
|
||||
.cleanup()
|
||||
.then(() => {
|
||||
// calling process.exit() won't inform parent process of signal
|
||||
process.kill(process.pid, signal);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (process.env.DEBUG) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -68,6 +68,7 @@ export default class ScanCmd extends Command {
|
||||
|
||||
public static primary = true;
|
||||
public static root = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const _ = await import('lodash');
|
||||
|
@ -117,6 +117,7 @@ export default class SshCmd extends Command {
|
||||
};
|
||||
|
||||
public static primary = true;
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
|
||||
@ -144,6 +145,7 @@ export default class SshCmd extends Command {
|
||||
const useProxy = !!proxyConfig && !options.noproxy;
|
||||
|
||||
// this will be a tunnelled SSH connection...
|
||||
await Command.checkNotUsingOfflineMode();
|
||||
await Command.checkLoggedIn();
|
||||
const deviceUuid = await getOnlineTargetDeviceUuid(
|
||||
sdk,
|
||||
|
@ -38,6 +38,8 @@ export default class UtilAvailableDrivesCmd extends Command {
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public async run() {
|
||||
this.parse<FlagsDef, {}>(UtilAvailableDrivesCmd);
|
||||
|
||||
|
@ -57,6 +57,8 @@ export default class VersionCmd extends Command {
|
||||
|
||||
public static usage = 'version';
|
||||
|
||||
public static offlineCompatible = true;
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
all: flags.boolean({
|
||||
default: false,
|
||||
|
@ -104,7 +104,11 @@ export class DeprecationChecker {
|
||||
const url = this.getNpmUrl(version);
|
||||
let response: import('got').Response<Dictionary<any>> | undefined;
|
||||
try {
|
||||
response = await got(url, { responseType: 'json', retry: 0 });
|
||||
response = await got(url, {
|
||||
responseType: 'json',
|
||||
retry: 0,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (e) {
|
||||
// 404 is expected if `version` hasn't been published yet
|
||||
if (e.response?.statusCode !== 404) {
|
||||
|
@ -31,6 +31,8 @@ export class NotLoggedInError extends ExpectedError {}
|
||||
|
||||
export class InsufficientPrivilegesError extends ExpectedError {}
|
||||
|
||||
export class NotAvailableInOfflineModeError extends ExpectedError {}
|
||||
|
||||
export class InvalidPortMappingError extends ExpectedError {
|
||||
constructor(mapping: string) {
|
||||
super(`'${mapping}' is not a valid port mapping.`);
|
||||
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import * as packageJSON from '../package.json';
|
||||
import { getBalenaSdk } from './utils/lazy';
|
||||
import { getBalenaSdk, stripIndent } from './utils/lazy';
|
||||
|
||||
interface CachedUsername {
|
||||
token: string;
|
||||
@ -129,10 +129,20 @@ async function sendEvent(balenaUrl: string, event: string, username?: string) {
|
||||
data: Buffer.from(JSON.stringify(trackData)).toString('base64'),
|
||||
};
|
||||
try {
|
||||
await got(url, { searchParams, retry: 0 });
|
||||
await got(url, { searchParams, retry: 0, timeout: 4000 });
|
||||
} catch (e) {
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] Event tracking error: ${e.message || e}`);
|
||||
}
|
||||
|
||||
if (e instanceof got.TimeoutError) {
|
||||
console.error(stripIndent`
|
||||
Timeout submitting analytics event to balenaCloud/openBalena.
|
||||
If you are using the balena CLI in an air-gapped environment with a filtered
|
||||
internet connection, set the BALENARC_OFFLINE_MODE=1 environment variable
|
||||
when using CLI commands that do not strictly require access to balenaCloud.
|
||||
`);
|
||||
}
|
||||
// Note: You can simulate a timeout using non-routable address 10.0.0.0
|
||||
}
|
||||
}
|
||||
|
19
lib/framework/index.ts
Normal file
19
lib/framework/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import type { DataOutputOptions, DataSetOutputOptions } from './output';
|
||||
|
||||
export { DataOutputOptions, DataSetOutputOptions };
|
158
lib/framework/output.ts
Normal file
158
lib/framework/output.ts
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { getCliUx, getChalk } from '../utils/lazy';
|
||||
|
||||
export interface DataOutputOptions {
|
||||
fields?: string;
|
||||
json?: boolean;
|
||||
}
|
||||
|
||||
export interface DataSetOutputOptions extends DataOutputOptions {
|
||||
filter?: string;
|
||||
'no-header'?: boolean;
|
||||
'no-truncate'?: boolean;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output message to STDERR
|
||||
*/
|
||||
export function outputMessage(msg: string) {
|
||||
// Messages go to STDERR
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output result data to STDOUT
|
||||
* Supports:
|
||||
* - arrays of items (displayed in a tabular way),
|
||||
* - single items (displayed in a field per row format).
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
export async function outputData(
|
||||
data: any[] | {},
|
||||
fields: string[],
|
||||
options: DataOutputOptions | DataSetOutputOptions,
|
||||
) {
|
||||
if (Array.isArray(data)) {
|
||||
await outputDataSet(data, fields, options as DataSetOutputOptions);
|
||||
} else {
|
||||
await outputDataItem(data, fields, options as DataOutputOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the cli.ux table implementation, to output tabular data
|
||||
*
|
||||
* @param data Array of data objects to output
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
async function outputDataSet(
|
||||
data: any[],
|
||||
fields: string[],
|
||||
options: DataSetOutputOptions,
|
||||
) {
|
||||
// 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
|
||||
// (e.g. as seen in json output).
|
||||
options.fields = options.fields?.replace(/_/g, ' ');
|
||||
options.filter = options.filter?.replace(/_/g, ' ');
|
||||
options.sort = options.sort?.replace(/_/g, ' ');
|
||||
|
||||
getCliUx().table(
|
||||
data,
|
||||
// Convert fields array to column object keys
|
||||
// that cli.ux expects. We can later add support
|
||||
// for both formats if beneficial
|
||||
fields.reduce((ac, a) => ({ ...ac, [a]: {} }), {}),
|
||||
{
|
||||
...options,
|
||||
...(options.json
|
||||
? {
|
||||
output: 'json',
|
||||
}
|
||||
: {}),
|
||||
columns: options.fields,
|
||||
printLine,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a single data object (like `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
|
||||
* @param fields Array of fieldnames, specifying the fields and display order
|
||||
* @param options Output options
|
||||
*/
|
||||
async function outputDataItem(
|
||||
data: any,
|
||||
fields: string[],
|
||||
options: DataOutputOptions,
|
||||
) {
|
||||
const 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`),
|
||||
// or the format displayed in headers (e.g. `Some field`, case insensitive).
|
||||
const userSelectedFields = options.fields?.split(',').map((f) => {
|
||||
return f.toLowerCase().trim().replace(/ /g, '_');
|
||||
});
|
||||
|
||||
// Order and filter the fields based on `fields` parameter and `options.fields`
|
||||
(userSelectedFields || fields).forEach((fieldName) => {
|
||||
if (fields.includes(fieldName)) {
|
||||
outData[fieldName] = data[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
// Output one field per line
|
||||
for (const [k, v] of Object.entries(outData)) {
|
||||
const shim = ' '.repeat(longestKeyLength - k.length);
|
||||
const kDisplay = capitalize(k.replace(/_/g, ' '));
|
||||
printLine(`${chalk.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;
|
||||
}
|
||||
|
||||
function printLine(s: any) {
|
||||
// Duplicating oclif cli-ux's default implementation here,
|
||||
// but using this one explicitly for ease of testing
|
||||
process.stdout.write(s + '\n');
|
||||
}
|
@ -58,7 +58,13 @@ export function normalizeEnvVar(varName: string) {
|
||||
process.env[varName] = parseBoolEnvVar(varName) ? '1' : '';
|
||||
}
|
||||
|
||||
const bootstrapVars = ['DEBUG', 'BALENARC_NO_SENTRY'];
|
||||
const bootstrapVars = [
|
||||
'BALENARC_NO_SENTRY',
|
||||
'BALENARC_NO_ANALYTICS',
|
||||
'BALENARC_OFFLINE_MODE',
|
||||
'BALENARC_UNSUPPORTED',
|
||||
'DEBUG',
|
||||
];
|
||||
|
||||
export function normalizeEnvVars(varNames: string[] = bootstrapVars) {
|
||||
for (const varName of varNames) {
|
||||
@ -66,6 +72,17 @@ export function normalizeEnvVars(varNames: string[] = bootstrapVars) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the individual env vars implied by BALENARC_OFFLINE_MODE.
|
||||
*/
|
||||
export function setOfflineModeEnvVars() {
|
||||
if (process.env.BALENARC_OFFLINE_MODE) {
|
||||
process.env.BALENARC_UNSUPPORTED = '1';
|
||||
process.env.BALENARC_NO_SENTRY = '1';
|
||||
process.env.BALENARC_NO_ANALYTICS = '1';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the 'pkgExec' command, used as a way to provide a Node.js
|
||||
* interpreter for child_process.spawn()-like operations when the CLI is
|
||||
|
@ -107,6 +107,16 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
|
||||
(_sdk, deviceUUID) => deviceUUID,
|
||||
);
|
||||
|
||||
/** Given a device type alias like 'nuc', return the actual slug like 'intel-nuc'. */
|
||||
export const unaliasDeviceType = _.memoize(async function (
|
||||
sdk: SDK.BalenaSDK,
|
||||
deviceType: string,
|
||||
): Promise<string> {
|
||||
return (
|
||||
(await sdk.models.device.getManifestBySlug(deviceType)).slug || deviceType
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Download balenaOS image for the specified `deviceType`.
|
||||
* `OSVersion` may be one of:
|
||||
@ -130,15 +140,13 @@ export async function downloadOSImage(
|
||||
console.info(`Getting device operating system for ${deviceType}`);
|
||||
|
||||
if (!OSVersion) {
|
||||
console.warn('OS version not specified: using latest stable version');
|
||||
console.warn('OS version not specified: using latest released version');
|
||||
}
|
||||
|
||||
OSVersion = OSVersion
|
||||
? await resolveOSVersion(deviceType, OSVersion)
|
||||
: 'default';
|
||||
|
||||
const displayVersion = OSVersion === 'default' ? '' : ` ${OSVersion}`;
|
||||
|
||||
// Override the default zlib flush value as we've seen cases of
|
||||
// incomplete files being identified as successful downloads when using Z_SYNC_FLUSH.
|
||||
// Using Z_NO_FLUSH results in a Z_BUF_ERROR instead of a corrupt image file.
|
||||
@ -150,10 +158,17 @@ export async function downloadOSImage(
|
||||
const manager = await import('balena-image-manager');
|
||||
const stream = await manager.get(deviceType, OSVersion);
|
||||
|
||||
const displayVersion = await new Promise((resolve, reject) => {
|
||||
stream.on('error', reject);
|
||||
stream.on('balena-image-manager:resolved-version', resolve);
|
||||
});
|
||||
|
||||
const visuals = getVisuals();
|
||||
const bar = new visuals.Progress(`Downloading Device OS${displayVersion}`);
|
||||
const bar = new visuals.Progress(
|
||||
`Downloading balenaOS version ${displayVersion}`,
|
||||
);
|
||||
const spinner = new visuals.Spinner(
|
||||
`Downloading Device OS${displayVersion} (size unknown)`,
|
||||
`Downloading balenaOS version ${displayVersion} (size unknown)`,
|
||||
);
|
||||
|
||||
stream.on('progress', (state: any) => {
|
||||
@ -183,28 +198,42 @@ export async function downloadOSImage(
|
||||
const streamToPromise = await import('stream-to-promise');
|
||||
await streamToPromise(stream.pipe(output));
|
||||
|
||||
console.info('The image was downloaded successfully');
|
||||
console.info(
|
||||
`balenaOS image version ${displayVersion} downloaded successfully`,
|
||||
);
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
async function resolveOSVersion(deviceType: string, version: string) {
|
||||
if (version !== 'menu') {
|
||||
async function resolveOSVersion(
|
||||
deviceType: string,
|
||||
version: string,
|
||||
): Promise<string> {
|
||||
if (['menu', 'menu-esr'].includes(version)) {
|
||||
return await selectOSVersionFromMenu(deviceType, version === 'menu-esr');
|
||||
}
|
||||
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
||||
if (version[0] === 'v') {
|
||||
version = version.slice(1);
|
||||
}
|
||||
return version;
|
||||
// The version must end with either '.dev' or '.prod', as expected
|
||||
// by `balena-image-manager` and the balena SDK.
|
||||
if (!version.endsWith('.dev') && !version.endsWith('.prod')) {
|
||||
version += '.prod';
|
||||
}
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
const vs = (
|
||||
(await getBalenaSdk().models.hostapp.getAllOsVersions([deviceType]))[
|
||||
deviceType
|
||||
] ?? []
|
||||
).filter((v) => v.osType === 'default');
|
||||
async function selectOSVersionFromMenu(
|
||||
deviceType: string,
|
||||
esr: boolean,
|
||||
): Promise<string> {
|
||||
const vs = await getFormattedOsVersions(deviceType, esr);
|
||||
|
||||
const choices = vs.map((v) => ({
|
||||
value: v.rawVersion,
|
||||
name: `v${v.rawVersion}` + (v.isRecommended ? ' (recommended)' : ''),
|
||||
name: v.formattedVersion,
|
||||
}));
|
||||
|
||||
return getCliForm().ask({
|
||||
@ -214,3 +243,44 @@ async function resolveOSVersion(deviceType: string, version: string) {
|
||||
default: (vs.find((v) => v.isRecommended) ?? vs[0])?.rawVersion,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the output of sdk.models.hostapp.getAvailableOsVersions(), filtered
|
||||
* regarding ESR or non-ESR versions, and having the `formattedVersion` field
|
||||
* reformatted for compatibility with the pre-existing output format of the
|
||||
* `os versions` and `os download` commands.
|
||||
*/
|
||||
export async function getFormattedOsVersions(
|
||||
deviceType: string,
|
||||
esr: boolean,
|
||||
): Promise<SDK.OsVersion[]> {
|
||||
const sdk = getBalenaSdk();
|
||||
let slug = deviceType;
|
||||
let versionsByDT: SDK.OsVersionsByDeviceType =
|
||||
await sdk.models.hostapp.getAvailableOsVersions([slug]);
|
||||
// if slug is an alias, fetch the real slug
|
||||
if (!versionsByDT[slug]?.length) {
|
||||
// unaliasDeviceType() produces a nice error msg if slug is invalid
|
||||
slug = await unaliasDeviceType(sdk, slug);
|
||||
if (slug !== deviceType) {
|
||||
versionsByDT = await sdk.models.hostapp.getAvailableOsVersions([slug]);
|
||||
}
|
||||
}
|
||||
const versions: SDK.OsVersion[] = (versionsByDT[slug] || [])
|
||||
.filter((v: SDK.OsVersion) => v.osType === (esr ? 'esr' : 'default'))
|
||||
.map((v: SDK.OsVersion) => {
|
||||
const i = v.formattedVersion.indexOf(' ');
|
||||
v.formattedVersion =
|
||||
i < 0
|
||||
? `v${v.rawVersion}`
|
||||
: `v${v.rawVersion}${v.formattedVersion.substring(i)}`;
|
||||
return v;
|
||||
});
|
||||
if (!versions.length) {
|
||||
const vType = esr ? 'ESR versions' : 'versions';
|
||||
throw new ExpectedError(
|
||||
`Error: No balenaOS ${vType} found for device type '${deviceType}'.`,
|
||||
);
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
@ -16,11 +16,12 @@
|
||||
*/
|
||||
|
||||
import { flags } from '@oclif/command';
|
||||
|
||||
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
|
||||
import { stripIndent } from './lazy';
|
||||
import { lowercaseIfSlug } from './normalization';
|
||||
|
||||
import { isV13 } from './version';
|
||||
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
|
||||
import type { DataOutputOptions, DataSetOutputOptions } from '../framework';
|
||||
|
||||
export const v13: IBooleanFlag<boolean> = flags.boolean({
|
||||
description: stripIndent`\
|
||||
@ -113,8 +114,49 @@ export const deviceType = flags.string({
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const deviceTypeIgnored = flags.string({
|
||||
description: 'ignored - no longer required',
|
||||
char: 't',
|
||||
required: false,
|
||||
hidden: isV13(),
|
||||
});
|
||||
|
||||
export const json: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'j',
|
||||
description: 'produce JSON output instead of tabular output',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const dataOutputFlags: flags.Input<DataOutputOptions> = {
|
||||
fields: flags.string({
|
||||
description: 'only show provided fields (comma-separated)',
|
||||
}),
|
||||
json: flags.boolean({
|
||||
char: 'j',
|
||||
exclusive: ['no-truncate'],
|
||||
description: 'output in json format',
|
||||
default: false,
|
||||
}),
|
||||
};
|
||||
|
||||
export const dataSetOutputFlags: flags.Input<DataOutputOptions> &
|
||||
flags.Input<DataSetOutputOptions> = {
|
||||
...dataOutputFlags,
|
||||
filter: flags.string({
|
||||
description:
|
||||
'filter results by substring matching of a given field, eg: --filter field=foo',
|
||||
}),
|
||||
'no-header': flags.boolean({
|
||||
exclusive: ['json'],
|
||||
description: 'hide table header from output',
|
||||
default: false,
|
||||
}),
|
||||
'no-truncate': flags.boolean({
|
||||
exclusive: ['json'],
|
||||
description: 'do not truncate output to fit screen',
|
||||
default: false,
|
||||
}),
|
||||
sort: flags.string({
|
||||
description: `field to sort by (prepend '-' for descending order)`,
|
||||
}),
|
||||
};
|
||||
|
@ -63,9 +63,7 @@ export function createProject(
|
||||
const compose = require('resin-compose-parse');
|
||||
|
||||
// both methods below may throw.
|
||||
const rawComposition = yml.load(composeStr, {
|
||||
schema: yml.FAILSAFE_SCHEMA,
|
||||
});
|
||||
const rawComposition = yml.load(composeStr);
|
||||
const composition = compose.normalize(rawComposition);
|
||||
|
||||
projectName ||= path.basename(composePath);
|
||||
@ -356,76 +354,6 @@ export const authorizePush = function (
|
||||
.catch(() => '');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('dockerode')} docker
|
||||
* @param {string} token
|
||||
* @param {Array<import('./compose-types').TaggedImage>} images
|
||||
* @param {(serviceImage: import('balena-release/build/models').ImageModel, props: object) => void} afterEach
|
||||
*/
|
||||
export const pushAndUpdateServiceImages = function (
|
||||
docker,
|
||||
token,
|
||||
images,
|
||||
afterEach,
|
||||
) {
|
||||
const { DockerProgress } = require('docker-progress');
|
||||
const { retry } = require('./helpers');
|
||||
const tty = require('./tty')(process.stdout);
|
||||
const Bluebird = require('bluebird');
|
||||
|
||||
const opts = { authconfig: { registrytoken: token } };
|
||||
|
||||
const progress = new DockerProgress({ docker });
|
||||
const renderer = pushProgressRenderer(
|
||||
tty,
|
||||
getChalk().blue('[Push]') + ' ',
|
||||
);
|
||||
const reporters = progress.aggregateProgress(images.length, renderer);
|
||||
|
||||
return Bluebird.using(tty.cursorHidden(), () =>
|
||||
Promise.all(
|
||||
images.map(({ serviceImage, localImage, props, logs }, index) =>
|
||||
Promise.all([
|
||||
localImage.inspect().then((img) => img.Size),
|
||||
retry({
|
||||
// @ts-ignore
|
||||
func: () => progress.push(localImage.name, reporters[index], opts),
|
||||
maxAttempts: 3, // try calling func 3 times (max)
|
||||
// @ts-ignore
|
||||
label: localImage.name, // label for retry log messages
|
||||
initialDelayMs: 2000, // wait 2 seconds before the 1st retry
|
||||
backoffScaler: 1.4, // wait multiplier for each retry
|
||||
}).finally(renderer.end),
|
||||
])
|
||||
.then(
|
||||
/** @type {([number, string]) => void} */
|
||||
function ([size, digest]) {
|
||||
serviceImage.image_size = size;
|
||||
serviceImage.content_hash = digest;
|
||||
serviceImage.build_log = logs;
|
||||
serviceImage.dockerfile = props.dockerfile;
|
||||
serviceImage.project_type = props.projectType;
|
||||
if (props.startTime) {
|
||||
serviceImage.start_timestamp = props.startTime;
|
||||
}
|
||||
if (props.endTime) {
|
||||
serviceImage.end_timestamp = props.endTime;
|
||||
}
|
||||
serviceImage.push_timestamp = new Date();
|
||||
serviceImage.status = 'success';
|
||||
},
|
||||
)
|
||||
.catch(function (e) {
|
||||
serviceImage.error_message = '' + e;
|
||||
serviceImage.status = 'failed';
|
||||
throw e;
|
||||
})
|
||||
.finally(() => afterEach?.(serviceImage, props)),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
// utilities
|
||||
|
||||
const renderProgressBar = function (percentage, stepCount) {
|
||||
@ -437,7 +365,7 @@ const renderProgressBar = function (percentage, stepCount) {
|
||||
return `${bar} ${_.padStart(percentage, 3)}%`;
|
||||
};
|
||||
|
||||
var pushProgressRenderer = function (tty, prefix) {
|
||||
export const pushProgressRenderer = function (tty, prefix) {
|
||||
const fn = function (e) {
|
||||
const { error, percentage } = e;
|
||||
if (error != null) {
|
||||
|
@ -1085,53 +1085,52 @@ async function performResolution(
|
||||
releaseHash: string,
|
||||
preprocessHook?: (dockerfile: string) => string,
|
||||
): Promise<MultiBuild.BuildTask[]> {
|
||||
const { cloneTarStream } = await import('tar-utils');
|
||||
const multiBuild = await import('resin-multibuild');
|
||||
|
||||
return await new Promise<MultiBuild.BuildTask[]>((resolve, reject) => {
|
||||
const buildTasks = multiBuild.performResolution(
|
||||
tasks,
|
||||
deviceInfo.arch,
|
||||
deviceInfo.deviceType,
|
||||
{ error: [reject] },
|
||||
{
|
||||
BALENA_RELEASE_HASH: releaseHash,
|
||||
BALENA_APP_NAME: appName,
|
||||
},
|
||||
preprocessHook,
|
||||
);
|
||||
(async () => {
|
||||
try {
|
||||
// Do one task at a time in order to reduce peak memory usage. Resolves to buildTasks.
|
||||
for (const buildTask of buildTasks) {
|
||||
// buildStream is falsy for "external" tasks (image pull)
|
||||
if (!buildTask.buildStream) {
|
||||
continue;
|
||||
}
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
// Consume each task.buildStream in order to trigger the
|
||||
// resolution events that define fields like:
|
||||
// task.dockerfile, task.dockerfilePath,
|
||||
// task.projectType, task.resolved
|
||||
// This mimics what is currently done in `resin-builder`.
|
||||
buildTask.buildStream = await cloneTarStream(buildTask.buildStream);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
if (error || (!buildTask.external && !buildTask.resolved)) {
|
||||
const cause = error ? `${error}\n` : '';
|
||||
throw new ExpectedError(
|
||||
`${cause}Project type for service "${buildTask.serviceName}" could not be determined. Missing a Dockerfile?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
resolve(buildTasks);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
})();
|
||||
const resolveListeners: MultiBuild.ResolveListeners = {};
|
||||
const resolvePromise = new Promise<never>((_resolve, reject) => {
|
||||
resolveListeners.error = [reject];
|
||||
});
|
||||
const buildTasks = multiBuild.performResolution(
|
||||
tasks,
|
||||
deviceInfo.arch,
|
||||
deviceInfo.deviceType,
|
||||
resolveListeners,
|
||||
{
|
||||
BALENA_RELEASE_HASH: releaseHash,
|
||||
BALENA_APP_NAME: appName,
|
||||
},
|
||||
preprocessHook,
|
||||
);
|
||||
await Promise.race([resolvePromise, resolveTasks(buildTasks)]);
|
||||
return buildTasks;
|
||||
}
|
||||
|
||||
async function resolveTasks(buildTasks: MultiBuild.BuildTask[]) {
|
||||
const { cloneTarStream } = await import('tar-utils');
|
||||
// Do one task at a time in order to reduce peak memory usage. Resolves to buildTasks.
|
||||
for (const buildTask of buildTasks) {
|
||||
// buildStream is falsy for "external" tasks (image pull)
|
||||
if (!buildTask.buildStream) {
|
||||
continue;
|
||||
}
|
||||
let error: Error | undefined;
|
||||
try {
|
||||
// Consume each task.buildStream in order to trigger the
|
||||
// resolution events that define fields like:
|
||||
// task.dockerfile, task.dockerfilePath,
|
||||
// task.projectType, task.resolved
|
||||
// This mimics what is currently done in `resin-builder`.
|
||||
buildTask.buildStream = await cloneTarStream(buildTask.buildStream);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
if (error || (!buildTask.external && !buildTask.resolved)) {
|
||||
const cause = error ? `${error}\n` : '';
|
||||
throw new ExpectedError(
|
||||
`${cause}Project type for service "${buildTask.serviceName}" could not be determined. Missing a Dockerfile?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1306,15 +1305,101 @@ async function getTokenForPreviousRepos(
|
||||
return token;
|
||||
}
|
||||
|
||||
async function pushAndUpdateServiceImages(
|
||||
docker: Dockerode,
|
||||
token: string,
|
||||
images: TaggedImage[],
|
||||
afterEach: (
|
||||
serviceImage: import('balena-release/build/models').ImageModel,
|
||||
props: object,
|
||||
) => void,
|
||||
) {
|
||||
const { DockerProgress } = await import('docker-progress');
|
||||
const { retry } = await import('./helpers');
|
||||
const { pushProgressRenderer } = await import('./compose');
|
||||
const tty = (await import('./tty'))(process.stdout);
|
||||
const opts = { authconfig: { registrytoken: token } };
|
||||
const progress = new DockerProgress({ docker });
|
||||
const renderer = pushProgressRenderer(
|
||||
tty,
|
||||
getChalk().blue('[Push]') + ' ',
|
||||
);
|
||||
const reporters = progress.aggregateProgress(images.length, renderer);
|
||||
|
||||
const pushImage = async (
|
||||
localImage: Dockerode.Image,
|
||||
index: number,
|
||||
): Promise<string> => {
|
||||
try {
|
||||
// TODO 'localImage as any': find out exactly why tsc warns about
|
||||
// 'name' that exists as a matter of fact, with a value similar to:
|
||||
// "name": "registry2.balena-cloud.com/v2/aa27790dff571ec7d2b4fbcf3d4648d5:latest"
|
||||
const imgName: string = (localImage as any).name || '';
|
||||
const imageDigest: string = await retry({
|
||||
func: () => progress.push(imgName, reporters[index], opts),
|
||||
maxAttempts: 3, // try calling func 3 times (max)
|
||||
label: imgName, // label for retry log messages
|
||||
initialDelayMs: 2000, // wait 2 seconds before the 1st retry
|
||||
backoffScaler: 1.4, // wait multiplier for each retry
|
||||
});
|
||||
if (!imageDigest) {
|
||||
throw new ExpectedError(stripIndent`\
|
||||
Unable to extract image digest (content hash) from image upload progress stream for image:
|
||||
${imgName}`);
|
||||
}
|
||||
return imageDigest;
|
||||
} finally {
|
||||
renderer.end();
|
||||
}
|
||||
};
|
||||
|
||||
const inspectAndPushImage = async (
|
||||
{ serviceImage, localImage, props, logs }: TaggedImage,
|
||||
index: number,
|
||||
) => {
|
||||
try {
|
||||
const [imgInfo, imgDigest] = await Promise.all([
|
||||
localImage.inspect(),
|
||||
pushImage(localImage, index),
|
||||
]);
|
||||
serviceImage.image_size = imgInfo.Size;
|
||||
serviceImage.content_hash = imgDigest;
|
||||
serviceImage.build_log = logs;
|
||||
serviceImage.dockerfile = props.dockerfile;
|
||||
serviceImage.project_type = props.projectType;
|
||||
if (props.startTime) {
|
||||
serviceImage.start_timestamp = props.startTime;
|
||||
}
|
||||
if (props.endTime) {
|
||||
serviceImage.end_timestamp = props.endTime;
|
||||
}
|
||||
serviceImage.push_timestamp = new Date();
|
||||
serviceImage.status = 'success';
|
||||
} catch (error) {
|
||||
serviceImage.error_message = '' + error;
|
||||
serviceImage.status = 'failed';
|
||||
throw error;
|
||||
} finally {
|
||||
await afterEach(serviceImage, props);
|
||||
}
|
||||
};
|
||||
|
||||
tty.hideCursor();
|
||||
try {
|
||||
await Promise.all(images.map(inspectAndPushImage));
|
||||
} finally {
|
||||
tty.showCursor();
|
||||
}
|
||||
}
|
||||
|
||||
async function pushServiceImages(
|
||||
docker: import('dockerode'),
|
||||
docker: Dockerode,
|
||||
logger: Logger,
|
||||
pineClient: ReturnType<typeof import('balena-release').createClient>,
|
||||
taggedImages: TaggedImage[],
|
||||
token: string,
|
||||
skipLogUpload: boolean,
|
||||
): Promise<void> {
|
||||
const { pushAndUpdateServiceImages } = await import('./compose');
|
||||
const releaseMod = await import('balena-release');
|
||||
logger.logInfo('Pushing images to registry...');
|
||||
await pushAndUpdateServiceImages(
|
||||
@ -1337,7 +1422,7 @@ async function pushServiceImages(
|
||||
const PLAIN_SEMVER_REGEX = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/;
|
||||
|
||||
export async function deployProject(
|
||||
docker: import('dockerode'),
|
||||
docker: Dockerode,
|
||||
logger: Logger,
|
||||
composition: Composition,
|
||||
images: BuiltImage[],
|
||||
|
@ -35,6 +35,9 @@ export interface ImgConfig {
|
||||
wifiKey?: string;
|
||||
initialDeviceName?: string;
|
||||
|
||||
apiKey?: string;
|
||||
deviceApiKey?: string;
|
||||
|
||||
// props for older OS versions
|
||||
connectivity?: string;
|
||||
files?: {
|
||||
@ -51,7 +54,7 @@ export interface ImgConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateBaseConfig(
|
||||
export async function generateApplicationConfig(
|
||||
application: BalenaSdk.Application,
|
||||
options: {
|
||||
version: string;
|
||||
@ -70,9 +73,7 @@ export async function generateBaseConfig(
|
||||
const config = (await getBalenaSdk().models.os.getConfig(
|
||||
application.slug,
|
||||
options,
|
||||
)) as ImgConfig & { apiKey?: string };
|
||||
// os.getConfig always returns a config for an app
|
||||
delete config.apiKey;
|
||||
)) as ImgConfig;
|
||||
|
||||
// merge sshKeys to config, when they have been specified
|
||||
if (options.os && options.os.sshKeys) {
|
||||
@ -86,25 +87,6 @@ export async function generateBaseConfig(
|
||||
return config;
|
||||
}
|
||||
|
||||
export async function generateApplicationConfig(
|
||||
application: BalenaSdk.Application,
|
||||
options: {
|
||||
version: string;
|
||||
deviceType?: string;
|
||||
appUpdatePollInterval?: number;
|
||||
},
|
||||
) {
|
||||
const config = await generateBaseConfig(application, options);
|
||||
|
||||
if (semver.satisfies(options.version, '<2.7.8')) {
|
||||
await addApplicationKey(config, application.id);
|
||||
} else {
|
||||
await addProvisioningKey(config, application.id);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function generateDeviceConfig(
|
||||
device: DeviceWithDeviceType & {
|
||||
belongs_to__application: BalenaSdk.PineDeferred;
|
||||
@ -112,19 +94,32 @@ export function generateDeviceConfig(
|
||||
deviceApiKey: string | true | undefined,
|
||||
options: { version: string },
|
||||
) {
|
||||
return getBalenaSdk()
|
||||
.models.application.get(device.belongs_to__application.__id)
|
||||
const sdk = getBalenaSdk();
|
||||
return sdk.models.application
|
||||
.get(device.belongs_to__application.__id)
|
||||
.then(async (application) => {
|
||||
const baseConfigOpts = {
|
||||
...options,
|
||||
deviceType: device.is_of__device_type[0].slug,
|
||||
};
|
||||
const config = await generateBaseConfig(application, baseConfigOpts);
|
||||
// TODO: Generate the correct key beforehand and pass it to os.getConfig() once
|
||||
// the API supports injecting a provided key, to avoid generating an unused one.
|
||||
const config = await generateApplicationConfig(
|
||||
application,
|
||||
baseConfigOpts,
|
||||
);
|
||||
// os.getConfig always returns a config for an app
|
||||
delete config.apiKey;
|
||||
|
||||
if (deviceApiKey == null && semver.satisfies(options.version, '<2.0.3')) {
|
||||
await addApplicationKey(config, application.id);
|
||||
config.apiKey = await sdk.models.application.generateApiKey(
|
||||
application.id,
|
||||
);
|
||||
} else {
|
||||
await addDeviceKey(config, device.uuid, deviceApiKey || true);
|
||||
config.deviceApiKey =
|
||||
typeof deviceApiKey === 'string' && deviceApiKey
|
||||
? deviceApiKey
|
||||
: await sdk.models.device.generateDeviceKey(device.uuid);
|
||||
}
|
||||
|
||||
return config;
|
||||
@ -139,35 +134,3 @@ export function generateDeviceConfig(
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
function addApplicationKey(config: any, applicationNameOrId: string | number) {
|
||||
return getBalenaSdk()
|
||||
.models.application.generateApiKey(applicationNameOrId)
|
||||
.then((apiKey) => {
|
||||
config.apiKey = apiKey;
|
||||
return apiKey;
|
||||
});
|
||||
}
|
||||
|
||||
function addProvisioningKey(config: any, applicationNameOrId: string | number) {
|
||||
return getBalenaSdk()
|
||||
.models.application.generateProvisioningKey(applicationNameOrId)
|
||||
.then((apiKey) => {
|
||||
config.apiKey = apiKey;
|
||||
return apiKey;
|
||||
});
|
||||
}
|
||||
|
||||
async function addDeviceKey(
|
||||
config: any,
|
||||
uuid: string,
|
||||
customDeviceApiKey: string | true,
|
||||
) {
|
||||
if (customDeviceApiKey === true) {
|
||||
config.deviceApiKey = await getBalenaSdk().models.device.generateDeviceKey(
|
||||
uuid,
|
||||
);
|
||||
} else {
|
||||
config.deviceApiKey = customDeviceApiKey;
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
loadProject,
|
||||
makeBuildTasks,
|
||||
tarDirectory,
|
||||
makeImageName,
|
||||
} from '../compose_ts';
|
||||
import Logger = require('../logger');
|
||||
import { DeviceAPI, DeviceInfo } from './api';
|
||||
@ -44,6 +45,7 @@ import { stripIndent } from '../lazy';
|
||||
|
||||
const LOCAL_APPNAME = 'localapp';
|
||||
const LOCAL_RELEASEHASH = 'localrelease';
|
||||
const LOCAL_PROJECT_NAME = 'local_image';
|
||||
|
||||
// Define the logger here so the debug output
|
||||
// can be used everywhere
|
||||
@ -197,14 +199,17 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
|
||||
await checkBuildSecretsRequirements(docker, opts.source);
|
||||
globalLogger.logDebug('Tarring all non-ignored files...');
|
||||
const tarStartTime = Date.now();
|
||||
const tarStream = await tarDirectory(opts.source, {
|
||||
composition: project.composition,
|
||||
convertEol: opts.convertEol,
|
||||
multiDockerignore: opts.multiDockerignore,
|
||||
nogitignore: opts.nogitignore, // v13: delete this line
|
||||
});
|
||||
globalLogger.logDebug(`Tarring complete in ${Date.now() - tarStartTime} ms`);
|
||||
|
||||
// Try to detect the device information
|
||||
globalLogger.logDebug('Fetching device information...');
|
||||
const deviceInfo = await api.getDeviceInformation();
|
||||
|
||||
let buildLogs: Dictionary<string> | undefined;
|
||||
@ -375,7 +380,11 @@ async function performBuilds(
|
||||
// We can be sure that localImage.name is set here, because of the failure code above
|
||||
const image = docker.getImage(localImage.name!);
|
||||
await image.tag({
|
||||
repo: generateImageName(localImage.serviceName),
|
||||
repo: makeImageName(
|
||||
LOCAL_PROJECT_NAME,
|
||||
localImage.serviceName,
|
||||
'latest',
|
||||
),
|
||||
force: true,
|
||||
});
|
||||
imagesToRemove.push(localImage.name!);
|
||||
@ -533,7 +542,7 @@ async function assignDockerBuildOpts(
|
||||
'io.resin.local.image': '1',
|
||||
'io.resin.local.service': task.serviceName,
|
||||
},
|
||||
t: generateImageName(task.serviceName),
|
||||
t: getImageNameFromTask(task),
|
||||
nocache: opts.nocache,
|
||||
forcerm: true,
|
||||
pull: opts.pull,
|
||||
@ -550,8 +559,10 @@ async function assignDockerBuildOpts(
|
||||
);
|
||||
}
|
||||
|
||||
function generateImageName(serviceName: string): string {
|
||||
return `local_image_${serviceName}:latest`;
|
||||
function getImageNameFromTask(task: BuildTask): string {
|
||||
return !task.external && task.tag
|
||||
? task.tag
|
||||
: makeImageName(LOCAL_PROJECT_NAME, task.serviceName, 'latest');
|
||||
}
|
||||
|
||||
export function generateTargetState(
|
||||
@ -587,6 +598,8 @@ export function generateTargetState(
|
||||
contract = keyedBuildTasks[name].contract;
|
||||
}
|
||||
|
||||
const task = keyedBuildTasks[name];
|
||||
|
||||
services[idx] = {
|
||||
...defaults,
|
||||
...opts,
|
||||
@ -595,7 +608,7 @@ export function generateTargetState(
|
||||
imageId: idx,
|
||||
serviceName: name,
|
||||
serviceId: idx,
|
||||
image: generateImageName(name),
|
||||
image: getImageNameFromTask(task),
|
||||
running: true,
|
||||
},
|
||||
};
|
||||
|
@ -201,43 +201,6 @@ function getApplication(
|
||||
) as Promise<ApplicationWithDeviceType>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boot partition number of the given image.
|
||||
* @param imagePath Local filesystem path to a balenaOS image file
|
||||
*/
|
||||
export async function getBootPartition(imagePath: string): Promise<number> {
|
||||
const imagefs = await import('balena-image-fs');
|
||||
const filedisk = await import('file-disk');
|
||||
const partitioninfo = await import('partitioninfo');
|
||||
|
||||
const partitionNumber = await filedisk.withOpenFile(
|
||||
imagePath,
|
||||
'r',
|
||||
async (handle) => {
|
||||
const disk = new filedisk.FileDisk(handle, true, false, false);
|
||||
const { partitions } = await partitioninfo.getPartitions(disk, {
|
||||
includeExtended: false,
|
||||
getLogical: true,
|
||||
});
|
||||
for (const { index } of partitions) {
|
||||
try {
|
||||
return await imagefs.interact(disk, index, async (fs) => {
|
||||
const statAsync = promisify(fs.stat);
|
||||
const stats = await statAsync('/device-type.json');
|
||||
if (stats.isFile()) {
|
||||
return index;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return partitionNumber ?? 1;
|
||||
}
|
||||
|
||||
const second = 1000; // 1000 milliseconds
|
||||
const minute = 60 * second;
|
||||
export const delay = promisify(setTimeout);
|
||||
|
@ -200,6 +200,45 @@ export interface FileStats {
|
||||
stats: Stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of files for the filesystem subtree rooted at
|
||||
* projectDir, excluding entries for directories themselves.
|
||||
* @param projectDir Source directory (root of subtree to be listed)
|
||||
*/
|
||||
async function listFiles(projectDir: string): Promise<string[]> {
|
||||
const files: string[] = [];
|
||||
async function walk(currentDirs: string[]): Promise<string[]> {
|
||||
if (!currentDirs.length) {
|
||||
return files;
|
||||
}
|
||||
|
||||
const foundDirs: string[] = [];
|
||||
|
||||
// Because `currentDirs` can be of arbitrary length, process them in smaller batches
|
||||
// to avoid out of memory errors.
|
||||
// This approach is significantly faster than using Bluebird.map with a
|
||||
// concurrency setting
|
||||
const chunks = _.chunk(currentDirs, 100);
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(
|
||||
chunk.map(async (dir) => {
|
||||
const _files = await fs.readdir(dir, { withFileTypes: true });
|
||||
for (const entry of _files) {
|
||||
const fpath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
foundDirs.push(fpath);
|
||||
} else {
|
||||
files.push(fpath);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
return walk(foundDirs);
|
||||
}
|
||||
return walk([projectDir]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contents of a .dockerignore file at projectDir, as a string.
|
||||
* Return an empty string if a .dockerignore file does not exist.
|
||||
@ -211,7 +250,7 @@ async function readDockerIgnoreFile(projectDir: string): Promise<string> {
|
||||
let dockerIgnoreStr = '';
|
||||
try {
|
||||
dockerIgnoreStr = await fs.readFile(dockerIgnorePath, 'utf8');
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw new ExpectedError(
|
||||
`Error reading file "${dockerIgnorePath}": ${err.message}`,
|
||||
@ -269,7 +308,10 @@ export async function filterFilesWithDockerignore(
|
||||
projectDir: string,
|
||||
multiDockerignore: boolean,
|
||||
serviceDirsByService: ServiceDirs,
|
||||
): Promise<{ filteredFileList: FileStats[]; dockerignoreFiles: FileStats[] }> {
|
||||
): Promise<{
|
||||
filteredFileList: FileStats[];
|
||||
dockerignoreFiles: FileStats[];
|
||||
}> {
|
||||
// path.resolve() also converts forward slashes to backslashes on Windows
|
||||
projectDir = path.resolve(projectDir);
|
||||
const root = '.' + path.sep;
|
||||
@ -294,45 +336,65 @@ export async function filterFilesWithDockerignore(
|
||||
const dockerignoreServiceDirs: string[] = multiDockerignore
|
||||
? Object.keys(ignoreByDir).filter((dir) => dir && dir !== root)
|
||||
: [];
|
||||
const files = await listFiles(projectDir);
|
||||
|
||||
const dockerignoreFiles: FileStats[] = [];
|
||||
const filteredFileList: FileStats[] = [];
|
||||
const klaw = await import('klaw');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
// Looking at klaw's source code, `preserveSymlinks` appears to only
|
||||
// afect the `stats` argument to the `data` event handler
|
||||
klaw(projectDir, { preserveSymlinks: false })
|
||||
.on('error', reject)
|
||||
.on('end', resolve)
|
||||
.on('data', (item: { path: string; stats: Stats }) => {
|
||||
const { path: filePath, stats } = item;
|
||||
// With `preserveSymlinks: false`, filePath cannot be a symlink.
|
||||
// filePath may be a directory or a regular or special file
|
||||
if (!stats.isFile()) {
|
||||
return;
|
||||
}
|
||||
// Because `files` can be of arbitrary length, process them in smaller batches
|
||||
// to avoid out of memory errors.
|
||||
// This approach is significantly faster than using Bluebird.map with a
|
||||
// concurrency setting
|
||||
const chunks = _.chunk(files, 750);
|
||||
for (const chunk of chunks) {
|
||||
await Promise.all(
|
||||
chunk.map(async (filePath) => {
|
||||
const relPath = path.relative(projectDir, filePath);
|
||||
const fileInfo = {
|
||||
filePath,
|
||||
relPath,
|
||||
stats,
|
||||
};
|
||||
|
||||
// .dockerignore files are always added to a list of known dockerignore files
|
||||
if (path.basename(relPath) === '.dockerignore') {
|
||||
dockerignoreFiles.push(fileInfo);
|
||||
const diStats = await fs.stat(filePath);
|
||||
dockerignoreFiles.push({
|
||||
filePath,
|
||||
relPath,
|
||||
stats: diStats,
|
||||
});
|
||||
}
|
||||
for (const dir of dockerignoreServiceDirs) {
|
||||
if (relPath.startsWith(dir)) {
|
||||
if (!ignoreByDir[dir].ignores(relPath.substring(dir.length))) {
|
||||
filteredFileList.push(fileInfo);
|
||||
}
|
||||
|
||||
// First check if the file is ignored by a .dockerignore file in a service directory
|
||||
const matchingDir = dockerignoreServiceDirs.find((dir) => {
|
||||
return relPath.startsWith(dir);
|
||||
});
|
||||
|
||||
// If the file is ignore in a service directory, exit early, otherwise check if it is ignored by the root .dockerignore file.
|
||||
// Crucially, if the file is in a known service directory, and isn't ignored, the root .dockerignore file should not be checked.
|
||||
if (matchingDir) {
|
||||
if (
|
||||
ignoreByDir[matchingDir].ignores(
|
||||
relPath.substring(matchingDir.length),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else if (ignoreByDir[root].ignores(relPath)) {
|
||||
return;
|
||||
}
|
||||
if (!ignoreByDir[root].ignores(relPath)) {
|
||||
filteredFileList.push(fileInfo);
|
||||
|
||||
// At this point we can do a final stat of the file, and check if it should be included
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
// filePath may be a special file that we should ignore, such as a socket
|
||||
if (stats.isFile()) {
|
||||
filteredFileList.push({
|
||||
filePath,
|
||||
relPath,
|
||||
stats,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return { filteredFileList, dockerignoreFiles };
|
||||
}
|
||||
|
||||
|
@ -317,12 +317,18 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
||||
Object.keys(build.opts.registrySecrets).length > 0
|
||||
? preFinalizeCallback
|
||||
: undefined;
|
||||
return await tarDirectory(path.resolve(build.source), {
|
||||
globalLogger.logDebug('Tarring all non-ignored files...');
|
||||
const tarStartTime = Date.now();
|
||||
const tarStream = await tarDirectory(path.resolve(build.source), {
|
||||
preFinalizeCallback: preFinalizeCb,
|
||||
convertEol: build.opts.convertEol,
|
||||
multiDockerignore: build.opts.multiDockerignore,
|
||||
nogitignore: build.nogitignore, // v13: delete this line
|
||||
});
|
||||
globalLogger.logDebug(
|
||||
`Tarring complete in ${Date.now() - tarStartTime} ms`,
|
||||
);
|
||||
return tarStream;
|
||||
} finally {
|
||||
tarSpinner.stop();
|
||||
}
|
||||
|
@ -1,3 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018-2021 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.
|
||||
*/
|
||||
|
||||
const windowSize: { width?: number; height?: number } = {};
|
||||
|
||||
const updateWindowSize = () => {
|
||||
@ -29,13 +46,6 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
|
||||
|
||||
const cursorDown = (rows: number = 0) => stream.write(`\u001B[${rows}B`);
|
||||
|
||||
const cursorHidden = () => {
|
||||
const Bluebird = require('bluebird') as typeof import('bluebird');
|
||||
return Bluebird.try(hideCursor).disposer(() => {
|
||||
showCursor();
|
||||
});
|
||||
};
|
||||
|
||||
const write = (str: string) => stream.write(str);
|
||||
|
||||
const writeLine = (str: string) => stream.write(`${str}\n`);
|
||||
@ -54,7 +64,6 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
|
||||
currentWindowSize,
|
||||
hideCursor,
|
||||
showCursor,
|
||||
cursorHidden,
|
||||
cursorUp,
|
||||
cursorDown,
|
||||
write,
|
||||
|
561
npm-shrinkwrap.json
generated
561
npm-shrinkwrap.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "12.50.1",
|
||||
"version": "12.55.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -915,12 +915,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.5.tgz",
|
||||
"integrity": "sha512-y3rlP+/G25OIX3mYKKIOlQRcqj7YgrvHxOLbVmyLJ9bPmi5ttvUmpydVjcFjZphOktWuA7ovbx91ECloWTfjIA==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz",
|
||||
"integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.5",
|
||||
"@babel/types": "^7.16.0",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
},
|
||||
@ -934,41 +934,41 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz",
|
||||
"integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz",
|
||||
"integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.14.5",
|
||||
"@babel/template": "^7.14.5",
|
||||
"@babel/types": "^7.14.5"
|
||||
"@babel/helper-get-function-arity": "^7.16.0",
|
||||
"@babel/template": "^7.16.0",
|
||||
"@babel/types": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz",
|
||||
"integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz",
|
||||
"integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.5"
|
||||
"@babel/types": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz",
|
||||
"integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz",
|
||||
"integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.5"
|
||||
"@babel/types": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz",
|
||||
"integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz",
|
||||
"integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.14.5"
|
||||
"@babel/types": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
@ -1035,46 +1035,46 @@
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz",
|
||||
"integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz",
|
||||
"integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
"@babel/parser": "^7.14.5",
|
||||
"@babel/types": "^7.14.5"
|
||||
"@babel/code-frame": "^7.16.0",
|
||||
"@babel/parser": "^7.16.0",
|
||||
"@babel/types": "^7.16.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
|
||||
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.14.5"
|
||||
"@babel/highlight": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
|
||||
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
|
||||
"version": "7.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
|
||||
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
|
||||
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.5",
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
@ -1115,52 +1115,52 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.7.tgz",
|
||||
"integrity": "sha512-9vDr5NzHu27wgwejuKL7kIOm4bwEtaPQ4Z6cpCmjSuaRqpH/7xc4qcGEscwMqlkwgcXl6MvqoAjZkQ24uSdIZQ==",
|
||||
"version": "7.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz",
|
||||
"integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.14.5",
|
||||
"@babel/generator": "^7.14.5",
|
||||
"@babel/helper-function-name": "^7.14.5",
|
||||
"@babel/helper-hoist-variables": "^7.14.5",
|
||||
"@babel/helper-split-export-declaration": "^7.14.5",
|
||||
"@babel/parser": "^7.14.7",
|
||||
"@babel/types": "^7.14.5",
|
||||
"@babel/code-frame": "^7.16.0",
|
||||
"@babel/generator": "^7.16.0",
|
||||
"@babel/helper-function-name": "^7.16.0",
|
||||
"@babel/helper-hoist-variables": "^7.16.0",
|
||||
"@babel/helper-split-export-declaration": "^7.16.0",
|
||||
"@babel/parser": "^7.16.3",
|
||||
"@babel/types": "^7.16.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
|
||||
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.14.5"
|
||||
"@babel/highlight": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
|
||||
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
|
||||
"version": "7.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
|
||||
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
|
||||
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.5",
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
@ -1201,19 +1201,19 @@
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
|
||||
"integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==",
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
|
||||
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.5",
|
||||
"@babel/helper-validator-identifier": "^7.15.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
|
||||
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
|
||||
"version": "7.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -1246,9 +1246,9 @@
|
||||
"integrity": "sha512-o3/sRDyrXC75BUUziMAs+W5C02aVST0YqY5Ny31Ot3a+7CzK2XDRinMGywvK93tm2QVdL83HGkN483S62Xo9Dw=="
|
||||
},
|
||||
"@balena/lint": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.1.1.tgz",
|
||||
"integrity": "sha512-i7FbC+3RNyOO0iNELmwXEfLfumJ4p/c9a/GP54U7ARMgzYzkFhVguwDqMjG8PE46DyjJyb5AAsekBkgbFWNMDA==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@balena/lint/-/lint-6.2.0.tgz",
|
||||
"integrity": "sha512-/17R2TAYIrjryiNUBVaDrFyeU5OQwTNybTo/gllJpR8DLa5+vnYGelCm+igHMU10u4wMgJVLz8SBNrEbaDW5iw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "^7.1.3",
|
||||
@ -1265,9 +1265,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.20.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.16.tgz",
|
||||
"integrity": "sha512-6CLxw83vQf6DKqXxMPwl8qpF8I7THFZuIwLt4TnNsumxkp1VsRZWT8txQxncT/Rl2UojTsFzWgDG4FRMwafrlA==",
|
||||
"version": "12.20.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.37.tgz",
|
||||
"integrity": "sha512-i1KGxqcvJaLQali+WuypQnXwcplhtNtjs66eNsZpp2P2FL/trJJxx/VWsM0YCL2iMoIJrbXje48lvIQAQ4p2ZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@ -2473,12 +2473,6 @@
|
||||
"integrity": "sha512-AUxAGNIPr7wQmzdFMNhHy/RkR5kk8gSzAZIuCYY//6ZYJKHvnjezmoEYP34coPleUPnqrUWt03cCq7NzNaA/qg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.48",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
|
||||
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/event-stream": {
|
||||
"version": "3.3.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/event-stream/-/event-stream-3.3.34.tgz",
|
||||
@ -2754,9 +2748,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/prettier": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz",
|
||||
"integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==",
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.2.tgz",
|
||||
"integrity": "sha512-ekoj4qOQYp7CvjX8ZDBgN86w3MqQhLE1hczEJbEIjgFEumDy+na/4AJAbLXfgEWFNB2pKadM5rPFtuSGMWK7xA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prettyjson": {
|
||||
@ -2954,92 +2948,96 @@
|
||||
"dev": true
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.5.tgz",
|
||||
"integrity": "sha512-TXBhFinoBaXKDykJzY26UEuQU1K07FOp/0Ie+OXySqqk0bS0ZO7Xvl7UmiTUPYcLrWbxWBR7Bs/y55AI0MNc2Q==",
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.23.tgz",
|
||||
"integrity": "sha512-4ZhiI/orx+7EJ1B+0zjgvXMV2uRN+XBfG06UN2sJfND9rH5gtEQT3QmO4erum1o6Irl7y754W8/KSaDJh4EUQg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.12.0",
|
||||
"@babel/types": "^7.12.0",
|
||||
"@vue/shared": "3.1.5",
|
||||
"estree-walker": "^2.0.1",
|
||||
"@babel/parser": "^7.15.0",
|
||||
"@vue/shared": "3.2.23",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/compiler-dom": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.5.tgz",
|
||||
"integrity": "sha512-ZsL3jqJ52OjGU/YiT/9XiuZAmWClKInZM2aFJh9gnsAPqOrj2JIELMbkIFpVKR/CrVO/f2VxfPiiQdQTr65jcQ==",
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.23.tgz",
|
||||
"integrity": "sha512-X2Nw8QFc5lgoK3kio5ktM95nqmLUH+q+N/PbV4kCHzF1avqv/EGLnAhaaF0Iu4bewNvHJAAhhwPZFeoV/22nbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.1.5",
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/compiler-core": "3.2.23",
|
||||
"@vue/shared": "3.2.23"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.5.tgz",
|
||||
"integrity": "sha512-mtMY6xMvZeSRx9MTa1+NgJWndrkzVTdJ1pQAmAKQuxyb5LsHVvrgP7kcQFvxPHVpLVTORbTJWHaiqoKrJvi1iA==",
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.23.tgz",
|
||||
"integrity": "sha512-Aw+pb50Q5zTjyvWod8mNKmYZDRGHJBptmNNWE+84ZxrzEztPgMz8cNYIzWGbwcFVkmJlhvioAMvKnB+LM/sjSA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.13.9",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@types/estree": "^0.0.48",
|
||||
"@vue/compiler-core": "3.1.5",
|
||||
"@vue/compiler-dom": "3.1.5",
|
||||
"@vue/compiler-ssr": "3.1.5",
|
||||
"@vue/shared": "3.1.5",
|
||||
"consolidate": "^0.16.0",
|
||||
"estree-walker": "^2.0.1",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "^5.1.1",
|
||||
"@babel/parser": "^7.15.0",
|
||||
"@vue/compiler-core": "3.2.23",
|
||||
"@vue/compiler-dom": "3.2.23",
|
||||
"@vue/compiler-ssr": "3.2.23",
|
||||
"@vue/ref-transform": "3.2.23",
|
||||
"@vue/shared": "3.2.23",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"postcss": "^8.1.10",
|
||||
"postcss-modules": "^4.0.0",
|
||||
"postcss-selector-parser": "^6.0.4",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/compiler-ssr": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.5.tgz",
|
||||
"integrity": "sha512-CU5N7Di/a4lyJ18LGJxJYZS2a8PlLdWpWHX9p/XcsjT2TngMpj3QvHVRkuik2u8QrIDZ8OpYmTyj1WDNsOV+Dg==",
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.23.tgz",
|
||||
"integrity": "sha512-Bqzn4jFyXPK1Ehqiq7e/czS8n62gtYF1Zfeu0DrR5uv+SBllh7LIvZjZU6+c8qbocAd3/T3I3gn2cZGmnDb6zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue/compiler-dom": "3.1.5",
|
||||
"@vue/shared": "3.1.5"
|
||||
"@vue/compiler-dom": "3.2.23",
|
||||
"@vue/shared": "3.2.23"
|
||||
}
|
||||
},
|
||||
"@vue/ref-transform": {
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.23.tgz",
|
||||
"integrity": "sha512-gW0GD2PSAs/th7mC7tPB/UwpIQxclbApVtsDtscDmOJXb2+cdu60ny+SuHNgfrlUT/JqWKQHq7jFKO4woxLNaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.15.0",
|
||||
"@vue/compiler-core": "3.2.23",
|
||||
"@vue/shared": "3.2.23",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.25.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==",
|
||||
"version": "3.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.23.tgz",
|
||||
"integrity": "sha512-U+/Jefa0QfXUF2qVy9Dqlrb6HKJSr9/wJcM66wXmWcTOoqg7hOWzF4qruDle51pyF4x3wMn6TSH54UdjKjCKMA==",
|
||||
"dev": true
|
||||
},
|
||||
"@yarnpkg/lockfile": {
|
||||
@ -3634,13 +3632,13 @@
|
||||
}
|
||||
},
|
||||
"balena-config-json": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/balena-config-json/-/balena-config-json-4.1.1.tgz",
|
||||
"integrity": "sha512-zpYt3SbXxe1OgmLcA8RVecrBgVH1sThYVAd9irfp/NOjKgk0bRiVjx2rG7yLROB7gcLxYDeivP4pLTTDtpylHQ==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-config-json/-/balena-config-json-4.2.0.tgz",
|
||||
"integrity": "sha512-k0/2y1FmZni2EAVYHfWFme/F1xchAcvHweDA9QCEHm83LU0+j6v/Hk9RZ1IhipRty2jDSEw1BorYVrYmx9+Psg==",
|
||||
"requires": {
|
||||
"balena-image-fs": "^7.0.4",
|
||||
"balena-sdk": "^15.2.1",
|
||||
"lodash": "^4.17.19"
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"file-disk": "^8.0.1",
|
||||
"partitioninfo": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"balena-device-init": {
|
||||
@ -3785,9 +3783,9 @@
|
||||
}
|
||||
},
|
||||
"balena-image-manager": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-7.0.3.tgz",
|
||||
"integrity": "sha512-KLb/5JksYxCR7JClq+MAuCMYPNMAXB9MgqubUvzj4mI/Yec3XD4xZ2BMITwINrl4sMKui/G7t/R5tBNFZsFe9w==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-7.1.1.tgz",
|
||||
"integrity": "sha512-ny0BrjdK23RFKG0VDk6PceYMtiekyqJEoh91Q/JxjWwayoV+CUajQJYN9YE95i2Qp/iqI97Hlm1V5N4E1YHLEg==",
|
||||
"requires": {
|
||||
"balena-sdk": "^15.2.1",
|
||||
"mime": "^2.4.6",
|
||||
@ -3814,9 +3812,9 @@
|
||||
}
|
||||
},
|
||||
"balena-preload": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-10.5.0.tgz",
|
||||
"integrity": "sha512-tgnTyOSOLB3HxIqlR1NFrTsy1eiiew5Vzmplb82/eZc/vJTrOqal2tFNn6aFay6UQ8+OASUJwANC99zBu1e8mQ==",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-11.0.0.tgz",
|
||||
"integrity": "sha512-2LuPTw6LoVxuasGhn+cLyA5n7kjvwBtQmBsg08KNhcbEpWLkzrV5jhpNxlMPUuoC2xcMv5Rcg6FWbY87QV5zYQ==",
|
||||
"requires": {
|
||||
"archiver": "^3.1.1",
|
||||
"balena-sdk": "^15.44.0",
|
||||
@ -3828,6 +3826,7 @@
|
||||
"get-port": "^3.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"request": "^2.88.2",
|
||||
"request-promise": "^4.2.6",
|
||||
"resin-cli-visuals": "^1.8.0",
|
||||
"tar-fs": "^2.1.1",
|
||||
@ -3885,11 +3884,6 @@
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
@ -3900,14 +3894,6 @@
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
@ -3933,11 +3919,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
},
|
||||
"zip-stream": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz",
|
||||
@ -4243,15 +4224,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.48",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
|
||||
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
|
||||
"dev": true
|
||||
"version": "1.6.50",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.50.tgz",
|
||||
"integrity": "sha512-+O2uoQWFRo8ysZNo/rjtri2jIwjr3XfeAgRjAUADRqGG+ZITvyn8J1kvXLTaKVr3hhGXk+f23tKfdzmklVM9vQ=="
|
||||
},
|
||||
"binary": {
|
||||
"version": "0.3.0",
|
||||
@ -5208,12 +5183,6 @@
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
|
||||
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||
"dev": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
@ -5428,15 +5397,6 @@
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
|
||||
},
|
||||
"consolidate": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz",
|
||||
"integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.7.2"
|
||||
}
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
@ -5785,12 +5745,6 @@
|
||||
"integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"csso": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
|
||||
@ -6042,9 +5996,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/parser": {
|
||||
"version": "7.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
|
||||
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
|
||||
"version": "7.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
|
||||
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
|
||||
"dev": true
|
||||
},
|
||||
"arrify": {
|
||||
@ -6054,9 +6008,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
|
||||
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz",
|
||||
"integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
@ -6069,9 +6023,9 @@
|
||||
}
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz",
|
||||
"integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==",
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
|
||||
"integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
@ -6777,12 +6731,6 @@
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"emojis-list": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
|
||||
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
@ -8366,15 +8314,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"generic-names": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz",
|
||||
"integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@ -9393,12 +9332,6 @@
|
||||
"resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz",
|
||||
"integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw=="
|
||||
},
|
||||
"hash-sum": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
|
||||
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
|
||||
"dev": true
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
@ -9743,18 +9676,6 @@
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"icss-replace-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
|
||||
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
|
||||
"dev": true
|
||||
},
|
||||
"icss-utils": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
|
||||
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
|
||||
"dev": true
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
@ -10946,28 +10867,6 @@
|
||||
"strip-bom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
|
||||
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
@ -11030,12 +10929,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
|
||||
"integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
@ -11870,15 +11763,6 @@
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"merge-source-map": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
|
||||
"integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "^0.6.1"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
@ -12368,7 +12252,7 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"multicast-dns": {
|
||||
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#db98d68b79bbefc936b6799de9de1038ba49f85d",
|
||||
"from": "git+https://github.com/resin-io-modules/multicast-dns.git#listen-on-all-interfaces",
|
||||
"requires": {
|
||||
"dns-packet": "^1.0.1",
|
||||
@ -13868,6 +13752,12 @@
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
@ -14089,91 +13979,24 @@
|
||||
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
|
||||
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.1.tgz",
|
||||
"integrity": "sha512-WqLs/TTzXdG+/A4ZOOK9WDZiikrRaiA+eoEb/jz2DT9KUhMNHgP7yKPO8vwi62ZCsb703Gwb7BMZwDzI54Y2Ag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colorette": "^1.2.2",
|
||||
"nanoid": "^3.1.23",
|
||||
"source-map-js": "^0.6.2"
|
||||
"nanoid": "^3.1.30",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanoid": {
|
||||
"version": "3.1.23",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
|
||||
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
|
||||
"version": "3.1.30",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
|
||||
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"postcss-modules": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz",
|
||||
"integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"generic-names": "^2.0.1",
|
||||
"icss-replace-symbols": "^1.1.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"postcss-modules-extract-imports": "^3.0.0",
|
||||
"postcss-modules-local-by-default": "^4.0.0",
|
||||
"postcss-modules-scope": "^3.0.0",
|
||||
"postcss-modules-values": "^4.0.0",
|
||||
"string-hash": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"postcss-modules-extract-imports": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
|
||||
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss-modules-local-by-default": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
|
||||
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"postcss-modules-scope": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
|
||||
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
}
|
||||
},
|
||||
"postcss-modules-values": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
|
||||
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"icss-utils": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
"version": "6.0.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
|
||||
"integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
|
||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
|
||||
"dev": true
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz",
|
||||
@ -14208,9 +14031,9 @@
|
||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
|
||||
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz",
|
||||
"integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-bytes": {
|
||||
@ -16156,9 +15979,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.35.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.35.2.tgz",
|
||||
"integrity": "sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==",
|
||||
"version": "1.43.5",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.43.5.tgz",
|
||||
"integrity": "sha512-WuNm+eAryMgQluL7Mbq9M4EruyGGMyal7Lu58FfnRMVWxgUzIvI7aSn60iNt3kn5yZBMR7G84fAGDcwqOF5JOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=3.0.0 <4.0.0"
|
||||
@ -16738,9 +16561,9 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
|
||||
"integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
|
||||
"integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-resolve": {
|
||||
@ -17048,12 +16871,6 @@
|
||||
"editor": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-hash": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
|
||||
"integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=",
|
||||
"dev": true
|
||||
},
|
||||
"string-template": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
|
||||
|
21
package.json
21
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "12.50.1",
|
||||
"version": "12.55.5",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -53,7 +53,7 @@
|
||||
"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-dev manifest",
|
||||
"build:test": "tsc -P ./tsconfig.dev.json --noEmit && tsc -P ./tsconfig.js.json --noEmit",
|
||||
"build:doc": "ts-node --transpile-only automation/capitanodoc/index.ts > doc/cli.markdown",
|
||||
"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",
|
||||
"build:installer": "ts-node --transpile-only automation/run.ts build:installer",
|
||||
@ -71,7 +71,9 @@
|
||||
"catch-uncommitted": "ts-node --transpile-only automation/run.ts catch-uncommitted",
|
||||
"ci": "npm run test && npm run catch-uncommitted",
|
||||
"watch": "gulp watch",
|
||||
"lint": "balena-lint -e ts -e js --fix automation/ completion/ lib/ typings/ tests/ bin/balena bin/balena-dev gulpfile.js .mocharc.js .mocharc-standalone.js",
|
||||
"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",
|
||||
"update": "ts-node --transpile-only ./automation/update-module.ts",
|
||||
"prepare": "echo {} > bin/.fast-boot.json",
|
||||
"prepublishOnly": "npm run build"
|
||||
@ -113,7 +115,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^6.1.1",
|
||||
"@balena/lint": "^6.2.0",
|
||||
"@oclif/config": "^1.17.0",
|
||||
"@oclif/dev-cli": "^1.26.0",
|
||||
"@oclif/parser": "^3.8.5",
|
||||
@ -201,12 +203,12 @@
|
||||
"@types/update-notifier": "^4.1.1",
|
||||
"@zeit/dockerignore": "0.0.3",
|
||||
"JSONStream": "^1.0.3",
|
||||
"balena-config-json": "^4.1.1",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^6.0.0",
|
||||
"balena-errors": "^4.7.1",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-image-manager": "^7.0.3",
|
||||
"balena-preload": "^10.5.0",
|
||||
"balena-image-manager": "^7.1.1",
|
||||
"balena-preload": "^11.0.0",
|
||||
"balena-release": "^3.2.0",
|
||||
"balena-sdk": "^15.51.1",
|
||||
"balena-semver": "^2.3.0",
|
||||
@ -218,6 +220,7 @@
|
||||
"chalk": "^3.0.0",
|
||||
"chokidar": "^3.4.3",
|
||||
"cli-truncate": "^2.1.0",
|
||||
"cli-ux": "^5.5.1",
|
||||
"color-hash": "^1.0.3",
|
||||
"columnify": "^1.5.2",
|
||||
"common-tags": "^1.7.2",
|
||||
@ -232,7 +235,6 @@
|
||||
"express": "^4.13.3",
|
||||
"fast-boot2": "^1.1.0",
|
||||
"fast-levenshtein": "^3.0.0",
|
||||
"file-disk": "^8.0.1",
|
||||
"filenamify": "^4.3.0",
|
||||
"get-stdin": "^8.0.0",
|
||||
"glob": "^7.1.7",
|
||||
@ -257,7 +259,6 @@
|
||||
"node-unzip-2": "^0.2.8",
|
||||
"oclif": "^1.18.1",
|
||||
"open": "^7.1.0",
|
||||
"partitioninfo": "^6.0.2",
|
||||
"patch-package": "^6.4.7",
|
||||
"prettyjson": "^1.1.3",
|
||||
"progress-stream": "^2.0.0",
|
||||
@ -289,6 +290,6 @@
|
||||
"windosu": "^0.3.0"
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2021-09-30T00:16:32.621Z"
|
||||
"publishedAt": "2021-12-13T23:29:59.373Z"
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
ExpectedTarStreamFilesByService,
|
||||
getDockerignoreWarn1,
|
||||
getDockerignoreWarn2,
|
||||
getDockerignoreWarn3,
|
||||
} from '../projects';
|
||||
|
||||
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
|
||||
@ -184,6 +185,10 @@ describe('balena build', function () {
|
||||
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
|
||||
`[Info] Creating default composition with source: "${projectPath}"`,
|
||||
'[Build] main Step 1/4 : FROM busybox',
|
||||
...getDockerignoreWarn1(
|
||||
[path.join(projectPath, 'src', '.dockerignore')],
|
||||
'build',
|
||||
),
|
||||
];
|
||||
if (isWindows) {
|
||||
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
|
||||
@ -383,7 +388,6 @@ describe('balena build', function () {
|
||||
'file1.sh': { fileSize: 12, type: 'file' },
|
||||
},
|
||||
service2: {
|
||||
'.dockerignore': { fileSize: 12, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 13, type: 'file' },
|
||||
'file2-crlf.sh': {
|
||||
fileSize: isWindows ? 12 : 14,
|
||||
@ -513,13 +517,7 @@ describe('balena build', function () {
|
||||
'[Build] service1 Step 1/4 : FROM busybox',
|
||||
'[Build] service2 Step 1/4 : FROM busybox',
|
||||
],
|
||||
...[
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
|
||||
'[Info] found at the project source (root) directory. Note that this file will not',
|
||||
'[Info] be used to filter service subdirectories. See "balena help build".',
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
],
|
||||
...getDockerignoreWarn3('build'),
|
||||
];
|
||||
if (isWindows) {
|
||||
expectedResponseLines.push(
|
||||
@ -602,13 +600,7 @@ describe('balena build', function () {
|
||||
'[Build] service1 Step 1/4 : FROM busybox',
|
||||
'[Build] service2 Step 1/4 : FROM busybox',
|
||||
],
|
||||
...[
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
|
||||
'[Info] found at the project source (root) directory. Note that this file will not',
|
||||
'[Info] be used to filter service subdirectories. See "balena help build".',
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
],
|
||||
...getDockerignoreWarn3('build'),
|
||||
];
|
||||
if (isWindows) {
|
||||
expectedResponseLines.push(
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
ExpectedTarStreamFiles,
|
||||
ExpectedTarStreamFilesByService,
|
||||
getDockerignoreWarn1,
|
||||
getDockerignoreWarn3,
|
||||
} from '../projects';
|
||||
|
||||
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
|
||||
@ -396,13 +397,7 @@ describe('balena deploy', function () {
|
||||
'[Build] service1 Step 1/4 : FROM busybox',
|
||||
'[Build] service2 Step 1/4 : FROM busybox',
|
||||
],
|
||||
...[
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
|
||||
'[Info] found at the project source (root) directory. Note that this file will not',
|
||||
'[Info] be used to filter service subdirectories. See "balena help deploy".',
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
],
|
||||
...getDockerignoreWarn3('deploy'),
|
||||
];
|
||||
if (isWindows) {
|
||||
expectedResponseLines.push(
|
||||
|
@ -49,7 +49,6 @@ if (process.platform !== 'win32') {
|
||||
api.expectGetApplication();
|
||||
api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
api.expectApplicationProvisioning();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpPath}`,
|
||||
@ -59,6 +58,7 @@ if (process.platform !== 'win32') {
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
'--initial-device-name testDeviceName',
|
||||
'--provisioning-key-name testKey',
|
||||
];
|
||||
|
||||
const { err } = await runCommand(command.join(' '));
|
||||
|
@ -26,9 +26,11 @@ import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build';
|
||||
import { cleanOutput, runCommand } from '../helpers';
|
||||
import {
|
||||
addRegSecretsEntries,
|
||||
exists,
|
||||
ExpectedTarStreamFiles,
|
||||
getDockerignoreWarn1,
|
||||
getDockerignoreWarn2,
|
||||
getDockerignoreWarn3,
|
||||
setupDockerignoreTestData,
|
||||
} from '../projects';
|
||||
|
||||
@ -36,6 +38,7 @@ const repoPath = path.normalize(path.join(__dirname, '..', '..'));
|
||||
const projectsPath = path.join(repoPath, 'tests', 'test-data', 'projects');
|
||||
|
||||
const itNoV13 = isV13() ? it.skip : it;
|
||||
const itNoWin = process.platform === 'win32' ? it.skip : it;
|
||||
|
||||
const commonResponseLines = {
|
||||
'build-POST-v3.json': [
|
||||
@ -451,12 +454,10 @@ describe('balena push', function () {
|
||||
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
'.balena/balena.yml': { fileSize: 197, type: 'file' },
|
||||
'.dockerignore': { fileSize: 22, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 332, type: 'file' },
|
||||
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
|
||||
'service1/file1.sh': { fileSize: 12, type: 'file' },
|
||||
'service2/Dockerfile-alt': { fileSize: 13, type: 'file' },
|
||||
'service2/.dockerignore': { fileSize: 12, type: 'file' },
|
||||
'service2/file2-crlf.sh': {
|
||||
fileSize: isWindows ? 12 : 14,
|
||||
testStream: isWindows ? expectStreamNoCRLF : undefined,
|
||||
@ -503,7 +504,6 @@ describe('balena push', function () {
|
||||
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
'.balena/balena.yml': { fileSize: 197, type: 'file' },
|
||||
'.dockerignore': { fileSize: 22, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 332, type: 'file' },
|
||||
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
|
||||
'service1/file1.sh': { fileSize: 12, type: 'file' },
|
||||
@ -524,13 +524,7 @@ describe('balena push', function () {
|
||||
);
|
||||
const expectedResponseLines: string[] = [
|
||||
...commonResponseLines[responseFilename],
|
||||
...[
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
|
||||
'[Info] found at the project source (root) directory. Note that this file will not',
|
||||
'[Info] be used to filter service subdirectories. See "balena help push".',
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
],
|
||||
...getDockerignoreWarn3('push'),
|
||||
];
|
||||
if (isWindows) {
|
||||
expectedResponseLines.push(
|
||||
@ -553,6 +547,66 @@ describe('balena push', function () {
|
||||
responseCode: 200,
|
||||
});
|
||||
});
|
||||
|
||||
// Skip on Windows because this test uses Unix domain sockets
|
||||
itNoWin('should create the expected tar stream (socket file)', async () => {
|
||||
// This test creates project files dynamically in a temp dir, where
|
||||
// a Unix domain socket file is created and listened on. A specific
|
||||
// reason use use a temp dir is that Unix domain socket paths are
|
||||
// limited in length to just over 100 characters, while the project
|
||||
// paths in the test-data folder easily exceed that limit.
|
||||
const tmp = await import('tmp');
|
||||
tmp.setGracefulCleanup();
|
||||
const projectPath = await new Promise<string>((resolve, reject) => {
|
||||
const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true };
|
||||
tmp.dir(opts, (e, p) => (e ? reject(e) : resolve(p)));
|
||||
});
|
||||
console.error(`[debug] Temp project dir: ${projectPath}`);
|
||||
|
||||
// Create a Unix Domain Socket file that should not be included in the tar stream
|
||||
const net = await import('net');
|
||||
const server = net.createServer();
|
||||
const socketPath = path.join(projectPath, 'socket');
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.on('error', reject);
|
||||
try {
|
||||
server.listen(socketPath, resolve);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
console.error(`[debug] Checking existence of socket at '${socketPath}'`);
|
||||
expect(await exists(socketPath), 'Socket existence').to.be.true;
|
||||
|
||||
await fs.writeFile(path.join(projectPath, 'Dockerfile'), 'FROM busybox\n');
|
||||
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
Dockerfile: { fileSize: 13, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST-v3.json';
|
||||
const responseBody = await fs.readFile(
|
||||
path.join(builderResponsePath, responseFilename),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
await testPushBuildStream({
|
||||
builderMock: builder,
|
||||
commandLine: `push testApp -s ${projectPath}`,
|
||||
expectedFiles,
|
||||
expectedQueryParams: commonQueryParams,
|
||||
expectedResponseLines: commonResponseLines[responseFilename],
|
||||
projectPath,
|
||||
responseBody,
|
||||
responseCode: 200,
|
||||
});
|
||||
|
||||
// Terminate Unix Domain Socket server
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((e) => (e ? reject(e) : resolve()));
|
||||
});
|
||||
|
||||
expect(await exists(socketPath), 'Socket existence').to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('balena push: project validation', function () {
|
||||
|
255
tests/framework/output.spec.ts
Normal file
255
tests/framework/output.spec.ts
Normal file
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020-2021 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.
|
||||
*/
|
||||
|
||||
/* tslint:disable: prefer-const no-empty */
|
||||
|
||||
import rewire = require('rewire');
|
||||
import sinon = require('sinon');
|
||||
import { expect } from 'chai';
|
||||
|
||||
const dataItem = {
|
||||
name: 'item1',
|
||||
id: 1,
|
||||
thing_color: 'blue',
|
||||
thing_shape: 'square',
|
||||
};
|
||||
|
||||
const dataSet = [
|
||||
{
|
||||
name: 'item1',
|
||||
id: 1,
|
||||
thing_color: 'red',
|
||||
thing_shape: 'square',
|
||||
},
|
||||
{
|
||||
name: 'item2',
|
||||
id: 2,
|
||||
thing_color: 'blue',
|
||||
thing_shape: 'round',
|
||||
},
|
||||
];
|
||||
|
||||
describe('outputData', function () {
|
||||
let outputData: any;
|
||||
let outputDataSetSpy: any;
|
||||
let outputDataItemSpy: any;
|
||||
|
||||
this.beforeEach(() => {
|
||||
const output = rewire('../../build/framework/output');
|
||||
|
||||
outputDataSetSpy = sinon.spy();
|
||||
outputDataItemSpy = sinon.spy();
|
||||
|
||||
output.__set__('outputDataSet', outputDataSetSpy);
|
||||
output.__set__('outputDataItem', outputDataItemSpy);
|
||||
|
||||
outputData = output.__get__('outputData');
|
||||
});
|
||||
|
||||
it('should call outputDataSet function when data param is an array', async () => {
|
||||
await outputData(dataSet);
|
||||
expect(outputDataSetSpy.called).to.be.true;
|
||||
expect(outputDataItemSpy.called).to.be.false;
|
||||
});
|
||||
|
||||
it('should call outputDataItem function when data param is an object', async () => {
|
||||
await outputData(dataItem);
|
||||
expect(outputDataSetSpy.called).to.be.false;
|
||||
expect(outputDataItemSpy.called).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('outputDataSet', function () {
|
||||
let outputDataSet: any;
|
||||
let printLineSpy: any;
|
||||
|
||||
this.beforeEach(() => {
|
||||
const output = rewire('../../build/framework/output');
|
||||
printLineSpy = sinon.spy();
|
||||
output.__set__('printLine', printLineSpy);
|
||||
outputDataSet = output.__get__('outputDataSet');
|
||||
});
|
||||
|
||||
it('should only output fields specified in `fields` param, in that order', async () => {
|
||||
const fields = ['id', 'name', 'thing_color'];
|
||||
const options = {};
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
// check correct number of rows (2 data, 2 header)
|
||||
expect(printLineSpy.callCount).to.equal(4);
|
||||
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
|
||||
// check we have fields we specified
|
||||
fields.forEach((f) => {
|
||||
expect(headerLine).to.include(f.replace(/_/g, ' '));
|
||||
});
|
||||
// check we don't have fields we didn't specify
|
||||
expect(headerLine).to.not.include('thing_shape');
|
||||
// check order
|
||||
// split header using the `name` column as delimiter
|
||||
const splitHeader = headerLine.split('name');
|
||||
expect(splitHeader[0]).to.include('id');
|
||||
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 = {
|
||||
// test all formats
|
||||
fields: 'Name,thing_color,Thing shape',
|
||||
};
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
const headerLine = printLineSpy.firstCall.firstArg.toLowerCase();
|
||||
// check we have fields we specified
|
||||
expect(headerLine).to.include('name');
|
||||
expect(headerLine).to.include('thing color');
|
||||
expect(headerLine).to.include('thing shape');
|
||||
// check we don't have fields we didn't specify
|
||||
expect(headerLine).to.not.include('id');
|
||||
});
|
||||
|
||||
it('should output records in order specified by `options.sort` if present', async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
sort: 'thing shape',
|
||||
'no-header': true,
|
||||
};
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
// blue should come before red
|
||||
expect(printLineSpy.getCall(0).firstArg).to.include('blue');
|
||||
expect(printLineSpy.getCall(1).firstArg).to.include('red');
|
||||
});
|
||||
|
||||
it('should only output records that match filter specified by `options.filter` if present', async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
filter: 'thing color=red',
|
||||
'no-header': true,
|
||||
};
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
// check correct number of rows (1 matched data, no-header)
|
||||
expect(printLineSpy.callCount).to.equal(1);
|
||||
expect(printLineSpy.getCall(0).firstArg).to.include('red');
|
||||
});
|
||||
|
||||
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
|
||||
// (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.
|
||||
const clonedDataSet = JSON.parse(JSON.stringify(dataSet));
|
||||
clonedDataSet.forEach((d: any) => {
|
||||
delete d.id;
|
||||
});
|
||||
|
||||
const expectedJson = JSON.stringify(clonedDataSet, undefined, 2);
|
||||
|
||||
await outputDataSet(dataSet, fields, options);
|
||||
|
||||
expect(printLineSpy.callCount).to.equal(1);
|
||||
expect(printLineSpy.getCall(0).firstArg).to.equal(expectedJson);
|
||||
});
|
||||
});
|
||||
|
||||
describe('outputDataItem', function () {
|
||||
let outputDataItem: any;
|
||||
let printLineSpy: any;
|
||||
|
||||
this.beforeEach(() => {
|
||||
const output = rewire('../../build/framework/output');
|
||||
printLineSpy = sinon.spy();
|
||||
output.__set__('printLine', printLineSpy);
|
||||
outputDataItem = output.__get__('outputDataItem');
|
||||
});
|
||||
|
||||
it('should only output fields specified in `fields` param, in that order', async () => {
|
||||
const fields = ['id', 'name', 'thing_color'];
|
||||
const options = {};
|
||||
|
||||
await outputDataItem(dataItem, fields, options);
|
||||
|
||||
// check correct number of rows (3 fields)
|
||||
expect(printLineSpy.callCount).to.equal(3);
|
||||
// check we have fields we specified
|
||||
fields.forEach((f, index) => {
|
||||
const kvPair = printLineSpy.getCall(index).firstArg.split(':');
|
||||
expect(kvPair[0].toLowerCase()).to.include(f.replace(/_/g, ' '));
|
||||
expect(kvPair[1]).to.include((dataItem as any)[f]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only output fields specified in `options.fields` if present', async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
// test all formats
|
||||
fields: 'Name,thing_color,Thing shape',
|
||||
};
|
||||
|
||||
const expectedFields = ['name', 'thing_color', 'thing_shape'];
|
||||
|
||||
await outputDataItem(dataItem, fields, options);
|
||||
|
||||
// check correct number of rows (3 fields)
|
||||
expect(printLineSpy.callCount).to.equal(3);
|
||||
// check we have fields we specified
|
||||
expectedFields.forEach((f, index) => {
|
||||
const kvPair = printLineSpy.getCall(index).firstArg.split(':');
|
||||
expect(kvPair[0].toLowerCase()).to.include(f.replace(/_/g, ' '));
|
||||
expect(kvPair[1]).to.include((dataItem as any)[f]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should output data in json format, if `options.json` true', async () => {
|
||||
const fields = ['name', 'id', 'thing_color', 'thing_shape'];
|
||||
const options = {
|
||||
json: true,
|
||||
};
|
||||
|
||||
const expectedJson = JSON.stringify(dataItem, undefined, 2);
|
||||
|
||||
await outputDataItem(dataItem, fields, options);
|
||||
|
||||
expect(printLineSpy.callCount).to.equal(1);
|
||||
expect(printLineSpy.getCall(0).firstArg).to.equal(expectedJson);
|
||||
});
|
||||
});
|
@ -15,12 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { Headers } from 'tar-stream';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const statAsync = promisify(fs.stat);
|
||||
|
||||
export interface ExpectedTarStreamFile {
|
||||
contents?: string;
|
||||
@ -49,6 +46,15 @@ export const projectsPath = path.join(
|
||||
'projects',
|
||||
);
|
||||
|
||||
export async function exists(fPath: string) {
|
||||
try {
|
||||
await fs.stat(fPath);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupDockerignoreTestData({ cleanup = false } = {}) {
|
||||
const { copy, remove } = await import('fs-extra');
|
||||
const dockerignoreProjDir = path.join(
|
||||
@ -78,7 +84,7 @@ export async function addRegSecretsEntries(
|
||||
): Promise<string> {
|
||||
const regSecretsPath = path.join(projectsPath, 'registry-secrets.json');
|
||||
expectedFiles['.balena/registry-secrets.json'] = {
|
||||
fileSize: (await statAsync(regSecretsPath)).size,
|
||||
fileSize: (await fs.stat(regSecretsPath)).size,
|
||||
type: 'file',
|
||||
};
|
||||
return regSecretsPath;
|
||||
@ -115,3 +121,13 @@ export function getDockerignoreWarn2(paths: string[], cmd: string) {
|
||||
);
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function getDockerignoreWarn3(cmd: string) {
|
||||
return [
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
|
||||
'[Info] found at the project source (root) directory. Note that this file will not',
|
||||
`[Info] be used to filter service subdirectories. See "balena help ${cmd}".`,
|
||||
`[Info] ---------------------------------------------------------------------------`,
|
||||
];
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
service1/test-ignore*
|
||||
**/.dockerignore
|
||||
|
@ -74,7 +74,6 @@ describe('detectEncoding() function', function () {
|
||||
'node_modules/.bin/gulp',
|
||||
'node_modules/.bin/tsc',
|
||||
'node_modules/.bin/balena-lint',
|
||||
'node_modules/.bin/balena-preload',
|
||||
'node_modules/.bin/catch-uncommitted',
|
||||
];
|
||||
|
||||
|
22
typings/balena-config-json/index.d.ts
vendored
22
typings/balena-config-json/index.d.ts
vendored
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'balena-config-json' {
|
||||
export function read(image: string, type: string): Promise<any>;
|
||||
|
||||
export function write(image: string, type: string, config: any): Promise;
|
||||
}
|
2
typings/balena-image-manager/index.d.ts
vendored
2
typings/balena-image-manager/index.d.ts
vendored
@ -22,4 +22,6 @@ declare module 'balena-image-manager' {
|
||||
deviceType: string,
|
||||
versionOrRange: string,
|
||||
): Promise<NodeJS.ReadableStream & { mime: string }>;
|
||||
|
||||
export function isESR(version: string): boolean;
|
||||
}
|
||||
|
Reference in New Issue
Block a user