Compare commits

..

40 Commits

Author SHA1 Message Date
8dbe1af551 v19.0.14 2024-10-08 12:44:20 +00:00
aae303202b Merge pull request #2841 from balena-io/temporarily-skip-broken-image-manager-tests
Temporarily skip broken image-manager tests on Windows and Mac
2024-10-08 12:43:19 +00:00
284784505d Deduplicate dependencies 2024-10-08 08:21:07 -04:00
77b9514442 Temporarily skip broken image-manager tests on Windows and Mac
Change-type: patch
2024-10-08 08:21:01 -04:00
ff4afe3ab2 v19.0.13 2024-09-23 11:35:49 +00:00
5ea246f016 Merge pull request #2837 from balena-io/adjust-changelog-message
Remove extra line from recent changelog entry
2024-09-23 11:35:03 +00:00
127bd7ec72 Remove extra line from recent changelog entry
Change-type: patch
2024-09-23 07:12:22 -04:00
fa35877137 v19.0.12 2024-09-20 17:48:43 +00:00
a402dffbc5 Merge pull request #2834 from balena-io/remove-image-manager
Embed balena-image-manager instead of having it as a dependency
2024-09-20 13:47:48 -04:00
c7441b06ac skip
Change-type: patch
2024-09-20 12:05:00 -04:00
251d64eb88 Add image-manager tests
Change-type: patch
2024-09-20 08:38:21 -04:00
ff9bb52a20 Remove balena-image-manager dependency
Change-type: patch
2024-09-20 08:38:21 -04:00
c799c3f10d Embed balena-image-manager instead of having it as a dependency
Change-type: patch
2024-09-20 08:38:21 -04:00
89efe2a2c8 Add mime dependency 2024-09-20 08:38:21 -04:00
f6ff397969 Move mkdirp from devDependency to dependency 2024-09-18 12:56:41 -04:00
aaf709a1d4 v19.0.11 2024-09-18 16:38:59 +00:00
ca6eea4371 Merge pull request #2835 from balena-io/reduce-bluebird
Remove Bluebird as a direct dependency
2024-09-18 19:38:06 +03:00
d39dc5a39a Remove Bluebird as a direct dependency
Change-type: patch
2024-09-18 18:37:39 +03:00
1699419788 v19.0.10 2024-09-12 23:00:18 +00:00
c25591cb4a Merge pull request #2828 from balena-io/remove-package-resin-valid-email
Remove package `@resin.io/valid-email`
2024-09-12 22:59:27 +00:00
a2b4f76c94 Remove package @resin.io/valid-email
Change-type: patch
2024-09-12 18:39:21 -04:00
6a1239bd52 v19.0.9 2024-09-12 16:12:12 +00:00
ddf34326a4 Merge pull request #2830 from balena-io/renovate/actions-download-artifact-4.1.x
Update actions/download-artifact action to v4.1.8
2024-09-12 16:11:15 +00:00
58f480ad7c Update actions/download-artifact action to v4.1.8
Update actions/download-artifact from 4.1.7 to 4.1.8

Change-type: patch
2024-09-12 15:48:40 +00:00
7e6589a7d7 v19.0.8 2024-09-12 15:07:56 +00:00
c699bb1dbc Merge pull request #2829 from balena-io/renovate/actions-upload-artifact-digest
Update actions/upload-artifact digest to 5076954
2024-09-12 15:07:01 +00:00
e101e0f466 Update actions/upload-artifact digest to 5076954
Update actions/upload-artifact

Change-type: patch
2024-09-12 14:47:50 +00:00
e29273142e v19.0.7 2024-09-12 14:13:22 +00:00
519395cfcd Merge pull request #2825 from balena-io/renovate/actions-setup-node-digest
Update actions/setup-node digest to 1e60f62
2024-09-12 14:12:33 +00:00
314e8800d0 Update actions/setup-node digest to 1e60f62
Update actions/setup-node

Change-type: patch
2024-09-12 13:48:25 +00:00
0bb1c892e8 v19.0.6 2024-09-12 13:47:30 +00:00
5eb79f5cf0 Merge pull request #2802 from balena-io/remove-moment-library
Remove moment and moment-duration-format in favor of native time parsing
2024-09-12 10:46:29 -03:00
707b249e97 Remove moment and moment-duration-format in favor of native time parsing
Change-type: patch
2024-09-12 10:05:13 -03:00
2a725cd1f0 v19.0.5 2024-09-10 15:13:27 +00:00
83f274cc62 Merge pull request #2789 from balena-io/renovate/apple-actions-import-codesign-certs-2.x
Update apple-actions/import-codesign-certs action to v2
2024-09-10 12:12:42 -03:00
9242a3493a Update apple-actions/import-codesign-certs action to v2
Update apple-actions/import-codesign-certs from 1 to 2

Change-type: patch
2024-09-10 14:47:56 +00:00
aa46d314b4 v19.0.4 2024-09-10 14:44:44 +00:00
58f7dfc894 Merge pull request #2824 from balena-io/bump-ts-5_6_2
Bump TypeScript to 5.6.2
2024-09-10 14:43:53 +00:00
39e1c02648 Deduplicate dependencies
Resolves: #
Change-type:
2024-09-10 11:53:09 +03:00
5f92bbc846 Update TypeScript to 5.6.2
Change-type: patch
2024-09-10 11:49:41 +03:00
34 changed files with 1386 additions and 761 deletions

View File

@ -28,7 +28,7 @@ runs:
using: 'composite'
steps:
- name: Download custom source artifact
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}
@ -39,7 +39,7 @@ runs:
run: tar -xf ${{ runner.temp }}/custom.tgz
- name: Setup Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4
with:
node-version: ${{ inputs.NODE_VERSION }}
cache: npm
@ -66,7 +66,7 @@ runs:
# https://github.com/Apple-Actions/import-codesign-certs
- name: Import Apple code signing certificate
if: runner.os == 'macOS'
uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 # v1
uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2
with:
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
@ -135,7 +135,7 @@ runs:
XCODE_APP_LOADER_TEAM_ID: ${{ inputs.XCODE_APP_LOADER_TEAM_ID }}
- name: Upload artifacts
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ strategy.job-index }}
path: dist

View File

@ -26,7 +26,7 @@ runs:
steps:
# https://github.com/actions/setup-node#caching-global-packages-data
- name: Setup Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # 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@65462800fd760344b1a7b4382951275a0abb4808 # v4
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz

View File

@ -1,3 +1,163 @@
- commits:
- subject: Temporarily skip broken image-manager tests on Windows and Mac
hash: 77b9514442ab81ef1375a2517eb3e9aab7e724da
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
version: 19.0.14
title: ""
date: 2024-10-08T12:44:14.300Z
- commits:
- subject: Remove extra line from recent changelog entry
hash: 127bd7ec722133d81cd94a5e028d9f2e5219df7c
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
version: 19.0.13
title: ""
date: 2024-09-23T11:35:45.475Z
- commits:
- subject: skip
hash: c7441b06ac97a50d8ffefebff3af55e1d12d4035
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
- subject: Add `image-manager` tests
hash: 251d64eb8831555e2cfc8a54c73701eadb8c4f06
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
- subject: Remove `balena-image-manager` dependency
hash: ff9bb52a20b3f21281189ddfbbe1b800e104be1d
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
- subject: Embed `balena-image-manager` instead of having it as a dependency
hash: c799c3f10d1491227fe770ceace46b26ae209b19
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
version: 19.0.12
title: ""
date: 2024-09-20T17:48:39.881Z
- commits:
- subject: Remove Bluebird as a direct dependency
hash: d39dc5a39ad0ec25e0a690b881c8212699f64162
body: ""
footer:
Change-type: patch
change-type: patch
author: Thodoris Greasidis
nested: []
version: 19.0.11
title: ""
date: 2024-09-18T16:38:55.929Z
- commits:
- subject: Remove package `@resin.io/valid-email`
hash: a2b4f76c94e7fda3f122adf721e0a12d9a0e9164
body: ""
footer:
Change-type: patch
change-type: patch
author: myarmolinsky
nested: []
version: 19.0.10
title: ""
date: 2024-09-12T23:00:13.119Z
- commits:
- subject: Update actions/download-artifact action to v4.1.8
hash: 58f480ad7c097952b3ff4e0a5daebc163f2ce7c1
body: |
Update actions/download-artifact from 4.1.7 to 4.1.8
footer:
Change-type: patch
change-type: patch
author: Self-hosted Renovate Bot
nested: []
version: 19.0.9
title: ""
date: 2024-09-12T16:12:08.049Z
- commits:
- subject: Update actions/upload-artifact digest to 5076954
hash: e101e0f46663585d2999c1bd59c5335a2d012ae4
body: |
Update actions/upload-artifact
footer:
Change-type: patch
change-type: patch
author: Self-hosted Renovate Bot
nested: []
version: 19.0.8
title: ""
date: 2024-09-12T15:07:51.366Z
- commits:
- subject: Update actions/setup-node digest to 1e60f62
hash: 314e8800d0c6dfeade2140125cb3dd996713713e
body: |
Update actions/setup-node
footer:
Change-type: patch
change-type: patch
author: Self-hosted Renovate Bot
nested: []
version: 19.0.7
title: ""
date: 2024-09-12T14:13:18.381Z
- commits:
- subject: Remove moment and moment-duration-format in favor of native time parsing
hash: 707b249e972a6943d75014f487285c0dd8085b15
body: ""
footer:
Change-type: patch
change-type: patch
author: Otavio Jacobi
nested: []
version: 19.0.6
title: ""
date: 2024-09-12T13:47:25.357Z
- commits:
- subject: Update apple-actions/import-codesign-certs action to v2
hash: 9242a3493af4c518c4d1328f19ddf2d95c182af7
body: |
Update apple-actions/import-codesign-certs from 1 to 2
footer:
Change-type: patch
change-type: patch
author: Self-hosted Renovate Bot
nested: []
version: 19.0.5
title: ""
date: 2024-09-10T15:13:23.938Z
- commits:
- subject: Update TypeScript to 5.6.2
hash: 5f92bbc846fe93cc03ebe7717baafe24f17d4e0d
body: ""
footer:
Change-type: patch
change-type: patch
author: Thodoris Greasidis
nested: []
version: 19.0.4
title: ""
date: 2024-09-10T14:44:39.949Z
- commits:
- subject: Reduce use of CJS require() on automation files
hash: facc66e9f97d075610d4383efa92dceb5b4f7acf

