Compare commits

...

128 Commits

Author SHA1 Message Date
Vipul Gupta (@vipulgupta2048)
82d0bba96a patch: Add etcherPro documentation
Signed-off-by: Vipul Gupta (@vipulgupta2048) <vipul@balena.io>
2022-11-08 03:47:51 +05:30
Balena CI
5945ab1f50 v1.7.9 2022-04-22 16:47:29 +03:00
bulldozer-balena[bot]
59d67220d4 Merge pull request #3747 from balena-io/next-release
patch: deb afterinstall and readme updates
2022-04-22 13:45:50 +00:00
mcraa
61610ded84 patch: update allowed extensions to include deb afterinstall in build 2022-04-22 15:08:04 +02:00
Peter Makra
c87a132f40 patch: add update notification 2022-04-22 14:44:36 +02:00
Andrew Scheller
350d4de32b patch: fix usb-device-boot link in README
Change-type: patch
2022-03-31 17:46:08 +02:00
Ken Bannister
f5f9025d6d Fix application directory for Debian postinst script
Change-type: patch
Signed-off-by: Ken Bannister <kb2ma@runbox.com>
2022-03-31 17:45:10 +02:00
Balena CI
549d744d04 v1.7.8 2022-03-18 19:22:12 +02:00
bulldozer-balena[bot]
6194460dc2 Merge pull request #3723 from balena-io/v1.7.8-draft
small ui updates
2022-03-18 17:20:11 +00:00
Peter Makra
8370f638b4 patch: complete suse uninstall readme
Change-Type: patch
2022-03-11 00:40:20 +01:00
Peter Makra
ac34c51125 patch: completed suse instructions
Change-Type: patch
2022-03-11 00:33:29 +01:00
Peter Makra
b241470fe1 patch: order rpm instrictions
Change-Type: patch
2022-03-10 17:00:43 +01:00
Peter Makra
179697040c Merge branch 'v1.7.8-draft' of github.com:balena-io/etcher into v1.7.8-draft 2022-03-10 16:37:01 +01:00
Peter Makra
335766ed12 patch: enabled update notification for version 1.7.8
Change-Type: patch
2022-03-10 16:36:24 +01:00
Peter Makra
4c5d052a71 patch: updated title to balenaEtcher
fixes #3592

Change-Type: patch
2022-03-10 16:35:03 +01:00
Peter Makra
86423342a8 patch: cleanup and organize readme
Change-Type: patch
2022-03-10 16:27:38 +01:00
Peter Makra
d8b41552e3 patch: extend cloudsmith attribution in readme
Change-Type: patch
2022-03-10 16:08:43 +01:00
Logicer
11c65fb392 Update macOS Icon to Big Sur Style
Change-type: patch
2022-03-10 15:55:27 +01:00
Balena CI
bed126506f v1.7.7 2022-02-22 11:25:43 +02:00
bulldozer-balena[bot]
f6aeb52b16 Merge pull request #3711 from balena-io/fix-auto-update
patch: Fix auto update
2022-02-22 09:23:16 +00:00
Peter Makra
a5201942b8 patch: clarified update check 2022-02-22 09:50:34 +01:00
Peter Makra
c1f7164273 patch: autoupdate stagingPercentage check, include default
Change-Type: patch
2022-02-21 21:09:49 +01:00
Balena CI
6774bf784c v1.7.6 2022-02-21 18:00:46 +02:00
bulldozer-balena[bot]
56ec8b4eac Merge pull request #3699 from balena-io/update-issue-template
patch: add requirements and help to issue template
2022-02-21 15:59:05 +00:00
Peter Makra
35868509af patch: version number notification 2022-02-21 15:21:58 +01:00
Peter Makra
3ab6749f49 Merge branch 'master' into update-issue-template 2022-02-21 14:33:16 +01:00
Balena CI
7a012a92bc v1.7.5 2022-02-21 15:19:52 +02:00
bulldozer-balena[bot]
aba01825a0 Merge pull request #3700 from flec/fix-basic-auth
patch: fix flashing from URL when using basic auth
2022-02-21 13:17:31 +00:00
Peter Makra
907a3308de updated branch with 1.7.4 2022-02-21 13:53:16 +01:00
Peter Makra
4366bb372f patch: fixed typos in template 2022-02-21 13:49:18 +01:00
Marco Füllemann
a6f6cd4a19 patch: fix flashing from URL when using basic auth 2022-02-21 13:36:40 +01:00
Balena CI
03ee428039 v1.7.4 2022-02-21 13:44:44 +02:00
bulldozer-balena[bot]
8d652d064d Merge pull request #3704 from balena-io/update-electron-12.2.3
patch: updated electron to 12.2.3
2022-02-21 11:42:46 +00:00
Peter Makra
28adc34239 patch: set version update notification 1.7.3 2022-02-17 14:15:46 +01:00
Peter Makra
120e9bf42f Merge branch 'update-electron-12.2.3' of github.com:balena-io/etcher into update-electron-12.2.3 2022-02-17 13:47:02 +01:00
Peter Makra
59f54e194b patch: updated electron to 12.2.3
Change-Type: patch
2022-02-17 13:46:35 +01:00
Peter Makra
c4834e61a7 patch: updated electron to 12.2.3
Change-Type: patch
2022-02-17 13:37:17 +01:00
mcraa
e4d02bc561 patch: add requirements and help to issue template 2022-02-08 08:19:55 +00:00
mcraa
b9e54e39f7 patch: add requirements and help to issue template 2022-02-08 09:19:55 +01:00
Balena CI
f3c32eac65 v1.7.3 2021-12-29 17:09:42 +02:00
bulldozer-balena[bot]
9a303ab344 Merge pull request #3667 from balena-io/fix-message-of.null
patch: fix mesage of null
2021-12-29 15:06:59 +00:00
Peter Makra
9c1b55bebc patch: fix mesage of null
Change-Type: patch
2021-12-29 15:23:21 +01:00
Balena CI
30ae4bbd86 v1.7.2 2021-12-21 20:38:54 +02:00
bulldozer-balena[bot]
c6126a980a Merge pull request #3661 from balena-io/fix-windows-open-from-web
patch: fixed open from browser on windows
2021-12-21 18:36:22 +00:00
Peter Makra
ef90d048ca patch: fixed open from browser on windows
Change-type: patch
Signed-off-by: Peter Makra <peter@balena.io>
2021-12-21 17:47:10 +01:00
Balena CI
b938132038 v1.7.1 2021-11-29 11:05:10 +02:00
bulldozer-balena[bot]
3cb2e78fe7 Merge pull request #3630 from balena-io/improve-webpack-build
patch: Improve webpack build time
2021-11-29 09:02:29 +00:00
Lorenzo Alberto Maria Ambrosi
ea9875ddf0 patch: Revert back to electron-rebuild
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-22 12:24:26 +01:00
Lorenzo Alberto Maria Ambrosi
65dacd2ff2 patch: Disallow TS in JS
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-16 20:08:11 +01:00
Lorenzo Alberto Maria Ambrosi
a190818827 patch: Remove esInterop TS flag
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-11 14:39:33 +01:00
Lorenzo Alberto Maria Ambrosi
98e33b619b patch: Use @balena/sudo-prompt
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-11 14:39:07 +01:00
Lorenzo Alberto Maria Ambrosi
685ed715ac patch: Update rpiboot guide link
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-11 11:47:11 +01:00
Lorenzo Alberto Maria Ambrosi
3cf3c4b398 patch: Improve webpack build time
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-11 10:45:00 +01:00
Balena CI
1c2ef4b1d4 v1.7.0 2021-11-09 18:09:26 +02:00
bulldozer-balena[bot]
d22fc91585 Merge pull request #3628 from balena-io/device-info-draft
Device info draft
2021-11-09 16:07:20 +00:00
Lorenzo Alberto Maria Ambrosi
0a28af5c35 patch: Add missing @types/react@16.8.5
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-09 14:10:18 +01:00
Lorenzo Alberto Maria Ambrosi
0c1e5b88ef patch: Use npm ci in Makefile
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-11-09 13:53:04 +01:00
Lorenzo Alberto Maria Ambrosi
790201be90 patch: Add draft info boxes for system information
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-27 10:41:56 +02:00
Lorenzo Alberto Maria Ambrosi
d8d379f05e patch: Remove electron-rebuild package
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-21 16:42:46 +02:00
Lorenzo Alberto Maria Ambrosi
b5e9701048 patch: Make electron a dev. dependency
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-21 16:40:37 +02:00
Lorenzo Alberto Maria Ambrosi
292f86d6f5 patch: Remove electron-rebuild package
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-21 16:40:15 +02:00
Lorenzo Alberto Maria Ambrosi
76ca9934c8 patch: Use exact modules versions
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-21 11:24:32 +02:00
Lorenzo Alberto Maria Ambrosi
37b826ee4e patch: Update etcher-sdk from v6.2.5 to v6.3.0
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-13 17:27:32 +02:00
JSReds
1e1bd3c508 Fix write step for Http file process
Change-type: patch
Signed-off-by: Andrea Rosci <andrear@balena.io>
2021-10-13 17:22:57 +02:00
Lorenzo Alberto Maria Ambrosi
00e8f11913 patch: Fix linting errors
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-13 17:21:48 +02:00
Lorenzo Alberto Maria Ambrosi
a3c24a26a0 minor: Refactor dependencies installation to avoid custom scripts
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-10-13 17:16:11 +02:00
Lorenzo Alberto Maria Ambrosi
4232928ad8 patch: Fix LEDs init error
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-09-30 13:05:47 +02:00
Balena CI
b165fb78da v1.6.0 2021-09-24 20:12:18 +03:00
bulldozer-balena[bot]
e9f6c5ead9 Merge pull request #3599 from balena-io/led-color-settings
Led color settings
2021-09-24 17:10:00 +00:00
Marco Füllemann
b2d0c1c9dd add support for basic auth when downloading images from URL
When selecting "Flash from URL" the user can optionally provide a username and password for basic authentication. The authentication input fields are collapsed by default. When the authentication input fields are collapsed after entering values the values are cleared to ensure that the user sees all parameter passed to the server.

Change-Type: minor
Changelog-Entry: Add support for basic auth when downloading images from URL.
2021-09-17 11:16:29 +02:00
Lorenzo Alberto Maria Ambrosi
14d91400a4 patch: Update etcher-sdk from v6.2.1 to v6.2.5
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-09-17 11:11:36 +02:00
David Gaspar
d0114aece7 Update Makefile to Apple M1 info
Expanding host architecture detection.

Change-type: patch
2021-09-07 16:56:10 +02:00
Lorenzo Alberto Maria Ambrosi
dff2df4aab Add LED settings for potentially different hardware
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-09-07 15:20:56 +02:00
Balena CI
13159f93ee v1.5.122 2021-09-02 17:48:21 +03:00
bulldozer-balena[bot]
3ece1fd841 Merge pull request #3590 from balena-io/various-fixes
Various fixes
2021-09-02 14:46:26 +00:00
Lorenzo Alberto Maria Ambrosi
f46963b6b3 Restore image file selection LED-drive pathing
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-09-02 14:16:18 +02:00
Lorenzo Alberto Maria Ambrosi
b97f4e0031 Update scripts submodule
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-08-31 16:40:48 +02:00
Lorenzo Alberto Maria Ambrosi
e2d233d74b Change LEDs colours
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-08-26 20:15:49 +02:00
Lorenzo Alberto Maria Ambrosi
a7ca2e527b Restore windows images warning
Change-type: patch
Changelog-entry: Windows images now show the proper warning again
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-08-26 20:15:48 +02:00
Mohamed Salah
396a053c0a Fix Update and install with DNF instructions
Change-type: patch
2021-08-26 20:15:48 +02:00
JSReds
d1a3f1cb88 Add possibile authorization as a query param
Change-type: patch
Signed-off-by: Andrea Rosci <andrear@balena.io>
2021-08-26 20:15:48 +02:00
Xtraim
9f96558cdd update the windows part
I choose to add this part because, after the clean the usb stick
could stay in a raw state without creating the new partions,
activating and formatting.
Thanks

Change-type: patch
2021-08-20 08:31:49 +02:00
thambu1710
b3bc589d70 Update SUPPORT.md
Change-type: patch
2021-08-20 08:31:45 +02:00
Seth Falco
18d2c28110 replace make webpack with npm run webpack
Change-type: patch
2021-08-20 08:31:38 +02:00
JSReds
b272ef296d Add loader on image select
Change-type: patch
Signed-off-by: Andrea Rosci <andrear@balena.io>
2021-08-20 08:31:27 +02:00
Zane Hitchcox
32ca28a3a9 add pnp-webpack-plugin
Change-type: patch
2021-08-20 08:31:15 +02:00
Lorenzo Alberto Maria Ambrosi
4d5e5a3b0b Remove redundant codespell dependency/tests
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-08-20 08:28:48 +02:00
Balena CI
8b3f37102d v1.5.121 2021-07-14 19:55:16 +03:00
bulldozer-balena[bot]
4b74253631 Merge pull request #3489 from balena-io/direct-select-drive
patch: Select drive on list interaction rather than modal closing
2021-07-14 16:52:42 +00:00
Vipul Gupta
a81b552b95 patch: Delete Codeowners 2021-07-02 13:59:11 +02:00
Lorenzo Alberto Maria Ambrosi
53f53c0f75 Add source maps for devtools
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-05-25 17:28:17 +02:00
Lorenzo Alberto Maria Ambrosi
fdaf5c69d6 Clone submodules when initializing modules
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-05-24 14:52:22 +02:00
Lorenzo Alberto Maria Ambrosi
061afca5d3 patch: Select drive on list interaction rather than modal closing
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-05-24 14:52:22 +02:00
Balena CI
ccb08a48f1 v1.5.120 2021-05-11 19:56:49 +03:00
bulldozer-balena[bot]
a8f3d45b12 Merge pull request #3514 from balena-io/add-cloudsmith-reference
Update README to reference Cloudsmith
2021-05-11 16:49:15 +00:00
Lorenzo Alberto Maria Ambrosi
7e333caaf9 Update README to reference Cloudsmith
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-05-11 17:59:17 +02:00
Balena CI
70229e8684 v1.5.119 2021-05-01 11:45:32 +03:00
bulldozer-balena[bot]
261700389b Merge pull request #3500 from balena-io/new-deb-rpm-ppa
Update readme for new PPA provider
2021-05-01 08:43:36 +00:00
Lorenzo Alberto Maria Ambrosi
250aed2eb1 Update readme for new PPA provider
Change-type: patch
Signed-off-by: Lorenzo Alberto Maria Ambrosi <lorenzothunder.ambrosi@gmail.com>
2021-04-30 23:30:08 +02:00
Balena CI
ed1f008fe2 v1.5.118 2021-04-29 11:47:01 +03:00
bulldozer-balena[bot]
e9ce270dab Merge pull request #3495 from balena-io/etcher-dev-server
Etcher dev server
2021-04-29 08:45:03 +00:00
Zane Hitchcox
1ee110bc95 patch: development environment
Add webpack dev server and hot module reloading to get live changes and reloads without reloading the whole electron app.

