Compare commits

..

61 Commits

Author SHA1 Message Date
flowzone-app[bot]
85b1e3c2c2
v2.1.0 2025-02-27 16:16:59 +00:00
Matthew Yarmolinsky
e5d1b4ce23
Merge pull request #4406 from balena-io/add-analytics-alert
Add informational notice about how to disable analytics collection
2025-02-27 11:16:08 -05:00
myarmolinsky
aac092fd4d Add informational notice about how to disable analytics collection
Change-type: minor
2025-02-20 09:51:30 -05:00
flowzone-app[bot]
ff852c029e
v2.0.0 2025-02-20 14:27:03 +00:00
flowzone-app[bot]
4759bc7686
Merge pull request #4407 from balena-io/build-ubuntu22-macos13
major: build on ubuntu 22 and macos 13
2025-02-20 14:26:06 +00:00
Edwin Joassart
039a022353 major: build on ubuntu 22 and macos 13 2025-02-20 09:12:20 +01:00
flowzone-app[bot]
4375b960c2
v1.19.25 2024-10-10 10:03:36 +00:00
flowzone-app[bot]
ee5505d596
Merge pull request #4335 from balena-io/bump-etcher-sdk
patch: bump etcher-sdk to 9.1.2
2024-10-10 10:02:28 +00:00
Edwin Joassart
c726b51dca patch: bump etcher-sdk to 9.1.2 2024-10-09 17:42:19 +02:00
flowzone-app[bot]
676eaf82e7
v1.19.24 2024-10-09 14:22:59 +00:00
flowzone-app[bot]
87fb4df9eb
Merge pull request #4333 from balena-io/rglidden/rpm-fix-etcher-util
patch: etcher-util is corrupted in RPM package
2024-10-09 14:21:45 +00:00
Richard Glidden
e43ee788ec patch: etcher-util is corrupted in RPM package
rpmbuild strips executables by default when generating an rpm packge.
This was causing the JavaScript code bundled in the etcher-util file
to be removed, causing "Pkg: Error reading from file." whenever
etcher-util was called.

This in turn caused balena-etcher to generate the error message
`Error: (0, h.requestMetadata) is not a function` when attempting
to write an SD card.

This fixes the issue for RPM builds by replacing the `strip` command
with `true` so that rpmbuild no longer strips the executables and
the embeded code stays intact.

See: https://github.com/balena-io/etcher/issues/4150