View File

@ -4,6 +4,52 @@ 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/).
## 19.0.14 - 2024-10-08
* Temporarily skip broken image-manager tests on Windows and Mac [myarmolinsky]
## 19.0.13 - 2024-09-23
* Remove extra line from recent changelog entry [myarmolinsky]
## 19.0.12 - 2024-09-20
* Add `image-manager` tests [myarmolinsky]
* Remove `balena-image-manager` dependency [myarmolinsky]
* Embed `balena-image-manager` instead of having it as a dependency [myarmolinsky]
## 19.0.11 - 2024-09-18
* Remove Bluebird as a direct dependency [Thodoris Greasidis]
## 19.0.10 - 2024-09-12
* Remove package `@resin.io/valid-email` [myarmolinsky]
## 19.0.9 - 2024-09-12
* Update actions/download-artifact action to v4.1.8 [Self-hosted Renovate Bot]
## 19.0.8 - 2024-09-12
* Update actions/upload-artifact digest to 5076954 [Self-hosted Renovate Bot]
## 19.0.7 - 2024-09-12
* Update actions/setup-node digest to 1e60f62 [Self-hosted Renovate Bot]
## 19.0.6 - 2024-09-12
* Remove moment and moment-duration-format in favor of native time parsing [Otavio Jacobi]
## 19.0.5 - 2024-09-10
* Update apple-actions/import-codesign-certs action to v2 [Self-hosted Renovate Bot]
## 19.0.4 - 2024-09-10
* Update TypeScript to 5.6.2 [Thodoris Greasidis]
## 19.0.3 - 2024-09-05
* Reduce use of CJS require() on automation files [Otavio Jacobi]

View File