This patch also runs the development environment in development mode, which is much, much faster on builds and rebuilds.
2021-04-26 21:17:32 -04:00
Zane Hitchcox
33dd07c675 patch: watch files for electron 2021-04-20 22:30:05 -04:00
Balena CI
39ccbbeeda v1.5.117 2021-04-06 14:44:31 +03:00
bulldozer-balena[bot]
55d2400ac7 Merge pull request #3432 from balena-io/electron-11
Electron 11
2021-04-06 11:42:19 +00:00
Alexis Svinartchouk
0bdea5c54c Rename mac releases (keep old naming)
Change-type: patch
2021-04-02 15:52:33 +02:00
Alexis Svinartchouk
3be372d49f Disable spectron tests on macOS
Change-type: patch
2021-04-01 15:48:39 +02:00
Alexis Svinartchouk
d0c66b2c48 Update electron to v12.0.2
Change-type: patch
2021-04-01 12:13:34 +02:00
Alexis Svinartchouk
65082c4790 Update etcher-sdk from 6.1.1 to 6.2.1
Update etcher-sdk from 6.1.1 to 6.2.1

Change-type: patch
2021-03-29 14:11:44 +02:00
Alexis Svinartchouk
e87ed9beed Fix getAppPath() returning an asar file on macOS
Change-type: patch
2021-03-23 17:53:54 +01:00
Andrew Scheller
bc5563d9c2 Grammar fix
"flash directly" sounds odd

Change-type: patch
2021-03-23 14:32:30 +01:00
vlad doster
ad83ab5dcc (docs) update README.md
- fix spelling
- emphasize notes
- add link
- fix macOS to account for new homebrew API

Change-type: patch
2021-03-23 12:00:40 +01:00
Andrew Scheller
0dc1cf9701 Update copyright year in electron-builder.yml
Change-type: patch
2021-03-23 11:55:43 +01:00
Andrew Scheller
11489c6538 Update copyright year in .resinci.json
Change-type: patch
2021-03-23 11:55:29 +01:00
Dugan Chen
2619d4bc86 Separate the Yum and DNF instructions.
Change-type: patch
2021-03-23 11:55:04 +01:00
Alexis Svinartchouk
3730efd350 Set msvs_version to 2019 when rebuilding
Change-type: patch
2021-03-22 17:23:43 +01:00
Alexis Svinartchouk
6ece32c546 Use moduleIds: 'natural' in webpack config to keep js files in arm64 and x64 mac builds identical
Change-type: patch
2021-03-22 15:38:57 +01:00
Alexis Svinartchouk
fd9996a3cc Update electron-builder to 22.10.5
Change-type: patch
2021-03-22 15:38:57 +01:00
Alexis Svinartchouk
f06cc89152 Update spectron to v13
Change-type: patch
2021-03-22 15:38:57 +01:00
Alexis Svinartchouk
c1d7ab3fa9 Update dependencies, use aws4-axios@2.2.1 to avoid adding more dependiencies
Also filter out dmg-license dependencies from the shrinkwrap file
aws4-axios@2.3.0 brings in react-native, see aws/aws-sdk-js-v3#1797

Change-type: patch
2021-03-22 15:38:57 +01:00
Alexis Svinartchouk
b206483c7c Update scripts to build universal mac dmgs on the ci
Change-type: patch
2021-03-22 15:38:57 +01:00
Alexis Svinartchouk
c3eb8c7b56 Fix beforeBuild.js script to also work on mac
Change-type: patch
2021-03-15 19:26:49 +01:00
Alexis Svinartchouk
0849d4f435 Support building universal dmgs (x64 and arm64) for mac
Change-type: patch
2021-03-15 19:26:49 +01:00
Alexis Svinartchouk
1dba3ae19b Update electron-builder to 22.10.4
Change-type: patch
2021-02-16 15:49:18 +01:00
Alexis Svinartchouk
f33f2e3771 Fix titlebar z-index
Change-type: patch
2021-02-16 15:49:18 +01:00
Alexis Svinartchouk
e56aaed973 Explicitly set contextIsolation to false
Change-type: patch
2021-02-16 15:49:12 +01:00
Alexis Svinartchouk
a4659f038e Update electron from 9.4.1 to 11.2.3
Change-type: patch
2021-02-10 17:51:56 +01:00
Alexis Svinartchouk
cd462818da Update etcher-sdk from 6.1.0 to 6.1.1
Update etcher-sdk from 6.1.0 to 6.1.1

Change-type: patch
2021-02-10 17:50:47 +01:00
64 changed files with 9415 additions and 4543 deletions

View File

@@ -7,7 +7,6 @@ indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

1
.gitattributes vendored
View File

@@ -27,6 +27,7 @@ Makefile text
*.yml text
*.patch text
*.txt text
*.tpl text
CODEOWNERS text
*.plist text

View File

@@ -1,6 +1,11 @@
- **Etcher version:**
- **Operating system and architecture:**
- **Image flashed:**
- **What do you think should have happened:** <!-- or a step by step reproduction process -->
- **What happened:**
- **Do you see any meaningful error information in the DevTools?**
<!-- You can open DevTools by pressing `Ctrl+Shift+I` (`Ctrl+Alt+I` for Etcher before v1.3.x), or `Cmd+Opt+I` if you're on macOS. -->
<!-- issues with missing information will be labeled as not-enough-info and closed shortly -->
<!-- please try to include as many influencing elements as possible are you root, does any other process block the device, etc. -->
<!-- if you find a solution in the meantime thank you for sharing the fix and not just closing / abandoning your issue -->

View File

