mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-24 18:45:07 +00:00
Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
776115ef5d | |||
f031ec1dea | |||
fe42438090 | |||
b616fbdd79 | |||
81edfbbae1 | |||
663e83c3b8 | |||
b650f8ff6d | |||
58234f17e1 | |||
77905f4a74 | |||
30076fabe6 | |||
28703bb5ae | |||
37b3c6abe9 | |||
b4e473e4d4 | |||
0d4e411777 | |||
7e6f2189e8 | |||
3903daf8a8 | |||
18bc0d61e7 | |||
7f2daeebb0 | |||
813e9cb82e | |||
3bcb3c1b2e | |||
20d76556c2 | |||
e829068725 | |||
650e896f70 | |||
a9042124ea | |||
d24d78dac7 | |||
42c50ef8ae | |||
ba4b9bd447 | |||
02c0ea5b59 | |||
bc3558dd8e | |||
aad62d1ccd | |||
ecc6f80164 | |||
c0fd1e3886 | |||
9d3120b144 | |||
ed0e03ddb2 | |||
8fe6d6c026 | |||
727033ae14 | |||
c19ce6a905 | |||
1a33029738 | |||
043bc48a1c | |||
a10156a441 | |||
4f665f43d2 | |||
9f097a96f5 | |||
64d1943804 | |||
666ce876e6 | |||
e01184080f | |||
93039b010d | |||
795259bf30 | |||
fa134d2d39 | |||
bef5221ed8 | |||
72d6db796c | |||
e848eb63ee | |||
6f0f7350cf | |||
07a88c700e | |||
9cae66bd92 | |||
cddea24cef | |||
b1c246c0b4 | |||
00b4d57a03 | |||
2cba82e914 | |||
1352c5c823 | |||
c86eb97010 | |||
53be743b9d | |||
d9f21b4c3f | |||
261ab398dd | |||
f28a9992e4 | |||
29e7827eb1 | |||
1d77cf3665 | |||
017c767f61 | |||
7d79c4e24b | |||
60bc5092e0 | |||
a33a794931 | |||
f0ede6fca2 | |||
dbe177e766 | |||
09f80730a8 | |||
327d28c103 | |||
56ab785a82 | |||
305d65d5ed | |||
c4d3686a34 | |||
ce06854b55 | |||
8db05cc8a7 | |||
7a22c987d2 | |||
45efbcdfe3 | |||
d6a9b78b3e | |||
e8ac3ea960 | |||
0ffa0f85a2 | |||
5e7479f60e | |||
07365c45f2 | |||
e5076434c6 | |||
5d687f5a55 | |||
e192767156 | |||
5a8d2fad5f | |||
45f482fad1 | |||
c0e7ae9c91 |
@ -1,2 +0,0 @@
|
||||
/completion/*
|
||||
/bin/*
|
21
.eslintrc.js
21
.eslintrc.js
@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@balena/lint/config/.eslintrc.js'],
|
||||
parserOptions: {
|
||||
project: 'tsconfig.dev.json',
|
||||
},
|
||||
root: true,
|
||||
rules: {
|
||||
ignoreDefinitionFiles: 0,
|
||||
// to avoid the `warning Forbidden non-null assertion @typescript-eslint/no-non-null-assertion`
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
},
|
||||
};
|
6
.github/actions/publish/action.yml
vendored
6
.github/actions/publish/action.yml
vendored
@ -39,7 +39,7 @@ runs:
|
||||
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
@ -94,7 +94,7 @@ runs:
|
||||
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
if [[ $runner_os =~ darwin|macos|osx ]]; then
|
||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||
CSC_KEY_PASSWORD='${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}'
|
||||
CSC_KEYCHAIN=signing_temp
|
||||
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||
|
||||
@ -135,7 +135,7 @@ runs:
|
||||
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }}
|
||||
path: dist
|
||||
|
4
.github/actions/test/action.yml
vendored
4
.github/actions/test/action.yml
vendored
@ -26,7 +26,7 @@ runs:
|
||||
steps:
|
||||
# https://github.com/actions/setup-node#caching-global-packages-data
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
|
||||
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
|
||||
with:
|
||||
node-version: ${{ inputs.NODE_VERSION }}
|
||||
cache: npm
|
||||
@ -58,7 +58,7 @@ runs:
|
||||
run: tar --exclude-vcs -acf ${{ runner.temp }}/custom.tgz .
|
||||
|
||||
- name: Upload custom artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
with:
|
||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||
path: ${{ runner.temp }}/custom.tgz
|
||||
|
@ -1,3 +1,789 @@
|
||||
- commits:
|
||||
- subject: Update actions/setup-node digest to cdca736
|
||||
hash: fe4243809033735a6439f39a1a33dfd00039d656
|
||||
body: |
|
||||
Update actions/setup-node
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: balena-renovate[bot]
|
||||
nested: []
|
||||
version: 21.1.6
|
||||
title: ""
|
||||
date: 2025-04-03T14:12:06.042Z
|
||||
- commits:
|
||||
- subject: Update dockerode/docker-modem dependencies for fixes
|
||||
hash: b650f8ff6d01d2144886253f93aa1d1867f51980
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
author: Ken Bannister
|
||||
nested: []
|
||||
version: 21.1.5
|
||||
title: ""
|
||||
date: 2025-04-03T13:28:08.855Z
|
||||
- commits:
|
||||
- subject: Add comment with secure boot signature file example for preload
|
||||
hash: 28703bb5ae13539ab4c1c597e6a53a5292a7edde
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
author: Ken Bannister
|
||||
nested: []
|
||||
version: 21.1.4
|
||||
title: ""
|
||||
date: 2025-04-02T09:16:27.791Z
|
||||
- commits:
|
||||
- subject: Fix device detail for open balena
|
||||
hash: 0d4e411777dd53d83c475da3653ab94176e07d7d
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: 21.1.3
|
||||
title: ""
|
||||
date: 2025-03-28T16:57:00.250Z
|
||||
- commits:
|
||||
- subject: Deny preload for an image with secure boot enabled
|
||||
hash: 7f2daeebb0973a59682ba4300e1b00bce6f6aead
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
author: Ken Bannister
|
||||
nested: []
|
||||
version: 21.1.2
|
||||
title: ""
|
||||
date: 2025-03-27T12:20:22.883Z
|
||||
- commits:
|
||||
- subject: Bump balena-sdk to 21.3.0
|
||||
hash: e82906872538a7401e31bd52e662e8356a89d413
|
||||
body: |
|
||||
Update balena-sdk from 21.2.1 to 21.3.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested:
|
||||
- commits:
|
||||
- subject: "device: add `changed_api_heartbeat_state_on__date` to typings"
|
||||
hash: bfa52cf58c7d06859a1b5c6f62ff7c71324d3d20
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: balena-sdk-21.3.0
|
||||
title: ""
|
||||
date: 2025-03-26T19:54:35.558Z
|
||||
- commits:
|
||||
- subject: fix linting
|
||||
hash: 13320aad81ba3dfc4950b9960f015b058222c4be
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: balena-sdk-21.2.2
|
||||
title: ""
|
||||
date: 2025-03-26T18:55:53.610Z
|
||||
version: 21.1.1
|
||||
title: ""
|
||||
date: 2025-03-26T20:34:44.546Z
|
||||
- commits:
|
||||
- subject: Add support for new requirement labels feature
|
||||
hash: 42c50ef8aed110b317a0472d928bf75e372b4c0b
|
||||
body: |
|
||||
Updates @balena/compose to v7 to include this new feature.
|
||||
footer:
|
||||
See: https://balena.fibery.io/Work/Project/Refactoring-container-contracts-1205
|
||||
see: https://balena.fibery.io/Work/Project/Refactoring-container-contracts-1205
|
||||
Depends-on: https://github.com/balena-io-modules/balena-compose/pull/64
|
||||
depends-on: https://github.com/balena-io-modules/balena-compose/pull/64
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Felipe Lalanne
|
||||
nested: []
|
||||
version: 21.1.0
|
||||
title: ""
|
||||
date: 2025-03-12T19:34:17.610Z
|
||||
- commits:
|
||||
- subject: Drop support for OS versions <2.14.0
|
||||
hash: aad62d1ccd11ebb69b1035d5b95aef93d384bfd5
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: myarmolinsky
|
||||
nested: []
|
||||
- subject: "api-key generate: Add required argument `expiryDate`"
|
||||
hash: ecc6f80164fca3c0cde42b140b6d7404abe8c877
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: myarmolinsky
|
||||
nested: []
|
||||
- subject: Update `balena-preload` to 18.0.1
|
||||
hash: 9d3120b144c2c017eda55463b034f1561d264213
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: myarmolinsky
|
||||
nested: []
|
||||
- subject: Add dependency `date-fns`
|
||||
hash: ed0e03ddb274da294f719dc0e307ec37591e10d7
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: myarmolinsky
|
||||
nested: []
|
||||
- subject: Update `balena-sdk` to 21.2.1
|
||||
hash: 8fe6d6c0268f69bcf3bcac3c57470272b959e9b0
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: myarmolinsky
|
||||
nested: []
|
||||
version: 21.0.0
|
||||
title: ""
|
||||
date: 2025-03-11T14:42:28.479Z
|
||||
- commits:
|
||||
- subject: Update TypeScript to 5.8.2
|
||||
hash: a10156a441b737275cabfb03bd10bfc5aba7bc88
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 20.2.10
|
||||
title: ""
|
||||
date: 2025-03-10T17:33:09.548Z
|
||||
- commits:
|
||||
- subject: Fix CORS issue with X-Balena-Client header
|
||||
hash: 64d19438042921e89c522f022327ead85b286e9f
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
See: https://balena.fibery.io/Work/Project/Extend-the-X-Balena-Client-header-to-include-the-UI-CLI-version-as-well-1174
|
||||
see: https://balena.fibery.io/Work/Project/Extend-the-X-Balena-Client-header-to-include-the-UI-CLI-version-as-well-1174
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 20.2.9
|
||||
title: ""
|
||||
date: 2025-02-26T12:52:06.672Z
|
||||
- commits:
|
||||
- subject: Update balena-config-json dependency and fix test
|
||||
hash: 93039b010db15fbf1c0d17d4ed8f0db554064de4
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
author: Ken Bannister
|
||||
nested: []
|
||||
version: 20.2.8
|
||||
title: ""
|
||||
date: 2025-02-26T00:22:14.010Z
|
||||
- commits:
|
||||
- subject: Use the CLI version in the X-Balena-Client header
|
||||
hash: bef5221ed891db12a0b760f12fc9654e2f4e241b
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
See: https://balena.fibery.io/Work/Project/Extend-the-X-Balena-Client-header-to-include-the-UI-CLI-version-as-well-1174
|
||||
see: https://balena.fibery.io/Work/Project/Extend-the-X-Balena-Client-header-to-include-the-UI-CLI-version-as-well-1174
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 20.2.7
|
||||
title: ""
|
||||
date: 2025-02-25T20:21:00.603Z
|
||||
- commits:
|
||||
- subject: Update actions/upload-artifact digest to 4cec3d8
|
||||
hash: 6f0f7350cf65c35abd099a901266821c218478eb
|
||||
body: |
|
||||
Update actions/upload-artifact
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: balena-renovate[bot]
|
||||
nested: []
|
||||
version: 20.2.6
|
||||
title: ""
|
||||
date: 2025-02-25T19:13:48.297Z
|
||||
- commits:
|
||||
- subject: Update actions/setup-node digest to 1d0ff46
|
||||
hash: cddea24cefdfef475731e0a7d2bdec4992959a6b
|
||||
body: |
|
||||
Update actions/setup-node
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: balena-renovate[bot]
|
||||
nested: []
|
||||
version: 20.2.5
|
||||
title: ""
|
||||
date: 2025-02-25T18:10:47.617Z
|
||||
- commits:
|
||||
- subject: Pin docker-modem and dockerode to avoid regression
|
||||
hash: 2cba82e914c720e75b68bd4370a2a92b4d4a7ba0
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
signed-off-by: Ken Bannister <kb2ma@runbox.com>
|
||||
author: Ken Bannister
|
||||
nested: []
|
||||
version: 20.2.4
|
||||
title: ""
|
||||
date: 2025-02-25T17:17:00.607Z
|
||||
- commits:
|
||||
- subject: Remove unused old eslint version files
|
||||
hash: 53be743b9dcecbb2f0d8841b6663e7af1a9006c3
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: 20.2.3
|
||||
title: ""
|
||||
date: 2025-01-15T18:21:02.810Z
|
||||
- commits:
|
||||
- subject: Use the promises namespace of balena-image-fs
|
||||
hash: 29e7827eb1dbe4b0395df51f09cea38c3b2fb2e4
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Update balena-device-init to 8.1.3 & balena-image-fs to 7.3.0
|
||||
hash: 1d77cf36652eaca007a25f6ebb85c8509662d576
|
||||
body: |
|
||||
Update balena-device-init from 8.1.0 to 8.1.3
|
||||
Update balena-image-fs from 7.0.6 to 7.3.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Drop Bluebird from devDependencies
|
||||
hash: e4b5c71032112664ba8dac4c9edc5dad5445818d
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: "flowzone: Remove empty with clause"
|
||||
hash: e83b4ef55eeebffb86d0bcda78dd45ee0651c76f
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Add the promises namespace as part of the exposed fs
|
||||
hash: 9b73f44020de213a682afc21092e20caf68eaff8
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-image-fs-7.3.0
|
||||
title: ""
|
||||
date: 2025-01-06T14:31:31.244Z
|
||||
- commits:
|
||||
- subject: Update dependency @types/node to v20
|
||||
hash: 4a5f55ae8752b1b28638d3f4016910d5b2057d80
|
||||
body: |
|
||||
Update @types/node from 18.11.18 to 20.10.6
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Self-hosted Renovate Bot
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.2
|
||||
title: ""
|
||||
date: 2024-01-02T18:50:24.990Z
|
||||
- commits:
|
||||
- subject: Remove repo config from flowzone.yml
|
||||
hash: 3e0053a097a04c1d0c47139bc53787f07bfeb014
|
||||
body: |
|
||||
This functionality is being deprecated in Flowzone.
|
||||
|
||||
See: https://github.com/product-os/flowzone/pull/833
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
author: Kyle Harding
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.1
|
||||
title: ""
|
||||
date: 2023-12-19T21:54:42.129Z
|
||||
- commits:
|
||||
- subject: Add support for Node 18
|
||||
hash: 2a3bef60d9688dce26bd1c9546a28c98cf6d2566
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Akis Kesoglou
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.0
|
||||
title: ""
|
||||
date: 2023-01-20T14:27:37.169Z
|
||||
- commits:
|
||||
- subject: Update dependencies
|
||||
hash: 9acc4bd2e793076095880f1924f8540b4d95fa9c
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: ab77
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.2
|
||||
title: ""
|
||||
date: 2023-01-05T22:29:15.022Z
|
||||
- commits:
|
||||
- subject: Update dependency jsdoc-to-markdown to 8.0.0
|
||||
hash: 5cf71efa734819263bd75c9938cb4367f90e1391
|
||||
body: |
|
||||
Update jsdoc-to-markdown to 8.0.0
|
||||
|
||||
Update jsdoc-to-markdown from 7.1.1 to 8.0.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Renovate Bot
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.1
|
||||
title: ""
|
||||
date: 2022-12-20T02:52:28.901Z
|
||||
- commits:
|
||||
- subject: update dependencies
|
||||
hash: 71a5bb92caa0658e45d1fb36e8652ca5ea50dee9
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Zane Hitchcox
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.0
|
||||
title: ""
|
||||
date: 2022-12-13T00:03:20.006Z
|
||||
- commits:
|
||||
- subject: "README: Remove the travisci badge"
|
||||
hash: 3602a9c0c503502229d12cc1ec1de5bbcbc1b13c
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-8.1.3
|
||||
title: ""
|
||||
date: 2025-01-09T21:25:00.155Z
|
||||
- commits:
|
||||
- subject: Use the promises namespace of balena-image-fs
|
||||
hash: de0af086c5d9124e4bbe5d66bf9aafe0f6c5f11b
|
||||
body: |
|
||||
Update balena-image-fs from 7.0.6 to 7.3.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Drop Bluebird from devDependencies
|
||||
hash: e4b5c71032112664ba8dac4c9edc5dad5445818d
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: "flowzone: Remove empty with clause"
|
||||
hash: e83b4ef55eeebffb86d0bcda78dd45ee0651c76f
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Add the promises namespace as part of the exposed fs
|
||||
hash: 9b73f44020de213a682afc21092e20caf68eaff8
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-image-fs-7.3.0
|
||||
title: ""
|
||||
date: 2025-01-06T14:31:31.244Z
|
||||
- commits:
|
||||
- subject: Update dependency @types/node to v20
|
||||
hash: 4a5f55ae8752b1b28638d3f4016910d5b2057d80
|
||||
body: |
|
||||
Update @types/node from 18.11.18 to 20.10.6
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Self-hosted Renovate Bot
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.2
|
||||
title: ""
|
||||
date: 2024-01-02T18:50:24.990Z
|
||||
- commits:
|
||||
- subject: Remove repo config from flowzone.yml
|
||||
hash: 3e0053a097a04c1d0c47139bc53787f07bfeb014
|
||||
body: |
|
||||
This functionality is being deprecated in Flowzone.
|
||||
|
||||
See: https://github.com/product-os/flowzone/pull/833
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||
author: Kyle Harding
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.1
|
||||
title: ""
|
||||
date: 2023-12-19T21:54:42.129Z
|
||||
- commits:
|
||||
- subject: Add support for Node 18
|
||||
hash: 2a3bef60d9688dce26bd1c9546a28c98cf6d2566
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Akis Kesoglou
|
||||
nested: []
|
||||
version: balena-image-fs-7.2.0
|
||||
title: ""
|
||||
date: 2023-01-20T14:27:37.169Z
|
||||
- commits:
|
||||
- subject: Update dependencies
|
||||
hash: 9acc4bd2e793076095880f1924f8540b4d95fa9c
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: ab77
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.2
|
||||
title: ""
|
||||
date: 2023-01-05T22:29:15.022Z
|
||||
- commits:
|
||||
- subject: Update dependency jsdoc-to-markdown to 8.0.0
|
||||
hash: 5cf71efa734819263bd75c9938cb4367f90e1391
|
||||
body: |
|
||||
Update jsdoc-to-markdown to 8.0.0
|
||||
|
||||
Update jsdoc-to-markdown from 7.1.1 to 8.0.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Renovate Bot
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.1
|
||||
title: ""
|
||||
date: 2022-12-20T02:52:28.901Z
|
||||
- commits:
|
||||
- subject: update dependencies
|
||||
hash: 71a5bb92caa0658e45d1fb36e8652ca5ea50dee9
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Zane Hitchcox
|
||||
nested: []
|
||||
version: balena-image-fs-7.1.0
|
||||
title: ""
|
||||
date: 2022-12-13T00:03:20.006Z
|
||||
version: balena-device-init-8.1.2
|
||||
title: ""
|
||||
date: 2025-01-09T20:28:30.604Z
|
||||
- commits:
|
||||
- subject: Convert some parts to async await and simplify
|
||||
hash: eb1105cd738998bae9c3dd604a8d25f71eb38e19
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Avoid unnecessary destructuring
|
||||
hash: 4cea6b650ec1c911cf2fc12a4a1dc410c0e7d075
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-8.1.1
|
||||
title: ""
|
||||
date: 2025-01-06T06:37:04.147Z
|
||||
version: 20.2.2
|
||||
title: ""
|
||||
date: 2025-01-12T14:49:03.225Z
|
||||
- commits:
|
||||
- subject: Update balena-preload to 17.0.0
|
||||
hash: 60bc5092e0d6014aecf57d157cac536b3c645f0f
|
||||
body: |
|
||||
Update balena-preload from 16.0.0 to 17.0.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Improve typings
|
||||
hash: 67f0f3e9e3b4b37fbaeef2843dc4171a7d661c41
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Stop returning Bluebird promises & drop it from the dependencies
|
||||
hash: 85375cbc1fdb0ce1ad08756f178ed55596e606d9
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-preload-17.0.0
|
||||
title: ""
|
||||
date: 2024-10-21T16:20:33.486Z
|
||||
version: 20.2.1
|
||||
title: ""
|
||||
date: 2025-01-01T18:04:56.723Z
|
||||
- commits:
|
||||
- subject: "os configure: Give precedence to the boot partition located in the
|
||||
image over the device-type.json contents"
|
||||
hash: dbe177e76639040c5fac49746fbbcee2e3360d0a
|
||||
body: |
|
||||
Update balena-device-init from 8.0.0 to 8.1.0
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Thodoris Greasidis
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Try to find the boot partition by inspecting the image
|
||||
hash: de53f11c56133bf2757c5361f8dbc6afa9e826fc
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: minor
|
||||
change-type: minor
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-8.1.0
|
||||
title: ""
|
||||
date: 2024-12-30T12:41:49.474Z
|
||||
- commits:
|
||||
- subject: Drop the unnecessary eslint.config.js
|
||||
hash: 91d587f34bf985bf65e6c21278e7cd70f9281656
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: "packacke.json: Explicitly set type commonjs"
|
||||
hash: f6a0be1d12f09d7d910e5ae35f48f1268542a74e
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-8.0.1
|
||||
title: ""
|
||||
date: 2024-12-19T13:41:14.170Z
|
||||
version: 20.2.0
|
||||
title: ""
|
||||
date: 2024-12-31T18:41:45.907Z
|
||||
- commits:
|
||||
- subject: Add more realistic os configure tests
|
||||
hash: 305d65d5ed07cce767599ac4e5dd131e2a87d10d
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: 20.1.6
|
||||
title: ""
|
||||
date: 2024-12-30T17:16:09.432Z
|
||||
- commits:
|
||||
- subject: Update shrinkwrapped express to v4.21.2
|
||||
hash: 7a22c987d24aca0754bde5618c232424ede72656
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Oskar Williams
|
||||
nested: []
|
||||
version: 20.1.5
|
||||
title: ""
|
||||
date: 2024-12-20T17:55:20.670Z
|
||||
- commits:
|
||||
- subject: Update balena-device-init to 8.0.0
|
||||
hash: e8ac3ea960b1765470b496dbd714036d9b2ecd93
|
||||
body: |
|
||||
Update balena-device-init from 7.0.1 to 8.0.0
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
See: https://github.com/balena-io-modules/balena-device-init/pull/46
|
||||
see: https://github.com/balena-io-modules/balena-device-init/pull/46
|
||||
author: Thodoris Greasidis
|
||||
nested:
|
||||
- commits:
|
||||
- subject: Avoid running linting in the custom tests
|
||||
hash: 420f93d5b2c60b67157897d8bbe0fab315c98b54
|
||||
body: |
|
||||
Balena-lint for some reason was failing in the
|
||||
windows custom test runner.
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Stop returning Bluebird promises
|
||||
hash: b76200a113fee69e11bf182945c382081f8d25e1
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: "package: Publish only the build & typings folders"
|
||||
hash: d1754650272913577b3ce0786941ad54d4acddc8
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Convert to TypeScript with es6 module notation
|
||||
hash: 3576d2c423b294a8703f5474f72a9b906cef927a
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Replace gulp, coffeelint & mochainon with tsc, @balena/lint, mocha,
|
||||
chai & sinon
|
||||
hash: 4c0cf8af370962e8eefda3a2855113629c4ffbb5
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Drop support for node <20.6.0
|
||||
hash: b7c76be409a7a691db66466bc6d6eb746c0ca51e
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: major
|
||||
change-type: major
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-8.0.0
|
||||
title: ""
|
||||
date: 2024-12-18T17:01:33.082Z
|
||||
- commits:
|
||||
- subject: "flowzone: Update runner versions"
|
||||
hash: ba1222ac43ca13b720b64d18129193be8b99af5b
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
- subject: Pin etcher-sdk to 9.0.8 to match resin-device-operations and fix tests
|
||||
hash: 65cde1b940b7bdcc64d34534afa4d9b7b1d14303
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Thodoris Greasidis
|
||||
nested: []
|
||||
version: balena-device-init-7.0.2
|
||||
title: ""
|
||||
date: 2024-12-17T20:16:06.532Z
|
||||
version: 20.1.4
|
||||
title: ""
|
||||
date: 2024-12-20T16:57:31.612Z
|
||||
- commits:
|
||||
- subject: Update oclif to 4.17.0 and @oclif/core 4.1.0
|
||||
hash: 07365c45f2d9a46094faa60d62395c4ba139c9f6
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: 20.1.3
|
||||
title: ""
|
||||
date: 2024-12-20T14:32:59.953Z
|
||||
- commits:
|
||||
- subject: Remove unnecessary `Promise.resolve` and `Promise.reject`
|
||||
hash: e192767156ef7123be76fb838b8cf1a4d80aeb54
|
||||
body: ""
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Pagan Gazzard
|
||||
nested: []
|
||||
version: 20.1.2
|
||||
title: ""
|
||||
date: 2024-12-17T11:34:55.264Z
|
||||
- commits:
|
||||
- subject: Update @balena/lint to v9.1.3
|
||||
hash: c0e7ae9c915d147faab215226d2fd04c35c72176
|
||||
body: |
|
||||
Update @balena/lint from 8.0.0 to 9.1.3
|
||||
footer:
|
||||
Change-type: patch
|
||||
change-type: patch
|
||||
author: Otavio Jacobi
|
||||
nested: []
|
||||
version: 20.1.1
|
||||
title: ""
|
||||
date: 2024-12-16T17:36:39.581Z
|
||||
- commits:
|
||||
- subject: "`device os-update`: Add handling for updates that require takeover"
|
||||
hash: 703dbd01c952474f28cbe3105a5ebaa41b4a0566
|
||||
|
245
CHANGELOG.md
245
CHANGELOG.md
@ -4,6 +4,251 @@ 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/).
|
||||
|
||||
## 21.1.6 - 2025-04-03
|
||||
|
||||
* Update actions/setup-node digest to cdca736 [balena-renovate[bot]]
|
||||
|
||||
## 21.1.5 - 2025-04-03
|
||||
|
||||
* Update dockerode/docker-modem dependencies for fixes [Ken Bannister]
|
||||
|
||||
## 21.1.4 - 2025-04-02
|
||||
|
||||
* Add comment with secure boot signature file example for preload [Ken Bannister]
|
||||
|
||||
## 21.1.3 - 2025-03-28
|
||||
|
||||
* Fix device detail for open balena [Otavio Jacobi]
|
||||
|
||||
## 21.1.2 - 2025-03-27
|
||||
|
||||
* Deny preload for an image with secure boot enabled [Ken Bannister]
|
||||
|
||||
## 21.1.1 - 2025-03-26
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Bump balena-sdk to 21.3.0 [Otavio Jacobi] </summary>
|
||||
|
||||
> ### balena-sdk-21.3.0 - 2025-03-26
|
||||
>
|
||||
> * device: add `changed_api_heartbeat_state_on__date` to typings [Otavio Jacobi]
|
||||
>
|
||||
> ### balena-sdk-21.2.2 - 2025-03-26
|
||||
>
|
||||
> * fix linting [Otavio Jacobi]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 21.1.0 - 2025-03-12
|
||||
|
||||
* Add support for new requirement labels feature [Felipe Lalanne]
|
||||
|
||||
## 21.0.0 - 2025-03-11
|
||||
|
||||
* Drop support for OS versions <2.14.0 [myarmolinsky]
|
||||
* api-key generate: Add required argument `expiryDate` [myarmolinsky]
|
||||
* Update `balena-preload` to 18.0.1 [myarmolinsky]
|
||||
* Add dependency `date-fns` [myarmolinsky]
|
||||
* Update `balena-sdk` to 21.2.1 [myarmolinsky]
|
||||
|
||||
## 20.2.10 - 2025-03-10
|
||||
|
||||
* Update TypeScript to 5.8.2 [Thodoris Greasidis]
|
||||
|
||||
## 20.2.9 - 2025-02-26
|
||||
|
||||
* Fix CORS issue with X-Balena-Client header [Thodoris Greasidis]
|
||||
|
||||
## 20.2.8 - 2025-02-26
|
||||
|
||||
* Update balena-config-json dependency and fix test [Ken Bannister]
|
||||
|
||||
## 20.2.7 - 2025-02-25
|
||||
|
||||
* Use the CLI version in the X-Balena-Client header [Thodoris Greasidis]
|
||||
|
||||
## 20.2.6 - 2025-02-25
|
||||
|
||||
* Update actions/upload-artifact digest to 4cec3d8 [balena-renovate[bot]]
|
||||
|
||||
## 20.2.5 - 2025-02-25
|
||||
|
||||
* Update actions/setup-node digest to 1d0ff46 [balena-renovate[bot]]
|
||||
|
||||
## 20.2.4 - 2025-02-25
|
||||
|
||||
* Pin docker-modem and dockerode to avoid regression [Ken Bannister]
|
||||
|
||||
## 20.2.3 - 2025-01-15
|
||||
|
||||
* Remove unused old eslint version files [Otavio Jacobi]
|
||||
|
||||
## 20.2.2 - 2025-01-12
|
||||
|
||||
* Use the promises namespace of balena-image-fs [Thodoris Greasidis]
|
||||
|
||||
<details>
|
||||
<summary> Update balena-device-init to 8.1.3 & balena-image-fs to 7.3.0 [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-image-fs-7.3.0 - 2025-01-06
|
||||
>
|
||||
> * Drop Bluebird from devDependencies [Thodoris Greasidis]
|
||||
> * flowzone: Remove empty with clause [Thodoris Greasidis]
|
||||
> * Add the promises namespace as part of the exposed fs [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-image-fs-7.2.2 - 2024-01-02
|
||||
>
|
||||
> * Update dependency @types/node to v20 [Self-hosted Renovate Bot]
|
||||
>
|
||||
> ### balena-image-fs-7.2.1 - 2023-12-19
|
||||
>
|
||||
> * Remove repo config from flowzone.yml [Kyle Harding]
|
||||
>
|
||||
> ### balena-image-fs-7.2.0 - 2023-01-20
|
||||
>
|
||||
> * Add support for Node 18 [Akis Kesoglou]
|
||||
>
|
||||
> ### balena-image-fs-7.1.2 - 2023-01-05
|
||||
>
|
||||
> * Update dependencies [ab77]
|
||||
>
|
||||
> ### balena-image-fs-7.1.1 - 2022-12-20
|
||||
>
|
||||
> * Update dependency jsdoc-to-markdown to 8.0.0 [Renovate Bot]
|
||||
>
|
||||
> ### balena-image-fs-7.1.0 - 2022-12-13
|
||||
>
|
||||
> * update dependencies [Zane Hitchcox]
|
||||
>
|
||||
> ### balena-device-init-8.1.3 - 2025-01-09
|
||||
>
|
||||
> * README: Remove the travisci badge [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-device-init-8.1.2 - 2025-01-09
|
||||
>
|
||||
>
|
||||
> <details>
|
||||
> <summary> Use the promises namespace of balena-image-fs [Thodoris Greasidis] </summary>
|
||||
>
|
||||
>> #### balena-image-fs-7.3.0 - 2025-01-06
|
||||
>>
|
||||
>> * Drop Bluebird from devDependencies [Thodoris Greasidis]
|
||||
>> * flowzone: Remove empty with clause [Thodoris Greasidis]
|
||||
>> * Add the promises namespace as part of the exposed fs [Thodoris Greasidis]
|
||||
>>
|
||||
>> #### balena-image-fs-7.2.2 - 2024-01-02
|
||||
>>
|
||||
>> * Update dependency @types/node to v20 [Self-hosted Renovate Bot]
|
||||
>>
|
||||
>> #### balena-image-fs-7.2.1 - 2023-12-19
|
||||
>>
|
||||
>> * Remove repo config from flowzone.yml [Kyle Harding]
|
||||
>>
|
||||
>> #### balena-image-fs-7.2.0 - 2023-01-20
|
||||
>>
|
||||
>> * Add support for Node 18 [Akis Kesoglou]
|
||||
>>
|
||||
>> #### balena-image-fs-7.1.2 - 2023-01-05
|
||||
>>
|
||||
>> * Update dependencies [ab77]
|
||||
>>
|
||||
>> #### balena-image-fs-7.1.1 - 2022-12-20
|
||||
>>
|
||||
>> * Update dependency jsdoc-to-markdown to 8.0.0 [Renovate Bot]
|
||||
>>
|
||||
>> #### balena-image-fs-7.1.0 - 2022-12-13
|
||||
>>
|
||||
>> * update dependencies [Zane Hitchcox]
|
||||
>>
|
||||
>
|
||||
> </details>
|
||||
>
|
||||
>
|
||||
> ### balena-device-init-8.1.1 - 2025-01-06
|
||||
>
|
||||
> * Convert some parts to async await and simplify [Thodoris Greasidis]
|
||||
> * Avoid unnecessary destructuring [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 20.2.1 - 2025-01-01
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update balena-preload to 17.0.0 [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-preload-17.0.0 - 2024-10-21
|
||||
>
|
||||
> * Improve typings [Thodoris Greasidis]
|
||||
> * Stop returning Bluebird promises & drop it from the dependencies [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 20.2.0 - 2024-12-31
|
||||
|
||||
|
||||
<details>
|
||||
<summary> os configure: Give precedence to the boot partition located in the image over the device-type.json contents [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-device-init-8.1.0 - Invalid date
|
||||
>
|
||||
> * Try to find the boot partition by inspecting the image [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-device-init-8.0.1 - 2024-12-19
|
||||
>
|
||||
> * Drop the unnecessary eslint.config.js [Thodoris Greasidis]
|
||||
> * packacke.json: Explicitly set type commonjs [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 20.1.6 - 2024-12-30
|
||||
|
||||
* Add more realistic os configure tests [Thodoris Greasidis]
|
||||
|
||||
## 20.1.5 - 2024-12-20
|
||||
|
||||
* Update shrinkwrapped express to v4.21.2 [Oskar Williams]
|
||||
|
||||
## 20.1.4 - 2024-12-20
|
||||
|
||||
|
||||
<details>
|
||||
<summary> Update balena-device-init to 8.0.0 [Thodoris Greasidis] </summary>
|
||||
|
||||
> ### balena-device-init-8.0.0 - 2024-12-18
|
||||
>
|
||||
> * Avoid running linting in the custom tests [Thodoris Greasidis]
|
||||
> * Stop returning Bluebird promises [Thodoris Greasidis]
|
||||
> * package: Publish only the build & typings folders [Thodoris Greasidis]
|
||||
> * Convert to TypeScript with es6 module notation [Thodoris Greasidis]
|
||||
> * Replace gulp, coffeelint & mochainon with tsc, @balena/lint, mocha, chai & sinon [Thodoris Greasidis]
|
||||
> * Drop support for node <20.6.0 [Thodoris Greasidis]
|
||||
>
|
||||
> ### balena-device-init-7.0.2 - 2024-12-17
|
||||
>
|
||||
> * flowzone: Update runner versions [Thodoris Greasidis]
|
||||
> * Pin etcher-sdk to 9.0.8 to match resin-device-operations and fix tests [Thodoris Greasidis]
|
||||
>
|
||||
|
||||
</details>
|
||||
|
||||
## 20.1.3 - 2024-12-20
|
||||
|
||||
* Update oclif to 4.17.0 and @oclif/core 4.1.0 [Otavio Jacobi]
|
||||
|
||||
## 20.1.2 - 2024-12-17
|
||||
|
||||
* Remove unnecessary `Promise.resolve` and `Promise.reject` [Pagan Gazzard]
|
||||
|
||||
## 20.1.1 - 2024-12-16
|
||||
|
||||
* Update @balena/lint to v9.1.3 [Otavio Jacobi]
|
||||
|
||||
## 20.1.0 - 2024-12-12
|
||||
|
||||
* `device os-update`: Add handling for updates that require takeover [myarmolinsky]
|
||||
|
@ -305,7 +305,7 @@ async function zipPkg() {
|
||||
);
|
||||
}
|
||||
await fs.mkdirp(path.dirname(outputFile));
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
console.log(`Zipping standalone package to "${outputFile}"...`);
|
||||
|
||||
const archive = archiver('zip', {
|
||||
|
@ -145,7 +145,7 @@ export async function getCapitanoDoc(): Promise<typeof capitanoDoc> {
|
||||
throw new Error(`Error parsing section title`);
|
||||
}
|
||||
// match[1] has the title, match[2] has the rest
|
||||
return match && match[2];
|
||||
return match?.[2];
|
||||
}),
|
||||
mdParser.getSectionOfTitle('Installation'),
|
||||
mdParser.getSectionOfTitle('Choosing a shell (command prompt/terminal)'),
|
||||
|
@ -3,7 +3,7 @@ import * as semver from 'semver';
|
||||
|
||||
const changeTypes = ['major', 'minor', 'patch'] as const;
|
||||
|
||||
const validateChangeType = (maybeChangeType: string = 'minor') => {
|
||||
const validateChangeType = (maybeChangeType = 'minor') => {
|
||||
maybeChangeType = maybeChangeType.toLowerCase();
|
||||
switch (maybeChangeType) {
|
||||
case 'patch':
|
||||
@ -136,5 +136,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
main();
|
||||
void main();
|
||||
|
@ -19,6 +19,7 @@ import { spawn } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { diffTrimmedLines } from 'diff';
|
||||
import * as whichMod from 'which';
|
||||
|
||||
export const ROOT = path.join(__dirname, '..');
|
||||
|
||||
@ -101,7 +102,6 @@ export function loadPackageJson() {
|
||||
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
||||
*/
|
||||
export async function which(program: string): Promise<string> {
|
||||
const whichMod = await import('which');
|
||||
let programPath: string;
|
||||
try {
|
||||
programPath = await whichMod(program);
|
||||
@ -132,7 +132,7 @@ export async function whichSpawn(
|
||||
.on('error', reject)
|
||||
.on('close', resolve);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -57,7 +57,10 @@ require('ts-node').register({
|
||||
project: path.join(rootDir, 'tsconfig.json'),
|
||||
transpileOnly: true,
|
||||
});
|
||||
require('../src/app').run(undefined, { dir: __dirname, development: true });
|
||||
void require('../src/app').run(undefined, {
|
||||
dir: __dirname,
|
||||
development: true,
|
||||
});
|
||||
|
||||
// Modify package.json oclif paths from build/ -> src/, or vice versa
|
||||
function modifyOclifPaths(revert) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
process.env.UV_THREADPOOL_SIZE = '64';
|
||||
|
||||
// Disable oclif registering ts-node
|
||||
process.env.OCLIF_TS_NODE = 0;
|
||||
process.env.OCLIF_TS_NODE = '0';
|
||||
|
||||
async function run() {
|
||||
// Use fast-boot to cache require lookups, speeding up startup
|
||||
@ -18,4 +18,4 @@ async function run() {
|
||||
await require('../build/app').run(undefined, { dir: __dirname });
|
||||
}
|
||||
|
||||
run();
|
||||
void run();
|
||||
|
@ -326,6 +326,8 @@ or to authenticate requests to the API with an 'Authorization: Bearer <key>' hea
|
||||
Examples:
|
||||
|
||||
$ balena api-key generate "Jenkins Key"
|
||||
$ balena api-key generate "Jenkins Key" 2025-10-30
|
||||
$ balena api-key generate "Jenkins Key" never
|
||||
|
||||
### Arguments
|
||||
|
||||
@ -333,6 +335,10 @@ Examples:
|
||||
|
||||
the API key name
|
||||
|
||||
#### EXPIRYDATE
|
||||
|
||||
the expiry date of the API key as an ISO date string, or "never" for no expiry
|
||||
|
||||
## api-key list
|
||||
|
||||
### Aliases
|
||||
|
32
eslint.config.js
Normal file
32
eslint.config.js
Normal file
@ -0,0 +1,32 @@
|
||||
const { FlatCompat } = require('@eslint/eslintrc');
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
module.exports = [
|
||||
...require('@balena/lint/config/eslint.config'),
|
||||
...compat.config({
|
||||
parserOptions: {
|
||||
project: 'tsconfig.dev.json',
|
||||
},
|
||||
ignorePatterns: ['**/generate-completion.js', '**/bin/**/*'],
|
||||
rules: {
|
||||
ignoreDefinitionFiles: 0,
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||
|
||||
'no-restricted-imports': ['error', {
|
||||
paths: ['resin-cli-visuals', 'chalk', 'common-tags', 'resin-cli-form'],
|
||||
}],
|
||||
|
||||
'@typescript-eslint/no-unused-vars': ['error', {
|
||||
argsIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
}],
|
||||
},
|
||||
}),
|
||||
];
|
5520
npm-shrinkwrap.json
generated
5520
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-cli",
|
||||
"version": "20.1.0",
|
||||
"version": "21.1.6",
|
||||
"description": "The official balena Command Line Interface",
|
||||
"main": "./build/app.js",
|
||||
"homepage": "https://github.com/balena-io/balena-cli",
|
||||
@ -58,6 +58,7 @@
|
||||
"build:completion": "node completion/generate-completion.js",
|
||||
"build:standalone": "ts-node --transpile-only automation/run.ts build:standalone",
|
||||
"build:installer": "ts-node --transpile-only automation/run.ts build:installer",
|
||||
"deduplicate-dependencies": "npm dd && git add npm-shrinkwrap.json && git commit --message \"Deduplicate dependencies\"",
|
||||
"package": "npm run build:fast && npm run build:standalone && npm run build:installer",
|
||||
"pretest": "npm run build",
|
||||
"test": "npm run test:shrinkwrap && npm run test:core",
|
||||
@ -111,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@balena/lint": "^8.0.0",
|
||||
"@balena/lint": "^9.1.3",
|
||||
"@electron/notarize": "^2.0.0",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/bluebird": "^3.5.36",
|
||||
@ -180,27 +181,27 @@
|
||||
"mock-fs": "^5.2.0",
|
||||
"mock-require": "^3.0.3",
|
||||
"nock": "^13.2.1",
|
||||
"oclif": "^4.14.0",
|
||||
"oclif": "^4.17.0",
|
||||
"rewire": "^7.0.0",
|
||||
"simple-git": "^3.14.1",
|
||||
"sinon": "^19.0.0",
|
||||
"string-to-stream": "^3.0.1",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@balena/compose": "^6.0.0",
|
||||
"@balena/compose": "^7.0.1",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"@balena/env-parsing": "^1.1.8",
|
||||
"@balena/es-version": "^1.0.1",
|
||||
"@oclif/core": "^4.0.31",
|
||||
"@oclif/core": "^4.1.0",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^7.0.1",
|
||||
"balena-config-json": "^4.2.2",
|
||||
"balena-device-init": "^8.1.3",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-preload": "^16.0.0",
|
||||
"balena-sdk": "^20.8.0",
|
||||
"balena-image-fs": "^7.5.0",
|
||||
"balena-preload": "^18.0.1",
|
||||
"balena-sdk": "^21.3.0",
|
||||
"balena-semver": "^2.3.0",
|
||||
"balena-settings-client": "^5.0.2",
|
||||
"balena-settings-storage": "^8.1.0",
|
||||
@ -211,10 +212,11 @@
|
||||
"cli-truncate": "^2.1.0",
|
||||
"color-hash": "^1.1.1",
|
||||
"common-tags": "^1.7.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"denymount": "^2.3.0",
|
||||
"docker-modem": "^5.0.3",
|
||||
"docker-modem": "^5.0.6",
|
||||
"docker-progress": "^5.1.3",
|
||||
"dockerode": "^4.0.2",
|
||||
"dockerode": "^4.0.5",
|
||||
"ejs": "^3.1.6",
|
||||
"etcher-sdk": "9.1.0",
|
||||
"express": "^4.17.2",
|
||||
@ -274,6 +276,6 @@
|
||||
}
|
||||
},
|
||||
"versionist": {
|
||||
"publishedAt": "2024-12-12T14:17:16.574Z"
|
||||
"publishedAt": "2025-04-03T14:12:06.976Z"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
diff --git a/node_modules/oclif/lib/commands/pack/win.js b/node_modules/oclif/lib/commands/pack/win.js
|
||||
index ef7f90e..8264b7c 100644
|
||||
index bfe9205..482519e 100644
|
||||
--- a/node_modules/oclif/lib/commands/pack/win.js
|
||||
+++ b/node_modules/oclif/lib/commands/pack/win.js
|
||||
@@ -76,6 +76,12 @@ InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
||||
@@ -86,6 +86,12 @@ InstallDir "\$PROGRAMFILES${arch === 'x64' ? '64' : ''}\\${config.dirname}"
|
||||
${customization}
|
||||
|
||||
Section "${config.name} CLI \${VERSION}"
|
||||
@ -16,20 +16,18 @@ index ef7f90e..8264b7c 100644
|
||||
File /r bin
|
||||
File /r client
|
||||
diff --git a/node_modules/oclif/lib/tarballs/build.js b/node_modules/oclif/lib/tarballs/build.js
|
||||
index 14d5a6e..7b42a6f 100644
|
||||
index f0c8d95..a72400e 100644
|
||||
--- a/node_modules/oclif/lib/tarballs/build.js
|
||||
+++ b/node_modules/oclif/lib/tarballs/build.js
|
||||
@@ -200,6 +200,13 @@ const extractCLI = async (tarball, c) => {
|
||||
@@ -218,6 +218,11 @@ const extractCLI = async (tarball, c) => {
|
||||
(0, promises_1.rm)(path.join(workspace, path.basename(tarball)), { recursive: true }),
|
||||
(0, fs_extra_1.remove)(path.join(workspace, 'bin', 'run.cmd')),
|
||||
]);
|
||||
+
|
||||
+ // The oclif installers are a production installation, while the source
|
||||
+ // `bin` folder may contain a `.fast-boot.json` file of a dev installation.
|
||||
+ // This has previously led to issues preventing the CLI from starting, so
|
||||
+ // delete `.fast-boot.json` (if any) from the destination folder.
|
||||
+ await (0, fs_extra_1.remove)(path.join(workspace, 'bin', '.fast-boot.json'));
|
||||
+
|
||||
};
|
||||
const buildTarget = async (target, c, options) => {
|
||||
const workspace = c.workspace(target);
|
||||
if (target.platform === 'win32' && target.arch === 'arm64' && (0, semver_1.lt)(c.nodeVersion, '20.0.0')) {
|
4
repo.yml
4
repo.yml
@ -6,6 +6,10 @@ upstream:
|
||||
url: 'https://github.com/balena-io/balena-sdk'
|
||||
- repo: 'balena-config-json'
|
||||
url: 'https://github.com/balena-io-modules/balena-config-json'
|
||||
- repo: 'balena-image-fs'
|
||||
url: 'https://github.com/balena-io-modules/balena-image-fs'
|
||||
- repo: 'balena-device-init'
|
||||
url: 'https://github.com/balena-io-modules/balena-device-init'
|
||||
- repo: 'balena-image-manager'
|
||||
url: 'https://github.com/balena-io-modules/balena-image-manager'
|
||||
- repo: 'balena-preload'
|
||||
|
@ -101,11 +101,9 @@ async function init() {
|
||||
|
||||
/** Execute the oclif parser and the CLI command. */
|
||||
async function oclifRun(command: string[], options: AppOptions) {
|
||||
let deprecationPromise: Promise<void>;
|
||||
let deprecationPromise: Promise<void> | undefined;
|
||||
// check and enforce the CLI's deprecation policy
|
||||
if (unsupportedFlag || process.env.BALENARC_UNSUPPORTED) {
|
||||
deprecationPromise = Promise.resolve();
|
||||
} else {
|
||||
if (!(unsupportedFlag || process.env.BALENARC_UNSUPPORTED)) {
|
||||
const { DeprecationChecker } = await import('./deprecation');
|
||||
const deprecationChecker = new DeprecationChecker(packageJSON.version);
|
||||
// warnAndAbortIfDeprecated uses previously cached data only
|
||||
|
@ -17,7 +17,16 @@
|
||||
|
||||
import { Args, Command } from '@oclif/core';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
|
||||
import {
|
||||
formatDuration,
|
||||
intervalToDuration,
|
||||
isValid,
|
||||
parseISO,
|
||||
} from 'date-fns';
|
||||
|
||||
// In days
|
||||
const durations = [1, 7, 30, 90];
|
||||
|
||||
async function isLoggedInWithJwt() {
|
||||
const balena = getBalenaSdk();
|
||||
@ -41,13 +50,21 @@ export default class GenerateCmd extends Command {
|
||||
This key can be used to log into the CLI using 'balena login --token <key>',
|
||||
or to authenticate requests to the API with an 'Authorization: Bearer <key>' header.
|
||||
`;
|
||||
public static examples = ['$ balena api-key generate "Jenkins Key"'];
|
||||
public static examples = [
|
||||
'$ balena api-key generate "Jenkins Key"',
|
||||
'$ balena api-key generate "Jenkins Key" 2025-10-30',
|
||||
'$ balena api-key generate "Jenkins Key" never',
|
||||
];
|
||||
|
||||
public static args = {
|
||||
name: Args.string({
|
||||
description: 'the API key name',
|
||||
required: true,
|
||||
}),
|
||||
expiryDate: Args.string({
|
||||
description:
|
||||
'the expiry date of the API key as an ISO date string, or "never" for no expiry',
|
||||
}),
|
||||
};
|
||||
|
||||
public static authenticated = true;
|
||||
@ -55,9 +72,61 @@ export default class GenerateCmd extends Command {
|
||||
public async run() {
|
||||
const { args: params } = await this.parse(GenerateCmd);
|
||||
|
||||
let expiryDateResponse: string | number | undefined = params.expiryDate;
|
||||
let key;
|
||||
try {
|
||||
key = await getBalenaSdk().models.apiKey.create(params.name);
|
||||
if (!expiryDateResponse) {
|
||||
expiryDateResponse = await getCliForm().ask({
|
||||
message: 'Please pick an expiry date for the API key',
|
||||
type: 'list',
|
||||
choices: [...durations, 'custom', 'never'].map((duration) => ({
|
||||
name:
|
||||
duration === 'never'
|
||||
? 'No expiration'
|
||||
: typeof duration === 'number'
|
||||
? formatDuration(
|
||||
intervalToDuration({
|
||||
start: 0,
|
||||
end: duration * 24 * 60 * 60 * 1000,
|
||||
}),
|
||||
)
|
||||
: 'Custom expiration',
|
||||
value: duration,
|
||||
})),
|
||||
});
|
||||
}
|
||||
let expiryDate: Date | null;
|
||||
if (expiryDateResponse === 'never') {
|
||||
expiryDate = null;
|
||||
} else if (expiryDateResponse === 'custom') {
|
||||
do {
|
||||
expiryDate = parseISO(
|
||||
await getCliForm().ask({
|
||||
message:
|
||||
'Please enter an expiry date for the API key as an ISO date string',
|
||||
type: 'input',
|
||||
}),
|
||||
);
|
||||
if (!isValid(expiryDate)) {
|
||||
console.error('Invalid date format');
|
||||
}
|
||||
} while (!isValid(expiryDate));
|
||||
} else if (typeof expiryDateResponse === 'string') {
|
||||
expiryDate = parseISO(expiryDateResponse);
|
||||
if (!isValid(expiryDate)) {
|
||||
throw new Error(
|
||||
'Invalid date format, please use a valid ISO date string',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
expiryDate = new Date(
|
||||
Date.now() + expiryDateResponse * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
}
|
||||
key = await getBalenaSdk().models.apiKey.create({
|
||||
name: params.name,
|
||||
expiryDate: expiryDate === null ? null : expiryDate.toISOString(),
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.name === 'BalenaNotLoggedIn') {
|
||||
if (await isLoggedInWithJwt()) {
|
||||
|
@ -50,9 +50,9 @@ export default class RevokeCmd extends Command {
|
||||
return;
|
||||
}
|
||||
await Promise.all(
|
||||
apiKeyIds.map(
|
||||
async (id) => await getBalenaSdk().models.apiKey.revoke(Number(id)),
|
||||
),
|
||||
apiKeyIds.map(async (id) => {
|
||||
await getBalenaSdk().models.apiKey.revoke(Number(id));
|
||||
}),
|
||||
);
|
||||
console.log('Successfully revoked the given API keys');
|
||||
}
|
||||
|
@ -368,6 +368,7 @@ ${dockerignoreHelp}
|
||||
!opts.shouldUploadLogs,
|
||||
composeOpts.projectPath,
|
||||
opts.createAsDraft,
|
||||
project.descriptors,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ export default class DeviceDetectCmd extends Command {
|
||||
try {
|
||||
await docker.ping();
|
||||
return true;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
@ -77,45 +77,59 @@ export default class DeviceCmd extends Command {
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
|
||||
const device = (await balena.models.device.get(
|
||||
params.uuid,
|
||||
options.json
|
||||
? {
|
||||
$expand: {
|
||||
device_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
},
|
||||
...expandForAppName.$expand,
|
||||
let device: ExtendedDevice;
|
||||
if (options.json) {
|
||||
const [deviceBase, deviceComputed] = await Promise.all([
|
||||
balena.models.device.get(params.uuid, {
|
||||
$expand: {
|
||||
device_tag: {
|
||||
$select: ['tag_key', 'value'],
|
||||
},
|
||||
}
|
||||
: {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
...expandForAppName.$expand,
|
||||
},
|
||||
)) as ExtendedDevice;
|
||||
}),
|
||||
balena.models.device.get(params.uuid, {
|
||||
$select: [
|
||||
'overall_status',
|
||||
'overall_progress',
|
||||
'should_be_running__release',
|
||||
],
|
||||
}),
|
||||
]);
|
||||
|
||||
device = {
|
||||
...deviceBase,
|
||||
...deviceComputed,
|
||||
} as ExtendedDevice;
|
||||
} else {
|
||||
device = (await balena.models.device.get(params.uuid, {
|
||||
$select: [
|
||||
'device_name',
|
||||
'id',
|
||||
'overall_status',
|
||||
'is_online',
|
||||
'ip_address',
|
||||
'mac_address',
|
||||
'last_connectivity_event',
|
||||
'uuid',
|
||||
'supervisor_version',
|
||||
'is_web_accessible',
|
||||
'note',
|
||||
'os_version',
|
||||
'memory_usage',
|
||||
'memory_total',
|
||||
'public_address',
|
||||
'storage_block_device',
|
||||
'storage_usage',
|
||||
'storage_total',
|
||||
'cpu_usage',
|
||||
'cpu_temp',
|
||||
'cpu_id',
|
||||
'is_undervolted',
|
||||
],
|
||||
...expandForAppName,
|
||||
})) as ExtendedDevice;
|
||||
}
|
||||
|
||||
if (options.view) {
|
||||
const open = await import('open');
|
||||
|
@ -155,7 +155,7 @@ export default class DeviceInitCmd extends Command {
|
||||
try {
|
||||
logger.logDebug(`Process failed, removing device ${device.uuid}`);
|
||||
await balena.models.device.remove(device.uuid);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Ignore removal failures, and throw original error
|
||||
}
|
||||
throw e;
|
||||
|
@ -135,7 +135,7 @@ export default class DeviceLogsCmd extends Command {
|
||||
logger.logDebug('Checking we can access device');
|
||||
try {
|
||||
await deviceApi.ping();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
const { ExpectedError } = await import('../../errors');
|
||||
throw new ExpectedError(
|
||||
`Cannot access device at address ${params.device}. Device may not be in local mode.`,
|
||||
|
@ -76,6 +76,6 @@ export default class DeviceRegisterCmd extends Command {
|
||||
options.deviceType,
|
||||
);
|
||||
|
||||
return result && result.uuid;
|
||||
return result.uuid;
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ export default class DeviceSSHCmd extends Command {
|
||||
SSH server port number (default 22222) if the target is an IP address or .local
|
||||
hostname. Otherwise, port number for the balenaCloud gateway (default 22).`,
|
||||
char: 'p',
|
||||
parse: async (p) => parseAsInteger(p, 'port'),
|
||||
parse: (p) => parseAsInteger(p, 'port'),
|
||||
}),
|
||||
tty: Flags.boolean({
|
||||
default: false,
|
||||
@ -110,13 +110,14 @@ export default class DeviceSSHCmd extends Command {
|
||||
// Local connection
|
||||
if (validateLocalHostnameOrIp(params.fleetOrDevice)) {
|
||||
const { performLocalDeviceSSH } = await import('../../utils/device/ssh');
|
||||
return await performLocalDeviceSSH({
|
||||
await performLocalDeviceSSH({
|
||||
hostname: params.fleetOrDevice,
|
||||
port: options.port || 'local',
|
||||
forceTTY: options.tty,
|
||||
verbose: options.verbose,
|
||||
service: params.service,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote connection
|
||||
@ -132,7 +133,7 @@ export default class DeviceSSHCmd extends Command {
|
||||
const useProxy = !!proxyConfig && !options.noproxy;
|
||||
|
||||
// this will be a tunnelled SSH connection...
|
||||
await checkNotUsingOfflineMode();
|
||||
checkNotUsingOfflineMode();
|
||||
await checkLoggedIn();
|
||||
const deviceUuid = await getOnlineTargetDeviceUuid(
|
||||
sdk,
|
||||
|
2
src/commands/env/rename.ts
vendored
2
src/commands/env/rename.ts
vendored
@ -41,7 +41,7 @@ export default class EnvRenameCmd extends Command {
|
||||
id: Args.integer({
|
||||
required: true,
|
||||
description: "variable's numeric database ID",
|
||||
parse: async (input) => parseAsInteger(input, 'id'),
|
||||
parse: (input) => parseAsInteger(input, 'id'),
|
||||
}),
|
||||
value: Args.string({
|
||||
required: true,
|
||||
|
2
src/commands/env/rm.ts
vendored
2
src/commands/env/rm.ts
vendored
@ -46,7 +46,7 @@ export default class EnvRmCmd extends Command {
|
||||
id: Args.integer({
|
||||
required: true,
|
||||
description: "variable's numeric database ID",
|
||||
parse: async (input) => parseAsInteger(input, 'id'),
|
||||
parse: (input) => parseAsInteger(input, 'id'),
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
import { Args, Command } from '@oclif/core';
|
||||
import { promisify } from 'util';
|
||||
import { stripIndent } from '../../utils/lazy';
|
||||
|
||||
export default class LocalConfigureCmd extends Command {
|
||||
@ -237,7 +236,7 @@ export default class LocalConfigureCmd extends Command {
|
||||
const bootPartition = await getBootPartition(target);
|
||||
|
||||
const files = await imagefs.interact(target, bootPartition, async (_fs) => {
|
||||
return await promisify(_fs.readdir)(this.CONNECTIONS_FOLDER);
|
||||
return await _fs.promises.readdir(this.CONNECTIONS_FOLDER);
|
||||
});
|
||||
|
||||
let connectionFileName;
|
||||
@ -246,13 +245,11 @@ export default class LocalConfigureCmd extends Command {
|
||||
} else if (_.includes(files, 'resin-sample.ignore')) {
|
||||
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
|
||||
await imagefs.interact(target, bootPartition, async (_fs) => {
|
||||
const readFileAsync = promisify(_fs.readFile);
|
||||
const writeFileAsync = promisify(_fs.writeFile);
|
||||
const contents = await readFileAsync(
|
||||
const contents = await _fs.promises.readFile(
|
||||
`${this.CONNECTIONS_FOLDER}/resin-sample.ignore`,
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
return await writeFileAsync(
|
||||
await _fs.promises.writeFile(
|
||||
`${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
contents,
|
||||
);
|
||||
@ -269,13 +266,13 @@ export default class LocalConfigureCmd extends Command {
|
||||
} else {
|
||||
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
|
||||
await imagefs.interact(target, bootPartition, async (_fs) => {
|
||||
return await promisify(_fs.writeFile)(
|
||||
await _fs.promises.writeFile(
|
||||
`${this.CONNECTIONS_FOLDER}/resin-wifi`,
|
||||
this.CONNECTION_FILE,
|
||||
);
|
||||
});
|
||||
}
|
||||
return await this.getConfigurationSchema(bootPartition, connectionFileName);
|
||||
return this.getConfigurationSchema(bootPartition, connectionFileName);
|
||||
}
|
||||
|
||||
async removeHostname(schema: any) {
|
||||
|
@ -132,7 +132,7 @@ export default class LoginCmd extends Command {
|
||||
// We can safely assume this won't be undefined as doLogin will throw if this call fails
|
||||
// We also don't need to worry too much about the amount of calls to whoami
|
||||
// as these are cached by the SDK
|
||||
const whoamiResult = (await balena.auth.whoami()) as WhoamiResult;
|
||||
const whoamiResult = (await balena.auth.whoami())!;
|
||||
|
||||
if (whoamiResult.actorType !== 'user' && !options.hideExperimentalWarning) {
|
||||
console.info(stripIndent`
|
||||
@ -168,7 +168,7 @@ ${messages.reachingOut}`);
|
||||
|
||||
async doLogin(
|
||||
loginOptions: FlagsDef,
|
||||
balenaUrl: string = 'balena-cloud.com',
|
||||
balenaUrl = 'balena-cloud.com',
|
||||
token?: string,
|
||||
): Promise<void> {
|
||||
// Token
|
||||
|
@ -18,7 +18,6 @@
|
||||
import { Flags, Args, Command } from '@oclif/core';
|
||||
import type { Interfaces } from '@oclif/core';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import { promisify } from 'util';
|
||||
import * as _ from 'lodash';
|
||||
import { ExpectedError } from '../../errors';
|
||||
import * as cf from '../../utils/common-flags';
|
||||
@ -292,7 +291,7 @@ export default class OsConfigureCmd extends Command {
|
||||
|
||||
for (const { name, content } of files) {
|
||||
await imagefs.interact(image, bootPartition, async (_fs) => {
|
||||
return await promisify(_fs.writeFile)(
|
||||
await _fs.promises.writeFile(
|
||||
path.join(CONNECTIONS_FOLDER, name),
|
||||
content,
|
||||
);
|
||||
|
@ -37,6 +37,7 @@ import type {
|
||||
Release,
|
||||
} from 'balena-sdk';
|
||||
import type { Preloader } from 'balena-preload';
|
||||
import type * as Fs from 'fs';
|
||||
|
||||
export default class PreloadCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
@ -109,7 +110,7 @@ https://github.com/balena-io-examples/staged-releases\
|
||||
'additional-space': Flags.integer({
|
||||
description:
|
||||
'expand the image by this amount of bytes instead of automatically estimating the required amount',
|
||||
parse: async (x) => parseAsInteger(x, 'additional-space'),
|
||||
parse: (x) => parseAsInteger(x, 'additional-space'),
|
||||
}),
|
||||
'add-certificate': Flags.string({
|
||||
description: `\
|
||||
@ -126,7 +127,7 @@ Can be repeated to add multiple certificates.\
|
||||
dockerPort: Flags.integer({
|
||||
description:
|
||||
'Docker daemon TCP port number (hint: 2375 for balena devices)',
|
||||
parse: async (p) => parseAsInteger(p, 'dockerPort'),
|
||||
parse: (p) => parseAsInteger(p, 'dockerPort'),
|
||||
}),
|
||||
};
|
||||
|
||||
@ -155,12 +156,48 @@ Can be repeated to add multiple certificates.\
|
||||
------------------------------------------------------------------------------
|
||||
`);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
throw new ExpectedError(
|
||||
`The provided image path does not exist: ${params.image}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that image is not enabled for secure boot. First, confirm it is
|
||||
// a secure boot image with a .sig file in the /opt directory of the rootA
|
||||
// partition. For example, below are contents for generic-amd64 device type:
|
||||
// $ ls -l opt
|
||||
// total 864696
|
||||
// -rw-r--r-- 1 root root 2378170368 Mar 26 09:14 balena-image-generic-amd64.balenaos-img
|
||||
// -rw-r--r-- 1 root root 512 Mar 9 2018 balena-image-generic-amd64.balenaos-img.sig
|
||||
const { explorePartition, BalenaPartition } = await import(
|
||||
'../../utils/image-contents'
|
||||
);
|
||||
const isSecureBoot = await explorePartition<boolean>(
|
||||
params.image,
|
||||
BalenaPartition.ROOTA,
|
||||
async (fs: typeof Fs): Promise<boolean> => {
|
||||
try {
|
||||
const files = await fs.promises.readdir('/opt');
|
||||
return files.some((el) => el.endsWith('balenaos-img.sig'));
|
||||
} catch {
|
||||
// Typically one of:
|
||||
// - Error: No such file or directory
|
||||
// - Error: Unsupported filesystem.
|
||||
// - ErrnoException: node_ext2fs_open ENOENT (44) args: [5261576,5268064,"r",0]
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
// Next verify that config.json enables secureboot.
|
||||
if (isSecureBoot) {
|
||||
const { read } = await import('balena-config-json');
|
||||
const config = await read(params.image, '');
|
||||
if (config.installer?.secureboot === true) {
|
||||
throw new ExpectedError("Can't preload image with secure boot enabled");
|
||||
}
|
||||
}
|
||||
|
||||
// balena-preload currently does not work with numerical app IDs
|
||||
// Load app here, and use app slug from hereon
|
||||
const fleetSlug: string | undefined = options.fleet
|
||||
@ -192,11 +229,11 @@ Can be repeated to add multiple certificates.\
|
||||
event.name,
|
||||
));
|
||||
if (event.action === 'start') {
|
||||
return spinner.start();
|
||||
} else {
|
||||
console.log();
|
||||
return spinner.stop();
|
||||
spinner.start();
|
||||
return;
|
||||
}
|
||||
console.log();
|
||||
spinner.stop();
|
||||
};
|
||||
|
||||
const commit = this.isCurrentCommit(options.commit || '')
|
||||
@ -295,7 +332,7 @@ Can be repeated to add multiple certificates.\
|
||||
owns__release: {
|
||||
$select: ['id', 'commit', 'end_timestamp', 'composition'],
|
||||
$expand: {
|
||||
contains__image: {
|
||||
release_image: {
|
||||
$select: ['image'],
|
||||
$expand: {
|
||||
image: {
|
||||
|
@ -24,7 +24,7 @@ import { tryAsInteger } from '../../utils/validation';
|
||||
import { jsonInfo } from '../../utils/messages';
|
||||
|
||||
export const commitOrIdArg = Args.custom({
|
||||
parse: async (commitOrId: string) => tryAsInteger(commitOrId),
|
||||
parse: tryAsInteger,
|
||||
});
|
||||
|
||||
type FlagsDef = Interfaces.InferredFlags<typeof ReleaseCmd.flags>;
|
||||
|
@ -34,7 +34,7 @@ export default class SSHKeyCmd extends Command {
|
||||
public static args = {
|
||||
id: Args.integer({
|
||||
description: 'balenaCloud ID for the SSH key',
|
||||
parse: async (x) => parseAsInteger(x, 'id'),
|
||||
parse: (x) => parseAsInteger(x, 'id'),
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ export default class SSHKeyRmCmd extends Command {
|
||||
public static args = {
|
||||
id: Args.integer({
|
||||
description: 'balenaCloud ID for the SSH key',
|
||||
parse: async (x) => parseAsInteger(x, 'id'),
|
||||
parse: (x) => parseAsInteger(x, 'id'),
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
@ -69,10 +69,9 @@ export default class VersionCmd extends Command {
|
||||
const { flags: options } = await this.parse(VersionCmd);
|
||||
const versions: JsonVersions = {
|
||||
'balena-cli': (await import('../../../package.json')).version,
|
||||
'Node.js':
|
||||
process.version && process.version.startsWith('v')
|
||||
? process.version.slice(1)
|
||||
: process.version,
|
||||
'Node.js': process.version.startsWith('v')
|
||||
? process.version.slice(1)
|
||||
: process.version,
|
||||
};
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(versions, null, 4));
|
||||
|
@ -93,7 +93,7 @@ function interpret(error: Error): string {
|
||||
|
||||
if (hasCode(error)) {
|
||||
const errorCodeHandler = messages[error.code];
|
||||
const message = errorCodeHandler && errorCodeHandler(error);
|
||||
const message = errorCodeHandler?.(error);
|
||||
|
||||
if (message) {
|
||||
return message;
|
||||
@ -229,7 +229,7 @@ async function sentryCaptureException(error: Error) {
|
||||
Sentry.captureException(error);
|
||||
try {
|
||||
await Sentry.close(1000);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
if (process.env.DEBUG) {
|
||||
console.error('[debug] Timeout reporting error to sentry.io');
|
||||
}
|
||||
|
@ -209,12 +209,12 @@ See: https://git.io/JRHUW#deprecation-policy`,
|
||||
return indent(body, 2);
|
||||
}
|
||||
|
||||
protected formatDescription(desc: string = '') {
|
||||
protected formatDescription(desc = '') {
|
||||
const chalk = getChalk();
|
||||
|
||||
desc = desc.split('\n')[0];
|
||||
// Remove any ending .
|
||||
if (desc[desc.length - 1] === '.') {
|
||||
if (desc.endsWith('.')) {
|
||||
desc = desc.substring(0, desc.length - 1);
|
||||
}
|
||||
// Lowercase first letter if second char is lowercase, to preserve e.g. 'SSH ...')
|
||||
|
@ -103,7 +103,7 @@ const hook: Hook<'prerun'> = async function (options) {
|
||||
.offlineCompatible ?? DEFAULT_OFFLINE_COMPATIBLE
|
||||
)
|
||||
) {
|
||||
await checkNotUsingOfflineMode();
|
||||
checkNotUsingOfflineMode();
|
||||
}
|
||||
} catch (error) {
|
||||
this.error(error);
|
||||
|
@ -48,7 +48,7 @@ export async function preparseArgs(argv: string[]): Promise<string[]> {
|
||||
if (
|
||||
cmdSlice.length > 1 &&
|
||||
cmdSlice[0] === 'help' &&
|
||||
cmdSlice[1][0] !== '-'
|
||||
!cmdSlice[1].startsWith('-')
|
||||
) {
|
||||
cmdSlice.shift();
|
||||
cmdSlice.push('--help');
|
||||
|
@ -164,9 +164,8 @@ export async function downloadOSImage(
|
||||
stream.on('progress', (state: any) => {
|
||||
if (state != null) {
|
||||
return bar.update(state);
|
||||
} else {
|
||||
return spinner.start();
|
||||
}
|
||||
spinner.start();
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
|
@ -128,6 +128,7 @@ export const createRelease = async function (
|
||||
draft: boolean,
|
||||
semver: string | undefined,
|
||||
contract: import('@balena/compose/dist/release/models').ReleaseModel['contract'],
|
||||
imgDescriptors: ImageDescriptor[],
|
||||
): Promise<Release> {
|
||||
const _ = require('lodash') as typeof import('lodash');
|
||||
const crypto = require('crypto') as typeof import('crypto');
|
||||
@ -167,6 +168,7 @@ export const createRelease = async function (
|
||||
semver,
|
||||
is_final: !draft,
|
||||
contract,
|
||||
imgDescriptors,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -240,7 +242,7 @@ export const getPreviousRepos = (
|
||||
status: 'success',
|
||||
},
|
||||
$expand: {
|
||||
contains__image: {
|
||||
release_image: {
|
||||
$select: 'image',
|
||||
$expand: { image: { $select: 'is_stored_at__image_location' } },
|
||||
},
|
||||
@ -252,7 +254,7 @@ export const getPreviousRepos = (
|
||||
.then(function (release) {
|
||||
// grab all images from the latest release, return all image locations in the registry
|
||||
if (release.length > 0) {
|
||||
const images = release[0].contains__image as Array<{
|
||||
const images = release[0].release_image as Array<{
|
||||
image: [SDK.Image];
|
||||
}>;
|
||||
const { getRegistryAndName } =
|
||||
@ -386,7 +388,7 @@ export class BuildProgressUI implements Renderer {
|
||||
.map(function (service) {
|
||||
const stream = through.obj(function (event, _enc, cb) {
|
||||
eventHandler(service, event);
|
||||
return cb();
|
||||
cb();
|
||||
});
|
||||
stream.pipe(tty.stream, { end: false });
|
||||
return [service, stream];
|
||||
@ -471,17 +473,20 @@ export class BuildProgressUI implements Renderer {
|
||||
const { status, progress, error } = serviceToDataMap[service] ?? {};
|
||||
if (error) {
|
||||
return `${error}`;
|
||||
} else if (progress) {
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
const bar = renderProgressBar(progress, 20);
|
||||
if (status) {
|
||||
return `${bar} ${status}`;
|
||||
}
|
||||
return `${bar}`;
|
||||
} else if (status) {
|
||||
return `${status}`;
|
||||
} else {
|
||||
return 'Waiting...';
|
||||
return bar;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
return 'Waiting...';
|
||||
})
|
||||
.map((data, index) => [services[index], data])
|
||||
.fromPairs()
|
||||
@ -552,7 +557,7 @@ export class BuildProgressInline implements Renderer {
|
||||
.map(function (service) {
|
||||
const stream = through.obj(function (event, _enc, cb) {
|
||||
eventHandler(service, event);
|
||||
return cb();
|
||||
cb();
|
||||
});
|
||||
stream.pipe(outStream, { end: false });
|
||||
return [service, stream];
|
||||
@ -606,11 +611,11 @@ export class BuildProgressInline implements Renderer {
|
||||
const { status, error } = event;
|
||||
if (error) {
|
||||
return `${error}`;
|
||||
} else if (status) {
|
||||
return `${status}`;
|
||||
} else {
|
||||
return 'Waiting...';
|
||||
}
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
return 'Waiting...';
|
||||
})();
|
||||
|
||||
const prefix = _.padEnd(getChalk().bold(service), this._prefixWidth);
|
||||
|
@ -966,7 +966,7 @@ export async function makeBuildTasks(
|
||||
deviceInfo: DeviceInfo,
|
||||
logger: Logger,
|
||||
projectName: string,
|
||||
releaseHash: string = 'unavailable',
|
||||
releaseHash = 'unavailable',
|
||||
preprocessHook?: (dockerfile: string) => string,
|
||||
): Promise<MultiBuild.BuildTask[]> {
|
||||
const multiBuild = await import('@balena/compose/dist/multibuild');
|
||||
@ -1375,6 +1375,7 @@ export async function deployProject(
|
||||
skipLogUpload: boolean,
|
||||
projectPath: string,
|
||||
isDraft: boolean,
|
||||
imgDescriptors: ImageDescriptor[],
|
||||
): Promise<import('@balena/compose/dist/release/models').ReleaseModel> {
|
||||
const releaseMod = await import('@balena/compose/dist/release');
|
||||
const { createRelease, tagServiceImages } = await import('./compose');
|
||||
@ -1405,6 +1406,7 @@ export async function deployProject(
|
||||
isDraft,
|
||||
contract?.version,
|
||||
contract,
|
||||
imgDescriptors,
|
||||
),
|
||||
);
|
||||
const { client: pineClient, release, serviceImages } = $release;
|
||||
@ -1492,7 +1494,7 @@ export function createRunLoop(tick: (...args: any[]) => void) {
|
||||
},
|
||||
end() {
|
||||
clearInterval(timerId);
|
||||
return runloop.onEnd();
|
||||
runloop.onEnd();
|
||||
},
|
||||
};
|
||||
return runloop;
|
||||
@ -1549,7 +1551,7 @@ function dropEmptyLinesStream() {
|
||||
if (str.trim()) {
|
||||
this.push(str);
|
||||
}
|
||||
return cb();
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1570,7 +1572,7 @@ function buildLogCapture(objectMode: boolean, buffer: string[]) {
|
||||
buffer.push(data);
|
||||
}
|
||||
|
||||
return cb(null, data);
|
||||
cb(null, data);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1585,14 +1587,16 @@ function buildProgressAdapter(inline: boolean) {
|
||||
|
||||
return through({ objectMode: true }, function (str, _enc, cb) {
|
||||
if (str == null) {
|
||||
return cb(null, str);
|
||||
cb(null, str);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inline) {
|
||||
return cb(null, { status: str });
|
||||
cb(null, { status: str });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^Successfully tagged /.test(str)) {
|
||||
if (!str.startsWith('Successfully tagged ')) {
|
||||
const match = stepRegex.exec(str);
|
||||
if (match) {
|
||||
step = match[1];
|
||||
@ -1607,7 +1611,7 @@ function buildProgressAdapter(inline: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
return cb(null, { status: str, progress });
|
||||
cb(null, { status: str, progress });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import * as semver from 'balena-semver';
|
||||
import { getBalenaSdk, stripIndent } from './lazy';
|
||||
|
||||
export interface ImgConfig {
|
||||
@ -81,7 +80,7 @@ export async function generateApplicationConfig(
|
||||
)) as ImgConfig;
|
||||
|
||||
// merge sshKeys to config, when they have been specified
|
||||
if (options.os && options.os.sshKeys) {
|
||||
if (options.os?.sshKeys) {
|
||||
// Create config.os object if it does not exist
|
||||
config.os = config.os ? config.os : {};
|
||||
config.os.sshKeys = config.os.sshKeys
|
||||
@ -122,16 +121,10 @@ export function generateDeviceConfig(
|
||||
// os.getConfig always returns a config for an app
|
||||
delete config.apiKey;
|
||||
|
||||
if (deviceApiKey == null && semver.satisfies(options.version, '<2.0.3')) {
|
||||
config.apiKey = await sdk.models.application.generateApiKey(
|
||||
application.id,
|
||||
);
|
||||
} else {
|
||||
config.deviceApiKey =
|
||||
typeof deviceApiKey === 'string' && deviceApiKey
|
||||
? deviceApiKey
|
||||
: await sdk.models.device.generateDeviceKey(device.uuid);
|
||||
}
|
||||
config.deviceApiKey =
|
||||
typeof deviceApiKey === 'string' && deviceApiKey
|
||||
? deviceApiKey
|
||||
: await sdk.models.device.generateDeviceKey(device.uuid);
|
||||
|
||||
return config;
|
||||
})
|
||||
|
@ -86,7 +86,7 @@ const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
||||
obj = JSON.parse(data);
|
||||
} catch (e) {
|
||||
logger.logError('Error parsing reply from remote side');
|
||||
reject(e);
|
||||
reject(e as Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ export const deployLegacy = async function (
|
||||
username,
|
||||
appName,
|
||||
]);
|
||||
await uploadLogs(...args);
|
||||
uploadLogs(...args);
|
||||
}
|
||||
|
||||
return buildId;
|
||||
|
@ -74,7 +74,7 @@ export class DeviceAPI {
|
||||
public constructor(
|
||||
private logger: Logger,
|
||||
addr: string,
|
||||
port: number = 48484,
|
||||
port = 48484,
|
||||
) {
|
||||
this.deviceAddress = `http://${addr}:${port}/`;
|
||||
}
|
||||
@ -201,7 +201,7 @@ export class DeviceAPI {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = request.get(url);
|
||||
|
||||
req.on('error', reject).on('response', async (res) => {
|
||||
req.on('error', reject).on('response', (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(
|
||||
new ApiErrors.DeviceAPIError(
|
||||
@ -213,7 +213,7 @@ export class DeviceAPI {
|
||||
try {
|
||||
res.socket.setKeepAlive(true, 1000);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
reject(error as Error);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
@ -238,7 +238,7 @@ export class DeviceAPI {
|
||||
if (_.isObject(opts) && (opts as ObjectWithUrl).url != null) {
|
||||
// the `as string` shouldn't be necessary, but the type system
|
||||
// is getting a little confused
|
||||
url = (opts as ObjectWithUrl).url as string;
|
||||
url = (opts as ObjectWithUrl).url!;
|
||||
} else if (typeof opts === 'string') {
|
||||
url = opts;
|
||||
}
|
||||
@ -252,21 +252,26 @@ export class DeviceAPI {
|
||||
return await new Promise((resolve, reject) => {
|
||||
return request(opts, (err, response, body) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
reject(err as Error);
|
||||
return;
|
||||
}
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
return resolve(body);
|
||||
case 400:
|
||||
return reject(
|
||||
new ApiErrors.BadRequestDeviceAPIError(body.message),
|
||||
);
|
||||
case 503:
|
||||
return reject(
|
||||
new ApiErrors.ServiceUnavailableAPIError(body.message),
|
||||
);
|
||||
default:
|
||||
return reject(new ApiErrors.DeviceAPIError(body.message));
|
||||
case 200: {
|
||||
resolve(body);
|
||||
return;
|
||||
}
|
||||
case 400: {
|
||||
reject(new ApiErrors.BadRequestDeviceAPIError(body.message));
|
||||
return;
|
||||
}
|
||||
case 503: {
|
||||
reject(new ApiErrors.ServiceUnavailableAPIError(body.message));
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
reject(new ApiErrors.DeviceAPIError(body.message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -74,11 +74,11 @@ interface ParsedEnvironment {
|
||||
[serviceName: string]: { [key: string]: string };
|
||||
}
|
||||
|
||||
async function environmentFromInput(
|
||||
function environmentFromInput(
|
||||
envs: string[],
|
||||
serviceNames: string[],
|
||||
logger: Logger,
|
||||
): Promise<ParsedEnvironment> {
|
||||
): ParsedEnvironment {
|
||||
// A normal environment variable regex, with an added part
|
||||
// to find a colon followed servicename at the start
|
||||
const varRegex = /^(?:([^\s:]+):)?([^\s]+?)=(.*)$/;
|
||||
@ -143,7 +143,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
try {
|
||||
globalLogger.logDebug('Checking we can access device');
|
||||
await api.ping();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
throw new ExpectedError(stripIndent`
|
||||
Could not communicate with device supervisor at address ${opts.deviceHost}:${port}.
|
||||
Device may not have local mode enabled. Check with:
|
||||
@ -191,10 +191,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
});
|
||||
|
||||
// Attempt to attach to the device's docker daemon
|
||||
const docker = connectToDocker(
|
||||
opts.deviceHost,
|
||||
opts.devicePort != null ? opts.devicePort : 2375,
|
||||
);
|
||||
const docker = connectToDocker(opts.deviceHost, opts.devicePort ?? 2375);
|
||||
|
||||
await checkBuildSecretsRequirements(docker, opts.source);
|
||||
globalLogger.logDebug('Tarring all non-ignored files...');
|
||||
@ -231,7 +228,7 @@ export async function deployToDevice(opts: DeviceDeployOptions): Promise<void> {
|
||||
// Print a newline to clearly separate build time and runtime
|
||||
console.log();
|
||||
|
||||
const envs = await environmentFromInput(
|
||||
const envs = environmentFromInput(
|
||||
opts.env,
|
||||
Object.getOwnPropertyNames(project.composition.services),
|
||||
globalLogger,
|
||||
@ -388,7 +385,7 @@ async function performBuilds(
|
||||
);
|
||||
|
||||
// Check for failures
|
||||
await inspectBuildResults(localImages);
|
||||
inspectBuildResults(localImages);
|
||||
|
||||
const imagesToRemove: string[] = [];
|
||||
|
||||
@ -497,7 +494,7 @@ export async function rebuildSingleTask(
|
||||
}
|
||||
|
||||
await assignDockerBuildOpts(docker, [task], opts);
|
||||
await assignOutputHandlers([task], logger, logHandler);
|
||||
assignOutputHandlers([task], logger, logHandler);
|
||||
|
||||
const [localImage] = await multibuild.performBuilds(
|
||||
[task],
|
||||
@ -568,7 +565,7 @@ async function assignDockerBuildOpts(
|
||||
globalLogger.logDebug(`Using ${images.length} on-device images for cache...`);
|
||||
|
||||
await Promise.all(
|
||||
buildTasks.map(async (task: BuildTask) => {
|
||||
buildTasks.map((task: BuildTask) => {
|
||||
task.dockerOpts = {
|
||||
...(task.dockerOpts || {}),
|
||||
...{
|
||||
@ -666,7 +663,7 @@ export function generateTargetState(
|
||||
return targetState;
|
||||
}
|
||||
|
||||
async function inspectBuildResults(images: LocalImage[]): Promise<void> {
|
||||
function inspectBuildResults(images: LocalImage[]): void {
|
||||
const failures: LocalPushErrors.BuildFailure[] = [];
|
||||
|
||||
_.each(images, (image) => {
|
||||
@ -679,6 +676,6 @@ async function inspectBuildResults(images: LocalImage[]): Promise<void> {
|
||||
});
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new LocalPushErrors.BuildError(failures).toString();
|
||||
throw new LocalPushErrors.BuildError(failures);
|
||||
}
|
||||
}
|
||||
|
@ -191,8 +191,8 @@ export class LivepushManager {
|
||||
);
|
||||
const eventQueue = this.updateEventsWaiting[$serviceName];
|
||||
eventQueue.push(changedPath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.getDebouncedEventHandler($serviceName)();
|
||||
|
||||
void this.getDebouncedEventHandler($serviceName)();
|
||||
};
|
||||
|
||||
const monitor = this.setupFilesystemWatcher(
|
||||
@ -252,7 +252,7 @@ export class LivepushManager {
|
||||
try {
|
||||
// sync because chokidar defines a sync interface
|
||||
stats = fs.lstatSync(filePath);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// OK: the file may have been deleted. See also:
|
||||
// https://github.com/paulmillr/chokidar/blob/3.4.3/lib/fsevents-handler.js#L326-L328
|
||||
// https://github.com/paulmillr/chokidar/blob/3.4.3/lib/nodefs-handler.js#L364
|
||||
@ -267,15 +267,15 @@ export class LivepushManager {
|
||||
return dockerignore.ignores(relPath);
|
||||
},
|
||||
});
|
||||
monitor.on('add', (changedPath: string) =>
|
||||
changedPathHandler(serviceName, changedPath),
|
||||
);
|
||||
monitor.on('change', (changedPath: string) =>
|
||||
changedPathHandler(serviceName, changedPath),
|
||||
);
|
||||
monitor.on('unlink', (changedPath: string) =>
|
||||
changedPathHandler(serviceName, changedPath),
|
||||
);
|
||||
monitor.on('add', (changedPath: string) => {
|
||||
changedPathHandler(serviceName, changedPath);
|
||||
});
|
||||
monitor.on('change', (changedPath: string) => {
|
||||
changedPathHandler(serviceName, changedPath);
|
||||
});
|
||||
monitor.on('unlink', (changedPath: string) => {
|
||||
changedPathHandler(serviceName, changedPath);
|
||||
});
|
||||
return monitor;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ export const dockerConnectionCliFlags = {
|
||||
description:
|
||||
'Docker daemon TCP port number (hint: 2375 for balena devices)',
|
||||
char: 'p',
|
||||
parse: async (p) => parseAsInteger(p, 'dockerPort'),
|
||||
parse: (p) => parseAsInteger(p, 'dockerPort'),
|
||||
}),
|
||||
ca: Flags.string({
|
||||
description: 'Docker host TLS certificate authority file',
|
||||
@ -169,9 +169,7 @@ export async function isBalenaEngine(docker: dockerode): Promise<boolean> {
|
||||
// version of balenaEngine, but it was at one point (mis)spelt 'balaena':
|
||||
// https://github.com/balena-os/balena-engine/pull/32/files
|
||||
const dockerVersion = (await docker.version()) as BalenaEngineVersion;
|
||||
return !!(
|
||||
dockerVersion.Engine && dockerVersion.Engine.match(/balena|balaena/)
|
||||
);
|
||||
return !!dockerVersion.Engine?.match(/balena|balaena/);
|
||||
}
|
||||
|
||||
export async function getDocker(
|
||||
|
@ -84,7 +84,7 @@ export async function readFileWithEolConversion(
|
||||
}
|
||||
|
||||
// Analyse encoding
|
||||
const encoding = await detectEncoding(fileBuffer);
|
||||
const encoding = detectEncoding(fileBuffer);
|
||||
|
||||
// Skip further processing of non-convertible encodings
|
||||
if (!CONVERTIBLE_ENCODINGS.includes(encoding)) {
|
||||
@ -132,10 +132,10 @@ export async function readFileWithEolConversion(
|
||||
* @param fileBuffer File contents whose encoding should be detected
|
||||
* @param bytesRead Optional "file size" if smaller than the buffer size
|
||||
*/
|
||||
export async function detectEncoding(
|
||||
export function detectEncoding(
|
||||
fileBuffer: Buffer,
|
||||
bytesRead = fileBuffer.length,
|
||||
): Promise<string> {
|
||||
): string {
|
||||
// empty file
|
||||
if (bytesRead === 0) {
|
||||
return '';
|
||||
|
@ -110,6 +110,27 @@ export async function getManifest(
|
||||
const init = await import('balena-device-init');
|
||||
const sdk = getBalenaSdk();
|
||||
const manifest = await init.getImageManifest(image);
|
||||
if (manifest != null) {
|
||||
const config = manifest.configuration?.config;
|
||||
if (config?.partition != null) {
|
||||
const { getBootPartition } = await import('balena-config-json');
|
||||
// Find the device-type.json property that holds the boot partition number for
|
||||
// this device type (config.partition or config.partition.primary) and overwrite it
|
||||
// with the boot partition number that was found by inspecting the image.
|
||||
// since it's deprecated & no longer updated for newer releases.
|
||||
if (typeof config.partition === 'number') {
|
||||
config.partition = await getBootPartition(image);
|
||||
} else if (config.partition.primary != null) {
|
||||
config.partition.primary = await getBootPartition(image);
|
||||
}
|
||||
// TODO: Add handling for when we no longer include a `config.partition` at all.
|
||||
}
|
||||
} else {
|
||||
// TODO: Change this in the next major to throw, after confirming that this works for all supported OS versions.
|
||||
console.error(
|
||||
`[warn] Error while finding a device-type.json on the provided image path. Attempting to fetch from the API.`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
manifest != null &&
|
||||
manifest.slug !== deviceType &&
|
||||
@ -281,8 +302,7 @@ export function isWindowsComExeShell() {
|
||||
// neither bash nor sh (e.g. not MSYS, MSYS2, Cygwin, WSL)
|
||||
process.env.SHELL == null &&
|
||||
// Windows cmd.exe or PowerShell
|
||||
process.env.ComSpec != null &&
|
||||
process.env.ComSpec.endsWith('cmd.exe')
|
||||
process.env.ComSpec?.endsWith('cmd.exe')
|
||||
);
|
||||
}
|
||||
|
||||
@ -366,7 +386,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
|
||||
let url: InstanceType<typeof URL>;
|
||||
try {
|
||||
url = new URL(proxyUrl);
|
||||
} catch (_e) {
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
@ -469,7 +489,7 @@ export function pickAndRename<T extends Dictionary<any>>(
|
||||
let renameFrom = f;
|
||||
let renameTo = f;
|
||||
const match = f.match(/(?<from>\S+)\s+=>\s+(?<to>\S+)/);
|
||||
if (match && match.groups) {
|
||||
if (match?.groups) {
|
||||
renameFrom = match.groups.from;
|
||||
renameTo = match.groups.to;
|
||||
}
|
||||
|
69
src/utils/image-contents.ts
Normal file
69
src/utils/image-contents.ts
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
// Utilities to explore the contents in a balenaOS image.
|
||||
|
||||
import * as imagefs from 'balena-image-fs';
|
||||
import * as filedisk from 'file-disk';
|
||||
import { getPartitions } from 'partitioninfo';
|
||||
import type * as Fs from 'fs';
|
||||
|
||||
/**
|
||||
* @summary IDs for the standard balenaOS partitions
|
||||
* @description Values are the base name for a partition on disk
|
||||
*/
|
||||
export enum BalenaPartition {
|
||||
BOOT = 'boot',
|
||||
ROOTA = 'rootA',
|
||||
ROOTB = 'rootB',
|
||||
STATE = 'state',
|
||||
DATA = 'data',
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Allow a provided function to explore the contents of one of the well-known
|
||||
* partitions of a balenaOS image
|
||||
*
|
||||
* @param {string} imagePath - pathname of image for search
|
||||
* @param {BalenaPartition} partitionId - partition to find
|
||||
* @param {(fs) => Promise<T>} - function for exploration
|
||||
* @returns {T}
|
||||
*/
|
||||
export async function explorePartition<T>(
|
||||
imagePath: string,
|
||||
partitionId: BalenaPartition,
|
||||
exploreFn: (fs: typeof Fs) => Promise<T>,
|
||||
): Promise<T> {
|
||||
return await filedisk.withOpenFile(imagePath, 'r', async (handle) => {
|
||||
const disk = new filedisk.FileDisk(handle, true, false, false);
|
||||
const partitionInfo = await getPartitions(disk, {
|
||||
includeExtended: false,
|
||||
getLogical: true,
|
||||
});
|
||||
|
||||
const findResult = await imagefs.findPartition(disk, partitionInfo, [
|
||||
`resin-${partitionId}`,
|
||||
`flash-${partitionId}`,
|
||||
`balena-${partitionId}`,
|
||||
]);
|
||||
if (findResult == null) {
|
||||
throw new Error(`Can't find partition for ${partitionId}`);
|
||||
}
|
||||
|
||||
return await imagefs.interact<T>(disk, findResult.index, exploreFn);
|
||||
});
|
||||
}
|
@ -76,38 +76,28 @@ export const getImagePath = async (deviceType: string, version?: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Determine if a device image is fresh
|
||||
* @summary Determine if a device image is cached
|
||||
*
|
||||
* @description
|
||||
* If the device image does not exist, return false.
|
||||
*
|
||||
* @param {String} deviceType - device type slug or alias
|
||||
* @param {String} version - the exact balenaOS version number
|
||||
* @returns {Promise<Boolean>} is image fresh
|
||||
* @returns {Promise<Boolean>} is image cached
|
||||
*
|
||||
* @example
|
||||
* isImageFresh('raspberry-pi', '1.2.3').then (isFresh) ->
|
||||
* if isFresh
|
||||
* console.log('The Raspberry Pi image v1.2.3 is fresh!')
|
||||
* isImageCached ('raspberry-pi', '1.2.3').then (isCached) ->
|
||||
* if isCached
|
||||
* console.log('The Raspberry Pi image v1.2.3 is cached!')
|
||||
*/
|
||||
export const isImageFresh = async (deviceType: string, version: string) => {
|
||||
export const isImageCached = async (deviceType: string, version: string) => {
|
||||
const imagePath = await getImagePath(deviceType, version);
|
||||
let createdDate;
|
||||
try {
|
||||
createdDate = await getFileCreatedDate(imagePath);
|
||||
const createdDate = await getFileCreatedDate(imagePath);
|
||||
return createdDate != null;
|
||||
} catch {
|
||||
// Swallow errors from getFileCreatedTime.
|
||||
}
|
||||
if (createdDate == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const balena = getBalenaSdk();
|
||||
const lastModifiedDate = await balena.models.os.getLastModified(
|
||||
deviceType,
|
||||
version,
|
||||
);
|
||||
return lastModifiedDate < createdDate;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -118,7 +108,7 @@ export const isImageFresh = async (deviceType: string, version: string) => {
|
||||
*/
|
||||
export const isESR = (version: string) => {
|
||||
const match = version.match(/^v?(\d+)\.\d+\.\d+/);
|
||||
const major = parseInt((match && match[1]) || '', 10);
|
||||
const major = parseInt(match?.[1] || '', 10);
|
||||
return major >= 2018; // note: (NaN >= 2018) is false
|
||||
};
|
||||
|
||||
@ -286,7 +276,7 @@ export const getStream = async (
|
||||
versionOrRange = 'latest';
|
||||
}
|
||||
const version = await resolveVersion(deviceType, versionOrRange);
|
||||
const isFresh = await isImageFresh(deviceType, version);
|
||||
const isFresh = await isImageCached(deviceType, version);
|
||||
const $stream = isFresh
|
||||
? await getImage(deviceType, version)
|
||||
: await doDownload({ ...options, deviceType, version });
|
||||
|
@ -21,6 +21,7 @@ import type { Chalk } from 'chalk';
|
||||
import type * as visuals from 'resin-cli-visuals';
|
||||
import type * as CliForm from 'resin-cli-form';
|
||||
import type { ux } from '@oclif/core';
|
||||
import { version } from '../../package.json';
|
||||
|
||||
// Equivalent of _.once but avoiding the need to import lodash for lazy deps
|
||||
const once = <T>(fn: () => T) => {
|
||||
@ -43,9 +44,26 @@ export const onceAsync = <T>(fn: () => Promise<T>) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getBalenaSdk = once(() =>
|
||||
(require('balena-sdk') as typeof BalenaSdk).fromSharedOptions(),
|
||||
);
|
||||
const cliXBalenaClientHeaderInterceptor: BalenaSdk.Interceptor = {
|
||||
request($request) {
|
||||
if ($request.headers['X-Balena-Client']) {
|
||||
// We intentionally overwrite the sdk version string from the header
|
||||
// to conserve bandwidth. We only do that when the SDK already has specified
|
||||
// the X-Balena-Client header, since that signals that this is a safe url to
|
||||
// include the extra header and will not cause CORS errors.
|
||||
$request.headers['X-Balena-Client'] = `balena-cli/${version}`;
|
||||
}
|
||||
return $request;
|
||||
},
|
||||
};
|
||||
|
||||
export const getBalenaSdk = once(() => {
|
||||
const sdk = (require('balena-sdk') as typeof BalenaSdk).fromSharedOptions();
|
||||
if (!sdk.interceptors.includes(cliXBalenaClientHeaderInterceptor)) {
|
||||
sdk.interceptors.push(cliXBalenaClientHeaderInterceptor);
|
||||
}
|
||||
return sdk;
|
||||
});
|
||||
|
||||
export const getVisuals = once(
|
||||
() => require('resin-cli-visuals') as typeof visuals,
|
||||
|
@ -39,7 +39,7 @@ export async function disambiguateReleaseParam(
|
||||
|
||||
// Accepting short hashes of 7,8,9 chars.
|
||||
const possibleUuidHashLength = [7, 8, 9, 32, 40, 62].includes(release.length);
|
||||
const hasLeadingZero = release[0] === '0';
|
||||
const hasLeadingZero = release.startsWith('0');
|
||||
const isOnlyNumerical = /^[0-9]+$/.test(release);
|
||||
|
||||
// Reject non-numerical values with invalid uuid/hash lengths
|
||||
@ -75,17 +75,19 @@ export async function disambiguateReleaseParam(
|
||||
return (await balena.models.release.get(release, { $select: 'id' })).id;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/require-await -- oclif parse functions require a Promise return */
|
||||
/**
|
||||
* Convert to lowercase if looks like slug
|
||||
*/
|
||||
export async function lowercaseIfSlug(s: string) {
|
||||
/* eslint-enable @typescript-eslint/require-await */
|
||||
return s.includes('/') ? s.toLowerCase() : s;
|
||||
}
|
||||
|
||||
export function normalizeOsVersion(version: string) {
|
||||
// Note that `version` may also be 'latest', 'recommended', 'default'
|
||||
if (/^v?\d+\.\d+\.\d+/.test(version)) {
|
||||
if (version[0] === 'v') {
|
||||
if (version.startsWith('v')) {
|
||||
version = version.slice(1);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export function capitanoizeOclifUsage(
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export async function getCommandsFromManifest() {
|
||||
export function getCommandsFromManifest() {
|
||||
const manifest = require('../../oclif.manifest.json');
|
||||
|
||||
if (manifest.commands == null) {
|
||||
|
@ -119,7 +119,7 @@ export const checkLoggedInIf = async (doCheck: boolean) => {
|
||||
*
|
||||
* @throws {NotAvailableInOfflineModeError}
|
||||
*/
|
||||
export const checkNotUsingOfflineMode = async () => {
|
||||
export const checkNotUsingOfflineMode = () => {
|
||||
if (process.env.BALENARC_OFFLINE_MODE) {
|
||||
throw new NotAvailableInOfflineModeError(stripIndent`
|
||||
This command requires an internet connection, and cannot be used in offline mode.
|
||||
|
@ -390,13 +390,12 @@ async function createApplication(
|
||||
try {
|
||||
const userInfo = await sdk.auth.getUserInfo();
|
||||
username = userInfo.username;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
throw new sdk.errors.BalenaNotLoggedIn();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
const applicationName = await new Promise<string>(async (resolve, reject) => {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
const appName = await getCliForm().ask({
|
||||
@ -418,11 +417,13 @@ async function createApplication(
|
||||
'You already have a fleet with that name; please choose another.',
|
||||
);
|
||||
continue;
|
||||
} catch (err) {
|
||||
return resolve(appName);
|
||||
} catch {
|
||||
resolve(appName);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
reject(err as Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -452,9 +453,7 @@ async function generateApplicationConfig(
|
||||
const manifest = await sdk.models.config.getDeviceTypeManifestBySlug(
|
||||
app.is_for__device_type[0].slug,
|
||||
);
|
||||
const opts =
|
||||
manifest.options &&
|
||||
manifest.options.filter((opt) => opt.name !== 'network');
|
||||
const opts = manifest.options?.filter((opt) => opt.name !== 'network');
|
||||
|
||||
const override = {
|
||||
appUpdatePollInterval: options.appUpdatePollInterval,
|
||||
|
@ -50,7 +50,7 @@ export function copyQemu(context: string, arch: string) {
|
||||
.then(() => getQemuPath(arch))
|
||||
.then(
|
||||
(qemu) =>
|
||||
new Promise(function (resolve, reject) {
|
||||
new Promise<void>(function (resolve, reject) {
|
||||
const read = fs.createReadStream(qemu);
|
||||
const write = fs.createWriteStream(binPath);
|
||||
|
||||
@ -114,7 +114,7 @@ async function installQemu(arch: string, qemuPath: string) {
|
||||
stream.resume();
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
reject(err as Error);
|
||||
}
|
||||
});
|
||||
request(qemuUrl)
|
||||
|
@ -110,7 +110,7 @@ export async function startRemoteBuild(
|
||||
const [buildRequest, stream] = await getRemoteBuildStream(build);
|
||||
|
||||
// Setup CTRL-C handler so the user can interrupt the build
|
||||
let cancellationPromise = Promise.resolve();
|
||||
let cancellationPromise: Promise<void> | undefined;
|
||||
const sigintHandler = () => {
|
||||
process.exitCode = 130;
|
||||
console.error('\nReceived SIGINT, cleaning up. Please wait.');
|
||||
@ -246,7 +246,8 @@ function getBuilderMessageHandler(
|
||||
console.error(`[debug] handling message: ${JSON.stringify(obj)}`);
|
||||
}
|
||||
if (obj.type != null && obj.type === 'metadata') {
|
||||
return handleBuilderMetadata(obj, build);
|
||||
handleBuilderMetadata(obj, build);
|
||||
return;
|
||||
}
|
||||
if (obj.message) {
|
||||
readline.clearLine(process.stdout, 0);
|
||||
@ -423,10 +424,20 @@ async function getRemoteBuildStream(
|
||||
stream = buildRequest.pipe(JSONStream.parse('*')) as NodeJS.ReadStream;
|
||||
}
|
||||
stream = stream
|
||||
.once('error', () => uploadSpinner.stop())
|
||||
.once('close', () => uploadSpinner.stop())
|
||||
.once('data', () => uploadSpinner.stop())
|
||||
.once('end', () => uploadSpinner.stop())
|
||||
.once('finish', () => uploadSpinner.stop());
|
||||
.once('error', () => {
|
||||
uploadSpinner.stop();
|
||||
})
|
||||
.once('close', () => {
|
||||
uploadSpinner.stop();
|
||||
})
|
||||
.once('data', () => {
|
||||
uploadSpinner.stop();
|
||||
})
|
||||
.once('end', () => {
|
||||
uploadSpinner.stop();
|
||||
})
|
||||
.once('finish', () => {
|
||||
uploadSpinner.stop();
|
||||
});
|
||||
return [buildRequest, stream];
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export function sshArgsForRemoteCommand({
|
||||
...['-o', 'LogLevel=ERROR'],
|
||||
...['-o', 'StrictHostKeyChecking=no'],
|
||||
...['-o', 'UserKnownHostsFile=/dev/null'],
|
||||
...(proxyCommand && proxyCommand.length
|
||||
...(proxyCommand?.length
|
||||
? ['-o', `ProxyCommand=${proxyCommand.join(' ')}`]
|
||||
: []),
|
||||
`${username}@${hostname}`,
|
||||
@ -155,9 +155,9 @@ export async function runRemoteCommand({
|
||||
[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]),
|
||||
);
|
||||
.on('close', (code, signal) => {
|
||||
resolve([code ?? undefined, signal ?? undefined]);
|
||||
});
|
||||
|
||||
if (ps.stdin && stdin && typeof stdin !== 'string') {
|
||||
stdin.pipe(ps.stdin);
|
||||
@ -272,7 +272,7 @@ export async function getLocalDeviceCmdStdout(
|
||||
export const isRootUserGood = _.memoize(async (hostname: string, port) => {
|
||||
try {
|
||||
await runRemoteCommand({ cmd: 'exit 0', hostname, port, ...stdioIgnore });
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -29,7 +29,11 @@ export function buffer(
|
||||
new Promise(function (resolve, reject) {
|
||||
const fstream = fs.createReadStream(bufferFile);
|
||||
|
||||
fstream.on('open', () => resolve(fstream)).on('error', reject);
|
||||
fstream
|
||||
.on('open', () => {
|
||||
resolve(fstream);
|
||||
})
|
||||
.on('error', reject);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ async function spawnAndPipe(
|
||||
});
|
||||
}
|
||||
|
||||
async function windosuExec(
|
||||
function windosuExec(
|
||||
escapedArgs: string[],
|
||||
stderr?: NodeJS.WritableStream,
|
||||
): Promise<void> {
|
||||
|
@ -42,9 +42,9 @@ export = (stream: NodeJS.WriteStream = process.stdout) => {
|
||||
|
||||
const showCursor = () => stream.write('\u001B[?25h');
|
||||
|
||||
const cursorUp = (rows: number = 0) => stream.write(`\u001B[${rows}A`);
|
||||
const cursorUp = (rows = 0) => stream.write(`\u001B[${rows}A`);
|
||||
|
||||
const cursorDown = (rows: number = 0) => stream.write(`\u001B[${rows}B`);
|
||||
const cursorDown = (rows = 0) => stream.write(`\u001B[${rows}B`);
|
||||
|
||||
const write = (str: string) => stream.write(str);
|
||||
|
||||
|
@ -61,11 +61,11 @@ export const tunnelConnectionToDevice = (
|
||||
client.pipe(remote);
|
||||
remote.pipe(client);
|
||||
remote.on('error', (err) => {
|
||||
console.error('Remote: ' + err);
|
||||
console.error(`Remote: ${err}`);
|
||||
client.end();
|
||||
});
|
||||
client.on('error', (err) => {
|
||||
console.error('Client: ' + err);
|
||||
console.error(`Client: ${err}`);
|
||||
remote.end();
|
||||
});
|
||||
remote.on('close', () => {
|
||||
|
@ -87,7 +87,8 @@ export function looksLikeInteger(input: string) {
|
||||
return /^(?:0|[1-9][0-9]*)$/.test(input);
|
||||
}
|
||||
|
||||
export function parseAsInteger(input: string, paramName?: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/require-await -- oclif parse functions require a Promise return
|
||||
export async function parseAsInteger(input: string, paramName?: string) {
|
||||
if (!looksLikeInteger(input)) {
|
||||
const message =
|
||||
paramName == null
|
||||
@ -100,14 +101,15 @@ export function parseAsInteger(input: string, paramName?: string) {
|
||||
return Number(input);
|
||||
}
|
||||
|
||||
export function tryAsInteger(input: string): number | string {
|
||||
export async function tryAsInteger(input: string): Promise<number | string> {
|
||||
try {
|
||||
return parseAsInteger(input);
|
||||
return await parseAsInteger(input);
|
||||
} catch {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await -- oclif parse functions require a Promise return
|
||||
export async function parseAsLocalHostnameOrIp(input: string) {
|
||||
if (input && !validateLocalHostnameOrIp(input)) {
|
||||
throw new ExpectedError(
|
||||
|
@ -31,7 +31,7 @@ chai.use(chaiAsPromised);
|
||||
|
||||
const { expect } = chai;
|
||||
|
||||
async function getPage(name: string): Promise<string> {
|
||||
function getPage(name: string): string {
|
||||
const pagePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
@ -88,7 +88,7 @@ describe('Login server:', function () {
|
||||
expect(body).to.equal(opt.expectedBody);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
reject(err as Error);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -134,7 +134,7 @@ describe('Login server:', function () {
|
||||
describe('given the token authenticates with the server', function () {
|
||||
beforeEach(function () {
|
||||
this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid');
|
||||
this.loginIfTokenValidStub.returns(Promise.resolve(true));
|
||||
this.loginIfTokenValidStub.resolves(true);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -143,7 +143,7 @@ describe('Login server:', function () {
|
||||
|
||||
it('should eventually be the token', async () => {
|
||||
await testLogin({
|
||||
expectedBody: await getPage('success'),
|
||||
expectedBody: getPage('success'),
|
||||
expectedStatusCode: 200,
|
||||
expectedToken: tokens.johndoe.token,
|
||||
});
|
||||
@ -153,7 +153,7 @@ describe('Login server:', function () {
|
||||
describe('given the token does not authenticate with the server', function () {
|
||||
beforeEach(function () {
|
||||
this.loginIfTokenValidStub = sinon.stub(utils, 'loginIfTokenValid');
|
||||
return this.loginIfTokenValidStub.returns(Promise.resolve(false));
|
||||
return this.loginIfTokenValidStub.resolves(false);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -162,7 +162,7 @@ describe('Login server:', function () {
|
||||
|
||||
it('should be rejected', async () => {
|
||||
await testLogin({
|
||||
expectedBody: await getPage('error'),
|
||||
expectedBody: getPage('error'),
|
||||
expectedStatusCode: 401,
|
||||
expectedToken: tokens.johndoe.token,
|
||||
expectedErrorMsg: 'Invalid token',
|
||||
@ -171,7 +171,7 @@ describe('Login server:', function () {
|
||||
|
||||
it('should be rejected if no token', async () => {
|
||||
await testLogin({
|
||||
expectedBody: await getPage('error'),
|
||||
expectedBody: getPage('error'),
|
||||
expectedStatusCode: 401,
|
||||
expectedToken: '',
|
||||
expectedErrorMsg: 'No token',
|
||||
@ -180,7 +180,7 @@ describe('Login server:', function () {
|
||||
|
||||
it('should be rejected if token is malformed', async () => {
|
||||
await testLogin({
|
||||
expectedBody: await getPage('error'),
|
||||
expectedBody: getPage('error'),
|
||||
expectedStatusCode: 401,
|
||||
expectedToken: 'asdf',
|
||||
expectedErrorMsg: 'Invalid token',
|
||||
|
@ -267,15 +267,15 @@ describe('balena build', function () {
|
||||
...fsMod,
|
||||
promises: {
|
||||
...fsMod.promises,
|
||||
access: async (p: string) =>
|
||||
access: (p: string) =>
|
||||
p === qemuBinPath ? undefined : fsMod.promises.access(p),
|
||||
stat: async (p: string) =>
|
||||
stat: (p: string) =>
|
||||
p === qemuBinPath ? { size: 1 } : fsMod.promises.stat(p),
|
||||
},
|
||||
});
|
||||
mock(qemuModPath, {
|
||||
...qemuMod,
|
||||
copyQemu: async () => '',
|
||||
copyQemu: () => '',
|
||||
});
|
||||
mock.reRequire('../../build/utils/qemu');
|
||||
docker.expectGetInfo({ OperatingSystem: 'balenaOS 2.44.0+rev1' });
|
||||
|
@ -114,6 +114,14 @@ describe('balena device', function () {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
api.scope
|
||||
.get(
|
||||
/^\/v\d+\/device\?.+&\$select=overall_status,overall_progress,should_be_running__release$/,
|
||||
)
|
||||
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
const { out, err } = await runCommand('device 27fda508c --json');
|
||||
expect(err).to.be.empty;
|
||||
const json = JSON.parse(out.join(''));
|
||||
|
@ -169,8 +169,12 @@ async function startMockSshServer(): Promise<[Server, number]> {
|
||||
console.error(`[debug] mock ssh server: ${msg}`);
|
||||
}
|
||||
};
|
||||
c.on('error', (err) => handler(err.message));
|
||||
c.on('end', () => handler('client disconnected'));
|
||||
c.on('error', (err) => {
|
||||
handler(err.message);
|
||||
});
|
||||
c.on('end', () => {
|
||||
handler('client disconnected');
|
||||
});
|
||||
c.end();
|
||||
});
|
||||
server.on('error', (err) => {
|
||||
|
@ -21,6 +21,8 @@ import * as process from 'process';
|
||||
import { runCommand } from '../../helpers';
|
||||
import { promisify } from 'util';
|
||||
import * as tmp from 'tmp';
|
||||
import type * as $imagefs from 'balena-image-fs';
|
||||
import * as stripIndent from 'common-tags/lib/stripIndent';
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
const tmpNameAsync = promisify(tmp.tmpName);
|
||||
@ -29,30 +31,94 @@ import { BalenaAPIMock } from '../../nock/balena-api-mock';
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
describe('balena os configure', function () {
|
||||
let imagefs: typeof $imagefs;
|
||||
let api: BalenaAPIMock;
|
||||
let tmpPath: string;
|
||||
let tmpDummyPath: string;
|
||||
let tmpMatchingDtJsonPartitionPath: string;
|
||||
let tmpNonMatchingDtJsonPartitionPath: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
before(async function () {
|
||||
// We conditionally import balena-image-fs, since when imported on top level then unrelated tests on win32 failed with:
|
||||
// EPERM: operation not permitted, rename 'C:\Users\RUNNER~1\AppData\Local\Temp\tmp-<...>.inprogress' -> 'C:\Users\RUNNER~1\AppData\Local\Temp\tmp-<...>'
|
||||
// at async Object.rename (node:internal/fs/promises:782:10) {
|
||||
imagefs = await import('balena-image-fs');
|
||||
tmpDummyPath = (await tmpNameAsync()) as string;
|
||||
await fs.copyFile('./tests/test-data/dummy.img', tmpDummyPath);
|
||||
tmpMatchingDtJsonPartitionPath = (await tmpNameAsync()) as string;
|
||||
await fs.copyFile(
|
||||
'./tests/test-data/mock-jetson-nano-6.0.13.with-boot-partition-12.img',
|
||||
tmpMatchingDtJsonPartitionPath,
|
||||
);
|
||||
|
||||
tmpNonMatchingDtJsonPartitionPath = (await tmpNameAsync()) as string;
|
||||
// Create an image with a device-type.json that mentions a non matching boot partition.
|
||||
// We copy the pre-existing image and modify it, since including a separate one
|
||||
// would add 18MB more to the repository.
|
||||
await fs.copyFile(
|
||||
'./tests/test-data/mock-jetson-nano-6.0.13.with-boot-partition-12.img',
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
);
|
||||
await imagefs.interact(
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
12,
|
||||
async (_fs) => {
|
||||
const dtJson = JSON.parse(
|
||||
await _fs.promises.readFile('/device-type.json', {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
);
|
||||
expect(dtJson).to.have.nested.property(
|
||||
'configuration.config.partition',
|
||||
12,
|
||||
);
|
||||
dtJson.configuration.config.partition = 999;
|
||||
await _fs.promises.writeFile(
|
||||
'/device-type.json',
|
||||
JSON.stringify(dtJson),
|
||||
);
|
||||
|
||||
await _fs.promises.writeFile(
|
||||
'/os-release',
|
||||
stripIndent`
|
||||
ID="balena-os"
|
||||
NAME="balenaOS"
|
||||
VERSION="6.1.25"
|
||||
VERSION_ID="6.1.25"
|
||||
PRETTY_NAME="balenaOS 6.1.25"
|
||||
DISTRO_CODENAME="kirkstone"
|
||||
MACHINE="jetson-nano"
|
||||
META_BALENA_VERSION="6.1.25"`,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
api = new BalenaAPIMock();
|
||||
api.expectGetWhoAmI({ optional: true, persist: true });
|
||||
tmpPath = (await tmpNameAsync()) as string;
|
||||
await fs.copyFile('./tests/test-data/dummy.img', tmpPath);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
afterEach(() => {
|
||||
api.done();
|
||||
await fs.unlink(tmpPath);
|
||||
});
|
||||
|
||||
it('should inject a valid config.json file', async () => {
|
||||
after(async () => {
|
||||
await fs.unlink(tmpDummyPath);
|
||||
await fs.unlink(tmpMatchingDtJsonPartitionPath);
|
||||
await fs.unlink(tmpNonMatchingDtJsonPartitionPath);
|
||||
});
|
||||
|
||||
it('should detect the OS version and inject a valid config.json file to a 6.0.13 image with partition 12 as boot & matching device-type.json', async () => {
|
||||
api.expectGetApplication();
|
||||
api.expectGetConfigDeviceTypes();
|
||||
api.expectGetDeviceTypes();
|
||||
// It should not reach to /config or /device-types/v1 but instead find
|
||||
// everything required from the device-type.json in the image.
|
||||
// api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpPath}`,
|
||||
'--device-type raspberrypi3',
|
||||
'--version 2.47.0+rev1',
|
||||
`os configure ${tmpMatchingDtJsonPartitionPath}`,
|
||||
'--device-type jetson-nano',
|
||||
'--fleet testApp',
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
@ -65,9 +131,124 @@ if (process.platform !== 'win32') {
|
||||
expect(err.join('')).to.equal('');
|
||||
|
||||
// confirm the image contains a config.json...
|
||||
const imagefs = await import('balena-image-fs');
|
||||
const config = await imagefs.interact(tmpPath, 1, async (_fs) => {
|
||||
return await promisify(_fs.readFile)('/config.json');
|
||||
const config = await imagefs.interact(
|
||||
tmpMatchingDtJsonPartitionPath,
|
||||
12,
|
||||
async (_fs) => {
|
||||
const dtJson = JSON.parse(
|
||||
await _fs.promises.readFile('/device-type.json', {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
);
|
||||
// confirm that the device-type.json mentions the expected partition
|
||||
expect(dtJson).to.have.nested.property(
|
||||
'configuration.config.partition',
|
||||
12,
|
||||
);
|
||||
return await _fs.promises.readFile('/config.json');
|
||||
},
|
||||
);
|
||||
expect(config).to.not.be.empty;
|
||||
|
||||
// confirm the image has the correct config.json values...
|
||||
const configObj = JSON.parse(config.toString('utf8'));
|
||||
expect(configObj).to.have.property('deviceType', 'jetson-nano');
|
||||
expect(configObj).to.have.property('initialDeviceName', 'testDeviceName');
|
||||
});
|
||||
|
||||
it('should detect the OS version and inject a valid config.json file to a 6.1.25 image with partition 12 as boot & a non-matching device-type.json', async () => {
|
||||
api.expectGetApplication();
|
||||
api.expectGetDeviceTypes();
|
||||
// It should not reach to /config or /device-types/v1 but instead find
|
||||
// everything required from the device-type.json in the image.
|
||||
// api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpNonMatchingDtJsonPartitionPath}`,
|
||||
'--device-type jetson-nano',
|
||||
'--fleet testApp',
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
'--initial-device-name testDeviceName',
|
||||
'--provisioning-key-name testKey',
|
||||
'--provisioning-key-expiry-date 2050-12-12',
|
||||
];
|
||||
|
||||
const { err } = await runCommand(command.join(' '));
|
||||
expect(err.join('')).to.equal('');
|
||||
|
||||
// confirm the image contains a config.json...
|
||||
const config = await imagefs.interact(
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
12,
|
||||
async (_fs) => {
|
||||
const dtJson = JSON.parse(
|
||||
await _fs.promises.readFile('/device-type.json', {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
);
|
||||
// confirm that the device-type.json mentions the expected partition
|
||||
expect(dtJson).to.have.nested.property(
|
||||
'configuration.config.partition',
|
||||
999,
|
||||
);
|
||||
return await _fs.promises.readFile('/config.json');
|
||||
},
|
||||
);
|
||||
expect(config).to.not.be.empty;
|
||||
|
||||
// confirm the image has the correct config.json values...
|
||||
const configObj = JSON.parse(config.toString('utf8'));
|
||||
expect(configObj).to.have.property('deviceType', 'jetson-nano');
|
||||
expect(configObj).to.have.property('initialDeviceName', 'testDeviceName');
|
||||
});
|
||||
|
||||
// TODO: In the next major consider just failing when we can't find a device-types.json in the image.
|
||||
it('should inject a valid config.json file to a dummy image', async () => {
|
||||
api.expectGetApplication();
|
||||
// Since the dummy image doesn't include a device-type.json
|
||||
// we have to reach to the API to fetch the manifest of the device type.
|
||||
api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpDummyPath}`,
|
||||
'--device-type raspberrypi3',
|
||||
'--version 2.47.0+rev1',
|
||||
'--fleet testApp',
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
'--initial-device-name testDeviceName',
|
||||
'--provisioning-key-name testKey',
|
||||
'--provisioning-key-expiry-date 2050-12-12',
|
||||
];
|
||||
|
||||
const { err } = await runCommand(command.join(' '));
|
||||
// Once we replace the dummy.img with one that includes a os-release & device-type.json
|
||||
// then we should be able to change this to expect no errors.
|
||||
expect(
|
||||
err.flatMap((line) => line.split('\n')).filter((line) => line !== ''),
|
||||
).to.deep.equal(
|
||||
stripIndent`
|
||||
[warn] "${tmpDummyPath}":
|
||||
[warn] Found partition table with 1 partitions,
|
||||
[warn] but none with a name/label in ['resin-boot', 'flash-boot', 'balena-boot'].
|
||||
[warn] Will scan all partitions for contents.
|
||||
[warn] "${tmpDummyPath}":
|
||||
[warn] 1 partition(s) found, but none containing file "/device-type.json".
|
||||
[warn] Assuming default boot partition number '1'.
|
||||
[warn] "${tmpDummyPath}":
|
||||
[warn] Could not find a previous "/config.json" file in partition '1'.
|
||||
[warn] Proceeding anyway, but this is unexpected.
|
||||
[warn] Error while finding a device-type.json on the provided image path. Attempting to fetch from the API.`.split(
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
|
||||
// confirm the image contains a config.json...
|
||||
const config = await imagefs.interact(tmpDummyPath, 1, async (_fs) => {
|
||||
return await _fs.promises.readFile('/config.json');
|
||||
});
|
||||
expect(config).to.not.be.empty;
|
||||
|
||||
|
@ -462,7 +462,13 @@ describe('balena push', function () {
|
||||
tmp.setGracefulCleanup();
|
||||
const projectPath = await new Promise<string>((resolve, reject) => {
|
||||
const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true };
|
||||
tmp.dir(opts, (e, p) => (e ? reject(e) : resolve(p)));
|
||||
tmp.dir(opts, (e, p) => {
|
||||
if (e) {
|
||||
reject(e);
|
||||
} else {
|
||||
resolve(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
console.error(`[debug] Temp project dir: ${projectPath}`);
|
||||
|
||||
@ -475,7 +481,7 @@ describe('balena push', function () {
|
||||
try {
|
||||
server.listen(socketPath, resolve);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
reject(e as Error);
|
||||
}
|
||||
});
|
||||
console.error(`[debug] Checking existence of socket at '${socketPath}'`);
|
||||
@ -505,7 +511,13 @@ describe('balena push', function () {
|
||||
|
||||
// Terminate Unix Domain Socket server
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((e) => (e ? reject(e) : resolve()));
|
||||
server.close((e) => {
|
||||
if (e) {
|
||||
reject(e);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
expect(await exists(socketPath), 'Socket existence').to.be.false;
|
||||
|
@ -82,7 +82,7 @@ describe('balena release', function () {
|
||||
expect(err).to.be.empty;
|
||||
const json = JSON.parse(out.join(''));
|
||||
expect(json[0].commit).to.equal('90247b54de4fa7a0a3cbc85e73c68039');
|
||||
expect(json[0].contains__image[0].image[0].start_timestamp).to.equal(
|
||||
expect(json[0].release_image[0].image[0].start_timestamp).to.equal(
|
||||
'2020-01-04T01:13:08.583Z',
|
||||
);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ describe('balena whoami', function () {
|
||||
api = new BalenaAPIMock();
|
||||
});
|
||||
|
||||
this.afterEach(async () => {
|
||||
this.afterEach(() => {
|
||||
// Check all expected api calls have been made and clean up.
|
||||
api.done();
|
||||
});
|
||||
|
@ -26,7 +26,6 @@ import * as tar from 'tar-stream';
|
||||
import { streamToBuffer } from 'tar-utils';
|
||||
import { URL } from 'url';
|
||||
import { diff } from 'deep-object-diff';
|
||||
|
||||
import { makeImageName } from '../build/utils/compose_ts';
|
||||
import { stripIndent } from '../build/utils/lazy';
|
||||
import type { BuilderMock } from './nock/builder-mock';
|
||||
@ -77,13 +76,13 @@ export async function inspectTarStream(
|
||||
type: header.type,
|
||||
};
|
||||
const expected = expectedFiles[header.name];
|
||||
if (expected && expected.testStream) {
|
||||
if (expected?.testStream) {
|
||||
await expected.testStream(header, stream, expected);
|
||||
} else {
|
||||
await defaultTestStream(header, stream, expected, projectPath);
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
reject(err as Error);
|
||||
}
|
||||
next();
|
||||
},
|
||||
@ -144,9 +143,8 @@ export async function expectStreamNoCRLF(
|
||||
_header: tar.Headers,
|
||||
stream: Readable,
|
||||
): Promise<void> {
|
||||
const chai = await import('chai');
|
||||
const buf = await streamToBuffer(stream);
|
||||
await chai.expect(buf.includes('\r\n')).to.be.false;
|
||||
expect(buf.includes('\r\n')).to.be.false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +182,7 @@ export async function testDockerBuildStream(o: {
|
||||
|
||||
o.dockerMock.expectPostBuild({
|
||||
...o,
|
||||
checkURI: async (uri: string) => {
|
||||
checkURI: (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(deepJsonParse(queryParams)).to.have.deep.members(
|
||||
@ -241,7 +239,7 @@ export async function testPushBuildStream(o: {
|
||||
|
||||
o.builderMock.expectPostBuild({
|
||||
...o,
|
||||
checkURI: async (uri: string) => {
|
||||
checkURI: (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(deepJsonParse(queryParams)).to.have.deep.members(
|
||||
|
@ -287,15 +287,14 @@ export function monochrome(text: string): string {
|
||||
*/
|
||||
export function fillTemplate(
|
||||
templateString: string,
|
||||
templateVars: object,
|
||||
templateVars: Record<string, unknown>,
|
||||
): string {
|
||||
const escaped = templateString.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
|
||||
const resolved = new Function(
|
||||
...Object.keys(templateVars),
|
||||
`return \`${escaped}\`;`,
|
||||
).call(null, ...Object.values(templateVars));
|
||||
const unescaped = resolved.replace(/\\`/g, '`').replace(/\\\\/g, '\\');
|
||||
return unescaped;
|
||||
return templateString.replace(/\$\{(\w+)\}/g, (_, key) => {
|
||||
if (key in templateVars) {
|
||||
return String(templateVars[key]);
|
||||
}
|
||||
throw new Error(`Missing template variable: ${key}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,22 +61,25 @@ export class BalenaAPIMock extends NockMock {
|
||||
}
|
||||
|
||||
public expectDownloadConfig(opts: ScopeOpts = {}) {
|
||||
this.optPost('/download-config', opts).reply(
|
||||
200,
|
||||
JSON.parse(`{
|
||||
"applicationId":1301645,
|
||||
"deviceType":"raspberrypi3",
|
||||
"userId":43699,
|
||||
"appUpdatePollInterval":600000,
|
||||
"listenPort":48484,
|
||||
"vpnPort":443,
|
||||
"apiEndpoint":"https://api.balena-cloud.com",
|
||||
"vpnEndpoint":"vpn.balena-cloud.com",
|
||||
"registryEndpoint":"registry2.balena-cloud.com",
|
||||
"deltaEndpoint":"https://delta.balena-cloud.com",
|
||||
"apiKey":"nothingtoseehere"
|
||||
}`),
|
||||
);
|
||||
this.optPost('/download-config', opts).reply(200, (_uri, body) => {
|
||||
let deviceType = 'raspberrypi3';
|
||||
if (typeof body === 'object' && 'deviceType' in body) {
|
||||
deviceType = body.deviceType;
|
||||
}
|
||||
return JSON.parse(`{
|
||||
"applicationId":1301645,
|
||||
"deviceType":"${deviceType}",
|
||||
"userId":43699,
|
||||
"appUpdatePollInterval":600000,
|
||||
"listenPort":48484,
|
||||
"vpnPort":443,
|
||||
"apiEndpoint":"https://api.balena-cloud.com",
|
||||
"vpnEndpoint":"vpn.balena-cloud.com",
|
||||
"registryEndpoint":"registry2.balena-cloud.com",
|
||||
"deltaEndpoint":"https://delta.balena-cloud.com",
|
||||
"apiKey":"nothingtoseehere"
|
||||
}`);
|
||||
});
|
||||
}
|
||||
|
||||
public expectApplicationProvisioning(opts: ScopeOpts = {}) {
|
||||
@ -283,7 +286,7 @@ export class BalenaAPIMock extends NockMock {
|
||||
this.optGet(/^\/v\d+\/service_environment_variable($|\?)/, opts).reply(
|
||||
function (uri, _requestBody) {
|
||||
const match = uri.match(/service_name%20eq%20%27(.+?)%27/);
|
||||
const serviceName = (match && match[1]) || undefined;
|
||||
const serviceName = match?.[1] || undefined;
|
||||
let varArray: any[];
|
||||
if (serviceName) {
|
||||
const varObj = appServiceVarsByService[serviceName];
|
||||
@ -331,7 +334,7 @@ export class BalenaAPIMock extends NockMock {
|
||||
opts,
|
||||
).reply(function (uri, _requestBody) {
|
||||
const match = uri.match(/service_name%20eq%20%27(.+?)%27/);
|
||||
const serviceName = (match && match[1]) || undefined;
|
||||
const serviceName = match?.[1] || undefined;
|
||||
let varArray: any[];
|
||||
if (serviceName) {
|
||||
const varObj = deviceServiceVarsByService[serviceName];
|
||||
|
@ -37,7 +37,7 @@ export class BuilderMock extends NockMock {
|
||||
persist?: boolean;
|
||||
responseBody: any;
|
||||
responseCode: number;
|
||||
checkURI: (uri: string) => Promise<void>;
|
||||
checkURI: (uri: string) => Promise<void> | void;
|
||||
checkBuildRequestBody: (requestBody: string | Buffer) => Promise<void>;
|
||||
}) {
|
||||
this.optPost(/^\/v3\/build($|[(?])/, opts).reply(
|
||||
|
@ -75,7 +75,7 @@ export class DockerMock extends NockMock {
|
||||
responseBody: any;
|
||||
responseCode: number;
|
||||
tag: string;
|
||||
checkURI: (uri: string) => Promise<void>;
|
||||
checkURI: (uri: string) => Promise<void> | void;
|
||||
checkBuildRequestBody: (requestBody: string) => Promise<void>;
|
||||
}) {
|
||||
this.optPost(
|
||||
|
@ -37,7 +37,7 @@ export class NockMock {
|
||||
|
||||
constructor(
|
||||
public basePathPattern: string | RegExp,
|
||||
public allowUnmocked: boolean = false,
|
||||
public allowUnmocked = false,
|
||||
) {
|
||||
if (NockMock.instanceCount === 0) {
|
||||
if (!nock.isActive()) {
|
||||
|
@ -50,7 +50,7 @@ export async function exists(fPath: string) {
|
||||
try {
|
||||
await fs.stat(fPath);
|
||||
return true;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"build_log": null,
|
||||
"start_timestamp": "2021-08-25T22:18:33.624Z",
|
||||
"end_timestamp": "2021-08-25T22:18:48.820Z",
|
||||
"contains__image": [
|
||||
"release_image": [
|
||||
{
|
||||
"image": [
|
||||
{
|
||||
|
Binary file not shown.
@ -75,12 +75,12 @@ describe('detectEncoding() function', function () {
|
||||
|
||||
for (const fname of sampleBinary) {
|
||||
const buf = await fs.readFile(path.join('node_modules', fname));
|
||||
const encoding = await detectEncoding(buf);
|
||||
const encoding = detectEncoding(buf);
|
||||
expect(encoding).to.equal('binary');
|
||||
}
|
||||
for (const fname of sampleText) {
|
||||
const buf = await fs.readFile(fname);
|
||||
const encoding = await detectEncoding(buf);
|
||||
const encoding = detectEncoding(buf);
|
||||
expect(encoding).to.equal('utf-8');
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as stream from 'stream';
|
||||
import { AssertionError, expect } from 'chai';
|
||||
import { expect } from 'chai';
|
||||
import { stub } from 'sinon';
|
||||
import * as tmp from 'tmp';
|
||||
import { delay } from '../../utils';
|
||||
@ -32,9 +32,7 @@ describe('image-manager', function () {
|
||||
fs.writeSync(this.image.fd, 'Cache image', 0, 'utf8');
|
||||
|
||||
this.cacheGetImagePathStub = stub(imageManager, 'getImagePath');
|
||||
return this.cacheGetImagePathStub.returns(
|
||||
Promise.resolve(this.image.name),
|
||||
);
|
||||
return this.cacheGetImagePathStub.resolves(this.image.name);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -44,8 +42,8 @@ describe('image-manager', function () {
|
||||
|
||||
describe('given the image is fresh', function () {
|
||||
beforeEach(function () {
|
||||
this.cacheIsImageFresh = stub(imageManager, 'isImageFresh');
|
||||
return this.cacheIsImageFresh.returns(Promise.resolve(true));
|
||||
this.cacheIsImageFresh = stub(imageManager, 'isImageCached');
|
||||
return this.cacheIsImageFresh.resolves(true);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -58,11 +56,11 @@ describe('image-manager', function () {
|
||||
void imageManager.getStream('raspberry-pi').then(function (stream) {
|
||||
let result = '';
|
||||
|
||||
stream.on('data', (chunk) => (result += chunk.toString()));
|
||||
stream.on('data', (chunk: string) => (result += chunk.toString()));
|
||||
|
||||
return stream.on('end', function () {
|
||||
expect(result).to.equal('Cache image');
|
||||
return done();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -70,8 +68,8 @@ describe('image-manager', function () {
|
||||
|
||||
describe('given the image is not fresh', function () {
|
||||
beforeEach(function () {
|
||||
this.cacheIsImageFresh = stub(imageManager, 'isImageFresh');
|
||||
return this.cacheIsImageFresh.returns(Promise.resolve(false));
|
||||
this.cacheIsImageFresh = stub(imageManager, 'isImageCached');
|
||||
return this.cacheIsImageFresh.resolves(false);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -82,9 +80,7 @@ describe('image-manager', function () {
|
||||
describe.skip('given a valid download endpoint', function () {
|
||||
beforeEach(function () {
|
||||
this.osDownloadStub = stub(balena.models.os, 'download');
|
||||
this.osDownloadStub.returns(
|
||||
Promise.resolve(stringToStream('Download image')),
|
||||
);
|
||||
this.osDownloadStub.resolves(stringToStream('Download image'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -95,7 +91,7 @@ describe('image-manager', function () {
|
||||
void imageManager.getStream('raspberry-pi').then((stream) => {
|
||||
let result = '';
|
||||
|
||||
stream.on('data', (chunk) => (result += chunk));
|
||||
stream.on('data', (chunk: string) => (result += chunk));
|
||||
|
||||
stream.on('end', async () => {
|
||||
expect(result).to.equal('Download image');
|
||||
@ -131,7 +127,7 @@ describe('image-manager', function () {
|
||||
beforeEach(function () {
|
||||
this.osDownloadStream = new stream.PassThrough();
|
||||
this.osDownloadStub = stub(balena.models.os, 'download');
|
||||
this.osDownloadStub.returns(Promise.resolve(this.osDownloadStream));
|
||||
this.osDownloadStub.resolves(this.osDownloadStream);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -153,9 +149,7 @@ describe('image-manager', function () {
|
||||
const contents = await fsAsync
|
||||
.stat(this.image.name + '.inprogress')
|
||||
.then(function () {
|
||||
throw new AssertionError(
|
||||
'Image cache should be deleted on failure',
|
||||
);
|
||||
throw new Error('Image cache should be deleted on failure');
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
@ -174,7 +168,7 @@ describe('image-manager', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a stream with the mime property', async function () {
|
||||
describe('given a stream with the mime property', function () {
|
||||
beforeEach(function () {
|
||||
this.osDownloadStub = stub(balena.models.os, 'download');
|
||||
const message = 'Lorem ipsum dolor sit amet';
|
||||
@ -184,7 +178,7 @@ describe('image-manager', function () {
|
||||
mime?: string;
|
||||
};
|
||||
mockResultStream.mime = 'application/zip';
|
||||
this.osDownloadStub.returns(Promise.resolve(mockResultStream));
|
||||
this.osDownloadStub.resolves(mockResultStream);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -209,12 +203,10 @@ describe('image-manager', function () {
|
||||
|
||||
this.balenaSettingsGetStub
|
||||
.withArgs('cacheDirectory')
|
||||
.returns(
|
||||
Promise.resolve(
|
||||
os.platform() === 'win32'
|
||||
? 'C:\\Users\\johndoe\\_balena\\cache'
|
||||
: '/Users/johndoe/.balena/cache',
|
||||
),
|
||||
.resolves(
|
||||
os.platform() === 'win32'
|
||||
? 'C:\\Users\\johndoe\\_balena\\cache'
|
||||
: '/Users/johndoe/.balena/cache',
|
||||
);
|
||||
});
|
||||
|
||||
@ -228,21 +220,21 @@ describe('image-manager', function () {
|
||||
balena.models.config,
|
||||
'getDeviceTypeManifestBySlug',
|
||||
);
|
||||
this.getDeviceTypeManifestBySlugStub.withArgs('raspberry-pi').returns(
|
||||
Promise.resolve({
|
||||
this.getDeviceTypeManifestBySlugStub
|
||||
.withArgs('raspberry-pi')
|
||||
.resolves({
|
||||
yocto: {
|
||||
fstype: 'resin-sdcard',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
this.getDeviceTypeManifestBySlugStub.withArgs('intel-edison').returns(
|
||||
Promise.resolve({
|
||||
this.getDeviceTypeManifestBySlugStub
|
||||
.withArgs('intel-edison')
|
||||
.resolves({
|
||||
yocto: {
|
||||
fstype: 'zip',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -288,20 +280,18 @@ describe('image-manager', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.isImageFresh()', () => {
|
||||
describe('.isImageCached()', () => {
|
||||
describe('given the raspberry-pi manifest', function () {
|
||||
beforeEach(function () {
|
||||
this.getDeviceTypeManifestBySlugStub = stub(
|
||||
balena.models.config,
|
||||
'getDeviceTypeManifestBySlug',
|
||||
);
|
||||
this.getDeviceTypeManifestBySlugStub.returns(
|
||||
Promise.resolve({
|
||||
yocto: {
|
||||
fstype: 'balena-sdcard',
|
||||
},
|
||||
}),
|
||||
);
|
||||
this.getDeviceTypeManifestBySlugStub.resolves({
|
||||
yocto: {
|
||||
fstype: 'balena-sdcard',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -314,8 +304,8 @@ describe('image-manager', function () {
|
||||
imageManager,
|
||||
'getFileCreatedDate',
|
||||
);
|
||||
this.utilsGetFileCreatedDate.returns(
|
||||
Promise.reject(new Error("ENOENT, stat 'raspberry-pi'")),
|
||||
this.utilsGetFileCreatedDate.rejects(
|
||||
new Error("ENOENT, stat 'raspberry-pi'"),
|
||||
);
|
||||
});
|
||||
|
||||
@ -324,78 +314,8 @@ describe('image-manager', function () {
|
||||
});
|
||||
|
||||
it('should return false', async function () {
|
||||
expect(await imageManager.isImageFresh('raspberry-pi', '1.2.3')).to.be
|
||||
.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a fixed created time', function () {
|
||||
beforeEach(function () {
|
||||
this.utilsGetFileCreatedDate = stub(
|
||||
imageManager,
|
||||
'getFileCreatedDate',
|
||||
);
|
||||
this.utilsGetFileCreatedDate.returns(
|
||||
Promise.resolve(new Date('2014-01-01T00:00:00.000Z')),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.utilsGetFileCreatedDate.restore();
|
||||
});
|
||||
|
||||
describe('given the file was created before the os last modified time', function () {
|
||||
beforeEach(function () {
|
||||
this.osGetLastModified = stub(balena.models.os, 'getLastModified');
|
||||
this.osGetLastModified.returns(
|
||||
Promise.resolve(new Date('2014-02-01T00:00:00.000Z')),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.osGetLastModified.restore();
|
||||
});
|
||||
|
||||
it('should return false', function () {
|
||||
const promise = imageManager.isImageFresh('raspberry-pi', '1.2.3');
|
||||
return expect(promise).to.eventually.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the file was created after the os last modified time', function () {
|
||||
beforeEach(function () {
|
||||
this.osGetLastModified = stub(balena.models.os, 'getLastModified');
|
||||
this.osGetLastModified.returns(
|
||||
Promise.resolve(new Date('2013-01-01T00:00:00.000Z')),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.osGetLastModified.restore();
|
||||
});
|
||||
|
||||
it('should return true', function () {
|
||||
const promise = imageManager.isImageFresh('raspberry-pi', '1.2.3');
|
||||
return expect(promise).to.eventually.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the file was created just at the os last modified time', function () {
|
||||
beforeEach(function () {
|
||||
this.osGetLastModified = stub(balena.models.os, 'getLastModified');
|
||||
this.osGetLastModified.returns(
|
||||
Promise.resolve(new Date('2014-00-01T00:00:00.000Z')),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.osGetLastModified.restore();
|
||||
});
|
||||
|
||||
it('should return false', function () {
|
||||
const promise = imageManager.isImageFresh('raspberry-pi', '1.2.3');
|
||||
return expect(promise).to.eventually.be.false;
|
||||
});
|
||||
expect(await imageManager.isImageCached('raspberry-pi', '1.2.3')).to
|
||||
.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -408,7 +328,7 @@ describe('image-manager', function () {
|
||||
fs.writeSync(this.image.fd, 'Lorem ipsum dolor sit amet', 0, 'utf8');
|
||||
|
||||
this.cacheGetImagePathStub = stub(imageManager, 'getImagePath');
|
||||
this.cacheGetImagePathStub.returns(Promise.resolve(this.image.name));
|
||||
this.cacheGetImagePathStub.resolves(this.image.name);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
@ -422,7 +342,7 @@ describe('image-manager', function () {
|
||||
.then(function (stream) {
|
||||
let result = '';
|
||||
|
||||
stream.on('data', (chunk) => (result += chunk));
|
||||
stream.on('data', (chunk) => (result += chunk as string));
|
||||
|
||||
stream.on('end', function () {
|
||||
expect(result).to.equal('Lorem ipsum dolor sit amet');
|
||||
@ -445,7 +365,7 @@ describe('image-manager', function () {
|
||||
beforeEach(function () {
|
||||
this.image = tmp.fileSync();
|
||||
this.cacheGetImagePathStub = stub(imageManager, 'getImagePath');
|
||||
this.cacheGetImagePathStub.returns(Promise.resolve(this.image.name));
|
||||
this.cacheGetImagePathStub.resolves(this.image.name);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
@ -488,9 +408,7 @@ describe('image-manager', function () {
|
||||
beforeEach(function () {
|
||||
this.date = new Date(2014, 1, 1);
|
||||
this.fsStatStub = stub(fs.promises, 'stat');
|
||||
this.fsStatStub
|
||||
.withArgs('foo')
|
||||
.returns(Promise.resolve({ ctime: this.date }));
|
||||
this.fsStatStub.withArgs('foo').resolves({ ctime: this.date });
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -508,7 +426,7 @@ describe('image-manager', function () {
|
||||
this.fsStatStub = stub(fs.promises, 'stat');
|
||||
this.fsStatStub
|
||||
.withArgs('foo')
|
||||
.returns(Promise.reject(new Error("ENOENT, stat 'foo'")));
|
||||
.rejects(new Error("ENOENT, stat 'foo'"));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -554,7 +472,9 @@ describe('image-manager', function () {
|
||||
mockFs({});
|
||||
});
|
||||
|
||||
afterEach(() => mockFs.restore());
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should keep the cache directory removed', async function () {
|
||||
const exists = await fsExistsAsync(this.cacheDirectory);
|
||||
|
@ -117,7 +117,7 @@ describe('disambiguateReleaseParam() function', () => {
|
||||
it('should return id from SDK on first call, if match is found', async () => {
|
||||
const input = '1234';
|
||||
const output = 1234;
|
||||
const getRelease = sinon.stub().returns(Promise.resolve({ id: output }));
|
||||
const getRelease = sinon.stub().resolves({ id: output });
|
||||
const sdk: any = {
|
||||
models: {
|
||||
release: {
|
||||
@ -139,9 +139,9 @@ describe('disambiguateReleaseParam() function', () => {
|
||||
const getRelease = sinon
|
||||
.stub()
|
||||
.onCall(0)
|
||||
.returns(Promise.reject(new BalenaReleaseNotFound(input)))
|
||||
.rejects(new BalenaReleaseNotFound(input))
|
||||
.onCall(1)
|
||||
.returns(Promise.resolve({ id: output }));
|
||||
.resolves({ id: output });
|
||||
|
||||
const sdk: any = {
|
||||
models: {
|
||||
@ -161,9 +161,7 @@ describe('disambiguateReleaseParam() function', () => {
|
||||
|
||||
it('should throw error if no match found', async () => {
|
||||
const input = '1234';
|
||||
const getRelease = sinon
|
||||
.stub()
|
||||
.returns(Promise.reject(new BalenaReleaseNotFound(input)));
|
||||
const getRelease = sinon.stub().rejects(new BalenaReleaseNotFound(input));
|
||||
|
||||
const sdk: any = {
|
||||
models: {
|
||||
@ -185,9 +183,7 @@ describe('disambiguateReleaseParam() function', () => {
|
||||
it('should throw error if unknown error returned from SDK', async () => {
|
||||
const input = '1234';
|
||||
|
||||
const getRelease = sinon
|
||||
.stub()
|
||||
.returns(Promise.reject(new Error('some error')));
|
||||
const getRelease = sinon.stub().rejects(new Error('some error'));
|
||||
|
||||
const sdk: any = {
|
||||
models: {
|
||||
|
@ -15,10 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as chai from 'chai';
|
||||
import { ExpectedError } from '../../build/errors';
|
||||
import * as v from '../../build/utils/validation';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const { expect } = chai;
|
||||
|
||||
describe('validateEmail() function', () => {
|
||||
it('should reject invalid email addresses with a message', () => {
|
||||
const errorMessage = 'Email is not valid';
|
||||
@ -186,55 +190,55 @@ describe('validateUuid() function', () => {
|
||||
|
||||
describe('parseAsInteger() function', () => {
|
||||
it('should reject non-numeric characters', () => {
|
||||
expect(() => v.parseAsInteger('abc')).to.throw(ExpectedError);
|
||||
expect(() => v.parseAsInteger('1a')).to.throw(ExpectedError);
|
||||
expect(() => v.parseAsInteger('a1')).to.throw(ExpectedError);
|
||||
expect(() => v.parseAsInteger('a')).to.throw(ExpectedError);
|
||||
expect(() => v.parseAsInteger('1.0')).to.throw(ExpectedError);
|
||||
expect(v.parseAsInteger('abc')).to.be.rejectedWith(ExpectedError);
|
||||
expect(v.parseAsInteger('1a')).to.be.rejectedWith(ExpectedError);
|
||||
expect(v.parseAsInteger('a1')).to.be.rejectedWith(ExpectedError);
|
||||
expect(v.parseAsInteger('a')).to.be.rejectedWith(ExpectedError);
|
||||
expect(v.parseAsInteger('1.0')).to.be.rejectedWith(ExpectedError);
|
||||
});
|
||||
|
||||
it('should reject leading zeros', () => {
|
||||
expect(() => v.parseAsInteger('01')).to.throw(ExpectedError);
|
||||
expect(() => v.parseAsInteger('001')).to.throw(ExpectedError);
|
||||
expect(v.parseAsInteger('01')).to.be.rejectedWith(ExpectedError);
|
||||
expect(v.parseAsInteger('001')).to.be.rejectedWith(ExpectedError);
|
||||
});
|
||||
|
||||
it('should throw with specific message when param name passed', () => {
|
||||
expect(() => v.parseAsInteger('abc')).to.throw(
|
||||
expect(v.parseAsInteger('abc')).to.be.rejectedWith(
|
||||
'The parameter must be an integer.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw with general message when no param name passed', () => {
|
||||
expect(() => v.parseAsInteger('abc', 'foo')).to.throw(
|
||||
expect(v.parseAsInteger('abc', 'foo')).to.be.rejectedWith(
|
||||
"The parameter 'foo' must be an integer.",
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse integers to number type', () => {
|
||||
expect(v.parseAsInteger('100')).to.equal(100);
|
||||
expect(v.parseAsInteger('100')).to.be.a('number');
|
||||
expect(v.parseAsInteger('0')).to.equal(0);
|
||||
expect(v.parseAsInteger('0')).to.be.a('number');
|
||||
it('should parse integers to number type', async () => {
|
||||
expect(await v.parseAsInteger('100')).to.equal(100);
|
||||
expect(await v.parseAsInteger('100')).to.be.a('number');
|
||||
expect(await v.parseAsInteger('0')).to.equal(0);
|
||||
expect(await v.parseAsInteger('0')).to.be.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryAsInteger() function', () => {
|
||||
it('should return string with non-numeric characters as string', () => {
|
||||
expect(v.tryAsInteger('abc')).to.be.a('string');
|
||||
expect(v.tryAsInteger('1a')).to.be.a('string');
|
||||
expect(v.tryAsInteger('a1')).to.be.a('string');
|
||||
expect(v.tryAsInteger('a')).to.be.a('string');
|
||||
expect(v.tryAsInteger('1.0')).to.be.a('string');
|
||||
it('should return string with non-numeric characters as string', async () => {
|
||||
expect(await v.tryAsInteger('abc')).to.be.a('string');
|
||||
expect(await v.tryAsInteger('1a')).to.be.a('string');
|
||||
expect(await v.tryAsInteger('a1')).to.be.a('string');
|
||||
expect(await v.tryAsInteger('a')).to.be.a('string');
|
||||
expect(await v.tryAsInteger('1.0')).to.be.a('string');
|
||||
});
|
||||
|
||||
it('should return numerical strings with leading zeros as string', () => {
|
||||
expect(v.tryAsInteger('01')).to.be.a('string');
|
||||
expect(v.tryAsInteger('001')).to.be.a('string');
|
||||
it('should return numerical strings with leading zeros as string', async () => {
|
||||
expect(await v.tryAsInteger('01')).to.be.a('string');
|
||||
expect(await v.tryAsInteger('001')).to.be.a('string');
|
||||
});
|
||||
|
||||
it('should return numerical strings without leading zeros as number', () => {
|
||||
expect(v.tryAsInteger('100')).to.be.a('number');
|
||||
expect(v.tryAsInteger('256')).to.be.a('number');
|
||||
expect(v.tryAsInteger('0')).to.be.a('number');
|
||||
it('should return numerical strings without leading zeros as number', async () => {
|
||||
expect(await v.tryAsInteger('100')).to.be.a('number');
|
||||
expect(await v.tryAsInteger('256')).to.be.a('number');
|
||||
expect(await v.tryAsInteger('0')).to.be.a('number');
|
||||
});
|
||||
});
|
||||
|
@ -9,6 +9,7 @@
|
||||
"./src/**/*",
|
||||
"./patches/*",
|
||||
"./tests/**/*",
|
||||
"./typings/**/*"
|
||||
"./typings/**/*",
|
||||
".mocharc-standalone.js",
|
||||
]
|
||||
}
|
||||
|
2
typings/JSONStream/index.d.ts
vendored
2
typings/JSONStream/index.d.ts
vendored
@ -32,7 +32,7 @@ declare module 'JSONStream' {
|
||||
recurse: boolean;
|
||||
}
|
||||
|
||||
export function parse(pattern: any | any[]): NodeJS.ReadWriteStream;
|
||||
export function parse(pattern: any): NodeJS.ReadWriteStream;
|
||||
|
||||
type NewlineOnlyIndicator = false;
|
||||
|
||||
|
105
typings/balena-device-init/index.d.ts
vendored
105
typings/balena-device-init/index.d.ts
vendored
@ -1,105 +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-device-init' {
|
||||
import { DeviceTypeJson } from 'balena-sdk';
|
||||
|
||||
interface OperationState {
|
||||
operation:
|
||||
| CopyOperation
|
||||
| ReplaceOperation
|
||||
| RunScriptOperation
|
||||
| BurnOperation;
|
||||
percentage: number;
|
||||
}
|
||||
|
||||
interface Operation {
|
||||
command: string;
|
||||
}
|
||||
|
||||
interface CopyOperation extends Operation {
|
||||
command: 'copy';
|
||||
from: { path: string };
|
||||
to: { path: string };
|
||||
}
|
||||
|
||||
interface ReplaceOperation extends Operation {
|
||||
command: 'replace';
|
||||
copy: string;
|
||||
replace: string;
|
||||
file: {
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface RunScriptOperation extends Operation {
|
||||
command: 'run-script';
|
||||
script: string;
|
||||
arguments?: string[];
|
||||
}
|
||||
|
||||
interface BurnOperation extends Operation {
|
||||
command: 'burn';
|
||||
image?: string;
|
||||
}
|
||||
|
||||
interface BurnProgress {
|
||||
type: 'write' | 'check';
|
||||
percentage: number;
|
||||
transferred: number;
|
||||
length: number;
|
||||
remaining: number;
|
||||
eta: number;
|
||||
runtime: number;
|
||||
delta: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
interface InitializeEmitter {
|
||||
on(event: 'stdout' | 'stderr', callback: (msg: string) => void): void;
|
||||
on(event: 'state', callback: (state: OperationState) => void): void;
|
||||
on(event: 'burn', callback: (state: BurnProgress) => void): void;
|
||||
on(event: 'end', callback: () => void): void;
|
||||
on(event: 'error', callback: (error: Error) => void): void;
|
||||
}
|
||||
|
||||
// As of writing this, these are Bluebird promises, but we are typing then
|
||||
// as normal Promises so that we do not rely on Bluebird specific methods,
|
||||
// so that the CLI will not require any change once the package drops Bluebird.
|
||||
|
||||
export function configure(
|
||||
image: string,
|
||||
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
|
||||
config: object,
|
||||
options?: object,
|
||||
): Promise<InitializeEmitter>;
|
||||
|
||||
export function initialize(
|
||||
image: string,
|
||||
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
|
||||
config: object,
|
||||
): Promise<InitializeEmitter>;
|
||||
|
||||
export function getImageOsVersion(
|
||||
image: string,
|
||||
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
|
||||
): Promise<string | null>;
|
||||
|
||||
export function getImageManifest(
|
||||
image: string,
|
||||
): Promise<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>;
|
||||
}
|
Reference in New Issue
Block a user