mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-24 11:46:31 +00:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
391164bf15 | ||
![]() |
7c2c2bc3d6 | ||
![]() |
c2d160f5c7 | ||
![]() |
385bf45883 | ||
![]() |
aa6d526fea | ||
![]() |
fdd082b9cd | ||
![]() |
624dc77969 | ||
![]() |
a1e9be2f94 | ||
![]() |
c2fc36971c | ||
![]() |
85b1e3c2c2 | ||
![]() |
e5d1b4ce23 | ||
![]() |
aac092fd4d | ||
![]() |
ff852c029e | ||
![]() |
4759bc7686 | ||
![]() |
039a022353 | ||
![]() |
4375b960c2 | ||
![]() |
ee5505d596 | ||
![]() |
c726b51dca | ||
![]() |
676eaf82e7 | ||
![]() |
87fb4df9eb | ||
![]() |
e43ee788ec | ||
![]() |
3dc17c89b4 | ||
![]() |
5774dded7b | ||
![]() |
9f408241f9 | ||
![]() |
2ed779ef37 | ||
![]() |
5fd6376f45 | ||
![]() |
818dcd3b13 | ||
![]() |
52d396aa7e | ||
![]() |
c748c2a9c0 | ||
![]() |
a5dac57b09 | ||
![]() |
8dad81ae34 | ||
![]() |
d28719daf2 | ||
![]() |
98db4df0dc | ||
![]() |
52144f4a6e | ||
![]() |
39b02f2168 | ||
![]() |
c4d3f8db87 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -62,7 +62,3 @@ CODEOWNERS text
|
|||||||
*.ttf binary diff=hex
|
*.ttf binary diff=hex
|
||||||
xz-without-extension binary diff=hex
|
xz-without-extension binary diff=hex
|
||||||
wmic-output.txt binary diff=hex
|
wmic-output.txt binary diff=hex
|
||||||
|
|
||||||
# gitsecret
|
|
||||||
*.secret binary
|
|
||||||
.gitsecret/** binary
|
|
||||||
|
21
.github/actions/publish/action.yml
vendored
21
.github/actions/publish/action.yml
vendored
@ -3,10 +3,10 @@ name: package and publish GitHub (draft) release
|
|||||||
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||||
inputs:
|
inputs:
|
||||||
json:
|
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
|
required: true
|
||||||
secrets:
|
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
|
required: true
|
||||||
|
|
||||||
# --- custom environment
|
# --- custom environment
|
||||||
@ -15,14 +15,14 @@ inputs:
|
|||||||
# Beware that native modules will be built for this version,
|
# Beware that native modules will be built for this version,
|
||||||
# which might not be compatible with the one used by pkg (see forge.sidecar.ts)
|
# which might not be compatible with the one used by pkg (see forge.sidecar.ts)
|
||||||
# https://github.com/vercel/pkg-fetch/releases
|
# https://github.com/vercel/pkg-fetch/releases
|
||||||
default: "20.x"
|
default: '20.x'
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: "true"
|
default: 'true'
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||||
using: "composite"
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Download custom source artifact
|
- name: Download custom source artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@ -53,6 +53,13 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm
|
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
|
- name: Install host dependencies
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
|
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
|
||||||
@ -131,7 +138,7 @@ runs:
|
|||||||
PLATFORM=Windows
|
PLATFORM=Windows
|
||||||
SHA256SUM_BIN=sha256sum
|
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 \
|
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" \
|
-H "x-api-key:$SM_API_KEY" \
|
||||||
-o smtools-windows-x64.msi
|
-o smtools-windows-x64.msi
|
||||||
@ -139,8 +146,8 @@ runs:
|
|||||||
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
|
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
|
||||||
smksp_registrar.exe list
|
smksp_registrar.exe list
|
||||||
smctl.exe keypair ls
|
smctl.exe keypair ls
|
||||||
|
smctl.exe windows certsync
|
||||||
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||||
smksp_cert_sync.exe
|
|
||||||
|
|
||||||
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
|
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
|
||||||
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
|
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
|
||||||
|
8
.github/actions/test/action.yml
vendored
8
.github/actions/test/action.yml
vendored
@ -12,7 +12,7 @@ inputs:
|
|||||||
# --- custom environment
|
# --- custom environment
|
||||||
NODE_VERSION:
|
NODE_VERSION:
|
||||||
type: string
|
type: string
|
||||||
default: '20.10'
|
default: '20.19'
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: 'true'
|
default: 'true'
|
||||||
@ -55,6 +55,12 @@ runs:
|
|||||||
# fi
|
# fi
|
||||||
|
|
||||||
npm ci
|
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 lint
|
||||||
npm run package
|
npm run package
|
||||||
npm run wdio # test stage, note that it requires the package to be done first
|
npm run wdio # test stage, note that it requires the package to be done first
|
||||||
|
19
.github/workflows/flowzone.yml
vendored
19
.github/workflows/flowzone.yml
vendored
@ -18,7 +18,24 @@ jobs:
|
|||||||
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
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
|
restrict_custom_actions: false
|
||||||
github_prerelease: true
|
github_prerelease: true
|
||||||
cloudflare_website: "etcher"
|
cloudflare_website: "etcher"
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
secrets/APPLE_SIGNING_PASSWORD.txt:5c9cfeb1ea5142b547bc842cc6e0b4a932641ae9811ee47abe2c3953f2a4de5d
|
|
||||||
secrets/WINDOWS_SIGNING_PASSWORD.txt:852e431628494f2559793c39cf09c34e9406dd79bb15b90c9f88194020470568
|
|
||||||
secrets/XCODE_APP_LOADER_PASSWORD.txt:005eb9a3c7035c77232973c9355468fc396b94e62783fb8e6dce16bce95b94a1
|
|
||||||
secrets/WINDOWS_SIGNING.pfx:929f401db38733ffc41572539de7c0d938023af51ed06c205a72a71c1f815714
|
|
||||||
secrets/APPLE_SIGNING.p12:61abf7b4ff2eec76ce889d71bcdd568b99a6a719b4947ac20f03966265b0946a
|
|
@ -1,3 +1,146 @@
|
|||||||
|
- commits:
|
||||||
|
- subject: Remove stale secrets
|
||||||
|
hash: c2fc36971c9460eac6bd02cfc7bdcabec7b97a6d
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
change-type: patch
|
||||||
|
author: Anton Belodedenko
|
||||||
|
nested: []
|
||||||
|
version: 2.1.3
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-15T18:09:55.848Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: remove analytics"
|
||||||
|
hash: aa6d526fea010d181f49dd81ae3bdaefb8d1938e
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 2.1.2
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-08T08:51:44.810Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: fix signin windows artifacts"
|
||||||
|
hash: a1e9be2f94629447e02994e52e12c67ec98de831
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 2.1.1
|
||||||
|
title: ""
|
||||||
|
date: 2025-05-05T17:19:50.443Z
|
||||||
|
- 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:
|
- commits:
|
||||||
- subject: "patch: add sentry debug flag"
|
- subject: "patch: add sentry debug flag"
|
||||||
hash: 8223130e8dfce180481550d77f022064255601e4
|
hash: 8223130e8dfce180481550d77f022064255601e4
|
||||||
@ -1164,13 +1307,10 @@
|
|||||||
nested: []
|
nested: []
|
||||||
- subject: "Patch: run linux build on ubuntu-20.04"
|
- subject: "Patch: run linux build on ubuntu-20.04"
|
||||||
hash: adcd8e0325bc891460b3e51aa5403f8675189f13
|
hash: adcd8e0325bc891460b3e51aa5403f8675189f13
|
||||||
body: >-
|
body: |-
|
||||||
as [`18.04` has been
|
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/)
|
||||||
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: {}
|
footer: {}
|
||||||
author: Edwin Joassart
|
author: Edwin Joassart
|
||||||
nested: []
|
nested: []
|
||||||
@ -11915,40 +12055,19 @@
|
|||||||
changelog-entry: Don't include user paths in Mixpanel usage reports
|
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
|
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
|
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
|
The following error is thrown if the open file dialog is cancelled
|
||||||
|
|
||||||
without any selection:
|
without any selection:
|
||||||
|
|
||||||
Unhandled rejection TypeError: Cannot read property '0' of undefined
|
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.indexedGetter
|
at Number.tryCatcher (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
|
||||||
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
|
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 Number.tryCatcher
|
at Promise._settlePromise0 (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
|
||||||
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
|
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 Promise._settlePromiseFromHandler
|
at Async._drainQueues (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
|
||||||
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
|
at Immediate.Async.drainQueues [as _onImmediate] (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
|
||||||
|
|
||||||
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)
|
at processImmediate [as _immediateCallback] (timers.js:383:17)
|
||||||
- hash: 6bd086f1c5c6654a47125cf2d46788655cae2553
|
- hash: 6bd086f1c5c6654a47125cf2d46788655cae2553
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
@ -12545,21 +12664,14 @@
|
|||||||
changelog-entry: Use info icon instead of "SHOW FULL FILE NAME" in first step.
|
changelog-entry: Use info icon instead of "SHOW FULL FILE NAME" in first step.
|
||||||
fixes: https://github.com/resin-io/etcher/issues/458
|
fixes: https://github.com/resin-io/etcher/issues/458
|
||||||
subject: Make use of AppImage desktop integration script
|
subject: Make use of AppImage desktop integration script
|
||||||
body: >-
|
body: |-
|
||||||
This is useful to prompt the user to install the `.desktop` file.
|
This is useful to prompt the user to install the `.desktop` file.
|
||||||
|
|
||||||
The `Description` key in `Etcher.desktop` was changed to `Comment` since
|
The `Description` key in `Etcher.desktop` was changed to `Comment` since
|
||||||
|
|
||||||
`desktop-file-validate` complained with:
|
`desktop-file-validate` complained with:
|
||||||
|
|
||||||
Etcher.desktop: error: file contains key "Description" in group "Desktop
|
Etcher.desktop: error: file contains key "Description" in group "Desktop
|
||||||
|
|
||||||
Entry", but keys extending the format should start with "X-"
|
Entry", but keys extending the format should start with "X-"
|
||||||
|
|
||||||
After checking the desktop file format specification, the correct key
|
After checking the desktop file format specification, the correct key
|
||||||
|
|
||||||
should be "Comment"
|
should be "Comment"
|
||||||
|
|
||||||
(https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html).
|
(https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s05.html).
|
||||||
- hash: c3e360e61933ef0044c005b5e92c879ff9a47c49
|
- hash: c3e360e61933ef0044c005b5e92c879ff9a47c49
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
@ -12772,17 +12884,12 @@
|
|||||||
changelog-entry: Fix flashing never starting after elevation in GNU/Linux.
|
changelog-entry: Fix flashing never starting after elevation in GNU/Linux.
|
||||||
fixes: https://github.com/resin-io/etcher/issues/665
|
fixes: https://github.com/resin-io/etcher/issues/665
|
||||||
subject: Make all angular modules export the name of the module
|
subject: Make all angular modules export the name of the module
|
||||||
body: >-
|
body: |-
|
||||||
This makes them very nicely require-able, for example:
|
This makes them very nicely require-able, for example:
|
||||||
|
|
||||||
angular.module('MyModule', [
|
angular.module('MyModule', [
|
||||||
|
|
||||||
require('my-dependency');
|
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
|
- hash: b8f63af3f81bca3abd055303bc91ab35eb126655
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
footers:
|
footers:
|
||||||
|
57
CHANGELOG.md
57
CHANGELOG.md
@ -3,6 +3,63 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# v2.1.3
|
||||||
|
## (2025-05-15)
|
||||||
|
|
||||||
|
* Remove stale secrets [Anton Belodedenko]
|
||||||
|
|
||||||
|
# v2.1.2
|
||||||
|
## (2025-05-08)
|
||||||
|
|
||||||
|
* patch: remove analytics [Edwin Joassart]
|
||||||
|
|
||||||
|
# v2.1.1
|
||||||
|
## (2025-05-05)
|
||||||
|
|
||||||
|
* patch: fix signin windows artifacts [Edwin Joassart]
|
||||||
|
|
||||||
|
# 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
|
# v1.19.19
|
||||||
## (2024-05-28)
|
## (2024-05-28)
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
Maintaining Etcher
|
# Maintaining Etcher
|
||||||
==================
|
|
||||||
|
|
||||||
This document is meant to serve as a guide for maintainers to perform common tasks.
|
This document is meant to serve as a guide for maintainers to perform common tasks.
|
||||||
|
|
||||||
Releasing
|
## Releasing
|
||||||
---------
|
|
||||||
|
|
||||||
### Release Types
|
### Release Types
|
||||||
|
|
||||||
@ -13,16 +11,15 @@ Releasing
|
|||||||
- **release**: Full releases
|
- **release**: Full releases
|
||||||
|
|
||||||
Draft release is created from each PR, tagged with the branch name.
|
Draft release is created from each PR, tagged with the branch name.
|
||||||
All merged PR will generate a new tag/version as a *pre-release*.
|
All merged PR will generate a new tag/version as a _pre-release_.
|
||||||
Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary.
|
Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary.
|
||||||
|
|
||||||
|
|
||||||
#### Preparation
|
#### Preparation
|
||||||
|
|
||||||
- [Prepare the new version](#preparing-a-new-version)
|
- [Prepare the new version](#preparing-a-new-version)
|
||||||
- [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
|
- [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
|
||||||
- [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
|
- [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
|
||||||
- Upload build artifacts to GitHub release draft
|
- Upload build artifacts to GitHub release draft
|
||||||
|
|
||||||
#### Testing
|
#### Testing
|
||||||
|
|
||||||
@ -35,7 +32,7 @@ Mark the pre-release as final when it is necessary, then distribute the packages
|
|||||||
- [Post release note to forums](https://forums.balena.io/c/etcher)
|
- [Post release note to forums](https://forums.balena.io/c/etcher)
|
||||||
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
||||||
- [Update the website](https://github.com/balena-io/etcher-homepage)
|
- [Update the website](https://github.com/balena-io/etcher-homepage)
|
||||||
- Wait 2-3 hours for analytics (Sentry, Amplitude) to trickle in and check for elevated error rates, or regressions
|
- Wait 2-3 hours for analytics (Sentry) to trickle in and check for elevated error rates, or regressions
|
||||||
- If regressions arise; pull the release, and release a patched version, else:
|
- If regressions arise; pull the release, and release a patched version, else:
|
||||||
- [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
|
- [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
|
||||||
- Post changelog with `#release-notes` tag on internal chat
|
- Post changelog with `#release-notes` tag on internal chat
|
||||||
@ -51,7 +48,6 @@ Make sure to set the analytics tokens when generating production release binarie
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
|
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
|
||||||
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
@ -71,7 +67,6 @@ npm run make
|
|||||||
|
|
||||||
Our CI will appropriately sign artifacts for macOS and some Windows targets.
|
Our CI will appropriately sign artifacts for macOS and some Windows targets.
|
||||||
|
|
||||||
|
|
||||||
### Uploading packages to Cloudfront
|
### Uploading packages to Cloudfront
|
||||||
|
|
||||||
Log in to cloudfront and upload the `rpm` and `deb` files.
|
Log in to cloudfront and upload the `rpm` and `deb` files.
|
||||||
@ -99,7 +94,6 @@ aws s3api delete-object --bucket <bucket name> --key <file name>
|
|||||||
|
|
||||||
The Bintray dashboard provides an easy way to delete a version's files.
|
The Bintray dashboard provides an easy way to delete a version's files.
|
||||||
|
|
||||||
|
|
||||||
### Submitting binaries to Symantec
|
### Submitting binaries to Symantec
|
||||||
|
|
||||||
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
|
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
Manual Testing
|
# Manual Testing
|
||||||
==============
|
|
||||||
|
|
||||||
This document describes a high-level script of manual tests to check for. We
|
This document describes a high-level script of manual tests to check for. We
|
||||||
should aim to replace items on this list with automated Spectron test cases.
|
should aim to replace items on this list with automated Spectron test cases.
|
||||||
|
|
||||||
Image Selection
|
## Image Selection
|
||||||
---------------
|
|
||||||
|
|
||||||
- [ ] Cancel image selection dialog
|
- [ ] Cancel image selection dialog
|
||||||
- [ ] Select an unbootable image (without a partition table), and expect a
|
- [ ] Select an unbootable image (without a partition table), and expect a
|
||||||
sensible warning
|
sensible warning
|
||||||
- [ ] Attempt to select a ZIP archive with more than one image
|
- [ ] Attempt to select a ZIP archive with more than one image
|
||||||
- [ ] Attempt to select a tar archive (with any compression method)
|
- [ ] Attempt to select a tar archive (with any compression method)
|
||||||
- [ ] Change image selection
|
- [ ] Change image selection
|
||||||
- [ ] Select a Windows image, and expect a sensible warning
|
- [ ] Select a Windows image, and expect a sensible warning
|
||||||
|
|
||||||
Drive Selection
|
## Drive Selection
|
||||||
---------------
|
|
||||||
|
|
||||||
- [ ] Open the drive selection modal
|
- [ ] Open the drive selection modal
|
||||||
- [ ] Switch drive selection
|
- [ ] Switch drive selection
|
||||||
@ -25,16 +22,15 @@ Drive Selection
|
|||||||
- [ ] Insert a locked SD Card and expect a warning
|
- [ ] Insert a locked SD Card and expect a warning
|
||||||
- [ ] Insert a too small drive and expect a warning
|
- [ ] Insert a too small drive and expect a warning
|
||||||
- [ ] Put an image into a drive and attempt to flash the image to the drive
|
- [ ] Put an image into a drive and attempt to flash the image to the drive
|
||||||
that contains it
|
that contains it
|
||||||
- [ ] Attempt to flash a compressed image (for which we can get the
|
- [ ] Attempt to flash a compressed image (for which we can get the
|
||||||
uncompressed size) into a drive that is big enough to hold the compressed
|
uncompressed size) into a drive that is big enough to hold the compressed
|
||||||
image, but not big enough to hold the uncompressed version
|
image, but not big enough to hold the uncompressed version
|
||||||
- [ ] Enable "Unsafe Mode" and attempt to select a system drive
|
- [ ] Enable "Unsafe Mode" and attempt to select a system drive
|
||||||
- [ ] Enable "Unsafe Mode", and if there is only one system drive (and no
|
- [ ] Enable "Unsafe Mode", and if there is only one system drive (and no
|
||||||
removable ones), don't expect autoselection
|
removable ones), don't expect autoselection
|
||||||
|
|
||||||
Image Support
|
## Image Support
|
||||||
-------------
|
|
||||||
|
|
||||||
Run the following tests with and without validation enabled:
|
Run the following tests with and without validation enabled:
|
||||||
|
|
||||||
@ -51,18 +47,17 @@ Run the following tests with and without validation enabled:
|
|||||||
- [ ] Flash an archive image containing a blockmap file
|
- [ ] Flash an archive image containing a blockmap file
|
||||||
- [ ] Flash an archive image containing a manifest metadata file
|
- [ ] Flash an archive image containing a manifest metadata file
|
||||||
|
|
||||||
Flashing Process
|
## Flashing Process
|
||||||
----------------
|
|
||||||
|
|
||||||
- [ ] Unplug the drive during flash or validation
|
- [ ] Unplug the drive during flash or validation
|
||||||
- [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
|
- [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
|
||||||
- [ ] Start flashing an image, try to close Etcher, cancel the application
|
- [ ] Start flashing an image, try to close Etcher, cancel the application
|
||||||
close warning dialog, and check that Etcher continues to flash the image
|
close warning dialog, and check that Etcher continues to flash the image
|
||||||
|
|
||||||
### Child Writer
|
### Child Writer
|
||||||
|
|
||||||
- [ ] Kill the child writer process (i.e. with `SIGINT` or `SIGKILL`), and
|
- [ ] Kill the child writer process (i.e. with `SIGINT` or `SIGKILL`), and
|
||||||
check that the UI reacts appropriately
|
check that the UI reacts appropriately
|
||||||
- [ ] Close the application while flashing using the window manager close icon
|
- [ ] Close the application while flashing using the window manager close icon
|
||||||
- [ ] Close the application while flashing using the OS keyboard shortcut
|
- [ ] Close the application while flashing using the OS keyboard shortcut
|
||||||
- [ ] Close the application from the terminal using Ctrl-C while flashing
|
- [ ] Close the application from the terminal using Ctrl-C while flashing
|
||||||
@ -72,11 +67,10 @@ In all these cases, the child writer process should not remain alive. Note that
|
|||||||
in some systems you need to open your process monitor tool of choice with extra
|
in some systems you need to open your process monitor tool of choice with extra
|
||||||
permissions to see the elevated child writer process.
|
permissions to see the elevated child writer process.
|
||||||
|
|
||||||
GUI
|
## GUI
|
||||||
----
|
|
||||||
|
|
||||||
- [ ] Close application from the terminal using Ctrl-C while the application is
|
- [ ] Close application from the terminal using Ctrl-C while the application is
|
||||||
idle
|
idle
|
||||||
- [ ] Click footer links that take you to an external website
|
- [ ] Click footer links that take you to an external website
|
||||||
- [ ] Attempt to change image or drive selection while flashing
|
- [ ] Attempt to change image or drive selection while flashing
|
||||||
- [ ] Go to the settings page while flashing and come back
|
- [ ] Go to the settings page while flashing and come back
|
||||||
@ -85,31 +79,20 @@ GUI
|
|||||||
- [ ] Minimize the application
|
- [ ] Minimize the application
|
||||||
- [ ] Start the application given no internet connection
|
- [ ] Start the application given no internet connection
|
||||||
|
|
||||||
Success Banner
|
## Success Banner
|
||||||
--------------
|
|
||||||
|
|
||||||
- [ ] Click an external link on the success banner (with and without internet
|
- [ ] Click an external link on the success banner (with and without internet
|
||||||
connection)
|
connection)
|
||||||
|
|
||||||
Elevation Prompt
|
## Elevation Prompt
|
||||||
----------------
|
|
||||||
|
|
||||||
- [ ] Flash an image as `root`/administrator
|
- [ ] Flash an image as `root`/administrator
|
||||||
- [ ] Reject elevation prompt
|
- [ ] Reject elevation prompt
|
||||||
- [ ] Put incorrect elevation prompt password
|
- [ ] Put incorrect elevation prompt password
|
||||||
- [ ] Unplug the drive during elevation
|
- [ ] Unplug the drive during elevation
|
||||||
|
|
||||||
Unmounting
|
## Unmounting
|
||||||
----------
|
|
||||||
|
|
||||||
- [ ] Disable unmounting and flash an image
|
- [ ] Disable unmounting and flash an image
|
||||||
- [ ] Flash an image with a file system that is readable by the host OS, and
|
- [ ] Flash an image with a file system that is readable by the host OS, and
|
||||||
check that is unmounted correctly
|
check that is unmounted correctly
|
||||||
|
|
||||||
Analytics
|
|
||||||
---------
|
|
||||||
|
|
||||||
- [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
|
|
||||||
check that no request is sent
|
|
||||||
- [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
|
|
||||||
F5), and check that initial events are not sent to Amplitude**
|
|
||||||
|
@ -122,7 +122,6 @@ run Etcher on a GNU/Linux system.
|
|||||||
- xrender
|
- xrender
|
||||||
- xtst
|
- xtst
|
||||||
- xscrnsaver
|
- xscrnsaver
|
||||||
- gconf-2.0
|
|
||||||
- gmodule-2.0
|
- gmodule-2.0
|
||||||
- nss
|
- nss
|
||||||
|
|
||||||
|
@ -64,9 +64,6 @@ store.dispatch({
|
|||||||
data: uuidV4(),
|
data: uuidV4(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const applicationSessionUuid = store.getState().toJS().applicationSessionUuid;
|
|
||||||
const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid;
|
|
||||||
|
|
||||||
console.log(outdent`
|
console.log(outdent`
|
||||||
${outdent}
|
${outdent}
|
||||||
_____ _ _
|
_____ _ _
|
||||||
@ -82,13 +79,6 @@ console.log(outdent`
|
|||||||
Version = ${packageJSON.version}, Type = ${packageJSON.packageType}
|
Version = ${packageJSON.version}, Type = ${packageJSON.packageType}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const currentVersion = packageJSON.version;
|
|
||||||
|
|
||||||
analytics.logEvent('Application start', {
|
|
||||||
packageType: packageJSON.packageType,
|
|
||||||
version: currentVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
const debouncedLog = debounce(console.log, 1000, { maxWait: 1000 });
|
const debouncedLog = debounce(console.log, 1000, { maxWait: 1000 });
|
||||||
|
|
||||||
function pluralize(word: string, quantity: number) {
|
function pluralize(word: string, quantity: number) {
|
||||||
@ -172,9 +162,6 @@ analytics.initAnalytics();
|
|||||||
|
|
||||||
window.addEventListener('beforeunload', async (event) => {
|
window.addEventListener('beforeunload', async (event) => {
|
||||||
if (!flashState.isFlashing() || popupExists) {
|
if (!flashState.isFlashing() || popupExists) {
|
||||||
analytics.logEvent('Close application', {
|
|
||||||
isFlashing: flashState.isFlashing(),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +171,6 @@ window.addEventListener('beforeunload', async (event) => {
|
|||||||
// Don't open any more popups
|
// Don't open any more popups
|
||||||
popupExists = true;
|
popupExists = true;
|
||||||
|
|
||||||
analytics.logEvent('Close attempt while flashing');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const confirmed = await osDialog.showWarning({
|
const confirmed = await osDialog.showWarning({
|
||||||
confirmationLabel: i18next.t('yesExit'),
|
confirmationLabel: i18next.t('yesExit'),
|
||||||
@ -194,19 +179,11 @@ window.addEventListener('beforeunload', async (event) => {
|
|||||||
description: messages.warning.exitWhileFlashing(),
|
description: messages.warning.exitWhileFlashing(),
|
||||||
});
|
});
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
analytics.logEvent('Close confirmed while flashing', {
|
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// This circumvents the 'beforeunload' event unlike
|
// This circumvents the 'beforeunload' event unlike
|
||||||
// remote.app.quit() which does not.
|
// remote.app.quit() which does not.
|
||||||
remote.process.exit(EXIT_CODES.SUCCESS);
|
remote.process.exit(EXIT_CODES.SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
analytics.logEvent('Close rejected while flashing', {
|
|
||||||
applicationSessionUuid,
|
|
||||||
flashingWorkflowUuid,
|
|
||||||
});
|
|
||||||
popupExists = false;
|
popupExists = false;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
exceptionReporter.report(error);
|
exceptionReporter.report(error);
|
||||||
|
@ -36,7 +36,7 @@ import prettyBytes from 'pretty-bytes';
|
|||||||
import { getDrives, hasAvailableDrives } from '../../models/available-drives';
|
import { getDrives, hasAvailableDrives } from '../../models/available-drives';
|
||||||
import { getImage, isDriveSelected } from '../../models/selection-state';
|
import { getImage, isDriveSelected } from '../../models/selection-state';
|
||||||
import { store } from '../../models/store';
|
import { store } from '../../models/store';
|
||||||
import { logEvent, logException } from '../../modules/analytics';
|
import { logException } from '../../modules/analytics';
|
||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
import type { GenericTableProps } from '../../styled-components';
|
import type { GenericTableProps } from '../../styled-components';
|
||||||
import { Alert, Modal, Table } from '../../styled-components';
|
import { Alert, Modal, Table } from '../../styled-components';
|
||||||
@ -355,9 +355,6 @@ export class DriveSelector extends React.Component<
|
|||||||
|
|
||||||
private installMissingDrivers(drive: DriverlessDrive) {
|
private installMissingDrivers(drive: DriverlessDrive) {
|
||||||
if (drive.link) {
|
if (drive.link) {
|
||||||
logEvent('Open driver link modal', {
|
|
||||||
url: drive.link,
|
|
||||||
});
|
|
||||||
this.setState({ missingDriversModal: { drive } });
|
this.setState({ missingDriversModal: { drive } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import * as flashState from '../../models/flash-state';
|
|||||||
import * as selectionState from '../../models/selection-state';
|
import * as selectionState from '../../models/selection-state';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
import { Actions, store } from '../../models/store';
|
import { Actions, store } from '../../models/store';
|
||||||
import * as analytics from '../../modules/analytics';
|
|
||||||
import { FlashAnother } from '../flash-another/flash-another';
|
import { FlashAnother } from '../flash-another/flash-another';
|
||||||
import type { FlashError } from '../flash-results/flash-results';
|
import type { FlashError } from '../flash-results/flash-results';
|
||||||
import { FlashResults } from '../flash-results/flash-results';
|
import { FlashResults } from '../flash-results/flash-results';
|
||||||
@ -30,7 +29,6 @@ import { SafeWebview } from '../safe-webview/safe-webview';
|
|||||||
|
|
||||||
function restart(goToMain: () => void) {
|
function restart(goToMain: () => void) {
|
||||||
selectionState.deselectAllDrives();
|
selectionState.deselectAllDrives();
|
||||||
analytics.logEvent('Restart');
|
|
||||||
|
|
||||||
// Reset the flashing workflow uuid
|
// Reset the flashing workflow uuid
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
|
@ -21,7 +21,6 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import * as packageJSON from '../../../../../package.json';
|
import * as packageJSON from '../../../../../package.json';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
import * as analytics from '../../modules/analytics';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Electron session identifier
|
* @summary Electron session identifier
|
||||||
@ -196,10 +195,6 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
// only care about this event if it's a request for the main frame
|
// only care about this event if it's a request for the main frame
|
||||||
if (event.resourceType === 'mainFrame') {
|
if (event.resourceType === 'mainFrame') {
|
||||||
const HTTP_OK = 200;
|
const HTTP_OK = 200;
|
||||||
const { webContents, ...webviewEvent } = event;
|
|
||||||
analytics.logEvent('SafeWebview loaded', {
|
|
||||||
...webviewEvent,
|
|
||||||
});
|
|
||||||
this.setState({
|
this.setState({
|
||||||
shouldShow: event.statusCode === HTTP_OK,
|
shouldShow: event.statusCode === HTTP_OK,
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,6 @@ import { Box, Checkbox, Flex, Txt } from 'rendition';
|
|||||||
|
|
||||||
import { version, packageType } from '../../../../../package.json';
|
import { version, packageType } from '../../../../../package.json';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
import * as analytics from '../../modules/analytics';
|
|
||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
import { Modal } from '../../styled-components';
|
import { Modal } from '../../styled-components';
|
||||||
import * as i18next from 'i18next';
|
import * as i18next from 'i18next';
|
||||||
@ -89,7 +88,6 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
|||||||
|
|
||||||
const toggleSetting = async (setting: string) => {
|
const toggleSetting = async (setting: string) => {
|
||||||
const value = currentSettings[setting];
|
const value = currentSettings[setting];
|
||||||
analytics.logEvent('Toggle setting', { setting, value });
|
|
||||||
await settings.set(setting, !value);
|
await settings.set(setting, !value);
|
||||||
setCurrentSettings({
|
setCurrentSettings({
|
||||||
...currentSettings,
|
...currentSettings,
|
||||||
|
@ -308,6 +308,7 @@ const FlowSelector = styled(
|
|||||||
|
|
||||||
interface SourceSelectorProps {
|
interface SourceSelectorProps {
|
||||||
flashing: boolean;
|
flashing: boolean;
|
||||||
|
hideAnalyticsAlert: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SourceSelectorState {
|
interface SourceSelectorState {
|
||||||
@ -359,6 +360,20 @@ export class SourceSelector extends React.Component<
|
|||||||
ipcRenderer.removeListener('select-image', this.onSelectImage);
|
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) {
|
private async onSelectImage(_event: IpcRendererEvent, imagePath: string) {
|
||||||
this.setState({ imageLoading: true });
|
this.setState({ imageLoading: true });
|
||||||
await this.selectSource(
|
await this.selectSource(
|
||||||
@ -377,11 +392,8 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private reselectSource() {
|
private reselectSource() {
|
||||||
analytics.logEvent('Reselect image', {
|
|
||||||
previousImage: selectionState.getImage(),
|
|
||||||
});
|
|
||||||
|
|
||||||
selectionState.deselectImage();
|
selectionState.deselectImage();
|
||||||
|
this.props.hideAnalyticsAlert();
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectSource(
|
private selectSource(
|
||||||
@ -410,7 +422,6 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (supportedFormats.looksLikeWindowsImage(selected)) {
|
if (supportedFormats.looksLikeWindowsImage(selected)) {
|
||||||
analytics.logEvent('Possibly Windows image', { image: selected });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.looksLikeWindowsImage(),
|
message: messages.warning.looksLikeWindowsImage(),
|
||||||
@ -434,7 +445,6 @@ export class SourceSelector extends React.Component<
|
|||||||
metadata = await requestMetadata({ selected, SourceType, auth });
|
metadata = await requestMetadata({ selected, SourceType, auth });
|
||||||
|
|
||||||
if (!metadata?.hasMBR && this.state.warning === null) {
|
if (!metadata?.hasMBR && this.state.warning === null) {
|
||||||
analytics.logEvent('Missing partition table', { metadata });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.missingPartitionTable(),
|
message: messages.warning.missingPartitionTable(),
|
||||||
@ -452,7 +462,6 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (selected.partitionTableType === null) {
|
if (selected.partitionTableType === null) {
|
||||||
analytics.logEvent('Missing partition table', { selected });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.driveMissingPartitionTable(),
|
message: messages.warning.driveMissingPartitionTable(),
|
||||||
@ -474,15 +483,6 @@ export class SourceSelector extends React.Component<
|
|||||||
metadata.auth = auth;
|
metadata.auth = auth;
|
||||||
metadata.SourceType = SourceType;
|
metadata.SourceType = SourceType;
|
||||||
selectionState.selectSource(metadata);
|
selectionState.selectSource(metadata);
|
||||||
analytics.logEvent('Select image', {
|
|
||||||
// An easy way so we can quickly identify if we're making use of
|
|
||||||
// certain features without printing pages of text to DevTools.
|
|
||||||
image: {
|
|
||||||
...metadata,
|
|
||||||
logo: Boolean(metadata.logo),
|
|
||||||
blockMap: Boolean(metadata.blockMap),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
};
|
};
|
||||||
@ -503,11 +503,9 @@ export class SourceSelector extends React.Component<
|
|||||||
analytics.logException(error);
|
analytics.logException(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
analytics.logEvent(title, { path: sourcePath });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openImageSelector() {
|
private async openImageSelector() {
|
||||||
analytics.logEvent('Open image selector');
|
|
||||||
this.setState({ imageSelectorOpen: true });
|
this.setState({ imageSelectorOpen: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -515,7 +513,6 @@ export class SourceSelector extends React.Component<
|
|||||||
// Avoid analytics and selection state changes
|
// Avoid analytics and selection state changes
|
||||||
// if no file was resolved from the dialog.
|
// if no file was resolved from the dialog.
|
||||||
if (!imagePath) {
|
if (!imagePath) {
|
||||||
analytics.logEvent('Image selector closed');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.selectSource(imagePath, 'File').promise;
|
await this.selectSource(imagePath, 'File').promise;
|
||||||
@ -534,16 +531,12 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private openURLSelector() {
|
private openURLSelector() {
|
||||||
analytics.logEvent('Open image URL selector');
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showURLSelector: true,
|
showURLSelector: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private openDriveSelector() {
|
private openDriveSelector() {
|
||||||
analytics.logEvent('Open drive selector');
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showDriveSelector: true,
|
showDriveSelector: true,
|
||||||
});
|
});
|
||||||
@ -560,10 +553,6 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showSelectedImageDetails() {
|
private showSelectedImageDetails() {
|
||||||
analytics.logEvent('Show selected image tooltip', {
|
|
||||||
imagePath: selectionState.getImage()?.path,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showImageDetails: true,
|
showImageDetails: true,
|
||||||
});
|
});
|
||||||
@ -743,9 +732,7 @@ export class SourceSelector extends React.Component<
|
|||||||
done={async (imageURL: string, auth?: Authentication) => {
|
done={async (imageURL: string, auth?: Authentication) => {
|
||||||
// Avoid analytics and selection state changes
|
// Avoid analytics and selection state changes
|
||||||
// if no file was resolved from the dialog.
|
// if no file was resolved from the dialog.
|
||||||
if (!imageURL) {
|
if (imageURL) {
|
||||||
analytics.logEvent('URL selector closed');
|
|
||||||
} else {
|
|
||||||
let promise;
|
let promise;
|
||||||
({ promise, cancel: cancelURLSelection } = this.selectSource(
|
({ promise, cancel: cancelURLSelection } = this.selectSource(
|
||||||
imageURL,
|
imageURL,
|
||||||
|
@ -20,7 +20,6 @@ import { Flex, Txt } from 'rendition';
|
|||||||
import type { DriveSelectorProps } from '../drive-selector/drive-selector';
|
import type { DriveSelectorProps } from '../drive-selector/drive-selector';
|
||||||
import { DriveSelector } from '../drive-selector/drive-selector';
|
import { DriveSelector } from '../drive-selector/drive-selector';
|
||||||
import {
|
import {
|
||||||
isDriveSelected,
|
|
||||||
getImage,
|
getImage,
|
||||||
getSelectedDrives,
|
getSelectedDrives,
|
||||||
deselectDrive,
|
deselectDrive,
|
||||||
@ -28,7 +27,6 @@ import {
|
|||||||
deselectAllDrives,
|
deselectAllDrives,
|
||||||
} from '../../models/selection-state';
|
} from '../../models/selection-state';
|
||||||
import { observe } from '../../models/store';
|
import { observe } from '../../models/store';
|
||||||
import * as analytics from '../../modules/analytics';
|
|
||||||
import { TargetSelectorButton } from './target-selector-button';
|
import { TargetSelectorButton } from './target-selector-button';
|
||||||
|
|
||||||
import TgtSvg from '../../../assets/tgt.svg';
|
import TgtSvg from '../../../assets/tgt.svg';
|
||||||
@ -77,21 +75,10 @@ export const selectAllTargets = (modalTargets: DrivelistDrive[]) => {
|
|||||||
);
|
);
|
||||||
// deselect drives
|
// deselect drives
|
||||||
deselected.forEach((drive) => {
|
deselected.forEach((drive) => {
|
||||||
analytics.logEvent('Toggle drive', {
|
|
||||||
drive,
|
|
||||||
previouslySelected: true,
|
|
||||||
});
|
|
||||||
deselectDrive(drive.device);
|
deselectDrive(drive.device);
|
||||||
});
|
});
|
||||||
// select drives
|
// select drives
|
||||||
modalTargets.forEach((drive) => {
|
modalTargets.forEach((drive) => {
|
||||||
// Don't send events for drives that were already selected
|
|
||||||
if (!isDriveSelected(drive.device)) {
|
|
||||||
analytics.logEvent('Toggle drive', {
|
|
||||||
drive,
|
|
||||||
previouslySelected: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
selectDrive(drive.device);
|
selectDrive(drive.device);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -100,12 +87,14 @@ interface TargetSelectorProps {
|
|||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
hasDrive: boolean;
|
hasDrive: boolean;
|
||||||
flashing: boolean;
|
flashing: boolean;
|
||||||
|
hideAnalyticsAlert: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TargetSelector = ({
|
export const TargetSelector = ({
|
||||||
disabled,
|
disabled,
|
||||||
hasDrive,
|
hasDrive,
|
||||||
flashing,
|
flashing,
|
||||||
|
hideAnalyticsAlert,
|
||||||
}: TargetSelectorProps) => {
|
}: TargetSelectorProps) => {
|
||||||
// TODO: inject these from redux-connector
|
// TODO: inject these from redux-connector
|
||||||
const [{ driveListLabel, targets }, setStateSlice] = React.useState(
|
const [{ driveListLabel, targets }, setStateSlice] = React.useState(
|
||||||
@ -137,9 +126,9 @@ export const TargetSelector = ({
|
|||||||
tooltip={driveListLabel}
|
tooltip={driveListLabel}
|
||||||
openDriveSelector={() => {
|
openDriveSelector={() => {
|
||||||
setShowTargetSelectorModal(true);
|
setShowTargetSelectorModal(true);
|
||||||
|
hideAnalyticsAlert();
|
||||||
}}
|
}}
|
||||||
reselectDrive={() => {
|
reselectDrive={() => {
|
||||||
analytics.logEvent('Reselect drive');
|
|
||||||
setShowTargetSelectorModal(true);
|
setShowTargetSelectorModal(true);
|
||||||
}}
|
}}
|
||||||
flashing={flashing}
|
flashing={flashing}
|
||||||
|
@ -133,8 +133,7 @@ const translation = {
|
|||||||
flashCompleted: 'Flash Completed!',
|
flashCompleted: 'Flash Completed!',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
errorReporting:
|
errorReporting: 'Anonymously report errors to balena.io',
|
||||||
'Anonymously report errors and usage statistics to balena.io',
|
|
||||||
autoUpdate: 'Auto-updates enabled',
|
autoUpdate: 'Auto-updates enabled',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
systemInformation: 'System Information',
|
systemInformation: 'System Information',
|
||||||
|
@ -15,12 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { findLastIndex, once } from 'lodash';
|
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 SentryRenderer from '@sentry/electron/renderer';
|
||||||
import * as settings from '../models/settings';
|
import * as settings from '../models/settings';
|
||||||
import { store } from '../models/store';
|
|
||||||
import { version } from '../../../../package.json';
|
|
||||||
|
|
||||||
type AnalyticsPayload = _.Dictionary<any>;
|
type AnalyticsPayload = _.Dictionary<any>;
|
||||||
|
|
||||||
@ -115,7 +111,6 @@ export const anonymizeAnalyticsPayload = (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
let analyticsClient: Client;
|
|
||||||
/**
|
/**
|
||||||
* @summary Init analytics configurations
|
* @summary Init analytics configurations
|
||||||
*/
|
*/
|
||||||
@ -127,95 +122,8 @@ export const initAnalytics = once(() => {
|
|||||||
beforeSend: anonymizeSentryData,
|
beforeSend: anonymizeSentryData,
|
||||||
debug: process.env.ETCHER_SENTRY_DEBUG === 'true',
|
debug: process.env.ETCHER_SENTRY_DEBUG === 'true',
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectName =
|
|
||||||
settings.getSync('analyticsAmplitudeToken') || process.env.AMPLITUDE_TOKEN;
|
|
||||||
|
|
||||||
const clientConfig = {
|
|
||||||
projectName,
|
|
||||||
endpoint: 'data.balena-cloud.com',
|
|
||||||
componentName: 'etcher',
|
|
||||||
componentVersion: version,
|
|
||||||
};
|
|
||||||
analyticsClient = projectName
|
|
||||||
? createClient(clientConfig)
|
|
||||||
: createNoopClient();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getCircularReplacer = () => {
|
|
||||||
const seen = new WeakSet();
|
|
||||||
return (_key: any, value: any) => {
|
|
||||||
if (typeof value === 'object' && value !== null) {
|
|
||||||
if (seen.has(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
seen.add(value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function flattenObject(obj: any) {
|
|
||||||
const toReturn: AnalyticsPayload = {};
|
|
||||||
|
|
||||||
for (const i in obj) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(obj, i)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(obj[i])) {
|
|
||||||
toReturn[i] = obj[i];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj[i] === 'object' && obj[i] !== null) {
|
|
||||||
const flatObject = flattenObject(obj[i]);
|
|
||||||
for (const x in flatObject) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(flatObject, x)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
toReturn[i.toLowerCase() + '.' + x.toLowerCase()] = flatObject[x];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toReturn[i] = obj[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatEvent(data: any): AnalyticsPayload {
|
|
||||||
const event = JSON.parse(JSON.stringify(data, getCircularReplacer()));
|
|
||||||
return anonymizeAnalyticsPayload(flattenObject(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportAnalytics(message: string, data: AnalyticsPayload = {}) {
|
|
||||||
const { applicationSessionUuid, flashingWorkflowUuid } = store
|
|
||||||
.getState()
|
|
||||||
.toJS();
|
|
||||||
|
|
||||||
const event = formatEvent({
|
|
||||||
...data,
|
|
||||||
applicationSessionUuid,
|
|
||||||
flashingWorkflowUuid,
|
|
||||||
});
|
|
||||||
analyticsClient.track(message, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Log an event
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* This function sends the debug message to product analytics services.
|
|
||||||
*/
|
|
||||||
export async function logEvent(message: string, data: AnalyticsPayload = {}) {
|
|
||||||
const shouldReportAnalytics = await settings.get('errorReporting');
|
|
||||||
if (shouldReportAnalytics) {
|
|
||||||
initAnalytics();
|
|
||||||
reportAnalytics(message, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Log an exception
|
* @summary Log an exception
|
||||||
*
|
*
|
||||||
|
@ -20,44 +20,11 @@ import type { Dictionary } from 'lodash';
|
|||||||
import * as errors from '../../../shared/errors';
|
import * as errors from '../../../shared/errors';
|
||||||
import type { SourceMetadata } from '../../../shared/typings/source-selector';
|
import type { SourceMetadata } from '../../../shared/typings/source-selector';
|
||||||
import * as flashState from '../models/flash-state';
|
import * as flashState from '../models/flash-state';
|
||||||
import * as selectionState from '../models/selection-state';
|
|
||||||
import * as settings from '../models/settings';
|
import * as settings from '../models/settings';
|
||||||
import * as analytics from '../modules/analytics';
|
|
||||||
import * as windowProgress from '../os/window-progress';
|
import * as windowProgress from '../os/window-progress';
|
||||||
import { spawnChildAndConnect } from './api';
|
import { spawnChildAndConnect } from './api';
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Handle a flash error and log it to analytics
|
|
||||||
*/
|
|
||||||
function handleErrorLogging(
|
|
||||||
error: Error & { code: string },
|
|
||||||
analyticsData: any,
|
|
||||||
) {
|
|
||||||
const eventData = {
|
|
||||||
...analyticsData,
|
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (error.code === 'EVALIDATION') {
|
|
||||||
analytics.logEvent('Validation error', eventData);
|
|
||||||
} else if (error.code === 'EUNPLUGGED') {
|
|
||||||
analytics.logEvent('Drive unplugged', eventData);
|
|
||||||
} else if (error.code === 'EIO') {
|
|
||||||
analytics.logEvent('Input/output error', eventData);
|
|
||||||
} else if (error.code === 'ENOSPC') {
|
|
||||||
analytics.logEvent('Out of space', eventData);
|
|
||||||
} else if (error.code === 'ECHILDDIED') {
|
|
||||||
analytics.logEvent('Child died unexpectedly', eventData);
|
|
||||||
} else {
|
|
||||||
analytics.logEvent('Flash error', {
|
|
||||||
...eventData,
|
|
||||||
error: errors.toJSON(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelEmitter: (type: string) => void | undefined;
|
let cancelEmitter: (type: string) => void | undefined;
|
||||||
|
|
||||||
interface FlashResults {
|
interface FlashResults {
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
cancelled?: boolean;
|
cancelled?: boolean;
|
||||||
@ -88,14 +55,6 @@ async function performWrite(
|
|||||||
|
|
||||||
const flashResults: FlashResults = {};
|
const flashResults: FlashResults = {};
|
||||||
|
|
||||||
const analyticsData = {
|
|
||||||
image,
|
|
||||||
drives,
|
|
||||||
driveCount: drives.length,
|
|
||||||
uuid: flashState.getFlashUuid(),
|
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFail = ({ device, error }: { device: any; error: any }) => {
|
const onFail = ({ device, error }: { device: any; error: any }) => {
|
||||||
console.log('fail event');
|
console.log('fail event');
|
||||||
console.log(device);
|
console.log(device);
|
||||||
@ -103,7 +62,6 @@ async function performWrite(
|
|||||||
if (device.devicePath) {
|
if (device.devicePath) {
|
||||||
flashState.addFailedDeviceError({ device, error });
|
flashState.addFailedDeviceError({ device, error });
|
||||||
}
|
}
|
||||||
handleErrorLogging(error, analyticsData);
|
|
||||||
finish();
|
finish();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -195,17 +153,6 @@ export async function flash(
|
|||||||
drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
|
drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const analyticsData = {
|
|
||||||
image,
|
|
||||||
drives,
|
|
||||||
driveCount: drives.length,
|
|
||||||
uuid: flashState.getFlashUuid(),
|
|
||||||
status: 'started',
|
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
|
||||||
};
|
|
||||||
|
|
||||||
analytics.logEvent('Flash', analyticsData);
|
|
||||||
|
|
||||||
// start api and call the flasher
|
// start api and call the flasher
|
||||||
try {
|
try {
|
||||||
const result = await write(image, drives, flashState.setProgressState);
|
const result = await write(image, drives, flashState.setProgressState);
|
||||||
@ -220,39 +167,10 @@ export async function flash(
|
|||||||
|
|
||||||
windowProgress.clear();
|
windowProgress.clear();
|
||||||
|
|
||||||
const { results = {} } = flashState.getFlashResults();
|
|
||||||
|
|
||||||
const eventData = {
|
|
||||||
...analyticsData,
|
|
||||||
errors: results.errors,
|
|
||||||
devices: results.devices,
|
|
||||||
status: 'failed',
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
analytics.logEvent('Write failed', eventData);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
windowProgress.clear();
|
windowProgress.clear();
|
||||||
|
|
||||||
if (flashState.wasLastFlashCancelled()) {
|
|
||||||
const eventData = {
|
|
||||||
...analyticsData,
|
|
||||||
status: 'cancel',
|
|
||||||
};
|
|
||||||
analytics.logEvent('Elevation cancelled', eventData);
|
|
||||||
} else {
|
|
||||||
const { results = {} } = flashState.getFlashResults();
|
|
||||||
const eventData = {
|
|
||||||
...analyticsData,
|
|
||||||
errors: results.errors,
|
|
||||||
devices: results.devices,
|
|
||||||
status: 'finished',
|
|
||||||
bytesWritten: results.bytesWritten,
|
|
||||||
sourceMetadata: results.sourceMetadata,
|
|
||||||
};
|
|
||||||
analytics.logEvent('Done', eventData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -261,16 +179,6 @@ export async function flash(
|
|||||||
*/
|
*/
|
||||||
export async function cancel(type: string) {
|
export async function cancel(type: string) {
|
||||||
const status = type.toLowerCase();
|
const status = type.toLowerCase();
|
||||||
const drives = selectionState.getSelectedDevices();
|
|
||||||
const analyticsData = {
|
|
||||||
image: selectionState.getImage()?.path,
|
|
||||||
drives,
|
|
||||||
driveCount: drives.length,
|
|
||||||
uuid: flashState.getFlashUuid(),
|
|
||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
|
||||||
status,
|
|
||||||
};
|
|
||||||
analytics.logEvent('Cancel', analyticsData);
|
|
||||||
|
|
||||||
if (cancelEmitter) {
|
if (cancelEmitter) {
|
||||||
cancelEmitter(status);
|
cancelEmitter(status);
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import * as settings from '../../../models/settings';
|
import * as settings from '../../../models/settings';
|
||||||
import { logEvent } from '../../../modules/analytics';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Open an external resource
|
* @summary Open an external resource
|
||||||
@ -27,8 +26,6 @@ export async function open(url: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logEvent('Open external link', { url });
|
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
electron.shell.openExternal(url);
|
electron.shell.openExternal(url);
|
||||||
}
|
}
|
||||||
|
@ -198,9 +198,7 @@ export class FlashStep extends React.PureComponent<
|
|||||||
private handleFlashErrorResponse(shouldRetry: boolean) {
|
private handleFlashErrorResponse(shouldRetry: boolean) {
|
||||||
this.setState({ errorMessage: '' });
|
this.setState({ errorMessage: '' });
|
||||||
flashState.resetState();
|
flashState.resetState();
|
||||||
if (shouldRetry) {
|
if (!shouldRetry) {
|
||||||
analytics.logEvent('Restart after failure');
|
|
||||||
} else {
|
|
||||||
selection.clear();
|
selection.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/gear.svg';
|
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 QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-question.svg';
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Flex } from 'rendition';
|
import { Alert, Flex, Link } from 'rendition';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import FinishPage from '../../components/finish/finish';
|
import FinishPage from '../../components/finish/finish';
|
||||||
@ -35,6 +36,7 @@ import { observe } from '../../models/store';
|
|||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
import {
|
import {
|
||||||
IconButton as BaseIcon,
|
IconButton as BaseIcon,
|
||||||
|
IconButton,
|
||||||
ThemedProvider,
|
ThemedProvider,
|
||||||
} from '../../styled-components';
|
} from '../../styled-components';
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ import { FlashStep } from './Flash';
|
|||||||
|
|
||||||
import EtcherSvg from '../../../assets/etcher.svg';
|
import EtcherSvg from '../../../assets/etcher.svg';
|
||||||
import { SafeWebview } from '../../components/safe-webview/safe-webview';
|
import { SafeWebview } from '../../components/safe-webview/safe-webview';
|
||||||
|
import { theme } from '../../theme';
|
||||||
|
|
||||||
const Icon = styled(BaseIcon)`
|
const Icon = styled(BaseIcon)`
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
@ -97,6 +100,8 @@ const StepBorder = styled.div<{
|
|||||||
margin-left: ${(props) => (props.right ? '-120px' : undefined)};
|
margin-left: ${(props) => (props.right ? '-120px' : undefined)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ANALYTICS_ALERT_VISIBILITY_KEY = 'analytics_alert_visible';
|
||||||
|
|
||||||
interface MainPageStateFromStore {
|
interface MainPageStateFromStore {
|
||||||
isFlashing: boolean;
|
isFlashing: boolean;
|
||||||
hasImage: boolean;
|
hasImage: boolean;
|
||||||
@ -113,6 +118,7 @@ interface MainPageState {
|
|||||||
isWebviewShowing: boolean;
|
isWebviewShowing: boolean;
|
||||||
hideSettings: boolean;
|
hideSettings: boolean;
|
||||||
featuredProjectURL?: string;
|
featuredProjectURL?: string;
|
||||||
|
analyticsAlertIsVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MainPage extends React.Component<
|
export class MainPage extends React.Component<
|
||||||
@ -125,6 +131,8 @@ export class MainPage extends React.Component<
|
|||||||
current: 'main',
|
current: 'main',
|
||||||
isWebviewShowing: false,
|
isWebviewShowing: false,
|
||||||
hideSettings: true,
|
hideSettings: true,
|
||||||
|
analyticsAlertIsVisible:
|
||||||
|
localStorage.getItem(ANALYTICS_ALERT_VISIBILITY_KEY) !== 'false',
|
||||||
...this.stateHelper(),
|
...this.stateHelper(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -153,6 +161,13 @@ export class MainPage extends React.Component<
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hideAnalyticsAlert = () => {
|
||||||
|
if (this.state.analyticsAlertIsVisible) {
|
||||||
|
localStorage.setItem(ANALYTICS_ALERT_VISIBILITY_KEY, 'false');
|
||||||
|
this.setState({ analyticsAlertIsVisible: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
observe(() => {
|
observe(() => {
|
||||||
this.setState(this.stateHelper());
|
this.setState(this.stateHelper());
|
||||||
@ -160,6 +175,17 @@ export class MainPage extends React.Component<
|
|||||||
this.setState({ featuredProjectURL: await this.getFeaturedProjectURL() });
|
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() {
|
private renderMain() {
|
||||||
const state = flashState.getFlashState();
|
const state = flashState.getFlashState();
|
||||||
const shouldDriveStepBeDisabled = !this.state.hasImage;
|
const shouldDriveStepBeDisabled = !this.state.hasImage;
|
||||||
@ -169,86 +195,127 @@ export class MainPage extends React.Component<
|
|||||||
!this.state.isFlashing || !this.state.isWebviewShowing;
|
!this.state.isFlashing || !this.state.isWebviewShowing;
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px`}
|
m={`110px ${this.state.isWebviewShowing ? 35 : 55}px 18px ${this.state.isWebviewShowing ? 35 : 55}px`}
|
||||||
justifyContent="space-between"
|
flexDirection="column"
|
||||||
>
|
>
|
||||||
{notFlashingOrSplitView && (
|
<Flex
|
||||||
<>
|
justifyContent="space-between"
|
||||||
<SourceSelector flashing={this.state.isFlashing} />
|
mb={this.state.analyticsAlertIsVisible ? '0px' : '92px'}
|
||||||
<Flex>
|
>
|
||||||
<StepBorder disabled={shouldDriveStepBeDisabled} left />
|
{notFlashingOrSplitView && (
|
||||||
</Flex>
|
<>
|
||||||
<TargetSelector
|
<SourceSelector
|
||||||
disabled={shouldDriveStepBeDisabled}
|
flashing={this.state.isFlashing}
|
||||||
hasDrive={this.state.hasDrive}
|
hideAnalyticsAlert={this.hideAnalyticsAlert}
|
||||||
flashing={this.state.isFlashing}
|
/>
|
||||||
/>
|
<Flex>
|
||||||
<Flex>
|
<StepBorder disabled={shouldDriveStepBeDisabled} left />
|
||||||
<StepBorder disabled={shouldFlashStepBeDisabled} right />
|
</Flex>
|
||||||
</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 && (
|
{this.state.isFlashing && this.state.isWebviewShowing && (
|
||||||
<Flex
|
<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}
|
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
color: '#fff',
|
top: 0,
|
||||||
left: 35,
|
left: 0,
|
||||||
top: 72,
|
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
|
<FlashStep
|
||||||
width={this.state.isWebviewShowing ? '220px' : '200px'}
|
width={this.state.isWebviewShowing ? '220px' : '200px'}
|
||||||
goToSuccess={() => this.setState({ current: 'success' })}
|
goToSuccess={() => this.setState({ current: 'success' })}
|
||||||
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
|
||||||
isFlashing={this.state.isFlashing}
|
isFlashing={this.state.isFlashing}
|
||||||
step={state.type}
|
step={state.type}
|
||||||
percentage={state.percentage}
|
percentage={state.percentage}
|
||||||
position={state.position}
|
position={state.position}
|
||||||
failed={state.failed}
|
failed={state.failed}
|
||||||
speed={state.speed}
|
speed={state.speed}
|
||||||
eta={state.eta}
|
eta={state.eta}
|
||||||
style={{ zIndex: 1 }}
|
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>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
190
npm-shrinkwrap.json
generated
190
npm-shrinkwrap.json
generated
@ -1,25 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "balena-etcher",
|
"name": "balena-etcher",
|
||||||
"version": "1.19.19",
|
"version": "2.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "balena-etcher",
|
"name": "balena-etcher",
|
||||||
"version": "1.19.19",
|
"version": "2.1.3",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/remote": "^2.1.2",
|
"@electron/remote": "^2.1.2",
|
||||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||||
"@ronomon/direct-io": "^3.0.1",
|
"@ronomon/direct-io": "^3.0.1",
|
||||||
"@sentry/electron": "^4.24.0",
|
"@sentry/electron": "^4.24.0",
|
||||||
"analytics-client": "^2.0.1",
|
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"drivelist": "^12.0.2",
|
"drivelist": "^12.0.2",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"electron-updater": "6.1.8",
|
"electron-updater": "6.1.8",
|
||||||
"etcher-sdk": "9.0.11",
|
"etcher-sdk": "9.1.2",
|
||||||
"i18next": "23.11.2",
|
"i18next": "23.11.2",
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
@ -88,7 +87,8 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.8",
|
"bufferutil": "^4.0.8",
|
||||||
"utf-8-validate": "^5.0.10"
|
"utf-8-validate": "^5.0.10",
|
||||||
|
"winusb-driver-generator": "2.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@aashutoshrathi/word-wrap": {
|
"node_modules/@aashutoshrathi/word-wrap": {
|
||||||
@ -100,148 +100,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@amplitude/analytics-browser": {
|
|
||||||
"version": "1.13.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-1.13.4.tgz",
|
|
||||||
"integrity": "sha512-FyNlrhLZUFI+lDHxbDGMoZED80iARS6VjTP+zZcfk0GWI7+lt0Meu5jD7G8xms21Ioxrg1+Lf1Q4v3CC6XN2Nw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-client-common": "^1.2.2",
|
|
||||||
"@amplitude/analytics-core": "^1.2.5",
|
|
||||||
"@amplitude/analytics-types": "^1.3.4",
|
|
||||||
"@amplitude/plugin-page-view-tracking-browser": "^1.0.12",
|
|
||||||
"@amplitude/plugin-web-attribution-browser": "^1.0.12",
|
|
||||||
"@amplitude/ua-parser-js": "^0.7.31",
|
|
||||||
"tslib": "^2.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/analytics-client-common": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-vwGgVXl9FKEi99OzjqqhX8RrulQQ55aAllhgbdyxpyyAQ5NbbZOPdrxp1ow0oliCVvbSDgUYOAeAwTChIgnStA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-connector": "^1.5.0",
|
|
||||||
"@amplitude/analytics-core": "^1.2.5",
|
|
||||||
"@amplitude/analytics-types": "^1.3.4",
|
|
||||||
"tslib": "^2.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/analytics-connector": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-connector/-/analytics-connector-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g=="
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/analytics-core": {
|
|
||||||
"version": "1.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-1.2.5.tgz",
|
|
||||||
"integrity": "sha512-V7CVlHVN+1diKiOpdp2bCPZ0mbS4CmUYF+v+eXDwVfJL3M/t3sVcT1apXnmVYGYi14cGu9hQOD11rD6qKbUOsw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-types": "^1.3.4",
|
|
||||||
"tslib": "^2.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/analytics-types": {
|
|
||||||
"version": "1.3.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-1.3.4.tgz",
|
|
||||||
"integrity": "sha512-tR70gzqFkEzX9QpxvWYMfLCledT7vMhgd3d4/bkp3nnGXTOORaVUOCcSgOyxyuFdSx84T61aP/eZPKIcZcaP+A=="
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser": {
|
|
||||||
"version": "0.2.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/marketing-analytics-browser/-/marketing-analytics-browser-0.2.9.tgz",
|
|
||||||
"integrity": "sha512-xOx5tCqV2A1r9+pYP7PPDfBqzQpKvhmIPR/CF4blpo7ZTYqCIWLg7QG1pN3uWFQuq5d4MWWz5QH+TUvKXifsHA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-browser": "^1.6.3",
|
|
||||||
"@amplitude/analytics-client-common": "^0.4.1",
|
|
||||||
"@amplitude/analytics-core": "^0.10.1",
|
|
||||||
"@amplitude/analytics-types": "^0.13.0",
|
|
||||||
"@amplitude/plugin-page-view-tracking-browser": "^0.4.9",
|
|
||||||
"@amplitude/plugin-web-attribution-browser": "^0.4.2",
|
|
||||||
"tslib": "^2.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-client-common": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-cwKHZVNfBt8kNmhXuSZ/BkEwdOSsCVQDXKgQysb4sp5AYkwqYV/bVd7yvWxffrrkK4N2PsYLnvODeTwANH/4UQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-connector": "^1.4.5",
|
|
||||||
"@amplitude/analytics-core": "^0.10.1",
|
|
||||||
"@amplitude/analytics-types": "^0.13.0",
|
|
||||||
"tslib": "^2.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-core": {
|
|
||||||
"version": "0.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-0.10.1.tgz",
|
|
||||||
"integrity": "sha512-XYJavGCnf0Y28chswEGNjSM2MqCMafsQvHpSgRD1JYTNrv+j/CTkj7P3TwyxriaXCTSfFcWTDPKRHd3SruU3Aw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-types": "^0.13.0",
|
|
||||||
"tslib": "^2.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/analytics-types": {
|
|
||||||
"version": "0.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-0.13.0.tgz",
|
|
||||||
"integrity": "sha512-yti2SytTIh0R5QknuKO1RMgB+r8CGjauhPfFaaYiTm4keAvqYxDdG9ULarPDoOx2VPSfB5Za779Kt1Muc+34PA=="
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
|
||||||
"version": "0.4.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-0.4.9.tgz",
|
|
||||||
"integrity": "sha512-dPeMativPA+UitDQbRv/FtBfAZtddbu9tgezbtSR90yubK+bS3oYWXETxR6X7P73qNBIZGUpH9i2nWVY6EXVQQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-client-common": "^0.4.1",
|
|
||||||
"@amplitude/analytics-types": "^0.13.0",
|
|
||||||
"tslib": "^2.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/marketing-analytics-browser/node_modules/@amplitude/plugin-web-attribution-browser": {
|
|
||||||
"version": "0.4.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-0.4.2.tgz",
|
|
||||||
"integrity": "sha512-9N1Qe4fTBmS7uDcgCA5PbIryJCf2V+BUhwP8n6BSwH1XOujx/sK0UQRgPlGRFSmmm2eH7QG9RKJkQj6VB3/zrQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-client-common": "^0.4.1",
|
|
||||||
"@amplitude/analytics-types": "^0.13.0",
|
|
||||||
"tslib": "^2.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/plugin-page-view-tracking-browser": {
|
|
||||||
"version": "1.0.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-1.0.12.tgz",
|
|
||||||
"integrity": "sha512-zbFbBi/+QrsWm1rPcFIAcxQ3t7uZwTuHCnHHzyZU/nQB/gyOgRh4U4uqt5DekLf4Tp3V2a+hmhmTE0KfRFuXLw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-client-common": "^1.2.2",
|
|
||||||
"@amplitude/analytics-types": "^1.3.4",
|
|
||||||
"tslib": "^2.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/plugin-web-attribution-browser": {
|
|
||||||
"version": "1.0.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-1.0.12.tgz",
|
|
||||||
"integrity": "sha512-zoIqgIT34xbE3V2TyQYoRVCs7j3biY/AXkGzYphiriuUvKmQtRjBoP2o08nZHYFzVSOSq4Ixk7OlelilW10Krg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-client-common": "^1.2.2",
|
|
||||||
"@amplitude/analytics-core": "^1.2.5",
|
|
||||||
"@amplitude/analytics-types": "^1.3.4",
|
|
||||||
"tslib": "^2.4.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@amplitude/ua-parser-js": {
|
|
||||||
"version": "0.7.33",
|
|
||||||
"resolved": "https://registry.npmjs.org/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
|
||||||
"integrity": "sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/ua-parser-js"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "paypal",
|
|
||||||
"url": "https://paypal.me/faisalman"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
@ -7025,16 +6883,6 @@
|
|||||||
"ajv": "^6.9.1"
|
"ajv": "^6.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/analytics-client": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/analytics-client/-/analytics-client-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-03Qo4r86wzw7NV0voG7xNwZjbba7h0wC6A8Dd85Slgt1bMg0jWKBXS9DnWIMiUMT4vfm8HtLaBDJyJIKlu1glQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@amplitude/analytics-browser": "^1.5.4",
|
|
||||||
"@amplitude/marketing-analytics-browser": "^0.2.4",
|
|
||||||
"js-cookie": "^3.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ansi-align": {
|
"node_modules/ansi-align": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
|
||||||
@ -12954,9 +12802,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/etcher-sdk": {
|
"node_modules/etcher-sdk": {
|
||||||
"version": "9.0.11",
|
"version": "9.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-9.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/etcher-sdk/-/etcher-sdk-9.1.2.tgz",
|
||||||
"integrity": "sha512-pWE+gqciw8V/NUnu/ywDLOUZZFdayx7ZJdZd5KVqIkm3pgEfAlpPgvmMNDnP9+b6/UW2wPaLy6NS8EiaDqK7tQ==",
|
"integrity": "sha512-mFH8Q2CsqmPkWGjq7MX+WkN5ndGVBmtelYNjlB9E5nj3qXnNBBNbu3YR7P9s/Ft9HN73sKg5mP72xu/U0klG5Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@balena/node-beaglebone-usbboot": "^3.0.0",
|
"@balena/node-beaglebone-usbboot": "^3.0.0",
|
||||||
"@balena/udif": "^1.1.2",
|
"@balena/udif": "^1.1.2",
|
||||||
@ -12977,7 +12826,7 @@
|
|||||||
"lzma-native": "^8.0.6",
|
"lzma-native": "^8.0.6",
|
||||||
"minimatch": "^9.0.3",
|
"minimatch": "^9.0.3",
|
||||||
"mountutils": "^1.3.20",
|
"mountutils": "^1.3.20",
|
||||||
"node-raspberrypi-usbboot": "1.0.7",
|
"node-raspberrypi-usbboot": "1.1.0",
|
||||||
"outdent": "^0.8.0",
|
"outdent": "^0.8.0",
|
||||||
"partitioninfo": "^6.0.2",
|
"partitioninfo": "^6.0.2",
|
||||||
"rwmutex": "^1.0.0",
|
"rwmutex": "^1.0.0",
|
||||||
@ -12989,7 +12838,7 @@
|
|||||||
"zip-part-stream": "^2.0.0"
|
"zip-part-stream": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18 <22"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"winusb-driver-generator": "^2.0.0"
|
"winusb-driver-generator": "^2.0.0"
|
||||||
@ -16846,14 +16695,6 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/js-cookie": {
|
|
||||||
"version": "3.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
|
||||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -20406,12 +20247,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-raspberrypi-usbboot": {
|
"node_modules/node-raspberrypi-usbboot": {
|
||||||
"version": "1.0.7",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-raspberrypi-usbboot/-/node-raspberrypi-usbboot-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/node-raspberrypi-usbboot/-/node-raspberrypi-usbboot-1.1.0.tgz",
|
||||||
"integrity": "sha512-ebL2xC7GQSrbrbAdaj2P6rWCViDoh0ewsgu3gHrtOCNeioCZ6ESirUob1iXT/0DCCMqUDPZA0VV3+euCPRruJw==",
|
"integrity": "sha512-X5+S+YO/jHcNosb+532shuSQAkaFrt9y0B+JiimTsH62gO+OenwBqWEEoVxrxr/fOtze30zPMe/66u/gA9WzhA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"usb": "^2.5.2"
|
"usb": "^2.12.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
|
13
package.json
13
package.json
@ -3,7 +3,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "balenaEtcher",
|
"displayName": "balenaEtcher",
|
||||||
"productName": "balenaEtcher",
|
"productName": "balenaEtcher",
|
||||||
"version": "1.19.19",
|
"version": "2.1.3",
|
||||||
"packageType": "local",
|
"packageType": "local",
|
||||||
"main": ".webpack/main",
|
"main": ".webpack/main",
|
||||||
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
||||||
@ -34,13 +34,12 @@
|
|||||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||||
"@ronomon/direct-io": "^3.0.1",
|
"@ronomon/direct-io": "^3.0.1",
|
||||||
"@sentry/electron": "^4.24.0",
|
"@sentry/electron": "^4.24.0",
|
||||||
"analytics-client": "^2.0.1",
|
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"drivelist": "^12.0.2",
|
"drivelist": "^12.0.2",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"electron-updater": "6.1.8",
|
"electron-updater": "6.1.8",
|
||||||
"etcher-sdk": "9.0.11",
|
"etcher-sdk": "9.1.2",
|
||||||
"i18next": "23.11.2",
|
"i18next": "23.11.2",
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
@ -106,8 +105,6 @@
|
|||||||
},
|
},
|
||||||
"hostDependencies": {
|
"hostDependencies": {
|
||||||
"debian": [
|
"debian": [
|
||||||
"gconf-service",
|
|
||||||
"gconf2",
|
|
||||||
"libasound2",
|
"libasound2",
|
||||||
"libatk1.0-0",
|
"libatk1.0-0",
|
||||||
"libc6",
|
"libc6",
|
||||||
@ -119,7 +116,6 @@
|
|||||||
"libfreetype6",
|
"libfreetype6",
|
||||||
"libgbm1",
|
"libgbm1",
|
||||||
"libgcc1",
|
"libgcc1",
|
||||||
"libgconf-2-4",
|
|
||||||
"libgdk-pixbuf2.0-0",
|
"libgdk-pixbuf2.0-0",
|
||||||
"libglib2.0-0",
|
"libglib2.0-0",
|
||||||
"libgtk-3-0",
|
"libgtk-3-0",
|
||||||
@ -147,10 +143,11 @@
|
|||||||
"node": ">=20 <21"
|
"node": ">=20 <21"
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2024-05-28T12:09:51.644Z"
|
"publishedAt": "2025-05-15T18:09:56.320Z"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.8",
|
"bufferutil": "^4.0.8",
|
||||||
"utf-8-validate": "^5.0.10"
|
"utf-8-validate": "^5.0.10",
|
||||||
|
"winusb-driver-generator": "2.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -63,9 +63,6 @@ const rules: Required<ModuleOptions>['rules'] = [
|
|||||||
|
|
||||||
const injectAnalyticsToken = new DefinePlugin({
|
const injectAnalyticsToken = new DefinePlugin({
|
||||||
'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''),
|
'process.env.SENTRY_TOKEN': JSON.stringify(process.env.SENTRY_TOKEN || ''),
|
||||||
'process.env.AMPLITUDE_TOKEN': JSON.stringify(
|
|
||||||
process.env.AMPLITUDE_TOKEN || '',
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const rendererConfig: Configuration = {
|
export const rendererConfig: Configuration = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user