mirror of
https://github.com/balena-io/etcher.git
synced 2025-11-09 02:18:31 +00:00
Compare commits
294 Commits
v1.7.4
...
aethernet/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec7e0b745e | ||
|
|
d5ba1ea5e1 | ||
|
|
54d3636a22 | ||
|
|
45f6ee667d | ||
|
|
d25eda9a7d | ||
|
|
7420283249 | ||
|
|
453952440f | ||
|
|
2475d576c7 | ||
|
|
8cd6da1260 | ||
|
|
82dd4fc1d1 | ||
|
|
33fe4b2c1a | ||
|
|
b1c1188107 | ||
|
|
63b45aefae | ||
|
|
f79cb0fac5 | ||
|
|
ec42892c7c | ||
|
|
371716fe6a | ||
|
|
d5fb6bec15 | ||
|
|
c5e7bf26d7 | ||
|
|
e3072ac416 | ||
|
|
dfaf06e4cf | ||
|
|
6e24d25576 | ||
|
|
b59b171e43 | ||
|
|
28726584c2 | ||
|
|
00b151311a | ||
|
|
36c813714b | ||
|
|
2ae6764dd9 | ||
|
|
debefc9652 | ||
|
|
b068b847c7 | ||
|
|
6c410c07ce | ||
|
|
c01206c1f3 | ||
|
|
2e85fb45de | ||
|
|
67513e384d | ||
|
|
828dafa493 | ||
|
|
5c5a761222 | ||
|
|
fab10e5fc5 | ||
|
|
797345fc1c | ||
|
|
a0388a43c3 | ||
|
|
f5b0a3023b | ||
|
|
dc1d7bd1fd | ||
|
|
9d674321b6 | ||
|
|
f9c8378d6a | ||
|
|
65da751a52 | ||
|
|
72142be0de | ||
|
|
11cea7c926 | ||
|
|
8d46ee4c22 | ||
|
|
d63c09e2c2 | ||
|
|
c9e9d7d109 | ||
|
|
2412d20eb4 | ||
|
|
7f90d23a12 | ||
|
|
b9a82be29b | ||
|
|
638673ba5e | ||
|
|
898fe4f216 | ||
|
|
7e805662d1 | ||
|
|
baf59c73ac | ||
|
|
38ad9c97c6 | ||
|
|
8fc574f059 | ||
|
|
78b0f00e88 | ||
|
|
0f10f2d483 | ||
|
|
eb5f5bbb9e | ||
|
|
67d26ff790 | ||
|
|
17f2008d88 | ||
|
|
db1bf7e488 | ||
|
|
4b786b8a9f | ||
|
|
fdfa0d3258 | ||
|
|
757aa77d89 | ||
|
|
d70ea06565 | ||
|
|
f2ebd10053 | ||
|
|
cd67b442c9 | ||
|
|
852c83c4fb | ||
|
|
e3b2ee3b83 | ||
|
|
927a026b86 | ||
|
|
c851e1d54f | ||
|
|
e6fdca171f | ||
|
|
c9cfb87733 | ||
|
|
b0b7c53294 | ||
|
|
e8dc6579fe | ||
|
|
f0747abe3f | ||
|
|
32fab87340 | ||
|
|
adcd8e0325 | ||
|
|
7b5808eb2b | ||
|
|
a8f7422cf5 | ||
|
|
5ae9a26361 | ||
|
|
cf1fdb8c5f | ||
|
|
bf7ebde100 | ||
|
|
88c5fa5035 | ||
|
|
887b0dd538 | ||
|
|
364d1db56a | ||
|
|
c431222909 | ||
|
|
55a0f68b97 | ||
|
|
af2563dfc2 | ||
|
|
33f8851083 | ||
|
|
fe1f19b9fa | ||
|
|
871cf3ec0a | ||
|
|
686a5837b6 | ||
|
|
23f2dd5ce5 | ||
|
|
d5d39b395b | ||
|
|
2acad790d3 | ||
|
|
30133306d6 | ||
|
|
04a62f2ad8 | ||
|
|
17858a7d72 | ||
|
|
620307568f | ||
|
|
a349c5d9ac | ||
|
|
0d740ad12d | ||
|
|
85a3f28869 | ||
|
|
dbd5397405 | ||
|
|
85c183b9ef | ||
|
|
0d0af1d1dd | ||
|
|
ad423fc187 | ||
|
|
d8b2a7a236 | ||
|
|
13ec8cbe98 | ||
|
|
a7cae23612 | ||
|
|
86bb093f3d | ||
|
|
997e1eb2f2 | ||
|
|
34cc8b8933 | ||
|
|
f26b074811 | ||
|
|
adaa07b4b0 | ||
|
|
96f4569342 | ||
|
|
be190c6c80 | ||
|
|
809617a82d | ||
|
|
df02732002 | ||
|
|
d35f3c3049 | ||
|
|
8b047e3b14 | ||
|
|
fa41d21e27 | ||
|
|
54e6c5e2c1 | ||
|
|
43fc3dd7eb | ||
|
|
12a1340c8e | ||
|
|
cf8b5790a1 | ||
|
|
659d85a833 | ||
|
|
96c44d31c9 | ||
|
|
ba812b4f64 | ||
|
|
4087258fbd | ||
|
|
955be13129 | ||
|
|
32011c0dea | ||
|
|
b68325c71c | ||
|
|
84bce86fce | ||
|
|
d68eab1dda | ||
|
|
09cf014d14 | ||
|
|
d5bab5805f | ||
|
|
b5ab500a14 | ||
|
|
49253d37c9 | ||
|
|
97cf3b25ad | ||
|
|
99862b95a5 | ||
|
|
8b765d58e5 | ||
|
|
8f566e45b8 | ||
|
|
b8af86e30c | ||
|
|
784f193b6d | ||
|
|
3967adb1b5 | ||
|
|
0667d1110f | ||
|
|
61dd22bdf3 | ||
|
|
24eb8b05b0 | ||
|
|
6991a4950b | ||
|
|
bb169cf674 | ||
|
|
e5d0d2e262 | ||
|
|
72b4d4f4fa | ||
|
|
9b2f2eb4c3 | ||
|
|
ce52ef95a9 | ||
|
|
aa3756ad17 | ||
|
|
73081e726d | ||
|
|
d53dc4149b | ||
|
|
0d5bb4935f | ||
|
|
14aeb0060b | ||
|
|
239726f3ce | ||
|
|
4ed3002716 | ||
|
|
7286fba240 | ||
|
|
895c306fb7 | ||
|
|
f3844d56e2 | ||
|
|
540dc3150a | ||
|
|
035c8dfec3 | ||
|
|
03d6a011db | ||
|
|
27f64650f9 | ||
|
|
ccca009972 | ||
|
|
57a6ceff0e | ||
|
|
30c4baa58b | ||
|
|
a930d77064 | ||
|
|
0d1cfffa5c | ||
|
|
3c7422764c | ||
|
|
55176b9f8f | ||
|
|
156b9314b5 | ||
|
|
76d22280dc | ||
|
|
e4251a3862 | ||
|
|
831339bd2c | ||
|
|
952ea80e15 | ||
|
|
813c497e4b | ||
|
|
1b5b647135 | ||
|
|
7de99003ca | ||
|
|
e09bdd734b | ||
|
|
306e087ec6 | ||
|
|
c6b0178a87 | ||
|
|
4e581ea1ce | ||
|
|
26dc2d19e5 | ||
|
|
b99282acfb | ||
|
|
4e48724d0c | ||
|
|
448ce141d5 | ||
|
|
695f287190 | ||
|
|
4de3271e15 | ||
|
|
77b33b127d | ||
|
|
9cd13ba381 | ||
|
|
9df23c8a3f | ||
|
|
e3618b939e | ||
|
|
6a39f5869a | ||
|
|
fd472efadc | ||
|
|
7e2c2eae63 | ||
|
|
5266571ca4 | ||
|
|
797868c474 | ||
|
|
2c2a5c7c2b | ||
|
|
9e536d5337 | ||
|
|
860e680dd9 | ||
|
|
7bb52aa170 | ||
|
|
1c370f9100 | ||
|
|
ec7c772d0b | ||
|
|
cc0285a77d | ||
|
|
256d3550d1 | ||
|
|
db3a5f3b0a | ||
|
|
0e58edf113 | ||
|
|
db136926a9 | ||
|
|
d84e7211be | ||
|
|
8357cc19d2 | ||
|
|
2752b9fa95 | ||
|
|
0214be4953 | ||
|
|
a4f944e795 | ||
|
|
cd2ebf15fc | ||
|
|
7a7ea374e9 | ||
|
|
330df325f9 | ||
|
|
2fc0882b2e | ||
|
|
4dd779e010 | ||
|
|
3dc54405fe | ||
|
|
3f1aa5bac3 | ||
|
|
8f52fdb900 | ||
|
|
1b93891ed8 | ||
|
|
33adc8ecf8 | ||
|
|
0455f7ea58 | ||
|
|
ea5a167f4f | ||
|
|
8a1c4a4cc8 | ||
|
|
bd8bc81713 | ||
|
|
98a5ddf58a | ||
|
|
6223dbc541 | ||
|
|
7c56621c57 | ||
|
|
a61aa8e2be | ||
|
|
7df4f9615b | ||
|
|
5742452fdf | ||
|
|
fe09f9f862 | ||
|
|
3a4687ea0f | ||
|
|
db6490fb1b | ||
|
|
1642297101 | ||
|
|
5ecd223cfc | ||
|
|
306e40fd7b | ||
|
|
b58249b9c8 | ||
|
|
b23b4b34d0 | ||
|
|
73bc921713 | ||
|
|
f356e4c303 | ||
|
|
9888167f2e | ||
|
|
4561690478 | ||
|
|
576113febf | ||
|
|
cc139bf750 | ||
|
|
ae91958c06 | ||
|
|
33dea6267f | ||
|
|
c9a8bca96f | ||
|
|
8af376e608 | ||
|
|
9ab307df4f | ||
|
|
e8a716f8bb | ||
|
|
a40e64f6cd | ||
|
|
2e53feb38c | ||
|
|
5945ab1f50 | ||
|
|
59d67220d4 | ||
|
|
61610ded84 | ||
|
|
c87a132f40 | ||
|
|
350d4de32b | ||
|
|
f5f9025d6d | ||
|
|
549d744d04 | ||
|
|
6194460dc2 | ||
|
|
8370f638b4 | ||
|
|
ac34c51125 | ||
|
|
b241470fe1 | ||
|
|
179697040c | ||
|
|
335766ed12 | ||
|
|
4c5d052a71 | ||
|
|
86423342a8 | ||
|
|
d8b41552e3 | ||
|
|
11c65fb392 | ||
|
|
bed126506f | ||
|
|
f6aeb52b16 | ||
|
|
a5201942b8 | ||
|
|
c1f7164273 | ||
|
|
6774bf784c | ||
|
|
56ec8b4eac | ||
|
|
35868509af | ||
|
|
3ab6749f49 | ||
|
|
7a012a92bc | ||
|
|
aba01825a0 | ||
|
|
907a3308de | ||
|
|
4366bb372f | ||
|
|
a6f6cd4a19 | ||
|
|
e4d02bc561 | ||
|
|
b9e54e39f7 |
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
# default
|
||||||
|
* text
|
||||||
|
|
||||||
# Javascript files must retain LF line-endings (to keep eslint happy)
|
# Javascript files must retain LF line-endings (to keep eslint happy)
|
||||||
*.js text eol=lf
|
*.js text eol=lf
|
||||||
*.jsx text eol=lf
|
*.jsx text eol=lf
|
||||||
@@ -27,6 +30,7 @@ Makefile text
|
|||||||
*.yml text
|
*.yml text
|
||||||
*.patch text
|
*.patch text
|
||||||
*.txt text
|
*.txt text
|
||||||
|
*.tpl text
|
||||||
CODEOWNERS text
|
CODEOWNERS text
|
||||||
*.plist text
|
*.plist text
|
||||||
|
|
||||||
@@ -58,3 +62,7 @@ CODEOWNERS text
|
|||||||
*.ttf binary diff=hex
|
*.ttf binary diff=hex
|
||||||
xz-without-extension binary diff=hex
|
xz-without-extension binary diff=hex
|
||||||
wmic-output.txt binary diff=hex
|
wmic-output.txt binary diff=hex
|
||||||
|
|
||||||
|
# gitsecret
|
||||||
|
*.secret binary
|
||||||
|
.gitsecret/** binary
|
||||||
|
|||||||
7
.github/ISSUE_TEMPLATE.md
vendored
7
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,6 +1,11 @@
|
|||||||
- **Etcher version:**
|
- **Etcher version:**
|
||||||
- **Operating system and architecture:**
|
- **Operating system and architecture:**
|
||||||
- **Image flashed:**
|
- **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?**
|
- **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. -->
|
<!-- 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 -->
|
||||||
|
|||||||
221
.github/actions/publish/action.yml
vendored
Normal file
221
.github/actions/publish/action.yml
vendored
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
---
|
||||||
|
name: package and publish GitHub (draft) release
|
||||||
|
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||||
|
inputs:
|
||||||
|
json:
|
||||||
|
description: "JSON stringified object containing all the inputs from the calling workflow"
|
||||||
|
required: true
|
||||||
|
secrets:
|
||||||
|
description: "JSON stringified object containing all the secrets from the calling workflow"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# --- custom environment
|
||||||
|
XCODE_APP_LOADER_EMAIL:
|
||||||
|
type: string
|
||||||
|
default: "accounts+apple@balena.io"
|
||||||
|
NODE_VERSION:
|
||||||
|
type: string
|
||||||
|
default: "14.x"
|
||||||
|
VERBOSE:
|
||||||
|
type: string
|
||||||
|
default: "true"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Download custom source artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
||||||
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
|
- name: Extract custom source artifact
|
||||||
|
shell: pwsh
|
||||||
|
working-directory: .
|
||||||
|
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.NODE_VERSION }}
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install yq
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: choco install yq
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
|
# FIXME: resinci-deploy is not actively maintained
|
||||||
|
# https://github.com/product-os/resinci-deploy
|
||||||
|
- name: Checkout resinci-deploy
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: product-os/resinci-deploy
|
||||||
|
token: ${{ fromJSON(inputs.secrets).FLOWZONE_TOKEN }}
|
||||||
|
path: resinci-deploy
|
||||||
|
|
||||||
|
- name: Build and install resinci-deploy
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
set -ea
|
||||||
|
|
||||||
|
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||||
|
|
||||||
|
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
|
||||||
|
rm -rf ../resinci-deploy && mv resinci-deploy ..
|
||||||
|
|
||||||
|
pushd ../resinci-deploy && npm ci && npm link && popd
|
||||||
|
|
||||||
|
if [[ $runner_os =~ linux|macos ]]; then
|
||||||
|
chmod +x "$(dirname "$(which node)")/resinci-deploy" && which resinci-deploy
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload sourcemaps to sentry
|
||||||
|
- name: Generate Sentry DSN
|
||||||
|
id: sentry
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
set -ea
|
||||||
|
|
||||||
|
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||||
|
|
||||||
|
branch="$(echo '${{ github.event.pull_request.head.ref }}' | sed 's/[^[:alnum:]]/-/g')"
|
||||||
|
|
||||||
|
stdout="$(resinci-deploy store sentry \
|
||||||
|
--branch="${branch}" \
|
||||||
|
--name="$(jq -r '.name' package.json)" \
|
||||||
|
--team="$(yq e '.sentry.team' repo.yml)" \
|
||||||
|
--org="$(yq e '.sentry.org' repo.yml)" \
|
||||||
|
--type="$(yq e '.sentry.type' repo.yml)")"
|
||||||
|
|
||||||
|
echo "dsn=$(echo "${stdout}" | tail -n 1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
env:
|
||||||
|
SENTRY_TOKEN: ${{ fromJSON(inputs.secrets).SENTRY_AUTH_TOKEN }}
|
||||||
|
|
||||||
|
# https://www.electron.build/code-signing.html
|
||||||
|
# https://github.com/Apple-Actions/import-codesign-certs
|
||||||
|
- name: Import Apple code signing certificate
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
uses: apple-actions/import-codesign-certs@v1
|
||||||
|
with:
|
||||||
|
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||||
|
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Import Windows code signing certificate
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
|
||||||
|
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
|
||||||
|
Remove-Item -path ${{ runner.temp }} -include certificate.base64
|
||||||
|
|
||||||
|
Import-PfxCertificate `
|
||||||
|
-FilePath ${{ runner.temp }}/certificate.pfx `
|
||||||
|
-CertStoreLocation Cert:\CurrentUser\My `
|
||||||
|
-Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
||||||
|
|
||||||
|
Remove-Item -path ${{ runner.temp }} -include certificate.pfx
|
||||||
|
|
||||||
|
env:
|
||||||
|
WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
||||||
|
WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||||
|
|
||||||
|
# ... or refactor (e.g.) https://github.com/samuelmeuli/action-electron-builder
|
||||||
|
# https://github.com/product-os/scripts/tree/master/electron
|
||||||
|
# https://github.com/product-os/scripts/tree/master/shared
|
||||||
|
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
|
||||||
|
- name: Package release
|
||||||
|
id: package_release
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
set -ea
|
||||||
|
|
||||||
|
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||||
|
|
||||||
|
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
|
||||||
|
ELECTRON_BUILDER_ARCHITECTURE="${runner_arch}"
|
||||||
|
APPLICATION_VERSION="$(jq -r '.version' package.json)"
|
||||||
|
ARCHITECTURE_FLAGS="--${ELECTRON_BUILDER_ARCHITECTURE}"
|
||||||
|
|
||||||
|
if [[ $runner_os =~ linux ]]; then
|
||||||
|
ELECTRON_BUILDER_OS='--linux'
|
||||||
|
TARGETS="$(yq e .linux.target[] electron-builder.yml)"
|
||||||
|
|
||||||
|
elif [[ $runner_os =~ darwin|macos|osx ]]; then
|
||||||
|
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||||
|
CSC_KEYCHAIN=signing_temp
|
||||||
|
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||||
|
ELECTRON_BUILDER_OS='--mac'
|
||||||
|
TARGETS="$(yq e .mac.target[] electron-builder.yml)"
|
||||||
|
|
||||||
|
elif [[ $runner_os =~ windows|win ]]; then
|
||||||
|
ARCHITECTURE_FLAGS="--ia32 ${ARCHITECTURE_FLAGS}"
|
||||||
|
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
||||||
|
CSC_LINK=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
||||||
|
ELECTRON_BUILDER_OS='--win'
|
||||||
|
TARGETS="$(yq e .win.target[] electron-builder.yml)"
|
||||||
|
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
npm link electron-builder
|
||||||
|
|
||||||
|
for target in ${TARGETS}; do
|
||||||
|
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
||||||
|
--c.extraMetadata.analytics.sentry.token='${{ steps.sentry.outputs.dsn }}' \
|
||||||
|
--c.extraMetadata.analytics.mixpanel.token='balena-etcher' \
|
||||||
|
--c.extraMetadata.packageType="${target}"
|
||||||
|
|
||||||
|
find dist -type f -maxdepth 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Apple notarization (afterSignHook.js)
|
||||||
|
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
|
||||||
|
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
|
||||||
|
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks
|
||||||
|
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
|
||||||
|
CSC_FOR_PULL_REQUEST: true
|
||||||
|
|
||||||
|
# https://www.electron.build/auto-update.html#staged-rollouts
|
||||||
|
- name: Configure staged rollout(s)
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
set -ea
|
||||||
|
|
||||||
|
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||||
|
|
||||||
|
percentage="$(cat < repo.yml | yq e .triggerNotification.stagingPercentage)"
|
||||||
|
|
||||||
|
find dist -type f -maxdepth 1 \
|
||||||
|
-name "latest*.yml" \
|
||||||
|
-exec yq -i e .version=\"${{ steps.package_release.outputs.version }}\" {} \;
|
||||||
|
|
||||||
|
find dist -type f -maxdepth 1 \
|
||||||
|
-name "latest*.yml" \
|
||||||
|
-exec yq -i e .stagingPercentage=\"$percentage\" {} \;
|
||||||
|
|
||||||
|
- name: Upload sourcemap to Sentry
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
VERSION=${{ steps.package_release.outputs.version }} npm run uploadSourcemap
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ fromJSON(inputs.secrets).SENTRY_AUTH_TOKEN }}
|
||||||
|
npm_config_SENTRY_ORG: balenaEtcher
|
||||||
|
npm_config_SENTRY_PROJECT: balenaetcher
|
||||||
|
npm_config_SENTRY_VERSION: ${{ steps.package_release.outputs.version }}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
|
||||||
|
path: dist
|
||||||
|
retention-days: 1
|
||||||
58
.github/actions/test/action.yml
vendored
Normal file
58
.github/actions/test/action.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
name: test release
|
||||||
|
# https://github.com/product-os/flowzone/tree/master/.github/actions
|
||||||
|
inputs:
|
||||||
|
json:
|
||||||
|
description: "JSON stringified object containing all the inputs from the calling workflow"
|
||||||
|
required: true
|
||||||
|
secrets:
|
||||||
|
description: "JSON stringified object containing all the secrets from the calling workflow"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
# --- custom environment
|
||||||
|
NODE_VERSION:
|
||||||
|
type: string
|
||||||
|
default: "14.x"
|
||||||
|
VERBOSE:
|
||||||
|
type: string
|
||||||
|
default: "true"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
# https://github.com/actions/setup-node#caching-global-packages-data
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.NODE_VERSION }}
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Test release
|
||||||
|
shell: bash --noprofile --norc -eo pipefail -x {0}
|
||||||
|
run: |
|
||||||
|
set -ea
|
||||||
|
|
||||||
|
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
||||||
|
|
||||||
|
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
|
||||||
|
npm run flowzone-preinstall-${runner_os}
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npm run test-${runner_os}
|
||||||
|
|
||||||
|
env:
|
||||||
|
# https://www.electronjs.org/docs/latest/api/environment-variables
|
||||||
|
ELECTRON_NO_ATTACH_CONSOLE: true
|
||||||
|
|
||||||
|
- name: Compress custom source
|
||||||
|
shell: pwsh
|
||||||
|
run: tar -acf ${{ runner.temp }}/custom.tgz .
|
||||||
|
|
||||||
|
- name: Upload custom artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
||||||
|
path: ${{ runner.temp }}/custom.tgz
|
||||||
|
retention-days: 1
|
||||||
29
.github/workflows/flowzone.yml
vendored
Normal file
29
.github/workflows/flowzone.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Flowzone
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, closed]
|
||||||
|
branches: [main, master]
|
||||||
|
# allow external contributions to use secrets within trusted code
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, closed]
|
||||||
|
branches: [main, master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flowzone:
|
||||||
|
name: Flowzone
|
||||||
|
uses: product-os/flowzone/.github/workflows/flowzone.yml@master
|
||||||
|
# prevent duplicate workflows and only allow one `pull_request` or `pull_request_target` for
|
||||||
|
# internal or external contributions respectively
|
||||||
|
if: |
|
||||||
|
(github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request') ||
|
||||||
|
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
||||||
|
secrets: inherit
|
||||||
|
with:
|
||||||
|
tests_run_on: '["ubuntu-20.04","macos-latest","windows-2019"]'
|
||||||
|
restrict_custom_actions: false
|
||||||
|
github_prerelease: true
|
||||||
|
repo_config: true
|
||||||
|
repo_description: "Flash OS images to SD cards & USB drives, safely and easily."
|
||||||
|
repo_homepage: https://etcher.io/
|
||||||
|
repo_enable_wiki: true
|
||||||
13
.github/workflows/winget.yml
vendored
Normal file
13
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
name: Publish to WinGet
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest # action can only be run on windows
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v1
|
||||||
|
with:
|
||||||
|
identifier: Balena.Etcher
|
||||||
|
installers-regex: 'balenaEtcher-Setup.*.exe$'
|
||||||
|
token: ${{ secrets.WINGET_PAT }}
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -51,3 +51,9 @@ node_modules
|
|||||||
# VSCode files
|
# VSCode files
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
|
.gitsecret/keys/random_seed
|
||||||
|
!*.secret
|
||||||
|
secrets/APPLE_SIGNING_PASSWORD.txt
|
||||||
|
secrets/WINDOWS_SIGNING_PASSWORD.txt
|
||||||
|
secrets/XCODE_APP_LOADER_PASSWORD.txt
|
||||||
|
secrets/WINDOWS_SIGNING.pfx
|
||||||
|
|||||||
BIN
.gitsecret/keys/pubring.kbx
Normal file
BIN
.gitsecret/keys/pubring.kbx
Normal file
Binary file not shown.
BIN
.gitsecret/keys/pubring.kbx~
Normal file
BIN
.gitsecret/keys/pubring.kbx~
Normal file
Binary file not shown.
BIN
.gitsecret/keys/trustdb.gpg
Normal file
BIN
.gitsecret/keys/trustdb.gpg
Normal file
Binary file not shown.
5
.gitsecret/paths/mapping.cfg
Normal file
5
.gitsecret/paths/mapping.cfg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
secrets/APPLE_SIGNING_PASSWORD.txt:5c9cfeb1ea5142b547bc842cc6e0b4a932641ae9811ee47abe2c3953f2a4de5d
|
||||||
|
secrets/WINDOWS_SIGNING_PASSWORD.txt:852e431628494f2559793c39cf09c34e9406dd79bb15b90c9f88194020470568
|
||||||
|
secrets/XCODE_APP_LOADER_PASSWORD.txt:005eb9a3c7035c77232973c9355468fc396b94e62783fb8e6dce16bce95b94a1
|
||||||
|
secrets/WINDOWS_SIGNING.pfx:929f401db38733ffc41572539de7c0d938023af51ed06c205a72a71c1f815714
|
||||||
|
secrets/APPLE_SIGNING.p12:61abf7b4ff2eec76ce889d71bcdd568b99a6a719b4947ac20f03966265b0946a
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"electron": {
|
|
||||||
"npm_version": "6.14.5",
|
|
||||||
"dependencies": {
|
|
||||||
"linux": [
|
|
||||||
"libudev-dev",
|
|
||||||
"libusb-1.0-0-dev",
|
|
||||||
"libyaml-dev",
|
|
||||||
"libgtk-3-0",
|
|
||||||
"libatk-bridge2.0-0",
|
|
||||||
"libdbus-1-3",
|
|
||||||
"libgbm1",
|
|
||||||
"libc6"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"builder": {
|
|
||||||
"appId": "io.balena.etcher",
|
|
||||||
"copyright": "Copyright 2016-2021 Balena Ltd",
|
|
||||||
"productName": "balenaEtcher",
|
|
||||||
"nodeGypRebuild": false,
|
|
||||||
"afterPack": "./afterPack.js",
|
|
||||||
"asar": false,
|
|
||||||
"files": [
|
|
||||||
"generated",
|
|
||||||
"lib/shared/catalina-sudo/sudo-askpass.osascript.js"
|
|
||||||
],
|
|
||||||
"afterSign": "./afterSignHook.js",
|
|
||||||
"mac": {
|
|
||||||
"category": "public.app-category.developer-tools",
|
|
||||||
"hardenedRuntime": true,
|
|
||||||
"entitlements": "entitlements.mac.plist",
|
|
||||||
"entitlementsInherit": "entitlements.mac.plist",
|
|
||||||
"artifactName": "${productName}-${version}.${ext}"
|
|
||||||
},
|
|
||||||
"dmg": {
|
|
||||||
"iconSize": 110,
|
|
||||||
"contents": [
|
|
||||||
{
|
|
||||||
"x": 140,
|
|
||||||
"y": 245
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": 415,
|
|
||||||
"y": 245,
|
|
||||||
"type": "link",
|
|
||||||
"path": "/Applications"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"window": {
|
|
||||||
"width": 544,
|
|
||||||
"height": 407
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"category": "Utility",
|
|
||||||
"packageCategory": "utils",
|
|
||||||
"synopsis": "balenaEtcher 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."
|
|
||||||
},
|
|
||||||
"deb": {
|
|
||||||
"compression": "bzip2",
|
|
||||||
"priority": "optional",
|
|
||||||
"depends": [
|
|
||||||
"polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"protocols": {
|
|
||||||
"name": "etcher",
|
|
||||||
"schemes": [
|
|
||||||
"etcher"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
468
CHANGELOG.md
468
CHANGELOG.md
@@ -3,6 +3,474 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# v1.13.4
|
||||||
|
## (2023-01-12)
|
||||||
|
|
||||||
|
* Adding EtcherPro device serial number to the Settings modal [Aurelien VALADE]
|
||||||
|
|
||||||
|
# v1.13.3
|
||||||
|
## (2023-01-11)
|
||||||
|
|
||||||
|
* patch: progress cm4 to second stage [Peter Makra]
|
||||||
|
|
||||||
|
# v1.13.2
|
||||||
|
## (2023-01-02)
|
||||||
|
|
||||||
|
* patch: fixed winget parameter name [mcraa]
|
||||||
|
|
||||||
|
# v1.13.1
|
||||||
|
## (2023-01-02)
|
||||||
|
|
||||||
|
* patch: updated sdk to fix bz2 issue [Peter Makra]
|
||||||
|
* patch: update copyright in electron-builder [JOASSART Edwin]
|
||||||
|
|
||||||
|
# v1.13.0
|
||||||
|
## (2022-12-28)
|
||||||
|
|
||||||
|
* minor: electron version bump [Peter Makra]
|
||||||
|
* patch: handle ext2fs with webpack [Peter Makra]
|
||||||
|
* Patch: update etcher-sdk version to fix CM4 issues [builder555]
|
||||||
|
|
||||||
|
# v1.12.7
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Update dependency i18next to 21.10.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.6
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Update dependency react-i18next to 11.18.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.5
|
||||||
|
## (2022-12-20)
|
||||||
|
|
||||||
|
* Patch: made trim setting more readable [builder555]
|
||||||
|
|
||||||
|
# v1.12.4
|
||||||
|
## (2022-12-19)
|
||||||
|
|
||||||
|
* patch: publish to winget with gh action [Begula]
|
||||||
|
|
||||||
|
# v1.12.3
|
||||||
|
## (2022-12-19)
|
||||||
|
|
||||||
|
* Patch: replaced plain text with i18n in settings [builder555]
|
||||||
|
|
||||||
|
# v1.12.2
|
||||||
|
## (2022-12-16)
|
||||||
|
|
||||||
|
* Update dependency webpack-dev-server to 4.11.1 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.12.1
|
||||||
|
## (2022-12-16)
|
||||||
|
|
||||||
|
* Patch: expose trim ext{2,3,4} setting [builder555]
|
||||||
|
|
||||||
|
# v1.12.0
|
||||||
|
## (2022-12-14)
|
||||||
|
|
||||||
|
* i18n support and Chinese translation [ab77]
|
||||||
|
* minor: optimize i18n [r-q]
|
||||||
|
|
||||||
|
# v1.11.10
|
||||||
|
## (2022-12-13)
|
||||||
|
|
||||||
|
* Update dependency webpack-cli to 4.10.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.9
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency webpack to 5.75.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.8
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.28 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.7
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency uuid to 8.3.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.6
|
||||||
|
## (2022-12-12)
|
||||||
|
|
||||||
|
* Update dependency tslib to 2.4.1 [Renovate Bot]
|
||||||
|
* Patch: run linux build on ubuntu-20.04 [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.11.5
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency ts-loader to 8.4.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.4
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency styled-components to 5.3.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.3
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency terser-webpack-plugin to 5.3.6 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.2
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency string-replace-loader to 3.1.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.1
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency sinon to 9.2.4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.11.0
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency shyaml to 0.6.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.29
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.27 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.28
|
||||||
|
## (2022-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update dependency rendition to 19.3.2 [Renovate Bot] </summary>
|
||||||
|
|
||||||
|
> ## rendition-19.3.2
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Add Breadcrumbs component export [JSReds]
|
||||||
|
>
|
||||||
|
> ## rendition-19.3.1
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Fix max-width on breadcrumbs container [JSReds]
|
||||||
|
>
|
||||||
|
> ## rendition-19.3.0
|
||||||
|
> ### (2020-12-29)
|
||||||
|
>
|
||||||
|
> * Add Breadcrumbs component [JSReds]
|
||||||
|
>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# v1.10.27
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency redux to 4.2.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.26
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency pretty-bytes to 5.6.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.25
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency pnp-webpack-plugin to 1.7.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.24
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency node-ipc to 9.2.1 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.23
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency mocha to 8.4.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.22
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency mini-css-extract-plugin to 1.6.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.21
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency lint-staged to 10.5.4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.20
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency husky to 4.3.8 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.19
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency esbuild-loader to 2.20.0 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.18
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency electron-updater to 4.6.5 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.17
|
||||||
|
## (2022-12-09)
|
||||||
|
|
||||||
|
* Update dependency electron-notarize to 1.2.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.16
|
||||||
|
## (2022-12-08)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.26 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.15
|
||||||
|
## (2022-12-08)
|
||||||
|
|
||||||
|
* Update dependency electron-builder to 22.14.13 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.14
|
||||||
|
## (2022-12-08)
|
||||||
|
|
||||||
|
* Update dependency debug to 4.3.4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.13
|
||||||
|
## (2022-12-08)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.25 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.12
|
||||||
|
## (2022-12-08)
|
||||||
|
|
||||||
|
* Update dependency css-loader to 5.2.7 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.11
|
||||||
|
## (2022-12-07)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.24 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.10
|
||||||
|
## (2022-12-07)
|
||||||
|
|
||||||
|
* Update dependency @types/node to 14.18.34 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.9
|
||||||
|
## (2022-12-06)
|
||||||
|
|
||||||
|
* Enable repository configuration [ab77]
|
||||||
|
|
||||||
|
# v1.10.8
|
||||||
|
## (2022-12-05)
|
||||||
|
|
||||||
|
* Update dependency chai to 4.3.7 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.10.7
|
||||||
|
## (2022-12-05)
|
||||||
|
|
||||||
|
* Use core workflow for GitHub publish [ab77]
|
||||||
|
|
||||||
|
# v1.10.6
|
||||||
|
## (2022-12-02)
|
||||||
|
|
||||||
|
* Dummy update to fix asset version issue [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.10.5
|
||||||
|
## (2022-12-02)
|
||||||
|
|
||||||
|
* Patch: run linux build on ubuntu-18.04 [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.10.4
|
||||||
|
## (2022-12-01)
|
||||||
|
|
||||||
|
* patch: remove Homebrew instructions in README [Patrick Linnane]
|
||||||
|
|
||||||
|
# v1.10.3
|
||||||
|
## (2022-12-01)
|
||||||
|
|
||||||
|
* Allow external contributors [ab77]
|
||||||
|
|
||||||
|
# v1.10.2
|
||||||
|
## (2022-11-25)
|
||||||
|
|
||||||
|
* Fix missing analytics token [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.10.1
|
||||||
|
## (2022-11-21)
|
||||||
|
|
||||||
|
* Fixing call to electron block screensaver methods invocation [Aurelien VALADE]
|
||||||
|
|
||||||
|
# v1.10.0
|
||||||
|
## (2022-11-10)
|
||||||
|
|
||||||
|
* testing renovate [builder555]
|
||||||
|
|
||||||
|
# v1.9.0
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency awscli to 1.27.5 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.17
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/react-dom to 16.9.17 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.16
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/react to 16.14.34 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.15
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* CI: generalise artefact handling [ab77]
|
||||||
|
|
||||||
|
# v1.8.14
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/node to 14.18.33 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.13
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/copy-webpack-plugin to 6.4.3 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.12
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @fortawesome/fontawesome-free to 5.15.4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.11
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @balena/lint to 5.4.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.10
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> Update dependency sys-class-rgb-led to 3.0.1 [Renovate Bot] </summary>
|
||||||
|
|
||||||
|
> ## sys-class-rgb-led-3.0.1
|
||||||
|
> ### (2021-07-01)
|
||||||
|
>
|
||||||
|
> * patch: Delete Codeowners [Vipul Gupta]
|
||||||
|
>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# v1.8.9
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency semver to 7.3.8 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.8
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency omit-deep-lodash to 1.1.7 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.7
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency immutable to 3.8.2 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.6
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency electron-rebuild to 3.2.9 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.5
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency electron-mocha to 9.3.3 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.4
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/webpack-node-externals to 2.5.3 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.3
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Update dependency @types/tmp to 0.2.3 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.2
|
||||||
|
## (2022-11-08)
|
||||||
|
|
||||||
|
* Generate release notes with git [ab77]
|
||||||
|
|
||||||
|
# v1.8.1
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Update dependency @types/mime-types to 2.1.1 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.8.0
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Update scripts/resin digest to 652fdd4 [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.7.15
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Build targets individually [ab77]
|
||||||
|
|
||||||
|
# v1.7.14
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Update dependency lodash to 4.17.21 [SECURITY] [Renovate Bot]
|
||||||
|
|
||||||
|
# v1.7.13
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Update release notes on finalize [ab77]
|
||||||
|
|
||||||
|
# v1.7.12
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Avoid duplicate releases [ab77]
|
||||||
|
|
||||||
|
# v1.7.11
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Only run finalize on Linux runners [ab77]
|
||||||
|
|
||||||
|
# v1.7.10
|
||||||
|
## (2022-11-07)
|
||||||
|
|
||||||
|
* Switch to Flowzone [ab77]
|
||||||
|
|
||||||
|
# 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
|
# v1.7.4
|
||||||
## (2022-02-21)
|
## (2022-02-21)
|
||||||
|
|
||||||
|
|||||||
74
README.md
74
README.md
@@ -5,11 +5,10 @@
|
|||||||
Etcher is a powerful OS image flasher built with web technologies to ensure
|
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
|
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
|
you from accidentally writing to your hard-drives, ensures every byte of data
|
||||||
was written correctly, and much more. It can also directly flash Raspberry Pi devices that support [USB device boot mode](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bootmodes/device.md).
|
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).
|
||||||
|
|
||||||
[](https://balena.io/etcher)
|
[](https://balena.io/etcher)
|
||||||
[](https://github.com/balena-io/etcher/blob/master/LICENSE)
|
[](https://github.com/balena-io/etcher/blob/master/LICENSE)
|
||||||
[](https://david-dm.org/balena-io/etcher)
|
|
||||||
[](https://forums.balena.io/c/etcher)
|
[](https://forums.balena.io/c/etcher)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -31,10 +30,18 @@ was written correctly, and much more. It can also directly flash Raspberry Pi de
|
|||||||
Refer to the [downloads page][etcher] for the latest pre-made
|
Refer to the [downloads page][etcher] for the latest pre-made
|
||||||
installers for all supported operating systems.
|
installers for all supported operating systems.
|
||||||
|
|
||||||
> Note: Our deb and rpm packages are now hosted on [Cloudsmith](https://cloudsmith.com)!
|
## Packages
|
||||||
|
|
||||||
|
> [](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)
|
#### Debian and Ubuntu based Package Repository (GNU/Linux x86/x64)
|
||||||
|
|
||||||
|
> Detailed or alternative steps in the [instructions by Cloudsmith](https://cloudsmith.io/~balena/repos/etcher/setup/#formats-deb)
|
||||||
|
|
||||||
1. Add Etcher Debian repository:
|
1. Add Etcher Debian repository:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -60,23 +67,11 @@ rm -rf /var/lib/apt/lists/*
|
|||||||
apt-get update
|
apt-get update
|
||||||
```
|
```
|
||||||
|
|
||||||
##### OpenSUSE LEAP & Tumbleweed install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -1sLf \
|
|
||||||
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
|
|
||||||
| sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Uninstall
|
|
||||||
|
|
||||||
```sh
|
|
||||||
zypper rr balena-etcher
|
|
||||||
zypper rr balena-etcher-source
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Redhat (RHEL) and Fedora-based Package Repository (GNU/Linux x86/x64)
|
#### 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
|
##### DNF
|
||||||
|
|
||||||
1. Add Etcher rpm repository:
|
1. Add Etcher rpm repository:
|
||||||
@@ -124,6 +119,31 @@ rm /etc/yum.repos.d/balena-etcher.repo
|
|||||||
rm /etc/yum.repos.d/balena-etcher-source.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
|
||||||
|
# remove the repo
|
||||||
|
sudo zypper rr balena-etcher
|
||||||
|
sudo zypper rr balena-etcher-source
|
||||||
|
```
|
||||||
|
|
||||||
#### Solus (GNU/Linux x64)
|
#### Solus (GNU/Linux x64)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -150,22 +170,6 @@ yay -S balena-etcher
|
|||||||
yay -R balena-etcher
|
yay -R balena-etcher
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Brew (macOS)
|
|
||||||
|
|
||||||
**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 install balenaetcher
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Uninstall
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew uninstall balenaetcher
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Chocolatey (Windows)
|
#### Chocolatey (Windows)
|
||||||
|
|
||||||
This package is maintained by [@majkinetor](https://github.com/majkinetor), and
|
This package is maintained by [@majkinetor](https://github.com/majkinetor), and
|
||||||
@@ -200,3 +204,5 @@ the [license].
|
|||||||
[milestones]: https://github.com/balena-io/etcher/milestones
|
[milestones]: https://github.com/balena-io/etcher/milestones
|
||||||
[newissue]: https://github.com/balena-io/etcher/issues/new
|
[newissue]: https://github.com/balena-io/etcher/issues/new
|
||||||
[license]: https://github.com/balena-io/etcher/blob/master/LICENSE
|
[license]: https://github.com/balena-io/etcher/blob/master/LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
after-install.tpl
Normal file
11
after-install.tpl
Normal 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
|
||||||
@@ -10,13 +10,15 @@ async function main(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appName = context.packager.appInfo.productFilename
|
const appName = context.packager.appInfo.productFilename
|
||||||
const appleId = 'accounts+apple@balena.io'
|
const appleId = process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io'
|
||||||
|
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD
|
||||||
|
|
||||||
|
// https://github.com/electron/notarize/blob/main/README.md
|
||||||
await notarize({
|
await notarize({
|
||||||
appBundleId: 'io.balena.etcher',
|
appBundleId: 'io.balena.etcher',
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId,
|
appleId,
|
||||||
appleIdPassword: `@keychain:Application Loader: ${appleId}`
|
appleIdPassword
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
assets/icon.icns
BIN
assets/icon.icns
Binary file not shown.
@@ -1,14 +1,14 @@
|
|||||||
|
# https://www.electron.build/configuration/configuration
|
||||||
appId: io.balena.etcher
|
appId: io.balena.etcher
|
||||||
copyright: Copyright 2016-2021 Balena Ltd
|
copyright: Copyright 2016-2023 Balena Ltd
|
||||||
productName: balenaEtcher
|
productName: balenaEtcher
|
||||||
npmRebuild: true
|
afterPack: ./afterPack.js
|
||||||
nodeGypRebuild: false
|
afterSign: ./afterSignHook.js
|
||||||
publish: null
|
|
||||||
afterPack: "./afterPack.js"
|
|
||||||
asar: false
|
asar: false
|
||||||
files:
|
files:
|
||||||
- generated
|
- generated
|
||||||
- lib/shared/catalina-sudo/sudo-askpass.osascript.js
|
- lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
|
||||||
|
- lib/shared/catalina-sudo/sudo-askpass.osascript-en.js
|
||||||
mac:
|
mac:
|
||||||
icon: assets/icon.icns
|
icon: assets/icon.icns
|
||||||
category: public.app-category.developer-tools
|
category: public.app-category.developer-tools
|
||||||
@@ -16,6 +16,8 @@ mac:
|
|||||||
entitlements: "entitlements.mac.plist"
|
entitlements: "entitlements.mac.plist"
|
||||||
entitlementsInherit: "entitlements.mac.plist"
|
entitlementsInherit: "entitlements.mac.plist"
|
||||||
artifactName: "${productName}-${version}.${ext}"
|
artifactName: "${productName}-${version}.${ext}"
|
||||||
|
target:
|
||||||
|
- dmg
|
||||||
dmg:
|
dmg:
|
||||||
background: assets/dmg/background.tiff
|
background: assets/dmg/background.tiff
|
||||||
icon: assets/icon.icns
|
icon: assets/icon.icns
|
||||||
@@ -32,6 +34,10 @@ dmg:
|
|||||||
height: 405
|
height: 405
|
||||||
win:
|
win:
|
||||||
icon: assets/icon.ico
|
icon: assets/icon.ico
|
||||||
|
target:
|
||||||
|
- zip
|
||||||
|
- nsis
|
||||||
|
- portable
|
||||||
nsis:
|
nsis:
|
||||||
oneClick: true
|
oneClick: true
|
||||||
runAfterFinish: true
|
runAfterFinish: true
|
||||||
@@ -44,16 +50,23 @@ portable:
|
|||||||
artifactName: "${productName}-Portable-${version}.${ext}"
|
artifactName: "${productName}-Portable-${version}.${ext}"
|
||||||
requestExecutionLevel: user
|
requestExecutionLevel: user
|
||||||
linux:
|
linux:
|
||||||
|
icon: assets/iconset
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- rpm
|
||||||
|
- deb
|
||||||
category: Utility
|
category: Utility
|
||||||
packageCategory: utils
|
packageCategory: utils
|
||||||
executableName: balena-etcher-electron
|
executableName: balena-etcher
|
||||||
synopsis: balenaEtcher 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.
|
synopsis: balenaEtcher 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.
|
||||||
icon: assets/iconset
|
appImage:
|
||||||
|
artifactName: ${productName}-${version}-${env.ELECTRON_BUILDER_ARCHITECTURE}.${ext}
|
||||||
deb:
|
deb:
|
||||||
priority: optional
|
priority: optional
|
||||||
|
compression: bzip2
|
||||||
depends:
|
depends:
|
||||||
- gconf2
|
|
||||||
- gconf-service
|
- gconf-service
|
||||||
|
- gconf2
|
||||||
- libasound2
|
- libasound2
|
||||||
- libatk1.0-0
|
- libatk1.0-0
|
||||||
- libc6
|
- libc6
|
||||||
@@ -87,6 +100,7 @@ deb:
|
|||||||
- libxss1
|
- libxss1
|
||||||
- libxtst6
|
- libxtst6
|
||||||
- polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1
|
- polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1
|
||||||
|
afterInstall: "./after-install.tpl"
|
||||||
rpm:
|
rpm:
|
||||||
depends:
|
depends:
|
||||||
- util-linux
|
- util-linux
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import * as osDialog from './os/dialog';
|
|||||||
import * as windowProgress from './os/window-progress';
|
import * as windowProgress from './os/window-progress';
|
||||||
import MainPage from './pages/main/MainPage';
|
import MainPage from './pages/main/MainPage';
|
||||||
import './css/main.css';
|
import './css/main.css';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'unhandledrejection',
|
'unhandledrejection',
|
||||||
@@ -313,9 +314,9 @@ window.addEventListener('beforeunload', async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const confirmed = await osDialog.showWarning({
|
const confirmed = await osDialog.showWarning({
|
||||||
confirmationLabel: 'Yes, quit',
|
confirmationLabel: i18next.t('yesExit'),
|
||||||
rejectionLabel: 'Cancel',
|
rejectionLabel: i18next.t('cancel'),
|
||||||
title: 'Are you sure you want to close Etcher?',
|
title: i18next.t('reallyExit'),
|
||||||
description: messages.warning.exitWhileFlashing(),
|
description: messages.warning.exitWhileFlashing(),
|
||||||
});
|
});
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
|
|
||||||
import { SourceMetadata } from '../source-selector/source-selector';
|
import { SourceMetadata } from '../source-selector/source-selector';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
interface UsbbootDrive extends sourceDestination.UsbbootDrive {
|
interface UsbbootDrive extends sourceDestination.UsbbootDrive {
|
||||||
progress: number;
|
progress: number;
|
||||||
@@ -189,7 +190,7 @@ export class DriveSelector extends React.Component<
|
|||||||
this.tableColumns = [
|
this.tableColumns = [
|
||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
label: 'Name',
|
label: i18next.t('drives.name'),
|
||||||
render: (description: string, drive: Drive) => {
|
render: (description: string, drive: Drive) => {
|
||||||
if (isDrivelistDrive(drive)) {
|
if (isDrivelistDrive(drive)) {
|
||||||
const isLargeDrive = isDriveSizeLarge(drive);
|
const isLargeDrive = isDriveSizeLarge(drive);
|
||||||
@@ -215,7 +216,7 @@ export class DriveSelector extends React.Component<
|
|||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
key: 'size',
|
key: 'size',
|
||||||
label: 'Size',
|
label: i18next.t('drives.size'),
|
||||||
render: (_description: string, drive: Drive) => {
|
render: (_description: string, drive: Drive) => {
|
||||||
if (isDrivelistDrive(drive) && drive.size !== null) {
|
if (isDrivelistDrive(drive) && drive.size !== null) {
|
||||||
return prettyBytes(drive.size);
|
return prettyBytes(drive.size);
|
||||||
@@ -225,7 +226,7 @@ export class DriveSelector extends React.Component<
|
|||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
key: 'link',
|
key: 'link',
|
||||||
label: 'Location',
|
label: i18next.t('drives.location'),
|
||||||
render: (_description: string, drive: Drive) => {
|
render: (_description: string, drive: Drive) => {
|
||||||
return (
|
return (
|
||||||
<Txt>
|
<Txt>
|
||||||
@@ -399,14 +400,14 @@ export class DriveSelector extends React.Component<
|
|||||||
color="#5b82a7"
|
color="#5b82a7"
|
||||||
style={{ fontWeight: 600 }}
|
style={{ fontWeight: 600 }}
|
||||||
>
|
>
|
||||||
{drives.length} found
|
{i18next.t('drives.find', { length: drives.length })}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
titleDetails={<Txt fontSize={11}>{getDrives().length} found</Txt>}
|
||||||
cancel={() => cancel(this.originalList)}
|
cancel={() => cancel(this.originalList)}
|
||||||
done={() => done(selectedList)}
|
done={() => done(selectedList)}
|
||||||
action={`Select (${selectedList.length})`}
|
action={i18next.t('drives.select', { select: selectedList.length })}
|
||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
primary: !showWarnings,
|
primary: !showWarnings,
|
||||||
warning: showWarnings,
|
warning: showWarnings,
|
||||||
@@ -512,7 +513,11 @@ export class DriveSelector extends React.Component<
|
|||||||
>
|
>
|
||||||
<Flex alignItems="center">
|
<Flex alignItems="center">
|
||||||
<ChevronDownSvg height="1em" fill="currentColor" />
|
<ChevronDownSvg height="1em" fill="currentColor" />
|
||||||
<Txt ml={8}>Show {numberOfHiddenSystemDrives} hidden</Txt>
|
<Txt ml={8}>
|
||||||
|
{i18next.t('drives.showHidden', {
|
||||||
|
num: numberOfHiddenSystemDrives,
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -520,7 +525,7 @@ export class DriveSelector extends React.Component<
|
|||||||
)}
|
)}
|
||||||
{this.props.showWarnings && hasSystemDrives ? (
|
{this.props.showWarnings && hasSystemDrives ? (
|
||||||
<Alert className="system-drive-alert" style={{ width: '67%' }}>
|
<Alert className="system-drive-alert" style={{ width: '67%' }}>
|
||||||
Selecting your system drive is dangerous and will erase your drive!
|
{i18next.t('drives.systemDriveDanger')}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -540,13 +545,15 @@ export class DriveSelector extends React.Component<
|
|||||||
this.setState({ missingDriversModal: {} });
|
this.setState({ missingDriversModal: {} });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
action="Yes, continue"
|
action={i18next.t('yesContinue')}
|
||||||
cancelButtonProps={{
|
cancelButtonProps={{
|
||||||
children: 'Cancel',
|
children: i18next.t('cancel'),
|
||||||
}}
|
}}
|
||||||
children={
|
children={
|
||||||
missingDriversModal.drive.linkMessage ||
|
missingDriversModal.drive.linkMessage ||
|
||||||
`Etcher will open ${missingDriversModal.drive.link} in your browser`
|
i18next.t('drives.openInBrowser', {
|
||||||
|
link: missingDriversModal.drive.link,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { middleEllipsis } from '../../utils/middle-ellipsis';
|
|||||||
|
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
import { DriveWithWarnings } from '../../pages/main/Flash';
|
import { DriveWithWarnings } from '../../pages/main/Flash';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const DriveStatusWarningModal = ({
|
const DriveStatusWarningModal = ({
|
||||||
done,
|
done,
|
||||||
@@ -17,12 +18,12 @@ const DriveStatusWarningModal = ({
|
|||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
drivesWithWarnings: DriveWithWarnings[];
|
drivesWithWarnings: DriveWithWarnings[];
|
||||||
}) => {
|
}) => {
|
||||||
let warningSubtitle = 'You are about to erase an unusually large drive';
|
let warningSubtitle = i18next.t('drives.largeDriveWarning');
|
||||||
let warningCta = 'Are you sure the selected drive is not a storage drive?';
|
let warningCta = i18next.t('drives.largeDriveWarningMsg');
|
||||||
|
|
||||||
if (isSystem) {
|
if (isSystem) {
|
||||||
warningSubtitle = "You are about to erase your computer's drives";
|
warningSubtitle = i18next.t('drives.systemDriveWarning');
|
||||||
warningCta = 'Are you sure you want to flash your system drive?';
|
warningCta = i18next.t('drives.systemDriveWarningMsg');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -33,9 +34,9 @@ const DriveStatusWarningModal = ({
|
|||||||
cancelButtonProps={{
|
cancelButtonProps={{
|
||||||
primary: false,
|
primary: false,
|
||||||
warning: true,
|
warning: true,
|
||||||
children: 'Change target',
|
children: i18next.t('drives.changeTarget'),
|
||||||
}}
|
}}
|
||||||
action={"Yes, I'm sure"}
|
action={i18next.t('sure')}
|
||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
primary: false,
|
primary: false,
|
||||||
outline: true,
|
outline: true,
|
||||||
@@ -50,7 +51,7 @@ const DriveStatusWarningModal = ({
|
|||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<ExclamationTriangleSvg height="2em" fill="#fca321" />
|
<ExclamationTriangleSvg height="2em" fill="#fca321" />
|
||||||
<Txt fontSize="24px" color="#fca321">
|
<Txt fontSize="24px" color="#fca321">
|
||||||
WARNING!
|
{i18next.t('warning')}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Txt fontSize="24px">{warningSubtitle}</Txt>
|
<Txt fontSize="24px">{warningSubtitle}</Txt>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { BaseButton } from '../../styled-components';
|
import { BaseButton } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export interface FlashAnotherProps {
|
export interface FlashAnotherProps {
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -25,7 +26,7 @@ export interface FlashAnotherProps {
|
|||||||
export const FlashAnother = (props: FlashAnotherProps) => {
|
export const FlashAnother = (props: FlashAnotherProps) => {
|
||||||
return (
|
return (
|
||||||
<BaseButton primary onClick={props.onClick}>
|
<BaseButton primary onClick={props.onClick}>
|
||||||
Flash another
|
{i18next.t('flash.another')}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { resetState } from '../../models/flash-state';
|
|||||||
import * as selection from '../../models/selection-state';
|
import * as selection from '../../models/selection-state';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
import { Modal, Table } from '../../styled-components';
|
import { Modal, Table } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
|
const ErrorsTable = styled((props) => <Table<FlashError> {...props} />)`
|
||||||
&&& [data-display='table-head'],
|
&&& [data-display='table-head'],
|
||||||
@@ -88,15 +89,15 @@ function formattedErrors(errors: FlashError[]) {
|
|||||||
const columns: Array<TableColumn<FlashError>> = [
|
const columns: Array<TableColumn<FlashError>> = [
|
||||||
{
|
{
|
||||||
field: 'description',
|
field: 'description',
|
||||||
label: 'Target',
|
label: i18next.t('flash.target'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'device',
|
field: 'device',
|
||||||
label: 'Location',
|
label: i18next.t('flash.location'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'message',
|
field: 'message',
|
||||||
label: 'Error',
|
label: i18next.t('flash.error'),
|
||||||
render: (message: string, { code }: FlashError) => {
|
render: (message: string, { code }: FlashError) => {
|
||||||
return message ?? code;
|
return message ?? code;
|
||||||
},
|
},
|
||||||
@@ -162,9 +163,11 @@ export function FlashResults({
|
|||||||
<Txt>{middleEllipsis(image, 24)}</Txt>
|
<Txt>{middleEllipsis(image, 24)}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Txt fontSize={24} color="#fff" mb="17px">
|
<Txt fontSize={24} color="#fff" mb="17px">
|
||||||
Flash {allFailed ? 'Failed' : 'Complete'}!
|
{allFailed
|
||||||
|
? i18next.t('flash.flashFailed')
|
||||||
|
: i18next.t('flash.flashCompleted')}
|
||||||
</Txt>
|
</Txt>
|
||||||
{skip ? <Txt color="#7e8085">Validation has been skipped</Txt> : null}
|
{skip ? <Txt color="#7e8085">{i18next.t('flash.skip')}</Txt> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex flexDirection="column" color="#7e8085">
|
<Flex flexDirection="column" color="#7e8085">
|
||||||
{results.devices.successful !== 0 ? (
|
{results.devices.successful !== 0 ? (
|
||||||
@@ -188,7 +191,7 @@ export function FlashResults({
|
|||||||
{progress.failed(errors.length)}
|
{progress.failed(errors.length)}
|
||||||
</Txt>
|
</Txt>
|
||||||
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
|
<Link ml="10px" onClick={() => setShowErrorsInfo(true)}>
|
||||||
more info
|
{i18next.t('flash.moreInfo')}
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -199,12 +202,9 @@ export function FlashResults({
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
tooltip={outdent({ newline: ' ' })`
|
tooltip={i18next.t('flash.speedTip')}
|
||||||
The speed is calculated by dividing the image size by the flashing time.
|
|
||||||
Disk images with ext partitions flash faster as we are able to skip unused parts.
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Effective speed: {effectiveSpeed} MB/s
|
{i18next.t('flash.speed', { speed: effectiveSpeed })}
|
||||||
</Txt>
|
</Txt>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -214,11 +214,11 @@ export function FlashResults({
|
|||||||
titleElement={
|
titleElement={
|
||||||
<Flex alignItems="baseline" mb={18}>
|
<Flex alignItems="baseline" mb={18}>
|
||||||
<Txt fontSize={24} align="left">
|
<Txt fontSize={24} align="left">
|
||||||
Failed targets
|
{i18next.t('failedTarget')}
|
||||||
</Txt>
|
</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
action="Retry failed targets"
|
action={i18next.t('failedRetry')}
|
||||||
cancel={() => setShowErrorsInfo(false)}
|
cancel={() => setShowErrorsInfo(false)}
|
||||||
done={() => {
|
done={() => {
|
||||||
setShowErrorsInfo(false);
|
setShowErrorsInfo(false);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { default as styled } from 'styled-components';
|
|||||||
|
|
||||||
import { fromFlashState } from '../../modules/progress-status';
|
import { fromFlashState } from '../../modules/progress-status';
|
||||||
import { StepButton } from '../../styled-components';
|
import { StepButton } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const FlashProgressBar = styled(ProgressBar)`
|
const FlashProgressBar = styled(ProgressBar)`
|
||||||
> div {
|
> div {
|
||||||
@@ -28,6 +29,7 @@ const FlashProgressBar = styled(ProgressBar)`
|
|||||||
color: white !important;
|
color: white !important;
|
||||||
text-shadow: none !important;
|
text-shadow: none !important;
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,7 @@ const colors = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const CancelButton = styled(({ type, onClick, ...props }) => {
|
const CancelButton = styled(({ type, onClick, ...props }) => {
|
||||||
const status = type === 'verifying' ? 'Skip' : 'Cancel';
|
const status = type === 'verifying' ? i18next.t('skip') : i18next.t('cancel');
|
||||||
return (
|
return (
|
||||||
<Button plain onClick={() => onClick(status)} {...props}>
|
<Button plain onClick={() => onClick(status)} {...props}>
|
||||||
{status}
|
{status}
|
||||||
@@ -69,6 +71,7 @@ const CancelButton = styled(({ type, onClick, ...props }) => {
|
|||||||
);
|
);
|
||||||
})`
|
})`
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
&&& {
|
&&& {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -126,7 +129,7 @@ export class ProgressButton extends React.PureComponent<ProgressButtonProps> {
|
|||||||
marginTop: 30,
|
marginTop: 30,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Flash!
|
{i18next.t('flash.flashNow')}
|
||||||
</StepButton>
|
</StepButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import * as settings from '../../models/settings';
|
|||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
import { open as openExternal } from '../../os/open-external/services/open-external';
|
import { open as openExternal } from '../../os/open-external/services/open-external';
|
||||||
import { Modal } from '../../styled-components';
|
import { Modal } from '../../styled-components';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
import { etcherProInfo } from '../../utils/etcher-pro-specific';
|
||||||
|
|
||||||
interface Setting {
|
interface Setting {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -34,13 +36,17 @@ async function getSettingsList(): Promise<Setting[]> {
|
|||||||
const list: Setting[] = [
|
const list: Setting[] = [
|
||||||
{
|
{
|
||||||
name: 'errorReporting',
|
name: 'errorReporting',
|
||||||
label: 'Anonymously report errors and usage statistics to balena.io',
|
label: i18next.t('settings.errorReporting'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'autoBlockmapping',
|
||||||
|
label: i18next.t('settings.trimExtPartitions'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
if (['appimage', 'nsis', 'dmg'].includes(packageType)) {
|
if (['appimage', 'nsis', 'dmg'].includes(packageType)) {
|
||||||
list.push({
|
list.push({
|
||||||
name: 'updatesEnabled',
|
name: 'updatesEnabled',
|
||||||
label: 'Auto-updates enabled',
|
label: i18next.t('settings.autoUpdate'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
@@ -50,7 +56,7 @@ interface SettingsModalProps {
|
|||||||
toggleModal: (value: boolean) => void;
|
toggleModal: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UUID = process.env.BALENA_DEVICE_UUID;
|
const EPInfo = etcherProInfo();
|
||||||
|
|
||||||
const InfoBox = (props: any) => (
|
const InfoBox = (props: any) => (
|
||||||
<Box fontSize={14}>
|
<Box fontSize={14}>
|
||||||
@@ -58,6 +64,7 @@ const InfoBox = (props: any) => (
|
|||||||
<TextWithCopy code text={props.value} copy={props.value} />
|
<TextWithCopy code text={props.value} copy={props.value} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
||||||
const [settingsList, setCurrentSettingsList] = React.useState<Setting[]>([]);
|
const [settingsList, setCurrentSettingsList] = React.useState<Setting[]>([]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -92,7 +99,7 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
|||||||
<Modal
|
<Modal
|
||||||
titleElement={
|
titleElement={
|
||||||
<Txt fontSize={24} mb={24}>
|
<Txt fontSize={24} mb={24}>
|
||||||
Settings
|
{i18next.t('settings.settings')}
|
||||||
</Txt>
|
</Txt>
|
||||||
}
|
}
|
||||||
done={() => toggleModal(false)}
|
done={() => toggleModal(false)}
|
||||||
@@ -111,10 +118,14 @@ export function SettingsModal({ toggleModal }: SettingsModalProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{UUID !== undefined && (
|
{EPInfo !== undefined && (
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<Txt fontSize={24}>System Information</Txt>
|
<Txt fontSize={24}>{i18next.t('settings.systemInformation')}</Txt>
|
||||||
<InfoBox label="UUID" value={UUID.substr(0, 7)} />
|
{EPInfo.get_serial() === undefined ? (
|
||||||
|
<InfoBox label="UUID" value={EPInfo.uuid} />
|
||||||
|
) : (
|
||||||
|
<InfoBox label="Serial" value={EPInfo.get_serial()} />
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import { DriveSelector } from '../drive-selector/drive-selector';
|
|||||||
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import { isJson } from '../../../../shared/utils';
|
import { isJson } from '../../../../shared/utils';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const recentUrlImagesKey = 'recentUrlImages';
|
const recentUrlImagesKey = 'recentUrlImages';
|
||||||
|
|
||||||
@@ -160,7 +161,7 @@ const URLSelector = ({
|
|||||||
primaryButtonProps={{
|
primaryButtonProps={{
|
||||||
disabled: loading || !imageURL,
|
disabled: loading || !imageURL,
|
||||||
}}
|
}}
|
||||||
action={loading ? <Spinner /> : 'OK'}
|
action={loading ? <Spinner /> : i18next.t('ok')}
|
||||||
done={async () => {
|
done={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const urlStrings = recentImages.map((url: URL) => url.href);
|
const urlStrings = recentImages.map((url: URL) => url.href);
|
||||||
@@ -176,11 +177,11 @@ const URLSelector = ({
|
|||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<Flex mb={15} style={{ width: '100%' }} flexDirection="column">
|
<Flex mb={15} style={{ width: '100%' }} flexDirection="column">
|
||||||
<Txt mb="10px" fontSize="24px">
|
<Txt mb="10px" fontSize="24px">
|
||||||
Use Image URL
|
{i18next.t('source.useSourceURL')}
|
||||||
</Txt>
|
</Txt>
|
||||||
<Input
|
<Input
|
||||||
value={imageURL}
|
value={imageURL}
|
||||||
placeholder="Enter a valid URL"
|
placeholder={i18next.t('source.enterValidURL')}
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setImageURL(evt.target.value)
|
setImageURL(evt.target.value)
|
||||||
@@ -205,7 +206,7 @@ const URLSelector = ({
|
|||||||
{!showBasicAuth && (
|
{!showBasicAuth && (
|
||||||
<ChevronRightSvg height="1em" fill="currentColor" />
|
<ChevronRightSvg height="1em" fill="currentColor" />
|
||||||
)}
|
)}
|
||||||
<Txt ml={8}>Authentication</Txt>
|
<Txt ml={8}>{i18next.t('source.auth')}</Txt>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Link>
|
</Link>
|
||||||
{showBasicAuth && (
|
{showBasicAuth && (
|
||||||
@@ -213,7 +214,7 @@ const URLSelector = ({
|
|||||||
<Input
|
<Input
|
||||||
mb={15}
|
mb={15}
|
||||||
value={username}
|
value={username}
|
||||||
placeholder="Enter username"
|
placeholder={i18next.t('source.username')}
|
||||||
type="text"
|
type="text"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setUsername(evt.target.value)
|
setUsername(evt.target.value)
|
||||||
@@ -221,7 +222,7 @@ const URLSelector = ({
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={password}
|
value={password}
|
||||||
placeholder="Enter password"
|
placeholder={i18next.t('source.password')}
|
||||||
type="password"
|
type="password"
|
||||||
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
setPassword(evt.target.value)
|
setPassword(evt.target.value)
|
||||||
@@ -295,7 +296,7 @@ const FlowSelector = styled(
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: ${colors.primary.foreground}!important;
|
color: ${colors.primary.foreground} !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -315,6 +316,7 @@ export interface SourceMetadata extends sourceDestination.Metadata {
|
|||||||
drive?: DrivelistDrive;
|
drive?: DrivelistDrive;
|
||||||
extension?: string;
|
extension?: string;
|
||||||
archiveExtension?: string;
|
archiveExtension?: string;
|
||||||
|
auth?: Authentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SourceSelectorProps {
|
interface SourceSelectorProps {
|
||||||
@@ -452,7 +454,7 @@ export class SourceSelector extends React.Component<
|
|||||||
!isURL(this.normalizeImagePath(selected))
|
!isURL(this.normalizeImagePath(selected))
|
||||||
) {
|
) {
|
||||||
this.handleError(
|
this.handleError(
|
||||||
'Unsupported protocol',
|
i18next.t('source.unsupportedProtocol'),
|
||||||
selected,
|
selected,
|
||||||
messages.error.unsupportedProtocol(),
|
messages.error.unsupportedProtocol(),
|
||||||
);
|
);
|
||||||
@@ -464,7 +466,7 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.looksLikeWindowsImage(),
|
message: messages.warning.looksLikeWindowsImage(),
|
||||||
title: 'Possible Windows image detected',
|
title: i18next.t('source.windowsImage'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -490,13 +492,13 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.missingPartitionTable(),
|
message: messages.warning.missingPartitionTable(),
|
||||||
title: 'Missing partition table',
|
title: i18next.t('source.partitionTable'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.handleError(
|
this.handleError(
|
||||||
'Error opening source',
|
i18next.t('source.errorOpen'),
|
||||||
sourcePath,
|
sourcePath,
|
||||||
messages.error.openSource(sourcePath, error.message),
|
messages.error.openSource(sourcePath, error.message),
|
||||||
error,
|
error,
|
||||||
@@ -514,7 +516,7 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
message: messages.warning.driveMissingPartitionTable(),
|
message: messages.warning.driveMissingPartitionTable(),
|
||||||
title: 'Missing partition table',
|
title: i18next.t('source.partitionTable'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -529,6 +531,7 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (metadata !== undefined) {
|
if (metadata !== undefined) {
|
||||||
|
metadata.auth = auth;
|
||||||
selectionState.selectSource(metadata);
|
selectionState.selectSource(metadata);
|
||||||
analytics.logEvent('Select image', {
|
analytics.logEvent('Select image', {
|
||||||
// An easy way so we can quickly identify if we're making use of
|
// An easy way so we can quickly identify if we're making use of
|
||||||
@@ -717,7 +720,7 @@ export class SourceSelector extends React.Component<
|
|||||||
mb={14}
|
mb={14}
|
||||||
onClick={() => this.reselectSource()}
|
onClick={() => this.reselectSource()}
|
||||||
>
|
>
|
||||||
Remove
|
{i18next.t('cancel')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{!_.isNil(imageSize) && !imageLoading && (
|
{!_.isNil(imageSize) && !imageLoading && (
|
||||||
@@ -732,7 +735,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Flash from file"
|
key="Flash from file"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openImageSelector(),
|
onClick: () => this.openImageSelector(),
|
||||||
label: 'Flash from file',
|
label: i18next.t('source.fromFile'),
|
||||||
icon: <FileSvg height="1em" fill="currentColor" />,
|
icon: <FileSvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -742,7 +745,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Flash from URL"
|
key="Flash from URL"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openURLSelector(),
|
onClick: () => this.openURLSelector(),
|
||||||
label: 'Flash from URL',
|
label: i18next.t('source.fromURL'),
|
||||||
icon: <LinkSvg height="1em" fill="currentColor" />,
|
icon: <LinkSvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -752,7 +755,7 @@ export class SourceSelector extends React.Component<
|
|||||||
key="Clone drive"
|
key="Clone drive"
|
||||||
flow={{
|
flow={{
|
||||||
onClick: () => this.openDriveSelector(),
|
onClick: () => this.openDriveSelector(),
|
||||||
label: 'Clone drive',
|
label: i18next.t('source.clone'),
|
||||||
icon: <CopySvg height="1em" fill="currentColor" />,
|
icon: <CopySvg height="1em" fill="currentColor" />,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
onMouseEnter={() => this.setDefaultFlowActive(false)}
|
||||||
@@ -773,7 +776,7 @@ export class SourceSelector extends React.Component<
|
|||||||
<span>{this.state.warning.title}</span>
|
<span>{this.state.warning.title}</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
action="Continue"
|
action={i18next.t('continue')}
|
||||||
cancel={() => {
|
cancel={() => {
|
||||||
this.setState({ warning: null });
|
this.setState({ warning: null });
|
||||||
this.reselectSource();
|
this.reselectSource();
|
||||||
@@ -791,17 +794,17 @@ export class SourceSelector extends React.Component<
|
|||||||
|
|
||||||
{showImageDetails && (
|
{showImageDetails && (
|
||||||
<SmallModal
|
<SmallModal
|
||||||
title="Image"
|
title={i18next.t('source.image')}
|
||||||
done={() => {
|
done={() => {
|
||||||
this.setState({ showImageDetails: false });
|
this.setState({ showImageDetails: false });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Txt.p>
|
<Txt.p>
|
||||||
<Txt.span bold>Name: </Txt.span>
|
<Txt.span bold>{i18next.t('source.name')}</Txt.span>
|
||||||
<Txt.span>{imageName || imageBasename}</Txt.span>
|
<Txt.span>{imageName || imageBasename}</Txt.span>
|
||||||
</Txt.p>
|
</Txt.p>
|
||||||
<Txt.p>
|
<Txt.p>
|
||||||
<Txt.span bold>Path: </Txt.span>
|
<Txt.span bold>{i18next.t('source.path')}</Txt.span>
|
||||||
<Txt.span>{imagePath}</Txt.span>
|
<Txt.span>{imagePath}</Txt.span>
|
||||||
</Txt.p>
|
</Txt.p>
|
||||||
</SmallModal>
|
</SmallModal>
|
||||||
@@ -840,8 +843,8 @@ export class SourceSelector extends React.Component<
|
|||||||
<DriveSelector
|
<DriveSelector
|
||||||
write={false}
|
write={false}
|
||||||
multipleSelection={false}
|
multipleSelection={false}
|
||||||
titleLabel="Select source"
|
titleLabel={i18next.t('source.selectSource')}
|
||||||
emptyListLabel="Plug a source drive"
|
emptyListLabel={i18next.t('source.plugSource')}
|
||||||
emptyListIcon={<SrcSvg width="40px" />}
|
emptyListIcon={<SrcSvg width="40px" />}
|
||||||
cancel={(originalList) => {
|
cancel={(originalList) => {
|
||||||
if (originalList.length) {
|
if (originalList.length) {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
StepNameButton,
|
StepNameButton,
|
||||||
} from '../../styled-components';
|
} from '../../styled-components';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
interface TargetSelectorProps {
|
interface TargetSelectorProps {
|
||||||
targets: any[];
|
targets: any[];
|
||||||
@@ -95,7 +96,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
</StepNameButton>
|
</StepNameButton>
|
||||||
{!props.flashing && (
|
{!props.flashing && (
|
||||||
<ChangeButton plain mb={14} onClick={props.reselectDrive}>
|
<ChangeButton plain mb={14} onClick={props.reselectDrive}>
|
||||||
Change
|
{i18next.t('target.change')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{target.size != null && (
|
{target.size != null && (
|
||||||
@@ -132,11 +133,11 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StepNameButton plain tooltip={props.tooltip}>
|
<StepNameButton plain tooltip={props.tooltip}>
|
||||||
{targets.length} Targets
|
{targets.length} {i18next.t('target.targets')}
|
||||||
</StepNameButton>
|
</StepNameButton>
|
||||||
{!props.flashing && (
|
{!props.flashing && (
|
||||||
<ChangeButton plain onClick={props.reselectDrive} mb={14}>
|
<ChangeButton plain onClick={props.reselectDrive} mb={14}>
|
||||||
Change
|
{i18next.t('target.change')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{targetsTemplate}
|
{targetsTemplate}
|
||||||
@@ -151,7 +152,7 @@ export function TargetSelectorButton(props: TargetSelectorProps) {
|
|||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
onClick={props.openDriveSelector}
|
onClick={props.openDriveSelector}
|
||||||
>
|
>
|
||||||
Select target
|
{i18next.t('target.selectTarget')}
|
||||||
</StepButton>
|
</StepButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import TgtSvg from '../../../assets/tgt.svg';
|
|||||||
import DriveSvg from '../../../assets/drive.svg';
|
import DriveSvg from '../../../assets/drive.svg';
|
||||||
import { warning } from '../../../../shared/messages';
|
import { warning } from '../../../../shared/messages';
|
||||||
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../../../shared/drive-constraints';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export const getDriveListLabel = () => {
|
export const getDriveListLabel = () => {
|
||||||
return getSelectedDrives()
|
return getSelectedDrives()
|
||||||
@@ -60,8 +61,8 @@ export const TargetSelectorModal = (
|
|||||||
) => (
|
) => (
|
||||||
<DriveSelector
|
<DriveSelector
|
||||||
multipleSelection={true}
|
multipleSelection={true}
|
||||||
titleLabel="Select target"
|
titleLabel={i18next.t('target.selectTarget')}
|
||||||
emptyListLabel="Plug a target drive"
|
emptyListLabel={i18next.t('target.plugTarget')}
|
||||||
emptyListIcon={<TgtSvg width="40px" />}
|
emptyListIcon={<TgtSvg width="40px" />}
|
||||||
showWarnings={true}
|
showWarnings={true}
|
||||||
selectedList={getSelectedDrives()}
|
selectedList={getSelectedDrives()}
|
||||||
|
|||||||
42
lib/gui/app/i18n.ts
Normal file
42
lib/gui/app/i18n.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import * as i18next from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import zh_CN_translation from './i18n/zh-CN';
|
||||||
|
import zh_TW_translation from './i18n/zh-TW';
|
||||||
|
import en_translation from './i18n/en';
|
||||||
|
|
||||||
|
export function langParser() {
|
||||||
|
if (process.env.LANG !== undefined) {
|
||||||
|
// Bypass mocha, where lang-detect don't works
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
|
||||||
|
switch (lang.substr(0, 2)) {
|
||||||
|
case 'zh':
|
||||||
|
if (lang === 'zh-CN' || lang === 'zh-SG') {
|
||||||
|
return 'zh-CN';
|
||||||
|
} // Simplified Chinese
|
||||||
|
else {
|
||||||
|
return 'zh-TW';
|
||||||
|
} // Traditional Chinese
|
||||||
|
default:
|
||||||
|
return lang.substr(0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18next.use(initReactI18next).init({
|
||||||
|
lng: langParser(),
|
||||||
|
fallbackLng: 'en',
|
||||||
|
nonExplicitSupportedLngs: true,
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
'zh-CN': zh_CN_translation,
|
||||||
|
'zh-TW': zh_TW_translation,
|
||||||
|
en: en_translation,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18next;
|
||||||
23
lib/gui/app/i18n/README.md
Normal file
23
lib/gui/app/i18n/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# i18n
|
||||||
|
|
||||||
|
## How it was done
|
||||||
|
|
||||||
|
Using the open-source lib [i18next](https://www.i18next.com/).
|
||||||
|
|
||||||
|
## How to add your own language
|
||||||
|
|
||||||
|
1. Go to `lib/gui/app/i18n` and add a file named `xx.ts` (use the codes mentioned
|
||||||
|
in [the link](https://www.science.co.il/language/Locale-codes.php), and we support styles as `fr`, `de`, `es-ES`
|
||||||
|
and `pt-BR`)
|
||||||
|
.
|
||||||
|
2. Copy the content from an existing translation and start to translate.
|
||||||
|
3. Once done, go to `lib/gui/app/i18n.ts` and add a line of `import xx_translation from './i18n/xx'` after the
|
||||||
|
already-added imports and add `xx: xx_translation` in the `resources` section of `i18next.init()` function.
|
||||||
|
4. Now go to `lib/shared/catalina-sudo/` and copy the `sudo-askpass.osascript-en.js`, change it to
|
||||||
|
be `sudo-askpass.osascript-xx.js` and edit
|
||||||
|
the `'balenaEtcher needs privileged access in order to flash disks.\n\nType your password to allow this.'` line and
|
||||||
|
those `Ok`s and `Cancel`s to your own language.
|
||||||
|
5. If, your language has several variations when they are used in several countries/regions, such as `zh-CN` and `zh-TW`
|
||||||
|
, or `pt-BR` and `pt-PT`, edit
|
||||||
|
the `langParser()` in the `lib/gui/app/i18n.ts` file to meet your need.
|
||||||
|
6. Make a commit, and then a pull request on GitHub.
|
||||||
161
lib/gui/app/i18n/en.ts
Normal file
161
lib/gui/app/i18n/en.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
continue: 'Continue',
|
||||||
|
ok: 'OK',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
skip: 'Skip',
|
||||||
|
sure: "Yes, I'm sure",
|
||||||
|
warning: 'WARNING! ',
|
||||||
|
attention: 'Attention',
|
||||||
|
failed: 'Failed',
|
||||||
|
completed: 'Completed',
|
||||||
|
yesContinue: 'Yes, continue',
|
||||||
|
reallyExit: 'Are you sure you want to close Etcher?',
|
||||||
|
yesExit: 'Yes, quit',
|
||||||
|
progress: {
|
||||||
|
starting: 'Starting...',
|
||||||
|
decompressing: 'Decompressing...',
|
||||||
|
flashing: 'Flashing...',
|
||||||
|
finishing: 'Finishing...',
|
||||||
|
verifying: 'Validating...',
|
||||||
|
failing: 'Failed',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: 'Not recommended',
|
||||||
|
tooSmall: 'Too small',
|
||||||
|
locked: 'Locked',
|
||||||
|
system: 'System drive',
|
||||||
|
containsImage: 'Source drive',
|
||||||
|
largeDrive: 'Large drive',
|
||||||
|
sourceLarger: 'The selected source is {{byte}} larger than this drive.',
|
||||||
|
flashSucceed_one: 'Successful target',
|
||||||
|
flashSucceed_other: 'Successful targets',
|
||||||
|
flashFail_one: 'Failed target',
|
||||||
|
flashFail_other: 'Failed targets',
|
||||||
|
toDrive: 'to {{description}} ({{name}})',
|
||||||
|
toTarget_one: 'to {{num}} target',
|
||||||
|
toTarget_other: 'to {{num}} targets',
|
||||||
|
andFailTarget_one: 'and failed to be flashed to {{num}} target',
|
||||||
|
andFailTarget_other: 'and failed to be flashed to {{num}} targets',
|
||||||
|
succeedTo: '{{name}} was successfully flashed {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'You are currently flashing a drive. Closing Etcher may leave your drive in an unusable state.',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'It looks like you are trying to burn a Windows image.\n\nUnlike other images, Windows images require special processing to be made bootable. We suggest you use a tool specially designed for this purpose, such as <a href="https://rufus.akeo.ie">Rufus</a> (Windows), <a href="https://github.com/slacka/WoeUSB">WoeUSB</a> (Linux), or Boot Camp Assistant (macOS).',
|
||||||
|
image: 'image',
|
||||||
|
drive: 'drive',
|
||||||
|
missingPartitionTable:
|
||||||
|
'It looks like this is not a bootable {{type}}.\n\nThe {{type}} does not appear to contain a partition table, and might not be recognized or bootable by your device.',
|
||||||
|
largeDriveSize:
|
||||||
|
"This is a large drive! Make sure it doesn't contain files that you want to keep.",
|
||||||
|
systemDrive:
|
||||||
|
'Selecting your system drive is dangerous and will erase your drive!',
|
||||||
|
sourceDrive: 'Contains the image you chose to flash',
|
||||||
|
noSpace:
|
||||||
|
'Not enough space on the drive. Please insert larger one and try again.',
|
||||||
|
genericFlashError:
|
||||||
|
'Something went wrong. If it is a compressed image, please check that the archive is not corrupted.\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'The write has been completed successfully but Etcher detected potential corruption issues when reading the image back from the drive. \n\nPlease consider writing the image to a different drive.',
|
||||||
|
openError:
|
||||||
|
'Something went wrong while opening {{source}}.\n\nError: {{error}}',
|
||||||
|
flashError: 'Something went wrong while writing {{image}} {{targets}}.',
|
||||||
|
unplug:
|
||||||
|
"Looks like Etcher lost access to the drive. Did it get unplugged accidentally?\n\nSometimes this error is caused by faulty readers that don't provide stable access to the drive.",
|
||||||
|
cannotWrite:
|
||||||
|
'Looks like Etcher is not able to write to this location of the drive. This error is usually caused by a faulty drive, reader, or port. \n\nPlease try again with another drive, reader, or port.',
|
||||||
|
childWriterDied:
|
||||||
|
'The writer process ended unexpectedly. Please try again, and contact the Etcher team if the problem persists.',
|
||||||
|
badProtocol: 'Only http:// and https:// URLs are supported.',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: 'Select target',
|
||||||
|
plugTarget: 'Plug a target drive',
|
||||||
|
targets: 'Targets',
|
||||||
|
change: 'Change',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: 'Use Image URL',
|
||||||
|
auth: 'Authentication',
|
||||||
|
username: 'Enter username',
|
||||||
|
password: 'Enter password',
|
||||||
|
unsupportedProtocol: 'Unsupported protocol',
|
||||||
|
windowsImage: 'Possible Windows image detected',
|
||||||
|
partitionTable: 'Missing partition table',
|
||||||
|
errorOpen: 'Error opening source',
|
||||||
|
fromFile: 'Flash from file',
|
||||||
|
fromURL: 'Flash from URL',
|
||||||
|
clone: 'Clone drive',
|
||||||
|
image: 'Image',
|
||||||
|
name: 'Name: ',
|
||||||
|
path: 'Path: ',
|
||||||
|
selectSource: 'Select source',
|
||||||
|
plugSource: 'Plug a source drive',
|
||||||
|
osImages: 'OS Images',
|
||||||
|
allFiles: 'All',
|
||||||
|
enterValidURL: 'Enter a valid URL',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: 'Name',
|
||||||
|
size: 'Size',
|
||||||
|
location: 'Location',
|
||||||
|
find: '{{length}} found',
|
||||||
|
select: 'Select {{select}}',
|
||||||
|
showHidden: 'Show {{num}} hidden',
|
||||||
|
systemDriveDanger:
|
||||||
|
'Selecting your system drive is dangerous and will erase your drive!',
|
||||||
|
openInBrowser: '`Etcher will open {{link}} in your browser`',
|
||||||
|
changeTarget: 'Change target',
|
||||||
|
largeDriveWarning: 'You are about to erase an unusually large drive',
|
||||||
|
largeDriveWarningMsg:
|
||||||
|
'Are you sure the selected drive is not a storage drive?',
|
||||||
|
systemDriveWarning: "You are about to erase your computer's drives",
|
||||||
|
systemDriveWarningMsg:
|
||||||
|
'Are you sure you want to flash your system drive?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: 'Flash another',
|
||||||
|
target: 'Target',
|
||||||
|
location: 'Location',
|
||||||
|
error: 'Error',
|
||||||
|
flash: 'Flash',
|
||||||
|
flashNow: 'Flash!',
|
||||||
|
skip: 'Validation has been skipped',
|
||||||
|
moreInfo: 'more info',
|
||||||
|
speedTip:
|
||||||
|
'The speed is calculated by dividing the image size by the flashing time.\nDisk images with ext partitions flash faster as we are able to skip unused parts.',
|
||||||
|
speed: 'Effective speed: {{speed}} MB/s',
|
||||||
|
speedShort: '{{speed}} MB/s',
|
||||||
|
eta: 'ETA: {{eta}}',
|
||||||
|
failedTarget: 'Failed targets',
|
||||||
|
failedRetry: 'Retry failed targets',
|
||||||
|
flashFailed: 'Flash Failed.',
|
||||||
|
flashCompleted: 'Flash Completed!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting:
|
||||||
|
'Anonymously report errors and usage statistics to balena.io',
|
||||||
|
autoUpdate: 'Auto-updates enabled',
|
||||||
|
settings: 'Settings',
|
||||||
|
systemInformation: 'System Information',
|
||||||
|
trimExtPartitions: 'Trim unallocated space on raw images (in ext-type partitions)',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: 'Edit',
|
||||||
|
view: 'View',
|
||||||
|
devTool: 'Toggle Developer Tools',
|
||||||
|
window: 'Window',
|
||||||
|
help: 'Help',
|
||||||
|
pro: 'Etcher Pro',
|
||||||
|
website: 'Etcher Website',
|
||||||
|
issue: 'Report an issue',
|
||||||
|
about: 'About Etcher',
|
||||||
|
hide: 'Hide Etcher',
|
||||||
|
hideOthers: 'Hide Others',
|
||||||
|
unhide: 'Unhide All',
|
||||||
|
quit: 'Quit Etcher',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
152
lib/gui/app/i18n/zh-CN.ts
Normal file
152
lib/gui/app/i18n/zh-CN.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
ok: '好',
|
||||||
|
cancel: '取消',
|
||||||
|
continue: '继续',
|
||||||
|
skip: '跳过',
|
||||||
|
sure: '我确定',
|
||||||
|
warning: '请注意!',
|
||||||
|
attention: '请注意',
|
||||||
|
failed: '失败',
|
||||||
|
completed: '完毕',
|
||||||
|
yesExit: '是的,可以退出',
|
||||||
|
reallyExit: '真的要现在退出 Etcher 吗?',
|
||||||
|
yesContinue: '是的,继续',
|
||||||
|
progress: {
|
||||||
|
starting: '正在启动……',
|
||||||
|
decompressing: '正在解压……',
|
||||||
|
flashing: '正在烧录……',
|
||||||
|
finishing: '正在结束……',
|
||||||
|
verifying: '正在验证……',
|
||||||
|
failing: '失败……',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: '大小不推荐',
|
||||||
|
tooSmall: '空间太小',
|
||||||
|
locked: '被锁定',
|
||||||
|
system: '系统盘',
|
||||||
|
containsImage: '存放源镜像',
|
||||||
|
largeDrive: '很大的磁盘',
|
||||||
|
sourceLarger: '所选的镜像比目标盘大了 {{byte}} 比特。',
|
||||||
|
flashSucceed_one: '烧录成功',
|
||||||
|
flashSucceed_other: '烧录成功',
|
||||||
|
flashFail_one: '烧录失败',
|
||||||
|
flashFail_other: '烧录失败',
|
||||||
|
toDrive: '到 {{description}} ({{name}})',
|
||||||
|
toTarget_one: '到 {{num}} 个目标',
|
||||||
|
toTarget_other: '到 {{num}} 个目标',
|
||||||
|
andFailTarget_one: '并烧录失败了 {{num}} 个目标',
|
||||||
|
andFailTarget_other: '并烧录失败了 {{num}} 个目标',
|
||||||
|
succeedTo: '{{name}} 被成功烧录 {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'您当前正在刷机。 关闭 Etcher 可能会导致您的磁盘无法使用。',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'看起来您正在尝试刻录 Windows 镜像。\n\n与其他镜像不同,Windows 镜像需要特殊处理才能使其可启动。 我们建议您使用专门为此目的设计的工具,例如 <a href="https://rufus.akeo.ie">Rufus</a> (Windows)、<a href="https://github. com/slacka/WoeUSB">WoeUSB</a> (Linux) 或 Boot Camp 助理 (macOS)。',
|
||||||
|
image: '镜像',
|
||||||
|
drive: '磁盘',
|
||||||
|
missingPartitionTable:
|
||||||
|
'看起来这不是一个可启动的{{type}}。\n\n这个{{type}}似乎不包含分区表,因此您的设备可能无法识别或无法正确启动。',
|
||||||
|
largeDriveSize: '这是个很大的磁盘!请检查并确认它不包含对您很重要的信息',
|
||||||
|
systemDrive: '选择系统盘很危险,因为这将会删除你的系统',
|
||||||
|
sourceDrive: '源镜像位于这个分区中',
|
||||||
|
noSpace: '磁盘空间不足。 请插入另一个较大的磁盘并重试。',
|
||||||
|
genericFlashError:
|
||||||
|
'出了点问题。如果源镜像曾被压缩过,请检查它是否已损坏。\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'写入已成功完成,但 Etcher 在从磁盘读取镜像时检测到潜在的损坏问题。 \n\n请考虑将镜像写入其他磁盘。',
|
||||||
|
openError: '打开 {{source}} 时出错。\n\n错误信息: {{error}}',
|
||||||
|
flashError: '烧录 {{image}} {{targets}} 失败。',
|
||||||
|
unplug:
|
||||||
|
'看起来 Etcher 失去了对磁盘的连接。 它是不是被意外拔掉了?\n\n有时这个错误是因为读卡器出了故障。',
|
||||||
|
cannotWrite:
|
||||||
|
'看起来 Etcher 无法写入磁盘的这个位置。 此错误通常是由故障的磁盘、读取器或端口引起的。 \n\n请使用其他磁盘、读卡器或端口重试。',
|
||||||
|
childWriterDied:
|
||||||
|
'写入进程意外崩溃。请再试一次,如果问题仍然存在,请联系 Etcher 团队。',
|
||||||
|
badProtocol: '仅支持 http:// 和 https:// 开头的网址。',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: '选择目标磁盘',
|
||||||
|
plugTarget: '请插入目标磁盘',
|
||||||
|
targets: '个目标',
|
||||||
|
change: '更改',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: '编辑',
|
||||||
|
view: '视图',
|
||||||
|
devTool: '打开开发者工具',
|
||||||
|
window: '窗口',
|
||||||
|
help: '帮助',
|
||||||
|
pro: 'Etcher 专业版',
|
||||||
|
website: 'Etcher 的官网',
|
||||||
|
issue: '提交一个 issue',
|
||||||
|
about: '关于 Etcher',
|
||||||
|
hide: '隐藏 Etcher',
|
||||||
|
hideOthers: '隐藏其它窗口',
|
||||||
|
unhide: '取消隐藏',
|
||||||
|
quit: '退出 Etcher',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: '使用镜像网络地址',
|
||||||
|
auth: '验证',
|
||||||
|
username: '输入用户名',
|
||||||
|
password: '输入密码',
|
||||||
|
unsupportedProtocol: '不支持的协议',
|
||||||
|
windowsImage: '这可能是 Windows 系统镜像',
|
||||||
|
partitionTable: '找不到分区表',
|
||||||
|
errorOpen: '打开源镜像时出错',
|
||||||
|
fromFile: '从文件烧录',
|
||||||
|
fromURL: '从在线地址烧录',
|
||||||
|
clone: '克隆磁盘',
|
||||||
|
image: '镜像信息',
|
||||||
|
name: '名称:',
|
||||||
|
path: '路径:',
|
||||||
|
selectSource: '选择源',
|
||||||
|
plugSource: '请插入源磁盘',
|
||||||
|
osImages: '系统镜像格式',
|
||||||
|
allFiles: '任何文件格式',
|
||||||
|
enterValidURL: '请输入一个正确的地址',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: '名称',
|
||||||
|
size: '大小',
|
||||||
|
location: '位置',
|
||||||
|
find: '找到 {{length}} 个',
|
||||||
|
select: '选定 {{select}}',
|
||||||
|
showHidden: '显示 {{num}} 个隐藏的磁盘',
|
||||||
|
systemDriveDanger: '选择系统盘很危险,因为这将会删除你的系统!',
|
||||||
|
openInBrowser: 'Etcher 会在浏览器中打开 {{link}}',
|
||||||
|
changeTarget: '改变目标',
|
||||||
|
largeDriveWarning: '您即将擦除一个非常大的磁盘',
|
||||||
|
largeDriveWarningMsg: '您确定所选磁盘不是存储磁盘吗?',
|
||||||
|
systemDriveWarning: '您将要擦除系统盘',
|
||||||
|
systemDriveWarningMsg: '您确定要烧录到系统盘吗?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: '烧录另一目标',
|
||||||
|
target: '目标',
|
||||||
|
location: '位置',
|
||||||
|
error: '错误',
|
||||||
|
flash: '烧录',
|
||||||
|
flashNow: '现在烧录!',
|
||||||
|
skip: '跳过了验证',
|
||||||
|
moreInfo: '更多信息',
|
||||||
|
speedTip:
|
||||||
|
'通过将镜像大小除以烧录时间来计算速度。\n由于我们能够跳过未使用的部分,因此具有EXT分区的磁盘镜像烧录速度更快。',
|
||||||
|
speed: '速度:{{speed}} MB/秒',
|
||||||
|
speedShort: '{{speed}} MB/秒',
|
||||||
|
eta: '预计还需要:{{eta}}',
|
||||||
|
failedTarget: '失败的烧录目标',
|
||||||
|
failedRetry: '重试烧录失败目标',
|
||||||
|
flashFailed: '烧录失败。',
|
||||||
|
flashCompleted: '烧录成功!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting: '匿名地向 balena.io 报告运行错误和使用统计',
|
||||||
|
autoUpdate: '自动更新',
|
||||||
|
settings: '软件设置',
|
||||||
|
systemInformation: '系统信息',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
152
lib/gui/app/i18n/zh-TW.ts
Normal file
152
lib/gui/app/i18n/zh-TW.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
const translation = {
|
||||||
|
translation: {
|
||||||
|
ok: '好',
|
||||||
|
cancel: '取消',
|
||||||
|
continue: '繼續',
|
||||||
|
skip: '跳過',
|
||||||
|
sure: '我確定',
|
||||||
|
warning: '請注意!',
|
||||||
|
attention: '請注意',
|
||||||
|
failed: '失敗',
|
||||||
|
completed: '完畢',
|
||||||
|
yesExit: '是的,可以退出',
|
||||||
|
reallyExit: '真的要現在退出 Etcher 嗎?',
|
||||||
|
yesContinue: '是的,繼續',
|
||||||
|
progress: {
|
||||||
|
starting: '正在啓動……',
|
||||||
|
decompressing: '正在解壓……',
|
||||||
|
flashing: '正在燒錄……',
|
||||||
|
finishing: '正在結束……',
|
||||||
|
verifying: '正在驗證……',
|
||||||
|
failing: '失敗……',
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
sizeNotRecommended: '大小不推薦',
|
||||||
|
tooSmall: '空間太小',
|
||||||
|
locked: '被鎖定',
|
||||||
|
system: '系統盤',
|
||||||
|
containsImage: '存放源鏡像',
|
||||||
|
largeDrive: '很大的磁盤',
|
||||||
|
sourceLarger: '所選的鏡像比目標盤大了 {{byte}} 比特。',
|
||||||
|
flashSucceed_one: '燒錄成功',
|
||||||
|
flashSucceed_other: '燒錄成功',
|
||||||
|
flashFail_one: '燒錄失敗',
|
||||||
|
flashFail_other: '燒錄失敗',
|
||||||
|
toDrive: '到 {{description}} ({{name}})',
|
||||||
|
toTarget_one: '到 {{num}} 個目標',
|
||||||
|
toTarget_other: '到 {{num}} 個目標',
|
||||||
|
andFailTarget_one: '並燒錄失敗了 {{num}} 個目標',
|
||||||
|
andFailTarget_other: '並燒錄失敗了 {{num}} 個目標',
|
||||||
|
succeedTo: '{{name}} 被成功燒錄 {{target}}',
|
||||||
|
exitWhileFlashing:
|
||||||
|
'您當前正在刷機。 關閉 Etcher 可能會導致您的磁盤無法使用。',
|
||||||
|
looksLikeWindowsImage:
|
||||||
|
'看起來您正在嘗試刻錄 Windows 鏡像。\n\n與其他鏡像不同,Windows 鏡像需要特殊處理才能使其可啓動。 我們建議您使用專門爲此目的設計的工具,例如 <a href="https://rufus.akeo.ie">Rufus</a> (Windows)、<a href="https://github. com/slacka/WoeUSB">WoeUSB</a> (Linux) 或 Boot Camp 助理 (macOS)。',
|
||||||
|
image: '鏡像',
|
||||||
|
drive: '磁盤',
|
||||||
|
missingPartitionTable:
|
||||||
|
'看起來這不是一個可啓動的{{type}}。\n\n這個{{type}}似乎不包含分區表,因此您的設備可能無法識別或無法正確啓動。',
|
||||||
|
largeDriveSize: '這是個很大的磁盤!請檢查並確認它不包含對您很重要的信息',
|
||||||
|
systemDrive: '選擇系統盤很危險,因爲這將會刪除你的系統',
|
||||||
|
sourceDrive: '源鏡像位於這個分區中',
|
||||||
|
noSpace: '磁盤空間不足。 請插入另一個較大的磁盤並重試。',
|
||||||
|
genericFlashError:
|
||||||
|
'出了點問題。如果源鏡像曾被壓縮過,請檢查它是否已損壞。\n{{error}}',
|
||||||
|
validation:
|
||||||
|
'寫入已成功完成,但 Etcher 在從磁盤讀取鏡像時檢測到潛在的損壞問題。 \n\n請考慮將鏡像寫入其他磁盤。',
|
||||||
|
openError: '打開 {{source}} 時出錯。\n\n錯誤信息: {{error}}',
|
||||||
|
flashError: '燒錄 {{image}} {{targets}} 失敗。',
|
||||||
|
unplug:
|
||||||
|
'看起來 Etcher 失去了對磁盤的連接。 它是不是被意外拔掉了?\n\n有時這個錯誤是因爲讀卡器出了故障。',
|
||||||
|
cannotWrite:
|
||||||
|
'看起來 Etcher 無法寫入磁盤的這個位置。 此錯誤通常是由故障的磁盤、讀取器或端口引起的。 \n\n請使用其他磁盤、讀卡器或端口重試。',
|
||||||
|
childWriterDied:
|
||||||
|
'寫入進程意外崩潰。請再試一次,如果問題仍然存在,請聯繫 Etcher 團隊。',
|
||||||
|
badProtocol: '僅支持 http:// 和 https:// 開頭的網址。',
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
selectTarget: '選擇目標磁盤',
|
||||||
|
plugTarget: '請插入目標磁盤',
|
||||||
|
targets: '個目標',
|
||||||
|
change: '更改',
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
edit: '編輯',
|
||||||
|
view: '視圖',
|
||||||
|
devTool: '打開開發者工具',
|
||||||
|
window: '窗口',
|
||||||
|
help: '幫助',
|
||||||
|
pro: 'Etcher 專業版',
|
||||||
|
website: 'Etcher 的官網',
|
||||||
|
issue: '提交一個 issue',
|
||||||
|
about: '關於 Etcher',
|
||||||
|
hide: '隱藏 Etcher',
|
||||||
|
hideOthers: '隱藏其它窗口',
|
||||||
|
unhide: '取消隱藏',
|
||||||
|
quit: '退出 Etcher',
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
useSourceURL: '使用鏡像網絡地址',
|
||||||
|
auth: '驗證',
|
||||||
|
username: '輸入用戶名',
|
||||||
|
password: '輸入密碼',
|
||||||
|
unsupportedProtocol: '不支持的協議',
|
||||||
|
windowsImage: '這可能是 Windows 系統鏡像',
|
||||||
|
partitionTable: '找不到分區表',
|
||||||
|
errorOpen: '打開源鏡像時出錯',
|
||||||
|
fromFile: '從文件燒錄',
|
||||||
|
fromURL: '從在線地址燒錄',
|
||||||
|
clone: '克隆磁盤',
|
||||||
|
image: '鏡像信息',
|
||||||
|
name: '名稱:',
|
||||||
|
path: '路徑:',
|
||||||
|
selectSource: '選擇源',
|
||||||
|
plugSource: '請插入源磁盤',
|
||||||
|
osImages: '系統鏡像格式',
|
||||||
|
allFiles: '任何文件格式',
|
||||||
|
enterValidURL: '請輸入一個正確的地址',
|
||||||
|
},
|
||||||
|
drives: {
|
||||||
|
name: '名稱',
|
||||||
|
size: '大小',
|
||||||
|
location: '位置',
|
||||||
|
find: '找到 {{length}} 個',
|
||||||
|
select: '選定 {{select}}',
|
||||||
|
showHidden: '顯示 {{num}} 個隱藏的磁盤',
|
||||||
|
systemDriveDanger: '選擇系統盤很危險,因爲這將會刪除你的系統!',
|
||||||
|
openInBrowser: 'Etcher 會在瀏覽器中打開 {{link}}',
|
||||||
|
changeTarget: '改變目標',
|
||||||
|
largeDriveWarning: '您即將擦除一個非常大的磁盤',
|
||||||
|
largeDriveWarningMsg: '您確定所選磁盤不是存儲磁盤嗎?',
|
||||||
|
systemDriveWarning: '您將要擦除系統盤',
|
||||||
|
systemDriveWarningMsg: '您確定要燒錄到系統盤嗎?',
|
||||||
|
},
|
||||||
|
flash: {
|
||||||
|
another: '燒錄另一目標',
|
||||||
|
target: '目標',
|
||||||
|
location: '位置',
|
||||||
|
error: '錯誤',
|
||||||
|
flash: '燒錄',
|
||||||
|
flashNow: '現在燒錄!',
|
||||||
|
skip: '跳過了驗證',
|
||||||
|
moreInfo: '更多信息',
|
||||||
|
speedTip:
|
||||||
|
'通過將鏡像大小除以燒錄時間來計算速度。\n由於我們能夠跳過未使用的部分,因此具有EXT分區的磁盤鏡像燒錄速度更快。',
|
||||||
|
speed: '速度:{{speed}} MB/秒',
|
||||||
|
speedShort: '{{speed}} MB/秒',
|
||||||
|
eta: '預計還需要:{{eta}}',
|
||||||
|
failedTarget: '失敗的燒錄目標',
|
||||||
|
failedRetry: '重試燒錄失敗目標',
|
||||||
|
flashFailed: '燒錄失敗。',
|
||||||
|
flashCompleted: '燒錄成功!',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
errorReporting: '匿名地向 balena.io 報告運行錯誤和使用統計',
|
||||||
|
autoUpdate: '自動更新',
|
||||||
|
settings: '軟件設置',
|
||||||
|
systemInformation: '系統信息',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default translation;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Etcher</title>
|
<title>balenaEtcher</title>
|
||||||
<link rel="stylesheet" type="text/css" href="index.css">
|
<link rel="stylesheet" type="text/css" href="index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Etcher</title>
|
<title>balenaEtcher</title>
|
||||||
<link rel="stylesheet" type="text/css" href="index.css">
|
<link rel="stylesheet" type="text/css" href="index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import * as electron from 'electron';
|
|||||||
import * as sdk from 'etcher-sdk';
|
import * as sdk from 'etcher-sdk';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { DrivelistDrive } from '../../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../../shared/drive-constraints';
|
||||||
|
|
||||||
import { bytesToMegabytes } from '../../../shared/units';
|
import { bytesToMegabytes } from '../../../shared/units';
|
||||||
import { Actions, store } from './store';
|
import { Actions, store } from './store';
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ export function isFlashing(): boolean {
|
|||||||
*/
|
*/
|
||||||
export function setFlashingFlag() {
|
export function setFlashingFlag() {
|
||||||
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
|
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
|
||||||
electron.ipcRenderer.send('disable-screensaver');
|
electron.ipcRenderer.invoke('disable-screensaver');
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: Actions.SET_FLASHING_FLAG,
|
type: Actions.SET_FLASHING_FLAG,
|
||||||
data: {},
|
data: {},
|
||||||
@@ -71,7 +70,7 @@ export function unsetFlashingFlag(results: {
|
|||||||
data: results,
|
data: results,
|
||||||
});
|
});
|
||||||
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
|
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
|
||||||
electron.ipcRenderer.send('enable-screensaver');
|
electron.ipcRenderer.invoke('enable-screensaver');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDevicePaths(devicePaths: string[]) {
|
export function setDevicePaths(devicePaths: string[]) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export interface FlashState {
|
export interface FlashState {
|
||||||
active: number;
|
active: number;
|
||||||
@@ -34,36 +35,45 @@ export function fromFlashState({
|
|||||||
position?: string;
|
position?: string;
|
||||||
} {
|
} {
|
||||||
if (type === undefined) {
|
if (type === undefined) {
|
||||||
return { status: 'Starting...' };
|
return { status: i18next.t('progress.starting') };
|
||||||
} else if (type === 'decompressing') {
|
} else if (type === 'decompressing') {
|
||||||
if (percentage == null) {
|
if (percentage == null) {
|
||||||
return { status: 'Decompressing...' };
|
return { status: i18next.t('progress.decompressing') };
|
||||||
} else {
|
} else {
|
||||||
return { position: `${percentage}%`, status: 'Decompressing...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.decompressing'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else if (type === 'flashing') {
|
} else if (type === 'flashing') {
|
||||||
if (percentage != null) {
|
if (percentage != null) {
|
||||||
if (percentage < 100) {
|
if (percentage < 100) {
|
||||||
return { position: `${percentage}%`, status: 'Flashing...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.flashing'),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return { status: 'Finishing...' };
|
return { status: i18next.t('progress.finishing') };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: 'Flashing...',
|
status: i18next.t('progress.flashing'),
|
||||||
position: `${position ? prettyBytes(position) : ''}`,
|
position: `${position ? prettyBytes(position) : ''}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (type === 'verifying') {
|
} else if (type === 'verifying') {
|
||||||
if (percentage == null) {
|
if (percentage == null) {
|
||||||
return { status: 'Validating...' };
|
return { status: i18next.t('progress.verifying') };
|
||||||
} else if (percentage < 100) {
|
} else if (percentage < 100) {
|
||||||
return { position: `${percentage}%`, status: 'Validating...' };
|
return {
|
||||||
|
position: `${percentage}%`,
|
||||||
|
status: i18next.t('progress.verifying'),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return { status: 'Finishing...' };
|
return { status: i18next.t('progress.finishing') };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { status: 'Failed' };
|
return { status: i18next.t('progress.failing') };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function titleFromFlashState(
|
export function titleFromFlashState(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import * as _ from 'lodash';
|
|||||||
import * as errors from '../../../shared/errors';
|
import * as errors from '../../../shared/errors';
|
||||||
import * as settings from '../../../gui/app/models/settings';
|
import * as settings from '../../../gui/app/models/settings';
|
||||||
import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats';
|
import { SUPPORTED_EXTENSIONS } from '../../../shared/supported-formats';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
async function mountSourceDrive() {
|
async function mountSourceDrive() {
|
||||||
// sourceDrivePath is the name of the link in /dev/disk/by-path
|
// sourceDrivePath is the name of the link in /dev/disk/by-path
|
||||||
@@ -53,11 +54,11 @@ export async function selectImage(): Promise<string | undefined> {
|
|||||||
properties: ['openFile', 'treatPackageAsDirectory'],
|
properties: ['openFile', 'treatPackageAsDirectory'],
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: 'OS Images',
|
name: i18next.t('source.osImages'),
|
||||||
extensions: SUPPORTED_EXTENSIONS,
|
extensions: SUPPORTED_EXTENSIONS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'All',
|
name: i18next.t('source.allFiles'),
|
||||||
extensions: ['*'],
|
extensions: ['*'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -79,8 +80,8 @@ export async function showWarning(options: {
|
|||||||
description: string;
|
description: string;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
_.defaults(options, {
|
_.defaults(options, {
|
||||||
confirmationLabel: 'OK',
|
confirmationLabel: i18next.t('ok'),
|
||||||
rejectionLabel: 'Cancel',
|
rejectionLabel: i18next.t('cancel'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const BUTTONS = [options.confirmationLabel, options.rejectionLabel];
|
const BUTTONS = [options.confirmationLabel, options.rejectionLabel];
|
||||||
@@ -98,7 +99,7 @@ export async function showWarning(options: {
|
|||||||
buttons: BUTTONS,
|
buttons: BUTTONS,
|
||||||
defaultId: BUTTON_REJECTION_INDEX,
|
defaultId: BUTTON_REJECTION_INDEX,
|
||||||
cancelId: BUTTON_REJECTION_INDEX,
|
cancelId: BUTTON_REJECTION_INDEX,
|
||||||
title: 'Attention',
|
title: i18next.t('attention'),
|
||||||
message: options.title,
|
message: options.title,
|
||||||
detail: options.description,
|
detail: options.description,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
|
|
||||||
import FlashSvg from '../../../assets/flash.svg';
|
import FlashSvg from '../../../assets/flash.svg';
|
||||||
import DriveStatusWarningModal from '../../components/drive-status-warning-modal/drive-status-warning-modal';
|
import DriveStatusWarningModal from '../../components/drive-status-warning-modal/drive-status-warning-modal';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const COMPLETED_PERCENTAGE = 100;
|
const COMPLETED_PERCENTAGE = 100;
|
||||||
const SPEED_PRECISION = 2;
|
const SPEED_PRECISION = 2;
|
||||||
@@ -293,9 +294,17 @@ export class FlashStep extends React.PureComponent<
|
|||||||
color="#7e8085"
|
color="#7e8085"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<Txt>{this.props.speed.toFixed(SPEED_PRECISION)} MB/s</Txt>
|
<Txt>
|
||||||
|
{i18next.t('flash.speedShort', {
|
||||||
|
speed: this.props.speed.toFixed(SPEED_PRECISION),
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
{!_.isNil(this.props.eta) && (
|
{!_.isNil(this.props.eta) && (
|
||||||
<Txt>ETA: {formatSeconds(this.props.eta)}</Txt>
|
<Txt>
|
||||||
|
{i18next.t('flash.eta', {
|
||||||
|
eta: formatSeconds(this.props.eta),
|
||||||
|
})}
|
||||||
|
</Txt>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { main } from './app';
|
import { main } from './app';
|
||||||
|
import './i18n';
|
||||||
|
import { langParser } from './i18n';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
ipcRenderer.send('change-lng', langParser());
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.accept('./app', () => {
|
module.hot.accept('./app', () => {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export const Modal = styled(({ style, children, ...props }) => {
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollableFlex flexDirection="column" width="100%" height="90%">
|
<ScrollableFlex flexDirection="column" width="100%" height="90%">
|
||||||
{...children}
|
{children.length ? children.map((c: any) => <>{c}</>) : children}
|
||||||
</ScrollableFlex>
|
</ScrollableFlex>
|
||||||
</ModalBase>
|
</ModalBase>
|
||||||
);
|
);
|
||||||
|
|||||||
73
lib/gui/app/utils/etcher-pro-specific.ts
Normal file
73
lib/gui/app/utils/etcher-pro-specific.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 balena.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Dictionary } from 'lodash';
|
||||||
|
|
||||||
|
type BalenaTag = {
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EtcherPro {
|
||||||
|
private supervisorAddr: string;
|
||||||
|
private supervisorKey: string;
|
||||||
|
private tags: Dictionary<string> | undefined;
|
||||||
|
public uuid: string;
|
||||||
|
|
||||||
|
constructor(supervisorAddr: string, supervisorKey: string) {
|
||||||
|
this.supervisorAddr = supervisorAddr;
|
||||||
|
this.supervisorKey = supervisorKey;
|
||||||
|
this.uuid = (process.env.BALENA_DEVICE_UUID ?? 'NO-UUID').substring(0, 7);
|
||||||
|
this.tags = undefined;
|
||||||
|
this.get_tags().then((tags) => (this.tags = tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_tags(): Promise<Dictionary<string>> {
|
||||||
|
const result = await fetch(
|
||||||
|
this.supervisorAddr + '/v2/device/tags?apikey=' + this.supervisorKey,
|
||||||
|
);
|
||||||
|
const parsed = await result.json();
|
||||||
|
if (parsed['status'] === 'success') {
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
...parsed['tags'].map((tag: BalenaTag) => {
|
||||||
|
return { [tag.name]: tag.value };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get_serial(): string | undefined {
|
||||||
|
if (this.tags) {
|
||||||
|
return this.tags['Serial'];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function etcherProInfo(): EtcherPro | undefined {
|
||||||
|
const BALENA_SUPERVISOR_ADDRESS = process.env.BALENA_SUPERVISOR_ADDRESS;
|
||||||
|
const BALENA_SUPERVISOR_API_KEY = process.env.BALENA_SUPERVISOR_API_KEY;
|
||||||
|
|
||||||
|
if (BALENA_SUPERVISOR_ADDRESS && BALENA_SUPERVISOR_API_KEY) {
|
||||||
|
return new EtcherPro(BALENA_SUPERVISOR_ADDRESS, BALENA_SUPERVISOR_API_KEY);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -21,18 +21,22 @@ import { platform } from 'os';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
|
||||||
|
import './app/i18n';
|
||||||
|
|
||||||
import { packageType, version } from '../../package.json';
|
import { packageType, version } from '../../package.json';
|
||||||
import * as EXIT_CODES from '../shared/exit-codes';
|
import * as EXIT_CODES from '../shared/exit-codes';
|
||||||
import { delay, getConfig } from '../shared/utils';
|
import { delay, getConfig } from '../shared/utils';
|
||||||
import * as settings from './app/models/settings';
|
import * as settings from './app/models/settings';
|
||||||
import { logException } from './app/modules/analytics';
|
import { logException } from './app/modules/analytics';
|
||||||
import { buildWindowMenu } from './menu';
|
import { buildWindowMenu } from './menu';
|
||||||
|
import * as i18n from 'i18next';
|
||||||
|
|
||||||
const customProtocol = 'etcher';
|
const customProtocol = 'etcher';
|
||||||
const scheme = `${customProtocol}://`;
|
const scheme = `${customProtocol}://`;
|
||||||
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
||||||
const packageUpdatable = updatablePackageTypes.includes(packageType);
|
const packageUpdatable = updatablePackageTypes.includes(packageType);
|
||||||
let packageUpdated = false;
|
let packageUpdated = false;
|
||||||
|
let mainWindow: any = null;
|
||||||
|
|
||||||
async function checkForUpdates(interval: number) {
|
async function checkForUpdates(interval: number) {
|
||||||
// We use a while loop instead of a setInterval to preserve
|
// We use a while loop instead of a setInterval to preserve
|
||||||
@@ -43,7 +47,7 @@ async function checkForUpdates(interval: number) {
|
|||||||
const release = await autoUpdater.checkForUpdates();
|
const release = await autoUpdater.checkForUpdates();
|
||||||
const isOutdated =
|
const isOutdated =
|
||||||
semver.compare(release.updateInfo.version, version) > 0;
|
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) {
|
if (shouldUpdate && isOutdated) {
|
||||||
await autoUpdater.downloadUpdate();
|
await autoUpdater.downloadUpdate();
|
||||||
packageUpdated = true;
|
packageUpdated = true;
|
||||||
@@ -130,7 +134,7 @@ async function createMainWindow() {
|
|||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
({ width, height } = electron.screen.getPrimaryDisplay().bounds);
|
({ width, height } = electron.screen.getPrimaryDisplay().bounds);
|
||||||
}
|
}
|
||||||
const mainWindow = new electron.BrowserWindow({
|
mainWindow = new electron.BrowserWindow({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
frame: !fullscreen,
|
frame: !fullscreen,
|
||||||
@@ -157,7 +161,6 @@ async function createMainWindow() {
|
|||||||
|
|
||||||
electron.app.setAsDefaultProtocolClient(customProtocol);
|
electron.app.setAsDefaultProtocolClient(customProtocol);
|
||||||
|
|
||||||
buildWindowMenu(mainWindow);
|
|
||||||
mainWindow.setFullScreen(true);
|
mainWindow.setFullScreen(true);
|
||||||
|
|
||||||
// Prevent flash of white when starting the application
|
// Prevent flash of white when starting the application
|
||||||
@@ -240,6 +243,17 @@ async function main(): Promise<void> {
|
|||||||
await selectImageURL(await getCommandLineURL(argv));
|
await selectImageURL(await getCommandLineURL(argv));
|
||||||
});
|
});
|
||||||
await selectImageURL(await getCommandLineURL(process.argv));
|
await selectImageURL(await getCommandLineURL(process.argv));
|
||||||
|
|
||||||
|
electron.ipcMain.on('change-lng', function (event, args) {
|
||||||
|
i18n.changeLanguage(args, () => {
|
||||||
|
console.log('Language changed to: ' + args);
|
||||||
|
});
|
||||||
|
if (mainWindow != null) {
|
||||||
|
buildWindowMenu(mainWindow);
|
||||||
|
} else {
|
||||||
|
console.log('Build menu failed. ');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import { displayName } from '../../package.json';
|
import { displayName } from '../../package.json';
|
||||||
|
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Builds a native application menu for a given window
|
* @summary Builds a native application menu for a given window
|
||||||
*/
|
*/
|
||||||
@@ -42,12 +44,13 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
const menuTemplate: electron.MenuItemConstructorOptions[] = [
|
const menuTemplate: electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
role: 'editMenu',
|
role: 'editMenu',
|
||||||
|
label: i18next.t('menu.edit'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'View',
|
label: i18next.t('menu.view'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Toggle Developer Tools',
|
label: i18next.t('menu.devTool'),
|
||||||
accelerator:
|
accelerator:
|
||||||
process.platform === 'darwin' ? 'Command+Alt+I' : 'Control+Shift+I',
|
process.platform === 'darwin' ? 'Command+Alt+I' : 'Control+Shift+I',
|
||||||
click: toggleDevTools,
|
click: toggleDevTools,
|
||||||
@@ -56,12 +59,14 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'windowMenu',
|
role: 'windowMenu',
|
||||||
|
label: i18next.t('menu.window'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'help',
|
role: 'help',
|
||||||
|
label: i18next.t('menu.help'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Etcher Pro',
|
label: i18next.t('menu.pro'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal(
|
electron.shell.openExternal(
|
||||||
'https://etcher.io/pro?utm_source=etcher_menu&ref=etcher_menu',
|
'https://etcher.io/pro?utm_source=etcher_menu&ref=etcher_menu',
|
||||||
@@ -69,13 +74,13 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Etcher Website',
|
label: i18next.t('menu.website'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal('https://etcher.io?ref=etcher_menu');
|
electron.shell.openExternal('https://etcher.io?ref=etcher_menu');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report an issue',
|
label: i18next.t('menu.issue'),
|
||||||
click() {
|
click() {
|
||||||
electron.shell.openExternal(
|
electron.shell.openExternal(
|
||||||
'https://github.com/balena-io/etcher/issues',
|
'https://github.com/balena-io/etcher/issues',
|
||||||
@@ -92,25 +97,29 @@ export function buildWindowMenu(window: electron.BrowserWindow) {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
role: 'about' as const,
|
role: 'about' as const,
|
||||||
label: 'About Etcher',
|
label: i18next.t('menu.about'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator' as const,
|
type: 'separator' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'hide' as const,
|
role: 'hide' as const,
|
||||||
|
label: i18next.t('menu.hide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'hideOthers' as const,
|
role: 'hideOthers' as const,
|
||||||
|
label: i18next.t('menu.hideOthers'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'unhide' as const,
|
role: 'unhide' as const,
|
||||||
|
label: i18next.t('menu.unhide'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator' as const,
|
type: 'separator' as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'quit' as const,
|
role: 'quit' as const,
|
||||||
|
label: i18next.t('menu.quit'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -291,9 +291,14 @@ ipc.connectTo(IPC_SERVER_ID, () => {
|
|||||||
url: imagePathObject.url,
|
url: imagePathObject.url,
|
||||||
avoidRandomAccess: true,
|
avoidRandomAccess: true,
|
||||||
axiosInstance: axios.create(_.omit(imagePathObject, ['url'])),
|
axiosInstance: axios.create(_.omit(imagePathObject, ['url'])),
|
||||||
|
auth: options.image.auth,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
source = new Http({ url: imagePath, avoidRandomAccess: true });
|
source = new Http({
|
||||||
|
url: imagePath,
|
||||||
|
avoidRandomAccess: true,
|
||||||
|
auth: options.image.auth,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
Executable file
21
lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env osascript -l JavaScript
|
||||||
|
|
||||||
|
ObjC.import('stdlib')
|
||||||
|
|
||||||
|
const app = Application.currentApplication()
|
||||||
|
app.includeStandardAdditions = true
|
||||||
|
|
||||||
|
const result = app.displayDialog('balenaEtcher 需要来自管理员的权限才能烧录镜像到磁盘。\n\n输入您的密码以允许此操作。', {
|
||||||
|
defaultAnswer: '',
|
||||||
|
withIcon: 'caution',
|
||||||
|
buttons: ['取消', '好'],
|
||||||
|
defaultButton: '好',
|
||||||
|
hiddenAnswer: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.buttonReturned === '好') {
|
||||||
|
result.textReturned
|
||||||
|
} else {
|
||||||
|
$.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,9 @@ export async function sudo(
|
|||||||
command: string,
|
command: string,
|
||||||
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
||||||
try {
|
try {
|
||||||
|
let lang = Intl.DateTimeFormat().resolvedOptions().locale;
|
||||||
|
lang = lang.substr(0, 2);
|
||||||
|
|
||||||
const { stdout, stderr } = await execFileAsync(
|
const { stdout, stderr } = await execFileAsync(
|
||||||
'sudo',
|
'sudo',
|
||||||
['--askpass', 'sh', '-c', `echo ${SUCCESSFUL_AUTH_MARKER} && ${command}`],
|
['--askpass', 'sh', '-c', `echo ${SUCCESSFUL_AUTH_MARKER} && ${command}`],
|
||||||
@@ -40,7 +43,7 @@ export async function sudo(
|
|||||||
SUDO_ASKPASS: join(
|
SUDO_ASKPASS: join(
|
||||||
getAppPath(),
|
getAppPath(),
|
||||||
__dirname,
|
__dirname,
|
||||||
'sudo-askpass.osascript.js',
|
'sudo-askpass.osascript-' + lang + '.js',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import { outdent } from 'outdent';
|
import { outdent } from 'outdent';
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
|
import '../gui/app/i18n';
|
||||||
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
export const progress: Dictionary<(quantity: number) => string> = {
|
export const progress: Dictionary<(quantity: number) => string> = {
|
||||||
successful: (quantity: number) => {
|
successful: (quantity: number) => {
|
||||||
const plural = quantity === 1 ? '' : 's';
|
return i18next.t('message.flashSucceed', { count: quantity });
|
||||||
return `Successful target${plural}`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
failed: (quantity: number) => {
|
failed: (quantity: number) => {
|
||||||
const plural = quantity === 1 ? '' : 's';
|
return i18next.t('message.flashFail', { count: quantity });
|
||||||
return `Failed target${plural}`;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,129 +38,121 @@ export const info = {
|
|||||||
) => {
|
) => {
|
||||||
const targets = [];
|
const targets = [];
|
||||||
if (failed + successful === 1) {
|
if (failed + successful === 1) {
|
||||||
targets.push(`to ${drive.description} (${drive.displayName})`);
|
targets.push(
|
||||||
|
i18next.t('message.toDrive', {
|
||||||
|
description: drive.description,
|
||||||
|
name: drive.displayName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (successful) {
|
if (successful) {
|
||||||
const plural = successful === 1 ? '' : 's';
|
targets.push(
|
||||||
targets.push(`to ${successful} target${plural}`);
|
i18next.t('message.toTarget', {
|
||||||
|
count: successful,
|
||||||
|
num: successful,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (failed) {
|
if (failed) {
|
||||||
const plural = failed === 1 ? '' : 's';
|
targets.push(
|
||||||
targets.push(`and failed to be flashed to ${failed} target${plural}`);
|
i18next.t('message.andFailTarget', { count: failed, num: failed }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return `${imageBasename} was successfully flashed ${targets.join(' ')}`;
|
return i18next.t('message.succeedTo', {
|
||||||
|
name: imageBasename,
|
||||||
|
target: targets.join(' '),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const compatibility = {
|
export const compatibility = {
|
||||||
sizeNotRecommended: () => {
|
sizeNotRecommended: () => {
|
||||||
return 'Not recommended';
|
return i18next.t('message.sizeNotRecommended');
|
||||||
},
|
},
|
||||||
|
|
||||||
tooSmall: () => {
|
tooSmall: () => {
|
||||||
return 'Too small';
|
return i18next.t('message.tooSmall');
|
||||||
},
|
},
|
||||||
|
|
||||||
locked: () => {
|
locked: () => {
|
||||||
return 'Locked';
|
return i18next.t('message.locked');
|
||||||
},
|
},
|
||||||
|
|
||||||
system: () => {
|
system: () => {
|
||||||
return 'System drive';
|
return i18next.t('message.system');
|
||||||
},
|
},
|
||||||
|
|
||||||
containsImage: () => {
|
containsImage: () => {
|
||||||
return 'Source drive';
|
return i18next.t('message.containsImage');
|
||||||
},
|
},
|
||||||
|
|
||||||
// The drive is large and therefore likely not a medium you want to write to.
|
// The drive is large and therefore likely not a medium you want to write to.
|
||||||
largeDrive: () => {
|
largeDrive: () => {
|
||||||
return 'Large drive';
|
return i18next.t('message.largeDrive');
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const warning = {
|
export const warning = {
|
||||||
tooSmall: (source: { size: number }, target: { size: number }) => {
|
tooSmall: (source: { size: number }, target: { size: number }) => {
|
||||||
return outdent({ newline: ' ' })`
|
return outdent({ newline: ' ' })`
|
||||||
The selected source is ${prettyBytes(source.size - target.size)}
|
${i18next.t('message.sourceLarger', {
|
||||||
larger than this drive.
|
byte: prettyBytes(source.size - target.size),
|
||||||
|
})}
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
exitWhileFlashing: () => {
|
exitWhileFlashing: () => {
|
||||||
return [
|
return i18next.t('message.exitWhileFlashing');
|
||||||
'You are currently flashing a drive.',
|
|
||||||
'Closing Etcher may leave your drive in an unusable state.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
looksLikeWindowsImage: () => {
|
looksLikeWindowsImage: () => {
|
||||||
return [
|
return i18next.t('message.looksLikeWindowsImage');
|
||||||
'It looks like you are trying to burn a Windows image.\n\n',
|
|
||||||
'Unlike other images, Windows images require special processing to be made bootable.',
|
|
||||||
'We suggest you use a tool specially designed for this purpose, such as',
|
|
||||||
'<a href="https://rufus.akeo.ie">Rufus</a> (Windows),',
|
|
||||||
'<a href="https://github.com/slacka/WoeUSB">WoeUSB</a> (Linux),',
|
|
||||||
'or Boot Camp Assistant (macOS).',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
missingPartitionTable: () => {
|
missingPartitionTable: () => {
|
||||||
return [
|
return i18next.t('message.missingPartitionTable', {
|
||||||
'It looks like this is not a bootable image.\n\n',
|
type: i18next.t('message.image'),
|
||||||
'The image does not appear to contain a partition table,',
|
});
|
||||||
'and might not be recognized or bootable by your device.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
driveMissingPartitionTable: () => {
|
driveMissingPartitionTable: () => {
|
||||||
return outdent({ newline: ' ' })`
|
return i18next.t('message.missingPartitionTable', {
|
||||||
It looks like this is not a bootable drive.
|
type: i18next.t('message.drive'),
|
||||||
The drive does not appear to contain a partition table,
|
});
|
||||||
and might not be recognized or bootable by your device.
|
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
largeDriveSize: () => {
|
largeDriveSize: () => {
|
||||||
return "This is a large drive! Make sure it doesn't contain files that you want to keep.";
|
return i18next.t('message.largeDriveSize');
|
||||||
},
|
},
|
||||||
|
|
||||||
systemDrive: () => {
|
systemDrive: () => {
|
||||||
return 'Selecting your system drive is dangerous and will erase your drive!';
|
return i18next.t('message.systemDrive');
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceDrive: () => {
|
sourceDrive: () => {
|
||||||
return 'Contains the image you chose to flash';
|
return i18next.t('message.sourceDrive');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const error = {
|
export const error = {
|
||||||
notEnoughSpaceInDrive: () => {
|
notEnoughSpaceInDrive: () => {
|
||||||
return [
|
return i18next.t('message.noSpace');
|
||||||
'Not enough space on the drive.',
|
|
||||||
'Please insert larger one and try again.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
genericFlashError: (err: Error) => {
|
genericFlashError: (err: Error) => {
|
||||||
return `Something went wrong. If it is a compressed image, please check that the archive is not corrupted.\n${err.message}`;
|
return i18next.t('message.genericFlashError', { error: err.message });
|
||||||
},
|
},
|
||||||
|
|
||||||
validation: () => {
|
validation: () => {
|
||||||
return [
|
return i18next.t('message.validation');
|
||||||
'The write has been completed successfully but Etcher detected potential',
|
|
||||||
'corruption issues when reading the image back from the drive.',
|
|
||||||
'\n\nPlease consider writing the image to a different drive.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
openSource: (sourceName: string, errorMessage: string) => {
|
openSource: (sourceName: string, errorMessage: string) => {
|
||||||
return outdent`
|
return i18next.t('message.openError', {
|
||||||
Something went wrong while opening ${sourceName}
|
source: sourceName,
|
||||||
|
error: errorMessage,
|
||||||
Error: ${errorMessage}
|
});
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
flashFailure: (
|
flashFailure: (
|
||||||
@@ -169,35 +161,33 @@ export const error = {
|
|||||||
) => {
|
) => {
|
||||||
const target =
|
const target =
|
||||||
drives.length === 1
|
drives.length === 1
|
||||||
? `${drives[0].description} (${drives[0].displayName})`
|
? i18next.t('message.toDrive', {
|
||||||
: `${drives.length} targets`;
|
description: drives[0].description,
|
||||||
return `Something went wrong while writing ${imageBasename} to ${target}.`;
|
name: drives[0].displayName,
|
||||||
|
})
|
||||||
|
: i18next.t('message.toTarget', {
|
||||||
|
count: drives.length,
|
||||||
|
num: drives.length,
|
||||||
|
});
|
||||||
|
return i18next.t('message.flashError', {
|
||||||
|
image: imageBasename,
|
||||||
|
targets: target,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
driveUnplugged: () => {
|
driveUnplugged: () => {
|
||||||
return [
|
return i18next.t('message.unplug');
|
||||||
'Looks like Etcher lost access to the drive.',
|
|
||||||
'Did it get unplugged accidentally?',
|
|
||||||
"\n\nSometimes this error is caused by faulty readers that don't provide stable access to the drive.",
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
inputOutput: () => {
|
inputOutput: () => {
|
||||||
return [
|
return i18next.t('message.cannotWrite');
|
||||||
'Looks like Etcher is not able to write to this location of the drive.',
|
|
||||||
'This error is usually caused by a faulty drive, reader, or port.',
|
|
||||||
'\n\nPlease try again with another drive, reader, or port.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
childWriterDied: () => {
|
childWriterDied: () => {
|
||||||
return [
|
return i18next.t('message.childWriterDied');
|
||||||
'The writer process ended unexpectedly.',
|
|
||||||
'Please try again, and contact the Etcher team if the problem persists.',
|
|
||||||
].join(' ');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unsupportedProtocol: () => {
|
unsupportedProtocol: () => {
|
||||||
return 'Only http:// and https:// URLs are supported.';
|
return i18next.t('message.badProtocol');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
8190
package-lock.json
generated
8190
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
144
package.json
144
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "balena-etcher",
|
"name": "balena-etcher",
|
||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "balenaEtcher",
|
"displayName": "balenaEtcher",
|
||||||
"version": "1.7.4",
|
"version": "1.13.4",
|
||||||
"packageType": "local",
|
"packageType": "local",
|
||||||
"main": "generated/etcher.js",
|
"main": "generated/etcher.js",
|
||||||
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
||||||
@@ -13,21 +13,27 @@
|
|||||||
"url": "git@github.com:balena-io/etcher.git"
|
"url": "git@github.com:balena-io/etcher.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts",
|
"build": "npm run webpack",
|
||||||
|
"flowzone-preinstall-linux": "sudo apt-get install -y xvfb libudev-dev && cat < electron-builder.yml | yq e .deb.depends[] - | xargs -L1 echo | sed 's/|//g' | xargs -L1 sudo apt-get --ignore-missing install || true",
|
||||||
|
"flowzone-preinstall-macos": "true",
|
||||||
|
"flowzone-preinstall-windows": "true",
|
||||||
|
"flowzone-preinstall": "npm run flowzone-preinstall-linux",
|
||||||
"lint-css": "prettier --write lib/**/*.css",
|
"lint-css": "prettier --write lib/**/*.css",
|
||||||
|
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts",
|
||||||
"lint": "npm run lint-ts && npm run lint-css",
|
"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",
|
"postinstall": "electron-rebuild -t prod,dev,optional",
|
||||||
"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",
|
"sanity-checks": "bash scripts/ci/ensure-all-file-extensions-in-gitattributes.sh",
|
||||||
"start": "./node_modules/.bin/electron .",
|
"start": "./node_modules/.bin/electron .",
|
||||||
"postinstall": "electron-rebuild -t prod,dev,optional",
|
"test-macos": "npm run lint && npm run test-gui && npm run test-shared && npm run test-spectron && npm run sanity-checks",
|
||||||
"webpack": "webpack",
|
"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-linux": "npm run lint && xvfb-run --auto-servernum npm run test-gui && xvfb-run --auto-servernum npm run test-shared && xvfb-run --auto-servernum npm run test-spectron && npm run sanity-checks",
|
||||||
|
"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-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-windows": "npm run lint && npm run test-gui && npm run test-shared && npm run test-spectron && npm run sanity-checks",
|
||||||
|
"test": "echo npm run test-{linux,windows,macos}",
|
||||||
|
"uploadSourcemap": "sentry-cli releases files $npm_config_SENTRY_VERSION upload-sourcemaps ./generated/*.js.map --org $npm_config_SENTRY_ORG --project $npm_config_SENTRY_PROJECT",
|
||||||
"watch": "webpack serve --no-optimization-minimize --config ./webpack.dev.config.ts",
|
"watch": "webpack serve --no-optimization-minimize --config ./webpack.dev.config.ts",
|
||||||
"concourse-build-electron": "npm run webpack",
|
"webpack": "webpack"
|
||||||
"concourse-test": "npx npm@6.14.8 test",
|
|
||||||
"concourse-test-electron": "npx npm@6.14.8 test"
|
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@@ -45,77 +51,83 @@
|
|||||||
"author": "Balena Inc. <hello@etcher.io>",
|
"author": "Balena Inc. <hello@etcher.io>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@balena/lint": "5.3.0",
|
"@balena/lint": "5.4.2",
|
||||||
"@balena/sudo-prompt": "9.2.1-workaround-windows-amperstand-in-username-0849e215b947987a643fe5763902aea201255534",
|
"@balena/sudo-prompt": "9.2.1-workaround-windows-amperstand-in-username-0849e215b947987a643fe5763902aea201255534",
|
||||||
"@fortawesome/fontawesome-free": "5.13.1",
|
"@fortawesome/fontawesome-free": "5.15.4",
|
||||||
|
"@sentry/cli": "^2.11.0",
|
||||||
"@svgr/webpack": "5.5.0",
|
"@svgr/webpack": "5.5.0",
|
||||||
"@types/chai": "4.2.7",
|
"@types/chai": "4.3.4",
|
||||||
"@types/copy-webpack-plugin": "6.0.0",
|
"@types/copy-webpack-plugin": "6.4.3",
|
||||||
"@types/mime-types": "2.1.0",
|
"@types/mime-types": "2.1.1",
|
||||||
"@types/mini-css-extract-plugin": "1.2.2",
|
"@types/mini-css-extract-plugin": "1.4.3",
|
||||||
"@types/mocha": "8.0.3",
|
"@types/mocha": "8.2.3",
|
||||||
"@types/node": "14.14.41",
|
"@types/node": "14.18.34",
|
||||||
"@types/node-ipc": "9.1.2",
|
"@types/node-ipc": "9.2.0",
|
||||||
"@types/react": "16.8.5",
|
"@types/react": "16.14.34",
|
||||||
"@types/react-dom": "16.8.4",
|
"@types/react-dom": "16.9.17",
|
||||||
"@types/semver": "7.1.0",
|
"@types/semver": "7.3.13",
|
||||||
"@types/sinon": "9.0.0",
|
"@types/sinon": "9.0.11",
|
||||||
"@types/terser-webpack-plugin": "5.0.2",
|
"@types/terser-webpack-plugin": "5.0.4",
|
||||||
"@types/tmp": "0.2.0",
|
"@types/tmp": "0.2.3",
|
||||||
"@types/webpack-node-externals": "2.5.0",
|
"@types/webpack-node-externals": "2.5.3",
|
||||||
"aws4-axios": "2.2.1",
|
"aws4-axios": "2.4.9",
|
||||||
"chai": "4.2.0",
|
"chai": "4.3.7",
|
||||||
"copy-webpack-plugin": "7.0.0",
|
"copy-webpack-plugin": "7.0.0",
|
||||||
"css-loader": "5.0.1",
|
"css-loader": "5.2.7",
|
||||||
"d3": "4.13.0",
|
"d3": "4.13.0",
|
||||||
"debug": "4.2.0",
|
"debug": "4.3.4",
|
||||||
"electron": "12.2.3",
|
"electron": "^13.5.0",
|
||||||
"electron-builder": "22.10.5",
|
"electron-builder": "^23.0.9",
|
||||||
"electron-mocha": "9.3.2",
|
"electron-mocha": "9.3.3",
|
||||||
"electron-notarize": "1.0.0",
|
"electron-notarize": "1.2.2",
|
||||||
"electron-rebuild": "3.2.5",
|
"electron-rebuild": "3.2.9",
|
||||||
"electron-updater": "4.3.5",
|
"electron-updater": "5.3.0",
|
||||||
"esbuild-loader": "2.16.0",
|
"esbuild-loader": "2.20.0",
|
||||||
"etcher-sdk": "6.3.0",
|
"etcher-sdk": "^7.4.7",
|
||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"husky": "4.2.5",
|
"husky": "4.3.8",
|
||||||
"immutable": "3.8.1",
|
"i18next": "21.10.0",
|
||||||
"lint-staged": "10.2.2",
|
"immutable": "3.8.2",
|
||||||
"lodash": "4.17.10",
|
"lint-staged": "10.5.4",
|
||||||
"mini-css-extract-plugin": "1.3.3",
|
"lodash": "4.17.21",
|
||||||
"mocha": "8.0.1",
|
"mini-css-extract-plugin": "1.6.2",
|
||||||
|
"mocha": "8.4.0",
|
||||||
"native-addon-loader": "2.0.1",
|
"native-addon-loader": "2.0.1",
|
||||||
"node-ipc": "9.1.1",
|
"node-ipc": "9.2.1",
|
||||||
"omit-deep-lodash": "1.1.4",
|
"omit-deep-lodash": "1.1.7",
|
||||||
"outdent": "0.7.1",
|
"outdent": "0.8.0",
|
||||||
"path-is-inside": "1.0.2",
|
"path-is-inside": "1.0.2",
|
||||||
"pnp-webpack-plugin": "1.6.4",
|
"pnp-webpack-plugin": "1.7.0",
|
||||||
"pretty-bytes": "5.3.0",
|
"pretty-bytes": "5.6.0",
|
||||||
"react": "16.8.5",
|
"react": "16.8.5",
|
||||||
"react-dom": "16.8.5",
|
"react-dom": "16.8.5",
|
||||||
"redux": "4.0.5",
|
"react-i18next": "11.18.6",
|
||||||
"rendition": "19.2.0",
|
"redux": "4.2.0",
|
||||||
|
"rendition": "19.3.2",
|
||||||
"resin-corvus": "2.0.5",
|
"resin-corvus": "2.0.5",
|
||||||
"semver": "7.3.2",
|
"semver": "7.3.8",
|
||||||
"simple-progress-webpack-plugin": "1.1.2",
|
"simple-progress-webpack-plugin": "1.1.2",
|
||||||
"sinon": "9.0.2",
|
"sinon": "9.2.4",
|
||||||
"spectron": "14.0.0",
|
"spectron": "15.0.0",
|
||||||
"string-replace-loader": "3.0.1",
|
"string-replace-loader": "3.1.0",
|
||||||
"style-loader": "2.0.0",
|
"style-loader": "2.0.0",
|
||||||
"styled-components": "5.1.0",
|
"styled-components": "5.3.6",
|
||||||
"sys-class-rgb-led": "3.0.0",
|
"sys-class-rgb-led": "3.0.1",
|
||||||
"terser-webpack-plugin": "5.2.5",
|
"terser-webpack-plugin": "5.3.6",
|
||||||
"ts-loader": "8.0.12",
|
"ts-loader": "8.4.0",
|
||||||
"ts-node": "9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"tslib": "2.0.0",
|
"tslib": "2.4.1",
|
||||||
"typescript": "4.4.4",
|
"typescript": "4.4.4",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"uuid": "8.1.0",
|
"uuid": "8.3.2",
|
||||||
"webpack": "5.11.0",
|
"webpack": "5.75.0",
|
||||||
"webpack-cli": "4.2.0",
|
"webpack-cli": "4.10.0",
|
||||||
"webpack-dev-server": "4.5.0"
|
"webpack-dev-server": "4.11.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14 < 16"
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2022-02-21T08:33:45.910Z"
|
"publishedAt": "2023-01-12T15:10:50.986Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
repo.yml
13
repo.yml
@@ -1,17 +1,18 @@
|
|||||||
|
---
|
||||||
type: electron
|
type: electron
|
||||||
release: github
|
release: github
|
||||||
publishMetadata: true
|
publishMetadata: true
|
||||||
sentry:
|
sentry:
|
||||||
org: balenaetcher
|
org: balenaetcher
|
||||||
team: resinio
|
team: resinio
|
||||||
type: electron
|
type: electron
|
||||||
triggerNotification:
|
triggerNotification:
|
||||||
version: 1.7.3
|
version: 1.7.9
|
||||||
stagingPercentage: 100
|
stagingPercentage: 100
|
||||||
upstream:
|
upstream:
|
||||||
- repo: etcher-sdk
|
- repo: etcher-sdk
|
||||||
url: https://github.com/balena-io-modules/etcher-sdk
|
url: https://github.com/balena-io-modules/etcher-sdk
|
||||||
module: 'etcher-sdk'
|
module: etcher-sdk
|
||||||
- repo: sys-class-rgb-led
|
- repo: sys-class-rgb-led
|
||||||
url: https://github.com/balena-io-modules/sys-class-rgb-led
|
url: https://github.com/balena-io-modules/sys-class-rgb-led
|
||||||
module: sys-class-rgb-led
|
module: sys-class-rgb-led
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
awscli==1.11.87
|
awscli==1.27.28
|
||||||
shyaml==0.5.0
|
shyaml==0.6.2
|
||||||
|
|||||||
BIN
secrets/APPLE_SIGNING.p12.secret
Normal file
BIN
secrets/APPLE_SIGNING.p12.secret
Normal file
Binary file not shown.
BIN
secrets/APPLE_SIGNING_PASSWORD.txt.secret
Normal file
BIN
secrets/APPLE_SIGNING_PASSWORD.txt.secret
Normal file
Binary file not shown.
BIN
secrets/WINDOWS_SIGNING.pfx.secret
Normal file
BIN
secrets/WINDOWS_SIGNING.pfx.secret
Normal file
Binary file not shown.
BIN
secrets/WINDOWS_SIGNING_PASSWORD.txt.secret
Normal file
BIN
secrets/WINDOWS_SIGNING_PASSWORD.txt.secret
Normal file
Binary file not shown.
BIN
secrets/XCODE_APP_LOADER_PASSWORD.txt.secret
Normal file
BIN
secrets/XCODE_APP_LOADER_PASSWORD.txt.secret
Normal file
Binary file not shown.
@@ -59,7 +59,7 @@ if (platform() !== 'darwin') {
|
|||||||
|
|
||||||
it('should set a proper title', async () => {
|
it('should set a proper title', async () => {
|
||||||
// @ts-ignore (SpectronClient.getTitle exists)
|
// @ts-ignore (SpectronClient.getTitle exists)
|
||||||
return expect(await app.client.getTitle()).to.equal('Etcher');
|
return expect(await app.client.getTitle()).to.equal('balenaEtcher');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as CopyPlugin from 'copy-webpack-plugin';
|
import * as CopyPlugin from 'copy-webpack-plugin';
|
||||||
import { readdirSync } from 'fs';
|
import { readdirSync, existsSync } from 'fs';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import outdent from 'outdent';
|
import outdent from 'outdent';
|
||||||
@@ -77,11 +77,80 @@ function renameNodeModules(resourcePath: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findExt2fsFolder(): string {
|
||||||
|
const ext2fs = 'node_modules/ext2fs';
|
||||||
|
const biFsExt2fs = 'node_modules/balena-image-fs/node_modules/ext2fs';
|
||||||
|
|
||||||
|
if (existsSync(ext2fs)) {
|
||||||
|
return ext2fs;
|
||||||
|
} else if (existsSync(biFsExt2fs)) {
|
||||||
|
return biFsExt2fs;
|
||||||
|
} else {
|
||||||
|
throw Error('ext2fs not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeExt2FsRegex(): RegExp {
|
||||||
|
const folder = findExt2fsFolder();
|
||||||
|
const libpath = '/lib/libext2fs\\.js$';
|
||||||
|
|
||||||
|
return new RegExp(folder.concat(libpath));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUsbPrebuild(): string[] {
|
||||||
|
const usbPrebuildsFolder = path.join('node_modules', 'usb', 'prebuilds');
|
||||||
|
const prebuildFolders = readdirSync(usbPrebuildsFolder);
|
||||||
|
let bindingFile: string | undefined = 'node.napi.node';
|
||||||
|
const platformFolder = prebuildFolders.find(
|
||||||
|
(f) => f.startsWith(os.platform()) && f.indexOf(os.arch()) > -1,
|
||||||
|
);
|
||||||
|
if (platformFolder === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
'Could not find usb prebuild. Should try fallback to node-gyp and use /build/Release instead of /prebuilds',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindingFiles = readdirSync(
|
||||||
|
path.join(usbPrebuildsFolder, platformFolder),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!bindingFiles.length) {
|
||||||
|
throw new Error('Could not find usb prebuild for platform');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindingFiles.length === 1) {
|
||||||
|
bindingFile = bindingFiles[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// armv6 vs v7 in linux-arm and
|
||||||
|
// glibc vs musl in linux-x64
|
||||||
|
if (bindingFiles.length > 1) {
|
||||||
|
bindingFile = bindingFiles.find((file) => {
|
||||||
|
if (bindingFiles.indexOf('arm') > -1) {
|
||||||
|
const process = require('process');
|
||||||
|
return file.indexOf(process.config.variables.arm_version) > -1;
|
||||||
|
} else {
|
||||||
|
return file.indexOf('glibc') > -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindingFile === undefined) {
|
||||||
|
throw new Error('Could not find usb prebuild for platform');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [platformFolder, bindingFile];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [USB_BINDINGS_FOLDER, USB_BINDINGS_FILE] = findUsbPrebuild();
|
||||||
|
|
||||||
function findLzmaNativeBindingsFolder(): string {
|
function findLzmaNativeBindingsFolder(): string {
|
||||||
const files = readdirSync(path.join('node_modules', 'lzma-native'));
|
const files = readdirSync(
|
||||||
|
path.join('node_modules', 'lzma-native', 'prebuilds'),
|
||||||
|
);
|
||||||
const bindingsFolder = files.find(
|
const bindingsFolder = files.find(
|
||||||
(f) =>
|
(f) =>
|
||||||
f.startsWith('binding-') &&
|
f.startsWith(os.platform()) &&
|
||||||
f.endsWith(env.npm_config_target_arch || os.arch()),
|
f.endsWith(env.npm_config_target_arch || os.arch()),
|
||||||
);
|
);
|
||||||
if (bindingsFolder === undefined) {
|
if (bindingsFolder === undefined) {
|
||||||
@@ -210,8 +279,8 @@ const commonConfig = {
|
|||||||
/node_modules\/lzma-native\/index\.js$/,
|
/node_modules\/lzma-native\/index\.js$/,
|
||||||
// remove node-pre-gyp magic from lzma-native
|
// remove node-pre-gyp magic from lzma-native
|
||||||
{
|
{
|
||||||
search: 'require(binding_path)',
|
search: `require('node-gyp-build')(__dirname);`,
|
||||||
replace: `require('./${LZMA_BINDINGS_FOLDER}/lzma_native.node')`,
|
replace: `require('./prebuilds/${LZMA_BINDINGS_FOLDER}/electron.napi.node')`,
|
||||||
},
|
},
|
||||||
// use regular stream module instead of readable-stream
|
// use regular stream module instead of readable-stream
|
||||||
{
|
{
|
||||||
@@ -220,9 +289,9 @@ const commonConfig = {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
// remove node-pre-gyp magic from usb
|
// remove node-pre-gyp magic from usb
|
||||||
replace(/node_modules\/@balena.io\/usb\/usb\.js$/, {
|
replace(/node_modules\/usb\/dist\/usb\/bindings\.js$/, {
|
||||||
search: 'require(binding_path)',
|
search: `require('node-gyp-build')(path_1.join(__dirname, '..', '..'));`,
|
||||||
replace: "require('./build/Release/usb_bindings.node')",
|
replace: `require('../../prebuilds/${USB_BINDINGS_FOLDER}/${USB_BINDINGS_FILE}')`,
|
||||||
}),
|
}),
|
||||||
// remove bindings magic from mountutils
|
// remove bindings magic from mountutils
|
||||||
replace(/node_modules\/mountutils\/index\.js$/, {
|
replace(/node_modules\/mountutils\/index\.js$/, {
|
||||||
@@ -273,8 +342,8 @@ const commonConfig = {
|
|||||||
// Use the libext2fs.wasm file in the generated folder
|
// Use the libext2fs.wasm file in the generated folder
|
||||||
// The way to find the app directory depends on whether we run in the renderer or in the child-writer
|
// The way to find the app directory depends on whether we run in the renderer or in the child-writer
|
||||||
// We use __dirname in the child-writer and electron.remote.app.getAppPath() in the renderer
|
// We use __dirname in the child-writer and electron.remote.app.getAppPath() in the renderer
|
||||||
replace(/node_modules\/ext2fs\/lib\/libext2fs\.js$/, {
|
replace(makeExt2FsRegex(), {
|
||||||
search: 'scriptDirectory=__dirname+"/"',
|
search: 'scriptDirectory = __dirname + "/";',
|
||||||
replace: fetchWasm('ext2fs', 'lib'),
|
replace: fetchWasm('ext2fs', 'lib'),
|
||||||
}),
|
}),
|
||||||
// Same for node-crc-utils
|
// Same for node-crc-utils
|
||||||
@@ -336,7 +405,7 @@ const guiConfigCopyPatterns = [
|
|||||||
to: 'modules/node-raspberrypi-usbboot/blobs',
|
to: 'modules/node-raspberrypi-usbboot/blobs',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: 'node_modules/ext2fs/lib/libext2fs.wasm',
|
from: `${findExt2fsFolder()}/lib/libext2fs.wasm`,
|
||||||
to: 'modules/ext2fs/lib/libext2fs.wasm',
|
to: 'modules/ext2fs/lib/libext2fs.wasm',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -348,8 +417,8 @@ const guiConfigCopyPatterns = [
|
|||||||
if (os.platform() === 'win32') {
|
if (os.platform() === 'win32') {
|
||||||
// liblzma.dll is required on Windows for lzma-native
|
// liblzma.dll is required on Windows for lzma-native
|
||||||
guiConfigCopyPatterns.push({
|
guiConfigCopyPatterns.push({
|
||||||
from: `node_modules/lzma-native/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
|
from: `node_modules/lzma-native/prebuilds/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
|
||||||
to: `modules/lzma-native/${LZMA_BINDINGS_FOLDER_RENAMED}/liblzma.dll`,
|
to: `modules/lzma-native/prebuilds/${LZMA_BINDINGS_FOLDER_RENAMED}/liblzma.dll`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,6 +455,7 @@ const guiConfig = {
|
|||||||
const mainConfig = {
|
const mainConfig = {
|
||||||
...commonConfig,
|
...commonConfig,
|
||||||
target: 'electron-main',
|
target: 'electron-main',
|
||||||
|
devtool: 'source-map',
|
||||||
node: {
|
node: {
|
||||||
__dirname: false,
|
__dirname: false,
|
||||||
__filename: true,
|
__filename: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user