Signed-off-by: Richard Glidden <richard@glidden.org>
2024-10-09 15:54:16 +02:00
flowzone-app[bot]
3dc17c89b4
v1.19.23 2024-10-09 13:52:59 +00:00
flowzone-app[bot]
5774dded7b
Merge pull request #4334 from balena-io/marcaurele/remove-gconf2
patch: remove gconf2 libgconf-2-4 deps
2024-10-09 13:52:04 +00:00
Edwin Joassart
9f408241f9
remove gcconf2 deps from docs 2024-10-09 13:55:44 +02:00
Marc-Aurèle Brothier
2ed779ef37 patch: remove gconf2 libgconf-2-4 deps
Closes #4096
2024-10-09 10:50:35 +02:00
flowzone-app[bot]
5fd6376f45
v1.19.22 2024-07-18 18:13:00 +00:00
flowzone-app[bot]
818dcd3b13
Merge pull request #4279 from balena-io/klutchell-patch-1
Replace deprecated Flowzone inputs
2024-07-18 18:12:06 +00:00
Kyle Harding
52d396aa7e
Replace deprecated Flowzone inputs
Change-type: patch
2024-07-17 09:37:12 -04:00
flowzone-app[bot]
c748c2a9c0
v1.19.21 2024-05-30 15:00:39 +00:00
Edwin Joassart
a5dac57b09
Merge pull request #4238 from balena-io/fix-win-2
patch: fix missing windows dependency
2024-05-30 16:59:45 +02:00
Edwin Joassart
8dad81ae34
patch: fix missing windows dependency 2024-05-30 16:28:56 +02:00
Edwin Joassart
d28719daf2
patch: fix missing windows dependency 2024-05-30 14:56:07 +02:00
Edwin Joassart
98db4df0dc patch: fix missing windows dependency 2024-05-30 14:35:02 +02:00
flowzone-app[bot]
52144f4a6e
v1.19.20 2024-05-30 10:17:34 +00:00
flowzone-app[bot]
39b02f2168
Merge pull request #4237 from balena-io/fix-win
patch: fix missing windows dependency
2024-05-30 10:16:39 +00:00
Edwin Joassart
c4d3f8db87 patch: fix missing windows dependency 2024-05-30 11:44:50 +02:00
flowzone-app[bot]
6d796df017
v1.19.19 2024-05-28 12:10:03 +00:00
flowzone-app[bot]
326a3c740f
Merge pull request #4233 from balena-io/sentry
patch: add sentry debug flag
2024-05-28 12:07:54 +00:00
Edwin Joassart
8223130e8d patch: add sentry debug flag 2024-05-28 12:22:34 +02:00
flowzone-app[bot]
3245439744
v1.19.18 2024-05-22 13:28:07 +00:00
flowzone-app[bot]
74854f1720
Merge pull request #4228 from balena-io/aethernet-patch-2
patch: fix sentry DSN
2024-05-22 13:27:17 +00:00
Edwin Joassart
4ffda6e208 patch: fix Sentry DSN for main process 2024-05-22 15:02:04 +02:00
flowzone-app[bot]
62ac0b98b9
v1.19.17 2024-05-09 06:33:47 +00:00
flowzone-app[bot]
ae70c20779
Merge pull request #4221 from balena-io/fix-analytics-imports
patch: fix injection of analytics key at build time
2024-05-09 06:33:00 +00:00
JOASSART Edwin
e94767aca7 patch: fix injection of analytics key at build time 2024-05-08 23:06:34 +02:00
flowzone-app[bot]
6a648e9215
v1.19.16 2024-04-26 14:33:23 +00:00
flowzone-app[bot]
fa8220d5ba
Merge pull request #4212 from balena-io/fix-race
patch: hold request for metadata while waiting for flasher
2024-04-26 14:32:11 +00:00
Edwin Joassart
2dfa795129 patch: hold request for metadata while waiting for flasher 2024-04-26 15:53:59 +02:00
flowzone-app[bot]
73afb2fc55
v1.19.15 2024-04-26 13:27:17 +00:00
flowzone-app[bot]
c5a8bfc0dc
Merge pull request #4211 from balena-io/fix-url-loading
patch: bump etcher-sdk to 9.0.11 to fix url loading using http/2
2024-04-26 13:24:43 +00:00
Edwin Joassart
cb03fb8375 patch: bump etcher-sdk to 9.0.11 to fix url loading using http/2 2024-04-26 14:51:16 +02:00
flowzone-app[bot]
c756b10a38
v1.19.14 2024-04-25 21:11:39 +00:00
flowzone-app[bot]
ebeacc9be9
Merge pull request #4210 from balena-io/bump-pretty-bytes
patch: pretty-bytes to 6.1.1
2024-04-25 21:10:38 +00:00
JOASSART Edwin
fa642270f7 patch: pretty-bytes to 6.1.1 2024-04-25 21:22:58 +02:00
flowzone-app[bot]
0cc7440573
v1.19.13 2024-04-25 19:02:27 +00:00
flowzone-app[bot]
bf5c00a839
Merge pull request #4209 from balena-io/fix-win-install
patch: fix windows squirrel install
2024-04-25 19:01:38 +00:00
Edwin Joassart
bc3340960a patch: use etcher icon as loading for windows installer 2024-04-25 19:24:01 +02:00
Edwin Joassart
d498248a0f patch: fix windows squirrel install 2024-04-25 19:24:01 +02:00
flowzone-app[bot]
2e8e0d77bc
v1.19.12 2024-04-25 16:47:45 +00:00
flowzone-app[bot]
8389537bf4
Merge pull request #4208 from balena-io/bump3
Bump (most) dependencies to latest
2024-04-25 16:46:54 +00:00
Edwin Joassart
afd659f9e5 patch: bump minors & patch 2024-04-25 17:13:27 +02:00
Edwin Joassart
ffdeccf7ef patch: bump @electron-forge/* to 7.4.0 2024-04-25 16:47:18 +02:00
Edwin Joassart
37ac323e10 patch: bump electron to 30.0.1 & @electron/remote to 2.1.2 2024-04-25 16:47:18 +02:00
Edwin Joassart
7c8f3c35d3 patch: npm upgrade 2024-04-25 16:47:18 +02:00
Edwin Joassart
4aa4140d65 patch: bump @balena/lint to 8.0.2 and fix formating 2024-04-25 16:47:18 +02:00
Edwin Joassart
0642611079 patch: fix pretty-bytes imports 2024-04-25 16:47:18 +02:00
Edwin Joassart
2f4a12a48f patch: bump etcher-sdk to 9.0.9 2024-04-25 15:06:05 +02:00
flowzone-app[bot]
70f0fb677c
v1.19.11 2024-04-25 13:00:18 +00:00
flowzone-app[bot]
58c82b33ec
Merge pull request #4207 from balena-io/switch-test-runner-to-wdio
patch: setup wdio and port most tests
2024-04-25 12:59:17 +00:00
Edwin Joassart
a661d102bc patch: setup wdio and port (most) tests 2024-04-25 14:24:36 +02:00
51 changed files with 13312 additions and 23359 deletions

View File

@ -53,6 +53,13 @@ runs:
shell: bash
run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm
# rpmbuild will strip binaries by default, which breaks the sidecar.
# Use a macro to override the "strip" to bypass stripping.
- name: Configure rpmbuild to not strip executables
if: runner.os == 'Linux'
shell: bash
run: echo '%__strip /usr/bin/true' > ~/.rpmmacros
- name: Install host dependencies
if: runner.os == 'macOS'
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
@ -131,7 +138,7 @@ runs:
PLATFORM=Windows
SHA256SUM_BIN=sha256sum
# Install DigiCert Signing Manager Tools
# Install DigiCert Signing Manager Tools
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \
-H "x-api-key:$SM_API_KEY" \
-o smtools-windows-x64.msi

View File

@ -3,23 +3,23 @@ name: test release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: "JSON stringified object containing all the inputs from the calling workflow"
description: 'JSON stringified object containing all the inputs from the calling workflow'
required: true
secrets:
description: "JSON stringified object containing all the secrets from the calling workflow"
description: 'JSON stringified object containing all the secrets from the calling workflow'
required: true
# --- custom environment
NODE_VERSION:
type: string
default: "20.10"
default: '20.10'
VERBOSE:
type: string
default: "true"
default: 'true'
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: "composite"
using: 'composite'
steps:
# https://github.com/actions/setup-node#caching-global-packages-data
- name: Setup Node.js
@ -55,9 +55,15 @@ runs:
# fi
npm ci
# as the shrinkwrap might have been done on mac/linux, this is ensure the package is there for windows
if [[ "$RUNNER_OS" == "Windows" ]]; then
npm i -D winusb-driver-generator
fi
npm run lint
npm run package
npm run test
npm run wdio # test stage, note that it requires the package to be done first
env:
# https://www.electronjs.org/docs/latest/api/environment-variables

View File

@ -18,7 +18,24 @@ jobs:
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
secrets: inherit
with:
custom_runs_on: '[["ubuntu-20.04"],["windows-2019"],["macos-12"],["macos-latest-xlarge"]]'
custom_test_matrix: >
{
"os": [
["ubuntu-22.04"],
["windows-2019"],
["macos-13"],
["macos-latest-xlarge"]
]
}
custom_publish_matrix: >
{
"os": [
["ubuntu-22.04"],
["windows-2019"],
["macos-13"],
["macos-latest-xlarge"]
]
}
restrict_custom_actions: false
github_prerelease: true
cloudflare_website: "etcher"

View File

@ -1,3 +1,247 @@
- commits:
- subject: Add informational notice about how to disable analytics collection
hash: aac092fd4df8750024c082b25dcbd0ae6ee618fd
body: ""
footer:
Change-type: minor
change-type: minor
author: myarmolinsky
nested: []
version: 2.1.0
title: ""
date: 2025-02-27T16:16:57.036Z
- commits:
- subject: "major: build on ubuntu 22 and macos 13"
hash: 039a022353d1980ef9ddd19166515c531e48aba4
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 2.0.0
title: ""
date: 2025-02-20T14:27:01.338Z
- commits:
- subject: "patch: bump etcher-sdk to 9.1.2"
hash: c726b51dca3383c76f4bf824fd5d594ac3069180
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.25
title: ""
date: 2024-10-10T10:03:29.519Z
- commits:
- subject: "patch: etcher-util is corrupted in RPM package"
hash: e43ee788ec5ec49e105ff804206919bb10a59ea7
body: |
rpmbuild strips executables by default when generating an rpm packge.
This was causing the JavaScript code bundled in the etcher-util file
to be removed, causing "Pkg: Error reading from file." whenever
etcher-util was called.
This in turn caused balena-etcher to generate the error message
`Error: (0, h.requestMetadata) is not a function` when attempting
to write an SD card.
This fixes the issue for RPM builds by replacing the `strip` command
with `true` so that rpmbuild no longer strips the executables and
the embeded code stays intact.
See: https://github.com/balena-io/etcher/issues/4150
footer:
Signed-off-by: Richard Glidden <richard@glidden.org>
signed-off-by: Richard Glidden <richard@glidden.org>
author: Richard Glidden
nested: []
version: 1.19.24
title: ""
date: 2024-10-09T14:22:56.623Z
- commits:
- subject: "patch: remove gconf2 libgconf-2-4 deps"
hash: 2ed779ef371db367e4e413c9d0d08fcd738edb5b
body: "Closes #4096"
footer: {}
author: Marc-Aurèle Brothier
nested: []
version: 1.19.23
title: ""
date: 2024-10-09T13:52:54.936Z
- commits:
- subject: Replace deprecated Flowzone inputs
hash: 52d396aa7ea9ae1ef6d68151f582f04f57191b14
body: ""
footer:
Change-type: patch
change-type: patch
author: Kyle Harding
nested: []
version: 1.19.22
title: ""
date: 2024-07-18T18:12:56.368Z
- commits:
- subject: "patch: fix missing windows dependency"
hash: 8dad81ae34b8d71f3d4f7151ee60717e6207ccd8
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: fix missing windows dependency"
hash: d28719daf249f2994acdf94b4bb7ea937ffcab9b
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: fix missing windows dependency"
hash: 98db4df0dc147e5fec9180c50f4e21acf1fd0a58
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.21
title: ""
date: 2024-05-30T15:00:35.706Z
- commits:
- subject: "patch: fix missing windows dependency"
hash: c4d3f8db8769418925a9909ac700edc5f425a068
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.20
title: ""
date: 2024-05-30T10:17:29.075Z
- commits:
- subject: "patch: add sentry debug flag"
hash: 8223130e8dfce180481550d77f022064255601e4
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.19
title: ""
date: 2024-05-28T12:09:51.167Z
- commits:
- subject: "patch: fix Sentry DSN for main process"
hash: 4ffda6e208a6e2f109f652d39e1248bec23a2ddf
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.18
title: ""
date: 2024-05-22T13:28:03.659Z
- commits:
- subject: "patch: fix injection of analytics key at build time"
hash: e94767aca7b07e674bd60176ef77c11440131ace
body: ""
footer: {}
author: JOASSART Edwin
nested: []
version: 1.19.17
title: ""
date: 2024-05-09T06:33:45.091Z
- commits:
- subject: "patch: hold request for metadata while waiting for flasher"
hash: 2dfa795129e287f887b9ea02f2eca717575d27ac
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.16
title: ""
date: 2024-04-26T14:33:19.111Z
- commits:
- subject: "patch: bump etcher-sdk to 9.0.11 to fix url loading using http/2"
hash: cb03fb83754f38d647fc951b94470725b46b2b31
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.15
title: ""
date: 2024-04-26T13:26:57.047Z
- commits:
- subject: "patch: pretty-bytes to 6.1.1"
hash: fa642270f7153f14e45ee03a73bad1f0797cbd51
body: ""
footer: {}
author: JOASSART Edwin
nested: []
version: 1.19.14
title: ""
date: 2024-04-25T21:11:35.350Z
- commits:
- subject: "patch: use etcher icon as loading for windows installer"
hash: bc3340960a765e99f2f02bc21adace91d228d26f
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: fix windows squirrel install"
hash: d498248a0f1416045b836646b72c7b4c588119d3
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.13
title: ""
date: 2024-04-25T19:02:23.576Z
- commits:
- subject: "patch: bump minors & patch"
hash: afd659f9e586e012be7e3b02490d14a8ac64bb35
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: bump @electron-forge/* to 7.4.0"
hash: ffdeccf7efd1412a2e2838fd07df5b21f1233efe
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: bump electron to 30.0.1 & @electron/remote to 2.1.2"
hash: 37ac323e10c07db35a7e47b576d07e1d4d41a470
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: npm upgrade"
hash: 7c8f3c35d3d159e7be73442ab215019dc2388f54
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: bump @balena/lint to 8.0.2 and fix formating"
hash: 4aa4140d65189920938c42c41a6a781c97148c8a
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: fix pretty-bytes imports"
hash: 064261107954dd64d03f94d6aeffd95cd2211df0
body: ""
footer: {}
author: Edwin Joassart
nested: []
- subject: "patch: bump etcher-sdk to 9.0.9"
hash: 2f4a12a48facf0634ed457fe6ed7c50e21b419ee
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.12
title: ""
date: 2024-04-25T16:47:43.024Z
- commits:
- subject: "patch: setup wdio and port (most) tests"
hash: a661d102bc94bf2707f01958d1e9d260efc06c14
body: ""
footer: {}
author: Edwin Joassart
nested: []
version: 1.19.11
title: ""
date: 2024-04-25T13:00:13.805Z
- commits:
- subject: "patch: remove node-ipc and tests"
hash: ccc31bb9aaba8df88b2af612824d9106051e2804
@ -1032,13 +1276,10 @@
nested: []
- subject: "Patch: run linux build on ubuntu-20.04"
hash: adcd8e0325bc891460b3e51aa5403f8675189f13
body: >-
as [`18.04` has been
removed](https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/)
body: |-
as [`18.04` has been removed](https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/)
We cannot use `latest` as the glibc version will cause issue with older
ubuntu version.
We cannot use `latest` as the glibc version will cause issue with older ubuntu version.
footer: {}
author: Edwin Joassart
nested: []
@ -11783,40 +12024,19 @@
changelog-entry: Don't include user paths in Mixpanel usage reports
link: https://github.com/resin-io-modules/etcher-image-stream/blob/master/CHANGELOG.md
subject: Fix uncaught exception if no file was selected from a dialog
body: >-
body: |-
The following error is thrown if the open file dialog is cancelled
without any selection:
Unhandled rejection TypeError: Cannot read property '0' of undefined
at Number.indexedGetter
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
at Number.tryCatcher
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
at Promise._settlePromise
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
at Promise._settlePromise0
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
at Promise._settlePromises
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
at Async._drainQueue
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
at Async._drainQueues
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
at Immediate.Async.drainQueues [as _onImmediate]
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
at Number.indexedGetter (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
at Number.tryCatcher (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
at Promise._settlePromise (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
at Promise._settlePromise0 (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
at Promise._settlePromises (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
at Async._drainQueue (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
at Async._drainQueues (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
at Immediate.Async.drainQueues [as _onImmediate] (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
at processImmediate [as _immediateCallback] (timers.js:383:17)
- hash: 6bd086f1c5c6654a47125cf2d46788655cae2553
author: Juan Cruz Viotti
@ -12413,21 +12633,14 @@
changelog-entry: Use info icon instead of "SHOW FULL FILE NAME" in first step.
fixes: https://github.com/resin-io/etcher/issues/458
subject: Make use of AppImage desktop integration script
body: >-
body: |-
This is useful to prompt the user to install the `.desktop` file.
The `Description` key in `Etcher.desktop` was changed to `Comment` since
`desktop-file-validate` complained with:
Etcher.desktop: error: file contains key "Description" in group "Desktop
Entry", but keys extending the format should start with "X-"
After checking the desktop file format specification, the correct key
should be "Comment"
(https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html).
- hash: c3e360e61933ef0044c005b5e92c879ff9a47c49
author: Juan Cruz Viotti
@ -12640,17 +12853,12 @@
changelog-entry: Fix flashing never starting after elevation in GNU/Linux.
fixes: https://github.com/resin-io/etcher/issues/665
subject: Make all angular modules export the name of the module
body: >-
body: |-
This makes them very nicely require-able, for example:
angular.module('MyModule', [
require('my-dependency');
]);
From
https://medium.com/@kentcdodds/how-to-distribute-your-angularjs-module-e04d4dd58ddc#.yqg2zo8im
From https://medium.com/@kentcdodds/how-to-distribute-your-angularjs-module-e04d4dd58ddc#.yqg2zo8im
- hash: b8f63af3f81bca3abd055303bc91ab35eb126655
author: Juan Cruz Viotti
footers:

View File

@ -3,6 +3,100 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
# v2.1.0
## (2025-02-27)
* Add informational notice about how to disable analytics collection [myarmolinsky]
# v2.0.0
## (2025-02-20)
* major: build on ubuntu 22 and macos 13 [Edwin Joassart]
# v1.19.25
## (2024-10-10)
* patch: bump etcher-sdk to 9.1.2 [Edwin Joassart]
# v1.19.24
## (2024-10-09)
* patch: etcher-util is corrupted in RPM package [Richard Glidden]
# v1.19.23
## (2024-10-09)
* patch: remove gconf2 libgconf-2-4 deps [Marc-Aurèle Brothier]
# v1.19.22
## (2024-07-18)
* Replace deprecated Flowzone inputs [Kyle Harding]
# v1.19.21
## (2024-05-30)
* patch: fix missing windows dependency [Edwin Joassart]
* patch: fix missing windows dependency [Edwin Joassart]
* patch: fix missing windows dependency [Edwin Joassart]
# v1.19.20
## (2024-05-30)
* patch: fix missing windows dependency [Edwin Joassart]
# v1.19.19
## (2024-05-28)
* patch: add sentry debug flag [Edwin Joassart]
# v1.19.18
## (2024-05-22)
* patch: fix Sentry DSN for main process [Edwin Joassart]
# v1.19.17
## (2024-05-09)
* patch: fix injection of analytics key at build time [JOASSART Edwin]
# v1.19.16
## (2024-04-26)
* patch: hold request for metadata while waiting for flasher [Edwin Joassart]
# v1.19.15
## (2024-04-26)
* patch: bump etcher-sdk to 9.0.11 to fix url loading using http/2 [Edwin Joassart]
# v1.19.14
## (2024-04-25)
* patch: pretty-bytes to 6.1.1 [JOASSART Edwin]
# v1.19.13
## (2024-04-25)
* patch: use etcher icon as loading for windows installer [Edwin Joassart]
* patch: fix windows squirrel install [Edwin Joassart]
# v1.19.12
## (2024-04-25)
* patch: bump minors & patch [Edwin Joassart]
* patch: bump @electron-forge/* to 7.4.0 [Edwin Joassart]
* patch: bump electron to 30.0.1 & @electron/remote to 2.1.2 [Edwin Joassart]
* patch: npm upgrade [Edwin Joassart]
* patch: bump @balena/lint to 8.0.2 and fix formating [Edwin Joassart]
* patch: fix pretty-bytes imports [Edwin Joassart]
* patch: bump etcher-sdk to 9.0.9 [Edwin Joassart]
# v1.19.11
## (2024-04-25)
* patch: setup wdio and port (most) tests [Edwin Joassart]
# v1.19.10
## (2024-04-23)

View File

@ -122,7 +122,6 @@ run Etcher on a GNU/Linux system.
- xrender
- xtst
- xscrnsaver
- gconf-2.0
- gmodule-2.0
- nss

View File

@ -7,6 +7,7 @@ import { MakerDMG } from '@electron-forge/maker-dmg';
import { MakerAppImage } from '@reforged/maker-appimage';
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
import { exec } from 'child_process';
import { mainConfig, rendererConfig } from './webpack.config';
import * as sidecar from './forge.sidecar';
@ -59,6 +60,7 @@ const config: ForgeConfig = {
new MakerZIP(),
new MakerSquirrel({
setupIcon: 'assets/icon.ico',
loadingGif: 'assets/icon.png',
...winSigningConfig,
}),
new MakerDMG({
@ -134,24 +136,22 @@ const config: ForgeConfig = {
new sidecar.SidecarPlugin(),
],
hooks: {
readPackageJson: async (_config, packageJson) => {
packageJson.analytics = {};
if (process.env.SENTRY_TOKEN) {
packageJson.analytics.sentry = {
token: process.env.SENTRY_TOKEN,
};
postPackage: async (_forgeConfig, options) => {
if (options.platform === 'linux') {
// symlink the etcher binary from balena-etcher to balenaEtcher to ensure compatibility with the wdio suite and the old name
await new Promise<void>((resolve, reject) => {
exec(
`ln -s "${options.outputPaths}/balena-etcher" "${options.outputPaths}/balenaEtcher"`,
(err) => {
if (err) {
reject(err);
} else {
resolve();
}
},
);
});
}
if (process.env.AMPLITUDE_TOKEN) {
packageJson.analytics.amplitude = {
token: 'balena-etcher',
};
}
// packageJson.packageType = 'dmg' | 'AppImage' | 'rpm' | 'deb' | 'zip' | 'nsis' | 'portable'
return packageJson;
},
},
};

View File

@ -1,5 +1,5 @@
import { PluginBase } from '@electron-forge/plugin-base';
import {
import type {
ForgeHookMap,
ResolvedForgeConfig,
} from '@electron-forge/shared-types';

View File

@ -16,14 +16,15 @@
import * as electron from 'electron';
import * as remote from '@electron/remote';
import { debounce, capitalize, Dictionary, values } from 'lodash';
import type { Dictionary } from 'lodash';
import { debounce, capitalize, values } from 'lodash';
import outdent from 'outdent';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { v4 as uuidV4 } from 'uuid';
import * as packageJSON from '../../../package.json';
import { DrivelistDrive } from '../../shared/drive-constraints';
import type { DrivelistDrive } from '../../shared/drive-constraints';
import * as EXIT_CODES from '../../shared/exit-codes';
import * as messages from '../../shared/messages';
import * as availableDrives from './models/available-drives';
@ -38,7 +39,7 @@ import * as windowProgress from './os/window-progress';
import MainPage from './pages/main/MainPage';
import './css/main.css';
import * as i18next from 'i18next';
import { SourceMetadata } from '../../shared/typings/source-selector';
import type { SourceMetadata } from '../../shared/typings/source-selector';
window.addEventListener(
'unhandledrejection',
@ -141,25 +142,29 @@ export let requestMetadata: any;
// start the api and spawn the child process
spawnChildAndConnect({
withPrivileges: false,
}).then(({ emit, registerHandler }) => {
// start scanning
emit('scan', {});
})
.then(({ emit, registerHandler }) => {
// start scanning
emit('scan', {});
// make the sourceMetada awaitable to be used on source selection
requestMetadata = async (params: any): Promise<SourceMetadata> => {
emit('sourceMetadata', JSON.stringify(params));
// make the sourceMetada awaitable to be used on source selection
requestMetadata = async (params: any): Promise<SourceMetadata> => {
emit('sourceMetadata', JSON.stringify(params));
return new Promise((resolve) =>
registerHandler('sourceMetadata', (data: any) => {
resolve(JSON.parse(data));
}),
);
};
return new Promise((resolve) =>
registerHandler('sourceMetadata', (data: any) => {
resolve(JSON.parse(data));
}),
);
};
registerHandler('drives', (data: any) => {
setDrives(JSON.parse(data));
registerHandler('drives', (data: any) => {
setDrives(JSON.parse(data));
});
})
.catch((error: any) => {
throw new Error(`Failed to start the flasher process. error: ${error}`);
});
});
let popupExists = false;

View File

@ -16,33 +16,32 @@
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import * as sourceDestination from 'etcher-sdk/build/source-destination/';
import type * as sourceDestination from 'etcher-sdk/build/source-destination/';
import * as React from 'react';
import { Flex, ModalProps, Txt, Badge, Link, TableColumn } from 'rendition';
import type { ModalProps, TableColumn } from 'rendition';
import { Flex, Txt, Badge, Link } from 'rendition';
import styled from 'styled-components';
import type {
DriveStatus,
DrivelistDrive,
} from '../../../../shared/drive-constraints';
import {
getDriveImageCompatibilityStatuses,
isDriveValid,
DriveStatus,
DrivelistDrive,
isDriveSizeLarge,
} from '../../../../shared/drive-constraints';
import { compatibility, warning } from '../../../../shared/messages';
import * as prettyBytes from 'pretty-bytes';
import prettyBytes from 'pretty-bytes';
import { getDrives, hasAvailableDrives } from '../../models/available-drives';
import { getImage, isDriveSelected } from '../../models/selection-state';
import { store } from '../../models/store';
import { logEvent, logException } from '../../modules/analytics';
import { open as openExternal } from '../../os/open-external/services/open-external';
import {
Alert,
GenericTableProps,
Modal,
Table,
} from '../../styled-components';
import type { GenericTableProps } from '../../styled-components';
import { Alert, Modal, Table } from '../../styled-components';
import { SourceMetadata } from '../../../../shared/typings/source-selector';
import type { SourceMetadata } from '../../../../shared/typings/source-selector';
import { middleEllipsis } from '../../utils/middle-ellipsis';
import * as i18next from 'i18next';

View File

@ -1,11 +1,12 @@
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import * as React from 'react';
import { Badge, Flex, Txt, ModalProps } from 'rendition';
import type { ModalProps } from 'rendition';
import { Badge, Flex, Txt } from 'rendition';
import { Modal, ScrollableFlex } from '../../styled-components';
import { middleEllipsis } from '../../utils/middle-ellipsis';
import prettyBytes from 'pretty-bytes';
import { DriveWithWarnings } from '../../pages/main/Flash';
import type { DriveWithWarnings } from '../../pages/main/Flash';
import * as i18next from 'i18next';
const DriveStatusWarningModal = ({

View File

@ -24,7 +24,8 @@ import * as settings from '../../models/settings';
import { Actions, store } from '../../models/store';
import * as analytics from '../../modules/analytics';
import { FlashAnother } from '../flash-another/flash-another';
import { FlashResults, FlashError } from '../flash-results/flash-results';
import type { FlashError } from '../flash-results/flash-results';
import { FlashResults } from '../flash-results/flash-results';
import { SafeWebview } from '../safe-webview/safe-webview';
function restart(goToMain: () => void) {

View File

@ -18,7 +18,8 @@ import CircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle.svg';
import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-check.svg';
import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-xmark.svg';
import * as React from 'react';
import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition';
import type { FlexProps, TableColumn } from 'rendition';
import { Flex, Link, Txt } from 'rendition';
import styled from 'styled-components';
import { progress } from '../../../../shared/messages';

View File

@ -20,16 +20,17 @@ import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import type { IpcRendererEvent } from 'electron';
import { ipcRenderer } from 'electron';
import { uniqBy, isNil } from 'lodash';
import * as path from 'path';
import * as prettyBytes from 'pretty-bytes';
import prettyBytes from 'pretty-bytes';
import * as React from 'react';
import { requestMetadata } from '../../app';
import type { ButtonProps } from 'rendition';
import {
Flex,
ButtonProps,
Modal as SmallModal,
Txt,
Card as BaseCard,
@ -63,9 +64,9 @@ import { SVGIcon } from '../svg-icon/svg-icon';
import ImageSvg from '../../../assets/image.svg';
import SrcSvg from '../../../assets/src.svg';
import { DriveSelector } from '../drive-selector/drive-selector';
import { DrivelistDrive } from '../../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../../shared/drive-constraints';
import { isJson } from '../../../../shared/utils';
import {
import type {
SourceMetadata,
Authentication,
Source,
@ -307,6 +308,7 @@ const FlowSelector = styled(
interface SourceSelectorProps {
flashing: boolean;
hideAnalyticsAlert: () => void;
}
interface SourceSelectorState {
@ -358,6 +360,20 @@ export class SourceSelector extends React.Component<
ipcRenderer.removeListener('select-image', this.onSelectImage);
}
public componentDidUpdate(
_prevProps: Readonly<SourceSelectorProps>,
prevState: Readonly<SourceSelectorState>,
) {
if (
(!prevState.showDriveSelector && this.state.showDriveSelector) ||
(!prevState.showURLSelector && this.state.showURLSelector) ||
(!prevState.showImageDetails && this.state.showImageDetails) ||
(!prevState.imageSelectorOpen && this.state.imageSelectorOpen)
) {
this.props.hideAnalyticsAlert();
}
}
private async onSelectImage(_event: IpcRendererEvent, imagePath: string) {
this.setState({ imageLoading: true });
await this.selectSource(
@ -381,6 +397,7 @@ export class SourceSelector extends React.Component<
});
selectionState.deselectImage();
this.props.hideAnalyticsAlert();
}
private selectSource(
@ -422,6 +439,14 @@ export class SourceSelector extends React.Component<
// this will send an event down the ipcMain asking for metadata
// we'll get the response through an event
// FIXME: This is a poor man wait while loading to prevent a potential race condition without completely blocking the interface
// This should be addressed when refactoring the GUI
let retriesLeft = 10;
while (requestMetadata === undefined && retriesLeft > 0) {
await new Promise((resolve) => setTimeout(resolve, 1050)); // api is trying to connect every 1000, this is offset to make sure we fall between retries
retriesLeft--;
}
metadata = await requestMetadata({ selected, SourceType, auth });
if (!metadata?.hasMBR && this.state.warning === null) {

View File

@ -16,14 +16,13 @@
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
import * as React from 'react';
import { Flex, FlexProps, Txt } from 'rendition';
import type { FlexProps } from 'rendition';
import { Flex, Txt } from 'rendition';
import {
getDriveImageCompatibilityStatuses,
DriveStatus,
} from '../../../../shared/drive-constraints';
import type { DriveStatus } from '../../../../shared/drive-constraints';
import { getDriveImageCompatibilityStatuses } from '../../../../shared/drive-constraints';
import { compatibility, warning } from '../../../../shared/messages';
import * as prettyBytes from 'pretty-bytes';
import prettyBytes from 'pretty-bytes';
import { getImage, getSelectedDrives } from '../../models/selection-state';
import {
ChangeButton,

View File

@ -17,10 +17,8 @@
import * as React from 'react';
import { Flex, Txt } from 'rendition';
import {
DriveSelector,
DriveSelectorProps,
} from '../drive-selector/drive-selector';
import type { DriveSelectorProps } from '../drive-selector/drive-selector';
import { DriveSelector } from '../drive-selector/drive-selector';
import {
isDriveSelected,
getImage,
@ -36,7 +34,7 @@ import { TargetSelectorButton } from './target-selector-button';
import TgtSvg from '../../../assets/tgt.svg';
import DriveSvg from '../../../assets/drive.svg';
import { warning } from '../../../../shared/messages';
import { DrivelistDrive } from '../../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../../shared/drive-constraints';
import * as i18next from 'i18next';
export const getDriveListLabel = () => {
@ -102,12 +100,14 @@ interface TargetSelectorProps {
disabled: boolean;
hasDrive: boolean;
flashing: boolean;
hideAnalyticsAlert: () => void;
}
export const TargetSelector = ({
disabled,
hasDrive,
flashing,
hideAnalyticsAlert,
}: TargetSelectorProps) => {
// TODO: inject these from redux-connector
const [{ driveListLabel, targets }, setStateSlice] = React.useState(
@ -139,6 +139,7 @@ export const TargetSelector = ({
tooltip={driveListLabel}
openDriveSelector={() => {
setShowTargetSelectorModal(true);
hideAnalyticsAlert();
}}
reselectDrive={() => {
analytics.logEvent('Reselect drive');

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { DrivelistDrive } from '../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../shared/drive-constraints';
import { Actions, store } from './store';
export function hasAvailableDrives() {

View File

@ -15,9 +15,9 @@
*/
import * as electron from 'electron';
import * as sdk from 'etcher-sdk';
import type * as sdk from 'etcher-sdk';
import * as _ from 'lodash';
import { DrivelistDrive } from '../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../shared/drive-constraints';
import { bytesToMegabytes } from '../../../shared/units';
import { Actions, store } from './store';

