Compare commits

...

153 Commits

Author SHA1 Message Date
406482b4da v16.7.0 2023-07-17 19:59:52 +00:00
a381c97ca9 Merge pull request #2649 from balena-io/preload-no-pin-device-to-release
preload: Add the --no-pin-device-to-release flag to avoid interactive questions
2023-07-17 19:59:04 +00:00
8ce78ba33c Rerun npm-shrinkwrap.json deduplication 2023-07-17 11:25:02 +03:00
f53f148c89 preload: Add the --no-pin-device-to-release flag to avoid interactive questions
Change-type: minor
See: https://balena.zulipchat.com/#narrow/stream/345746-aspect.2Fproduct/topic/Level.20-.20need.20thoughts.20on.20questions.20.26.20feature.20suggestions
2023-07-17 11:19:03 +03:00
0086feb645 v16.6.6 2023-07-10 17:16:08 +00:00
4ee55b049f Merge pull request #2646 from balena-io/reduce-lodash-usage
Reduce lodash usage in common user interaction patterns
2023-07-10 17:15:14 +00:00
90c6f121cc Rerun npm-shrinkwrap.json deduplication 2023-07-10 19:36:36 +03:00
d3c27ae859 Reduce lodash usage in common user interaction patterns
Change-type: patch
2023-07-10 17:19:01 +03:00
8f39c1de6c v16.6.5 2023-07-09 21:29:55 +00:00
4df1831187 Merge pull request #2645 from balena-io/application-create-hostApp-based-supported-DTs
fleet/block/app create: Fetch the supported device types using the hostApps
2023-07-09 21:29:02 +00:00
2bce761ace Rerun npm-shrinkwrap.json deduplication 2023-07-07 20:17:53 +03:00
d78b76aceb fleet/block/app create: Fetch the supported device types using the hostApps
Change-type: patch
See: https://balena.zulipchat.com/#narrow/stream/360838-balena-io.2Fos.2Fdevices/topic/state.20field.20in.20device-type.2Ejson
See: https://balena.fibery.io/Organisation/Improvements-849#Improvements/Stop-relying-on-device-types-v1-device-type.json-for-unrelated-things-257
2023-07-07 19:57:36 +03:00
f07f6b84d4 v16.6.4 2023-07-06 13:58:27 +00:00
d297a10570 Merge pull request #2643 from balena-io/bump-balena-compose
Bump balena-compose to v2.3.0
2023-07-06 13:57:37 +00:00
9d0b82122a Bump balena-compose to v2.3.0
This allows the the CLI to use docker registry config when querying the
images manifest.

Relates-to: balena-io-modules/balena-compose#31
Change-type: patch
2023-07-05 15:46:42 -04:00
338477463a v16.6.3 2023-06-30 17:07:34 +00:00
d1275760fa Merge pull request #2641 from balena-io/drop-toolbelt
Pin dockerode and drop docker-toolbelt
2023-06-30 20:06:46 +03:00
0f4054fa4d Remove redundant dependency on docker-toolbelt
Change-type: patch
2023-06-30 19:22:07 +03:00
7545fc5d6e Pin dockerode to v3.3.3
v3.3.4 introduces a regression that is fixed by https://github.com/apocas/dockerode/pull/695 but Dockerode has not published a version that includes the fix yet. Pin the dependency to ensure we don’t ever update to a broken version.

Change-type: patch
2023-06-30 19:19:52 +03:00
a1f25809cb v16.6.2 2023-06-29 12:27:14 +00:00
e0a3c4bd95 Merge pull request #2640 from balena-io/make-apple-notarization-team-id-public
macos notarization: Expose team ID instead of keeping it in secrets
2023-06-29 12:26:27 +00:00
d843e75512 macos notarization: Expose team ID instead of keeping it in secrets
Change-type: patch
2023-06-29 07:41:07 -04:00
72c57608d5 v16.6.1 2023-06-28 12:47:56 +00:00
d9de7636db Merge pull request #2639 from balena-io/update-electron-notarize
Drop `electron-notarize` dependency in favor of `@electron/notarize`
2023-06-28 12:47:13 +00:00
10b5af6967 Drop electron-notarize dependency in favor of @electron/notarize
Change-type: patch
2023-06-28 08:01:39 -04:00
51c050c725 v16.6.0 2023-06-26 13:05:11 +00:00
eb52c47de5 Merge pull request #2631 from balena-io/add-api-key-revoke-command
api-key: Add `revoke` command which accepts a list of API key ids
2023-06-26 09:04:21 -04:00
4b1378dfbc api-key: Add revoke command which accepts a list of API key ids
Change-type: minor
2023-06-26 08:21:23 -04:00
1a77d86347 v16.5.2 2023-06-09 16:03:10 +00:00
bd5188f4b9 Merge pull request #2637 from balena-io/klutchell-patch-2
Re-enable automatic github final releases
2023-06-09 16:02:22 +00:00
034f459bfa Update npm-shrinkwrap
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-06-09 11:23:49 -04:00
bc405d997e Re-enable automatic github final releases
Change-type: patch
2023-06-09 11:05:01 -04:00
af27cf2cbe v16.5.1 2023-06-02 06:30:00 +00:00
83b9bf67c2 Merge pull request #2636 from balena-io/bump-ts
Update TypeScript to 5.1.3
2023-06-02 06:29:11 +00:00
abd73b805b Update TypeScript to 5.1.3
Change-type: patch
2023-06-01 21:58:34 +03:00
37bfd4db98 v16.5.0 2023-05-25 16:45:02 +00:00
be74143d5f Merge pull request #2633 from balena-io/add-block-create-command
Add `balena block create` command for creating Blocks
2023-05-25 16:44:19 +00:00
9975e5d9ac Add balena block create command for creating Blocks
Change-type: minor
2023-05-25 12:00:39 -04:00
1341413966 v16.4.0 2023-05-25 15:53:23 +00:00
1a5b914a6f Merge pull request #2632 from balena-io/add-app-create-command
Add `balena app create` command for creatings Apps
2023-05-25 15:52:37 +00:00
c5e8f0d6ea Add balena app create command for creatings Apps
Change-type: minor
2023-05-25 11:09:02 -04:00
3a143fe413 v16.3.0 2023-05-25 15:06:39 +00:00
3445e4a08e Merge pull request #2630 from balena-io/add-command-to-list-user-api-keys
Add `balena api-keys` command for listing user API keys
2023-05-25 15:05:38 +00:00
166130c3df Add balena api-keys command for listing user/fleet API keys
Change-type: minor
2023-05-25 09:11:36 -04:00
c3a8a905f7 v16.2.7 2023-05-24 12:04:59 +00:00
2b878e87d8 Merge pull request #2629 from balena-io/less-requests-2
Fetch the application and its devices in one request in balena devices, device init, ssh, tunnel
2023-05-24 12:04:05 +00:00
063e9d40f0 device init: Avoid extra request when not providing the --fleet option
Change-type: patch
2023-05-24 14:22:04 +03:00
2b58143164 devices: Use a single request when providing the --fleet parameter
Reduces the response time when using
--fleet from 1.5s to 1s.

Change-type: patch
2023-05-24 14:22:04 +03:00
861d4f33b7 ssh,tunnel: Fetch the fleet & devices in one request
Change-type: patch
2023-05-24 14:22:04 +03:00
81f4aae7d2 v16.2.6 2023-05-24 11:17:30 +00:00
46ab335407 Merge pull request #2626 from balena-io/less-requests
Stop fetching unnecessary fields
2023-05-24 14:16:42 +03:00
07cb0cbfcd device init: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
a2392dc580 device move: Stop fetching unnecessary device & app fields
Reduces the amount of device data retrieved
by 66%.

Change-type: patch
2023-05-24 13:37:19 +03:00
8b3235ab2b device register: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
15dac6f194 config generate: Stop fetching unnecessary app fields
Change-type: patch
2023-05-24 13:37:19 +03:00
3c93db8449 devices: Stop fetching unnecessary device fields
Change-type: patch
2023-05-24 13:37:19 +03:00
9d8df0b781 fleet rm,restart,rename,purge: Stop fetching unnecessary app fields
Halves the amout of application data retrieved.

Change-type: patch
2023-05-24 13:37:19 +03:00
bcadbdbed8 push: Stop unnecessarily fetching the application name
Change-type: patch
2023-05-24 13:37:19 +03:00
05a96fa60e ssh,tunnel: Reduce the amount of application fields fetched
Halves the amout of application data retrieved.

Change-type: patch
2023-05-24 13:37:19 +03:00
2e37536e7a orgs: Stop fetching unnecessary org fields
Halves the amount of org data retrieved to
show the list of orgs to select from.

Change-type: patch
2023-05-24 13:37:19 +03:00
025c4ef7f2 fleet create: Reduce the amount of org fields fetched
Halves the amount of org data retrieved to
show the list of orgs to select from.

Change-type: patch
2023-05-24 13:37:19 +03:00
ecbc660bf5 Fetch only the app slug when resolving an app by name
Affects many env & tags commands, as well
as support, releases & preload.

Change-type: patch
2023-05-24 13:37:19 +03:00
ba1f17d537 v16.2.5 2023-05-24 10:30:21 +00:00
3ab8f7500e Merge pull request #2628 from balena-io/73-fix-device-init-os-initialize
Fix device int & os initialize failing to initialize the drive list
2023-05-24 13:29:36 +03:00
0a25bec010 Fix device int & os initialize failing to initialize the drive list
Resolves: #2627
Change-type: patch
2023-05-24 12:47:06 +03:00
01e765e670 v16.2.4 2023-05-23 20:31:27 +00:00
61844f2386 Merge pull request #2625 from balena-io/app-select-reduce-requests
Remove extra request when filling the application selection list
2023-05-23 20:30:32 +00:00
46aa08c953 Remove extra request when filling the application selection list
Saves one request of about 450ms on the
init and move commands.

Change-type: patch
2023-05-23 22:47:10 +03:00
b6c7fb82c3 v16.2.3 2023-05-23 19:46:47 +00:00
fcda09009a Merge pull request #2624 from balena-io/improve-typings
Use stricter typings
2023-05-23 19:46:02 +00:00
1a6fe1f3de Use stricter typings
Change-type: patch
2023-05-23 18:57:54 +03:00
98e91c0607 v16.2.2 2023-05-23 14:02:36 +00:00
bed2387d83 Merge pull request #2623 from balena-io/fix-setting-service-env-vars-by-app-name
env add: Fix accepting fleet names when setting service vars
2023-05-23 14:01:21 +00:00
50e852acee env add: Fix accepting fleet names when setting service vars
Change-type: patch
2023-05-23 15:59:41 +03:00
da30623e4e v16.2.1 2023-05-23 12:28:25 +00:00
7a46b367a7 Merge pull request #2621 from balena-io/sdk-v17
Update balena-sdk to 17.0.0
2023-05-23 12:27:29 +00:00
d9651c7393 Update balena-settings-client to 5.0.2
Change-type: patch
2023-05-23 13:22:38 +03:00
e371b1e759 Update balena-preload & balena-image-manager
Update balena-image-manager from 8.0.1 to 9.0.0
Update balena-preload from 13.0.0 to 14.0.0

Change-type: patch
2023-05-23 13:22:38 +03:00
77cf4af166 Update balena-sdk to 17.0.0
Update balena-sdk from 16.45.1 to 17.0.0

Change-type: patch
2023-05-23 13:22:38 +03:00
9d197317ca v16.2.0 2023-05-19 18:11:57 +00:00
9a8b0b4a0d Merge pull request #2619 from balena-io/alexgg/sb
os configure, config generate: Add '--secureBoot' option to opt-in secure boot
2023-05-19 18:11:02 +00:00
0c62b9ef08 Deduplicate npm-shrinkwra.json 2023-05-19 20:28:12 +03:00
83a5e7392a secureboot: Retrieve the OS release & contract in one request
Change-type: patch
2023-05-19 19:22:23 +03:00
f0c8c37022 os configure, config generate: Add '--secureBoot' option to opt-in secure boot
Allow to generate a config file with `installer.secureboot` set so that
a secure boot and disk encrypted system can be installed.

Change-type: minor
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 18:10:00 +02:00
ba26d3204d package.json: Update balena-sdk to 16.44.2
Update balena-sdk from 16.40.0 to 16.44.2

Change-type: patch
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 18:10:00 +02:00
d53542975e flowzone: update custom runs to use macos-12
After the flowzone update to use zstd as compression algorithm for sources
there is an error on macos-11 as tar does not support it.

Change-type: patch
Signed-off-by: Alex Gonzalez <alexg@balena.io>
2023-05-19 11:33:31 +02:00
632296a271 v16.1.0 2023-05-16 18:26:10 +00:00
3e089fcdb2 Merge pull request #2616 from balena-io/ab77/operational
build linux/arm packages
2023-05-16 18:25:27 +00:00
d61c300750 build linux/arm packages
change-type: minor
2023-05-16 10:39:00 -07:00
a0a97c5f40 v16.0.0 2023-05-16 00:02:37 +00:00
165f3b83ca Merge pull request #2618 from balena-io/node-16
Update to Node 16
2023-05-16 00:01:48 +00:00
5bf95300ee support: Change the printed support expiry date in ISO 8601 UTC format
Change-type: major
2023-05-12 19:00:10 +03:00
adb460b270 logs: Change the timestamp format to ISO 8601 UTC
Resolves: #2608
Change-type: major
2023-05-12 19:00:10 +03:00
ca80bd52fe Pin flowzone to v4.7.1
The macos-11 runners apparently do not support zst compression format as
added in flowzone 4.7.2. While support is rolled out, we can keep
the flowzone branch to the previous working version.

Change-type: patch
2023-05-11 19:10:10 -04:00
281f8abb9a Update etcher-sdk to v8.5.3
This removes the dependency on our custom fork of [node-usb](https://github.com/balena-io-modules/node-usb)
and uses the maintained building method of the official node-usb repo

Change-type: patch
2023-05-11 18:10:52 -04:00
2cf2918d73 Update vercel/pkg to v5.8.1
This seems to be needed to build the binaries for node v16 since earlier
versions failed with

```
Error: Could not detect abi for version 16.13.0 and runtime node.  Updating "node-abi" might help solve this issue if it is a new release of node
```

Change-type: patch
2023-05-11 17:49:49 -04:00
7dfb7474f5 Update to Node 16
This also drops support for Node 14

Change-type: major
2023-05-11 17:49:37 -04:00
6ee0b48c9a v15.2.3 2023-05-03 20:03:49 +00:00
bd01fbf90c Merge pull request #2614 from balena-io/local-release-uuid
Use valid release uuid for local releases
2023-05-03 20:02:59 +00:00
cd19845b6b Use valid release uuid for local releases
On local push, the CLI uses `localrelease` as the `commit` property for
the development application. This is not a valid uuid and will not be
read properly by the supervisor, as seen in

https://github.com/balena-os/balena-supervisor/blob/master/src/compose/service.ts#L652

While this is not a problem right now, the commit is becoming the main
way to identify a service release (replacing `releaseId` and `imageId`),
and the invalid release uuid could cause update issues when pushing a
local release on when using some API endpoints.

Change-type: patch
Relates-to: balena-os/balena-supervisor#2136
2023-05-03 15:08:19 -04:00
5545883c3f v15.2.2 2023-04-28 16:16:44 +00:00
75a380b0ba Merge pull request #2615 from balena-io/remove-nvmrc
Remove nvmrc
2023-04-28 16:15:57 +00:00
35fe7c6a58 Remove nvmrc
There is not a lot of benefit to using `.nvmrc` as it still requires
`nvm use`, and not everybody uses `nvm`. The call to `npm install` will
already warn about using the wrong version.

Change-type: patch
2023-04-28 10:27:15 -04:00
69249b3139 v15.2.1 2023-04-28 09:25:50 +00:00
bf897fd56d Merge pull request #2612 from balena-io/sync-tslib
Fix tslib going out of sync causing HUP to fail
2023-04-28 09:25:01 +00:00
150c6e75f5 Fix tslib going out of sync causing HUP to fail
Change-type: patch
2023-04-27 14:07:57 +03:00
e8bc43dc64 v15.2.0 2023-04-05 13:09:24 +00:00
1213689de2 Merge pull request #2606 from balena-io/update-balena-sdk-16.40.0
Add support for device restarts in open-balena
2023-04-05 13:08:14 +00:00
c1017e8e27 Add support for device restarts in open-balena
Update balena-sdk from 16.28.2 to 16.40.0

Change-type: minor
2023-04-05 12:57:33 +03:00
7ad9e685f6 v15.1.3 2023-04-05 08:06:56 +00:00
c778aaffaf Merge pull request #2607 from balena-io/update-balena-sdk-16.28.2
devices supported: Fix showing types without a valid & finalized release
2023-04-05 08:06:05 +00:00
b98047cacf devices supported: Fix showing types without a valid & finalized release
Update balena-sdk from 16.28.0 to 16.28.2

Resolves: #2524
Change-type: patch
2023-04-05 10:19:39 +03:00
03ace6e4b2 v15.1.2 2023-03-27 15:14:47 +00:00
9b4701bcb7 Merge pull request #2601 from balena-io/use-satisfies
Improve type checking by using the satisfies operator
2023-03-27 18:13:56 +03:00
174312977a Improve type checking by using the satisfies operator
Change-type: patch
2023-03-27 16:39:09 +03:00
963d9af817 v15.1.1 2023-03-17 10:20:03 +00:00
af5ec51232 Merge pull request #2600 from balena-io/bump-ts
Update TypeScript to 5.0.2
2023-03-17 12:19:13 +02:00
1cd9fbf6a0 Update TypeScript to 5.0.2
Change-type: patch
2023-03-16 20:53:08 +02:00
72639e9e59 v15.1.0 2023-03-14 20:19:08 +00:00
447dcc1480 Merge pull request #2599 from balena-io/kyle/balena-compose-v2.2.x
Update balena-compose to v2.2.1
2023-03-14 16:18:19 -04:00
564716faa7 Update balena-compose to v2.2.1
Update balena-compose from 2.1.1 to 2.2.1

Change-type: minor
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-03-14 14:59:52 -04:00
3e5b4457c2 v15.0.6 2023-03-13 14:03:48 +00:00
793e70d909 Merge pull request #2597 from balena-io/explicitly-select-devices-fields
Devices: explicitly fetches only used fields
2023-03-13 11:02:51 -03:00
5761a306be Devices: explicitly fetches only used fields
Change-type: patch
2023-03-13 09:35:43 -03:00
adff0f2a0a v15.0.5 2023-03-10 16:25:40 +00:00
4ec45a0c43 Merge pull request #2596 from balena-io/fix-is-legacy-check
Fix isLegacy check which should always relay on the slug
2023-03-10 18:24:48 +02:00
ecf4b046b5 Fix application isLegacy check for rename and deploy
Change-type: patch
2023-03-10 16:33:00 +01:00
b0cae93ac9 v15.0.4 2023-02-21 07:24:19 +00:00
53b66678d4 Merge pull request #2583 from balena-io/hraftery-patch-1-1
Clarify update rate of update-notifier info
2023-02-21 09:23:30 +02:00
0b9b65ef88 patch: Clarify update rate of update notifier info
If the cli has not been run in a while, it will show old update information. It's not obvious why, and this might lead to confusion. So this commit just adds a comment to clarify that out-of-date update notifier info is expected behaviour, and why.
2023-01-26 14:15:43 +11:00
8a84d9d792 v15.0.3 2023-01-18 16:16:39 +00:00
c535b8e1ea Merge pull request #2582 from balena-io/https-npm
Use https for the npm deprecation check, avoiding a redirect
2023-01-18 16:15:01 +00:00
234fb6cd39 Use https for the npm deprecation check, avoiding a redirect
Change-type: patch
2023-01-18 13:11:31 +00:00
8714830b48 v15.0.2 2023-01-14 07:35:13 +00:00
0e07b36691 Merge pull request #2580 from balena-io/joshbwlng/fix-typo
Fix push --nolive doc typo
2023-01-14 09:33:56 +02:00
ba80d3c38c Fix push --nolive doc typo
Change-type: patch
2023-01-13 13:36:44 +09:00
e65dc82cfe v15.0.1 2023-01-10 13:43:24 +00:00
bc727521c6 Merge pull request #2571 from balena-io/nodejs-14
Update to Node 14
2023-01-10 08:41:54 -05:00
a8c0c884d3 Extra linting 2023-01-03 16:08:10 -03:00
b11c7157d3 Update to node 14 2023-01-03 16:04:24 -03:00
578de7bcd4 Process livepush build logs inline
When using livepush, the CLI parses the build logs to obtain the stage
image ids, which are necessary for properly running livepush.

This process used to store the full log output in memory before parsing
the logs for obtaining the stage ids. We have seen this cause issues
before because of the excessive memory usage and it is one the suspects
of #2165, which is blocking the update to Node 14

Change-type: patch
2023-01-03 12:29:54 -03:00
cfc6b3ce9e v15.0.0 2023-01-02 15:21:59 +00:00
1c7a354fe7 Merge pull request #2573 from balena-io/balena-preload-13
Upgrade balena-preload to 13.0.0
2023-01-02 10:20:01 -05:00
40a0941ca3 preload: Drops ability to preload Intel Edison (EOL 2017)
Upgrade balena-preload from 12.2.0 to 13.0.0

Change-type: major
Signed-off-by: Edwin Joassart <edwin.joassart@balena.io>
2023-01-02 15:34:32 +01:00
0ab4760272 v14.5.18 2022-12-29 07:20:50 +00:00
42b2269e81 Merge pull request #2576 from balena-io/flowzone-npm-ci
Update flowzone tests to use npm ci
2022-12-29 02:19:24 -05:00
c818d846b3 Update flowzone tests to use npm ci
Will also make sure that the shrinkwrap is
matching the committed package.json.

Change-type: patch
2022-12-29 08:24:24 +02:00
3328f40416 v14.5.17 2022-12-28 23:56:12 +00:00
58d10c1908 Merge pull request #2575 from balena-io/drop-balena-sync
Stop using the deprecated balena-sync module
2022-12-29 01:54:48 +02:00
2fd0ca6a02 Stop using the deprecated balena-sync module
Change-type: patch
2022-12-29 01:05:51 +02:00
173028fd0d v14.5.16 2022-12-28 23:01:25 +00:00
62d5bf4436 Merge pull request #2574 from balena-io/align-package-json-shrinkwrap
Update the npm-shrinkwrap.json dependencies to match the package.json
2022-12-29 01:00:11 +02:00
63a0d19770 Update the npm-shrinkwrap.json dependencies to match the package.json
Change-type: patch
2022-12-28 21:22:48 +02:00
79 changed files with 29369 additions and 4810 deletions

View File

@ -3,32 +3,34 @@ name: package and draft GitHub release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: "JSON stringified object containing all the inputs from the calling workflow"
description: 'JSON stringified object containing all the inputs from the calling workflow'
required: true
secrets:
description: "JSON stringified object containing all the secrets from the calling workflow"
description: 'JSON stringified object containing all the secrets from the calling workflow'
required: true
variables:
description: 'JSON stringified object containing all the variables from the calling workflow'
required: true
# --- custom environment
XCODE_APP_LOADER_EMAIL:
type: string
default: "accounts+apple@balena.io"
default: 'accounts+apple@balena.io'
NODE_VERSION:
type: string
# FIXME: (please) https://github.com/balena-io/balena-cli/issues/2165
default: "12.x"
default: '16.x'
VERBOSE:
type: string
default: "true"
default: 'true'
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: "composite"
using: 'composite'
steps:
- name: Download custom source artifact
uses: actions/download-artifact@v3
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}
- name: Extract custom source artifact
@ -122,6 +124,7 @@ runs:
# Apple notarization (automation/build-bin.ts)
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
- name: Upload artifacts
uses: actions/upload-artifact@v3