@ -19,7 +19,6 @@ import type { JsonVersions } from '../src/commands/version/index';
import { run as oclifRun } from '@oclif/core';
import * as archiver from 'archiver';
import * as Bluebird from 'bluebird';
import { exec, execFile } from 'child_process';
import * as filehound from 'filehound';
import type { Stats } from 'fs';
@ -42,6 +41,7 @@ import {
const execFileAsync = promisify(execFile);
const execAsync = promisify(exec);
const rimrafAsync = promisify(rimraf);
export const packageJSON = loadPackageJson();
export const version = 'v' + packageJSON.version;
@ -517,7 +517,7 @@ export async function buildOclifInstaller() {
}
for (const dir of dirs) {
console.log(`rimraf(${dir})`);
await Bluebird.fromCallback((cb) => rimraf(dir, cb));
await rimrafAsync(dir);
}
console.log('=======================================================');
console.log(`oclif ${packCmd} ${packOpts.join(' ')}`);

View File

@ -22,7 +22,7 @@ _balena() {
key_cmds=( add rm )
local_cmds=( configure flash )
os_cmds=( build-config configure download initialize versions )
release_cmds=( export finalize import invalidate validate )
release_cmds=( finalize invalidate validate )
tag_cmds=( rm set )

View File

@ -21,7 +21,7 @@ _balena_complete()
key_cmds="add rm"
local_cmds="configure flash"
os_cmds="build-config configure download initialize versions"
release_cmds="export finalize import invalidate validate"
release_cmds="finalize invalidate validate"
tag_cmds="rm set"

View File

@ -282,9 +282,7 @@ are encouraged to regularly update the balena CLI to the latest version.
- Releases
- [release export <commitorid>](#release-export-commitorid)
- [release finalize <commitorid>](#release-finalize-commitorid)
- [release import <file> <fleet>](#release-import-file-fleet)
- [release <commitorid>](#release-commitorid)
- [release invalidate <commitorid>](#release-invalidate-commitorid)
- [release validate <commitorid>](#release-validate-commitorid)
@ -3347,37 +3345,6 @@ The notes for this release
# Releases
## release export <commitOrId>
Exporting a release to a file allows you to import an exact
copy of the original release into another app.
If the SemVer of a release is provided using the --version option,
the first argument is assumed to be the fleet's slug.
Only successful releases can be exported.
Examples:
$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar
$ balena release export myOrg/myFleet --version 1.2.3 -o ../path/to/release.tar
### Arguments
#### COMMITORID
commit, ID, or version of the release to export
### Options
#### -o, --output OUTPUT
output path
#### --version VERSION
version of the release to export from the specified fleet
## release finalize <commitOrId>
Finalize a release. Releases can be "draft" or "final", and this command
@ -3404,40 +3371,6 @@ the commit or ID of the release to finalize
### Options
## release import <file> <fleet>
is automatically omitted when importing a release. The backend will auto-increment
the revision field of the imported release if a release exists with the same semver.
A release will not be imported if a successful release with the same commit already
exists.
To export a release to a file, use 'balena release export'.
Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.
Examples:
$ balena release import ../path/to/release.tar myFleet
$ balena release import ../path/to/release.tar myOrg/myFleet
$ balena release import ../path/to/release.tar myOrg/myFleet --override-version 1.2.3
### Arguments
#### BUNDLE
path to a file, e.g. "./release.tar"
#### FLEET
fleet that the release will be imported to, e.g. "myOrg/myFleet"
### Options
#### --override-version OVERRIDE-VERSION
Imports this release with the specified version overriding the version in the file.
## release <commitOrId>
The --json option is recommended when scripting the output of this command,

500
npm-shrinkwrap.json generated
View File

@ -1,12 +1,12 @@
{
"name": "balena-cli",
"version": "19.0.3",
"version": "19.0.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "balena-cli",
"version": "19.0.3",
"version": "19.0.14",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@ -14,21 +14,17 @@
"@balena/dockerignore": "^1.0.2",
"@balena/env-parsing": "^1.1.8",
"@balena/es-version": "^1.0.1",
"@balena/release-bundle": "^0.5.2",
"@oclif/core": "^4.0.8",
"@resin.io/valid-email": "^0.1.0",
"@sentry/node": "^6.16.1",
"balena-config-json": "^4.2.0",
"balena-device-init": "^7.0.1",
"balena-errors": "^4.7.3",
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^10.0.1",
"balena-preload": "^15.0.6",
"balena-sdk": "^19.7.3",
"balena-semver": "^2.3.0",
"balena-settings-client": "^5.0.2",
"balena-settings-storage": "^8.1.0",
"bluebird": "^3.7.2",
"body-parser": "^1.19.1",
"bonjour-service": "^1.2.1",
"chalk": "^3.0.0",
@ -59,8 +55,8 @@
"JSONStream": "^1.0.3",
"livepush": "^3.5.1",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"mime": "^2.4.6",
"mkdirp": "^3.0.1",
"ndjson": "^2.0.0",
"node-cleanup": "^2.1.2",
"node-unzip-2": "^0.2.8",
@ -71,7 +67,7 @@
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
"request": "^2.88.2",
"resin-cli-form": "^3.0.0",
"resin-cli-visuals": "^2.0.0",
"resin-cli-visuals": "^2.0.1",
"resin-doodles": "^0.2.0",
"resin-stream-logger": "^0.1.2",
"rimraf": "^3.0.2",
@ -121,10 +117,12 @@
"@types/jsonwebtoken": "^9.0.6",
"@types/klaw": "^3.0.6",
"@types/lodash": "^4.14.178",
"@types/mime": "^3.0.4",
"@types/mixpanel": "^2.14.3",
"@types/mocha": "^10.0.7",
"@types/mock-fs": "^4.13.4",
"@types/mock-require": "^2.0.1",
"@types/moment-duration-format": "^2.2.3",
"@types/mockery": "^1.4.33",
"@types/ndjson": "^2.0.1",
"@types/node": "^20.0.0",
"@types/node-cleanup": "^2.1.2",
@ -162,17 +160,19 @@
"intercept-stdout": "^0.1.2",
"jsonwebtoken": "^9.0.0",
"klaw": "^4.1.0",
"mkdirp": "^3.0.1",
"mocha": "^10.6.0",
"mock-fs": "^5.2.0",
"mock-require": "^3.0.3",
"mockery": "^2.1.0",
"nock": "^13.2.1",
"oclif": "^4.14.0",
"parse-link-header": "^2.0.0",
"rewire": "^7.0.0",
"simple-git": "^3.14.1",
"sinon": "^18.0.0",
"string-to-stream": "^3.0.1",
"ts-node": "^10.4.0",
"typescript": "^5.5.2"
"typescript": "^5.6.2"
},
"engines": {
"node": "^20.6.0"
@ -1197,20 +1197,19 @@
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
"integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
"integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@ -1271,12 +1270,12 @@
"dev": true
},
"node_modules/@babel/parser": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz",
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz",
"integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==",
"dev": true,
"dependencies": {
"@babel/types": "^7.25.6"
"@babel/types": "^7.25.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -1286,13 +1285,13 @@
}
},
"node_modules/@babel/parser/node_modules/@babel/types": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz",
"integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-string-parser": "^7.25.7",
"@babel/helper-validator-identifier": "^7.25.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
@ -1314,13 +1313,13 @@
}
},
"node_modules/@babel/template/node_modules/@babel/types": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz",
"integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-string-parser": "^7.25.7",
"@babel/helper-validator-identifier": "^7.25.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
@ -1349,34 +1348,46 @@
}
},
"node_modules/@babel/traverse/node_modules/@babel/generator": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz",
"integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz",
"integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.25.6",
"@babel/types": "^7.25.7",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/@babel/types": {
"version": "7.25.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz",
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz",
"integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-string-parser": "^7.25.7",
"@babel/helper-validator-identifier": "^7.25.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/jsesc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
@ -1657,40 +1668,6 @@
"web-streams-polyfill": "^3.1.0"
}
},
"node_modules/@balena/release-bundle": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@balena/release-bundle/-/release-bundle-0.5.2.tgz",
"integrity": "sha512-q2ji3Pky9RGeztApTBaoZEF2R8FSiHsFutIvvlmA0ggJKgATxNNavZd4ueYtlK/Nl53g9vUrKmiwzCVgw9rDRw==",
"dependencies": {
"@balena/resource-bundle": "^0.8.3",
"balena-semver": "^2.3.5"
},
"peerDependencies": {
"balena-sdk": "^19.0.0"
}
},
"node_modules/@balena/resource-bundle": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@balena/resource-bundle/-/resource-bundle-0.8.3.tgz",
"integrity": "sha512-WKkeZkZIcrey1l08G1gS60EQCYtTZsOwwmnRhvmjnmWmUAcqa3Z9WqYDqM7ePbFO/pdo9Cd0JK0Xr+pgj3A8ng==",
"dependencies": {
"auth-header": "^1.0.0",
"tar-stream": "^3.1.7"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@balena/resource-bundle/node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/@balena/udif": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@balena/udif/-/udif-1.1.2.tgz",
@ -2600,14 +2577,6 @@
"resolved": "https://registry.npmjs.org/@resin.io/types-home-or-tmp/-/types-home-or-tmp-3.0.0.tgz",
"integrity": "sha1-PsiRM0Nv7msb7jkC8fluJgXXnU0="
},
"node_modules/@resin.io/valid-email": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@resin.io/valid-email/-/valid-email-0.1.0.tgz",
"integrity": "sha1-DnUwmoQ8AUqAqhSC+WmQYvL6UV0=",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@ronomon/direct-io": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@ronomon/direct-io/-/direct-io-3.0.1.tgz",
@ -3868,9 +3837,9 @@
"integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA=="
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz",
"integrity": "sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==",
"dev": true
},
"node_modules/@types/minimatch": {
@ -3891,6 +3860,15 @@
"integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==",
"dev": true
},
"node_modules/@types/mock-fs": {
"version": "4.13.4",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
"integrity": "sha512-mXmM0o6lULPI8z3XNnQCpL0BGxPwx1Ul1wXYEPBGl4efShyxW2Rln0JOPEWGyZaYZMM6OVXM/15zUuFMY52ljg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/mock-require": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-2.0.1.tgz",
@ -3900,14 +3878,11 @@
"@types/node": "*"
}
},
"node_modules/@types/moment-duration-format": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@types/moment-duration-format/-/moment-duration-format-2.2.3.tgz",
"integrity": "sha512-NQrnFOX1PTvHY8OH1aLoZntAkc2ad/1Cdl31UWIEaqBpDJ/m5SwfFBtFaX7TBLavASeLxG8DL7rm3NHKCLduaA==",
"dev": true,
"dependencies": {
"moment": ">=2.14.0"
}
"node_modules/@types/mockery": {
"version": "1.4.33",
"resolved": "https://registry.npmjs.org/@types/mockery/-/mockery-1.4.33.tgz",
"integrity": "sha512-vpuuVxCnCEM0OakYNoyFs40mjJFJFJahBHyx0Z0Piysof+YwlDJzNO4V1weRvYySAmtAvlb0UHtxVO2IfTcykw==",
"dev": true
},
"node_modules/@types/mute-stream": {
"version": "0.0.4",
@ -3929,9 +3904,9 @@
}
},
"node_modules/@types/node": {
"version": "20.16.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
"version": "20.16.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz",
"integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==",
"dependencies": {
"undici-types": "~6.19.2"
}
@ -4061,6 +4036,12 @@
"@types/node": "*"
}
},
"node_modules/@types/serve-static/node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
},
"node_modules/@types/shell-escape": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@types/shell-escape/-/shell-escape-0.2.0.tgz",
@ -5362,11 +5343,6 @@
"node": ">= 4.0.0"
}
},
"node_modules/auth-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@ -5509,6 +5485,15 @@
"node": ">=18"
}
},
"node_modules/balena-device-init/node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
"integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==",
"dependencies": {
"inherits": "^2.0.1",
"readable-stream": "^2.1.0"
}
},
"node_modules/balena-errors": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.9.0.tgz",
@ -5549,87 +5534,6 @@
"node": ">=16"
}
},
"node_modules/balena-image-manager": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/balena-image-manager/-/balena-image-manager-10.0.1.tgz",
"integrity": "sha512-HUMH6NZdKfJFgNYk9K7GVN6OLbt/aY4POOx+atu2tG6cRZnuaClDYGxwPS9+oWEoBdjD3MHkcjfDa3rW1niUsQ==",
"dependencies": {
"balena-sdk": "^19.0.1",
"mime": "^2.4.6",
"mkdirp": "^1.0.4",
"rimraf": "^5.0.5"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/balena-image-manager/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/balena-image-manager/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/balena-image-manager/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/balena-image-manager/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/balena-image-manager/node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/balena-preload": {
"version": "15.0.6",
"resolved": "https://registry.npmjs.org/balena-preload/-/balena-preload-15.0.6.tgz",
@ -5721,9 +5625,9 @@
}
},
"node_modules/balena-sdk/node_modules/@types/node": {
"version": "18.19.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
"integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
"version": "18.19.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz",
"integrity": "sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -5732,6 +5636,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
@ -5905,9 +5810,9 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@ -5917,7 +5822,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@ -7783,7 +7688,7 @@
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
@ -8735,36 +8640,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@ -8796,6 +8701,14 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -9153,12 +9066,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@ -9177,6 +9090,14 @@
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -12465,9 +12386,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge2": {
"version": "1.4.1",
@ -12501,6 +12425,7 @@
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
@ -12577,7 +12502,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"dev": true,
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
@ -12726,6 +12650,15 @@
"node": ">=10"
}
},
"node_modules/mock-fs": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-5.2.0.tgz",
"integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==",
"dev": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/mock-require": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz",
@ -12757,19 +12690,21 @@
"node": ">=0.10.0"
}
},
"node_modules/mockery": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz",
"integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==",
"dev": true
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/moment-duration-format": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.3.2.tgz",
"integrity": "sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ=="
},
"node_modules/mountutils": {
"version": "1.3.21",
"resolved": "https://registry.npmjs.org/mountutils/-/mountutils-1.3.21.tgz",
@ -13077,23 +13012,26 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"node_modules/nise": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz",
"integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/nise/-/nise-6.0.1.tgz",
"integrity": "sha512-DAyWGPQEuJVlL2eqKw6gdZKT+E/jo/ZrjEUDAslJLluCz81nWy+KSYybNp3KFm887Yvp7hv12jSM82ld8BmLxg==",
"dev": true,
"dependencies": {
"@sinonjs/commons": "^3.0.0",
"@sinonjs/fake-timers": "^11.2.2",
"@sinonjs/text-encoding": "^0.7.2",
"just-extend": "^6.2.0",
"path-to-regexp": "^6.2.1"
"path-to-regexp": "^8.1.0"
}
},
"node_modules/nise/node_modules/path-to-regexp": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
"integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==",
"dev": true
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"dev": true,
"engines": {
"node": ">=16"
}
},
"node_modules/no-case": {
"version": "3.0.4",
@ -14425,9 +14363,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/path-type": {
"version": "4.0.0",
@ -14621,9 +14559,9 @@
}
},
"node_modules/prebuild-install/node_modules/node-abi": {
"version": "3.67.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz",
"integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==",
"version": "3.68.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz",
"integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==",
"dependencies": {
"semver": "^7.3.5"
},
@ -14810,9 +14748,9 @@
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
},
"node_modules/pump": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.1.tgz",
"integrity": "sha512-2ynnAmUu45oUSq51AQbeugLkMSKaz8FqVpZ6ykTqzOVkzXe8u/ezkGsYrFJqKZx+D9cVxoDrSbR7CeAwxFa5cQ==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@ -14848,11 +14786,11 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@ -15295,9 +15233,9 @@
}
},
"node_modules/resin-cli-visuals": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resin-cli-visuals/-/resin-cli-visuals-2.0.0.tgz",
"integrity": "sha512-2iv6NydvsdBWaSxhQJBnbtP68BrEzd8nduVC9Il9NptRMiXf6zUEeWxlJY2ZHQcPAxqhLMJS/+SrdZ8gR+2XPg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/resin-cli-visuals/-/resin-cli-visuals-2.0.1.tgz",
"integrity": "sha512-Wfhhh+p/Mmdv4n/cD+WRC8KjDOj1RmEhbmiz2mu2YMdz53Umn7/Z4mQF1zNajwmQW1LfltFfUyz7G74Rprw6yg==",
"dependencies": {
"bluebird": "^3.5.1",
"chalk": "^3.0.0",
@ -15307,8 +15245,6 @@
"inquirer": "^7.3.3",
"is-promise": "^4.0.0",
"lodash": "^4.17.20",
"moment": "^2.27.0",
"moment-duration-format": "^2.2.2",
"progress-bar-formatter": "^2.0.1"
},
"engines": {
@ -15603,6 +15539,15 @@
"string-to-stream": "^1.0.1"
}
},
"node_modules/rindle/node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
"integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==",
"dependencies": {
"inherits": "^2.0.1",
"readable-stream": "^2.1.0"
}
},
"node_modules/roarr": {
"version": "2.15.4",
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
@ -15788,9 +15733,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@ -15880,19 +15825,27 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -16466,12 +16419,26 @@
}
},
"node_modules/string-to-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
"integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-3.0.1.tgz",
"integrity": "sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg==",
"dev": true,
"dependencies": {
"inherits": "^2.0.1",
"readable-stream": "^2.1.0"
"readable-stream": "^3.4.0"
}
},
"node_modules/string-to-stream/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/string-width": {
@ -17265,9 +17232,10 @@
}
},
"node_modules/typescript": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -17519,20 +17487,6 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
},
"node_modules/url/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/usb": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/usb/-/usb-2.13.0.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "balena-cli",
"version": "19.0.3",
"version": "19.0.14",
"description": "The official balena Command Line Interface",
"main": "./build/app.js",
"homepage": "https://github.com/balena-io/balena-cli",
@ -138,10 +138,12 @@
"@types/jsonwebtoken": "^9.0.6",
"@types/klaw": "^3.0.6",
"@types/lodash": "^4.14.178",
"@types/mime": "^3.0.4",
"@types/mixpanel": "^2.14.3",
"@types/mocha": "^10.0.7",
"@types/mock-fs": "^4.13.4",
"@types/mock-require": "^2.0.1",
"@types/moment-duration-format": "^2.2.3",
"@types/mockery": "^1.4.33",
"@types/ndjson": "^2.0.1",
"@types/node": "^20.0.0",
"@types/node-cleanup": "^2.1.2",
@ -179,38 +181,36 @@
"intercept-stdout": "^0.1.2",
"jsonwebtoken": "^9.0.0",
"klaw": "^4.1.0",
"mkdirp": "^3.0.1",
"mocha": "^10.6.0",
"mock-fs": "^5.2.0",
"mock-require": "^3.0.3",
"mockery": "^2.1.0",
"nock": "^13.2.1",
"oclif": "^4.14.0",
"parse-link-header": "^2.0.0",
"rewire": "^7.0.0",
"simple-git": "^3.14.1",
"sinon": "^18.0.0",
"string-to-stream": "^3.0.1",
"ts-node": "^10.4.0",
"typescript": "^5.5.2"
"typescript": "^5.6.2"
},
"dependencies": {
"@balena/compose": "^4.0.1",
"@balena/dockerignore": "^1.0.2",
"@balena/env-parsing": "^1.1.8",
"@balena/es-version": "^1.0.1",
"@balena/release-bundle": "^0.5.2",
"@oclif/core": "^4.0.8",
"@resin.io/valid-email": "^0.1.0",
"@sentry/node": "^6.16.1",
"balena-config-json": "^4.2.0",
"balena-device-init": "^7.0.1",
"balena-errors": "^4.7.3",
"balena-image-fs": "^7.0.6",
"balena-image-manager": "^10.0.1",
"balena-preload": "^15.0.6",
"balena-sdk": "^19.7.3",
"balena-semver": "^2.3.0",
"balena-settings-client": "^5.0.2",
"balena-settings-storage": "^8.1.0",
"bluebird": "^3.7.2",
"body-parser": "^1.19.1",
"bonjour-service": "^1.2.1",
"chalk": "^3.0.0",
@ -241,8 +241,8 @@
"JSONStream": "^1.0.3",
"livepush": "^3.5.1",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"mime": "^2.4.6",
"mkdirp": "^3.0.1",
"ndjson": "^2.0.0",
"node-cleanup": "^2.1.2",
"node-unzip-2": "^0.2.8",
@ -253,7 +253,7 @@
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
"request": "^2.88.2",
"resin-cli-form": "^3.0.0",
"resin-cli-visuals": "^2.0.0",
"resin-cli-visuals": "^2.0.1",
"resin-doodles": "^0.2.0",
"resin-stream-logger": "^0.1.2",
"rimraf": "^3.0.2",
@ -281,6 +281,6 @@
}
},
"versionist": {
"publishedAt": "2024-09-05T12:34:09.871Z"
"publishedAt": "2024-10-08T12:44:15.145Z"
}
}