View File

@ -15,12 +15,11 @@
*/
import * as _ from 'lodash';
import { Animator, AnimationFunction, Color, RGBLed } from 'sys-class-rgb-led';
import type { AnimationFunction, Color } from 'sys-class-rgb-led';
import { Animator, RGBLed } from 'sys-class-rgb-led';
import {
DrivelistDrive,
isSourceDrive,
} from '../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../shared/drive-constraints';
import { isSourceDrive } from '../../../shared/drive-constraints';
import { getDrives } from './available-drives';
import { getSelectedDrives } from './selection-state';
import * as settings from './settings';

View File

@ -1,4 +1,4 @@
import { DrivelistDrive } from '../../../shared/drive-constraints';
import type { DrivelistDrive } from '../../../shared/drive-constraints';
/*
* Copyright 2016 balena.io
*
@ -15,7 +15,7 @@ import { DrivelistDrive } from '../../../shared/drive-constraints';
* limitations under the License.
*/
import { SourceMetadata } from '../components/source-selector/source-selector';
import type { SourceMetadata } from '../../../shared/typings/source-selector';
import * as availableDrives from './available-drives';
import { Actions, store } from './store';

View File

@ -14,12 +14,13 @@
* limitations under the License.
*/
import * as _ from 'lodash';
import { Client, createClient, createNoopClient } from 'analytics-client';
import { findLastIndex, once } from 'lodash';
import type { Client } from 'analytics-client';
import { createClient, createNoopClient } from 'analytics-client';
import * as SentryRenderer from '@sentry/electron/renderer';
import * as settings from '../models/settings';
import { store } from '../models/store';
import * as packageJSON from '../../../../package.json';
import { version } from '../../../../package.json';
type AnalyticsPayload = _.Dictionary<any>;
@ -72,7 +73,7 @@ export const anonymizePath = (input: string) => {
const segments = mainPart.split(sep);
// Moving from the end, find the first marker and cut the path from there.
const startCutIndex = _.findLastIndex(segments, (segment) =>
const startCutIndex = findLastIndex(segments, (segment) =>
etcherSegmentMarkers.includes(segment),
);
return (
@ -118,21 +119,23 @@ let analyticsClient: Client;
/**
* @summary Init analytics configurations
*/
export const initAnalytics = _.once(() => {
export const initAnalytics = once(() => {
const dsn =
settings.getSync('analyticsSentryToken') ||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
SentryRenderer.init({ dsn, beforeSend: anonymizeSentryData });
settings.getSync('analyticsSentryToken') || process.env.SENTRY_TOKEN;
SentryRenderer.init({
dsn,
beforeSend: anonymizeSentryData,
debug: process.env.ETCHER_SENTRY_DEBUG === 'true',
});
const projectName =
settings.getSync('analyticsAmplitudeToken') ||
_.get(packageJSON, ['analytics', 'amplitude', 'token']);
settings.getSync('analyticsAmplitudeToken') || process.env.AMPLITUDE_TOKEN;
const clientConfig = {
projectName,
endpoint: 'data.balena-cloud.com',
componentName: 'etcher',
componentVersion: packageJSON.version,
componentVersion: version,
};
analyticsClient = projectName
? createClient(clientConfig)
@ -141,7 +144,7 @@ export const initAnalytics = _.once(() => {
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key: any, value: any) => {
return (_key: any, value: any) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;

View File

@ -14,11 +14,11 @@
* limitations under the License.
*/
import { Drive as DrivelistDrive } from 'drivelist';
import * as sdk from 'etcher-sdk';
import { Dictionary } from 'lodash';
import type { Drive as DrivelistDrive } from 'drivelist';
import type * as sdk from 'etcher-sdk';
import type { Dictionary } from 'lodash';
import * as errors from '../../../shared/errors';
import { SourceMetadata } from '../../../shared/typings/source-selector';
import type { SourceMetadata } from '../../../shared/typings/source-selector';
import * as flashState from '../models/flash-state';
import * as selectionState from '../models/selection-state';
import * as settings from '../models/settings';

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import * as prettyBytes from 'pretty-bytes';
import prettyBytes from 'pretty-bytes';
import * as i18next from 'i18next';
export interface FlashState {
@ -34,6 +34,8 @@ export function fromFlashState({
status: string;
position?: string;
} {
console.log(i18next.t('progress.starting'));
if (type === undefined) {
return { status: i18next.t('progress.starting') };
} else if (type === 'decompressing') {

View File

@ -17,7 +17,8 @@
import * as remote from '@electron/remote';
import { percentageToFloat } from '../../../shared/utils';
import { FlashState, titleFromFlashState } from '../modules/progress-status';
import type { FlashState } from '../modules/progress-status';
import { titleFromFlashState } from '../modules/progress-status';
/**
* @summary The title of the main window upon program launch

View File

@ -15,19 +15,20 @@
*/
import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/gear.svg';
import CloseSvg from '@fortawesome/fontawesome-free/svgs/solid/x.svg';
import QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-question.svg';
import * as path from 'path';
import * as prettyBytes from 'pretty-bytes';
import prettyBytes from 'pretty-bytes';
import * as React from 'react';
import { Flex } from 'rendition';
import { Alert, Flex, Link } from 'rendition';
import styled from 'styled-components';
import FinishPage from '../../components/finish/finish';
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import { SettingsModal } from '../../components/settings/settings';
import { SourceSelector } from '../../components/source-selector/source-selector';
import { SourceMetadata } from '../../../../shared/typings/source-selector';
import type { SourceMetadata } from '../../../../shared/typings/source-selector';
import * as flashState from '../../models/flash-state';
import * as selectionState from '../../models/selection-state';
import * as settings from '../../models/settings';
@ -35,6 +36,7 @@ import { observe } from '../../models/store';
import { open as openExternal } from '../../os/open-external/services/open-external';
import {
IconButton as BaseIcon,
IconButton,
ThemedProvider,
} from '../../styled-components';
@ -46,6 +48,7 @@ import { FlashStep } from './Flash';
import EtcherSvg from '../../../assets/etcher.svg';
import { SafeWebview } from '../../components/safe-webview/safe-webview';
import { theme } from '../../theme';
const Icon = styled(BaseIcon)`
margin-right: 20px;
@ -97,6 +100,8 @@ const StepBorder = styled.div<{
margin-left: ${(props) => (props.right ? '-120px' : undefined)};
`;
const ANALYTICS_ALERT_VISIBILITY_KEY = 'analytics_alert_visible';
interface MainPageStateFromStore {
isFlashing: boolean;
hasImage: boolean;
@ -113,6 +118,7 @@ interface MainPageState {
isWebviewShowing: boolean;
hideSettings: boolean;
featuredProjectURL?: string;
analyticsAlertIsVisible: boolean;
}
export class MainPage extends React.Component<
@ -125,6 +131,8 @@ export class MainPage extends React.Component<
current: 'main',
isWebviewShowing: false,
hideSettings: true,
analyticsAlertIsVisible:
localStorage.getItem(ANALYTICS_ALERT_VISIBILITY_KEY) !== 'false',
...this.stateHelper(),
};
}
@ -153,6 +161,13 @@ export class MainPage extends React.Component<
return url.toString();
}
private hideAnalyticsAlert = () => {
if (this.state.analyticsAlertIsVisible) {
localStorage.setItem(ANALYTICS_ALERT_VISIBILITY_KEY, 'false');
this.setState({ analyticsAlertIsVisible: false });
}
};
public async componentDidMount() {
observe(() => {
this.setState(this.stateHelper());
@ -160,6 +175,17 @@ export class MainPage extends React.Component<
this.setState({ featuredProjectURL: await this.getFeaturedProjectURL() });
}
public componentDidUpdate(
_prevProps: object,
prevState: Readonly<MainPageState & MainPageStateFromStore>,
) {
if (this.state.analyticsAlertIsVisible) {
if (prevState.hideSettings !== this.state.hideSettings) {
this.setState({ analyticsAlertIsVisible: false });
}
}
}
private renderMain() {
const state = flashState.getFlashState();
const shouldDriveStepBeDisabled = !this.state.hasImage;
@ -169,86 +195,127 @@ export class MainPage extends React.Component<
!this.state.isFlashing || !this.state.isWebviewShowing;
return (
<Flex
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
justifyContent="space-between"
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px 18px ${this.state.isWebviewShowing ? 35 : 55}px`}
flexDirection="column"
>
{notFlashingOrSplitView && (
<>
<SourceSelector flashing={this.state.isFlashing} />
<Flex>
<StepBorder disabled={shouldDriveStepBeDisabled} left />
</Flex>
<TargetSelector
disabled={shouldDriveStepBeDisabled}
hasDrive={this.state.hasDrive}
flashing={this.state.isFlashing}
/>
<Flex>
<StepBorder disabled={shouldFlashStepBeDisabled} right />
</Flex>
</>
)}
<Flex
justifyContent="space-between"
mb={this.state.analyticsAlertIsVisible ? '0px' : '92px'}
>
{notFlashingOrSplitView && (
<>
<SourceSelector
flashing={this.state.isFlashing}
hideAnalyticsAlert={this.hideAnalyticsAlert}
/>
<Flex>
<StepBorder disabled={shouldDriveStepBeDisabled} left />
</Flex>
<TargetSelector
disabled={shouldDriveStepBeDisabled}
hasDrive={this.state.hasDrive}
flashing={this.state.isFlashing}
hideAnalyticsAlert={this.hideAnalyticsAlert}
/>
<Flex>
<StepBorder disabled={shouldFlashStepBeDisabled} right />
</Flex>
</>
)}
{this.state.isFlashing && this.state.isWebviewShowing && (
<Flex
style={{
position: 'absolute',
top: 0,
left: 0,
width: '36.2vw',
height: '100vh',
zIndex: 1,
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
}}
>
<ReducedFlashingInfos
imageLogo={this.state.imageLogo}
imageName={this.state.imageName}
imageSize={
typeof this.state.imageSize === 'number'
? prettyBytes(this.state.imageSize)
: ''
}
driveTitle={this.state.driveTitle}
driveLabel={this.state.driveLabel}
{this.state.isFlashing && this.state.isWebviewShowing && (
<Flex
style={{
position: 'absolute',
color: '#fff',
left: 35,
top: 72,
top: 0,
left: 0,
width: '36.2vw',
height: '100vh',
zIndex: 1,
boxShadow: '0 2px 15px 0 rgba(0, 0, 0, 0.2)',
}}
>
<ReducedFlashingInfos
imageLogo={this.state.imageLogo}
imageName={this.state.imageName}
imageSize={
typeof this.state.imageSize === 'number'
? prettyBytes(this.state.imageSize)
: ''
}
driveTitle={this.state.driveTitle}
driveLabel={this.state.driveLabel}
style={{
position: 'absolute',
color: '#fff',
left: 35,
top: 72,
}}
/>
</Flex>
)}
{this.state.isFlashing && this.state.featuredProjectURL && (
<SafeWebview
src={this.state.featuredProjectURL}
onWebviewShow={(isWebviewShowing: boolean) => {
this.setState({ isWebviewShowing });
}}
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '63.8vw',
height: '100vh',
}}
/>
</Flex>
)}
{this.state.isFlashing && this.state.featuredProjectURL && (
<SafeWebview
src={this.state.featuredProjectURL}
onWebviewShow={(isWebviewShowing: boolean) => {
this.setState({ isWebviewShowing });
}}
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '63.8vw',
height: '100vh',
}}
/>
)}
)}
<FlashStep
width={this.state.isWebviewShowing ? '220px' : '200px'}
goToSuccess={() => this.setState({ current: 'success' })}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
isFlashing={this.state.isFlashing}
step={state.type}
percentage={state.percentage}
position={state.position}
failed={state.failed}
speed={state.speed}
eta={state.eta}
style={{ zIndex: 1 }}
/>
<FlashStep
width={this.state.isWebviewShowing ? '220px' : '200px'}
goToSuccess={() => this.setState({ current: 'success' })}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
isFlashing={this.state.isFlashing}
step={state.type}
percentage={state.percentage}
position={state.position}
failed={state.failed}
speed={state.speed}
eta={state.eta}
style={{ zIndex: 1 }}
/>
</Flex>
{this.state.analyticsAlertIsVisible && (
<Alert mt="18px" style={{ boxShadow: 'none', fontSize: '12px' }}>
<Flex alignItems="center" justifyContent="space-between">
<Flex flexDirection="column">
<div>
Etcher collects a limited amount of anonymous data to help us
improve user experience. You can opt out in the{' '}
<Link onClick={() => this.setState({ hideSettings: false })}>
settings
</Link>
.
</div>
<div>
For more information about how we use this data, see our{' '}
<Link
onClick={(e) => {
e.stopPropagation();
openExternal('https://www.balena.io/privacy-policy');
}}
>
privacy policy
</Link>
.
</div>
</Flex>
{/* TODO: can we use onDismiss instead? */}
<IconButton onClick={this.hideAnalyticsAlert}>
<CloseSvg height="0.75rem" fill={theme.colors.text.main} />
</IconButton>
</Flex>
</Alert>
)}
</Flex>
);
}

View File

@ -15,16 +15,18 @@
*/
import * as React from 'react';
import type {
FlexProps,
ButtonProps,
TableProps as BaseTableProps,
} from 'rendition';
import {
Alert as AlertBase,
Flex,
FlexProps,
Button,
ButtonProps,
Modal as ModalBase,
Provider,
Table as BaseTable,
TableProps as BaseTableProps,
Txt,
} from 'rendition';
import styled, { css } from 'styled-components';

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Dictionary } from 'lodash';
import type { Dictionary } from 'lodash';
type BalenaTag = {
id: number;

View File

@ -27,7 +27,7 @@ import { promises as fs } from 'fs';
import { platform } from 'os';
import * as path from 'path';
import * as semver from 'semver';
import * as lodash from 'lodash';
import { once } from 'lodash';
import './app/i18n';
@ -37,7 +37,6 @@ import * as settings from './app/models/settings';
import { buildWindowMenu } from './menu';
import * as i18n from 'i18next';
import * as SentryMain from '@sentry/electron/main';
import * as packageJSON from '../../package.json';
import { anonymizeSentryData } from './app/modules/analytics';
import { delay } from '../shared/utils';
@ -115,12 +114,16 @@ async function getCommandLineURL(argv: string[]): Promise<string | undefined> {
}
}
const initSentryMain = lodash.once(() => {
const initSentryMain = once(() => {
const dsn =
settings.getSync('analyticsSentryToken') ||
lodash.get(packageJSON, ['analytics', 'sentry', 'token']);
settings.getSync('analyticsSentryToken') || process.env.SENTRY_TOKEN;
SentryMain.init({ dsn, beforeSend: anonymizeSentryData });
SentryMain.init({
dsn,
beforeSend: anonymizeSentryData,
debug: process.env.ETCHER_SENTRY_DEBUG === 'true',
});
console.log(SentryMain.getCurrentScope());
});
const sourceSelectorReady = new Promise((resolve) => {
@ -300,7 +303,7 @@ async function main(): Promise<void> {
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
// tslint:disable-next-line:no-var-requires
if (require('electron-squirrel-startup')) {
app.quit();
electron.app.quit();
}
main();

View File

@ -14,12 +14,12 @@
* limitations under the License.
*/
import { Drive } from 'drivelist';
import type { Drive } from 'drivelist';
import { isNil } from 'lodash';
import * as pathIsInside from 'path-is-inside';
import * as messages from './messages';
import { SourceMetadata } from './typings/source-selector';
import type { SourceMetadata } from './typings/source-selector';
/**
* @summary The default unknown size for things such as images and drives

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Dictionary } from 'lodash';
import type { Dictionary } from 'lodash';
import { outdent } from 'outdent';
import prettyBytes from 'pretty-bytes';
import '../gui/app/i18n';

View File

@ -42,11 +42,9 @@ import { mkdir, writeFile, copyFile, readFile } from 'fs/promises';
export async function sudo(
command: string,
name: string,
_name: string,
env: any,
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
// console.log('name', name);
const uuid = uuidv4();
const temp = tmpdir();

View File

@ -1,6 +1,6 @@
import { GPTPartition, MBRPartition } from 'partitioninfo';
import { sourceDestination } from 'etcher-sdk';
import { DrivelistDrive } from '../drive-constraints';
import type { GPTPartition, MBRPartition } from 'partitioninfo';
import type { sourceDestination } from 'etcher-sdk';
import type { DrivelistDrive } from '../drive-constraints';
export type Source = 'File' | 'BlockDevice' | 'Http';

View File

@ -15,18 +15,19 @@
*/
import { WebSocketServer } from 'ws';
import { Dictionary, values } from 'lodash';
import type { Dictionary } from 'lodash';
import { values } from 'lodash';
import type { MultiDestinationProgress } from 'etcher-sdk/build/multi-write';
import { toJSON } from '../shared/errors';
import { GENERAL_ERROR, SUCCESS } from '../shared/exit-codes';
import { WriteOptions } from './types/types';
import type { WriteOptions } from './types/types';
import { write, cleanup } from './child-writer';
import { startScanning } from './scanner';
import { getSourceMetadata } from './source-metadata';
import { DrivelistDrive } from '../shared/drive-constraints';
import { SourceMetadata } from '../shared/typings/source-selector';
import type { DrivelistDrive } from '../shared/drive-constraints';
import type { SourceMetadata } from '../shared/typings/source-selector';
const ETCHER_SERVER_ADDRESS = process.env.ETCHER_SERVER_ADDRESS as string;
const ETCHER_SERVER_PORT = process.env.ETCHER_SERVER_PORT as string;

View File

@ -16,26 +16,24 @@
* This file handles the writer process.
*/
import {
import type {
OnProgressFunction,
OnFailFunction,
MultiDestinationProgress,
} from 'etcher-sdk/build/multi-write';
import {
decompressThenFlash,
DECOMPRESSED_IMAGE_PREFIX,
MultiDestinationProgress,
} from 'etcher-sdk/build/multi-write';
import { totalmem } from 'os';
import { cleanupTmpFiles } from 'etcher-sdk/build/tmp';
import {
File,
Http,
BlockDevice,
SourceDestination,
} from 'etcher-sdk/build/source-destination';
import type { SourceDestination } from 'etcher-sdk/build/source-destination';
import { File, Http, BlockDevice } from 'etcher-sdk/build/source-destination';
import { WriteResult, FlashError, WriteOptions } from './types/types';
import type { WriteResult, FlashError, WriteOptions } from './types/types';
import { isJson } from '../shared/utils';
import { toJSON } from '../shared/errors';

View File

@ -15,8 +15,8 @@
*/
import * as sdk from 'etcher-sdk';
import type { Adapter } from 'etcher-sdk/build/scanner/adapters';
import {
Adapter,
BlockDeviceAdapter,
UsbbootDeviceAdapter,
} from 'etcher-sdk/build/scanner/adapters';

View File

@ -1,8 +1,9 @@
import { scanner as driveScanner } from './drive-scanner';
import * as sdk from 'etcher-sdk';
import { DrivelistDrive } from '../shared/drive-constraints';
import type { DrivelistDrive } from '../shared/drive-constraints';
import outdent from 'outdent';
import { Dictionary, values, keyBy, padStart } from 'lodash';
import type { Dictionary } from 'lodash';
import { values, keyBy, padStart } from 'lodash';
import { emitDrives } from './api';
let availableDrives: DrivelistDrive[] = [];

View File

@ -2,15 +2,16 @@
import { sourceDestination } from 'etcher-sdk';
import { replaceWindowsNetworkDriveLetter } from '../gui/app/os/windows-network-drives';
import axios, { AxiosRequestConfig } from 'axios';
import type { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { isJson } from '../shared/utils';
import * as path from 'path';
import {
import type {
SourceMetadata,
Authentication,
Source,
} from '../shared/typings/source-selector';
import { DrivelistDrive } from '../shared/drive-constraints';
import type { DrivelistDrive } from '../shared/drive-constraints';
import { omit } from 'lodash';
function isString(value: any): value is string {

View File

@ -1,6 +1,6 @@
import { Metadata } from 'etcher-sdk/build/source-destination';
import { SourceMetadata } from '../../shared/typings/source-selector';
import { Drive as DrivelistDrive } from 'drivelist';
import type { Metadata } from 'etcher-sdk/build/source-destination';
import type { SourceMetadata } from '../../shared/typings/source-selector';
import type { Drive as DrivelistDrive } from 'drivelist';
export interface WriteResult {
bytesWritten?: number;

35122
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"private": true,
"displayName": "balenaEtcher",
"productName": "balenaEtcher",
"version": "1.19.10",
"version": "2.1.0",
"packageType": "local",
"main": ".webpack/main",
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
@ -16,12 +16,11 @@
"scripts": {
"prettify": "prettier --write lib/**/*.css && balena-lint --fix --typescript typings lib tests forge.config.ts forge.sidecar.ts webpack.config.ts",
"lint": "npm run prettify && catch-uncommitted",
"test-gui": "xvfb-maybe electron-mocha --recursive --reporter spec --window-config tests/gui/window-config.json --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox --renderer tests/gui/**/*.ts",
"test-shared": "xvfb-maybe electron-mocha --recursive --reporter spec --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox tests/shared/**/*.ts",
"test": "npm run test-gui && npm run test-shared",
"test": "echo 'Only use custom tests; if you want to test locally, use `npm run wdio`' && exit 0",
"package": "electron-forge package",
"start": "electron-forge start",
"make": "electron-forge make"
"make": "electron-forge make",
"wdio": "xvfb-maybe wdio run ./wdio.conf.ts"
},
"husky": {
"hooks": {
@ -31,70 +30,70 @@
"author": "Balena Ltd. <hello@balena.io>",
"license": "Apache-2.0",
"dependencies": {
"@electron/remote": "^2.1.0",
"@fortawesome/fontawesome-free": "6.5.1",
"@electron/remote": "^2.1.2",
"@fortawesome/fontawesome-free": "^6.5.2",
"@ronomon/direct-io": "^3.0.1",
"@sentry/electron": "^4.15.1",
"@sentry/electron": "^4.24.0",
"analytics-client": "^2.0.1",
"axios": "^1.6.0",
"axios": "^1.6.8",
"debug": "4.3.4",
"drivelist": "^12.0.2",
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "6.1.7",
"etcher-sdk": "9.0.7",
"i18next": "23.7.8",
"electron-updater": "6.1.8",
"etcher-sdk": "9.1.2",
"i18next": "23.11.2",
"immutable": "3.8.2",
"lodash": "4.17.21",
"outdent": "0.8.0",
"path-is-inside": "1.0.2",
"pretty-bytes": "5.6.0",
"pretty-bytes": "6.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "13.5.0",
"redux": "4.2.1",
"rendition": "35.1.2",
"semver": "7.5.4",
"rendition": "35.2.0",
"semver": "7.6.0",
"styled-components": "5.3.6",
"sys-class-rgb-led": "3.0.1",
"uuid": "9.0.1",
"ws": "^8.16.0"
},
"devDependencies": {
"@balena/lint": "7.2.4",
"@electron-forge/cli": "7.2.0",
"@electron-forge/maker-deb": "7.2.0",
"@electron-forge/maker-dmg": "7.2.0",
"@electron-forge/maker-rpm": "7.2.0",
"@electron-forge/maker-squirrel": "7.2.0",
"@electron-forge/maker-zip": "7.2.0",
"@electron-forge/plugin-auto-unpack-natives": "7.2.0",
"@electron-forge/plugin-webpack": "7.2.0",
"@balena/lint": "8.0.2",
"@electron-forge/cli": "7.4.0",
"@electron-forge/maker-deb": "7.4.0",
"@electron-forge/maker-dmg": "7.4.0",
"@electron-forge/maker-rpm": "7.4.0",
"@electron-forge/maker-squirrel": "7.4.0",
"@electron-forge/maker-zip": "7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "7.4.0",
"@electron-forge/plugin-webpack": "7.4.0",
"@reforged/maker-appimage": "3.3.2",
"@svgr/webpack": "8.1.0",
"@types/chai": "4.3.11",
"@types/chai": "4.3.14",
"@types/debug": "^4.1.12",
"@types/mime-types": "2.1.4",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.6",
"@types/react": "17.0.2",
"@types/react-dom": "17.0.2",
"@types/semver": "7.5.6",
"@types/sinon": "17.0.2",
"@types/semver": "7.5.8",
"@types/sinon": "17.0.3",
"@types/tmp": "0.2.6",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"@yao-pkg/pkg": "^5.11.1",
"@wdio/cli": "^8.36.1",
"@wdio/local-runner": "^8.36.1",
"@wdio/mocha-framework": "^8.36.1",
"@wdio/spec-reporter": "^8.36.1",
"@yao-pkg/pkg": "^5.11.5",
"catch-uncommitted": "^2.0.0",
"chai": "4.3.10",
"css-loader": "5.2.7",
"electron": "27.1.3",
"electron-mocha": "^12.2.0",
"electron": "30.0.1",
"file-loader": "6.2.0",
"husky": "8.0.3",
"mocha": "^10.2.0",
"native-addon-loader": "2.0.1",
"node-loader": "^2.0.0",
"omit-deep-lodash": "1.1.7",
"sinon": "17.0.1",
"sinon": "^17.0.1",
"string-replace-loader": "3.1.0",
"style-loader": "3.3.3",
"ts-loader": "^9.5.1",
@ -102,12 +101,11 @@
"tslib": "2.6.2",
"typescript": "^5.3.3",
"url-loader": "4.1.1",
"wdio-electron-service": "^6.4.1",
"xvfb-maybe": "^0.2.1"
},
"hostDependencies": {
"debian": [
"gconf-service",
"gconf2",
"libasound2",
"libatk1.0-0",
"libc6",
@ -119,7 +117,6 @@
"libfreetype6",
"libgbm1",
"libgcc1",
"libgconf-2-4",
"libgdk-pixbuf2.0-0",
"libglib2.0-0",
"libgtk-3-0",
@ -147,10 +144,11 @@
"node": ">=20 <21"
},
"versionist": {
"publishedAt": "2024-04-23T10:28:00.623Z"
"publishedAt": "2025-02-27T16:16:57.534Z"
},
"optionalDependencies": {
"bufferutil": "^4.0.8",
"utf-8-validate": "^5.0.10"
"utf-8-validate": "^5.0.10",
"winusb-driver-generator": "2.1.2"
}
}

View File

@ -15,7 +15,6 @@
*/
import { expect } from 'chai';
import { File } from 'etcher-sdk/build/source-destination';
import * as path from 'path';
import * as availableDrives from '../../../lib/gui/app/models/available-drives';
@ -165,7 +164,7 @@ describe('Model: availableDrives', function () {
extension: 'img',
size: 999999999,
isSizeEstimated: false,
SourceType: File,
SourceType: 'File',
recommendedDriveSize: 2000000000,
});
});

View File

@ -15,13 +15,12 @@
*/
import { expect } from 'chai';
import { File } from 'etcher-sdk/build/source-destination';
import * as path from 'path';
import { SourceMetadata } from '../../../lib/gui/app/components/source-selector/source-selector';
import type { SourceMetadata } from '../../../lib/shared/typings/source-selector';
import * as availableDrives from '../../../lib/gui/app/models/available-drives';
import * as selectionState from '../../../lib/gui/app/models/selection-state';
import { DrivelistDrive } from '../../../lib/shared/drive-constraints';
import type { DrivelistDrive } from '../../../lib/shared/drive-constraints';
describe('Model: selectionState', function () {
describe('given a clean state', function () {
@ -375,7 +374,7 @@ describe('Model: selectionState', function () {
extension: 'img',
size: 999999999,
isSizeEstimated: false,
SourceType: File,
SourceType: 'File',
});
const imagePath = selectionState.getImage()?.path;
@ -408,7 +407,7 @@ describe('Model: selectionState', function () {
extension: 'img',
size: 999999999,
isSizeEstimated: false,
SourceType: File,
SourceType: 'File',
recommendedDriveSize: 2000000000,
};
@ -581,7 +580,7 @@ describe('Model: selectionState', function () {
path: 'foo.img',
extension: 'img',
size: 999999999,
SourceType: File,
SourceType: 'File',
isSizeEstimated: false,
};
@ -670,7 +669,7 @@ describe('Model: selectionState', function () {
path: 'foo.img',
extension: 'img',
size: 999999999,
SourceType: File,
SourceType: 'File',
isSizeEstimated: false,
};

View File

@ -1,3 +1,10 @@
/*
*
* TODO:
* This test should be replaced by an E2E test.
*
*/
/*
* Copyright 2020 balena.io
*
@ -15,11 +22,11 @@
*/
import { expect } from 'chai';
import { Drive as DrivelistDrive } from 'drivelist';
import { sourceDestination } from 'etcher-sdk';
import { assert, SinonStub, stub } from 'sinon';
import type { Drive as DrivelistDrive } from 'drivelist';
import type { SinonStub } from 'sinon';
import { assert, stub } from 'sinon';
import { SourceMetadata } from '../../../lib/gui/app/components/source-selector/source-selector';
import type { SourceMetadata } from '../../../lib/shared/typings/source-selector';
import * as flashState from '../../../lib/gui/app/models/flash-state';
import * as imageWriter from '../../../lib/gui/app/modules/image-writer';
@ -34,7 +41,7 @@ describe('Browser: imageWriter', () => {
description: 'foo.img',
displayName: 'foo.img',
path: 'foo.img',
SourceType: sourceDestination.File,
SourceType: 'File',
extension: 'img',
};

View File

@ -15,12 +15,14 @@
*/
import { expect } from 'chai';
import * as i18next from 'i18next';
import en_translation from '../../../lib/gui/app/i18n/en';
import * as progressStatus from '../../../lib/gui/app/modules/progress-status';
describe('Browser: progressStatus', function () {
describe('.titleFromFlashState()', function () {
beforeEach(function () {
beforeEach(async function () {
this.state = {
active: 1,
type: 'flashing',
@ -29,6 +31,13 @@ describe('Browser: progressStatus', function () {
eta: 15,
speed: 100000000000000,
};
await i18next.init({
lng: 'en', // Set the default language
resources: {
en: en_translation,
},
});
});
it('should report 0% if percentage == 0 but speed != 0', function () {

View File

@ -1,3 +1,10 @@
/*
*
* TODO:
* This test should be replaced by an E2E test.
*
*/
/*
* Copyright 2016 balena.io
*

View File

@ -17,7 +17,8 @@
import { expect } from 'chai';
import { promises as fs } from 'fs';
import * as os from 'os';
import { SinonStub, stub } from 'sinon';
import type { SinonStub } from 'sinon';
import { stub } from 'sinon';
import * as wnd from '../../../lib/gui/app/os/windows-network-drives';

View File

@ -15,9 +15,8 @@
*/
import { expect } from 'chai';
import { sourceDestination } from 'etcher-sdk';
import * as path from 'path';
import { SourceMetadata } from '../../lib/gui/app/components/source-selector/source-selector';
import type { SourceMetadata } from '../../lib/shared/typings/source-selector';
import * as constraints from '../../lib/shared/drive-constraints';
import * as messages from '../../lib/shared/messages';
@ -87,7 +86,7 @@ describe('Shared: DriveConstraints', function () {
path: '/Volumes/Untitled/image.img',
hasMBR: false,
partitions: [],
SourceType: sourceDestination.File,
SourceType: 'File',
},
);
@ -101,7 +100,7 @@ describe('Shared: DriveConstraints', function () {
path: 'E:\\image.img',
hasMBR: false,
partitions: [],
SourceType: sourceDestination.File,
SourceType: 'File',
};
beforeEach(function () {
this.separator = path.sep;
@ -207,7 +206,7 @@ describe('Shared: DriveConstraints', function () {
path: '/Volumes/Untitled/image.img',
hasMBR: false,
partitions: [],
SourceType: sourceDestination.File,
SourceType: 'File',
};
beforeEach(function () {
this.separator = path.sep;
@ -522,7 +521,7 @@ describe('Shared: DriveConstraints', function () {
size: 1000000000,
isSizeEstimated: false,
recommendedDriveSize: 2000000000,
SourceType: sourceDestination.File,
SourceType: 'File',
};
it('should return true if the drive size is greater than the recommended size ', function () {
const result = constraints.isDriveSizeRecommended(
@ -626,7 +625,7 @@ describe('Shared: DriveConstraints', function () {
description: 'rpi.img',
displayName: 'rpi.img',
path: '',
SourceType: sourceDestination.File,
SourceType: 'File',
size: 2000000000,
isSizeEstimated: false,
};
@ -672,7 +671,7 @@ describe('Shared: DriveConstraints', function () {
description: 'rpi.img',
displayName: 'rpi.img',
path: '',
SourceType: sourceDestination.File,
SourceType: 'File',
size: 2000000000,
isSizeEstimated: false,
};
@ -720,7 +719,7 @@ describe('Shared: DriveConstraints', function () {
description: 'rpi.img',
displayName: 'rpi.img',
path: '',
SourceType: sourceDestination.File,
SourceType: 'File',
size: 2000000000,
isSizeEstimated: false,
};
@ -829,7 +828,7 @@ describe('Shared: DriveConstraints', function () {
};
this.image = {
SourceType: sourceDestination.File,
SourceType: 'File',
path: path.join(__dirname, 'rpi.img'),
size: this.drive.size - 1,
isSizeEstimated: false,
@ -874,7 +873,7 @@ describe('Shared: DriveConstraints', function () {
};
this.image = {
SourceType: sourceDestination.File,
SourceType: 'File',
path: path.join(__dirname, 'rpi.img'),
size: this.drive.size - 1,
isSizeEstimated: false,
@ -1227,7 +1226,7 @@ describe('Shared: DriveConstraints', function () {
description: 'rpi.img',
displayName: 'rpi.img',
path: path.join(__dirname, 'rpi.img'),
SourceType: sourceDestination.File,
SourceType: 'File',
// @ts-ignore
size: drives[2].size + 1,
isSizeEstimated: false,

7
tests/test.e2e.ts Normal file
View File

@ -0,0 +1,7 @@
import { browser } from '@wdio/globals';
describe('Electron Testing', () => {
it('should print application title', async () => {
console.log('Hello', await browser.getTitle(), 'application!');
});
});

View File

@ -1,21 +1,21 @@
{
"compilerOptions": {
"strict": true,
"target": "es2019",
"typeRoots": ["./node_modules/@types", "./typings"],
"module": "commonjs",
"lib": ["dom", "esnext"],
"declaration": true,
"declarationMap": true,
"jsx": "react",
"pretty": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
}
"compilerOptions": {
"strict": true,
"target": "es2019",
"typeRoots": ["./node_modules/@types", "./typings"],
"module": "commonjs",
"lib": ["dom", "esnext"],
"declaration": true,
"declarationMap": true,
"jsx": "react",
"pretty": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
}
}

318
wdio.conf.ts Normal file
View File

@ -0,0 +1,318 @@
/// <reference types="wdio-electron-service" />
import type { Options } from '@wdio/types';
export const config: Options.Testrunner = {
//
// ====================
// Runner Configuration
// ====================
// WebdriverIO supports running e2e tests as well as unit and component tests.
runner: 'local',
autoCompileOpts: {
autoCompile: true,
tsNodeOpts: {
project: './tsconfig.json',
transpileOnly: true,
},
},
//
// ==================
// Specify Test Files
// ==================
// Define which test specs should run. The pattern is relative to the directory
// of the configuration file being run.
//
// The specs are defined as an array of spec files (optionally using wildcards
// that will be expanded). The test for each spec file will be run in a separate
// worker process. In order to have a group of spec files run in the same worker
// process simply enclose them in an array within the specs array.
//
// The path of the spec files will be resolved relative from the directory of
// of the config file unless it's absolute.
//
specs: ['./tests/**/*.spec.ts'],
// Patterns to exclude.
// FIXME: Remove the following exclusions once the tests are ported to WDIO
exclude: [
'tests/gui/modules/image-writer.spec.ts',
'tests/gui/os/window-progress.spec.ts',
'tests/gui/models/available-drives.spec.ts',
'tests/gui/models/flash-state.spec.ts',
'tests/gui/models/selection-state.spec.ts',
'tests/gui/models/settings.spec.ts',
'tests/shared/drive-constraints.spec.ts',
'tests/shared/messages.spec.ts',
'tests/gui/modules/progress-status.spec.ts',
],
//
// ============
// Capabilities
// ============
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
// time. Depending on the number of capabilities, WebdriverIO launches several test
// sessions. Within your capabilities you can overwrite the spec and exclude options in
// order to group specific specs to a specific capability.
//
// First, you can define how many instances should be started at the same time. Let's
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
// files and you set maxInstances to 10, all spec files will get tested at the same time
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
//
maxInstances: 10,
//
// If you have trouble getting all important capabilities together, check out the
// Sauce Labs platform configurator - a great tool to configure your capabilities:
// https://saucelabs.com/platform/platform-configurator
//
capabilities: [
{
browserName: 'electron',
// Electron service options
// see https://webdriver.io/docs/desktop-testing/electron/configuration/#service-options
'wdio:electronServiceOptions': {
appArgs: process.platform === 'linux' ? ['headless'] : [],
},
},
],
//
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'info',
//
// Set specific log levels per logger
// loggers:
// - webdriver, webdriverio
// - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
// - @wdio/mocha-framework, @wdio/jasmine-framework
// - @wdio/local-runner
// - @wdio/sumologic-reporter
// - @wdio/cli, @wdio/config, @wdio/utils
// Level of logging verbosity: trace | debug | info | warn | error | silent
// logLevels: {
// webdriver: 'info',
// '@wdio/appium-service': 'info'
// },
//
// If you only want to run your tests until a specific amount of tests have failed use
// bail (default is 0 - don't bail, run all tests).
bail: 0,
//
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
// gets prepended directly.
// baseUrl: 'http://localhost:8080',
//
// Default timeout for all waitFor* commands.
waitforTimeout: 10000,
//
// Default timeout in milliseconds for request
// if browser driver or grid doesn't send response
connectionRetryTimeout: 120000,
//
// Default request retries count
connectionRetryCount: 3,
//
// Test runner services
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.
services: ['electron'],
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
// see also: https://webdriver.io/docs/frameworks
//
// Make sure you have the wdio adapter package for the specific framework installed
// before running any tests.
framework: 'mocha',
//
// The number of times to retry the entire specfile when it fails as a whole
// specFileRetries: 1,
//
// Delay in seconds between the spec file retry attempts
// specFileRetriesDelay: 0,
//
// Whether or not retried spec files should be retried immediately or deferred to the end of the queue
// specFileRetriesDeferred: false,
//
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: https://webdriver.io/docs/dot-reporter
reporters: ['spec'],
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
mochaOpts: {
ui: 'bdd',
timeout: 60000,
},
//
// =====
// Hooks
// =====
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
// it and to build services around it. You can either apply a single function or an array of
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
// resolved to continue.
/**
* Gets executed once before all workers get launched.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
*/
// onPrepare: function (config, capabilities) {
// },
/**
* Gets executed before a worker process is spawned and can be used to initialize specific service
* for that worker as well as modify runtime environments in an async fashion.
* @param {string} cid capability id (e.g 0-0)
* @param {object} caps object containing capabilities for session that will be spawn in the worker
* @param {object} specs specs to be run in the worker process
* @param {object} args object that will be merged with the main configuration once worker is initialized
* @param {object} execArgv list of string arguments passed to the worker process
*/
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
// },
/**
* Gets executed just after a worker process has exited.
* @param {string} cid capability id (e.g 0-0)
* @param {number} exitCode 0 - success, 1 - fail
* @param {object} specs specs to be run in the worker process
* @param {number} retries number of retries used
*/
// onWorkerEnd: function (cid, exitCode, specs, retries) {
// },
/**
* Gets executed just before initialising the webdriver session and test framework. It allows you
* to manipulate configurations depending on the capability or spec.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
* @param {string} cid worker id (e.g. 0-0)
*/
// beforeSession: function (config, capabilities, specs, cid) {
// },
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
* @param {object} browser instance of created browser/device session
*/
// before: function (capabilities, specs) {
// },
/**
* Runs before a WebdriverIO command gets executed.
* @param {string} commandName hook command name
* @param {Array} args arguments that command would receive
*/
// beforeCommand: function (commandName, args) {
// },
/**
* Hook that gets executed before the suite starts
* @param {object} suite suite details
*/
// beforeSuite: function (suite) {
// },
/**
* Function to be executed before a test (in Mocha/Jasmine) starts.
*/
// beforeTest: function (test, context) {
// },
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
*/
// beforeHook: function (test, context, hookName) {
// },
/**
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
* afterEach in Mocha)
*/
// afterHook: function (test, context, { error, result, duration, passed, retries }, hookName) {
// },
/**
* Function to be executed after a test (in Mocha/Jasmine only)
* @param {object} test test object
* @param {object} context scope object the test was executed with
* @param {Error} result.error error object in case the test fails, otherwise `undefined`
* @param {*} result.result return object of test function
* @param {number} result.duration duration of test
* @param {boolean} result.passed true if test has passed, otherwise false
* @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }`
*/
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
// },
/**
* Hook that gets executed after the suite has ended
* @param {object} suite suite details
*/
// afterSuite: function (suite) {
// },
/**
* Runs after a WebdriverIO command gets executed
* @param {string} commandName hook command name
* @param {Array} args arguments that command would receive
* @param {number} result 0 - command success, 1 - command error
* @param {object} error error object if any
*/
// afterCommand: function (commandName, args, result, error) {
// },
/**
* Gets executed after all tests are done. You still have access to all global variables from
* the test.
* @param {number} result 0 - test pass, 1 - test fail
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// after: function (result, capabilities, specs) {
// },
/**
* Gets executed right after terminating the webdriver session.
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// afterSession: function (config, capabilities, specs) {
// },
/**
* Gets executed after all workers got shut down and the process is about to exit. An error
* thrown in the onComplete hook will result in the test run failing.
* @param {object} exitCode 0 - success, 1 - fail
* @param {object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {<Object>} results object containing test results
*/
// onComplete: function(exitCode, config, capabilities, results) {
// },
/**
* Gets executed when a refresh happens.
* @param {string} oldSessionId session ID of the old session
* @param {string} newSessionId session ID of the new session
*/
// onReload: function(oldSessionId, newSessionId) {
// }
/**
* Hook that gets executed before a WebdriverIO assertion happens.
* @param {object} params information about the assertion to be executed
*/
// beforeAssertion: function(params) {
// }
/**
* Hook that gets executed after a WebdriverIO assertion happened.
* @param {object} params information about the assertion that was executed, including its results
*/
// afterAssertion: function(params) {
// }
};

View File

@ -17,29 +17,7 @@
import type { Configuration, ModuleOptions } from 'webpack';
import { resolve } from 'path';
import {
BannerPlugin,
IgnorePlugin,
NormalModuleReplacementPlugin,
} from 'webpack';
interface ReplacementRule {
search: string;
replace: string | (() => string);
}
function slashOrAntislash(pattern: RegExp): RegExp {
return new RegExp(pattern.source.replace(/\\\//g, '(\\/|\\\\)'));
}
function replace(test: RegExp, ...replacements: ReplacementRule[]) {
return {
loader: 'string-replace-loader',
// Handle windows path separators
test: slashOrAntislash(test),
options: { multiple: replacements.map((r) => ({ ...r, strict: true })) },
};
}
import { BannerPlugin, IgnorePlugin, DefinePlugin } from 'webpack';
const rules: Required<ModuleOptions>['rules'] = [
// Add support for native node modules
@ -81,24 +59,20 @@ const rules: Required<ModuleOptions>['rules'] = [
test: /\.svg$/,
use: '@svgr/webpack',
},
// force axios to use http backend (not xhr) to support streams
replace(/node_modules\/axios\/lib\/defaults\.js$/, {
search: './adapters/xhr',
replace: './adapters/http',
}),
];
const injectAnalyticsToken = new DefinePlugin({
'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''),
'process.env.AMPLITUDE_TOKEN': JSON.stringify(
process.env.AMPLITUDE_TOKEN || '',
),
});
export const rendererConfig: Configuration = {
module: {
rules,
},
plugins: [
// Force axios to use http.js, not xhr.js as we need stream support
// (its package.json file replaces http with xhr for browser targets).
new NormalModuleReplacementPlugin(
slashOrAntislash(/node_modules\/axios\/lib\/adapters\/xhr\.js/),
'./http.js',
),
// Ignore `aws-crt` which is a dependency of (ultimately) `aws4-axios` which is used
// by etcher-sdk and does a runtime check to its availability. Were not currently
// using the “assume role” functionality (AFAIU) of aws4-axios and we dont care that
@ -112,6 +86,7 @@ export const rendererConfig: Configuration = {
banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };',
raw: true,
}),
injectAnalyticsToken,
],
resolve: {
@ -133,4 +108,5 @@ export const mainConfig: Configuration = {
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
},
plugins: [injectAnalyticsToken],
};