View File

@ -8,12 +8,14 @@ inputs:
secrets:
description: "JSON stringified object containing all the secrets from the calling workflow"
required: true
variables:
description: "JSON stringified object containing all the variables from the calling workflow"
required: true
# --- custom environment
NODE_VERSION:
type: string
# FIXME: (please) https://github.com/balena-io/balena-cli/issues/2165
default: "12.x"
default: '16.x'
VERBOSE:
type: string
default: "true"
@ -36,7 +38,7 @@ runs:
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
if [[ -e package-lock.json ]]; then
if [[ -e package-lock.json ]] || [[ -e npm-shrinkwrap.json ]]; then
npm ci
else
npm i
@ -52,6 +54,6 @@ runs:
- name: Upload custom artifact
uses: actions/upload-artifact@v3
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz
retention-days: 1

View File

@ -3,14 +3,27 @@ name: Flowzone
on:
pull_request:
types: [opened, synchronize, closed]
branches:
- "main"
- "master"
branches: [main, master]
pull_request_target:
types: [opened, synchronize, closed]
branches: [main, master]
jobs:
flowzone:
name: Flowzone
uses: product-os/flowzone/.github/workflows/flowzone.yml@master
uses: product-os/flowzone/.github/workflows/flowzone.yml@v4.7.1
# prevent duplicate workflow executions for pull_request and pull_request_target
if: |
(
github.event.pull_request.head.repo.full_name == github.repository &&
github.event_name == 'pull_request'
) || (
github.event.pull_request.head.repo.full_name != github.repository &&
github.event_name == 'pull_request_target'
)
secrets: inherit
with:
tests_run_on: '["ubuntu-20.04","macos-11","windows-2019"]'
custom_runs_on: '[["self-hosted","Linux","distro:focal","X64"],["self-hosted","Linux","distro:focal","ARM64"],["macos-12"],["windows-2019"]]'
repo_config: true
repo_description: "The official balena CLI tool."
github_prerelease: false

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
*.seed
/.idea/
/.lock-wscript
/.nvmrc
/.nyc_output/
/.vscode/
/coverage/

1
.nvmrc
View File