@@ -15,7 +15,7 @@
},
"builder": {
"appId": "io.balena.etcher",
"copyright": "Copyright 2016-2020 Balena Ltd",
"copyright": "Copyright 2016-2021 Balena Ltd",
"productName": "balenaEtcher",
"nodeGypRebuild": false,
"afterPack": "./afterPack.js",
@@ -24,13 +24,13 @@
"generated",
"lib/shared/catalina-sudo/sudo-askpass.osascript.js"
],
"beforeBuild": "./beforeBuild.js",
"afterSign": "./afterSignHook.js",
"mac": {
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
"entitlements": "entitlements.mac.plist",
"entitlementsInherit": "entitlements.mac.plist"
"entitlementsInherit": "entitlements.mac.plist",
"artifactName": "${productName}-${version}.${ext}"
},
"dmg": {
"iconSize": 110,

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,209 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
# v1.7.9
## (2022-04-22)
* patch: update allowed extensions to include deb afterinstall in build [mcraa]
* patch: add update notification [Peter Makra]
* patch: fix usb-device-boot link in README [Andrew Scheller]
* Fix application directory for Debian postinst script [Ken Bannister]
# v1.7.8
## (2022-03-18)
* patch: complete suse uninstall readme [Peter Makra]
* patch: completed suse instructions [Peter Makra]
* patch: order rpm instrictions [Peter Makra]
* patch: enabled update notification for version 1.7.8 [Peter Makra]
* patch: updated title to balenaEtcher [Peter Makra]
* patch: cleanup and organize readme [Peter Makra]
* patch: extend cloudsmith attribution in readme [Peter Makra]
* Update macOS Icon to Big Sur Style [Logicer]
# v1.7.7
## (2022-02-22)
* patch: clarified update check [Peter Makra]
* patch: autoupdate stagingPercentage check, include default [Peter Makra]
# v1.7.6
## (2022-02-21)
* patch: version number notification [Peter Makra]
* patch: fixed typos in template [Peter Makra]
* patch: add requirements and help to issue template [mcraa]
* patch: add requirements and help to issue template [mcraa]
# v1.7.5
## (2022-02-21)
* patch: fix flashing from URL when using basic auth [Marco Füllemann]
# v1.7.4
## (2022-02-21)
* patch: set version update notification 1.7.3 [Peter Makra]
* patch: updated electron to 12.2.3 [Peter Makra]
* patch: updated electron to 12.2.3 [Peter Makra]
# v1.7.3
## (2021-12-29)
* patch: fix mesage of null [Peter Makra]
# v1.7.2
## (2021-12-21)
* patch: fixed open from browser on windows [Peter Makra]
# v1.7.1
## (2021-11-22)
* patch: Revert back to electron-rebuild [Lorenzo Alberto Maria Ambrosi]
* patch: Disallow TS in JS [Lorenzo Alberto Maria Ambrosi]
* patch: Remove esInterop TS flag [Lorenzo Alberto Maria Ambrosi]
* patch: Use @balena/sudo-prompt [Lorenzo Alberto Maria Ambrosi]
* patch: Update rpiboot guide link [Lorenzo Alberto Maria Ambrosi]
* patch: Improve webpack build time [Lorenzo Alberto Maria Ambrosi]
# v1.7.0
## (2021-11-09)
* patch: Add missing @types/react@16.8.5 [Lorenzo Alberto Maria Ambrosi]
* patch: Use npm ci in Makefile [Lorenzo Alberto Maria Ambrosi]
* patch: Add draft info boxes for system information [Lorenzo Alberto Maria Ambrosi]
* patch: Remove electron-rebuild package [Lorenzo Alberto Maria Ambrosi]
* patch: Make electron a dev. dependency [Lorenzo Alberto Maria Ambrosi]
* patch: Remove electron-rebuild package [Lorenzo Alberto Maria Ambrosi]
* patch: Use exact modules versions [Lorenzo Alberto Maria Ambrosi]
* patch: Update etcher-sdk from v6.2.5 to v6.3.0 [Lorenzo Alberto Maria Ambrosi]
* Fix write step for Http file process [JSReds]
* patch: Fix linting errors [Lorenzo Alberto Maria Ambrosi]
* minor: Refactor dependencies installation to avoid custom scripts [Lorenzo Alberto Maria Ambrosi]
* patch: Fix LEDs init error [Lorenzo Alberto Maria Ambrosi]
# v1.6.0
## (2021-09-20)
* Add support for basic auth when downloading images from URL. [Marco Füllemann]
* patch: Update etcher-sdk from v6.2.1 to v6.2.5 [Lorenzo Alberto Maria Ambrosi]
* Update Makefile to Apple M1 info [David Gaspar]
* Add LED settings for potentially different hardware [Lorenzo Alberto Maria Ambrosi]
# v1.5.122
## (2021-09-02)
* Restore image file selection LED-drive pathing [Lorenzo Alberto Maria Ambrosi]
* Update scripts submodule [Lorenzo Alberto Maria Ambrosi]
* Change LEDs colours [Lorenzo Alberto Maria Ambrosi]
* Windows images now show the proper warning again [Lorenzo Alberto Maria Ambrosi]
* Fix Update and install with DNF instructions [Mohamed Salah]
* Add possibile authorization as a query param [JSReds]
* update the windows part [Xtraim]
* Update SUPPORT.md [thambu1710]
* replace make webpack with npm run webpack [Seth Falco]
* Add loader on image select [JSReds]
* add pnp-webpack-plugin [Zane Hitchcox]
* Remove redundant codespell dependency/tests [Lorenzo Alberto Maria Ambrosi]
# v1.5.121
## (2021-07-05)
* patch: Delete Codeowners [Vipul Gupta]
* Add source maps for devtools [Lorenzo Alberto Maria Ambrosi]
* Clone submodules when initializing modules [Lorenzo Alberto Maria Ambrosi]
* patch: Select drive on list interaction rather than modal closing [Lorenzo Alberto Maria Ambrosi]
# v1.5.120
## (2021-05-11)
* Update README to reference Cloudsmith [Lorenzo Alberto Maria Ambrosi]
# v1.5.119
## (2021-04-30)
* Update readme for new PPA provider [Lorenzo Alberto Maria Ambrosi]
# v1.5.118
## (2021-04-27)
* patch: development environment [Zane Hitchcox]
* patch: watch files for electron [Zane Hitchcox]
# v1.5.117
## (2021-04-02)
* Rename mac releases (keep old naming) [Alexis Svinartchouk]
* Disable spectron tests on macOS [Alexis Svinartchouk]
* Update electron to v12.0.2 [Alexis Svinartchouk]
<details>
<summary> Update etcher-sdk from 6.1.1 to 6.2.1 [Alexis Svinartchouk] </summary>
> ## etcher-sdk-6.2.1
> ### (2021-03-26)
>
>
> <details>
> <summary> Update node-raspberrypi-usbboot from 0.2.11 to 0.3.0 [Alexis Svinartchouk] </summary>
>
>> ### node-raspberrypi-usbboot-0.3.0
>> #### (2021-03-26)
>>
>> * Add support for compute module 4 [Alexis Svinartchouk]
>> * Fix size endianness of boot_message_t message [Alexis Svinartchouk]
>>
> </details>
>
>
> ## etcher-sdk-6.2.0
> ### (2021-02-18)
>
> * Added BeagleBone USB Boot example [Parthiban Gandhi]
> * Added BeagleBone USB Boot support [Parthiban Gandhi]
>
</details>
* Fix getAppPath() returning an asar file on macOS [Alexis Svinartchouk]
* Grammar fix [Andrew Scheller]
* (docs) update README.md [vlad doster]
* Update copyright year in electron-builder.yml [Andrew Scheller]
* Update copyright year in .resinci.json [Andrew Scheller]
* Separate the Yum and DNF instructions. [Dugan Chen]
* Set msvs_version to 2019 when rebuilding [Alexis Svinartchouk]
* Use moduleIds: 'natural' in webpack config to keep js files in arm64 and x64 mac builds identical [Alexis Svinartchouk]
* Update electron-builder to 22.10.5 [Alexis Svinartchouk]
* Update spectron to v13 [Alexis Svinartchouk]
* Update dependencies, use aws4-axios@2.2.1 to avoid adding more dependiencies [Alexis Svinartchouk]
* Update scripts to build universal mac dmgs on the ci [Alexis Svinartchouk]
* Fix beforeBuild.js script to also work on mac [Alexis Svinartchouk]
* Support building universal dmgs (x64 and arm64) for mac [Alexis Svinartchouk]
* Update electron-builder to 22.10.4 [Alexis Svinartchouk]
* Fix titlebar z-index [Alexis Svinartchouk]
* Explicitly set contextIsolation to false [Alexis Svinartchouk]
* Update electron from 9.4.1 to 11.2.3 [Alexis Svinartchouk]
<details>
<summary> Update etcher-sdk from 6.1.0 to 6.1.1 [Alexis Svinartchouk] </summary>
> ## etcher-sdk-6.1.1
> ### (2021-02-10)
>
>
> <details>
> <summary> Update node-raspberrypi-usbboot from 0.2.10 to 0.2.11 [Alexis Svinartchouk] </summary>
>
>> ### node-raspberrypi-usbboot-0.2.11
>> #### (2021-02-10)
>>
>> * Update @balena.io/usb from 1.3.12 to 1.3.14 [Alexis Svinartchouk]
>>
> </details>
>
>
</details>
# v1.5.116
## (2021-02-03)

View File

@@ -1,2 +0,0 @@
* @thundron @zvin @jviotti
/scripts @nazrhom

View File

@@ -66,6 +66,9 @@ else
ifeq ($(shell uname -m),x86_64)
HOST_ARCH = x64
endif
ifeq ($(shell uname -m),arm64)
HOST_ARCH = aarch64
endif
endif
endif
@@ -86,11 +89,9 @@ TARGET_ARCH ?= $(HOST_ARCH)
# Electron
# ---------------------------------------------------------------------
electron-develop:
$(RESIN_SCRIPTS)/electron/install.sh \
-b $(shell pwd) \
-r $(TARGET_ARCH) \
-s $(PLATFORM) \
-m $(NPM_VERSION)
git submodule update --init && \
npm ci && \
npm run webpack
electron-test:
$(RESIN_SCRIPTS)/electron/test.sh \
@@ -125,7 +126,7 @@ TARGETS = \
.PHONY: $(TARGETS)
lint:
lint:
npm run lint
test:

175
README.md
View File

@@ -5,16 +5,15 @@
Etcher is a powerful OS image flasher built with web technologies to ensure
flashing an SDCard or USB drive is a pleasant and safe experience. It protects
you from accidentally writing to your hard-drives, ensures every byte of data
was written correctly and much more. It can also flash directly Raspberry Pi devices that support the usbboot protocol
was written correctly, and much more. It can also directly flash Raspberry Pi devices that support [USB device boot mode](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#usb-device-boot-mode).
[![Current Release](https://img.shields.io/github/release/balena-io/etcher.svg?style=flat-square)](https://balena.io/etcher)
[![License](https://img.shields.io/github/license/balena-io/etcher.svg?style=flat-square)](https://github.com/balena-io/etcher/blob/master/LICENSE)
[![Dependency status](https://img.shields.io/david/balena-io/etcher.svg?style=flat-square)](https://david-dm.org/balena-io/etcher)
[![Balena.io Forums](https://img.shields.io/discourse/https/forums.balena.io/topics.svg?style=flat-square&label=balena.io%20forums)](https://forums.balena.io/c/etcher)
***
---
[**Download**][etcher] | [**Support**][SUPPORT] | [**Documentation**][USER-DOCUMENTATION] | [**Contributing**][CONTRIBUTING] | [**Roadmap**][milestones]
[**Download**][etcher] | [**Support**][support] | [**Documentation**][user-documentation] | [**Contributing**][contributing] | [**Roadmap**][milestones]
## Supported Operating Systems
@@ -22,7 +21,7 @@ was written correctly and much more. It can also flash directly Raspberry Pi dev
- macOS 10.10 (Yosemite) and later
- Microsoft Windows 7 and later
Note that Etcher will run on any platform officially supported by
**Note**: Etcher will run on any platform officially supported by
[Electron][electron]. Read more in their
[documentation][electron-supported-platforms].
@@ -31,81 +30,118 @@ Note that Etcher will run on any platform officially supported by
Refer to the [downloads page][etcher] for the latest pre-made
installers for all supported operating systems.
## Packages
> [![Hosted By: Cloudsmith](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=for-the-badge)](https://cloudsmith.com) \
Package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com).
Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that
enables your organization to create, store and share packages in any format, to any place, with total
confidence.
#### Debian and Ubuntu based Package Repository (GNU/Linux x86/x64)
1. Add Etcher debian repository:
> Detailed or alternative steps in the [instructions by Cloudsmith](https://cloudsmith.io/~balena/repos/etcher/setup/#formats-deb)
```sh
echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
```
1. Add Etcher Debian repository:
2. Trust Bintray.com's GPG key:
```sh
curl -1sLf \
'https://dl.cloudsmith.io/public/balena/etcher/setup.deb.sh' \
| sudo -E bash
```
```sh
sudo apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv-keys 379CE192D401AB61
```
2. Update and install:
3. Update and install:
```sh
sudo apt-get update
sudo apt-get install balena-etcher-electron
```
```sh
sudo apt-get update
sudo apt-get install balena-etcher-electron
```
##### Uninstall
```sh
sudo apt-get remove balena-etcher-electron
sudo rm /etc/apt/sources.list.d/balena-etcher.list
sudo apt-get update
rm /etc/apt/sources.list.d/balena-etcher.list
apt-get clean
rm -rf /var/lib/apt/lists/*
apt-get update
```
##### OpenSUSE LEAP & Tumbleweed install
#### Redhat (RHEL) and Fedora-based Package Repository (GNU/Linux x86/x64)
> Detailed or alternative steps in the [instructions by Cloudsmith](https://cloudsmith.io/~balena/repos/etcher/setup/#formats-rpm)
##### DNF
1. Add Etcher rpm repository:
```sh
curl -1sLf \
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
| sudo -E bash
```
2. Update and install:
```sh
sudo dnf install -y balena-etcher-electron
```
###### Uninstall
```sh
sudo zypper ar https://balena.io/etcher/static/etcher-rpm.repo
sudo zypper ref
sudo zypper in balena-etcher-electron
rm /etc/yum.repos.d/balena-etcher.repo
rm /etc/yum.repos.d/balena-etcher-source.repo
```
##### Yum
1. Add Etcher rpm repository:
```sh
curl -1sLf \
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
| sudo -E bash
```
2. Update and install:
```sh
sudo yum install -y balena-etcher-electron
```
###### Uninstall
```sh
sudo yum remove -y balena-etcher-electron
rm /etc/yum.repos.d/balena-etcher.repo
rm /etc/yum.repos.d/balena-etcher-source.repo
```
#### OpenSUSE LEAP & Tumbleweed install (zypper)
1. Add the repo
```sh
curl -1sLf \
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
| sudo -E bash
```
2. Update and install
```sh
sudo zypper up
sudo zypper install balena-etcher-electron
```
##### Uninstall
```sh
sudo zypper rm balena-etcher-electron
```
#### Redhat (RHEL) and Fedora based Package Repository (GNU/Linux x86/x64)
1. Add Etcher rpm repository:
```sh
sudo wget https://balena.io/etcher/static/etcher-rpm.repo -O /etc/yum.repos.d/etcher-rpm.repo
```
2. Update and install:
```sh
sudo yum install -y balena-etcher-electron
```
or
```sh
sudo dnf install -y balena-etcher-electron
```
##### Uninstall
```sh
sudo yum remove -y balena-etcher-electron
sudo rm /etc/yum.repos.d/etcher-rpm.repo
sudo yum clean all
sudo yum makecache fast
```
or
```sh
sudo dnf remove -y balena-etcher-electron
sudo rm /etc/yum.repos.d/etcher-rpm.repo
sudo dnf clean all
sudo dnf makecache
# remove the repo
sudo zypper rr balena-etcher
sudo zypper rr balena-etcher-source
```
#### Solus (GNU/Linux x64)
@@ -120,11 +156,10 @@ sudo eopkg it etcher
sudo eopkg rm etcher
```
#### Arch Linux / Manjaro (GNU/Linux x64)
#### Arch/Manjaro Linux (GNU/Linux x64)
Etcher is offered through the Arch User Repository and can be installed on both Manjaro and Arch systems. You can compile it from the source code in this repository using [`balena-etcher`](https://aur.archlinux.org/packages/balena-etcher/). The following example uses a common AUR helper to install the latest release:
```sh
yay -S balena-etcher
```
@@ -135,20 +170,20 @@ yay -S balena-etcher
yay -R balena-etcher
```
#### Brew Cask (macOS)
#### Brew (macOS)
Note that the Etcher Cask has to be updated manually to point to new versions,
**Note**: Etcher has to be updated manually to point to new versions,
so it might not refer to the latest version immediately after an Etcher
release.
```sh
brew cask install balenaetcher
brew install balenaetcher
```
##### Uninstall
```sh
brew cask uninstall balenaetcher
brew uninstall balenaetcher
```
#### Chocolatey (Windows)
@@ -168,20 +203,20 @@ choco uninstall etcher
## Support
If you're having any problem, please [raise an issue][newissue] on GitHub and
If you're having any problem, please [raise an issue][newissue] on GitHub, and
the balena.io team will be happy to help.
## License
Etcher is free software, and may be redistributed under the terms specified in
Etcher is free software and may be redistributed under the terms specified in
the [license].
[etcher]: https://balena.io/etcher
[electron]: https://electronjs.org/
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
[SUPPORT]: https://github.com/balena-io/etcher/blob/master/SUPPORT.md
[CONTRIBUTING]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
[USER-DOCUMENTATION]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
[support]: https://github.com/balena-io/etcher/blob/master/SUPPORT.md
[contributing]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
[user-documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
[milestones]: https://github.com/balena-io/etcher/milestones
[newissue]: https://github.com/balena-io/etcher/issues/new
[license]: https://github.com/balena-io/etcher/blob/master/LICENSE

View File

@@ -1,5 +1,5 @@
Getting help with Etcher
========================
Getting help with BalenaEtcher
===============================
There are various ways to get support for Etcher if you experience an issue or
have an idea you'd like to share with us.
@@ -22,7 +22,7 @@ a look at the existing threads before opening a new one!
Make sure to mention the following information to help us provide better
support:
- The Etcher version you're running.
- The BalenaEtcher version you're running.
- The operating system you're running Etcher in.
@@ -32,7 +32,7 @@ support:
GitHub
------
If you encounter an issue or have a suggestion, head on over to Etcher's [issue
If you encounter an issue or have a suggestion, head on over to BalenaEtcher's [issue
tracker][issues] and if there isn't a ticket covering it, [create
one][new-issue].

11
after-install.tpl Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Link to the binary
# Must hardcode balenaEtcher directory; no variable available
ln -sf '/opt/balenaEtcher/${executable}' '/usr/bin/${executable}'
# SUID chrome-sandbox for Electron 5+
chmod 4755 '/opt/balenaEtcher/chrome-sandbox' || true
update-mime-database /usr/share/mime || true
update-desktop-database /usr/share/applications || true

Binary file not shown.

View File

@@ -1,26 +0,0 @@
'use strict'
const cp = require('child_process');
const rimraf = require('rimraf');
const process = require('process');
// Rebuild native modules for ia32 and run webpack again for the ia32 part of windows packages
exports.default = function(context) {
if (context.platform.name === 'windows') {
cp.execFileSync(
'bash',
['./node_modules/.bin/electron-rebuild', '--types', 'dev', '--arch', context.arch],
);
rimraf.sync('generated');
cp.execFileSync(
'bash',
['./node_modules/.bin/webpack'],
{
env: {
...process.env,
npm_config_target_arch: context.arch,
},
},
);
}
}

View File

@@ -91,7 +91,7 @@ make electron-develop
```sh
# Build the GUI
make webpack
npm run webpack
# Start Electron
npm start
```

View File

@@ -159,6 +159,18 @@ pre-installed in all modern Windows versions.
- Run `clean`. This command will completely clean your drive by erasing any
existent filesystem.
- Run `create partition primary`. This command will create a new partition.
- Run `active`. This command will active the partition.
- Run `list partition`. This command will show available partition.
- Run `select partition N`, where `N` corresponds to the id of the newly available partition.
- Run `format override quick`. This command will format the partition. You can choose a specific formatting by adding `FS=xx` where `xx` could be `NTFS or FAT or FAT32` after `format`. Example : `format FS=NTFS override quick`
- Run `exit` to quit diskpart.
### OS X

View File

@@ -0,0 +1,178 @@
---
description: Getting started for etcherPro
slug: /
---
# EtcherPro User manual
## 1. Features
![EtcherPro-manual-01](img/etcher-pro-top-view.jpeg)
![EtcherPro-manual-02](img/etcher-pro-slots.jpeg)
![EtcherPro-manual-03](img/etcher-pro-rear-view.jpeg)
| Specifications | |
| --- | --- |
| Voltage in | 100 to 240 V AC - 10A - 50 to 60 Hz |
| Voltage out | 100 to 240 V AC - 10A - 50 to 60 Hz |
| Ports | 16 x USB 3.0, 16 X SD, 16 x mSD |
| Flashing capacity | 16 x targets when using an online image or, 15 x target drives and 1 x local source drive |
| Daisy-chaining | Up to 10 EtcherPro devices supported (160 x targets) |
| Language support | English |
| Software | balenaEtcher on balenaOS with auto-updates |
| Display | 7in RGB touch screen |
| Network connectivity | WiFi 2.4GHz, 5GHz |
| Working temperature | 5°C ~ 30°C |
| Certifications | CE, FCC |
## 2. Getting started
### 2.1 Setup
### Powering up the device
- EtcherPro is supplied with a mains power cable according to your region's plug standards
- On the back of the device, there are two groups of sockets, labelled as **IN** and **OUT**
- Plug the AC power cable to the socket labelled **POWER IN**, and then to the mains outlet
- The device should boot up automatically; wait until you see the Etcher interface show up
- [Warning] Please avoid using adaptors or extension leads, as this may damage the device or cause it to malfunction
### Connecting to WiFi
- The device will prompt you to connect to a local network the first time it boots (you can skip this step if you are not connecting to WiFi)
- Select the network to which you want to connect, type the password and select **OK**
- You can access the WiFi settings by selecting the WiFi icon at the top left corner of the screen
### 2.2 Etcher functions
### Flash from file
Using an image file as source to flash to one or multiple targets
- From the Etcher menu, select **Flash from file**
- Plug a drive that contains the image you would like to flash into the slot with the blue-colored blinking LED
- Select the image you want to flash from the file browser and select **OK**
- Plug at least one drive or device into an available slot. The plugged target(s) will be selected automatically and the LEDs will turn white
- Select **Flash**, to begin the flashing process
- The LED of each slot will first blink purple for flashing and then green for validating
- Once the flashing is complete, the LEDs will turn green for successfully flashed drives, and red for the failed ones
- You may safely unplug the drives when flashing is complete
### Flash from URL
Using an online image file as source to flash to one or multiple targets
- From the Etcher menu, select **Flash from URL**
- Enter the image URL of you would like to flash in the input field, and select **OK**
- Plug at least one drive or device into an available slot. The plugged target(s) will be selected automatically, and the LEDs will turn white
- Select **Flash**, to begin the flashing process
- The LED of each slot will first blink purple for flashing and then green for validating
- Once the flashing is complete, the LEDs will turn green for successfully flashed drives, and red for the failed ones
- You may safely unplug the drives when flashing is complete
### Clone drive
Using a drive as source, and cloning it to multiple drives
- From the Etcher menu, select **Clone drive**
- Plug a drive you would like to clone into the slot with the blue-colored blinking LED
- The drive will be selected automatically and the LED will stop blinking
- Plug at least one drive into an available slot. The plugged targets will be selected automatically, and the LEDs will turn white
- Select **Flash**, to begin the flashing process
- The LED of each slot will first blink purple for flashing and then green for validating
- Once the flashing is complete, the LEDs will turn green for successfully flashed drives, and red for the failed ones
- You may safely unplug the drives when flashing is complete
### Backup drive
Backing up one or multiple drives into another drive
- From the Etcher menu, select **more options**, then **Backup drive**
- Plug one or more drives you would like to backup into the slot(s) with the blue-colored blinking LED
- The drives will be autoselected and the LED will stop blinking
- Select **OK** to move on to the next step
- Plug the backup drive into the slot with the white-colored blinking LED. The plugged target will be automatically selected and the LED will stop blinking
- Select **Flash**, to begin the flashing process
- The LED of the target slot will first blink purple for flashing, and then green for validating
- Once the flashing is complete, the LED will turn green for a successful flash, and red for a failed one
- You may safely unplug the drive when flashing is complete
### Format drive
Formatting one or multiple drives
- From the Etcher menu, select **more options**, then **Format drive**
- Plug one or more drives you would like to format on the slots with the white-colored blinking LEDs
- The drives will be selected automatically, and the LED will stop blinking
- Select **Flash**, to begin the flashing process
- The LED of the target slot will first blink purple for flashing, and then green for validating
- Once the formatting is complete, the LEDs will turn green for successfully formatted drives, and red for the failed ones
- You may safely unplug the drive when formatting is complete
### 2.3 Daisy-chaining (power only)
Connecting up to 10 EtcherPro devices and power them from one socket.
- EtcherPro allows you to chain power (data chaining will be released later). To do this, you will need a male to female IEC C13/C14 power extension lead (min 10A rated) to connect one EtcherPro to another
- Plug the power extension lead to the 'POWER IN' side of the leading EtcherPro, and then to the 'POWER OUT' side of the successive EtcherPro
- The last EtcherPro of the stack should be plugged directly to the mains power socket
### 2.4 Sleep, wake and power off
- EtcherPro is set to automatically go into sleep mode after a few minutes
- You may change this setting by selecting the settings icon on the top right corner of the screen
- You may put the device to sleep manually by selecting the sleep button on the top left corner of the screen
- To wake up your device, just tap anywhere on the screen
- It is not necessary to power off your device, but if you would like to, you may simply unplug the power-in cable
### 3. Safety and handling
WARNING: Make sure you read and follow the safety and handling instructions before using EtcherPro in order to avoid the potential risk of causing damage to the device, electrical shock, fire, or damage to any other property. If EtcherPro gets physically damaged in any way, or you suspect liquid has leaked into the enclosure, unplug the power cable from the socket and avoid using the device before contacting support (pro@etcher.io).
Handling
It is important not to block the air vents on the back and bottom of the device. As such, we suggest setting up EtcherPro on a well supported desk, with plenty of surrounding space to ensure the device is properly ventilated while in use.
Liquid exposure
EtcherPros enclosure is not waterproof. It is important to keep liquids away from the device to avoid spillages. High humidity environments, rain or snow may also cause damage to the device.
Power
EtcherPro does not have an on/off switch. If you would like to power on the device, you need to plug the power cable into the mains socket. If you would like to power off the device, you need to unplug the power cable. Be sure to unplug the power cable from the socket if you suspect either the cable or the device is physically damaged in some way.
For your own protection and protection of the device, EtcherPro comes with a grounded AC power cable which only fits a grounded mains socket. If you dont have a grounded socket installed, you should contact a specialist who can safely install an appropriate grounded socket. Do not attempt to power on the device without a connected grounding wire or with a power cable that does not meet the original specifications.
Repairing
EtcherPro is not meant to be serviced or repaired by the user. If your device has any issues you should contact support (pro@etcher.io). Attempting to disassemble the device will void the warranty, and could also cause injury or harm.
Radio interference
EtcherPro contains components and radios that emit electromagnetic fields. These electromagnetic fields may interfere with medical devices, such as pacemakers and defibrillators. Consult your physician and medical device manufacturer for information specific to your medical device and whether you need to maintain a safe distance of separation between your medical device(s) and EtcherPro. If you suspect EtcherPro is interfering with your medical device, stop using EtcherPro immediately.
EtcherPro emits electromagnetic fields due to the usage of components and radios. These fields can interfere with other devices and potentially cause them to malfunction. If you suspect EtcherPro is interfering with another device, unplug EtcherPro from the power and contact support (pro@etcher.io).
This equipment is not suitable for use in locations where children are likely to be present.
Atmospheric conditions (dust and vapor)
The EtcherPro enclosure is not sealed. Using the device in an environment that has increased amounts of dust, powder, vapors, corrosive substances, or other contaminants can cause malfunction, injury, and/or fire.
### 4. Warranty
**Limited Product Warranty**
Balena warrants that, for a period of one (1) year after the date of shipment, the Products will be free from defects in materials and workmanship under normal use. As Balenas sole liability and Customers sole and exclusive remedy for any breach of the limited warranty set forth herein, Balena will, at its option and expense, repair or replace any Product returned to Customer during the warranty period that does not comply with such warranty, as confirmed by Balena. Replacement Products will be warranted for the remainder of the original warranty period or ninety (90) days, whichever is longer. All Products that are replaced become the property of Balena. Balena will have no obligation to the extent that any failure of a Product to comply with the limited warranty set forth in this limited product warranty results from or is otherwise attributable to: (i) negligence, misuse, or abuse of the Product; (ii) use of the Product other than in accordance with Balenas published specifications or user manual; (iii) modifications, alterations or repairs to the Product made by a party other than Balena or a party authorized by Balena; (iv) any failure by Customer or a third party to comply with environmental and storage requirements for the Product specified by Balena, including, but not limited to, temperature or humidity ranges; or (v) use of the Product in combination with any third-party devices or products that have not been provided or recommended by Balena. The parties agree that Balenas RMA Policy shall apply to Products returned pursuant to this limited product warranty for a breach of warranty.
THE LIMITED WARRANTY SET FORTH HEREIN IS IN LIEU OF, AND BALENA SPECIFICALLY DISCLAIMS, ANY AND ALL OTHER WARRANTIES AND CONDITIONS, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, AND ANY WARRANTIES ARISING OUT OF COURSE OF DEALING OR USAGE OF TRADE. NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED FROM BALENA OR ELSEWHERE, WILL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THESE TERMS.
The limited warranty does not apply to:
- Returned items that failed due to an accident, purchasers abuse, neglect or failure to operate in accordance with instructions provided in this refund policy.
- Returned items that failed due to incorrect voltage or improper wiring.
- Returned items that failed due to rain, excessive humidity, corrosive environments, or other contaminants.
- Any item damaged in shipment.
- Any product failure caused by installing or operating product under conditions not in accordance with installation and operation guidelines, or damaged by contact with tools or surroundings.
- Returned items with cosmetic defects that do not interfere with product functionality.
- Returned items that are incomplete or defaced.
- Returned items with a different serial number from what was authorized for return.
- Freight damaged items. If your shipment arrives damaged, you must note the damage on the carrier's delivery record in accordance with the carrier's policy, save the merchandise in the original box and packing it arrived in, and arrange for a carrier inspection of damaged merchandise.
**Initiating a warranty claim**
To initiate a warranty claim, please contact support (pro@etcher.io) to receive a copy of the RMA form. When filling the form, make sure you describe the issue as accurately as possible since it will be used as a basis for determining if the warranty claim is valid or not.
After Balenas evaluation of the return item, Warranty or Out-of-Warranty status will be determined. If the description of the problem is the same as listed on Page 1 of the RMA form, the product will be repaired or replaced under warranty at no charge and shipped back, prepaid, to the customer.
If the description of the problem is different from the problem listed on Page 1 of the RMA form we will contact the customer. At such time, the customer must issue a written confirmation to proceed with the repair(s), agree to cover the costs of the repair and return freight, or authorize the product to be shipped back as is, at the customers expense. Failure to obtain written confirmation within thirty (30) days of notification will result in the product being returned as is, at the customers expense.
If the product has no identifiable problem, we reserve the right to charge for testing and return shipping costs.
For any product returned to balena for reasons other than warranty, a 20% restocking fee and round-trip shipping costs will be deducted from the credit refund. All returned items must be in their original box or crating and must include all packing material, manuals, and accessories.

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
docs/static/img/etcher-pro-slots.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

BIN
docs/static/img/etcher-pro-top-view.jpeg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

BIN
docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/static/img/logo.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -1,10 +1,9 @@
appId: io.balena.etcher
copyright: Copyright 2016-2020 Balena Ltd
copyright: Copyright 2016-2021 Balena Ltd
productName: balenaEtcher
npmRebuild: true
nodeGypRebuild: false
publish: null
beforeBuild: "./beforeBuild.js"
afterPack: "./afterPack.js"
asar: false
files:
@@ -16,6 +15,7 @@ mac:
hardenedRuntime: true
entitlements: "entitlements.mac.plist"
entitlementsInherit: "entitlements.mac.plist"
artifactName: "${productName}-${version}.${ext}"
dmg:
background: assets/dmg/background.tiff
icon: assets/icon.icns
@@ -87,6 +87,7 @@ deb:
- libxss1
- libxtst6
- polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1
afterInstall: "./after-install.tpl"
rpm:
depends:
- util-linux

View File

@@ -28,7 +28,6 @@ import * as EXIT_CODES from '../../shared/exit-codes';
import * as messages from '../../shared/messages';
import * as availableDrives from './models/available-drives';
import * as flashState from './models/flash-state';
import { init as ledsInit } from './models/leds';
import { deselectImage, getImage } from './models/selection-state';
import * as settings from './models/settings';
import { Actions, observe, store } from './models/store';
@@ -38,6 +37,7 @@ import * as exceptionReporter from './modules/exception-reporter';
import * as osDialog from './os/dialog';
import * as windowProgress from './os/window-progress';
import MainPage from './pages/main/MainPage';
import './css/main.css';
window.addEventListener(
'unhandledrejection',
@@ -216,8 +216,7 @@ function prepareDrive(drive: Drive) {
disabled: true,
icon: 'warning',
size: null,
link:
'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md',
link: 'https://www.raspberrypi.com/documentation/computers/compute-module.html#flashing-the-compute-module-emmc',
linkCTA: 'Install',
linkTitle: 'Install missing drivers',
linkMessage: outdent`
@@ -334,13 +333,19 @@ window.addEventListener('beforeunload', async (event) => {
flashingWorkflowUuid,
});
popupExists = false;
} catch (error) {
} catch (error: any) {
exceptionReporter.report(error);
}
});
async function main() {
await ledsInit();
export async function main() {
try {
const { init: ledsInit } = require('./models/leds');
await ledsInit();
} catch (error: any) {
exceptionReporter.report(error);
}
ReactDOM.render(
React.createElement(MainPage),
document.getElementById('main'),
@@ -356,5 +361,3 @@ async function main() {
},
);
}
main();

View File

@@ -43,6 +43,7 @@ import {
} from '../../styled-components';
import { SourceMetadata } from '../source-selector/source-selector';
import { middleEllipsis } from '../../utils/middle-ellipsis';
interface UsbbootDrive extends sourceDestination.UsbbootDrive {
progress: number;
@@ -136,17 +137,18 @@ const InitProgress = styled(
`;
export interface DriveSelectorProps
extends Omit<ModalProps, 'done' | 'cancel'> {
extends Omit<ModalProps, 'done' | 'cancel' | 'onSelect'> {
write: boolean;
multipleSelection: boolean;
showWarnings?: boolean;
cancel: () => void;
cancel: (drives: DrivelistDrive[]) => void;
done: (drives: DrivelistDrive[]) => void;
titleLabel: string;
emptyListLabel: string;
emptyListIcon: JSX.Element;
selectedList?: DrivelistDrive[];
updateSelectedList?: () => DrivelistDrive[];
onSelect?: (drive: DrivelistDrive) => void;
}
interface DriveSelectorState {
@@ -167,12 +169,14 @@ export class DriveSelector extends React.Component<
> {
private unsubscribe: (() => void) | undefined;
tableColumns: Array<TableColumn<Drive>>;
originalList: DrivelistDrive[];
constructor(props: DriveSelectorProps) {
super(props);
const defaultMissingDriversModalState: { drive?: DriverlessDrive } = {};
const selectedList = this.props.selectedList || [];
this.originalList = [...(this.props.selectedList || [])];
this.state = {
drives: getDrives(),
@@ -199,7 +203,9 @@ export class DriveSelector extends React.Component<
fill={drive.isSystem ? '#fca321' : '#8f9297'}
/>
)}
<Txt ml={(hasWarnings && 8) || 0}>{description}</Txt>
<Txt ml={(hasWarnings && 8) || 0}>
{middleEllipsis(description, 32)}
</Txt>
</Flex>
);
}
@@ -259,7 +265,7 @@ export class DriveSelector extends React.Component<
return (
isUsbbootDrive(drive) ||
isDriverlessDrive(drive) ||
!isDriveValid(drive, image) ||
!isDriveValid(drive, image, this.props.write) ||
(this.props.write && drive.isReadOnly)
);
}
@@ -348,16 +354,6 @@ export class DriveSelector extends React.Component<
}
}
private deselectingAll(rows: DrivelistDrive[]) {
return (
rows.length > 0 &&
rows.length === this.state.selectedList.length &&
this.state.selectedList.every(
(d) => rows.findIndex((r) => d.device === r.device) > -1,
)
);
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
const drives = getDrives();
@@ -383,8 +379,8 @@ export class DriveSelector extends React.Component<
const displayedDrives = this.getDisplayedDrives(drives);
const disabledDrives = this.getDisabledDrives(drives, image);
const numberOfSystemDrives = drives.filter(isSystemDrive).length;
const numberOfDisplayedSystemDrives = displayedDrives.filter(isSystemDrive)
.length;
const numberOfDisplayedSystemDrives =
displayedDrives.filter(isSystemDrive).length;
const numberOfHiddenSystemDrives =
numberOfSystemDrives - numberOfDisplayedSystemDrives;
const hasSystemDrives = selectedList.filter(isSystemDrive).length;
@@ -408,7 +404,7 @@ export class DriveSelector extends React.Component<
</Flex>
}
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
cancel={cancel}
cancel={() => cancel(this.originalList)}
done={() => done(selectedList)}
action={`Select (${selectedList.length})`}
primaryButtonProps={{
@@ -448,14 +444,34 @@ export class DriveSelector extends React.Component<
onCheck={(rows: Drive[]) => {
let newSelection = rows.filter(isDrivelistDrive);
if (this.props.multipleSelection) {
if (this.deselectingAll(newSelection)) {
if (rows.length === 0) {
newSelection = [];
}
const deselecting = selectedList.filter(
(selected) =>
newSelection.filter(
(row) => row.device === selected.device,
).length === 0,
);
const selecting = newSelection.filter(
(row) =>
selectedList.filter(
(selected) => row.device === selected.device,
).length === 0,
);
deselecting.concat(selecting).forEach((row) => {
if (this.props.onSelect) {
this.props.onSelect(row);
}
});
this.setState({
selectedList: newSelection,
});
return;
}
if (this.props.onSelect) {
this.props.onSelect(newSelection[newSelection.length - 1]);
}
this.setState({
selectedList: newSelection.slice(newSelection.length - 1),
});
@@ -467,6 +483,9 @@ export class DriveSelector extends React.Component<
) {
return;
}
if (this.props.onSelect) {
this.props.onSelect(row);
}
const index = selectedList.findIndex(
(d) => d.device === row.device,
);
@@ -515,7 +534,7 @@ export class DriveSelector extends React.Component<
if (missingDriversModal.drive !== undefined) {
openExternal(missingDriversModal.drive.link);
}
} catch (error) {
} catch (error: any) {
logException(error);
} finally {
this.setState({ missingDriversModal: {} });

View File

@@ -59,13 +59,8 @@ function FinishPage({ goToMain }: { goToMain: () => void }) {
).map(([, error]: [string, FlashError]) => ({
...error,
}));
const {
averageSpeed,
blockmappedSize,
bytesWritten,
failed,
size,
} = flashState.getFlashState();
const { averageSpeed, blockmappedSize, bytesWritten, failed, size } =
flashState.getFlashState();
const {
skip,
results = {

View File

@@ -17,7 +17,7 @@
import GithubSvg from '@fortawesome/fontawesome-free/svgs/brands/github.svg';
import * as _ from 'lodash';
import * as React from 'react';
import { Flex, Checkbox, Txt } from 'rendition';
import { Box, Checkbox, Flex, TextWithCopy, Txt } from 'rendition';
import { version, packageType } from '../../../../../package.json';
import * as settings from '../../models/settings';
@@ -50,6 +50,14 @@ interface SettingsModalProps {
toggleModal: (value: boolean) => void;
}
const UUID = process.env.BALENA_DEVICE_UUID;
const InfoBox = (props: any) => (
<Box fontSize={14}>
<Txt>{props.label}</Txt>
<TextWithCopy code text={props.value} copy={props.value} />
</Box>
);
export function SettingsModal({ toggleModal }: SettingsModalProps) {
const [settingsList, setCurrentSettingsList] = React.useState<Setting[]>([]);
React.useEffect(() => {
@@ -103,6 +111,12 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
</Flex>
);
})}
{UUID !== undefined && (
<Flex flexDirection="column">
<Txt fontSize={24}>System Information</Txt>
<InfoBox label="UUID" value={UUID.substr(0, 7)} />
</Flex>
)}
<Flex
mt={18}
alignItems="center"

View File

@@ -18,6 +18,8 @@ import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg';
import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg';
import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
import { sourceDestination } from 'etcher-sdk';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import * as _ from 'lodash';
@@ -33,6 +35,7 @@ import {
Card as BaseCard,
Input,
Spinner,
Link,
} from 'rendition';
import styled from 'styled-components';
@@ -61,6 +64,8 @@ import ImageSvg from '../../../assets/image.svg';
import SrcSvg from '../../../assets/src.svg';
import { DriveSelector } from '../drive-selector/drive-selector';
import { DrivelistDrive } from '../../../../shared/drive-constraints';
import axios, { AxiosRequestConfig } from 'axios';
import { isJson } from '../../../../shared/utils';
const recentUrlImagesKey = 'recentUrlImages';
@@ -72,7 +77,7 @@ function normalizeRecentUrlImages(urls: any[]): URL[] {
.map((url) => {
try {
return new URL(url);
} catch (error) {
} catch (error: any) {
// Invalid URL, skip
}
})
@@ -133,12 +138,15 @@ const URLSelector = ({
done,
cancel,
}: {
done: (imageURL: string) => void;
done: (imageURL: string, auth?: Authentication) => void;
cancel: () => void;
}) => {
const [imageURL, setImageURL] = React.useState('');
const [recentImages, setRecentImages] = React.useState<URL[]>([]);
const [loading, setLoading] = React.useState(false);
const [showBasicAuth, setShowBasicAuth] = React.useState(false);
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');
React.useEffect(() => {
const fetchRecentUrlImages = async () => {
const recentUrlImages: URL[] = await getRecentUrlImages();
@@ -161,11 +169,12 @@ const URLSelector = ({
imageURL,
]);
setRecentUrlImages(normalizedRecentUrls);
await done(imageURL);
const auth = username ? { username, password } : undefined;
await done(imageURL, auth);
}}
>
<Flex flexDirection="column">
<Flex style={{ width: '100%' }} flexDirection="column">
<Flex mb={15} style={{ width: '100%' }} flexDirection="column">
<Txt mb="10px" fontSize="24px">
Use Image URL
</Txt>
@@ -177,6 +186,49 @@ const URLSelector = ({
setImageURL(evt.target.value)
}
/>
<Link
mt={15}
mb={15}
fontSize="14px"
onClick={() => {
if (showBasicAuth) {
setUsername('');
setPassword('');
}
setShowBasicAuth(!showBasicAuth);
}}
>
<Flex alignItems="center">
{showBasicAuth && (
<ChevronDownSvg height="1em" fill="currentColor" />
)}
{!showBasicAuth && (
<ChevronRightSvg height="1em" fill="currentColor" />
)}
<Txt ml={8}>Authentication</Txt>
</Flex>
</Link>
{showBasicAuth && (
<React.Fragment>
<Input
mb={15}
value={username}
placeholder="Enter username"
type="text"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setUsername(evt.target.value)
}
/>
<Input
value={password}
placeholder="Enter password"
type="password"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setPassword(evt.target.value)
}
/>
</React.Fragment>
)}
</Flex>
{recentImages.length > 0 && (
<Flex flexDirection="column" height="78.6%">
@@ -263,6 +315,7 @@ export interface SourceMetadata extends sourceDestination.Metadata {
drive?: DrivelistDrive;
extension?: string;
archiveExtension?: string;
auth?: Authentication;
}
interface SourceSelectorProps {
@@ -279,6 +332,12 @@ interface SourceSelectorState {
showDriveSelector: boolean;
defaultFlowActive: boolean;
imageSelectorOpen: boolean;
imageLoading: boolean;
}
interface Authentication {
username: string;
password: string;
}
export class SourceSelector extends React.Component<
@@ -297,6 +356,7 @@ export class SourceSelector extends React.Component<
showDriveSelector: false,
defaultFlowActive: true,
imageSelectorOpen: false,
imageLoading: false,
};
// Bind `this` since it's used in an event's callback
@@ -317,25 +377,52 @@ export class SourceSelector extends React.Component<
}
private async onSelectImage(_event: IpcRendererEvent, imagePath: string) {
this.setState({ imageLoading: true });
await this.selectSource(
imagePath,
isURL(imagePath) ? sourceDestination.Http : sourceDestination.File,
isURL(this.normalizeImagePath(imagePath))
? sourceDestination.Http
: sourceDestination.File,
).promise;
this.setState({ imageLoading: false });
}
private async createSource(selected: string, SourceType: Source) {
private async createSource(
selected: string,
SourceType: Source,
auth?: Authentication,
) {
try {
selected = await replaceWindowsNetworkDriveLetter(selected);
} catch (error) {
} catch (error: any) {
analytics.logException(error);
}
if (isJson(decodeURIComponent(selected))) {
const config: AxiosRequestConfig = JSON.parse(
decodeURIComponent(selected),
);
return new sourceDestination.Http({
url: config.url!,
axiosInstance: axios.create(_.omit(config, ['url'])),
});
}
if (SourceType === sourceDestination.File) {
return new sourceDestination.File({
path: selected,
});
}
return new sourceDestination.Http({ url: selected });
return new sourceDestination.Http({ url: selected, auth });
}
public normalizeImagePath(imgPath: string) {
const decodedPath = decodeURIComponent(imgPath);
if (isJson(decodedPath)) {
return JSON.parse(decodedPath).url ?? decodedPath;
}
return decodedPath;
}
private reselectSource() {
@@ -349,6 +436,7 @@ export class SourceSelector extends React.Component<
private selectSource(
selected: string | DrivelistDrive,
SourceType: Source,
auth?: Authentication,
): { promise: Promise<void>; cancel: () => void } {
let cancelled = false;
return {
@@ -360,7 +448,10 @@ export class SourceSelector extends React.Component<
let source;
let metadata: SourceMetadata | undefined;
if (isString(selected)) {
if (SourceType === sourceDestination.Http && !isURL(selected)) {
if (
SourceType === sourceDestination.Http &&
!isURL(this.normalizeImagePath(selected))
) {
this.handleError(
'Unsupported protocol',
selected,
@@ -378,7 +469,7 @@ export class SourceSelector extends React.Component<
},
});
}
source = await this.createSource(selected, SourceType);
source = await this.createSource(selected, SourceType, auth);
if (cancelled) {
return;
@@ -395,7 +486,7 @@ export class SourceSelector extends React.Component<
}
metadata.SourceType = SourceType;
if (!metadata.hasMBR) {
if (!metadata.hasMBR && this.state.warning === null) {
analytics.logEvent('Missing partition table', { metadata });
this.setState({
warning: {
@@ -404,7 +495,7 @@ export class SourceSelector extends React.Component<
},
});
}
} catch (error) {
} catch (error: any) {
this.handleError(
'Error opening source',
sourcePath,
@@ -414,7 +505,7 @@ export class SourceSelector extends React.Component<
} finally {
try {
await source.close();
} catch (error) {
} catch (error: any) {
// Noop
}
}
@@ -439,6 +530,7 @@ export class SourceSelector extends React.Component<
}
if (metadata !== undefined) {
metadata.auth = auth;
selectionState.selectSource(metadata);
analytics.logEvent('Select image', {
// An easy way so we can quickly identify if we're making use of
@@ -504,7 +596,7 @@ export class SourceSelector extends React.Component<
return;
}
await this.selectSource(imagePath, sourceDestination.File).promise;
} catch (error) {
} catch (error: any) {
exceptionReporter.report(error);
} finally {
this.setState({ imageSelectorOpen: false });
@@ -558,10 +650,21 @@ export class SourceSelector extends React.Component<
this.setState({ defaultFlowActive });
}
private closeModal() {
this.setState({
showDriveSelector: false,
});
}
// TODO add a visual change when dragging a file over the selector
public render() {
const { flashing } = this.props;
const { showImageDetails, showURLSelector, showDriveSelector } = this.state;
const {
showImageDetails,
showURLSelector,
showDriveSelector,
imageLoading,
} = this.state;
const selectionImage = selectionState.getImage();
let image: SourceMetadata | DrivelistDrive =
selectionImage !== undefined ? selectionImage : ({} as SourceMetadata);
@@ -599,16 +702,18 @@ export class SourceSelector extends React.Component<
}}
/>
{selectionImage !== undefined ? (
{selectionImage !== undefined || imageLoading ? (
<>
<StepNameButton
plain
onClick={() => this.showSelectedImageDetails()}
tooltip={imageName || imageBasename}
>
{middleEllipsis(imageName || imageBasename, 20)}
<Spinner show={imageLoading}>
{middleEllipsis(imageName || imageBasename, 20)}
</Spinner>
</StepNameButton>
{!flashing && (
{!flashing && !imageLoading && (
<ChangeButton
plain
mb={14}
@@ -617,7 +722,7 @@ export class SourceSelector extends React.Component<
Remove
</ChangeButton>
)}
{!_.isNil(imageSize) && (
{!_.isNil(imageSize) && !imageLoading && (
<DetailsText>{prettyBytes(imageSize)}</DetailsText>
)}
</>
@@ -661,6 +766,9 @@ export class SourceSelector extends React.Component<
{this.state.warning != null && (
<SmallModal
style={{
boxShadow: '0 3px 7px rgba(0, 0, 0, 0.3)',
}}
titleElement={
<span>
<ExclamationTriangleSvg fill="#fca321" height="1em" />{' '}
@@ -709,7 +817,7 @@ export class SourceSelector extends React.Component<
showURLSelector: false,
});
}}
done={async (imageURL: string) => {
done={async (imageURL: string, auth?: Authentication) => {
// Avoid analytics and selection state changes
// if no file was resolved from the dialog.
if (!imageURL) {
@@ -719,6 +827,7 @@ export class SourceSelector extends React.Component<
({ promise, cancel: cancelURLSelection } = this.selectSource(
imageURL,
sourceDestination.Http,
auth,
));
await promise;
}
@@ -736,21 +845,30 @@ export class SourceSelector extends React.Component<
titleLabel="Select source"
emptyListLabel="Plug a source drive"
emptyListIcon={<SrcSvg width="40px" />}
cancel={() => {
this.setState({
showDriveSelector: false,
});
}}
done={async (drives: DrivelistDrive[]) => {
if (drives.length) {
await this.selectSource(
drives[0],
sourceDestination.BlockDevice,
);
cancel={(originalList) => {
if (originalList.length) {
const originalSource = originalList[0];
if (selectionImage?.drive?.device !== originalSource.device) {
this.selectSource(
originalSource,
sourceDestination.BlockDevice,
);
}
} else {
selectionState.deselectImage();
}
this.closeModal();
}}
done={() => this.closeModal()}
onSelect={(drive) => {
if (drive) {
if (
selectionState.getImage()?.drive?.device === drive?.device
) {
return selectionState.deselectImage();
}
this.selectSource(drive, sourceDestination.BlockDevice);
}
this.setState({
showDriveSelector: false,
});
}}
/>
)}

View File

@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { scanner } from 'etcher-sdk';
import * as React from 'react';
import { Flex, Txt } from 'rendition';
@@ -28,6 +27,7 @@ import {
getSelectedDrives,
deselectDrive,
selectDrive,
deselectAllDrives,
} from '../../models/selection-state';
import { observe } from '../../models/store';
import * as analytics from '../../modules/analytics';
@@ -36,6 +36,7 @@ import { TargetSelectorButton } from './target-selector-button';
import TgtSvg from '../../../assets/tgt.svg';
import DriveSvg from '../../../assets/drive.svg';
import { warning } from '../../../../shared/messages';
import { DrivelistDrive } from '../../../../shared/drive-constraints';
export const getDriveListLabel = () => {
return getSelectedDrives()
@@ -69,9 +70,7 @@ export const TargetSelectorModal = (
/>
);
export const selectAllTargets = (
modalTargets: scanner.adapters.DrivelistDrive[],
) => {
export const selectAllTargets = (modalTargets: DrivelistDrive[]) => {
const selectedDrivesFromState = getSelectedDrives();
const deselected = selectedDrivesFromState.filter(
(drive) =>
@@ -113,9 +112,8 @@ export const TargetSelector = ({
const [{ driveListLabel, targets }, setStateSlice] = React.useState(
getDriveSelectionStateSlice(),
);
const [showTargetSelectorModal, setShowTargetSelectorModal] = React.useState(
false,
);
const [showTargetSelectorModal, setShowTargetSelectorModal] =
React.useState(false);
React.useEffect(() => {
return observe(() => {
@@ -164,11 +162,30 @@ export const TargetSelector = ({
{showTargetSelectorModal && (
<TargetSelectorModal
write={true}
cancel={() => setShowTargetSelectorModal(false)}
done={(modalTargets) => {
selectAllTargets(modalTargets);
cancel={(originalList) => {
if (originalList.length) {
selectAllTargets(originalList);
} else {
deselectAllDrives();
}
setShowTargetSelectorModal(false);
}}
done={(modalTargets) => {
if (modalTargets.length === 0) {
deselectAllDrives();
}
setShowTargetSelectorModal(false);
}}
onSelect={(drive) => {
if (
getSelectedDrives().find(
(selectedDrive) => selectedDrive.device === drive.device,
)
) {
return deselectDrive(drive.device);
}
selectDrive(drive.device);
}}
/>
)}
</Flex>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>balenaEtcher</title>
<link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>
<main id="main"></main>
<script src="http://localhost:3030/gui.js"></script>
</body>
</html>

View File

@@ -2,7 +2,7 @@
<html>
<head>
<meta charset="UTF-8">
<title>Etcher</title>
<title>balenaEtcher</title>
<link rel="stylesheet" type="text/css" href="index.css">
</head>
<body>

View File

@@ -17,6 +17,7 @@
import * as electron from 'electron';
import * as sdk from 'etcher-sdk';
import * as _ from 'lodash';
import { DrivelistDrive } from '../../../shared/drive-constraints';
import { bytesToMegabytes } from '../../../shared/units';
import { Actions, store } from './store';
@@ -84,7 +85,7 @@ export function addFailedDeviceError({
device,
error,
}: {
device: sdk.scanner.adapters.DrivelistDrive;
device: DrivelistDrive;
error: Error;
}) {
const failedDeviceErrorsMap = new Map(

View File

@@ -18,29 +18,24 @@ import * as _ from 'lodash';
import { Animator, AnimationFunction, Color, RGBLed } from 'sys-class-rgb-led';
import {
isSourceDrive,
DrivelistDrive,
isSourceDrive,
} from '../../../shared/drive-constraints';
import { getDrives } from './available-drives';
import { getSelectedDrives } from './selection-state';
import * as settings from './settings';
import { DEFAULT_STATE, observe } from './store';
import { observe, store } from './store';
const leds: Map<string, RGBLed> = new Map();
const animator = new Animator([], 10);
const red: Color = [0.59, 0, 0];
const green: Color = [0, 0.59, 0];
const blue: Color = [0, 0, 0.59];
const white: Color = [0.04, 0.04, 0.04];
const black: Color = [0, 0, 0];
const purple: Color = [0.117, 0, 0.196];
function createAnimationFunction(
intensityFunction: (t: number) => number,
color: Color,
): AnimationFunction {
return (t: number): Color => {
const intensity = intensityFunction(t);
return color.map((v) => v * intensity) as Color;
return color.map((v: number) => v * intensity) as Color;
};
}
@@ -52,13 +47,27 @@ function one(_t: number) {
return 1;
}
const blinkGreen = createAnimationFunction(blink, green);
const blinkPurple = createAnimationFunction(blink, purple);
const staticRed = createAnimationFunction(one, red);
const staticGreen = createAnimationFunction(one, green);
const staticBlue = createAnimationFunction(one, blue);
const staticWhite = createAnimationFunction(one, white);
const staticBlack = createAnimationFunction(one, black);
type LEDColors = {
green: Color;
purple: Color;
red: Color;
blue: Color;
white: Color;
black: Color;
};
type LEDAnimationFunctions = {
blinkGreen: AnimationFunction;
blinkPurple: AnimationFunction;
staticRed: AnimationFunction;
staticGreen: AnimationFunction;
staticBlue: AnimationFunction;
staticWhite: AnimationFunction;
staticBlack: AnimationFunction;
};
let ledColors: LEDColors;
let ledAnimationFunctions: LEDAnimationFunctions;
interface LedsState {
step: 'main' | 'flashing' | 'verifying' | 'finish';
@@ -130,65 +139,77 @@ export function updateLeds({
if (sourceDrive !== undefined) {
if (plugged.has(sourceDrive)) {
plugged.delete(sourceDrive);
mapping.push(setLeds(staticBlue, new Set([sourceDrive])));
mapping.push(
setLeds(ledAnimationFunctions.staticBlue, new Set([sourceDrive])),
);
}
}
if (step === 'main') {
mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])),
setLeds(staticWhite, new Set([...selectedOk, ...selectedFailed])),
setLeds(
ledAnimationFunctions.staticBlack,
new Set([...unplugged, ...plugged]),
),
setLeds(
ledAnimationFunctions.staticWhite,
new Set([...selectedOk, ...selectedFailed]),
),
);
} else if (step === 'flashing') {
mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])),
setLeds(blinkPurple, selectedOk),
setLeds(staticRed, selectedFailed),
setLeds(
ledAnimationFunctions.staticBlack,
new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.blinkPurple, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
);
} else if (step === 'verifying') {
mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])),
setLeds(blinkGreen, selectedOk),
setLeds(staticRed, selectedFailed),
setLeds(
ledAnimationFunctions.staticBlack,
new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.blinkGreen, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
);
} else if (step === 'finish') {
mapping.push(
setLeds(staticBlack, new Set([...unplugged, ...plugged])),
setLeds(staticGreen, selectedOk),
setLeds(staticRed, selectedFailed),
setLeds(
ledAnimationFunctions.staticBlack,
new Set([...unplugged, ...plugged]),
),
setLeds(ledAnimationFunctions.staticGreen, selectedOk),
setLeds(ledAnimationFunctions.staticRed, selectedFailed),
);
}
animator.mapping = mapping;
}
interface DeviceFromState {
devicePath?: string;
device: string;
}
let ledsState: LedsState | undefined;
function stateObserver(state: typeof DEFAULT_STATE) {
const s = state.toJS();
function stateObserver() {
const s = store.getState().toJS();
let step: 'main' | 'flashing' | 'verifying' | 'finish';
if (s.isFlashing) {
step = s.flashState.type;
} else {
step = s.lastAverageFlashingSpeed == null ? 'main' : 'finish';
}
const availableDrives = s.availableDrives.filter(
(d: DeviceFromState) => d.devicePath,
const availableDrives = getDrives().filter(
(d: DrivelistDrive) => d.devicePath,
);
const sourceDrivePath = availableDrives.filter((d: DrivelistDrive) =>
isSourceDrive(d, s.selection.image),
)[0]?.devicePath;
const availableDrivesPaths = availableDrives.map(
(d: DeviceFromState) => d.devicePath,
(d: DrivelistDrive) => d.devicePath,
);
let selectedDrivesPaths: string[];
if (step === 'main') {
selectedDrivesPaths = availableDrives
.filter((d: DrivelistDrive) => s.selection.devices.includes(d.device))
.map((d: DrivelistDrive) => d.devicePath);
selectedDrivesPaths = getSelectedDrives()
.filter((drive) => drive.devicePath !== null)
.map((drive) => drive.devicePath) as string[];
} else {
selectedDrivesPaths = s.devicePaths;
}
@@ -201,7 +222,7 @@ function stateObserver(state: typeof DEFAULT_STATE) {
availableDrives: availableDrivesPaths,
selectedDrives: selectedDrivesPaths,
failedDrives: failedDevicePaths,
};
} as LedsState;
if (!_.isEqual(newLedsState, ledsState)) {
updateLeds(newLedsState);
ledsState = newLedsState;
@@ -224,6 +245,16 @@ export async function init(): Promise<void> {
for (const [drivePath, ledsNames] of Object.entries(ledsMapping)) {
leds.set('/dev/disk/by-path/' + drivePath, new RGBLed(ledsNames));
}
ledColors = (await settings.get('ledColors')) || {};
ledAnimationFunctions = {
blinkGreen: createAnimationFunction(blink, ledColors['green']),
blinkPurple: createAnimationFunction(blink, ledColors['purple']),
staticRed: createAnimationFunction(one, ledColors['red']),
staticGreen: createAnimationFunction(one, ledColors['green']),
staticBlue: createAnimationFunction(one, ledColors['blue']),
staticWhite: createAnimationFunction(one, ledColors['white']),
staticBlack: createAnimationFunction(one, ledColors['black']),
};
observe(_.debounce(stateObserver, 1000, { maxWait: 1000 }));
}
}

View File

@@ -51,7 +51,7 @@ async function readConfigFile(filename: string): Promise<_.Dictionary<any>> {
let contents = '{}';
try {
contents = await fs.readFile(filename, { encoding: 'utf8' });
} catch (error) {
} catch (error: any) {
// noop
}
try {
@@ -104,7 +104,7 @@ export async function set(
settings[key] = value;
try {
await writeConfigFileFn(CONFIG_PATH, settings);
} catch (error) {
} catch (error: any) {
// Revert to previous value if persisting settings failed
settings[key] = previousValue;
throw error;

View File

@@ -102,10 +102,9 @@ function validateMixpanelConfig(config: {
* This function sends the debug message to product analytics services.
*/
export function logEvent(message: string, data: _.Dictionary<any> = {}) {
const {
applicationSessionUuid,
flashingWorkflowUuid,
} = store.getState().toJS();
const { applicationSessionUuid, flashingWorkflowUuid } = store
.getState()
.toJS();
resinCorvus.logEvent(message, {
...data,
sample: mixpanelSample,

View File

@@ -15,10 +15,15 @@
*/
import * as sdk from 'etcher-sdk';
import {
Adapter,
BlockDeviceAdapter,
UsbbootDeviceAdapter,
} from 'etcher-sdk/build/scanner/adapters';
import { geteuid, platform } from 'process';
const adapters: sdk.scanner.adapters.Adapter[] = [
new sdk.scanner.adapters.BlockDeviceAdapter({
const adapters: Adapter[] = [
new BlockDeviceAdapter({
includeSystemDrives: () => true,
}),
];
@@ -26,14 +31,15 @@ const adapters: sdk.scanner.adapters.Adapter[] = [
// Can't use permissions.isElevated() here as it returns a promise and we need to set
// module.exports = scanner right now.
if (platform !== 'linux' || geteuid() === 0) {
adapters.push(new sdk.scanner.adapters.UsbbootDeviceAdapter());
adapters.push(new UsbbootDeviceAdapter());
}
if (
platform === 'win32' &&
sdk.scanner.adapters.DriverlessDeviceAdapter !== undefined
) {
adapters.push(new sdk.scanner.adapters.DriverlessDeviceAdapter());
if (platform === 'win32') {
const {
DriverlessDeviceAdapter: driverless,
// tslint:disable-next-line:no-var-requires
} = require('etcher-sdk/build/scanner/adapters/driverless');
adapters.push(new driverless());
}
export const scanner = new sdk.scanner.Scanner(adapters);

View File

@@ -15,7 +15,6 @@
*/
import { Drive as DrivelistDrive } from 'drivelist';
import * as electron from 'electron';
import * as sdk from 'etcher-sdk';
import { Dictionary } from 'lodash';
import * as ipc from 'node-ipc';
@@ -25,6 +24,7 @@ import * as path from 'path';
import * as packageJSON from '../../../../package.json';
import * as errors from '../../../shared/errors';
import * as permissions from '../../../shared/permissions';
import { getAppPath } from '../../../shared/utils';
import { SourceMetadata } from '../components/source-selector/source-selector';
import * as flashState from '../models/flash-state';
import * as selectionState from '../models/selection-state';
@@ -93,11 +93,7 @@ function terminateServer() {
}
function writerArgv(): string[] {
let entryPoint = path.join(
electron.remote.app.getAppPath(),
'generated',
'child-writer.js',
);
let entryPoint = path.join(getAppPath(), 'generated', 'child-writer.js');
// AppImages run over FUSE, so the files inside the mount point
// can only be accessed by the user that mounted the AppImage.
// This means we can't re-spawn Etcher as root from the same
@@ -222,7 +218,7 @@ async function performWrite(
});
flashResults.cancelled = cancelled || results.cancelled;
flashResults.skip = skip;
} catch (error) {
} catch (error: any) {
// This happens when the child is killed using SIGKILL
const SIGKILL_EXIT_CODE = 137;
if (error.code === SIGKILL_EXIT_CODE) {
@@ -291,7 +287,7 @@ export async function flash(
try {
const result = await write(image, drives, flashState.setProgressState);
await flashState.unsetFlashingFlag(result);
} catch (error) {
} catch (error: any) {
await flashState.unsetFlashingFlag({
cancelled: false,
errorCode: error.code,
@@ -353,7 +349,7 @@ export async function cancel(type: string) {
if (socket !== undefined) {
ipc.server.emit(socket, status);
}
} catch (error) {
} catch (error: any) {
analytics.logException(error);
}
}

View File

@@ -27,7 +27,7 @@ async function mountSourceDrive() {
if (sourceDrivePath) {
try {
await electron.ipcRenderer.invoke('mount-drive', sourceDrivePath);
} catch (error) {
} catch (error: any) {
// noop
}
}

View File

@@ -117,7 +117,7 @@ async function flashImageToDrive(
}
goToSuccess();
}
} catch (error) {
} catch (error: any) {
notifyFailure(iconPath, basename, drives);
let errorMessage = getErrorMessageFromCode(error.code);
if (!errorMessage) {

View File

@@ -276,9 +276,9 @@ export class MainPage extends React.Component<
style={{
// Allow window to be dragged from header
// @ts-ignore
'-webkit-app-region': 'drag',
WebkitAppRegion: 'drag',
position: 'relative',
zIndex: 1,
zIndex: 2,
}}
>
<Flex width="100%" />
@@ -304,7 +304,7 @@ export class MainPage extends React.Component<
onClick={() => this.setState({ hideSettings: false })}
style={{
// Make touch events click instead of dragging
'-webkit-app-region': 'no-drag',
WebkitAppRegion: 'no-drag',
}}
/>
{!settings.getSync('disableExternalLinks') && (
@@ -319,7 +319,7 @@ export class MainPage extends React.Component<
tabIndex={6}
style={{
// Make touch events click instead of dragging
'-webkit-app-region': 'no-drag',
WebkitAppRegion: 'no-drag',
}}
/>
)}

10
lib/gui/app/renderer.ts Normal file
View File

@@ -0,0 +1,10 @@
// @ts-nocheck
import { main } from './app';
if (module.hot) {
module.hot.accept('./app', () => {
main();
});
}
main();

View File

@@ -43,7 +43,7 @@ async function checkForUpdates(interval: number) {
const release = await autoUpdater.checkForUpdates();
const isOutdated =
semver.compare(release.updateInfo.version, version) > 0;
const shouldUpdate = release.updateInfo.stagingPercentage || 0 > 0;
const shouldUpdate = release.updateInfo.stagingPercentage !== 0; // undefinded (default) means 100%
if (shouldUpdate && isOutdated) {
await autoUpdater.downloadUpdate();
packageUpdated = true;
@@ -97,6 +97,7 @@ const sourceSelectorReady = new Promise((resolve) => {
async function selectImageURL(url?: string) {
// 'data:,' is the default chromedriver url that is passed as last argument when running spectron tests
if (url !== undefined && url !== 'data:,') {
url = url.replace(/\/$/, ''); // on windows the url ends with an extra slash
url = url.startsWith(scheme) ? url.slice(scheme.length) : url;
await sourceSelectorReady;
electron.BrowserWindow.getAllWindows().forEach((window) => {
@@ -147,6 +148,7 @@ async function createMainWindow() {
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
contextIsolation: false,
webviewTag: true,
zoomFactor: width / defaultWidth,
enableRemoteModule: true,

View File

@@ -35,8 +35,10 @@ import { totalmem } from 'os';
import { toJSON } from '../../shared/errors';
import { GENERAL_ERROR, SUCCESS } from '../../shared/exit-codes';
import { delay } from '../../shared/utils';
import { delay, isJson } from '../../shared/utils';
import { SourceMetadata } from '../app/components/source-selector/source-selector';
import axios from 'axios';
import * as _ from 'lodash';
ipc.config.id = process.env.IPC_CLIENT_ID as string;
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
@@ -171,6 +173,7 @@ interface WriteOptions {
autoBlockmapping: boolean;
decompressFirst: boolean;
SourceType: string;
httpRequest?: any;
}
ipc.connectTo(IPC_SERVER_ID, () => {
@@ -281,7 +284,22 @@ ipc.connectTo(IPC_SERVER_ID, () => {
path: imagePath,
});
} else {
source = new Http({ url: imagePath, avoidRandomAccess: true });
const decodedImagePath = decodeURIComponent(imagePath);
if (isJson(decodedImagePath)) {
const imagePathObject = JSON.parse(decodedImagePath);
source = new Http({
url: imagePathObject.url,
avoidRandomAccess: true,
axiosInstance: axios.create(_.omit(imagePathObject, ['url'])),
auth: options.image.auth,
});
} else {
source = new Http({
url: imagePath,
avoidRandomAccess: true,
auth: options.image.auth,
});
}
}
}
const results = await writeAndValidate({
@@ -300,7 +318,7 @@ ipc.connectTo(IPC_SERVER_ID, () => {
ipc.of[IPC_SERVER_ID].emit('done', { results });
await delay(DISCONNECT_DELAY);
await terminate(exitCode);
} catch (error) {
} catch (error: any) {
exitCode = GENERAL_ERROR;
ipc.of[IPC_SERVER_ID].emit('error', toJSON(error));
}

View File

@@ -15,11 +15,12 @@
*/
import { execFile } from 'child_process';
import { app, remote } from 'electron';
import { join } from 'path';
import { env } from 'process';
import { promisify } from 'util';
import { getAppPath } from '../utils';
const execFileAsync = promisify(execFile);
const SUCCESSFUL_AUTH_MARKER = 'AUTHENTICATION SUCCEEDED';
@@ -37,7 +38,7 @@ export async function sudo(
env: {
PATH: env.PATH,
SUDO_ASKPASS: join(
(app || remote.app).getAppPath(),
getAppPath(),
__dirname,
'sudo-askpass.osascript.js',
),
@@ -49,7 +50,7 @@ export async function sudo(
stdout: stdout.slice(EXPECTED_SUCCESSFUL_AUTH_MARKER.length),
stderr,
};
} catch (error) {
} catch (error: any) {
if (error.code === 1) {
if (!error.stdout.startsWith(EXPECTED_SUCCESSFUL_AUTH_MARKER)) {
return { cancelled: true };

View File

@@ -104,24 +104,19 @@ export function isDriveLargeEnough(
return driveSize >= (image.size || UNKNOWN_SIZE);
}
/**
* @summary Check if a drive is disabled (i.e. not ready for selection)
*/
export function isDriveDisabled(drive: DrivelistDrive): boolean {
return drive.disabled || false;
}
/**
* @summary Check if a drive is valid, i.e. large enough for an image
*/
export function isDriveValid(
drive: DrivelistDrive,
image?: SourceMetadata,
write: boolean = true,
): boolean {
return (
isDriveLargeEnough(drive, image) &&
!isSourceDrive(drive, image as SourceMetadata) &&
!isDriveDisabled(drive)
!write ||
(!drive.disabled &&
isDriveLargeEnough(drive, image) &&
!isSourceDrive(drive, image as SourceMetadata))
);
}

View File

@@ -20,7 +20,7 @@ import { promises as fs } from 'fs';
import * as _ from 'lodash';
import * as os from 'os';
import * as semver from 'semver';
import * as sudoPrompt from 'sudo-prompt';
import * as sudoPrompt from '@balena/sudo-prompt';
import { promisify } from 'util';
import { sudo as catalinaSudo } from './catalina-sudo/sudo';
@@ -29,16 +29,18 @@ import * as errors from './errors';
const execAsync = promisify(childProcess.exec);
const execFileAsync = promisify(childProcess.execFile);
type Std = string | Buffer | undefined;
function sudoExecAsync(
cmd: string,
options: { name: string },
): Promise<{ stdout: string; stderr: string }> {
): Promise<{ stdout: Std; stderr: Std }> {
return new Promise((resolve, reject) => {
sudoPrompt.exec(
cmd,
options,
(error: Error | null, stdout: string, stderr: string) => {
if (error != null) {
(error: Error | undefined, stdout: Std, stderr: Std) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
@@ -60,7 +62,7 @@ export async function isElevated(): Promise<boolean> {
// See http://stackoverflow.com/a/28268802
try {
await execAsync('fltmc');
} catch (error) {
} catch (error: any) {
if (error.code === os.constants.errno.EPERM) {
return false;
}
@@ -146,7 +148,7 @@ async function elevateScriptCatalina(
try {
const { cancelled } = await catalinaSudo(cmd);
return { cancelled };
} catch (error) {
} catch (error: any) {
throw errors.createError({ title: error.stderr });
}
}
@@ -190,7 +192,7 @@ export async function elevateCommand(
}
try {
return await elevateScriptUnix(path, options.applicationName);
} catch (error) {
} catch (error: any) {
// We're hardcoding internal error messages declared by `sudo-prompt`.
// There doesn't seem to be a better way to handle these errors, so
// for now, we should make sure we double check if the error messages

View File

@@ -15,6 +15,7 @@
*/
import axios from 'axios';
import { app, remote } from 'electron';
import { Dictionary } from 'lodash';
import * as errors from './errors';
@@ -47,3 +48,25 @@ export async function delay(duration: number): Promise<void> {
setTimeout(resolve, duration);
});
}
export function getAppPath(): string {
return (
(app || remote.app)
.getAppPath()
// With macOS universal builds, getAppPath() returns the path to an app.asar file containing an index.js file which will
// include the app-x64 or app-arm64 folder depending on the arch.
// We don't care about the app.asar file, we want the actual folder.
.replace(/\.asar$/, () =>
process.platform === 'darwin' ? '-' + process.arch : '',
)
);
}
export function isJson(jsonString: string) {
try {
JSON.parse(jsonString);
} catch (e) {
return false;
}
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "balena-etcher",
"private": true,
"displayName": "balenaEtcher",
"version": "1.5.116",
"version": "1.7.9",
"packageType": "local",
"main": "generated/etcher.js",
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
@@ -15,17 +15,16 @@
"scripts": {
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts",
"lint-css": "prettier --write lib/**/*.css",
"lint-spell": "codespell --dictionary - --dictionary dictionary.txt --skip *.ttf *.svg *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,*.rpi-sdcard,*.wic,.DS_Store,*.dtb,*.dtbo,*.dat,*.elf,*.bin,*.foo,xz-without-extension lib tests docs Makefile *.md LICENSE",
"lint": "npm run lint-ts && npm run lint-css && npm run lint-spell",
"test-spectron": "mocha --recursive --reporter spec --require ts-node/register --require-main tests/gui/allow-renderer-process-reuse.ts tests/spectron/runner.spec.ts",
"test-gui": "electron-mocha --recursive --reporter spec --require ts-node/register --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox --renderer tests/gui/**/*.ts",
"test-shared": "electron-mocha --recursive --reporter spec --require ts-node/register --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox tests/shared/**/*.ts",
"lint": "npm run lint-ts && npm run lint-css",
"test-spectron": "mocha --recursive --reporter spec --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts tests/spectron/runner.spec.ts",
"test-gui": "electron-mocha --recursive --reporter spec --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox --renderer tests/gui/**/*.ts",
"test-shared": "electron-mocha --recursive --reporter spec --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox tests/shared/**/*.ts",
"test": "npm run lint && npm run test-gui && npm run test-shared && npm run test-spectron && npm run sanity-checks",
"sanity-checks": "bash scripts/ci/ensure-all-file-extensions-in-gitattributes.sh",
"start": "./node_modules/.bin/electron .",
"postshrinkwrap": "ts-node ./scripts/clean-shrinkwrap.ts",
"postinstall": "electron-rebuild -t prod,dev,optional",
"webpack": "webpack",
"watch": "webpack --watch",
"watch": "webpack serve --no-optimization-minimize --config ./webpack.dev.config.ts",
"concourse-build-electron": "npm run webpack",
"concourse-test": "npx npm@6.14.8 test",
"concourse-test-electron": "npx npm@6.14.8 test"
@@ -45,72 +44,78 @@
},
"author": "Balena Inc. <hello@etcher.io>",
"license": "Apache-2.0",
"platformSpecificDependencies": [
"fsevents",
"winusb-driver-generator"
],
"devDependencies": {
"@balena/lint": "^5.3.0",
"@fortawesome/fontawesome-free": "^5.13.1",
"@svgr/webpack": "^5.5.0",
"@types/chai": "^4.2.7",
"@types/copy-webpack-plugin": "^6.0.0",
"@types/mime-types": "^2.1.0",
"@types/mini-css-extract-plugin": "^1.2.2",
"@types/mocha": "^8.0.3",
"@types/node": "^12.12.39",
"@types/node-ipc": "^9.1.2",
"@types/react-dom": "^16.8.4",
"@types/semver": "^7.1.0",
"@types/sinon": "^9.0.0",
"@types/terser-webpack-plugin": "^5.0.2",
"@types/tmp": "^0.2.0",
"@types/webpack-node-externals": "^2.5.0",
"chai": "^4.2.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^5.0.1",
"d3": "^4.13.0",
"debug": "^4.2.0",
"electron": "9.4.1",
"electron-builder": "^22.9.1",
"electron-mocha": "^9.3.2",
"electron-notarize": "^1.0.0",
"electron-rebuild": "^2.3.2",
"electron-updater": "^4.3.5",
"etcher-sdk": "^6.1.0",
"file-loader": "^6.2.0",
"husky": "^4.2.5",
"immutable": "^3.8.1",
"lint-staged": "^10.2.2",
"lodash": "^4.17.10",
"mini-css-extract-plugin": "^1.3.3",
"mocha": "^8.0.1",
"native-addon-loader": "^2.0.1",
"node-ipc": "^9.1.1",
"@balena/lint": "5.3.0",
"@balena/sudo-prompt": "9.2.1-workaround-windows-amperstand-in-username-0849e215b947987a643fe5763902aea201255534",
"@fortawesome/fontawesome-free": "5.13.1",
"@svgr/webpack": "5.5.0",
"@types/chai": "4.2.7",
"@types/copy-webpack-plugin": "6.0.0",
"@types/mime-types": "2.1.0",
"@types/mini-css-extract-plugin": "1.2.2",
"@types/mocha": "8.0.3",
"@types/node": "14.14.41",
"@types/node-ipc": "9.1.2",
"@types/react": "16.8.5",
"@types/react-dom": "16.8.4",
"@types/semver": "7.1.0",
"@types/sinon": "9.0.0",
"@types/terser-webpack-plugin": "5.0.2",
"@types/tmp": "0.2.0",
"@types/webpack-node-externals": "2.5.0",
"aws4-axios": "2.2.1",
"chai": "4.2.0",
"copy-webpack-plugin": "7.0.0",
"css-loader": "5.0.1",
"d3": "4.13.0",
"debug": "4.2.0",
"electron": "12.2.3",
"electron-builder": "22.10.5",
"electron-mocha": "9.3.2",
"electron-notarize": "1.0.0",
"electron-rebuild": "3.2.5",
"electron-updater": "4.3.5",
"esbuild-loader": "2.16.0",
"etcher-sdk": "6.3.0",
"file-loader": "6.2.0",
"husky": "4.2.5",
"immutable": "3.8.1",
"lint-staged": "10.2.2",
"lodash": "4.17.10",
"mini-css-extract-plugin": "1.3.3",
"mocha": "8.0.1",
"native-addon-loader": "2.0.1",
"node-ipc": "9.1.1",
"omit-deep-lodash": "1.1.4",
"outdent": "^0.7.1",
"path-is-inside": "^1.0.2",
"pretty-bytes": "^5.3.0",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"redux": "^4.0.5",
"rendition": "^19.2.0",
"resin-corvus": "^2.0.5",
"semver": "^7.3.2",
"simple-progress-webpack-plugin": "^1.1.2",
"sinon": "^9.0.2",
"spectron": "^11.0.0",
"string-replace-loader": "^3.0.1",
"styled-components": "^5.1.0",
"sudo-prompt": "github:zvin/sudo-prompt#7cdede2f0da28fbcc2db48402d7d935f3a825c91",
"sys-class-rgb-led": "^3.0.0",
"ts-loader": "^8.0.12",
"ts-node": "^9.0.0",
"tslib": "^2.0.0",
"typescript": "^4.1.2",
"uuid": "^8.1.0",
"webpack": "^5.11.0",
"webpack-cli": "^4.2.0"
"outdent": "0.7.1",
"path-is-inside": "1.0.2",
"pnp-webpack-plugin": "1.6.4",
"pretty-bytes": "5.3.0",
"react": "16.8.5",
"react-dom": "16.8.5",
"redux": "4.0.5",
"rendition": "19.2.0",
"resin-corvus": "2.0.5",
"semver": "7.3.2",
"simple-progress-webpack-plugin": "1.1.2",
"sinon": "9.0.2",
"spectron": "14.0.0",
"string-replace-loader": "3.0.1",
"style-loader": "2.0.0",
"styled-components": "5.1.0",
"sys-class-rgb-led": "3.0.0",
"terser-webpack-plugin": "5.2.5",
"ts-loader": "8.0.12",
"ts-node": "9.1.1",
"tslib": "2.0.0",
"typescript": "4.4.4",
"url-loader": "4.1.1",
"uuid": "8.1.0",
"webpack": "5.11.0",
"webpack-cli": "4.2.0",
"webpack-dev-server": "4.5.0"
},
"dependencies": {}
"versionist": {
"publishedAt": "2022-04-22T13:10:47.906Z"
}
}

View File

@@ -6,7 +6,7 @@ sentry:
team: resinio
type: electron
triggerNotification:
version: 1.5.81
version: 1.7.9
stagingPercentage: 100
upstream:
- repo: etcher-sdk

View File

@@ -1,3 +1,2 @@
codespell==1.12.0
awscli==1.11.87
shyaml==0.5.0

View File

@@ -29,11 +29,15 @@ const SHRINKWRAP_FILENAME = path.join(__dirname, '..', 'npm-shrinkwrap.json');
async function main() {
try {
const cleaned = omit(shrinkwrap, packageInfo.platformSpecificDependencies);
for (const item of Object.values(cleaned.dependencies)) {
// @ts-ignore
item.dev = true;
}
await writeFileAsync(
SHRINKWRAP_FILENAME,
JSON.stringify(cleaned, null, JSON_INDENT),
);
} catch (error) {
} catch (error: any) {
console.log(`[ERROR] Couldn't write shrinkwrap file: ${error.stack}`);
process.exitCode = 1;
}

View File

@@ -573,7 +573,8 @@ describe('Model: flashState', function () {
});
describe('.getFlashUuid()', function () {
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
const UUID_REGEX =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
it('should be initially undefined', function () {
expect(flashState.getFlashUuid()).to.be.undefined;

View File

@@ -23,7 +23,7 @@ import * as settings from '../../../lib/gui/app/models/settings';
async function checkError(promise: Promise<any>, fn: (err: Error) => any) {
try {
await promise;
} catch (error) {
} catch (error: any) {
await fn(error);
return;
}

View File

@@ -83,7 +83,7 @@ describe('Browser: imageWriter', () => {
imageWriter.flash(image, [fakeDrive], performWriteStub),
]);
assert.fail('Writing twice should fail');
} catch (error) {
} catch (error: any) {
expect(error.message).to.equal(
'There is already a flash in progress',
);
@@ -133,7 +133,7 @@ describe('Browser: imageWriter', () => {
});
try {
await imageWriter.flash(image, [fakeDrive], performWriteStub);
} catch (error) {
} catch (error: any) {
expect(error).to.be.an.instanceof(Error);
expect(error.message).to.equal('write error');
}

View File

@@ -514,40 +514,6 @@ describe('Shared: DriveConstraints', function () {
});
});
describe('.isDriveDisabled()', function () {
it('should return true if the drive is disabled', function () {
const result = constraints.isDriveDisabled(({
device: '/dev/disk1',
size: 1000000000,
isReadOnly: false,
disabled: true,
} as unknown) as constraints.DrivelistDrive);
expect(result).to.be.true;
});
it('should return false if the drive is not disabled', function () {
const result = constraints.isDriveDisabled(({
device: '/dev/disk1',
size: 1000000000,
isReadOnly: false,
disabled: false,
} as unknown) as constraints.DrivelistDrive);
expect(result).to.be.false;
});
it('should return false if "disabled" is undefined', function () {
const result = constraints.isDriveDisabled({
device: '/dev/disk1',
size: 1000000000,
isReadOnly: false,
} as constraints.DrivelistDrive);
expect(result).to.be.false;
});
});
describe('.isDriveSizeRecommended()', function () {
const image: SourceMetadata = {
description: 'rpi.img',
@@ -1192,7 +1158,7 @@ describe('Shared: DriveConstraints', function () {
'/dev/disk6',
];
const drives = [
({
{
device: drivePaths[0],
description: 'My Drive',
size: 123456789,
@@ -1200,8 +1166,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [{ path: __dirname }],
isSystem: false,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[1],
description: 'My Other Drive',
size: 123456789,
@@ -1209,8 +1175,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: false,
isReadOnly: true,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[2],
description: 'My Drive',
size: 1234567,
@@ -1218,8 +1184,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: false,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[3],
description: 'My Drive',
size: 123456789,
@@ -1227,8 +1193,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: true,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[4],
description: 'My Drive',
size: 128000000001,
@@ -1236,8 +1202,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: false,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[5],
description: 'My Drive',
size: 12345678,
@@ -1245,8 +1211,8 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: false,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
({
} as unknown as constraints.DrivelistDrive,
{
device: drivePaths[6],
description: 'My Drive',
size: 123456789,
@@ -1254,7 +1220,7 @@ describe('Shared: DriveConstraints', function () {
mountpoints: [],
isSystem: false,
isReadOnly: false,
} as unknown) as constraints.DrivelistDrive,
} as unknown as constraints.DrivelistDrive,
];
const image: SourceMetadata = {

View File

@@ -30,9 +30,8 @@ describe('Shared: SupportedFormats', function () {
],
(imagePath) => {
it(`should return true if filename is ${imagePath}`, function () {
const looksLikeWindowsImage = supportedFormats.looksLikeWindowsImage(
imagePath,
);
const looksLikeWindowsImage =
supportedFormats.looksLikeWindowsImage(imagePath);
expect(looksLikeWindowsImage).to.be.true;
});
},
@@ -45,9 +44,8 @@ describe('Shared: SupportedFormats', function () {
],
(imagePath) => {
it(`should return false if filename is ${imagePath}`, function () {
const looksLikeWindowsImage = supportedFormats.looksLikeWindowsImage(
imagePath,
);
const looksLikeWindowsImage =
supportedFormats.looksLikeWindowsImage(imagePath);
expect(looksLikeWindowsImage).to.be.false;
});
},

View File

@@ -15,46 +15,52 @@
*/
import { expect } from 'chai';
import { platform } from 'os';
import { Application } from 'spectron';
import * as electronPath from 'electron';
describe('Spectron', function () {
// Mainly for CI jobs
this.timeout(40000);
// TODO: spectron fails to start on the CI with:
// Error: Failed to create session.
// unknown error: Chrome failed to start: exited abnormally
if (platform() !== 'darwin') {
describe('Spectron', function () {
// Mainly for CI jobs
this.timeout(40000);
const app = new Application({
path: (electronPath as unknown) as string,
args: ['--no-sandbox', '.'],
});
before('app:start', async () => {
await app.start();
});
after('app:stop', async () => {
if (app && app.isRunning()) {
await app.stop();
}
});
describe('Browser Window', () => {
it('should open a browser window', async () => {
// We can't use `isVisible()` here as it won't work inside
// a Windows Docker container, but we can approximate it
// with these set of checks:
const bounds = await app.browserWindow.getBounds();
expect(bounds.height).to.be.above(0);
expect(bounds.width).to.be.above(0);
expect(await app.browserWindow.isMinimized()).to.be.false;
expect(
(await app.browserWindow.isVisible()) ||
(await app.browserWindow.isFocused()),
).to.be.true;
const app = new Application({
path: electronPath as unknown as string,
args: ['--no-sandbox', '.'],
});
it('should set a proper title', async () => {
// @ts-ignore (SpectronClient.getTitle exists)
return expect(await app.client.getTitle()).to.equal('Etcher');
before('app:start', async () => {
await app.start();
});
after('app:stop', async () => {
if (app && app.isRunning()) {
await app.stop();
}
});
describe('Browser Window', () => {
it('should open a browser window', async () => {
// We can't use `isVisible()` here as it won't work inside
// a Windows Docker container, but we can approximate it
// with these set of checks:
const bounds = await app.browserWindow.getBounds();
expect(bounds.height).to.be.above(0);
expect(bounds.width).to.be.above(0);
expect(await app.browserWindow.isMinimized()).to.be.false;
expect(
(await app.browserWindow.isVisible()) ||
(await app.browserWindow.isFocused()),
).to.be.true;
});
it('should set a proper title', async () => {
// @ts-ignore (SpectronClient.getTitle exists)
return expect(await app.client.getTitle()).to.equal('balenaEtcher');
});
});
});
});
}

View File

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

View File

@@ -10,7 +10,16 @@
"jsx": "react",
"typeRoots": ["./node_modules/@types", "./typings"],
"importHelpers": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"lib": ["dom", "esnext"],
"declaration": true,
"declarationMap": true,
"pretty": true,
"sourceMap": true,
"baseUrl": "./src",
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true
},
"include": [
"lib/**/*.ts",

1
typings/pnp-webpack-plugin/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'pnp-webpack-plugin';

View File

@@ -1 +1 @@
declare module 'sudo-prompt';
declare module '@balena/sudo-prompt';

View File

@@ -17,7 +17,6 @@
import * as CopyPlugin from 'copy-webpack-plugin';
import { readdirSync } from 'fs';
import * as _ from 'lodash';
import * as MiniCssExtractPlugin from 'mini-css-extract-plugin';
import * as os from 'os';
import outdent from 'outdent';
import * as path from 'path';
@@ -25,6 +24,9 @@ import { env } from 'process';
import * as SimpleProgressWebpackPlugin from 'simple-progress-webpack-plugin';
import * as TerserPlugin from 'terser-webpack-plugin';
import { BannerPlugin, NormalModuleReplacementPlugin } from 'webpack';
import * as PnpWebpackPlugin from 'pnp-webpack-plugin';
import * as tsconfigRaw from './tsconfig.webpack.json';
/**
* Don't webpack package.json as mixpanel & sentry tokens
@@ -68,6 +70,8 @@ function renameNodeModules(resourcePath: string) {
path
.relative(__dirname, resourcePath)
.replace('node_modules', 'modules')
// use the same name on all architectures so electron-builder can build a universal dmg on mac
.replace(LZMA_BINDINGS_FOLDER, LZMA_BINDINGS_FOLDER_RENAMED)
// file-loader expects posix paths, even on Windows
.replace(/\\/g, '/')
);
@@ -87,6 +91,7 @@ function findLzmaNativeBindingsFolder(): string {
}
const LZMA_BINDINGS_FOLDER = findLzmaNativeBindingsFolder();
const LZMA_BINDINGS_FOLDER_RENAMED = 'binding';
interface ReplacementRule {
search: string;
@@ -117,7 +122,15 @@ function fetchWasm(...where: string[]) {
} catch {
}
function appPath() {
return Path.isAbsolute(__dirname) ? __dirname : Path.join(electron.remote.app.getAppPath(), 'generated');
return Path.isAbsolute(__dirname) ?
__dirname :
Path.join(
// With macOS universal builds, getAppPath() returns the path to an app.asar file containing an index.js file which will
// include the app-x64 or app-arm64 folder depending on the arch.
// We don't care about the app.asar file, we want the actual folder.
electron.remote.app.getAppPath().replace(/\\.asar$/, () => process.platform === 'darwin' ? '-' + process.arch : ''),
'generated'
);
}
scriptDirectory = Path.join(appPath(), 'modules', ${whereStr}, '/');
`;
@@ -126,16 +139,17 @@ function fetchWasm(...where: string[]) {
const commonConfig = {
mode: 'production',
optimization: {
moduleIds: 'natural',
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: false,
mangle: false,
output: {
beautify: true,
format: {
comments: false,
ecma: 2018,
ecma: 2020,
},
},
extractComments: false,
@@ -146,7 +160,12 @@ const commonConfig = {
rules: [
{
test: /\.css$/,
use: 'css-loader',
use: ['style-loader', 'css-loader'],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader',
options: { name: renameNodeModules },
},
{
test: /\.svg$/,
@@ -156,9 +175,11 @@ const commonConfig = {
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
loader: 'esbuild-loader',
options: {
configFile: 'tsconfig.webpack.json',
loader: 'tsx',
target: 'es2021',
tsconfigRaw,
},
},
],
@@ -190,12 +211,7 @@ const commonConfig = {
// remove node-pre-gyp magic from lzma-native
{
search: 'require(binding_path)',
replace: () => {
return `require('./${path.posix.join(
LZMA_BINDINGS_FOLDER,
'lzma_native.node',
)}')`;
},
replace: `require('./${LZMA_BINDINGS_FOLDER}/lzma_native.node')`,
},
// use regular stream module instead of readable-stream
{
@@ -239,7 +255,19 @@ const commonConfig = {
"return await readFile(Path.join(__dirname, '..', 'blobs', filename));",
replace: outdent`
const { app, remote } = require('electron');
return await readFile(Path.join((app || remote.app).getAppPath(), 'generated', __dirname.replace('node_modules', 'modules'), '..', 'blobs', filename));
return await readFile(
Path.join(
// With macOS universal builds, getAppPath() returns the path to an app.asar file containing an index.js file which will
// include the app-x64 or app-arm64 folder depending on the arch.
// We don't care about the app.asar file, we want the actual folder.
(app || remote.app).getAppPath().replace(/\\.asar$/, () => process.platform === 'darwin' ? '-' + process.arch : ''),
'generated',
__dirname.replace('node_modules', 'modules'),
'..',
'blobs',
filename
)
);
`,
}),
// Use the libext2fs.wasm file in the generated folder
@@ -270,6 +298,7 @@ const commonConfig = {
extensions: ['.node', '.js', '.json', '.ts', '.tsx'],
},
plugins: [
PnpWebpackPlugin,
new SimpleProgressWebpackPlugin({
format: process.env.WEBPACK_PROGRESS || 'verbose',
}),
@@ -280,6 +309,9 @@ const commonConfig = {
'./http.js',
),
],
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
output: {
path: path.join(__dirname, 'generated'),
filename: '[name].js',
@@ -317,7 +349,7 @@ if (os.platform() === 'win32') {
// liblzma.dll is required on Windows for lzma-native
guiConfigCopyPatterns.push({
from: `node_modules/lzma-native/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
to: `modules/lzma-native/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
to: `modules/lzma-native/${LZMA_BINDINGS_FOLDER_RENAMED}/liblzma.dll`,
});
}
@@ -329,10 +361,19 @@ const guiConfig = {
__filename: true,
},
entry: {
gui: path.join(__dirname, 'lib', 'gui', 'app', 'app.ts'),
gui: path.join(__dirname, 'lib', 'gui', 'app', 'renderer.ts'),
},
// entry: path.join(__dirname, 'lib', 'gui', 'app', 'renderer.ts'),
plugins: [
...commonConfig.plugins,
new CopyPlugin({
patterns: [
{ from: 'lib/gui/app/index.html', to: 'index.html' },
// electron-builder doesn't bundle folders named "assets"
// See https://github.com/electron-userland/electron-builder/issues/4545
{ from: 'assets/icon.png', to: 'media/icon.png' },
],
}),
// Remove "Download the React DevTools for a better development experience" message
new BannerPlugin({
banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };',
@@ -371,42 +412,4 @@ const childWriterConfig = {
},
};
const cssConfig = {
mode: 'production',
optimization: {
minimize: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.(woff|woff2|eot|ttf|otf|svg)$/,
loader: 'file-loader',
options: { name: renameNodeModules },
},
],
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].css' }),
new CopyPlugin({
patterns: [
{ from: 'lib/gui/app/index.html', to: 'index.html' },
// electron-builder doesn't bundle folders named "assets"
// See https://github.com/electron-userland/electron-builder/issues/4545
{ from: 'assets/icon.png', to: 'media/icon.png' },
],
}),
],
entry: {
index: path.join(__dirname, 'lib', 'gui', 'app', 'css', 'main.css'),
},
output: {
publicPath: '',
path: path.join(__dirname, 'generated'),
},
};
module.exports = [cssConfig, guiConfig, etcherConfig, childWriterConfig];
export default [guiConfig, etcherConfig, childWriterConfig];

24
webpack.dev.config.ts Normal file
View File

@@ -0,0 +1,24 @@
import configs from './webpack.config';
import { WebpackOptionsNormalized } from 'webpack';
import * as fs from 'fs';
const [
guiConfig,
etcherConfig,
childWriterConfig,
] = (configs as unknown) as WebpackOptionsNormalized[];
configs.forEach((config) => {
config.mode = 'development';
// @ts-ignore
config.devtool = 'source-map';
});
guiConfig.devServer = {
hot: true,
port: 3030,
};
fs.copyFileSync('./lib/gui/app/index.dev.html', './generated/index.html');
export default [guiConfig, etcherConfig, childWriterConfig];