View File

@ -89,7 +89,7 @@ export default class OsDownloadCmd extends Command {
// balenaOS ESR versions require user authentication
if (options.version) {
const { isESR } = await import('balena-image-manager');
const { isESR } = await import('../../utils/image-manager');
if (options.version === 'menu-esr' || isESR(options.version)) {
try {
await OsDownloadCmd.checkLoggedIn();

View File

@ -1,116 +0,0 @@
/**
* @license
* Copyright 2016-2024 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { commitOrIdArg } from '.';
import { Flags } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { create } from '@balena/release-bundle';
import * as fs from 'fs/promises';
import * as semver from 'balena-semver';
import { ExpectedError } from '../../errors';
export default class ReleaseExportCmd extends Command {
public static description = stripIndent`
Exports a release into a file.
Exporting a release to a file allows you to import an exact
copy of the original release into another app.
If the SemVer of a release is provided using the --version option,
the first argument is assumed to be the fleet's slug.
Only successful releases can be exported.
`;
public static examples = [
'$ balena release export a777f7345fe3d655c1c981aa642e5555 -o ../path/to/release.tar',
'$ balena release export myOrg/myFleet --version 1.2.3 -o ../path/to/release.tar',
];
public static usage = 'release export <commitOrId>';
public static flags = {
output: Flags.string({
description: 'output path',
char: 'o',
required: true,
}),
version: Flags.string({
description: 'version of the release to export from the specified fleet',
}),
help: cf.help,
};
public static args = {
commitOrId: commitOrIdArg({
description: 'commit, ID, or version of the release to export',
required: true,
}),
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(ReleaseExportCmd);
const balena = getBalenaSdk();
let release: balenaSdk.Release;
if (typeof options.version === 'string') {
const application = params.commitOrId;
const parsedVersion = semver.parse(options.version);
if (parsedVersion == null) {
throw new ExpectedError(
`Release of ${application} with version ${options.version} could not be exported; version must be valid SemVer.`,
);
} else {
const rawVersion =
parsedVersion.build.length === 0
? parsedVersion.version
: `${parsedVersion.version}+${parsedVersion.build[0]}`;
release = await balena.models.release.get(
{ application, rawVersion },
{ $select: ['id'] },
);
}
} else {
release = await balena.models.release.get(params.commitOrId, {
$select: ['id'],
});
}
try {
const releaseBundle = await create({
sdk: balena,
releaseId: release.id,
});
await fs.writeFile(options.output, releaseBundle);
const versionInfo =
typeof options.version === 'string'
? ` version ${options.version}`
: '';
console.log(
`Release ${params.commitOrId}${versionInfo} has been exported to ${options.output}.`,
);
} catch (error) {
throw new ExpectedError(
`Release ${params.commitOrId} could not be exported: ${error.message}`,
);
}
}
}

View File

@ -1,103 +0,0 @@
/**
* @license
* Copyright 2016-2024 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Flags, Args } from '@oclif/core';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { apply } from '@balena/release-bundle';
import { createReadStream } from 'fs';
import { ExpectedError } from '../../errors';
export default class ReleaseImportCmd extends Command {
public static description = stripIndent`
Imports a release from a file to an app or fleet. The revision field of the release
is automatically omitted when importing a release. The backend will auto-increment
the revision field of the imported release if a release exists with the same semver.
A release will not be imported if a successful release with the same commit already
exists.
To export a release to a file, use 'balena release export'.
Use the --override-version option to specify the version
of the imported release, overriding the one saved in the file.
`;
public static examples = [
'$ balena release import ../path/to/release.tar myFleet',
'$ balena release import ../path/to/release.tar myOrg/myFleet',
'$ balena release import ../path/to/release.tar myOrg/myFleet --override-version 1.2.3',
];
public static usage = 'release import <file> <fleet>';
public static flags = {
'override-version': Flags.string({
description:
'Imports this release with the specified version overriding the version in the file.',
required: false,
}),
help: cf.help,
};
public static args = {
bundle: Args.string({
required: true,
description: 'path to a file, e.g. "./release.tar"',
}),
fleet: Args.string({
required: true,
description:
'fleet that the release will be imported to, e.g. "myOrg/myFleet"',
}),
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = await this.parse(ReleaseImportCmd);
const balena = getBalenaSdk();
const bundle = createReadStream(params.bundle).on('error', () => {
throw new ExpectedError(
`Release bundle ${params.bundle} does not exist or is not accessible.`,
);
});
try {
const application = await balena.models.application.get(params.fleet, {
$select: ['id'],
});
if (application == null) {
throw new ExpectedError(`Fleet ${params.fleet} not found.`);
}
await apply({
sdk: balena,
application: application.id,
stream: bundle,
version: options['override-version'],
});
console.log(
`Release bundle ${params.bundle} has been imported to ${params.fleet}.`,
);
} catch (error) {
throw new ExpectedError(
`Could not import release bundle ${params.bundle} to fleet ${params.fleet}: ${error.message}`,
);
}
}
}

View File

@ -145,8 +145,8 @@ export async function downloadOSImage(
// some ongoing issues with the os download stream.
process.env.ZLIB_FLUSH = 'Z_NO_FLUSH';
const manager = await import('balena-image-manager');
const stream = await manager.get(deviceType, OSVersion);
const { getStream } = await import('./image-manager');
const stream = await getStream(deviceType, OSVersion);
const displayVersion = await new Promise((resolve, reject) => {
stream.on('error', reject);

View File

@ -308,6 +308,21 @@ export const authorizePush = function (
// utilities
const formatDuration = (seconds: number): string => {
const SECONDS_PER_MINUTE = 60;
const SECONDS_PER_HOUR = 3600;
const hours = Math.floor(seconds / SECONDS_PER_HOUR);
seconds %= SECONDS_PER_HOUR;
const minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
seconds = Math.floor(seconds % SECONDS_PER_MINUTE);
return hours > 0
? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
: `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
const renderProgressBar = function (percentage: number, stepCount: number) {
const _ = require('lodash') as typeof import('lodash');
percentage = _.clamp(percentage, 0, 100);
@ -479,11 +494,6 @@ export class BuildProgressUI implements Renderer {
}
_renderStatus(end = false) {
const moment = require('moment') as typeof import('moment');
(
require('moment-duration-format') as typeof import('moment-duration-format')
)(moment);
this._tty.clearLine();
this._tty.write(this._prefix);
if (end && this._cancelled) {
@ -495,12 +505,8 @@ export class BuildProgressUI implements Renderer {
const durationStr =
this._startTime == null
? 'unknown time'
: moment
.duration(
Math.floor((Date.now() - this._startTime) / 1000),
'seconds',
)
.format();
: formatDuration((Date.now() - this._startTime) / 1000);
this._tty.writeLine(`Built ${serviceStr} in ${durationStr}`);
} else {
this._tty.writeLine(`Building services... ${this._spinner()}`);
@ -577,11 +583,6 @@ export class BuildProgressInline implements Renderer {
}
end(summary?: Dictionary<string>) {
const moment = require('moment') as typeof import('moment');
(
require('moment-duration-format') as typeof import('moment-duration-format')
)(moment);
if (this._ended) {
return;
}
@ -599,12 +600,7 @@ export class BuildProgressInline implements Renderer {
const durationStr =
this._startTime == null
? 'unknown time'
: moment
.duration(
Math.floor((Date.now() - this._startTime) / 1000),
'seconds',
)
.format();
: formatDuration((Date.now() - this._startTime) / 1000);
this._outStream.write(`Built ${serviceStr} in ${durationStr}\n`);
}

View File

@ -309,7 +309,6 @@ function connectToDocker(host: string, port: number): Docker {
return new Docker({
host,
port,
Promise: require('bluebird'),
});
}

View File

@ -410,7 +410,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxyUrl) {
const { URL } = require('url') as typeof import('url');
let url: URL;
let url: InstanceType<typeof URL>;
try {
url = new URL(proxyUrl);
} catch (_e) {

299
src/utils/image-manager.ts Normal file
View File

@ -0,0 +1,299 @@
/**
* @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.
*/
import type * as SDK from 'balena-sdk';
import { getBalenaSdk } from './lazy';
// eslint-disable-next-line no-useless-escape
const BALENAOS_VERSION_REGEX = /v?\d+\.\d+\.\d+(\.rev\d+)?((\-|\+).+)?/;
/**
* @summary Check if the string is a valid balenaOS version number
* @description Throws an error if the version is invalid
*
* @param {String} version - version number to validate
* @returns {void} the most recent compatible version.
*/
const validateVersion = (version: string) => {
if (!BALENAOS_VERSION_REGEX.test(version)) {
throw new Error('Invalid version number');
}
};
/**
* @summary Get file created date
*
* @param {String} filePath - file path
* @returns {Promise<Date>} date since creation
*
* @example
* getFileCreatedDate('foo/bar').then (createdTime) ->
* console.log("The file was created in #{createdTime}")
*/
export const getFileCreatedDate = async (filePath: string) => {
const { promises: fs } = await import('fs');
const { ctime } = await fs.stat(filePath);
return ctime;
};
/**
* @summary Get path to image in cache
*
* @param {String} deviceType - device type slug or alias
* @param {String} version - the exact balenaOS version number
* @returns {Promise<String>} image path
*
* @example
* getImagePath('raspberry-pi', '1.2.3').then (imagePath) ->
* console.log(imagePath)
*/
export const getImagePath = async (deviceType: string, version?: string) => {
if (typeof version === 'string') {
validateVersion(version);
}
const balena = getBalenaSdk();
const [cacheDirectory, deviceTypeInfo] = await Promise.all([
balena.settings.get('cacheDirectory'),
balena.models.config.getDeviceTypeManifestBySlug(deviceType),
]);
const extension = deviceTypeInfo.yocto.fstype === 'zip' ? 'zip' : 'img';
const path = await import('path');
return path.join(cacheDirectory, `${deviceType}-v${version}.${extension}`);
};
/**
* @summary Determine if a device image is fresh
*
* @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
*
* @example
* isImageFresh('raspberry-pi', '1.2.3').then (isFresh) ->
* if isFresh
* console.log('The Raspberry Pi image v1.2.3 is fresh!')
*/
export const isImageFresh = async (deviceType: string, version: string) => {
const imagePath = await getImagePath(deviceType, version);
let createdDate;
try {
createdDate = await getFileCreatedDate(imagePath);
} 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;
};
/**
* Heuristically determine whether the given semver version is a balenaOS
* ESR version.
*
* @param {string} version Semver version. If invalid or range, return false.
*/
export const isESR = (version: string) => {
const match = version.match(/^v?(\d+)\.\d+\.\d+/);
const major = parseInt((match && match[1]) || '', 10);
return major >= 2018; // note: (NaN >= 2018) is false
};
/**
* @summary Get the most recent compatible version
*
* @param {String} deviceType - device type slug or alias
* @param {String} versionOrRange - supports the same version options
* as `balena.models.os.getMaxSatisfyingVersion`.
* See `getStream` for the detailed explanation.
* @returns {Promise<String>} the most recent compatible version.
*/
const resolveVersion = async (deviceType: string, versionOrRange: string) => {
const balena = getBalenaSdk();
const version = await balena.models.os.getMaxSatisfyingVersion(
deviceType,
versionOrRange,
isESR(versionOrRange) ? 'esr' : 'default',
);
if (!version) {
throw new Error('No such version for the device type');
}
return version;
};
/**
* @summary Get an image from the cache
*
* @param {String} deviceType - device type slug or alias
* @param {String} version - the exact balenaOS version number
* @returns {Promise<fs.ReadStream>} image readable stream
*
* @example
* getImage('raspberry-pi', '1.2.3').then (stream) ->
* stream.pipe(fs.createWriteStream('foo/bar.img'))
*/
export const getImage = async (deviceType: string, version: string) => {
const imagePath = await getImagePath(deviceType, version);
const fs = await import('fs');
const stream = fs.createReadStream(imagePath) as ReturnType<
typeof fs.createReadStream
> & { mime: string };
// Default to application/octet-stream if we could not find a more specific mime type
const { getType } = await import('mime');
stream.mime = getType(imagePath) ?? 'application/octet-stream';
return stream;
};
/**
* @summary Get a writable stream for an image in the cache
*
* @param {String} deviceType - device type slug or alias
* @param {String} version - the exact balenaOS version number
* @returns {Promise<fs.WriteStream & { persistCache: () => Promise<void>, removeCache: () => Promise<void> }>} image writable stream
*
* @example
* getImageWritableStream('raspberry-pi', '1.2.3').then (stream) ->
* fs.createReadStream('foo/bar').pipe(stream)
*/
export const getImageWritableStream = async (
deviceType: string,
version?: string,
) => {
const imagePath = await getImagePath(deviceType, version);
// Ensure the cache directory exists, to prevent
// ENOENT errors when trying to write to it.
const path = await import('path');
const { mkdirp } = await import('mkdirp');
await mkdirp(path.dirname(imagePath));
// Append .inprogress to streams, move them to the right location only on success
const inProgressPath = imagePath + '.inprogress';
const { promises, createWriteStream } = await import('fs');
type ImageWritableStream = ReturnType<typeof createWriteStream> &
Record<'persistCache' | 'removeCache', () => Promise<void>>;
const stream = createWriteStream(inProgressPath) as ImageWritableStream;
// Call .isCompleted on the stream
stream.persistCache = () => promises.rename(inProgressPath, imagePath);
stream.removeCache = () => promises.unlink(inProgressPath);
return stream;
};
type DownloadConfig = NonNullable<
Parameters<SDK.BalenaSDK['models']['os']['download']>[0]
>;
const doDownload = async (options: DownloadConfig) => {
const balena = getBalenaSdk();
const imageStream = await balena.models.os.download(options);
// Piping to a PassThrough stream is needed to be able
// to then pipe the stream to multiple destinations.
const { PassThrough } = await import('stream');
const pass = new PassThrough();
imageStream.pipe(pass);
// Save a copy of the image in the cache
const cacheStream = await getImageWritableStream(
options.deviceType,
options.version,
);
pass.pipe(cacheStream, { end: false });
pass.on('end', cacheStream.persistCache);
// If we return `pass` directly, the client will not be able
// to read all data from it after a delay, since it will be
// instantly piped to `cacheStream`.
// The solution is to create yet another PassThrough stream,
// pipe to it and return the new stream instead.
const pass2 = new PassThrough() as InstanceType<typeof PassThrough> & {
mime: string;
};
pass2.mime = imageStream.mime;
imageStream.on('progress', (state) => pass2.emit('progress', state));
imageStream.on('error', async (err) => {
await cacheStream.removeCache();
pass2.emit('error', err);
});
return pass.pipe(pass2);
};
/**
* @summary Get a device operating system image
* @public
*
* @description
* This function saves a copy of the downloaded image in the cache directory setting specified in [balena-settings-client](https://github.com/balena-io-modules/balena-settings-client).
*
* @param {String} deviceType - device type slug or alias
* @param {String} versionOrRange - can be one of
* * the exact version number,
* in which case it is used if the version is supported,
* or the promise is rejected,
* * a [semver](https://www.npmjs.com/package/semver)-compatible
* range specification, in which case the most recent satisfying version is used
* if it exists, or the promise is rejected,
* * `'latest'` in which case the most recent version is used, including pre-releases,
* * `'recommended'` in which case the recommended version is used, i.e. the most
* recent version excluding pre-releases, the promise is rejected
* if only pre-release versions are available,
* * `'default'` in which case the recommended version is used if available,
* or `latest` is used otherwise.
* Defaults to `'latest'`.
* @param {Object} options
* @param {boolean} options?.developmentMode
* @returns {Promise<NodeJS.ReadableStream>} image readable stream
*
* @example
* getStream('raspberry-pi', 'default').then (stream) ->
* stream.pipe(fs.createWriteStream('foo/bar.img'))
*/
export const getStream = async (
deviceType: string,
versionOrRange?: string,
options: Omit<DownloadConfig, 'deviceType' | 'version'> = {},
) => {
if (versionOrRange == null) {
versionOrRange = 'latest';
}
const version = await resolveVersion(deviceType, versionOrRange);
const isFresh = await isImageFresh(deviceType, version);
const $stream = isFresh
? await getImage(deviceType, version)
: await doDownload({ ...options, deviceType, version });
// schedule the 'version' event for the next iteration of the event loop
// so that callers have a chance of adding an event handler
setImmediate(() =>
$stream.emit('balena-image-manager:resolved-version', version),
);
return $stream;
};

View File

@ -13,10 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import validEmail = require('@resin.io/valid-email');
import { ExpectedError } from '../errors';
// Sufficiently good email regex in order not to bring in another dependency.
const isValidEmail = (email: string): boolean => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
const APPNAME_REGEX = new RegExp(/^[a-zA-Z0-9_-]+$/);
// An regex to detect an IP address, from https://www.regular-expressions.info/ip.html
const IP_REGEX = new RegExp(
@ -26,7 +29,7 @@ const DOTLOCAL_REGEX = new RegExp(/^([a-zA-Z0-9-]+\.)+local$/);
const UUID_REGEX = new RegExp(/^[0-9a-f]+$/);
export function validateEmail(input: string) {
if (!validEmail(input)) {
if (!isValidEmail(input)) {
return 'Email is not valid';
}

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import { expect } from 'chai';
import * as sinon from 'sinon';
import * as url from 'url';
@ -19,28 +18,28 @@ describe('Utils:', async function () {
));
it('should eventually contain an https protocol', () =>
Bluebird.props({
dashboardUrl: balena.settings.get('dashboardUrl'),
loginUrl: utils.getDashboardLoginURL('https://127.0.0.1:3000/callback'),
}).then(function ({ dashboardUrl, loginUrl }) {
Promise.all([
balena.settings.get('dashboardUrl'),
utils.getDashboardLoginURL('https://127.0.0.1:3000/callback'),
]).then(function ([dashboardUrl, loginUrl]) {
const { protocol } = url.parse(loginUrl);
return expect(protocol).to.equal(url.parse(dashboardUrl).protocol);
}));
it('should correctly escape a callback url without a path', () =>
Bluebird.props({
dashboardUrl: balena.settings.get('dashboardUrl'),
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000'),
}).then(function ({ dashboardUrl, loginUrl }) {
Promise.all([
balena.settings.get('dashboardUrl'),
utils.getDashboardLoginURL('http://127.0.0.1:3000'),
]).then(function ([dashboardUrl, loginUrl]) {
const expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000`;
return expect(loginUrl).to.equal(expectedUrl);
}));
return it('should correctly escape a callback url with a path', () =>
Bluebird.props({
dashboardUrl: balena.settings.get('dashboardUrl'),
loginUrl: utils.getDashboardLoginURL('http://127.0.0.1:3000/callback'),
}).then(function ({ dashboardUrl, loginUrl }) {
Promise.all([
balena.settings.get('dashboardUrl'),
utils.getDashboardLoginURL('http://127.0.0.1:3000/callback'),
]).then(function ([dashboardUrl, loginUrl]) {
const expectedUrl = `${dashboardUrl}/login/cli/http%253A%252F%252F127.0.0.1%253A3000%252Fcallback`;
return expect(loginUrl).to.equal(expectedUrl);
}));

View File

@ -1,76 +0,0 @@
import * as stream from 'node:stream';
import { cleanOutput, runCommand } from '../../helpers';
import { BalenaAPIMock } from '../../nock/balena-api-mock';
import { expect } from 'chai';
import * as mock from 'mock-require';
import * as sinon from 'sinon';
// "itSS" means "it() Skip Standalone"
const itSS = process.env.BALENA_CLI_TEST_TYPE === 'standalone' ? it.skip : it;
describe('export fleet content to a file', function () {
let api: BalenaAPIMock;
const releaseBundleCreateStub = sinon.stub();
this.beforeEach(() => {
api = new BalenaAPIMock();
mock('@balena/release-bundle', {
create: releaseBundleCreateStub,
});
});
this.afterEach(() => {
// Check all expected api calls have been made and clean up.
api.done();
mock.stop('@balena/release-bundle');
});
itSS('should export a release to a file', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
releaseBundleCreateStub.resolves(stream.Readable.from('something'));
const { out, err } = await runCommand(
'release export badc0ffe -o /tmp/release.tar.gz',
);
const lines = cleanOutput(out);
expect(lines[0]).to.contain(
'Release badc0ffe has been exported to /tmp/release.tar.gz.',
);
expect(err).to.be.empty;
});
itSS('should fail if the create throws an error', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
releaseBundleCreateStub.rejects(
new Error('Something went wrong creating the bundle'),
);
const { err } = await runCommand(
'release export badc0ffe -o /tmp/release.tar.gz',
);
expect(cleanOutput(err, true)).to.include(
'Release badc0ffe could not be exported: Something went wrong creating the bundle',
);
});
itSS('should parse with application slug and version', async () => {
api.expectGetWhoAmI();
api.expectGetRelease();
api.expectGetApplication();
releaseBundleCreateStub.resolves(stream.Readable.from('something'));
const { out, err } = await runCommand(
'release export org/superApp -o /tmp/release.tar.gz --version 1.2.3+rev1',
);
const lines = cleanOutput(out);
expect(lines[0]).to.contain(
'Release org/superApp version 1.2.3+rev1 has been exported to /tmp/release.tar.gz.',
);
expect(err).to.be.empty;
});
});

View File

@ -15,11 +15,13 @@
* limitations under the License.
*/
import * as Bluebird from 'bluebird';
import * as path from 'path';
import * as zlib from 'zlib';
import { NockMock } from './nock-mock';
import { promisify } from 'util';
const gunzipAsync = promisify(zlib.gunzip);
export const builderResponsePath = path.normalize(
path.join(__dirname, '..', 'test-data', 'builder-response'),
@ -45,9 +47,7 @@ export class BuilderMock extends NockMock {
await opts.checkURI(uri);
if (typeof requestBody === 'string') {
const gzipped = Buffer.from(requestBody, 'hex');
const gunzipped = await Bluebird.fromCallback<Buffer>((cb) => {
zlib.gunzip(gzipped, cb);
});
const gunzipped = await gunzipAsync(gzipped);
await opts.checkBuildRequestBody(gunzipped);
} else {
throw new Error(

View File

@ -205,12 +205,6 @@
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/push/index.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/export.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/import.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/index.js

View File

@ -205,12 +205,6 @@
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/push/index.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/export.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/import.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/index.js

View File

@ -205,12 +205,6 @@
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/push/index.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/export.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/import.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/index.js

View File

@ -205,12 +205,6 @@
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/push/index.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/export.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/import.js
> Warning Entry 'main' not found in %1
%1: node_modules/@oclif/core/package.json
%2: build/commands/release/index.js

View File

@ -205,12 +205,6 @@
> Warning Entry 'main' not found in %1
%1: node_modules\@oclif\core\package.json
%2: build\commands\push\index.js
> Warning Entry 'main' not found in %1
%1: node_modules\@oclif\core\package.json
%2: build\commands\release\export.js
> Warning Entry 'main' not found in %1
%1: node_modules\@oclif\core\package.json
%2: build\commands\release\import.js
> Warning Entry 'main' not found in %1
%1: node_modules\@oclif\core\package.json
%2: build\commands\release\index.js

5
tests/utils.ts Normal file
View File

@ -0,0 +1,5 @@
export async function delay(ms: number) {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

View File

@ -0,0 +1 @@
Lorem ipsum dolor sit amet

Binary file not shown.

View File

@ -0,0 +1,568 @@
import * as stream from 'stream';
import { AssertionError, expect } from 'chai';
import { stub } from 'sinon';
import * as tmp from 'tmp';
import { delay } from '../../utils';
import * as fs from 'fs';
import * as fsAsync from 'fs/promises';
import * as stringToStream from 'string-to-stream';
import { Writable as WritableStream } from 'stream';
import * as imageManager from '../../../build/utils/image-manager';
import { resolve, extname } from 'path';
import * as mockFs from 'mock-fs';
import * as rimraf from 'rimraf';
import { promisify } from 'util';
import * as os from 'os';
// Make sure we're all using literally the same instance of balena-sdk
// so we can mock out methods called by the real code
import { getBalenaSdk } from '../../../build/utils/lazy';
const balena = getBalenaSdk();
const fsExistsAsync = promisify(fs.exists);
const clean = async () => {
await promisify(rimraf)(await balena.settings.get('cacheDirectory'));
};
describe('image-manager', function () {
describe('.getStream()', () => {
describe('given the existing image', function () {
beforeEach(function () {
this.image = tmp.fileSync();
fs.writeSync(this.image.fd, 'Cache image', 0, 'utf8');
this.cacheGetImagePathStub = stub(imageManager, 'getImagePath');
return this.cacheGetImagePathStub.returns(
Promise.resolve(this.image.name),
);
});
afterEach(function () {
this.cacheGetImagePathStub.restore();
return this.image.removeCallback();
});
describe('given the image is fresh', function () {
beforeEach(function () {
this.cacheIsImageFresh = stub(imageManager, 'isImageFresh');
return this.cacheIsImageFresh.returns(Promise.resolve(true));
});
afterEach(function () {
return this.cacheIsImageFresh.restore();
});
it('should eventually become a readable stream of the cached image', function (done) {
this.timeout(5000);
void imageManager.getStream('raspberry-pi').then(function (stream) {
let result = '';
stream.on('data', (chunk) => (result += chunk.toString()));
return stream.on('end', function () {
expect(result).to.equal('Cache image');
return done();
});
});
});
});
describe('given the image is not fresh', function () {
beforeEach(function () {
this.cacheIsImageFresh = stub(imageManager, 'isImageFresh');
return this.cacheIsImageFresh.returns(Promise.resolve(false));
});
afterEach(function () {
return this.cacheIsImageFresh.restore();
});
// Skipping test because we keep getting `Cache image` instead of `Download image`
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')),
);
});
afterEach(function () {
this.osDownloadStub.restore();
});
it('should eventually become a readable stream of the download image and save a backup copy', function (done) {
void imageManager.getStream('raspberry-pi').then((stream) => {
let result = '';
stream.on('data', (chunk) => (result += chunk));
stream.on('end', async () => {
expect(result).to.equal('Download image');
const contents = await fsAsync.readFile(this.image.name, {
encoding: 'utf8',
});
expect(contents).to.equal('Download image');
done();
});
});
});
it('should be able to read from the stream after a slight delay', function (done) {
void imageManager.getStream('raspberry-pi').then(async (s) => {
await delay(200);
const pass = new stream.PassThrough();
s.pipe(pass);
let result = '';
pass.on('data', (chunk) => (result += chunk));
pass.on('end', function () {
expect(result).to.equal('Download image');
done();
});
});
});
});
describe('given a failing download', function () {
beforeEach(function () {
this.osDownloadStream = new stream.PassThrough();
this.osDownloadStub = stub(balena.models.os, 'download');
this.osDownloadStub.returns(Promise.resolve(this.osDownloadStream));
});
afterEach(function () {
this.osDownloadStub.restore();
});
it('should clean up the in progress cached stream if an error occurs', function (done) {
if (os.platform() === 'win32') {
// Skipping test on Windows because we get `EPERM: operation not permitted, rename` for `getImageWritableStream` on the windows runner
this.skip();
}
void imageManager.getStream('raspberry-pi').then((stream) => {
stream.on('data', () => {
// After the first chunk, error
return this.osDownloadStream.emit('error');
});
stream.on('error', async () => {
const contents = await fsAsync
.stat(this.image.name + '.inprogress')
.then(function () {
throw new AssertionError(
'Image cache should be deleted on failure',
);
})
.catch((err) => {
if (err.code !== 'ENOENT') {
throw err;
}
return fsAsync.readFile(this.image.name, {
encoding: 'utf8',
});
});
expect(contents).to.equal('Cache image');
done();
});
stringToStream('Download image').pipe(this.osDownloadStream);
});
});
});
describe('given a stream with the mime property', async function () {
beforeEach(function () {
this.osDownloadStub = stub(balena.models.os, 'download');
const message = 'Lorem ipsum dolor sit amet';
const mockResultStream = stringToStream(message) as ReturnType<
typeof stringToStream
> & {
mime?: string;
};
mockResultStream.mime = 'application/zip';
this.osDownloadStub.returns(Promise.resolve(mockResultStream));
});
afterEach(function () {
this.osDownloadStub.restore();
});
it('should preserve the property', () =>
imageManager
.getStream('raspberry-pi')
.then((resultStream) =>
expect(resultStream.mime).to.equal('application/zip'),
));
});
});
});
});
describe('.getImagePath()', () => {
describe('given a cache directory', function () {
beforeEach(function () {
this.balenaSettingsGetStub = stub(balena.settings, 'get');
this.balenaSettingsGetStub
.withArgs('cacheDirectory')
.returns(
Promise.resolve(
os.platform() === 'win32'
? 'C:\\Users\\johndoe\\_balena\\cache'
: '/Users/johndoe/.balena/cache',
),
);
});
afterEach(function () {
this.balenaSettingsGetStub.restore();
});
describe('given valid slugs', function () {
beforeEach(function () {
this.getDeviceTypeManifestBySlugStub = stub(
balena.models.config,
'getDeviceTypeManifestBySlug',
);
this.getDeviceTypeManifestBySlugStub.withArgs('raspberry-pi').returns(
Promise.resolve({
yocto: {
fstype: 'resin-sdcard',
},
}),
);
this.getDeviceTypeManifestBySlugStub.withArgs('intel-edison').returns(
Promise.resolve({
yocto: {
fstype: 'zip',
},
}),
);
});
afterEach(function () {
this.getDeviceTypeManifestBySlugStub.restore();
});
it('should eventually equal an absolute path', async () => {
await imageManager
.getImagePath('raspberry-pi', '1.2.3')
.then(function (imagePath) {
const isAbsolute = imagePath === resolve(imagePath);
expect(isAbsolute).to.be.true;
});
});
it('should eventually equal the correct path', async function () {
const result = await imageManager.getImagePath(
'raspberry-pi',
'1.2.3',
);
expect(result).to.equal(
os.platform() === 'win32'
? 'C:\\Users\\johndoe\\_balena\\cache\\raspberry-pi-v1.2.3.img'
: '/Users/johndoe/.balena/cache/raspberry-pi-v1.2.3.img',
);
});
it('should use a zip extension for directory images', async () => {
const imagePath = await imageManager.getImagePath(
'intel-edison',
'1.2.3',
);
expect(extname(imagePath)).to.equal('.zip');
});
it('given invalid version should be rejected', async function () {
const promise = imageManager.getImagePath('intel-edison', 'DOUGH');
await expect(promise).to.be.eventually.rejectedWith(
'Invalid version number',
);
});
});
});
});
describe('.isImageFresh()', () => {
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',
},
}),
);
});
afterEach(function () {
this.getDeviceTypeManifestBySlugStub.restore();
});
describe('given the file does not exist', function () {
beforeEach(function () {
this.utilsGetFileCreatedDate = stub(
imageManager,
'getFileCreatedDate',
);
this.utilsGetFileCreatedDate.returns(
Promise.reject(new Error("ENOENT, stat 'raspberry-pi'")),
);
});
afterEach(function () {
this.utilsGetFileCreatedDate.restore();
});
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;
});
});
});
});
});
describe('.getImage()', () => {
describe('given an existing image', function () {
beforeEach(function () {
this.image = tmp.fileSync();
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));
});
afterEach(function (done) {
this.cacheGetImagePathStub.restore();
fs.unlink(this.image.name, done);
});
it('should return a stream to the image', function (done) {
void imageManager
.getImage('lorem-ipsum', '1.2.3')
.then(function (stream) {
let result = '';
stream.on('data', (chunk) => (result += chunk));
stream.on('end', function () {
expect(result).to.equal('Lorem ipsum dolor sit amet');
done();
});
});
});
it('should contain the mime property', () =>
imageManager
.getImage('lorem-ipsum', '1.2.3')
.then((stream) =>
expect(stream.mime).to.equal('application/octet-stream'),
));
});
});
describe('.getImageWritableStream()', () => {
describe('given the valid image path', function () {
beforeEach(function () {
this.image = tmp.fileSync();
this.cacheGetImagePathStub = stub(imageManager, 'getImagePath');
this.cacheGetImagePathStub.returns(Promise.resolve(this.image.name));
});
afterEach(function (done) {
this.cacheGetImagePathStub.restore();
fs.unlink(this.image.name, done);
});
it('should return a writable stream', () =>
imageManager
.getImageWritableStream('raspberry-pi', '1.2.3')
.then((stream) =>
expect(stream).to.be.an.instanceof(WritableStream),
));
it('should allow writing to the stream', function (done) {
if (os.platform() === 'win32') {
// Skipping test on Windows because we get `EPERM: operation not permitted, rename` for `getImageWritableStream` on the windows runner
this.skip();
}
void imageManager
.getImageWritableStream('raspberry-pi', '1.2.3')
.then((stream) => {
const stringStream = stringToStream('Lorem ipsum dolor sit amet');
stringStream.pipe(stream);
stream.on('finish', async () => {
await stream.persistCache();
const contents = await fsAsync.readFile(this.image.name, {
encoding: 'utf8',
});
expect(contents).to.equal('Lorem ipsum dolor sit amet');
done();
});
});
});
});
});
describe('.getFileCreatedDate()', function () {
describe('given the file exists', 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 }));
});
afterEach(function () {
this.fsStatStub.restore();
});
it('should eventually equal the created time in milliseconds', async function () {
const promise = imageManager.getFileCreatedDate('foo');
await expect(promise).to.eventually.equal(this.date);
});
});
describe('given the file does not exist', function () {
beforeEach(function () {
this.fsStatStub = stub(fs.promises, 'stat');
this.fsStatStub
.withArgs('foo')
.returns(Promise.reject(new Error("ENOENT, stat 'foo'")));
});
afterEach(function () {
this.fsStatStub.restore();
});
it('should be rejected with an error', async function () {
const promise = imageManager.getFileCreatedDate('foo');
await expect(promise).to.be.rejectedWith('ENOENT');
});
});
});
describe('.clean()', function () {
describe('given the cache with saved images', function () {
beforeEach(async function () {
this.cacheDirectory = await balena.settings.get('cacheDirectory');
mockFs({
[this.cacheDirectory]: {
'raspberry-pi': 'Raspberry Pi Image',
'intel-edison': 'Intel Edison Image',
parallela: 'Parallela Image',
},
});
});
afterEach(() => {
mockFs.restore();
});
it('should remove the cache directory completely', async function () {
const exists = await fsExistsAsync(this.cacheDirectory);
expect(exists).to.be.true;
await clean();
const exists2 = await fsExistsAsync(this.cacheDirectory);
expect(exists2).to.be.false;
});
});
describe('given no cache', function () {
beforeEach(async function () {
this.cacheDirectory = await balena.settings.get('cacheDirectory');
mockFs({});
});
afterEach(() => mockFs.restore());
it('should keep the cache directory removed', async function () {
const exists = await fsExistsAsync(this.cacheDirectory);
expect(exists).to.be.false;
await clean();
const exists2 = await fsExistsAsync(this.cacheDirectory);
expect(exists2).to.be.false;
});
});
});
});

View File

@ -17,7 +17,6 @@
declare module 'balena-device-init' {
import { DeviceTypeJson } from 'balena-sdk';
import type * as Bluebird from 'bluebird';
interface OperationState {
operation:
@ -78,25 +77,29 @@ declare module 'balena-device-init' {
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,
): Bluebird<InitializeEmitter>;
): Promise<InitializeEmitter>;
export function initialize(
image: string,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
config: object,
): Bluebird<InitializeEmitter>;
): Promise<InitializeEmitter>;
export function getImageOsVersion(
image: string,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
): Bluebird<string | null>;
): Promise<string | null>;
export function getImageManifest(
image: string,
): Bluebird<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>;
): Promise<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>;
}

View File

@ -16,8 +16,6 @@
*/
declare module 'resin-cli-form' {
import Bluebird = require('bluebird');
export type TypeOrPromiseLike<T> = T | PromiseLike<T>;
export type Validate = (
@ -43,9 +41,13 @@ declare module 'resin-cli-form' {
validate?: Validate;
}
export const ask: <T = string>(options: AskOptions<T>) => Bluebird<T>;
// 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 const ask: <T = string>(options: AskOptions<T>) => Promise<T>;
export const run: <T = any>(
questions?: RunQuestion[],
extraOptions?: { override?: object },
) => Bluebird<T>;
) => Promise<T>;
}

View File

@ -1,18 +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 '@resin.io/valid-email';