@ -1 +0,0 @@
12

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,574 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/).
## 16.7.0 - 2023-07-17
* preload: Add the --no-pin-device-to-release flag to avoid interactive questions [Thodoris Greasidis]
## 16.6.6 - 2023-07-10
* Reduce lodash usage in common user interaction patterns [Thodoris Greasidis]
## 16.6.5 - 2023-07-09
* fleet/block/app create: Fetch the supported device types using the hostApps [Thodoris Greasidis]
## 16.6.4 - 2023-07-06
* Bump balena-compose to v2.3.0 [Felipe Lalanne]
## 16.6.3 - 2023-06-30
* Remove redundant dependency on docker-toolbelt [Akis Kesoglou]
* Pin dockerode to v3.3.3 [Akis Kesoglou]
## 16.6.2 - 2023-06-29
* macos notarization: Expose team ID instead of keeping it in secrets [myarmolinsky]
## 16.6.1 - 2023-06-28
* Drop `electron-notarize` dependency in favor of `@electron/notarize` [myarmolinsky]
## 16.6.0 - 2023-06-26
* api-key: Add `revoke` command which accepts a list of API key ids [myarmolinsky]
## 16.5.2 - 2023-06-09
* Re-enable automatic github final releases [Kyle Harding]
## 16.5.1 - 2023-06-02
* Update TypeScript to 5.1.3 [Thodoris Greasidis]
## 16.5.0 - 2023-05-25
* Add `balena block create` command for creating Blocks [myarmolinsky]
## 16.4.0 - 2023-05-25
* Add `balena app create` command for creatings Apps [myarmolinsky]
## 16.3.0 - 2023-05-25
* Add `balena api-keys` command for listing user/fleet API keys [myarmolinsky]
## 16.2.7 - 2023-05-24
* device init: Avoid extra request when not providing the --fleet option [Thodoris Greasidis]
* devices: Use a single request when providing the --fleet parameter [Thodoris Greasidis]
* ssh,tunnel: Fetch the fleet & devices in one request [Thodoris Greasidis]
## 16.2.6 - 2023-05-24
* device init: Stop fetching unnecessary app fields [Thodoris Greasidis]
* device move: Stop fetching unnecessary device & app fields [Thodoris Greasidis]
* device register: Stop fetching unnecessary app fields [Thodoris Greasidis]
* config generate: Stop fetching unnecessary app fields [Thodoris Greasidis]
* devices: Stop fetching unnecessary device fields [Thodoris Greasidis]
* fleet rm,restart,rename,purge: Stop fetching unnecessary app fields [Thodoris Greasidis]
* push: Stop unnecessarily fetching the application name [Thodoris Greasidis]
* ssh,tunnel: Reduce the amount of application fields fetched [Thodoris Greasidis]
* orgs: Stop fetching unnecessary org fields [Thodoris Greasidis]
* fleet create: Reduce the amount of org fields fetched [Thodoris Greasidis]
* Fetch only the app slug when resolving an app by name [Thodoris Greasidis]
## 16.2.5 - 2023-05-24
* Fix device int & os initialize failing to initialize the drive list [Thodoris Greasidis]
## 16.2.4 - 2023-05-23
* Remove extra request when filling the application selection list [Thodoris Greasidis]
## 16.2.3 - 2023-05-23
* Use stricter typings [Thodoris Greasidis]
## 16.2.2 - 2023-05-23
* env add: Fix accepting fleet names when setting service vars [Thodoris Greasidis]
## 16.2.1 - 2023-05-23
* Update balena-settings-client to 5.0.2 [Thodoris Greasidis]
<details>
<summary> Update balena-preload & balena-image-manager [Thodoris Greasidis] </summary>
> ### balena-image-manager-9.0.0 - 2023-05-23
>
> * Drop .hound.yml [Thodoris Greasidis]
> * Update dependencies [Thodoris Greasidis]
> * Update README.md [Thodoris Greasidis]
> * Update to balena-sdk 17.0.0 [Thodoris Greasidis]
> * Convert to typescript an emit type declarations [Thodoris Greasidis]
> * Require es2019 capable runtime [Thodoris Greasidis]
> * Drop support for Node.js v12 & v14 [Thodoris Greasidis]
>
> ### balena-image-manager-8.0.3 - 2022-09-26
>
> * Delete redundant .resinci.yml [Thodoris Greasidis]
>
> ### balena-image-manager-8.0.2 - 2022-09-22
>
> * Drop the .travis.yml [Thodoris Greasidis]
> * Replace balenaCI with flowzone [Thodoris Greasidis]
>
> ### balena-preload-14.0.0 - 2023-05-23
>
> * Improve typings & source type safety [Thodoris Greasidis]
> * Update dev dependencies [Thodoris Greasidis]
> * Update to balena-sdk 17.0.0 [Thodoris Greasidis]
> * Emit type declaration files [Thodoris Greasidis]
> * Switch to es6 module exports [Thodoris Greasidis]
> * Require es2019 capable runtime [Thodoris Greasidis]
> * Drop support for Node.js v12 & v14 [Thodoris Greasidis]
>
</details>
<details>
<summary> Update balena-sdk to 17.0.0 [Thodoris Greasidis] </summary>
> ### balena-sdk-17.0.0 - 2023-05-22
>
> * Fully type the result of os.getAllOsVersions when providing pine options [Thodoris Greasidis]
> * Change all .create() methods to return fully typed results [Thodoris Greasidis]
> * Change the pine.post() method to return fully typed results [Thodoris Greasidis]
> * Add the imageType property to the os.download method [Thodoris Greasidis]
> * Add OS download custom properties [Otávio Jacobi]
> * **BREAKING**: Drop the tags.getAll() method from all models [Thodoris Greasidis]
> * **BREAKING**: Drop organization.membership.getAll() [Thodoris Greasidis]
> * **BREAKING**: Drop application.membership.getAll() [Thodoris Greasidis]
> * **BREAKING**: Drop device.getAll() in favor of getAllByOrganization() [Thodoris Greasidis]
> * Replace lodash flatten with native flatMap [Thodoris Greasidis]
> * Move the sources under the /src folder [Thodoris Greasidis]
> * types: Add type checks for the direction of the string $orderby variant [Thodoris Greasidis]
> * types: Require a generic param in pine's associated resource helpers [Thodoris Greasidis]
> * tests: Convert some async expectations to asyn-await [Thodoris Greasidis]
> * types: Rename ODataOptionsWithSelect to ODataOptionsStrict [Thodoris Greasidis]
> * types: Rename PineOptionsWithSelect to PineOptionsStrict [Thodoris Greasidis]
> * Types: Rename ParamsObjWithSelect to ParamsObjStrict [Thodoris Greasidis]
> * release: Rename the `.note()` method to `.setNote()` [Thodoris Greasidis]
> * device: Rename the `.note()` method to `.setNote()` [Thodoris Greasidis]
> * os.download: Change to accept a single object parameter [Thodoris Greasidis]
> * OsVersion: Make `variant` non-nullable to match the Release field [Thodoris Greasidis]
> * OsVersion: Drop the formattedVersion property [Thodoris Greasidis]
> * OsVersion: Drop the rawVersion property in favor of raw_version [Thodoris Greasidis]
> * Drop device.getManifestByApplication() [Thodoris Greasidis]
> * Drop device.getManifestBySlug in favor of config.getDeviceTypeManifestBySlug [Thodoris Greasidis]
> * Drop support for callbacks [Thodoris Greasidis]
> * getWithServiceDetails: Drop the current_gateway_downloads property [Thodoris Greasidis]
> * types: Drop the deprecated public_key property from the JWTUser [Thodoris Greasidis]
> * Drop the deprecated needsPasswordReset property from the JWTUser [Thodoris Greasidis]
> * Bump mockttp to v3.0.0 [Thodoris Greasidis]
>
> <details>
> <summary> Update balena-hup-action-utils to 5.0.0 [Thodoris Greasidis] </summary>
>
>> #### balena-hup-action-utils-5.0.0 - 2023-04-28
>>
>> * Re-enable TS-compatibility checks [Thodoris Greasidis]
>> * Update Typescript to 5.0.4 [Thodoris Greasidis]
>> * Throw a typed HUPActionError for expected errors [Thodoris Greasidis]
>> * Change the build target to es6 [Thodoris Greasidis]
>> * Drop support for node < v12 [Thodoris Greasidis]
>>
>> #### balena-hup-action-utils-4.1.3 - 2022-09-26
>>
>> * Delete redundant .resinci.yml [Thodoris Greasidis]
>>
>> #### balena-hup-action-utils-4.1.2 - 2022-09-22
>>
>> * Specify node 10 as the minimum supported node engine in the package.json [Thodoris Greasidis]
>> * Replace balenaCI with flowzone [Thodoris Greasidis]
>>
>> #### balena-hup-action-utils-4.1.1 - 2022-04-09
>>
>> * Bump karma to v6 [Thodoris Greasidis]
>>
>
> </details>
>
> * Update balena-settings-client to v5.0.0 [Thodoris Greasidis]
> * Bump minimum supported Typescript version to v5.0.2 [Thodoris Greasidis]
> * **BREAKING**: Drop device.getAllByParentDevice [Thodoris Greasidis]
> * **BREAKING**: Drop support for node 12, require es2019 builtin APIs [Thodoris Greasidis]
> * **BREAKING**: Stop publishing an unminified browser bundle [Thodoris Greasidis]
>
</details>
## 16.2.0 - 2023-05-19
* secureboot: Retrieve the OS release & contract in one request [Thodoris Greasidis]
* os configure, config generate: Add '--secureBoot' option to opt-in secure boot [Alex Gonzalez]
<details>
<summary> package.json: Update balena-sdk to 16.44.2 [Alex Gonzalez] </summary>
> ### balena-sdk-16.44.2 - 2023-05-16
>
> * Update flowzone's macos runner to v12 [Thodoris Greasidis]
> * Add device type yocto properties to typings [Otávio Jacobi]
> * Optimize getDeviceUrl request in one query [Otávio Jacobi]
>
> ### balena-sdk-16.44.1 - 2023-05-09
>
> * Fix device.getAllByOrganization parameter docs [Otávio Jacobi]
>
> ### balena-sdk-16.44.0 - 2023-04-27
>
> * Add device.getAllByOrganization() [Thodoris Greasidis]
> * Deprecate Device's is_managed_by__device & manages__device properties [Thodoris Greasidis]
>
> ### balena-sdk-16.43.0 - 2023-04-19
>
> * Add test case DeviceHistory expandable resources [fisehara]
> * Make DeviceHistory referenced resources expandable [fisehara]
>
> ### balena-sdk-16.42.0 - 2023-04-18
>
> * Add support for pine queries on Concept Type properties [Thodoris Greasidis]
> * Properly type Actor properties on resources [fisehara]
>
> ### balena-sdk-16.41.0 - 2023-04-07
>
> * Release model: Add support for getting/patching releases by `application` & `rawVersion` pairs [myarmolinsky]
>
</details>
* flowzone: update custom runs to use macos-12 [Alex Gonzalez]
## 16.1.0 - 2023-05-16
* build linux/arm packages [balenaCI]
## 16.0.0 - 2023-05-16
* support: Change the printed support expiry date in ISO 8601 UTC format [Thodoris Greasidis]
* logs: Change the timestamp format to ISO 8601 UTC [Thodoris Greasidis]
* Pin flowzone to v4.7.1 [Felipe Lalanne]
* Update etcher-sdk to v8.5.3 [Felipe Lalanne]
* Update vercel/pkg to v5.8.1 [Felipe Lalanne]
* Update to Node 16 [Felipe Lalanne]
## 15.2.3 - 2023-05-03
* Use valid release uuid for local releases [Felipe Lalanne]
## 15.2.2 - 2023-04-28
* Remove nvmrc [Felipe Lalanne]
## 15.2.1 - 2023-04-28
* Fix tslib going out of sync causing HUP to fail [Thodoris Greasidis]
## 15.2.0 - 2023-04-05
<details>
<summary> Add support for device restarts in open-balena [Thodoris Greasidis] </summary>
> ### balena-sdk-16.40.0 - 2023-04-05
>
> * device.reboot: Fix the typings requiring a second argument [Thodoris Greasidis]
> * device.restartApplication: Use the supervisor endpoint to issue restarts [Thodoris Greasidis]
>
> ### balena-sdk-16.39.1 - 2023-04-04
>
> * patch: Split instruction strings on linebreak [Vipul Gupta (@vipulgupta2048)]
>
> ### balena-sdk-16.39.0 - Invalid date
>
> * Add `device history` model [fisehara]
>
> ### balena-sdk-16.38.2 - 2023-03-28
>
> * Fix credit-bundle jsdocs [Josh Bowling]
>
> ### balena-sdk-16.38.1 - 2023-03-27
>
> * Deprecate the device-type.json's instructions field [Thodoris Greasidis]
>
> ### balena-sdk-16.38.0 - 2023-03-21
>
> * Add aliases for the DT contrast slugs used in getInstructions [Thodoris Greasidis]
>
> ### balena-sdk-16.37.0 - 2023-03-21
>
> * device-type/getInstructions: Overload to accept the device type contract [Thodoris Greasidis]
>
> ### balena-sdk-16.36.6 - 2023-03-20
>
> * Update TypeScript to 5.0.2 [Thodoris Greasidis]
>
> ### balena-sdk-16.36.5 - 2023-03-16
>
> * patch: Improve jetsonFlash provisioning partial [Vipul Gupta (@vipulgupta2048)]
>
> ### balena-sdk-16.36.4 - 2023-03-15
>
> * Avoid running write operation tests in parallel to support retries [Thodoris Greasidis]
> * Retry failing tests twice [Thodoris Greasidis]
> * Fix tests per removal of `microservices-starter` application type [myarmolinsky]
>
> ### balena-sdk-16.36.3 - 2023-02-28
>
> * models/device-type: Add test for Radxa Zero instructions [Alexandru Costache]
> * lib/models: Add radxaFlash protocol for Radxa boards [Alexandru Costache]
>
> ### balena-sdk-16.36.2 - 2023-02-24
>
> * tests: Stop using flowzone internal env vars to for skipping npm test [Thodoris Greasidis]
>
> ### balena-sdk-16.36.1 - 2023-02-20
>
> * Add plan validity date fields [Josh Bowling]
>
> ### balena-sdk-16.36.0 - 2023-02-16
>
> * Add contract partial based instruction generation [Micah Halter]
>
> ### balena-sdk-16.35.0 - 2023-02-10
>
> * Add `CreditBundle` model [myarmolinsky]
>
> ### balena-sdk-16.34.0 - 2023-02-09
>
> * Add configVarInvalidRegex to Config Var typing [Felipe Lalanne]
>
> ### balena-sdk-16.33.0 - 2023-02-09
>
> * CurrentServiceWithCommit: Add release `raw_version` to type [myarmolinsky]
>
> ### balena-sdk-16.32.3 - 2023-02-07
>
> * Optimize the device.get method [Thodoris Greasidis]
>
> ### balena-sdk-16.32.2 - 2023-02-02
>
> * Improve pine typings for public resources without id fields [Thodoris Greasidis]
>
> ### balena-sdk-16.32.1 - 2023-01-16
>
> * Drop no longer used .travis.yml & .hound.yml [Thodoris Greasidis]
> * Rerun prettier [Thodoris Greasidis]
>
> ### balena-sdk-16.32.0 - 2023-01-05
>
> * typings: Add the device.is_frozen field [Thodoris Greasidis]
>
> ### balena-sdk-16.31.2 - 2022-12-20
>
> * application.create: Deprecate the `parent` option [Thodoris Greasidis]
> * Deprecate the device.getAllByParentDevice() method [Thodoris Greasidis]
> * Simplify the device.move() checks [Thodoris Greasidis]
>
> ### balena-sdk-16.31.1 - 2022-12-17
>
> * Replace appveyor with flowzone [Thodoris Greasidis]
>
> ### balena-sdk-16.31.0 - 2022-12-16
>
> * Add `updateAccountInfo` method to billing model for updating billing account info [myarmolinsky]
>
> ### balena-sdk-16.30.2 - 2022-12-13
>
> * Flowzone: Allow external contributions [Thodoris Greasidis]
>
> ### balena-sdk-16.30.1 - 2022-12-07
>
> * patch: bump catch-uncommitted from 1.6.2 to 2.0.0 [dependabot[bot]]
>
> ### balena-sdk-16.30.0 - 2022-11-24
>
> * Add utils and export mergePineOptions `balena.utils.mergePineOptions()` [JSReds]
>
> ### balena-sdk-16.29.3 - 2022-11-24
>
> * device.getWithServiceDetails: Stop auto-expanding the gateway_downloads [Thodoris Greasidis]
>
> ### balena-sdk-16.29.2 - 2022-11-16
>
> * Update TypeScript to 4.9.3 [Thodoris Greasidis]
>
> ### balena-sdk-16.29.1 - 2022-11-12
>
> * Fix release end_timestamp type [Thodoris Greasidis]
>
> ### balena-sdk-16.29.0 - 2022-11-12
>
>
> <details>
> <summary> Support filtered $count operations inside $filter & $orderby [Thodoris Greasidis] </summary>
>
>> #### pinejs-client-js-6.12.0 - 2022-11-10
>>
>> * Deprecate the 'a/count' notation in $orderby [Thodoris Greasidis]
>> * Deprecate the $count: { $op: number } notation [Thodoris Greasidis]
>> * Add support for `$filter: { $op: [{ $count: {} }, number] }` notation [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.11.0 - 2022-11-09
>>
>> * Deprecate non-$filter props inside `$expand: { a: { $count: {...}}}` [Thodoris Greasidis]
>> * Add support for `$orderby: { a: { $count: ... }, $dir: 'asc' }` notation [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.7 - 2022-11-07
>>
>> * Refactor the deprecation message definitions [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.6 - 2022-11-01
>>
>> * tests: Support `.only` & `.skip` in the higher level test functions [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.5 - 2022-10-14
>>
>> * Flowzone: Use inherited secrets [Pagan Gazzard]
>>
>> #### pinejs-client-js-6.10.4 - 2022-09-26
>>
>> * Specify node 10 as the minimum supported node engine in the package.json [Thodoris Greasidis]
>> * Replace balenaCI with flowzone [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.3 - 2022-09-15
>>
>> * Fix $count typings to only allow $filter under it [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.2 - 2022-04-08
>>
>> * Update dependencies [Pagan Gazzard]
>> * Remove circleci [Pagan Gazzard]
>>
>> #### pinejs-client-js-6.10.1 - 2022-02-08
>>
>> * Do not await the _request() result to allow enhanced promises downstream [Thodoris Greasidis]
>>
>> #### pinejs-client-js-6.10.0 - 2022-01-24
>>
>> * Add optional retry logic to client [Paul Jonathan Zoulin]
>>
>
> </details>
>
>
> ### balena-sdk-16.28.4 - 2022-11-04
>
> * Use deep imports for date-fns to improve tree-shaking [Thodoris Greasidis]
> * Enable esModuleInterop build option [Thodoris Greasidis]
>
> ### balena-sdk-16.28.3 - 2022-11-03
>
> * Update balena-errors to v4.7.3 [JSReds]
>
</details>
## 15.1.3 - 2023-04-05
<details>
<summary> devices supported: Fix showing types without a valid & finalized release [Thodoris Greasidis] </summary>
> ### balena-sdk-16.28.2 - 2022-10-27
>
> * Update tests to run on node 18 [Thodoris Greasidis]
> * deviceType.getAllSupported: Require a valid & final release to exist [Thodoris Greasidis]
>
> ### balena-sdk-16.28.1 - 2022-10-14
>
> * flowzone: Run the node tests using the latest LTS version [Thodoris Greasidis]
>
</details>
## 15.1.2 - 2023-03-27
* Improve type checking by using the satisfies operator [Thodoris Greasidis]
## 15.1.1 - 2023-03-17
* Update TypeScript to 5.0.2 [Thodoris Greasidis]
## 15.1.0 - 2023-03-14
<details>
<summary> Update balena-compose to v2.2.1 [Kyle Harding] </summary>
> ### balena-compose-2.2.1 - 2023-03-14
>
> * Ignore references to build stages when evaluating manifests [Kyle Harding]
>
> ### balena-compose-2.2.0 - 2023-03-13
>
> * OCI Image Index should allow platform opts [Kyle Harding]
>
> ### balena-compose-2.1.4 - 2023-03-13
>
> * Write to debug log when using platform option [Kyle Harding]
>
> ### balena-compose-2.1.3 - 2023-03-01
>
> * Fixup tests to use recent debian:bullseye-slim images [Kyle Harding]
>
> ### balena-compose-2.1.2 - 2022-10-17
>
> * test/multibuild: Use 127.0.0.1 for the extra_hosts test [Ken Bannister]
> * Output error text to aid test debugging [Ken Bannister]
> * Replace balenaCI & circleCI with flowzone [Thodoris Greasidis]
> * Pin dockerode to v3.3.3 to avoid regression [Ken Bannister]
> * Prettify fixup [Ken Bannister]
> * Fix underspecified generics in release/models [Ken Bannister]
>
</details>
## 15.0.6 - 2023-03-13
* Devices: explicitly fetches only used fields [Otávio Jacobi]
## 15.0.5 - 2023-03-10
* Fix application isLegacy check for rename and deploy [JSReds]
## 15.0.4 - 2023-02-21
* patch: Clarify update rate of update notifier info [Heath Raftery]
## 15.0.3 - 2023-01-18
* Use https for the npm deprecation check, avoiding a redirect [Pagan Gazzard]
## 15.0.2 - 2023-01-14
* Fix push --nolive doc typo [Josh Bowling]
## 15.0.1 - 2023-01-10
* Process livepush build logs inline [Felipe Lalanne]
## 15.0.0 - 2023-01-02
* preload: Drops ability to preload Intel Edison (EOL 2017) Upgrade balena-preload from 12.2.0 to 13.0.0 [JOASSART Edwin]
## 14.5.18 - 2022-12-29
* Update flowzone tests to use npm ci [Thodoris Greasidis]
## 14.5.17 - 2022-12-28
* Stop using the deprecated balena-sync module [Thodoris Greasidis]
## 14.5.16 - 2022-12-28
* Update the npm-shrinkwrap.json dependencies to match the package.json [Thodoris Greasidis]
## 14.5.15 - 2022-12-12
* patch: update balena-preload to 12.2.0 [Edwin Joassart]

View File

@ -78,8 +78,8 @@ If you are a Node.js developer, you may wish to install the balena CLI via [npm]
The npm installation involves building native (platform-specific) binary modules, which require
some development tools to be installed first, as follows.
> **The balena CLI currently requires Node.js version 12 (min 12.8.0).**
> **Versions 13 and later are not yet fully supported.**
> **The balena CLI currently requires Node.js version 16.**
> **Versions 17 and later are not yet fully supported.**
### Install development tools
@ -89,7 +89,7 @@ some development tools to be installed first, as follows.
$ sudo apt-get update && sudo apt-get -y install curl python3 git make g++
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc
$ nvm install 12
$ nvm install 16
```
The `curl` command line above uses
@ -106,15 +106,15 @@ recommended.
```sh
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
$ . ~/.bashrc
$ nvm install 12
$ nvm install 16
```
#### **Windows** (not WSL)
Install:
* Node.js v12 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
* If you'd like the ability to switch between Node.js versions, install
- Node.js v16 from the [Nodejs.org releases page](https://nodejs.org/en/download/releases/).
[nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows)
instead.
* The [MSYS2 shell](https://www.msys2.org/), which provides `git`, `make`, `g++` and more:

View File

@ -87,7 +87,7 @@ async function diffPkgOutput(pkgOut: string) {
'tests',
'test-data',
'pkg',
`expected-warnings-${process.platform}.txt`,
`expected-warnings-${process.platform}-${arch}.txt`,
);
const absSavedPath = path.join(ROOT, relSavedPath);
const ignoreStartsWith = [
@ -180,9 +180,18 @@ async function execPkg(...args: any[]) {
* to be directly executed from inside another binary executable.)
*/
async function buildPkg() {
// https://github.com/vercel/pkg#targets
let targets = `linux-${arch}`;
// TBC: not possible to build for macOS or Windows arm64 on x64 nodes
if (process.platform === 'darwin') {
targets = `macos-x64`;
}
if (process.platform === 'win32') {
targets = `win-x64`;
}
const args = [
'--target',
'host',
'--targets',
targets,
'--output',
'build-bin/balena',
'package.json',
@ -456,16 +465,17 @@ async function signWindowsInstaller() {
* Wait for Apple Installer Notarization to continue
*/
async function notarizeMacInstaller(): Promise<void> {
const teamId = process.env.XCODE_APP_LOADER_TEAM_ID || '66H43P8FRG';
const appleId =
process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io';
const appBundleId = packageJSON.oclif.macos.identifier || 'io.balena.cli';
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD;
if (appleIdPassword) {
const { notarize } = await import('electron-notarize');
// https://github.com/electron/notarize/blob/main/README.md
if (appleIdPassword && teamId) {
const { notarize } = await import('@electron/notarize');
// https://github.com/electron/notarize#readme
await notarize({
appBundleId,
tool: 'notarytool',
teamId,
appPath: renamedOclifInstallers.darwin,
appleId,
appleIdPassword,

View File

@ -8,9 +8,11 @@ _balena() {
local context state line curcontext="$curcontext"
# Valid top-level completions
main_commands=( build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util )
main_commands=( build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key api-keys app block config device device devices env fleet fleet internal key key local os release release tag util )
# Sub-completions
api_key_cmds=( generate )
api_key_cmds=( generate revoke )
app_cmds=( create )
block_cmds=( create )
config_cmds=( generate inject read reconfigure write )
device_cmds=( deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet )
devices_cmds=( supported )
@ -43,6 +45,12 @@ _balena_sec_cmds() {
"api-key")
_describe -t api_key_cmds 'api-key_cmd' api_key_cmds "$@" && ret=0
;;
"app")
_describe -t app_cmds 'app_cmd' app_cmds "$@" && ret=0
;;
"block")
_describe -t block_cmds 'block_cmd' block_cmds "$@" && ret=0
;;
"config")
_describe -t config_cmds 'config_cmd' config_cmds "$@" && ret=0
;;

View File

@ -7,9 +7,11 @@ _balena_complete()
local cur prev
# Valid top-level completions
main_commands="build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key config device device devices env fleet fleet internal key key local os release release tag util"
main_commands="build deploy envs fleets join keys leave login logout logs note orgs preload push releases scan settings ssh support tags tunnel version whoami api-key api-keys app block config device device devices env fleet fleet internal key key local os release release tag util"
# Sub-completions
api_key_cmds="generate"
api_key_cmds="generate revoke"
app_cmds="create"
block_cmds="create"
config_cmds="generate inject read reconfigure write"
device_cmds="deactivate identify init local-mode move os-update pin public-url purge reboot register rename restart rm shutdown track-fleet"
devices_cmds="supported"
@ -37,6 +39,12 @@ _balena_complete()
api-key)
COMPREPLY=( $(compgen -W "$api_key_cmds" -- $cur) )
;;
app)
COMPREPLY=( $(compgen -W "$app_cmds" -- $cur) )
;;
block)
COMPREPLY=( $(compgen -W "$block_cmds" -- $cur) )
;;
config)
COMPREPLY=( $(compgen -W "$config_cmds" -- $cur) )
;;

View File

@ -2160,6 +2160,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.
The --system-connection (-c) option is used to inject NetworkManager connection
profiles for additional network interfaces, such as cellular/GSM or additional
WiFi or ethernet connections. This option may be passed multiple times in case there
@ -2230,6 +2233,10 @@ WiFi SSID (network name) (non-interactive configuration)
Configure balenaOS to operate in development mode
#### --secureBoot
Configure balenaOS installer to opt-in secure boot and disk encryption
#### -d, --device DEVICE
device UUID
@ -2314,6 +2321,9 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.
To configure an image for a fleet of mixed device types, use the --fleet option
alongside the --deviceType option to specify the target device type.
@ -2337,6 +2347,7 @@ Examples:
$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev
$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot
$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3
$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json
$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15
@ -2355,6 +2366,10 @@ fleet name or slug (preferred)
Configure balenaOS to operate in development mode
#### --secureBoot
Configure balenaOS installer to opt-in secure boot and disk encryption
#### -d, --device DEVICE
device UUID
@ -2788,7 +2803,7 @@ used (usually $HOME/.balena/secrets.yml|.json)
Don't run a live session on this push. The filesystem will not be monitored,
and changes will not be synchronized to any running containers. Note that both
this flag and --detached and required to cause the process to end once the
this flag and --detached are required to cause the process to end once the
initial build has completed.
#### -d, --detached

View File

@ -0,0 +1,80 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
help: void;
}
interface ArgsDef {
ids: string;
}
export default class RevokeCmd extends Command {
public static description = stripIndent`
Revoke balenaCloud API keys.
Revoke balenaCloud API keys with the given
comma-separated list of ids.
The given balenaCloud API keys will no longer be usable.
`;
public static examples = [
'$ balena api-key revoke 123',
'$ balena api-key revoke 123,124,456',
];
public static args = [
{
name: 'ids',
description: 'the API key ids',
required: true,
},
];
public static usage = 'api-key revoke <ids>';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(RevokeCmd);
try {
const apiKeyIds = params.ids.split(',');
if (apiKeyIds.filter((apiKeyId) => !apiKeyId.match(/^\d+$/)).length > 0) {
console.log('API key ids must be positive integers');
return;
}
await Promise.all(
apiKeyIds.map(
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
),
);
console.log('Successfully revoked the given API keys');
} catch (e) {
throw e;
}
}
}

View File

@ -0,0 +1,95 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
help: void;
user?: void;
fleet?: string;
}
export default class ApiKeysCmd extends Command {
public static description = stripIndent`
Print a list of balenaCloud API keys.
Print a list of balenaCloud API keys.
Print a list of balenaCloud API keys for the current user or for a specific fleet with the \`--fleet\` option.
`;
public static examples = ['$ balena api-keys'];
public static args = [];
public static usage = 'api-keys';
public static flags: flags.Input<FlagsDef> = {
help: cf.help,
user: flags.boolean({
char: 'u',
description: 'show API keys for your user',
}),
fleet: cf.fleet,
};
public static authenticated = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ApiKeysCmd);
try {
const { getApplication } = await import('../../utils/sdk');
const actorId = options.fleet
? (
await getApplication(getBalenaSdk(), options.fleet, {
$select: 'actor',
})
).actor
: await getBalenaSdk().auth.getUserActorId();
const keys = await getBalenaSdk().pine.get({
resource: 'api_key',
options: {
$select: ['id', 'created_at', 'name', 'description', 'expiry_date'],
$filter: {
is_of__actor: actorId,
...(options.user
? {
name: {
$ne: null,
},
}
: {}),
},
$orderby: 'name asc',
},
});
const fields = ['id', 'name', 'created_at', 'description', 'expiry_date'];
const _ = await import('lodash');
console.log(
getVisuals().table.horizontal(
keys.map((key) => _.mapValues(key, (val) => val ?? 'N/a')),
fields,
),
);
} catch (e) {
throw e;
}
}
}

150
lib/commands/app/create.ts Normal file
View File

@ -0,0 +1,150 @@
/**
* @license
* Copyright 2016-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.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
organization?: string;
type?: string; // application device type
help: void;
}
interface ArgsDef {
name: string;
}
export default class AppCreateCmd extends Command {
public static description = stripIndent`
Create an app.
Create a new balena app.
You can specify the organization the app should belong to using
the \`--organization\` option. The organization's handle, not its name,
should be provided. Organization handles can be listed with the
\`balena orgs\` command.
The app's default device type is specified with the \`--type\` option.
The \`balena devices supported\` command can be used to list the available
device types.
Interactive dropdowns will be shown for selection if no device type or
organization is specified and there are multiple options to choose from.
If there is a single option to choose from, it will be chosen automatically.
This interactive behavior can be disabled by explicitly specifying a device
type and organization.
`;
public static examples = [
'$ balena app create MyApp',
'$ balena app create MyApp --organization mmyorg',
'$ balena app create MyApp -o myorg --type raspberry-pi',
];
public static args = [
{
name: 'name',
description: 'app name',
required: true,
},
];
public static usage = 'app create <name>';
public static flags: flags.Input<FlagsDef> = {
organization: flags.string({
char: 'o',
description: 'handle of the organization the app should belong to',
}),
type: flags.string({
char: 't',
description:
'app device type (Check available types with `balena devices supported`)',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
AppCreateCmd,
);
// Ascertain device type
const deviceType =
options.type ||
(await (await import('../../utils/patterns')).selectDeviceType());
// Ascertain organization
const organization =
options.organization?.toLowerCase() || (await this.getOrganization());
// Create application
try {
const application = await getBalenaSdk().models.application.create({
name: params.name,
deviceType,
organization,
applicationClass: 'app',
});
// Output
console.log(
`App created: slug "${application.slug}", device type "${deviceType}"`,
);
} catch (err) {
if ((err.message || '').toLowerCase().includes('unique')) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
throw new ExpectedError(
`Error: An app or block or fleet with the name "${params.name}" already exists in organization "${organization}".`,
);
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
// BalenaRequestError: Request error: Unauthorized
throw new ExpectedError(
`Error: You are not authorized to create apps in organization "${organization}".`,
);
}
throw err;
}
}
async getOrganization() {
const { getOwnOrganizations } = await import('../../utils/sdk');
const organizations = await getOwnOrganizations(getBalenaSdk(), {
$select: ['name', 'handle'],
});
if (organizations.length === 0) {
// User is not a member of any organizations (should not happen).
throw new Error('This account is not a member of any organizations');
} else if (organizations.length === 1) {
// User is a member of only one organization - use this.
return organizations[0].handle;
} else {
// User is a member of multiple organizations -
const { selectOrganization } = await import('../../utils/patterns');
return selectOrganization(organizations);
}
}
}

View File

@ -0,0 +1,150 @@
/**
* @license
* Copyright 2016-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.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
interface FlagsDef {
organization?: string;
type?: string; // application device type
help: void;
}
interface ArgsDef {
name: string;
}
export default class BlockCreateCmd extends Command {
public static description = stripIndent`
Create an block.
Create a new balena block.
You can specify the organization the block should belong to using
the \`--organization\` option. The organization's handle, not its name,
should be provided. Organization handles can be listed with the
\`balena orgs\` command.
The block's default device type is specified with the \`--type\` option.
The \`balena devices supported\` command can be used to list the available
device types.
Interactive dropdowns will be shown for selection if no device type or
organization is specified and there are multiple options to choose from.
If there is a single option to choose from, it will be chosen automatically.
This interactive behavior can be disabled by explicitly specifying a device
type and organization.
`;
public static examples = [
'$ balena block create MyBlock',
'$ balena block create MyBlock --organization mmyorg',
'$ balena block create MyBlock -o myorg --type raspberry-pi',
];
public static args = [
{
name: 'name',
description: 'block name',
required: true,
},
];
public static usage = 'block create <name>';
public static flags: flags.Input<FlagsDef> = {
organization: flags.string({
char: 'o',
description: 'handle of the organization the block should belong to',
}),
type: flags.string({
char: 't',
description:
'block device type (Check available types with `balena devices supported`)',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
BlockCreateCmd,
);
// Ascertain device type
const deviceType =
options.type ||
(await (await import('../../utils/patterns')).selectDeviceType());
// Ascertain organization
const organization =
options.organization?.toLowerCase() || (await this.getOrganization());
// Create application
try {
const application = await getBalenaSdk().models.application.create({
name: params.name,
deviceType,
organization,
applicationClass: 'block',
});
// Output
console.log(
`Block created: slug "${application.slug}", device type "${deviceType}"`,
);
} catch (err) {
if ((err.message || '').toLowerCase().includes('unique')) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
throw new ExpectedError(
`Error: An app or block or fleet with the name "${params.name}" already exists in organization "${organization}".`,
);
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
// BalenaRequestError: Request error: Unauthorized
throw new ExpectedError(
`Error: You are not authorized to create blocks in organization "${organization}".`,
);
}
throw err;
}
}
async getOrganization() {
const { getOwnOrganizations } = await import('../../utils/sdk');
const organizations = await getOwnOrganizations(getBalenaSdk(), {
$select: ['name', 'handle'],
});
if (organizations.length === 0) {
// User is not a member of any organizations (should not happen).
throw new Error('This account is not a member of any organizations');
} else if (organizations.length === 1) {
// User is a member of only one organization - use this.
return organizations[0].handle;
} else {
// User is a member of multiple organizations -
const { selectOrganization } = await import('../../utils/patterns');
return selectOrganization(organizations);
}
}
}

View File

@ -20,7 +20,7 @@ import Command from '../command';
import { getBalenaSdk } from '../utils/lazy';
import * as cf from '../utils/common-flags';
import * as compose from '../utils/compose';
import type { Application, ApplicationType, BalenaSDK } from 'balena-sdk';
import type { ApplicationType, BalenaSDK } from 'balena-sdk';
import {
buildArgDeprecation,
dockerignoreHelp,
@ -208,7 +208,7 @@ ${dockerignoreHelp}
* buildEmulated
* buildOpts: arguments to forward to docker build command
*
* @param {DockerToolbelt} docker
* @param {Dockerode} docker
* @param {Logger} logger
* @param {ComposeOpts} composeOpts
* @param opts
@ -218,7 +218,9 @@ ${dockerignoreHelp}
logger: import('../utils/logger'),
composeOpts: ComposeOpts,
opts: {
app?: Application;
app?: {
application_type: [Pick<ApplicationType, 'supports_multicontainer'>];
};
arch: string;
deviceType: string;
buildEmulated: boolean;
@ -234,7 +236,7 @@ ${dockerignoreHelp}
opts.buildOpts.t,
);
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
const appType = opts.app?.application_type?.[0];
if (
appType != null &&
project.descriptors.length > 1 &&

View File

@ -19,13 +19,18 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
import type { PineDeferred } from 'balena-sdk';
import {
applicationIdInfo,
devModeInfo,
secureBootInfo,
} from '../../utils/messages';
import type { BalenaSDK, PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
fleet?: string;
dev?: boolean; // balenaOS development variant
secureBoot?: boolean;
device?: string;
deviceApiKey?: string;
deviceType?: string;
@ -51,6 +56,8 @@ export default class ConfigGenerateCmd extends Command {
${devModeInfo.split('\n').join('\n\t\t')}
${secureBootInfo.split('\n').join('\n\t\t')}
To configure an image for a fleet of mixed device types, use the --fleet option
alongside the --deviceType option to specify the target device type.
@ -66,6 +73,7 @@ export default class ConfigGenerateCmd extends Command {
'$ balena config generate --device 7cf02a6 --version 2.12.7 --deviceApiKey <existingDeviceKey>',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --dev',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --secureBoot',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --deviceType fincm3',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --output config.json',
'$ balena config generate --fleet myorg/fleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
@ -80,6 +88,7 @@ export default class ConfigGenerateCmd extends Command {
}),
fleet: { ...cf.fleet, exclusive: ['device'] },
dev: cf.dev,
secureBoot: cf.secureBoot,
device: {
...cf.device,
exclusive: [
@ -135,17 +144,25 @@ export default class ConfigGenerateCmd extends Command {
public static authenticated = true;
public async getApplication(balena: BalenaSDK, fleet: string) {
const { getApplication } = await import('../../utils/sdk');
return await getApplication(balena, fleet, {
$select: 'slug',
$expand: {
is_for__device_type: { $select: 'slug' },
},
});
}
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
const { getApplication } = await import('../../utils/sdk');
const balena = getBalenaSdk();
await this.validateOptions(options);
let resourceDeviceType: string;
let application: ApplicationWithDeviceType | null = null;
let application: Awaited<ReturnType<typeof this.getApplication>> | null =
null;
let device:
| (DeviceWithDeviceType & { belongs_to__application: PineDeferred })
| null = null;
@ -165,11 +182,7 @@ export default class ConfigGenerateCmd extends Command {
resourceDeviceType = device.is_of__device_type[0].slug;
} else {
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
application = (await getApplication(balena, options.fleet!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
application = await this.getApplication(balena, options.fleet!);
resourceDeviceType = application.is_for__device_type[0].slug;
}
@ -191,8 +204,16 @@ export default class ConfigGenerateCmd extends Command {
}
}
const deviceManifest = await balena.models.device.getManifestBySlug(
const deviceManifest =
await balena.models.config.getDeviceTypeManifestBySlug(deviceType);
const { validateSecureBootOptionAndWarn } = await import(
'../../utils/config'
);
await validateSecureBootOptionAndWarn(
options.secureBoot,
deviceType,
options.version,
);
// Prompt for values
@ -203,6 +224,7 @@ export default class ConfigGenerateCmd extends Command {
});
answers.version = options.version;
answers.developmentMode = options.dev;
answers.secureBoot = options.secureBoot;
answers.provisioningKeyName = options['provisioning-key-name'];
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];

View File

@ -43,15 +43,13 @@ import {
parseReleaseTagKeysAndValues,
} from '../utils/compose_ts';
import { dockerCliFlags } from '../utils/docker';
import type {
Application,
ApplicationType,
DeviceType,
Release,
} from 'balena-sdk';
import type { ApplicationType, DeviceType, Release } from 'balena-sdk';
interface ApplicationWithArch extends Application {
interface ApplicationWithArch {
id: number;
arch: string;
is_for__device_type: [Pick<DeviceType, 'slug'>];
application_type: [Pick<ApplicationType, 'slug' | 'supports_multicontainer'>];
}
interface FlagsDef extends ComposeCliFlags, DockerCliFlags {
@ -235,7 +233,7 @@ ${dockerignoreHelp}
releaseTagValues,
);
if (options.note) {
await sdk.models.release.note(release.id, options.note);
await sdk.models.release.setNote(release.id, options.note);
}
}
@ -262,7 +260,7 @@ ${dockerignoreHelp}
'../utils/compose_ts'
);
const appType = (opts.app?.application_type as ApplicationType[])?.[0];
const appType = opts.app.application_type[0];
try {
const project = await loadProject(
@ -319,7 +317,7 @@ ${dockerignoreHelp}
projectName: project.name,
composition: compositionToBuild,
arch: opts.app.arch,
deviceType: (opts.app?.is_for__device_type as DeviceType[])?.[0].slug,
deviceType: opts.app.is_for__device_type[0].slug,
emulated: opts.buildEmulated,
buildOpts: opts.buildOpts,
inlineLogs: composeOpts.inlineLogs,
@ -340,7 +338,7 @@ ${dockerignoreHelp}
);
let release: Release | ComposeReleaseInfo['release'];
if (appType?.is_legacy) {
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
const { deployLegacy } = require('../utils/deploy-legacy');
const msg = getChalk().yellow(

View File

@ -124,20 +124,16 @@ export default class DeviceInitCmd extends Command {
const balena = getBalenaSdk();
// Get application and
const application = (await getApplication(
balena,
options.fleet ||
(
await (await import('../../utils/patterns')).selectApplication()
).slug,
{
$expand: {
is_for__device_type: {
$select: 'slug',
const application = options.fleet
? await getApplication(balena, options.fleet, {
$select: ['id', 'slug'],
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
},
},
)) as ApplicationWithDeviceType;
})
: await (await import('../../utils/patterns')).selectApplication();
// Register new device
const deviceUuid = balena.models.device.generateUniqueKey();

View File

@ -21,6 +21,7 @@ import type {
BalenaSDK,
Device,
DeviceType,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
import Command from '../../command';
@ -29,13 +30,6 @@ import { ExpectedError } from '../../errors';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
type ExtendedDevice = PineTypedResult<
Device,
typeof import('../../utils/helpers').expandForAppNameAndCpuArch
> & {
application_name?: string;
};
interface FlagsDef {
fleet?: string;
help: void;
@ -81,6 +75,33 @@ export default class DeviceMoveCmd extends Command {
public static authenticated = true;
private async getDevices(balena: BalenaSDK, deviceUuids: string[]) {
const deviceOptions = {
$select: 'belongs_to__application',
$expand: {
is_of__device_type: {
$select: 'is_of__cpu_architecture',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
},
},
} satisfies PineOptions<Device>;
// TODO: Refacor once `device.get()` accepts an array of uuids`
const devices = await Promise.all(
deviceUuids.map(
(uuid) =>
balena.models.device.get(uuid, deviceOptions) as Promise<
PineTypedResult<Device, typeof deviceOptions>
>,
),
);
return devices;
}
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
DeviceMoveCmd,
@ -88,36 +109,17 @@ export default class DeviceMoveCmd extends Command {
const balena = getBalenaSdk();
const { expandForAppNameAndCpuArch } = await import('../../utils/helpers');
// Split uuids string into array of uuids
const deviceUuids = params.uuid.split(',');
// Get devices
const devices = await Promise.all(
deviceUuids.map(
(uuid) =>
balena.models.device.get(
uuid,
expandForAppNameAndCpuArch,
) as Promise<ExtendedDevice>,
),
);
// Map application name for each device
for (const device of devices) {
const belongsToApplication = device.belongs_to__application;
device.application_name = belongsToApplication?.[0]
? belongsToApplication[0].app_name
: 'N/a';
}
const devices = await this.getDevices(balena, deviceUuids);
// Disambiguate application
const { getApplication } = await import('../../utils/sdk');
// Get destination application
const application = options.fleet
? await getApplication(balena, options.fleet)
? await getApplication(balena, options.fleet, { $select: ['id', 'slug'] })
: await this.interactivelySelectApplication(balena, devices);
// Move each device
@ -134,7 +136,7 @@ export default class DeviceMoveCmd extends Command {
async interactivelySelectApplication(
balena: BalenaSDK,
devices: ExtendedDevice[],
devices: Awaited<ReturnType<typeof this.getDevices>>,
) {
const { getExpandedProp } = await import('../../utils/pine');
// deduplicate the slugs
@ -153,7 +155,7 @@ export default class DeviceMoveCmd extends Command {
$select: 'slug',
},
},
} as const;
} satisfies PineOptions<DeviceType>;
const deviceTypes = (await balena.models.deviceType.getAllSupported(
deviceTypeOptions,
)) as Array<PineTypedResult<DeviceType, typeof deviceTypeOptions>>;
@ -180,7 +182,9 @@ export default class DeviceMoveCmd extends Command {
const application = await patterns.selectApplication(
(app) =>
compatibleDeviceTypeSlugs.has(app.is_for__device_type[0].slug) &&
devices.some((device) => device.application_name !== app.app_name),
devices.some(
(device) => device.belongs_to__application.__id !== app.id,
),
true,
);
return application;

View File

@ -78,7 +78,9 @@ export default class DeviceRegisterCmd extends Command {
const balena = getBalenaSdk();
const application = await getApplication(balena, params.fleet);
const application = await getApplication(balena, params.fleet, {
$select: ['id', 'slug'],
});
const uuid = options.uuid ?? balena.models.device.generateUniqueKey();
console.info(`Registering to ${application.slug}: ${uuid}`);

View File

@ -22,13 +22,7 @@ import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { applicationIdInfo, jsonInfo } from '../../utils/messages';
import type { Application } from 'balena-sdk';
interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string;
fleet?: string | null; // 'org/name' slug
device_type?: string | null;
}
import type { Device, PineOptions } from 'balena-sdk';
interface FlagsDef {
fleet?: string;
@ -36,6 +30,18 @@ interface FlagsDef {
json: boolean;
}
const devicesSelectFields = {
$select: [
'id',
'uuid',
'device_name',
'status',
'is_online',
'supervisor_version',
'os_version',
],
} satisfies PineOptions<Device>;
export default class DevicesCmd extends Command {
public static description = stripIndent`
List all devices.
@ -70,36 +76,39 @@ export default class DevicesCmd extends Command {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesCmd);
const balena = getBalenaSdk();
const devicesOptions = {
...devicesSelectFields,
...expandForAppName,
$orderby: { device_name: 'asc' },
} satisfies PineOptions<Device>;
let devices;
const devices = (
await (async () => {
if (options.fleet != null) {
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, options.fleet, {
$select: 'slug',
$expand: {
owns__device: devicesOptions,
},
});
return application.owns__device;
}
if (options.fleet != null) {
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, options.fleet);
devices = (await balena.models.device.getAllByApplication(
application.id,
expandForAppName,
)) as ExtendedDevice[];
} else {
devices = (await balena.models.device.getAll(
expandForAppName,
)) as ExtendedDevice[];
}
return await balena.pine.get({
resource: 'device',
options: devicesOptions,
});
})()
).map((device) => ({
...device,
dashboard_url: balena.models.device.getDashboardUrl(device.uuid),
fleet: device.belongs_to__application?.[0]?.slug || null,
uuid: options.json ? device.uuid : device.uuid.slice(0, 7),
device_type: device.is_of__device_type?.[0]?.slug || null,
}));
devices = devices.map(function (device) {
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
const belongsToApplication =
device.belongs_to__application as Application[];
device.fleet = belongsToApplication?.[0]?.slug || null;
device.uuid = options.json ? device.uuid : device.uuid.slice(0, 7);
device.device_type = device.is_of__device_type?.[0]?.slug || null;
return device;
});
const fields = [
const fields: Array<keyof (typeof devices)[number]> = [
'id',
'uuid',
'device_name',

View File

@ -61,15 +61,15 @@ export default class DevicesSupportedCmd extends Command {
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
const pineOptions = {
$select: (['slug', 'name'] as const).slice(),
$select: ['slug', 'name'],
$expand: {
is_of__cpu_architecture: { $select: 'slug' },
device_type_alias: {
$select: 'is_referenced_by__alias',
$orderby: 'is_referenced_by__alias asc',
$orderby: { is_referenced_by__alias: 'asc' },
},
},
} as const;
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
const dts = (await getBalenaSdk().models.deviceType.getAllSupported(
pineOptions,
)) as Array<

View File

@ -151,16 +151,15 @@ export default class EnvAddCmd extends Command {
const varType = isConfigVar ? 'configVar' : 'envVar';
if (options.fleet) {
const { getFleetSlug } = await import('../../utils/sdk');
for (const app of options.fleet.split(',')) {
for (const appSlug of await resolveFleetSlugs(balena, options.fleet)) {
try {
await balena.models.application[varType].set(
await getFleetSlug(balena, app),
appSlug,
params.name,
params.value,
);
} catch (err) {
console.error(`${err.message}, fleet: ${app}`);
console.error(`${err.message}, fleet: ${appSlug}`);
process.exitCode = 1;
}
}
@ -181,6 +180,25 @@ export default class EnvAddCmd extends Command {
}
}
// TODO: Stop accepting application names in the next major
// and just drop this in favor of doing the .split(',') directly.
async function resolveFleetSlugs(
balena: BalenaSdk.BalenaSDK,
fleetOption: string,
) {
const fleetSlugs: string[] = [];
const { getFleetSlug } = await import('../../utils/sdk');
for (const appNameOrSlug of fleetOption.split(',')) {
try {
fleetSlugs.push(await getFleetSlug(balena, appNameOrSlug));
} catch (err) {
console.error(`${err.message}, fleet: ${appNameOrSlug}`);
process.exitCode = 1;
}
}
return fleetSlugs;
}
/**
* Add service variables for a device or fleet.
*/
@ -190,17 +208,17 @@ async function setServiceVars(
options: FlagsDef,
) {
if (options.fleet) {
for (const app of options.fleet.split(',')) {
for (const appSlug of await resolveFleetSlugs(sdk, options.fleet)) {
for (const service of options.service!.split(',')) {
try {
const serviceId = await getServiceIdForApp(sdk, app, service);
const serviceId = await getServiceIdForApp(sdk, appSlug, service);
await sdk.models.service.var.set(
serviceId,
params.name,
params.value!,
);
} catch (err) {
console.error(`${err.message}, fleet: ${app}`);
console.error(`${err.message}, fleet: ${appSlug}`);
process.exitCode = 1;
}
}
@ -245,11 +263,11 @@ async function setServiceVars(
*/
async function getServiceIdForApp(
sdk: BalenaSdk.BalenaSDK,
appName: string,
appSlug: string,
serviceName: string,
): Promise<number> {
let serviceId: number | undefined;
const services = await sdk.models.service.getAllByApplication(appName, {
const services = await sdk.models.service.getAllByApplication(appSlug, {
$filter: { service_name: serviceName },
});
if (services.length > 0) {
@ -257,7 +275,7 @@ async function getServiceIdForApp(
}
if (serviceId === undefined) {
throw new ExpectedError(
`Cannot find service ${serviceName} for fleet ${appName}`,
`Cannot find service ${serviceName} for fleet ${appSlug}`,
);
}
return serviceId;

View File

@ -16,7 +16,6 @@
*/
import { flags } from '@oclif/command';
import type { Application } from 'balena-sdk';
import Command from '../../command';
import { ExpectedError } from '../../errors';
@ -101,18 +100,22 @@ export default class FleetCreateCmd extends Command {
options.organization?.toLowerCase() || (await this.getOrganization());
// Create application
let application: Application;
try {
application = await getBalenaSdk().models.application.create({
const application = await getBalenaSdk().models.application.create({
name: params.name,
deviceType,
organization,
});
// Output
console.log(
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
);
} catch (err) {
if ((err.message || '').toLowerCase().includes('unique')) {
// BalenaRequestError: Request error: "organization" and "app_name" must be unique.
throw new ExpectedError(
`Error: fleet "${params.name}" already exists in organization "${organization}".`,
`Error: An app or block or fleet with the name "${params.name}" already exists in organization "${organization}".`,
);
} else if ((err.message || '').toLowerCase().includes('unauthorized')) {
// BalenaRequestError: Request error: Unauthorized
@ -123,16 +126,13 @@ export default class FleetCreateCmd extends Command {
throw err;
}
// Output
console.log(
`Fleet created: slug "${application.slug}", device type "${deviceType}"`,
);
}
async getOrganization() {
const { getOwnOrganizations } = await import('../../utils/sdk');
const organizations = await getOwnOrganizations(getBalenaSdk());
const organizations = await getOwnOrganizations(getBalenaSdk(), {
$select: ['name', 'handle'],
});
if (organizations.length === 0) {
// User is not a member of any organizations (should not happen).

View File

@ -17,7 +17,6 @@
import type { flags as flagsType } from '@oclif/command';
import { flags } from '@oclif/command';
import type { Release } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
@ -74,17 +73,12 @@ export default class FleetCmd extends Command {
const balena = getBalenaSdk();
const application = (await getApplication(balena, params.fleet, {
const application = await getApplication(balena, params.fleet, {
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
})) as ApplicationWithDeviceType & {
should_be_running__release: [Release?];
// For display purposes:
device_type: string;
commit?: string;
};
});
if (options.view) {
const open = await import('open');
@ -95,11 +89,14 @@ export default class FleetCmd extends Command {
return;
}
application.device_type = application.is_for__device_type[0].slug;
application.commit = application.should_be_running__release[0]?.commit;
const outputApplication = {
...application,
device_type: application.is_for__device_type[0].slug,
commit: application.should_be_running__release[0]?.commit,
};
await this.outputData(
application,
outputApplication,
['app_name', 'id', 'device_type', 'slug', 'commit'],
options,
);

View File

@ -65,7 +65,9 @@ export default class FleetPurgeCmd extends Command {
// balena.models.application.purge only accepts a numeric id
// so we must first fetch the app to get it's id,
const application = await getApplication(balena, params.fleet);
const application = await getApplication(balena, params.fleet, {
$select: 'id',
});
try {
await balena.models.application.purge(application.id);

View File

@ -16,7 +16,6 @@
*/
import type { flags } from '@oclif/command';
import type { ApplicationType } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
@ -78,9 +77,10 @@ export default class FleetRenameCmd extends Command {
// Disambiguate target application (if params.params is a number, it could either be an ID or a numerical name)
const { getApplication } = await import('../../utils/sdk');
const application = await getApplication(balena, params.fleet, {
$select: ['id', 'app_name', 'slug'],
$expand: {
application_type: {
$select: ['is_legacy'],
$select: 'slug',
},
},
});
@ -91,8 +91,8 @@ export default class FleetRenameCmd extends Command {
}
// Check app supports renaming
const appType = (application.application_type as ApplicationType[])?.[0];
if (appType.is_legacy) {
const appType = application.application_type[0];
if (appType.slug === 'legacy-v1' || appType.slug === 'legacy-v2') {
throw new ExpectedError(
`Fleet ${params.fleet} is of 'legacy' type, and cannot be renamed.`,
);
@ -133,9 +133,9 @@ export default class FleetRenameCmd extends Command {
}
// Get application again, to be sure of results
const renamedApplication = await balena.models.application.get(
application.id,
);
const renamedApplication = await getApplication(balena, application.id, {
$select: ['app_name', 'slug'],
});
// Output result
console.log(`Fleet renamed`);

View File

@ -63,7 +63,9 @@ export default class FleetRestartCmd extends Command {
const balena = getBalenaSdk();
// Disambiguate application
const application = await getApplication(balena, params.fleet);
const application = await getApplication(balena, params.fleet, {
$select: 'slug',
});
await balena.models.application.restart(application.slug);
}

View File

@ -76,7 +76,9 @@ export default class FleetRmCmd extends Command {
);
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
const application = await getApplication(balena, params.fleet);
const application = await getApplication(balena, params.fleet, {
$select: 'slug',
});
// Remove
await balena.models.application.remove(application.slug);

View File

@ -88,6 +88,6 @@ export default class NoteCmd extends Command {
const balena = getBalenaSdk();
return balena.models.device.note(options.device!, params.note);
return balena.models.device.setNote(options.device, params.note);
}
}

View File

@ -46,7 +46,9 @@ export default class OrgsCmd extends Command {
const { getOwnOrganizations } = await import('../utils/sdk');
// Get organizations
const organizations = await getOwnOrganizations(getBalenaSdk());
const organizations = await getOwnOrganizations(getBalenaSdk(), {
$select: ['name', 'handle'],
});
// Display
console.log(

View File

@ -23,7 +23,11 @@ import Command from '../../command';
import { ExpectedError } from '../../errors';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { applicationIdInfo, devModeInfo } from '../../utils/messages';
import {
applicationIdInfo,
devModeInfo,
secureBootInfo,
} from '../../utils/messages';
const CONNECTIONS_FOLDER = '/system-connections';
@ -36,6 +40,7 @@ interface FlagsDef {
'config-wifi-key'?: string;
'config-wifi-ssid'?: string;
dev?: boolean; // balenaOS development variant
secureBoot?: boolean;
device?: string; // device UUID
'device-type'?: string;
help?: void;
@ -53,6 +58,7 @@ interface ArgsDef {
interface Answers {
appUpdatePollInterval: number; // in minutes
developmentMode?: boolean; // balenaOS development variant
secureBoot?: boolean;
deviceType: string; // e.g. "raspberrypi3"
network: 'ethernet' | 'wifi';
version: string; // e.g. "2.32.0+rev1"
@ -80,6 +86,8 @@ export default class OsConfigureCmd extends Command {
${devModeInfo.split('\n').join('\n\t\t')}
${secureBootInfo.split('\n').join('\n\t\t')}
The --system-connection (-c) option is used to inject NetworkManager connection
profiles for additional network interfaces, such as cellular/GSM or additional
WiFi or ethernet connections. This option may be passed multiple times in case there
@ -140,6 +148,7 @@ export default class OsConfigureCmd extends Command {
description: 'WiFi SSID (network name) (non-interactive configuration)',
}),
dev: cf.dev,
secureBoot: cf.secureBoot,
device: {
...cf.device,
exclusive: [
@ -238,6 +247,15 @@ export default class OsConfigureCmd extends Command {
const { validateDevOptionAndWarn } = await import('../../utils/config');
await validateDevOptionAndWarn(options.dev, osVersion);
const { validateSecureBootOptionAndWarn } = await import(
'../../utils/config'
);
await validateSecureBootOptionAndWarn(
options.secureBoot,
deviceTypeSlug,
osVersion,
);
const answers: Answers = await askQuestionsForDeviceType(
deviceTypeManifest,
options,
@ -248,6 +266,7 @@ export default class OsConfigureCmd extends Command {
}
answers.version = osVersion;
answers.developmentMode = options.dev;
answers.secureBoot = options.secureBoot;
answers.provisioningKeyName = options['provisioning-key-name'];
answers.provisioningKeyExpiryDate = options['provisioning-key-expiry-date'];
@ -362,7 +381,9 @@ async function getOsVersionFromImage(
*/
async function checkDeviceTypeCompatibility(
options: FlagsDef,
app: ApplicationWithDeviceType,
app: {
is_for__device_type: [Pick<BalenaSdk.DeviceType, 'slug'>];
},
) {
if (options['device-type']) {
const helpers = await import('../../utils/helpers');

View File

@ -31,7 +31,14 @@ import { parseAsInteger } from '../utils/validation';
import { flags } from '@oclif/command';
import * as _ from 'lodash';
import type { Application, BalenaSDK, PineExpand, Release } from 'balena-sdk';
import type {
Application,
BalenaSDK,
PineExpand,
PineOptions,
PineTypedResult,
Release,
} from 'balena-sdk';
import type { Preloader } from 'balena-preload';
interface FlagsDef extends DockerConnectionCliFlags {
@ -39,7 +46,7 @@ interface FlagsDef extends DockerConnectionCliFlags {
commit?: string;
'splash-image'?: string;
'dont-check-arch': boolean;
'pin-device-to-release': boolean;
'pin-device-to-release'?: boolean;
'additional-space'?: number;
'add-certificate'?: string[];
help: void;
@ -115,7 +122,7 @@ https://github.com/balena-io-examples/staged-releases\
'disable architecture compatibility check between image and fleet',
}),
'pin-device-to-release': flags.boolean({
default: false,
allowNo: true,
description:
'pin the preloaded device to the preloaded release on provision',
char: 'p',
@ -187,7 +194,7 @@ Can be repeated to add multiple certificates.\
: undefined;
const progressBars: {
[key: string]: ReturnType<typeof getVisuals>['Progress'];
[key: string]: InstanceType<ReturnType<typeof getVisuals>['Progress']>;
} = {};
const progressHandler = function (event: {
@ -201,7 +208,7 @@ Can be repeated to add multiple certificates.\
};
const spinners: {
[key: string]: ReturnType<typeof getVisuals>['Spinner'];
[key: string]: InstanceType<ReturnType<typeof getVisuals>['Spinner']>;
} = {};
const spinnerHandler = function (event: { name: string; action: string }) {
@ -223,7 +230,7 @@ Can be repeated to add multiple certificates.\
const splashImage = options['splash-image'];
const additionalSpace = options['additional-space'];
const dontCheckArch = options['dont-check-arch'] || false;
const pinDevice = options['pin-device-to-release'] || false;
const pinDevice = options['pin-device-to-release'];
if (dontCheckArch && !fleetSlug) {
throw new ExpectedError(
@ -242,7 +249,7 @@ Can be repeated to add multiple certificates.\
const dockerUtils = await import('../utils/docker');
const docker = await dockerUtils.getDocker(options);
const preloader = new balenaPreload.Preloader(
null,
undefined,
docker,
fleetSlug,
commit,
@ -250,7 +257,7 @@ Can be repeated to add multiple certificates.\
splashImage,
undefined, // TODO: Currently always undefined, investigate approach in ssh command.
dontCheckArch,
pinDevice,
pinDevice ?? false,
certificates,
additionalSpace,
);
@ -308,7 +315,7 @@ Can be repeated to add multiple certificates.\
}
}
readonly applicationExpandOptions: PineExpand<Application> = {
readonly applicationExpandOptions = {
owns__release: {
$select: ['id', 'commit', 'end_timestamp', 'composition'],
$expand: {
@ -329,7 +336,7 @@ Can be repeated to add multiple certificates.\
should_be_running__release: {
$select: 'commit',
},
};
} satisfies PineExpand<Application>;
isCurrentCommit(commit: string) {
return commit === 'latest' || commit === 'current';
@ -343,7 +350,7 @@ Can be repeated to add multiple certificates.\
} catch {
throw new Error(`Device type "${deviceTypeSlug}" not found in API query`);
}
return (await balena.models.application.getAllDirectlyAccessible({
const options = {
$select: ['id', 'slug', 'should_track_latest_release'],
$expand: this.applicationExpandOptions,
$filter: {
@ -388,11 +395,10 @@ Can be repeated to add multiple certificates.\
},
},
$orderby: 'slug asc',
})) as Array<
ApplicationWithDeviceType & {
should_be_running__release: [Release?];
}
>;
} satisfies PineOptions<Application>;
return (await balena.models.application.getAllDirectlyAccessible(
options,
)) as Array<PineTypedResult<Application, typeof options>>;
}
async selectApplication(deviceTypeSlug: string) {
@ -442,16 +448,16 @@ Can be repeated to add multiple certificates.\
}
async offerToDisableAutomaticUpdates(
application: Application,
application: Pick<Application, 'id' | 'should_track_latest_release'>,
commit: string,
pinDevice: boolean,
pinDevice: boolean | undefined,
) {
const balena = getBalenaSdk();
if (
this.isCurrentCommit(commit) ||
!application.should_track_latest_release ||
pinDevice
pinDevice != null
) {
return;
}
@ -470,8 +476,9 @@ through the web dashboard or programatically through the balena API / SDK.
Documentation about release policies and pinning can be found at:
https://www.balena.io/docs/learn/deploy/release-strategy/release-policy/
Alternatively, the --pin-device-to-release flag may be used to pin only the
preloaded device to the selected release.
Alternatively, the --pin-device-to-release or --no-pin-device-to-release flags may be used
to avoid this interactive confirmation and pin only the preloaded device to the selected release
or keep it unpinned respectively.
Would you like to disable automatic updates for this fleet now?\
`;
@ -494,9 +501,9 @@ Would you like to disable automatic updates for this fleet now?\
async getAppWithReleases(balenaSdk: BalenaSDK, slug: string) {
const { getApplication } = await import('../utils/sdk');
return (await getApplication(balenaSdk, slug, {
return await getApplication(balenaSdk, slug, {
$expand: this.applicationExpandOptions,
})) as Application & { should_be_running__release: [Release?] };
});
}
async prepareAndPreload(
@ -505,14 +512,14 @@ Would you like to disable automatic updates for this fleet now?\
options: {
slug?: string;
commit?: string;
pinDevice: boolean;
pinDevice?: boolean;
},
) {
await preloader.prepare();
const application = options.slug
? await this.getAppWithReleases(balenaSdk, options.slug)
: await this.selectApplication(preloader.config.deviceType);
: await this.selectApplication(preloader.config!.deviceType);
let commit: string; // commit hash or the strings 'latest' or 'current'

View File

@ -178,7 +178,7 @@ export default class PushCmd extends Command {
description: stripIndent`
Don't run a live session on this push. The filesystem will not be monitored,
and changes will not be synchronized to any running containers. Note that both
this flag and --detached and required to cause the process to end once the
this flag and --detached are required to cause the process to end once the
initial build has completed.`,
default: false,
}),
@ -328,7 +328,7 @@ export default class PushCmd extends Command {
]);
const application = await getApplication(sdk, appNameOrSlug, {
$select: ['app_name', 'slug'],
$select: 'slug',
});
const opts = {
@ -358,7 +358,7 @@ export default class PushCmd extends Command {
releaseTagValues,
);
if (options.note) {
await sdk.models.release.note(releaseId, options.note);
await sdk.models.release.setNote(releaseId, options.note);
}
} else if (releaseTagKeys.length > 0) {
throw new Error(stripIndent`

View File

@ -16,7 +16,6 @@
*/
import { flags } from '@oclif/command';
import type { LocalBalenaOsDevice } from 'balena-sync';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getCliUx, stripIndent } from '../utils/lazy';
@ -72,7 +71,7 @@ export default class ScanCmd extends Command {
public async run() {
const _ = await import('lodash');
const { discover } = await import('balena-sync');
const { discoverLocalBalenaOsDevices } = await import('../utils/discover');
const prettyjson = await import('prettyjson');
const dockerUtils = await import('../utils/docker');
@ -88,8 +87,7 @@ export default class ScanCmd extends Command {
const ux = getCliUx();
ux.action.start('Scanning for local balenaOS devices');
const localDevices: LocalBalenaOsDevice[] =
await discover.discoverLocalBalenaOsDevices(discoverTimeout);
const localDevices = await discoverLocalBalenaOsDevices(discoverTimeout);
const engineReachableDevices: boolean[] = await Promise.all(
localDevices.map(async ({ address }: { address: string }) => {
const docker = await dockerUtils.createClient({
@ -106,7 +104,7 @@ export default class ScanCmd extends Command {
}),
);
const developmentDevices: LocalBalenaOsDevice[] = localDevices.filter(
const developmentDevices = localDevices.filter(
(_localDevice, index) => engineReachableDevices[index],
);
@ -116,18 +114,15 @@ export default class ScanCmd extends Command {
_.isEqual,
);
const productionDevicesInfo = _.map(
productionDevices,
(device: LocalBalenaOsDevice) => {
return {
host: device.host,
address: device.address,
osVariant: 'production',
dockerInfo: undefined,
dockerVersion: undefined,
};
},
);
const productionDevicesInfo = productionDevices.map((device) => {
return {
host: device.host,
address: device.address,
osVariant: 'production',
dockerInfo: undefined,
dockerVersion: undefined,
};
});
// Query devices for info
const devicesInfo = await Promise.all(

View File

@ -149,7 +149,7 @@ export default class SupportCmd extends Command {
console.log(
`Access has been granted for ${duration}, expiring ${new Date(
expiryTs,
).toLocaleString()}`,
).toISOString()}`,
);
}
}

View File

@ -153,19 +153,19 @@ export default class TunnelCmd extends Command {
try {
await handler(client);
logConnection(
client.remoteAddress || '',
client.remotePort || 0,
client.localAddress,
client.localPort,
client.remoteAddress ?? '',
client.remotePort ?? 0,
client.localAddress ?? '',
client.localPort ?? 0,
uuid,
remotePort,
);
} catch (err) {
logConnection(
client.remoteAddress || '',
client.remotePort || 0,
client.localAddress,
client.localPort,
client.remoteAddress ?? '',
client.remotePort ?? 0,
client.localAddress ?? '',
client.localPort ?? 0,
uuid,
remotePort,
err,

View File

@ -86,7 +86,7 @@ export class DeprecationChecker {
* @param version Semver without 'v' prefix, e.g. '12.0.0.'
*/
protected getNpmUrl(version: string) {
return `http://registry.npmjs.org/balena-cli/${version}`;
return `https://registry.npmjs.org/balena-cli/${version}`;
}
/**

View File

@ -91,7 +91,7 @@ export default class BalenaHelp extends Help {
.map((pc) => {
return commands.find((c) => c.id === pc.replace(' ', ':'));
})
.filter((c): c is typeof commands[0] => !!c);
.filter((c): c is (typeof commands)[0] => !!c);
let usageLength = 0;
for (const cmd of primaryCommands) {

View File

@ -74,6 +74,12 @@ export const dev: IBooleanFlag<boolean> = flags.boolean({
default: false,
});
export const secureBoot: IBooleanFlag<boolean> = flags.boolean({
description:
'Configure balenaOS installer to opt-in secure boot and disk encryption',
default: false,
});
export const drive = flags.string({
char: 'd',
description: stripIndent`

View File

@ -52,10 +52,14 @@ export interface ImgConfig {
os?: {
sshKeys?: string[];
};
installer?: {
secureboot?: boolean;
};
}
export async function generateApplicationConfig(
application: BalenaSdk.Application,
application: Pick<BalenaSdk.Application, 'slug'>,
options: {
version: string;
appUpdatePollInterval?: number;
@ -63,6 +67,7 @@ export async function generateApplicationConfig(
os?: {
sshKeys?: string[];
};
secureBoot?: boolean;
},
): Promise<ImgConfig> {
options = {
@ -84,6 +89,12 @@ export async function generateApplicationConfig(
: options.os.sshKeys;
}
// configure installer secure boot opt-in if specified
if (options.secureBoot) {
config.installer ??= {};
config.installer.secureboot = options.secureBoot;
}
return config;
}
@ -165,3 +176,54 @@ export async function validateDevOptionAndWarn(
and exposes network ports such as 2375 that allows unencrypted access to balenaEngine.
Therefore, development mode should only be used in private, trusted local networks.`);
}
/**
* Chech whether the `--secureBoot` option of commands related to OS configuration
* such as `os configure` and `config generate` is compatible with a given
* OS release, and print a warning regarding the consequences of using that
* option.
*/
export async function validateSecureBootOptionAndWarn(
secureBoot?: boolean,
slug?: string,
version?: string,
logger?: import('./logger'),
) {
if (!secureBoot) {
return;
}
const { ExpectedError } = await import('../errors');
if (!version) {
throw new ExpectedError(`Error: No version provided`);
}
if (!slug) {
throw new ExpectedError(`Error: No device type provided`);
}
const sdk = getBalenaSdk();
const [osRelease] = await sdk.models.os.getAllOsVersions(slug, {
$select: 'contract',
$filter: { raw_version: `${version.replace(/^v/, '')}` },
});
if (!osRelease) {
throw new ExpectedError(`Error: No ${version} release for ${slug}`);
}
const contract = osRelease.contract ? JSON.parse(osRelease.contract) : null;
if (
contract?.provides.some((entry: Dictionary<string>) => {
return entry.type === 'sw.feature' && entry.slug === 'secureboot';
})
) {
if (!logger) {
const Logger = await import('./logger');
logger = Logger.getLogger();
}
logger.logInfo(stripIndent`
The '--secureBoot' option is being used to configure a balenaOS installer image
into secure boot and full disk encryption.`);
} else {
throw new ExpectedError(
`Error: The '--secureBoot' option is not supported for ${slug} in ${version}`,
);
}
}

View File

@ -44,7 +44,7 @@ import { displayBuildLog } from './logs';
import { stripIndent } from '../lazy';
const LOCAL_APPNAME = 'localapp';
const LOCAL_RELEASEHASH = 'localrelease';
const LOCAL_RELEASEHASH = '10ca12e1ea5e';
const LOCAL_PROJECT_NAME = 'local_image';
// Define the logger here so the debug output
@ -209,9 +209,9 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
globalLogger.logDebug('Fetching device information...');
const deviceInfo = await api.getDeviceInformation();
let buildLogs: Dictionary<string> | undefined;
let imageIds: Dictionary<string[]> | undefined;
if (!opts.nolive) {
buildLogs = {};
imageIds = {};
}
const { awaitInterruptibleTask } = await import('../helpers');
@ -223,7 +223,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
deviceInfo,
globalLogger,
opts,
buildLogs,
imageIds,
);
globalLogger.outputDeferredMessages();
@ -265,7 +265,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
docker,
logger: globalLogger,
composition: project.composition,
buildLogs: buildLogs!,
imageIds: imageIds!,
deployOpts: opts,
});
promises.push(livepush.init());
@ -312,6 +312,14 @@ function connectToDocker(host: string, port: number): Docker {
});
}
function extractDockerArrowMessage(outputLine: string): string | undefined {
const arrowTest = /^.*\s*-+>\s*(.+)/i;
const match = arrowTest.exec(outputLine);
if (match != null) {
return match[1];
}
}
async function performBuilds(
composition: Composition,
tarStream: Readable,
@ -319,7 +327,7 @@ async function performBuilds(
deviceInfo: DeviceInfo,
logger: Logger,
opts: DeviceDeployOptions,
buildLogs?: Dictionary<string>,
imageIds?: Dictionary<string[]>,
): Promise<BuildTask[]> {
const multibuild = await import('@balena/compose/dist/multibuild');
@ -345,14 +353,29 @@ async function performBuilds(
// If we're passed a build logs object make sure to set it
// up properly
let logHandlers: ((serviceName: string, line: string) => void) | undefined;
if (buildLogs != null) {
const lastArrowMessage: Dictionary<string> = {};
if (imageIds != null) {
for (const task of buildTasks) {
if (!task.external) {
buildLogs[task.serviceName] = '';
imageIds[task.serviceName] = [];
}
}
logHandlers = (serviceName: string, line: string) => {
buildLogs[serviceName] += `${line}\n`;
// If this was a from line, take the last found
// image id and save it
if (
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
lastArrowMessage[serviceName] != null
) {
imageIds[serviceName].push(lastArrowMessage[serviceName]);
} else {
const msg = extractDockerArrowMessage(line);
if (msg != null) {
lastArrowMessage[serviceName] = msg;
}
}
};
}
@ -413,12 +436,26 @@ export async function rebuildSingleTask(
// the logs, so any calller who wants to keep track of
// this should provide the following callback
containerIdCb?: (id: string) => void,
): Promise<string> {
): Promise<string[]> {
const multibuild = await import('@balena/compose/dist/multibuild');
// First we run the build task, to get the new image id
let buildLogs = '';
const stageIds = [] as string[];
let lastArrowMessage: string | undefined;
const logHandler = (_s: string, line: string) => {
buildLogs += `${line}\n`;
// If this was a FROM line, take the last found
// image id and save it as a stage id
if (
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
lastArrowMessage != null
) {
stageIds.push(lastArrowMessage);
} else {
const msg = extractDockerArrowMessage(line);
if (msg != null) {
lastArrowMessage = msg;
}
}
if (containerIdCb != null) {
const match = line.match(/^\s*--->\s*Running\s*in\s*([a-f0-9]*)\s*$/i);
@ -477,7 +514,7 @@ export async function rebuildSingleTask(
]);
}
return buildLogs;
return stageIds;
}
function assignOutputHandlers(
@ -551,7 +588,7 @@ async function assignDockerBuildOpts(
pull: opts.pull,
};
if (task.external) {
task.dockerOpts.authconfig = await getAuthConfigObj(
task.dockerOpts.authconfig = getAuthConfigObj(
task.imageName!,
opts.registrySecrets,
);

View File

@ -52,7 +52,6 @@ interface MonitoredContainer {
containerId: string;
}
type BuildLogs = Dictionary<string>;
type StageImageIDs = Dictionary<string[]>;
export interface LivepushOpts {
@ -62,7 +61,7 @@ export interface LivepushOpts {
docker: Dockerode;
api: DeviceAPI;
logger: Logger;
buildLogs: BuildLogs;
imageIds: StageImageIDs;
deployOpts: DeviceDeployOptions;
}
@ -97,7 +96,7 @@ export class LivepushManager {
this.api = opts.api;
this.logger = opts.logger;
this.deployOpts = opts.deployOpts;
this.imageIds = LivepushManager.getMultistageImageIDs(opts.buildLogs);
this.imageIds = opts.imageIds;
}
public async init(): Promise<void> {
@ -250,7 +249,7 @@ export class LivepushManager {
cwd: serviceContext,
followSymlinks: true,
ignoreInitial: true,
ignored: (filePath: string, stats: fs.Stats | undefined) => {
ignored: (filePath: string, stats?: fs.Stats) => {
if (!stats) {
try {
// sync because chokidar defines a sync interface
@ -297,33 +296,6 @@ export class LivepushManager {
return new Dockerfile(content).generateLiveDockerfile();
}
private static getMultistageImageIDs(buildLogs: BuildLogs): StageImageIDs {
const stageIds: StageImageIDs = {};
_.each(buildLogs, (log, serviceName) => {
stageIds[serviceName] = [];
const lines = log.split(/\r?\n/);
let lastArrowMessage: string | undefined;
for (const line of lines) {
// If this was a from line, take the last found
// image id and save it
if (
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
lastArrowMessage != null
) {
stageIds[serviceName].push(lastArrowMessage);
} else {
const msg = LivepushManager.extractDockerArrowMessage(line);
if (msg != null) {
lastArrowMessage = msg;
}
}
}
});
return stageIds;
}
private async awaitDeviceStateSettle(): Promise<void> {
// Cache the state to avoid unnecessary calls
this.lastDeviceStatus = await this.api.getStatus();
@ -405,9 +377,9 @@ export class LivepushManager {
);
}
let buildLog: string;
let stageImages: string[];
try {
buildLog = await rebuildSingleTask(
stageImages = await rebuildSingleTask(
serviceName,
this.docker,
this.logger,
@ -466,17 +438,13 @@ export class LivepushManager {
);
}
const buildLogs: Dictionary<string> = {};
buildLogs[serviceName] = buildLog;
const stageImages = LivepushManager.getMultistageImageIDs(buildLogs);
const dockerfile = new Dockerfile(buildTask.dockerfile!);
instance.livepush = await Livepush.init({
dockerfile,
context: buildTask.context!,
containerId: container.containerId,
stageImages: stageImages[serviceName],
stageImages,
docker: this.docker,
});
this.assignLivepushOutputHandlers(serviceName, instance.livepush);
@ -536,16 +504,6 @@ export class LivepushManager {
});
}
private static extractDockerArrowMessage(
outputLine: string,
): string | undefined {
const arrowTest = /^.*\s*-+>\s*(.+)/i;
const match = arrowTest.exec(outputLine);
if (match != null) {
return match[1];
}
}
private getDockerfilePathFromTask(task: BuildTask): string[] {
switch (task.projectType) {
case 'Standard Dockerfile':

View File

@ -155,12 +155,8 @@ export function displayLogObject<T extends Log>(
system: boolean,
filterServices?: string[],
): void {
let toPrint: string;
if (obj.timestamp != null) {
toPrint = `[${new Date(obj.timestamp).toLocaleString()}]`;
} else {
toPrint = `[${new Date().toLocaleString()}]`;
}
const d = obj.timestamp != null ? new Date(obj.timestamp) : new Date();
let toPrint = `[${d.toISOString()}]`;
if (obj.serviceName != null) {
if (filterServices) {

40
lib/utils/discover.ts Normal file
View File

@ -0,0 +1,40 @@
import { enumerateServices, findServices } from 'resin-discoverable-services';
interface LocalBalenaOsDevice {
address: string;
host: string;
osVariant?: string;
port: number;
}
// Although we only check for 'balena-ssh', we know, implicitly, that balenaOS
// devices come with 'rsync' installed that can be used over SSH.
const avahiBalenaSshTag = 'resin-ssh';
export async function discoverLocalBalenaOsDevices(
timeout = 4000,
): Promise<LocalBalenaOsDevice[]> {
const availableServices = await enumerateServices();
const serviceDefinitions = Array.from(availableServices)
.filter((s) => Array.from(s.tags).includes(avahiBalenaSshTag))
.map((s) => s.service);
if (serviceDefinitions.length === 0) {
throw new Error(
`Could not find any available '${avahiBalenaSshTag}' services`,
);
}
const services = await findServices(serviceDefinitions, timeout);
return services.map(function (service) {
// User referer address to get device IP. This will work fine assuming that
// a device only advertises own services.
const {
referer: { address },
host,
port,
} = service;
return { address, host, port };
});
}

View File

@ -119,7 +119,10 @@ export async function getManifest(
`The device type of the provided OS image ${manifest.slug}, does not match the expected device type ${deviceType}`,
);
}
return manifest ?? (await sdk.models.device.getManifestBySlug(deviceType));
return (
manifest ??
(await sdk.models.config.getDeviceTypeManifestBySlug(deviceType))
);
}
export const areDeviceTypesCompatible = async (
@ -137,7 +140,7 @@ export const areDeviceTypesCompatible = async (
$select: 'slug',
},
},
} as const;
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
const [appDeviceType, osDeviceType] = await Promise.all(
[appDeviceTypeSlug, osDeviceTypeSlug].map(
(dtSlug) =>
@ -177,14 +180,13 @@ export async function osProgressHandler(step: InitializeEmitter) {
});
}
export async function getAppWithArch(
applicationName: string,
): Promise<ApplicationWithDeviceType & { arch: string }> {
export async function getAppWithArch(applicationName: string) {
const { getApplication } = await import('./sdk');
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
const balena = getBalenaSdk();
const app = await getApplication(balena, applicationName, {
$expand: {
application_type: {
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
$select: ['name', 'slug', 'supports_multicontainer'],
},
is_for__device_type: {
$select: 'slug',
@ -195,20 +197,10 @@ export async function getAppWithArch(
},
},
},
};
const balena = getBalenaSdk();
const app = (await getApplication(
balena,
applicationName,
options,
)) as ApplicationWithDeviceType;
const { getExpanded } = await import('./pine');
});
return {
...app,
arch: getExpanded(
getExpanded(app.is_for__device_type)!.is_of__cpu_architecture,
)!.slug,
arch: app.is_for__device_type[0].is_of__cpu_architecture[0].slug,
};
}
@ -439,25 +431,11 @@ export function getProxyConfig(): ProxyConfig | undefined {
export const expandForAppName = {
$expand: {
belongs_to__application: { $select: ['app_name', 'slug'] as any },
belongs_to__application: { $select: ['app_name', 'slug'] },
is_of__device_type: { $select: 'slug' },
is_running__release: { $select: 'commit' },
},
} as const;
export const expandForAppNameAndCpuArch = {
$expand: {
...expandForAppName.$expand,
is_of__device_type: {
$select: 'slug',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
},
},
} as const;
} satisfies BalenaSdk.PineOptions<BalenaSdk.Device>;
/**
* Use the `readline` library on Windows to install SIGINT handlers.

View File

@ -169,6 +169,10 @@ confuse the balenaOS "development mode" with a device's "local mode", the latter
being a supervisor feature that allows the "balena push" command to push a user's
application directly to a device in the local network.`;
export const secureBootInfo = `\
The '--secureBoot' option is used to configure a balenaOS installer image to opt-in
secure boot and disk encryption.`;
export const jsonInfo = `\
The --json option is recommended when scripting the output of this command,
because field names are less likely to change in JSON format and because it

View File

@ -14,8 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { Application, BalenaSDK, Device, Organization } from 'balena-sdk';
import _ = require('lodash');
import type {
Application,
BalenaSDK,
Device,
Organization,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
@ -108,22 +114,22 @@ export function askLoginType() {
});
}
export function selectDeviceType() {
return getBalenaSdk()
.models.config.getDeviceTypes()
.then((deviceTypes) => {
deviceTypes = _.sortBy(deviceTypes, 'name').filter(
(dt) => dt.state !== 'DISCONTINUED',
);
return getCliForm().ask({
message: 'Device Type',
type: 'list',
choices: _.map(deviceTypes, ({ slug: value, name }) => ({
name,
value,
})),
});
});
export async function selectDeviceType() {
const sdk = getBalenaSdk();
let deviceTypes = await sdk.models.deviceType.getAllSupported();
if (deviceTypes.length === 0) {
// Without this open-balena users would get an empty list
// until we add a hostApps import in open-balena.
deviceTypes = await sdk.models.deviceType.getAll();
}
return getCliForm().ask({
message: 'Device Type',
type: 'list',
choices: deviceTypes.map(({ slug: value, name }) => ({
name,
value,
})),
});
}
/**
@ -157,46 +163,57 @@ export async function confirm(
}
}
export function selectApplication(
filter?: (app: ApplicationWithDeviceType) => boolean,
const selectApplicationPineOptions = {
$select: ['id', 'slug', 'app_name'],
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
} satisfies PineOptions<Application>;
type SelectApplicationResult = PineTypedResult<
Application,
typeof selectApplicationPineOptions
>;
export async function selectApplication(
filter?: (app: SelectApplicationResult) => boolean,
errorOnEmptySelection = false,
) {
const balena = getBalenaSdk();
return balena.models.application
.hasAny()
.then(async (hasAnyApplications) => {
if (!hasAnyApplications) {
throw new ExpectedError('No fleets found');
}
let apps = (await balena.models.application.getAllDirectlyAccessible(
selectApplicationPineOptions,
)) as SelectApplicationResult[];
const apps = (await balena.models.application.getAllDirectlyAccessible({
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
})) as ApplicationWithDeviceType[];
return apps.filter(filter || _.constant(true));
})
.then((applications) => {
if (errorOnEmptySelection && applications.length === 0) {
throw new ExpectedError('No suitable fleets found for selection');
}
return getCliForm().ask({
message: 'Select an application',
type: 'list',
choices: _.map(applications, (application) => ({
name: `${application.app_name} (${application.slug}) [${application.is_for__device_type[0].slug}]`,
value: application,
})),
});
});
if (!apps.length) {
throw new ExpectedError('No fleets found');
}
if (filter != null) {
apps = apps.filter(filter);
}
if (errorOnEmptySelection && apps.length === 0) {
throw new ExpectedError('No suitable fleets found for selection');
}
return getCliForm().ask({
message: 'Select an application',
type: 'list',
choices: apps.map((application) => ({
name: `${application.app_name} (${application.slug}) [${application.is_for__device_type[0].slug}]`,
value: application,
})),
});
}
export async function selectOrganization(organizations?: Organization[]) {
export async function selectOrganization(
organizations?: Array<Pick<Organization, 'handle' | 'name'>>,
) {
// Use either provided orgs (if e.g. already loaded) or load from cloud
organizations =
organizations || (await getBalenaSdk().models.organization.getAll());
organizations ??= await getBalenaSdk().models.organization.getAll({
$select: ['name', 'handle'],
});
return getCliForm().ask({
message: 'Select an organization',
type: 'list',
@ -252,33 +269,6 @@ export async function awaitDeviceOsUpdate(
return uuid;
}
export function inferOrSelectDevice(preferredUuid: string) {
const balena = getBalenaSdk();
return balena.models.device.getAll().then((devices) => {
const onlineDevices = devices.filter((device) => device.is_online);
if (_.isEmpty(onlineDevices)) {
throw new ExpectedError("You don't have any devices online");
}
const defaultUuid = _(onlineDevices).map('uuid').includes(preferredUuid)
? preferredUuid
: onlineDevices[0].uuid;
return getCliForm().ask({
message: 'Select a device',
type: 'list',
default: defaultUuid,
choices: _.map(onlineDevices, (device) => ({
name: `${device.device_name || 'Untitled'} (${device.uuid.slice(
0,
7,
)})`,
value: device.uuid,
})),
});
});
}
/*
* Given fleetOrDevice, which may be
* - a fleet name
@ -322,28 +312,34 @@ export async function getOnlineTargetDeviceUuid(
}
// Not a device UUID, try application
let application: Application;
try {
logger.logDebug(`Fetching fleet ${fleetOrDevice}`);
const { getApplication } = await import('./sdk');
application = await getApplication(sdk, fleetOrDevice);
} catch (err) {
const { BalenaApplicationNotFound } = await import('balena-errors');
if (instanceOf(err, BalenaApplicationNotFound)) {
throw new ExpectedError(`Fleet or Device not found: ${fleetOrDevice}`);
} else {
throw err;
const application = await (async () => {
try {
logger.logDebug(`Fetching fleet ${fleetOrDevice}`);
const { getApplication } = await import('./sdk');
return await getApplication(sdk, fleetOrDevice, {
$select: ['id', 'slug'],
$expand: {
owns__device: {
$select: ['device_name', 'uuid'],
$filter: { is_online: true },
},
},
});
} catch (err) {
const { BalenaApplicationNotFound } = await import('balena-errors');
if (instanceOf(err, BalenaApplicationNotFound)) {
throw new ExpectedError(`Fleet or Device not found: ${fleetOrDevice}`);
} else {
throw err;
}
}
}
})();
// App found, load its devices
const devices = await sdk.models.device.getAllByApplication(application.id, {
$select: ['device_name', 'uuid'],
$filter: { is_online: true },
});
const devices = application.owns__device;
// Throw if no devices online
if (_.isEmpty(devices)) {
if (!devices.length) {
throw new ExpectedError(
`Fleet ${application.slug} found, but has no devices online.`,
);
@ -354,7 +350,7 @@ export async function getOnlineTargetDeviceUuid(
message: `Select a device on fleet ${application.slug}`,
type: 'list',
default: devices[0].uuid,
choices: _.map(devices, (device) => ({
choices: devices.map((device) => ({
name: `${device.device_name || 'Untitled'} (${device.uuid.slice(0, 7)})`,
value: device.uuid,
})),
@ -368,7 +364,7 @@ export function selectFromList<T>(
return getCliForm().ask<T>({
message,
type: 'list',
choices: _.map(choices, (s) => ({
choices: choices.map((s) => ({
name: s.name,
value: s,
})),

View File

@ -16,10 +16,10 @@ limitations under the License.
import type { OptionalNavigationResource } from 'balena-sdk';
export const getExpanded = <T>(obj: OptionalNavigationResource<T>) =>
export const getExpanded = <T extends {}>(obj: OptionalNavigationResource<T>) =>
(Array.isArray(obj) && obj[0]) || undefined;
export const getExpandedProp = <T, K extends keyof T>(
export const getExpandedProp = <T extends {}, K extends keyof T>(
obj: OptionalNavigationResource<T>,
key: K,
) => (Array.isArray(obj) && obj[0] && obj[0][key]) || undefined;

View File

@ -163,12 +163,61 @@ async function getOsVersion(deviceIp: string): Promise<string> {
return match[1];
}
const dockerPort = 2375;
const dockerTimeout = 2000;
async function selectLocalBalenaOsDevice(timeout = 4000): Promise<string> {
const { discoverLocalBalenaOsDevices } = await import('../utils/discover');
const { SpinnerPromise } = getVisuals();
const devices = await new SpinnerPromise({
promise: discoverLocalBalenaOsDevices(timeout),
startMessage: 'Discovering local balenaOS devices..',
stopMessage: 'Reporting discovered devices',
});
const responsiveDevices: typeof devices = [];
const Docker = await import('dockerode');
await Promise.all(
devices.map(async function (device) {
const address = device?.address;
if (!address) {
return;
}
try {
const docker = new Docker({
host: address,
port: dockerPort,
timeout: dockerTimeout,
});
await docker.ping();
responsiveDevices.push(device);
} catch {
return;
}
}),
);
if (!responsiveDevices.length) {
throw new Error('Could not find any local balenaOS devices');
}
return getCliForm().ask({
message: 'select a device',
type: 'list',
default: devices[0].address,
choices: responsiveDevices.map((device) => ({
name: `${device.host || 'untitled'} (${device.address})`,
value: device.address,
})),
});
}
async function selectLocalDevice(): Promise<string> {
const { forms } = await import('balena-sync');
let hostnameOrIp;
try {
hostnameOrIp = await forms.selectLocalBalenaOsDevice();
const hostnameOrIp = await selectLocalBalenaOsDevice();
console.error(`==> Selected device: ${hostnameOrIp}`);
return hostnameOrIp;
} catch (e) {
if (e.message.toLowerCase().includes('could not find any')) {
throw new ExpectedError(e);
@ -176,8 +225,6 @@ async function selectLocalDevice(): Promise<string> {
throw e;
}
}
return hostnameOrIp;
}
async function selectAppFromList(
@ -208,7 +255,7 @@ async function getOrSelectApplication(
$select: 'slug',
},
},
} as const;
} satisfies BalenaSdk.PineOptions<BalenaSdk.DeviceType>;
const [deviceType, allDeviceTypes] = await Promise.all([
sdk.models.deviceType.get(deviceTypeSlug, pineOptions) as Promise<
BalenaSdk.PineTypedResult<BalenaSdk.DeviceType, typeof pineOptions>
@ -380,7 +427,7 @@ async function generateApplicationConfig(
) {
const { generateApplicationConfig: configGen } = await import('./config');
const manifest = await sdk.models.device.getManifestBySlug(
const manifest = await sdk.models.config.getDeviceTypeManifestBySlug(
app.is_for__device_type[0].slug,
);
const opts =

View File

@ -20,8 +20,18 @@ import type {
BalenaSDK,
Organization,
PineOptions,
PineTypedResult,
} from 'balena-sdk';
export async function getApplication(
sdk: BalenaSDK,
nameOrSlugOrId: string | number,
): Promise<Application>;
export async function getApplication<TP extends PineOptions<Application>>(
sdk: BalenaSDK,
nameOrSlugOrId: string | number,
options?: TP,
): Promise<PineTypedResult<Application, TP>>;
/**
* Get a fleet object, disambiguating the fleet identifier which may be a
* a fleet slug or name.
@ -29,21 +39,24 @@ import type {
*/
export async function getApplication(
sdk: BalenaSDK,
nameOrSlug: string,
nameOrSlugOrId: string | number,
options?: PineOptions<Application>,
): Promise<Application> {
const { looksLikeFleetSlug } = await import('./validation');
if (!looksLikeFleetSlug(nameOrSlug)) {
if (
typeof nameOrSlugOrId === 'string' &&
!looksLikeFleetSlug(nameOrSlugOrId)
) {
// Not a slug: must be an app name.
// TODO: revisit this logic when we add support for fleet UUIDs.
return await sdk.models.application.getAppByName(
nameOrSlug,
nameOrSlugOrId,
options,
'directly_accessible',
);
}
return await sdk.models.application.getDirectlyAccessible(
nameOrSlug,
nameOrSlugOrId,
options,
);
}
@ -63,31 +76,44 @@ export async function getFleetSlug(
if (!looksLikeFleetSlug(nameOrSlug)) {
// Not a slug: must be an app name.
// TODO: revisit this logic when we add support for fleet UUIDs.
return (await getApplication(sdk, nameOrSlug)).slug;
return (await getApplication(sdk, nameOrSlug, { $select: 'slug' })).slug;
}
return nameOrSlug.toLowerCase();
}
export async function getOwnOrganizations(
sdk: BalenaSDK,
): Promise<Organization[]>;
export async function getOwnOrganizations<TP extends PineOptions<Organization>>(
sdk: BalenaSDK,
options: TP,
): Promise<Array<PineTypedResult<Organization, TP>>>;
/**
* Wraps the sdk organization.getAll method,
* restricting to those orgs user is a member of
*/
export async function getOwnOrganizations(
sdk: BalenaSDK,
options?: PineOptions<Organization>,
): Promise<Organization[]> {
return await sdk.models.organization.getAll({
$filter: {
organization_membership: {
$any: {
$alias: 'orm',
$expr: {
orm: {
user: await sdk.auth.getUserId(),
return await sdk.models.organization.getAll(
sdk.utils.mergePineOptions(
{
$filter: {
organization_membership: {
$any: {
$alias: 'orm',
$expr: {
orm: {
user: await sdk.auth.getUserId(),
},
},
},
},
},
$orderby: 'name asc',
},
},
$orderby: 'name asc',
});
options,
),
);
}

View File

@ -151,23 +151,23 @@ export async function runRemoteCommand({
let exitCode: number | undefined;
let exitSignal: NodeJS.Signals | undefined;
try {
[exitCode, exitSignal] = await new Promise<[number, NodeJS.Signals]>(
(resolve, reject) => {
const ps = spawn(program, args, { stdio })
.on('error', reject)
.on('close', (code, signal) => resolve([code, signal]));
[exitCode, exitSignal] = await new Promise((resolve, reject) => {
const ps = spawn(program, args, { stdio })
.on('error', reject)
.on('close', (code, signal) =>
resolve([code ?? undefined, signal ?? undefined]),
);
if (ps.stdin && stdin && typeof stdin !== 'string') {
stdin.pipe(ps.stdin);
}
if (ps.stdout && stdout && typeof stdout !== 'string') {
ps.stdout.pipe(stdout);
}
if (ps.stderr && stderr && typeof stderr !== 'string') {
ps.stderr.pipe(stderr);
}
},
);
if (ps.stdin && stdin && typeof stdin !== 'string') {
stdin.pipe(ps.stdin);
}
if (ps.stdout && stdout && typeof stdout !== 'string') {
ps.stdout.pipe(stdout);
}
if (ps.stderr && stderr && typeof stderr !== 'string') {
ps.stderr.pipe(stderr);
}
});
} catch (error) {
const msg = [
`ssh failed with exit code=${exitCode} signal=${exitSignal}:`,

View File

@ -19,8 +19,10 @@ import * as UpdateNotifier from 'update-notifier';
import packageJSON = require('../../package.json');
// Check for an update once a day. 1 day granularity should be
// enough, rather than every run.
// Check for an update at most once a day. 1 day granularity should be
// enough, rather than every run. Note because we show the information
// from the *last* time we ran, if the cli has not been run for a while
// the update info can be out of date.
const balenaUpdateInterval = 1000 * 60 * 60 * 24 * 1;
let notifier: UpdateNotifier.UpdateNotifier;

28872
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "14.5.15",
"version": "16.7.0",
"description": "The official balena Command Line Interface",
"main": "./build/app.js",
"homepage": "https://github.com/balena-io/balena-cli",
@ -27,7 +27,6 @@
"scripts": [
"build/**/*.js",
"node_modules/balena-sdk/es2018/index.js",
"node_modules/balena-sync/build/**/*.js",
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
"node_modules/@balena/compose/dist/parse/schemas/*.json"
],
@ -90,7 +89,7 @@
"author": "Balena Inc. (https://balena.io/)",
"license": "Apache-2.0",
"engines": {
"node": ">=12 <16"
"node": ">=16 <18"
},
"husky": {
"hooks": {
@ -114,7 +113,8 @@
]
},
"devDependencies": {
"@balena/lint": "^6.2.0",
"@balena/lint": "^6.2.2",
"@electron/notarize": "^2.0.0",
"@oclif/config": "^1.18.2",
"@oclif/parser": "^3.8.6",
"@octokit/plugin-throttling": "^3.5.1",
@ -133,6 +133,7 @@
"@types/global-agent": "^2.1.1",
"@types/global-tunnel-ng": "^2.1.1",
"@types/http-proxy": "^1.17.8",
"@types/inquirer": "^7.3.3",
"@types/intercept-stdout": "^0.1.0",
"@types/is-root": "^2.1.2",
"@types/js-yaml": "^4.0.5",
@ -146,7 +147,7 @@
"@types/ndjson": "^2.0.1",
"@types/net-keepalive": "^0.4.1",
"@types/nock": "^11.1.0",
"@types/node": "^12.20.42",
"@types/node": "^16.18.25",
"@types/node-cleanup": "^2.1.2",
"@types/parse-link-header": "^1.0.1",
"@types/prettyjson": "^0.0.30",
@ -170,7 +171,6 @@
"cross-env": "^7.0.3",
"deep-object-diff": "^1.1.0",
"diff": "^5.0.0",
"electron-notarize": "^1.0.0",
"ent": "^2.2.0",
"filehound": "^1.17.5",
"fs-extra": "^9.1.0",
@ -184,16 +184,16 @@
"mock-require": "^3.0.3",
"nock": "^13.2.1",
"parse-link-header": "^2.0.0",
"pkg": "^5.5.1",
"pkg": "^5.8.1",
"publish-release": "^1.6.1",
"rewire": "^5.0.0",
"simple-git": "^3.14.1",
"sinon": "^11.1.2",
"ts-node": "^10.4.0",
"typescript": "^4.6.4"
"typescript": "^5.1.3"
},
"dependencies": {
"@balena/compose": "^2.1.1",
"@balena/compose": "^2.3.0",
"@balena/dockerignore": "^1.0.2",
"@balena/es-version": "^1.0.1",
"@oclif/command": "^1.8.16",
@ -201,18 +201,16 @@
"@sentry/node": "^6.16.1",
"@types/fast-levenshtein": "0.0.1",
"@types/update-notifier": "^4.1.1",
"JSONStream": "^1.0.3",
"balena-config-json": "^4.2.0",
"balena-device-init": "^6.0.0",
"balena-errors": "^4.7.1",
"balena-errors": "^4.7.3",
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^8.0.0",
"balena-preload": "^12.2.0",
"balena-sdk": "^16.28.0",
"balena-image-manager": "^9.0.0",
"balena-preload": "^14.0.0",
"balena-sdk": "^17.0.0",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.7",
"balena-settings-client": "^5.0.2",
"balena-settings-storage": "^7.0.0",
"balena-sync": "^11.0.2",
"bluebird": "^3.7.2",
"body-parser": "^1.19.1",
"chalk": "^3.0.0",
@ -225,9 +223,9 @@
"denymount": "^2.3.0",
"docker-modem": "3.0.0",
"docker-progress": "^5.1.3",
"dockerode": "^3.3.1",
"dockerode": "3.3.3",
"ejs": "^3.1.6",
"etcher-sdk": "^6.2.1",
"etcher-sdk": "^8.5.3",
"event-stream": "3.3.4",
"express": "^4.17.2",
"fast-boot2": "^1.1.0",
@ -243,6 +241,7 @@
"is-elevated": "^3.0.0",
"is-root": "^2.1.0",
"js-yaml": "^4.1.0",
"JSONStream": "^1.0.3",
"klaw": "^3.0.0",
"livepush": "^3.5.1",
"lodash": "^4.17.21",
@ -261,7 +260,8 @@
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
"request": "^2.88.2",
"resin-cli-form": "^2.0.2",
"resin-cli-visuals": "^1.8.0",
"resin-cli-visuals": "^1.8.3",
"resin-discoverable-services": "^2.0.4",
"resin-doodles": "^0.2.0",
"resin-stream-logger": "^0.1.2",
"rimraf": "^3.0.2",
@ -284,6 +284,6 @@
"windosu": "^0.3.0"
},
"versionist": {
"publishedAt": "2022-12-12T13:41:12.779Z"
"publishedAt": "2023-07-17T19:59:50.432Z"
}
}

View File

@ -1,24 +1,24 @@
diff --git a/node_modules/@oclif/parser/lib/errors.js b/node_modules/@oclif/parser/lib/errors.js
index 0c93a81..95d06be 100644
index 39936e3..23e3925 100644
--- a/node_modules/@oclif/parser/lib/errors.js
+++ b/node_modules/@oclif/parser/lib/errors.js
@@ -13,7 +13,8 @@ const m = deps_1.default()
@@ -14,7 +14,8 @@ const m = deps_1.default()
.add('list', () => require('./list'));
class CLIParseError extends errors_1.CLIError {
constructor(options) {
- options.message += '\nSee more help with --help';
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
+ options.message += `\nSee more help with ${help}`;
+ const help = options.command ? `\`${options.command} --help\`` : '--help';
+ options.message += `\nSee more help with ${help}`;
super(options.message);
this.parse = options.parse;
}
@@ -34,22 +35,24 @@ class InvalidArgsSpecError extends CLIParseError {
@@ -35,22 +36,24 @@ class InvalidArgsSpecError extends CLIParseError {
exports.InvalidArgsSpecError = InvalidArgsSpecError;
class RequiredArgsError extends CLIParseError {
constructor({ args, parse }) {
- let message = `Missing ${args.length} required arg${args.length === 1 ? '' : 's'}`;
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
+ let message = `Missing ${args.length} required argument${args.length === 1 ? '' : 's'}`;
const namedArgs = args.filter(a => a.name);
if (namedArgs.length > 0) {
const list = m.list.renderList(namedArgs.map(a => [a.name, a.description]));
@ -32,7 +32,7 @@ index 0c93a81..95d06be 100644
exports.RequiredArgsError = RequiredArgsError;
class RequiredFlagError extends CLIParseError {
constructor({ flag, parse }) {
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
+ const command = 'balena ' + parse.input.context.id.replace(/:/g, ' ');
const usage = m.list.renderList(m.help.flagUsages([flag], { displayRequired: false }));
const message = `Missing required flag:\n${usage}`;
- super({ parse, message });
@ -41,15 +41,15 @@ index 0c93a81..95d06be 100644
}
}
diff --git a/node_modules/@oclif/parser/lib/list.js b/node_modules/@oclif/parser/lib/list.js
index 3907cc0..b689ca1 100644
index 9d020b7..6ea9eb9 100644
--- a/node_modules/@oclif/parser/lib/list.js
+++ b/node_modules/@oclif/parser/lib/list.js
@@ -21,7 +21,7 @@ function renderList(items) {
@@ -22,7 +22,7 @@ function renderList(items) {
}
left = left.padEnd(maxLength);
right = linewrap(maxLength + 2, right);
- return `${left} ${right}`;
+ return `${left} : ${right}`;
+ return `${left} : ${right}`;
});
return lines.join('\n');
}

View File

@ -1,15 +1,16 @@
diff --git a/node_modules/open/index.js b/node_modules/open/index.js
index 3bf5373..e042b64 100644
index 13147d0..ff161dd 100644
--- a/node_modules/open/index.js
+++ b/node_modules/open/index.js
@@ -11,7 +11,9 @@ const pAccess = promisify(fs.access);
const pExecFile = promisify(childProcess.execFile);
@@ -10,7 +10,10 @@ const pAccess = promisify(fs.access);
const pReadFile = promisify(fs.readFile);
// Path to included `xdg-open`.
-const localXdgOpenPath = path.join(__dirname, 'xdg-open');
+const localXdgOpenPath = process.pkg
+ ? path.join(path.dirname(process.execPath), 'xdg-open')
+ : path.join(__dirname, 'xdg-open');
+
// Convert a path from WSL format to Windows format:
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
/**
Get the mount point for fixed drives in WSL.

View File

@ -1,8 +1,8 @@
diff --git a/node_modules/node-gyp-build/index.js b/node_modules/node-gyp-build/index.js
index b5096ed..7cd451a 100644
--- a/node_modules/node-gyp-build/index.js
+++ b/node_modules/node-gyp-build/index.js
@@ -29,6 +29,9 @@ load.path = function (dir) {
diff --git a/node_modules/node-gyp-build/node-gyp-build.js b/node_modules/node-gyp-build/node-gyp-build.js
index 61b398e..3cc3be8 100644
--- a/node_modules/node-gyp-build/node-gyp-build.js
+++ b/node_modules/node-gyp-build/node-gyp-build.js
@@ -30,6 +30,9 @@ load.resolve = load.path = function (dir) {
if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD']
} catch (err) {}

View File

@ -10,8 +10,6 @@ upstream:
url: 'https://github.com/balena-io-modules/balena-image-manager'
- repo: 'balena-preload'
url: 'https://github.com/balena-io-modules/balena-preload'
- repo: 'balena-sync'
url: 'https://github.com/balena-io-modules/balena-sync'
- repo: 'etcher-sdk'
url: 'https://github.com/balena-io-modules/etcher-sdk/'
- repo: 'balena-compose'

View File

@ -38,7 +38,7 @@ describe('balena devices', function () {
it('should list devices from own and collaborator apps', async () => {
api.scope
.get(
'/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name,slug),is_of__device_type($select=slug),is_running__release($select=commit)',
'/v6/device?$orderby=device_name%20asc&$select=id,uuid,device_name,status,is_online,supervisor_version,os_version&$expand=belongs_to__application($select=app_name,slug),is_of__device_type($select=slug),is_running__release($select=commit)',
)
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
'Content-Type': 'application/json',

View File

@ -138,6 +138,7 @@ describe('balena envs', function () {
});
it('should successfully list env variables for a test device', async () => {
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceEnvVars();
api.expectGetApplication();
@ -145,20 +146,19 @@ describe('balena envs', function () {
api.expectGetAppServiceVars();
api.expectGetDeviceServiceVars();
const uuid = shortUUID;
const result = await runCommand(`envs -d ${uuid}`);
const result = await runCommand(`envs -d ${shortUUID}`);
let { out } = result;
let expected =
stripIndent`
ID NAME VALUE FLEET DEVICE SERVICE
120110 svar1 svar1-value org/test * service1
120111 svar2 svar2-value org/test * service2
120120 svar3 svar3-value org/test ${uuid} service1
120121 svar4 svar4-value org/test ${uuid} service2
120120 svar3 svar3-value org/test ${shortUUID} service1
120121 svar4 svar4-value org/test ${shortUUID} service2
120101 var1 var1-val org/test * *
120102 var2 22 org/test * *
120203 var3 var3-val org/test ${uuid} *
120204 var4 44 org/test ${uuid} *
120203 var3 var3-val org/test ${shortUUID} *
120204 var4 44 org/test ${shortUUID} *
` + '\n';
out = out.map((l) => l.replace(/ +/g, ' '));
@ -168,6 +168,7 @@ describe('balena envs', function () {
});
it('should successfully list env variables for a test device (JSON output)', async () => {
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceEnvVars();
api.expectGetApplication();
@ -192,6 +193,7 @@ describe('balena envs', function () {
});
it('should successfully list config variables for a test device', async () => {
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceConfigVars();
api.expectGetApplication();
@ -216,24 +218,24 @@ describe('balena envs', function () {
const serviceName = 'service2';
api.expectGetService({ serviceName });
api.expectGetApplication();
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceServiceVars();
api.expectGetAppEnvVars();
api.expectGetAppServiceVars();
api.expectGetDeviceEnvVars();
const uuid = shortUUID;
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
const result = await runCommand(`envs -d ${shortUUID} -s ${serviceName}`);
let { out } = result;
let expected =
stripIndent`
ID NAME VALUE FLEET DEVICE SERVICE
120111 svar2 svar2-value org/test * service2
120121 svar4 svar4-value org/test ${uuid} service2
120121 svar4 svar4-value org/test ${shortUUID} service2
120101 var1 var1-val org/test * *
120102 var2 22 org/test * *
120203 var3 var3-val org/test ${uuid} *
120204 var4 44 org/test ${uuid} *
120203 var3 var3-val org/test ${shortUUID} *
120204 var4 44 org/test ${shortUUID} *
` + '\n';
out = out.map((l) => l.replace(/ +/g, ' '));
@ -243,20 +245,20 @@ describe('balena envs', function () {
});
it('should successfully list env and service variables for a test device (unknown fleet)', async () => {
api.expectGetDevice({ shortUUID, fullUUID, inaccessibleApp: true });
api.expectGetDevice({ fullUUID, inaccessibleApp: true });
api.expectGetDeviceEnvVars();
api.expectGetDeviceServiceVars();
const uuid = shortUUID;
const result = await runCommand(`envs -d ${uuid}`);
const result = await runCommand(`envs -d ${shortUUID}`);
let { out } = result;
let expected =
stripIndent`
ID NAME VALUE FLEET DEVICE SERVICE
120120 svar3 svar3-value N/A ${uuid} service1
120121 svar4 svar4-value N/A ${uuid} service2
120203 var3 var3-val N/A ${uuid} *
120204 var4 44 N/A ${uuid} *
120120 svar3 svar3-value N/A ${shortUUID} service1
120121 svar4 svar4-value N/A ${shortUUID} service2
120203 var3 var3-val N/A ${shortUUID} *
120204 var4 44 N/A ${shortUUID} *
` + '\n';
out = out.map((l) => l.replace(/ +/g, ' '));
@ -271,22 +273,22 @@ describe('balena envs', function () {
api.expectGetApplication();
api.expectGetAppEnvVars();
api.expectGetAppServiceVars();
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceEnvVars();
api.expectGetDeviceServiceVars();
const uuid = shortUUID;
const result = await runCommand(`envs -d ${uuid} -s ${serviceName}`);
const result = await runCommand(`envs -d ${shortUUID} -s ${serviceName}`);
let { out } = result;
let expected =
stripIndent`
ID NAME VALUE FLEET DEVICE SERVICE
120110 svar1 svar1-value org/test * ${serviceName}
120120 svar3 svar3-value org/test ${uuid} ${serviceName}
120120 svar3 svar3-value org/test ${shortUUID} ${serviceName}
120101 var1 var1-val org/test * *
120102 var2 22 org/test * *
120203 var3 var3-val org/test ${uuid} *
120204 var4 44 org/test ${uuid} *
120203 var3 var3-val org/test ${shortUUID} *
120204 var4 44 org/test ${shortUUID} *
` + '\n';
out = out.map((l) => l.replace(/ +/g, ' '));
@ -301,6 +303,7 @@ describe('balena envs', function () {
api.expectGetApplication();
api.expectGetAppEnvVars();
api.expectGetAppServiceVars();
api.expectGetDevice({ shortUUID, fullUUID });
api.expectGetDevice({ fullUUID });
api.expectGetDeviceEnvVars();
api.expectGetDeviceServiceVars();

View File

@ -178,7 +178,7 @@ async function startMockSshServer(): Promise<[Server, number]> {
});
return await new Promise<[Server, number]>((resolve, reject) => {
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
// callback type is `() => void`, but our code assumes `(err: Error) => void`
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
// this callback is called for the 'listening' event

View File

@ -212,13 +212,20 @@ export class BalenaAPIMock extends NockMock {
public expectGetDevice(opts: {
fullUUID: string;
shortUUID?: string;
inaccessibleApp?: boolean;
isOnline?: boolean;
optional?: boolean;
persist?: boolean;
}) {
const id = 7654321;
this.optGet(/^\/v\d+\/device($|\?)/, opts).reply(200, {
const providedUuid = opts.shortUUID ?? opts.fullUUID;
this.optGet(
providedUuid.length !== 32
? /^\/v\d+\/device($|\?)/
: /^\/v\d+\/device\(uuid=%27[0-9a-f]{32}%27\)/,
opts,
).reply(200, {
d: [
{
id,

View File

@ -113,7 +113,7 @@ async function createProxyServer(): Promise<[number, number]> {
let proxyPort = 0; // TCP port number, 0 means automatic allocation
await new Promise<void>((resolve, reject) => {
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
// callback type is `() => void`, but our code assumes `(err: Error) => void`
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
if (err) {
@ -197,7 +197,7 @@ async function createInterceptorServer(): Promise<number> {
let interceptorPort = 0;
await new Promise<void>((resolve, reject) => {
// TODO: remove 'as any' below. According to @types/node v12.20.42, the
// TODO: remove 'as any' below. According to @types/node v16.18.25, the
// callback type is `() => void`, but our code assumes `(err: Error) => void`
const listener = (server.listen as any)(0, '127.0.0.1', (err: Error) => {
if (err) {

View File

@ -6,7 +6,7 @@
"name": "Starter",
"slug": "microservices-starter",
"supports_multicontainer": true,
"is_legacy": false,
"is_legacy": true,
"__metadata": {}
}
],

View File

@ -6,7 +6,7 @@
"name": "Starter",
"slug": "microservices-starter",
"supports_multicontainer": true,
"is_legacy": false,
"is_legacy": true,
"__metadata": {}
}
],

View File

@ -3,20 +3,11 @@
{
"belongs_to__application": [
{
"app_name": "test app",
"slug": "org/test app",
"__metadata": {}
}
],
"id": 1747415,
"belongs_to__user": {
"__deferred": {
"uri": "/resin/user(46272)"
},
"__id": 46272
},
"is_managed_by__device": null,
"actor": 4180757,
"device_name": "sparkling-wood",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2",
@ -25,50 +16,10 @@
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null,
"local_id": null,
"status": "Idle",
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"is_connected_to_vpn": false,
"last_vpn_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"vpn_address": null,
"public_address": "89.186.29.129",
"os_version": "balenaOS 2.44.0+rev3",
"os_variant": "dev",
"supervisor_version": "10.3.7",
"should_be_managed_by__supervisor_release": null,
"is_managed_by__service_instance": {
"__deferred": {
"uri": "/resin/service_instance(124111)"
},
"__id": 124111
},
"provisioning_progress": null,
"provisioning_state": "",
"download_progress": null,
"is_web_accessible": false,
"longitude": "22.5853",
"latitude": "51.2712",
"location": "Lublin, Lublin, Poland",
"custom_longitude": "",
"custom_latitude": "",
"logs_channel": null,
"is_locked_until__date": null,
"is_accessible_by_support_until__date": null,
"created_at": "2019-11-18T12:27:37.423Z",
"is_active": true,
"api_heartbeat_state": "offline",
"cpu_usage" : 34,
"cpu_temp" : 56.2,
"cpu_id" : "some cpu id",
"memory_usage" : 1000,
"memory_total" : 4000,
"storage_block_device" : "/dev/mmcblk0",
"storage_usage" : 1000,
"storage_total" : 64000,
"is_undervolted" : true,
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}
@ -76,14 +27,6 @@
{
"belongs_to__application": [],
"id": 1747416,
"belongs_to__user": {
"__deferred": {
"uri": "/resin/user(46272)"
},
"__id": 46272
},
"is_managed_by__device": null,
"actor": 4180757,
"device_name": "dashing-spruce",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f3",
@ -92,50 +35,10 @@
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null,
"local_id": null,
"status": "Idle",
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"is_connected_to_vpn": false,
"last_vpn_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"vpn_address": null,
"public_address": "89.186.29.129",
"os_version": "balenaOS 2.44.0+rev3",
"os_variant": "dev",
"supervisor_version": "10.3.7",
"should_be_managed_by__supervisor_release": null,
"is_managed_by__service_instance": {
"__deferred": {
"uri": "/resin/service_instance(124111)"
},
"__id": 124111
},
"provisioning_progress": null,
"provisioning_state": "",
"download_progress": null,
"is_web_accessible": false,
"longitude": "22.5853",
"latitude": "51.2712",
"location": "Lublin, Lublin, Poland",
"custom_longitude": "",
"custom_latitude": "",
"logs_channel": null,
"is_locked_until__date": null,
"is_accessible_by_support_until__date": null,
"created_at": "2019-11-18T12:27:37.423Z",
"is_active": true,
"api_heartbeat_state": "offline",
"cpu_usage" : 34,
"cpu_temp" : 56.2,
"cpu_id" : "some cpu id",
"memory_usage" : 1000,
"memory_total" : 4000,
"storage_block_device" : "/dev/mmcblk0",
"storage_usage" : 1000,
"storage_total" : 64000,
"is_undervolted" : true,
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}

View File

@ -1,21 +1,3 @@
> Warning Cannot resolve 'module'
node_modules/balena-sync/build/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + command'
node_modules/balena-sync/build/capitano/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + target'
node_modules/balena-sync/build/sync/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/open/xdg-open
@ -56,14 +38,6 @@
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/patch-package/node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/patch-package/node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot resolve 'path'
node_modules/@balena/compose/dist/parse/schemas/index.js
Dynamic require may fail at run time, because the requested file
@ -110,3 +84,8 @@
The file must be distributed with executable as %2.
%1: node_modules/opn/xdg-open
%2: path-to-executable/xdg-open
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=darwin)

View File

@ -1,21 +1,3 @@
> Warning Cannot resolve 'module'
node_modules/balena-sync/build/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + command'
node_modules/balena-sync/build/capitano/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + target'
node_modules/balena-sync/build/sync/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/open/xdg-open
@ -56,14 +38,6 @@
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/patch-package/node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/patch-package/node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot resolve 'path'
node_modules/@balena/compose/dist/parse/schemas/index.js
Dynamic require may fail at run time, because the requested file
@ -110,3 +84,7 @@
The file must be distributed with executable as %2.
%1: node_modules/opn/xdg-open
%2: path-to-executable/xdg-open
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=arm64 libc= platform=linux)

View File

@ -0,0 +1,90 @@
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/open/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
%2: path-to-executable/drivelist.node
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/darwin.sh
%2: path-to-executable/drivelist/darwin.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/linux.sh
%2: path-to-executable/drivelist/linux.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/build/Release/drivelist.node
%2: path-to-executable/drivelist.node
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/darwin.sh
%2: path-to-executable/drivelist/darwin.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/linux.sh
%2: path-to-executable/drivelist/linux.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/etcher-sdk/node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot resolve 'path'
node_modules/@balena/compose/dist/parse/schemas/index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/build/Release/drivelist.node
%2: path-to-executable/drivelist.node
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/darwin.sh
%2: path-to-executable/drivelist/darwin.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/linux.sh
%2: path-to-executable/drivelist/linux.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/build/Release/drivelist.node
%2: path-to-executable/drivelist.node
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/darwin.sh
%2: path-to-executable/drivelist/darwin.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/linux.sh
%2: path-to-executable/drivelist/linux.sh
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/drivelist/scripts/win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/opn/xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules/opn/xdg-open
%2: path-to-executable/xdg-open
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)
prebuild-install warn install No prebuilt binaries found (target=v16.16.0 runtime=node arch=x64 libc= platform=linux)

View File

@ -1,21 +1,3 @@
> Warning Cannot resolve 'module'
node_modules\balena-sync\build\index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + command'
node_modules\balena-sync\build\capitano\index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot resolve ''./' + target'
node_modules\balena-sync\build\sync\index.js
Dynamic require may fail at run time, because the requested file
is unknown at compilation time and not included into executable.
Use a string literal as an argument for 'require', or leave it
as is and specify the resolved file name in 'scripts' option.
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules\open\xdg-open
@ -56,14 +38,6 @@
The file must be distributed with executable as %2.
%1: node_modules\etcher-sdk\node_modules\drivelist\scripts\win32.bat
%2: path-to-executable/drivelist/win32.bat
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules\patch-package\node_modules\open\xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot include file %1 into executable.
The file must be distributed with executable as %2.
%1: node_modules\patch-package\node_modules\open\xdg-open
%2: path-to-executable/xdg-open
> Warning Cannot resolve 'path'
node_modules\@balena\compose\dist\parse\schemas\index.js
Dynamic require may fail at run time, because the requested file

View File

@ -45,7 +45,7 @@ class MockLivepushManager extends LivepushManager {
docker: {} as import('dockerode'),
api: {} as import('../../../lib/utils/device/api').DeviceAPI,
logger: {} as import('../../../lib/utils/logger'),
buildLogs: {},
imageIds: {},
deployOpts:
{} as import('../../../lib/utils/device/deploy').DeviceDeployOptions,
});

View File

@ -63,8 +63,6 @@ describe('detectEncoding() function', function () {
it('should correctly detect the encoding of a few selected files', async () => {
const sampleBinary = [
'drivelist/build/Release/drivelist.node',
'@balena.io/usb/build/Release/usb_bindings.node',
'xxhash/build/Release/hash.node',
'mountutils/build/Release/MountUtils.node',
];
const sampleText = [

View File

@ -1,27 +0,0 @@
/**
* @license
* Copyright 2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module 'balena-image-manager' {
export function cleanCache(): Promise<void>;
export function get(
deviceType: string,
versionOrRange: string,
): Promise<NodeJS.ReadableStream & { mime: string }>;
export function isESR(version: string): boolean;
}

View File

@ -1,19 +0,0 @@
declare module 'balena-preload' {
export class Preloader {
constructor(...args: any[]);
cleanup(): Promise<void>;
on(...args: any[]): void;
preload(): Promise<void>;
prepare(): Promise<void>;
setAppIdAndCommit(appId: string | number, commit: string): Promise<void>;
config: any;
stderr: any;
}
}

View File

@ -1,41 +0,0 @@
/**
* @license
* Copyright 2019 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare module 'balena-sync' {
import { CommandDefinition } from 'capitano';
export function capitano(tool: 'balena-cli'): CommandDefinition;
export interface LocalBalenaOsDevice {
address: string;
host: string;
osVariant: string;
port: number;
}
declare namespace forms {
export function selectLocalBalenaOsDevice(
timeout?: number,
): Promise<string>;
}
declare namespace discover {
export function discoverLocalBalenaOsDevices(
timeout?: number,
): Promise<LocalBalenaOsDevice[]>;
}
}

View File

@ -15,4 +15,25 @@
* limitations under the License.
*/
declare module 'resin-cli-visuals';
declare module 'resin-cli-visuals' {
export const Progress: new (...options: any[]) => any;
export class Spinner {
constructor(message?: string);
spinner: any;
start(): void;
stop(): void;
}
export const SpinnerPromise: new <T>(options: {
promise: T;
startMessage: string;
stopMessage: string;
}) => T;
export const table: {
horizontal: (...options: any[]) => any;
vertical: (...options: any[]) => any;
};
export const drive: (...options: any[]) => any;
}