mirror of
https://github.com/balena-io/etcher.git
synced 2025-11-10 18:59:27 +00:00
Compare commits
66 Commits
aethernet/
...
v1.19.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc5c68a6a1 | ||
|
|
d76adfb081 | ||
|
|
c696c389c9 | ||
|
|
96f00aa024 | ||
|
|
59356c5bd1 | ||
|
|
1a9a3d2cdc | ||
|
|
faeaa58ec5 | ||
|
|
3957273f40 | ||
|
|
a02a233177 | ||
|
|
f629e6d53b | ||
|
|
37618ce2fd | ||
|
|
14c3e28642 | ||
|
|
bec0e50741 | ||
|
|
9ea7a25323 | ||
|
|
e71d432675 | ||
|
|
196fd8ae24 | ||
|
|
5d43699242 | ||
|
|
3626ffc7ef | ||
|
|
cb8e57bfbe | ||
|
|
4a7fb996e4 | ||
|
|
0f2b4dbc10 | ||
|
|
70304b492d | ||
|
|
8eacab2c4b | ||
|
|
aaac133670 | ||
|
|
d1b5a2aea1 | ||
|
|
fffe5e278f | ||
|
|
ea184eb635 | ||
|
|
5bb8ba857a | ||
|
|
6e4db830e9 | ||
|
|
a0dd6c5401 | ||
|
|
01a96bb6de | ||
|
|
2e3a75e685 | ||
|
|
da4f3ca28e | ||
|
|
a22d2468fd | ||
|
|
559f2b4d68 | ||
|
|
bd33c5b092 | ||
|
|
2cdf65b244 | ||
|
|
8645273fef | ||
|
|
ecb24dad25 | ||
|
|
a970f55b55 | ||
|
|
e969735955 | ||
|
|
45bb29a393 | ||
|
|
f38bca290f | ||
|
|
fb8ed5b529 | ||
|
|
09e13e9b43 | ||
|
|
13e1e8e504 | ||
|
|
acab03ad77 | ||
|
|
0a6c15f702 | ||
|
|
589ce9c28e | ||
|
|
f716c74ef7 | ||
|
|
2d7a6220cd | ||
|
|
e0b26d455c | ||
|
|
06d246e3fd | ||
|
|
67b26a5b69 | ||
|
|
b4b9db7ffa | ||
|
|
cc037d23c4 | ||
|
|
9c9c036956 | ||
|
|
7fdbc439f7 | ||
|
|
9410669294 | ||
|
|
497bb0e2cb | ||
|
|
a42be8ee74 | ||
|
|
16b50d2a71 | ||
|
|
882b385c88 | ||
|
|
059a36659e | ||
|
|
cd9cf09422 | ||
|
|
02a4067118 |
10
.eslintrc.js
Normal file
10
.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ["./node_modules/@balena/lint/config/.eslintrc.js"],
|
||||||
|
root: true,
|
||||||
|
ignorePatterns: ["node_modules/"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-floating-promises": "off",
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
},
|
||||||
|
};
|
||||||
454
.eslintrc.yml
454
.eslintrc.yml
@@ -1,454 +0,0 @@
|
|||||||
env:
|
|
||||||
browser: true
|
|
||||||
commonjs: true
|
|
||||||
es6: true
|
|
||||||
node: true
|
|
||||||
mocha: true
|
|
||||||
plugins:
|
|
||||||
- lodash
|
|
||||||
- jsdoc
|
|
||||||
- node
|
|
||||||
- react
|
|
||||||
extends: 'standard'
|
|
||||||
parserOptions:
|
|
||||||
sourceType: 'script'
|
|
||||||
ecmaFeatures:
|
|
||||||
jsx: true
|
|
||||||
settings:
|
|
||||||
jsdoc:
|
|
||||||
additionalTagNames:
|
|
||||||
customTags:
|
|
||||||
- fulfil
|
|
||||||
rules:
|
|
||||||
|
|
||||||
# Possible Errors
|
|
||||||
|
|
||||||
no-console:
|
|
||||||
- off
|
|
||||||
no-empty:
|
|
||||||
- error
|
|
||||||
no-extra-semi:
|
|
||||||
- error
|
|
||||||
no-negated-in-lhs:
|
|
||||||
- error
|
|
||||||
no-prototype-builtins:
|
|
||||||
- error
|
|
||||||
valid-jsdoc:
|
|
||||||
- error
|
|
||||||
- requireReturn: false
|
|
||||||
requireReturnDescription: false
|
|
||||||
requireReturnType: true
|
|
||||||
requireParamDescription: true
|
|
||||||
preferType:
|
|
||||||
boolean: "Boolean"
|
|
||||||
number: "Number"
|
|
||||||
object: "Object"
|
|
||||||
string: "String"
|
|
||||||
array: "Array"
|
|
||||||
prefer:
|
|
||||||
arg: "param"
|
|
||||||
return: "returns"
|
|
||||||
|
|
||||||
# Best Practices
|
|
||||||
|
|
||||||
array-callback-return:
|
|
||||||
- error
|
|
||||||
block-scoped-var:
|
|
||||||
- error
|
|
||||||
class-methods-use-this:
|
|
||||||
- error
|
|
||||||
complexity:
|
|
||||||
- off
|
|
||||||
consistent-return:
|
|
||||||
- error
|
|
||||||
curly:
|
|
||||||
- error
|
|
||||||
default-case:
|
|
||||||
- error
|
|
||||||
dot-notation:
|
|
||||||
- error
|
|
||||||
guard-for-in:
|
|
||||||
- error
|
|
||||||
no-alert:
|
|
||||||
- error
|
|
||||||
no-case-declarations:
|
|
||||||
- error
|
|
||||||
no-div-regex:
|
|
||||||
- error
|
|
||||||
no-else-return:
|
|
||||||
- error
|
|
||||||
no-empty-function:
|
|
||||||
- error
|
|
||||||
no-eq-null:
|
|
||||||
- error
|
|
||||||
no-extra-label:
|
|
||||||
- error
|
|
||||||
no-implicit-coercion:
|
|
||||||
- error
|
|
||||||
no-implicit-globals:
|
|
||||||
- error
|
|
||||||
no-loop-func:
|
|
||||||
- error
|
|
||||||
no-magic-numbers:
|
|
||||||
- error
|
|
||||||
no-native-reassign:
|
|
||||||
- error
|
|
||||||
no-param-reassign:
|
|
||||||
- error
|
|
||||||
no-restricted-properties:
|
|
||||||
- error
|
|
||||||
- property: __proto__
|
|
||||||
no-return-await:
|
|
||||||
- error
|
|
||||||
no-script-url:
|
|
||||||
- error
|
|
||||||
no-unused-expressions:
|
|
||||||
- error
|
|
||||||
no-unused-labels:
|
|
||||||
- error
|
|
||||||
no-useless-concat:
|
|
||||||
- error
|
|
||||||
no-void:
|
|
||||||
- error
|
|
||||||
no-warning-comments:
|
|
||||||
- off
|
|
||||||
radix:
|
|
||||||
- error
|
|
||||||
vars-on-top:
|
|
||||||
- off
|
|
||||||
|
|
||||||
# Strict mode
|
|
||||||
|
|
||||||
strict:
|
|
||||||
- error
|
|
||||||
- global
|
|
||||||
|
|
||||||
# Variables
|
|
||||||
|
|
||||||
init-declarations:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
no-catch-shadow:
|
|
||||||
- error
|
|
||||||
no-restricted-globals:
|
|
||||||
- error
|
|
||||||
- event
|
|
||||||
no-shadow:
|
|
||||||
- error
|
|
||||||
no-undefined:
|
|
||||||
- error
|
|
||||||
no-unused-vars:
|
|
||||||
- error
|
|
||||||
no-use-before-define:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# NodeJS and CommonJS
|
|
||||||
|
|
||||||
callback-return:
|
|
||||||
- error
|
|
||||||
global-require:
|
|
||||||
- off
|
|
||||||
no-mixed-requires:
|
|
||||||
- error
|
|
||||||
no-process-env:
|
|
||||||
- off
|
|
||||||
no-process-exit:
|
|
||||||
- off
|
|
||||||
no-sync:
|
|
||||||
- off
|
|
||||||
|
|
||||||
# Stylistic Issues
|
|
||||||
|
|
||||||
array-bracket-spacing:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
capitalized-comments:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
- ignoreConsecutiveComments: true
|
|
||||||
comma-spacing:
|
|
||||||
- error
|
|
||||||
- before: false
|
|
||||||
after: true
|
|
||||||
computed-property-spacing:
|
|
||||||
- error
|
|
||||||
- never
|
|
||||||
consistent-this:
|
|
||||||
- error
|
|
||||||
- self
|
|
||||||
func-name-matching:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
func-names:
|
|
||||||
- error
|
|
||||||
- never
|
|
||||||
func-style:
|
|
||||||
- error
|
|
||||||
- expression
|
|
||||||
id-blacklist:
|
|
||||||
- error
|
|
||||||
id-length:
|
|
||||||
- error
|
|
||||||
- min: 2
|
|
||||||
exceptions:
|
|
||||||
- "_"
|
|
||||||
id-match:
|
|
||||||
- error
|
|
||||||
- "^[_0-9A-Za-z\\$]+$"
|
|
||||||
line-comment-position:
|
|
||||||
- error
|
|
||||||
- position: above
|
|
||||||
linebreak-style:
|
|
||||||
- error
|
|
||||||
- unix
|
|
||||||
lines-around-comment:
|
|
||||||
- error
|
|
||||||
- beforeBlockComment: true
|
|
||||||
afterBlockComment: false
|
|
||||||
beforeLineComment: true
|
|
||||||
afterLineComment: false
|
|
||||||
allowBlockStart: true
|
|
||||||
allowBlockEnd: false
|
|
||||||
allowObjectStart: true
|
|
||||||
allowObjectEnd: false
|
|
||||||
allowArrayStart: true
|
|
||||||
allowArrayEnd: false
|
|
||||||
lines-around-directive:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
max-len:
|
|
||||||
- error
|
|
||||||
- code: 130
|
|
||||||
comments: 150
|
|
||||||
ignoreComments: false
|
|
||||||
ignoreTrailingComments: false
|
|
||||||
ignoreUrls: true
|
|
||||||
max-params:
|
|
||||||
- off
|
|
||||||
max-statements-per-line:
|
|
||||||
- error
|
|
||||||
- max: 1
|
|
||||||
multiline-ternary:
|
|
||||||
- off
|
|
||||||
newline-per-chained-call:
|
|
||||||
- off
|
|
||||||
no-bitwise:
|
|
||||||
- error
|
|
||||||
no-continue:
|
|
||||||
- error
|
|
||||||
no-inline-comments:
|
|
||||||
- error
|
|
||||||
no-lonely-if:
|
|
||||||
- error
|
|
||||||
no-mixed-operators:
|
|
||||||
- error
|
|
||||||
no-multi-assign:
|
|
||||||
- error
|
|
||||||
no-negated-condition:
|
|
||||||
- error
|
|
||||||
no-nested-ternary:
|
|
||||||
- error
|
|
||||||
no-plusplus:
|
|
||||||
- error
|
|
||||||
no-restricted-syntax:
|
|
||||||
- error
|
|
||||||
- WithStatement
|
|
||||||
- ForInStatement
|
|
||||||
no-spaced-func:
|
|
||||||
- error
|
|
||||||
no-underscore-dangle:
|
|
||||||
- error
|
|
||||||
- allowAfterThis: false
|
|
||||||
object-curly-newline:
|
|
||||||
- error
|
|
||||||
- minProperties: 3
|
|
||||||
consistent: true
|
|
||||||
object-curly-spacing:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
one-var-declaration-per-line:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
operator-assignment:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
quotes:
|
|
||||||
- error
|
|
||||||
- single
|
|
||||||
quote-props:
|
|
||||||
- error
|
|
||||||
- as-needed
|
|
||||||
require-jsdoc:
|
|
||||||
- error
|
|
||||||
- require:
|
|
||||||
FunctionDeclaration: true
|
|
||||||
ClassDeclaration: true
|
|
||||||
MethodDefinition: true
|
|
||||||
ArrowFunctionExpression: true
|
|
||||||
space-before-function-paren:
|
|
||||||
- error
|
|
||||||
- anonymous: always
|
|
||||||
named: always
|
|
||||||
asyncArrow: always
|
|
||||||
template-tag-spacing:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
unicode-bom:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# ECMAScript 6
|
|
||||||
|
|
||||||
arrow-parens:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
arrow-spacing:
|
|
||||||
- error
|
|
||||||
- before: true
|
|
||||||
after: true
|
|
||||||
generator-star-spacing:
|
|
||||||
- error
|
|
||||||
- before: true
|
|
||||||
after: false
|
|
||||||
no-confusing-arrow:
|
|
||||||
- error
|
|
||||||
no-var:
|
|
||||||
- error
|
|
||||||
object-shorthand:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
prefer-const:
|
|
||||||
- error
|
|
||||||
prefer-spread:
|
|
||||||
- error
|
|
||||||
prefer-numeric-literals:
|
|
||||||
- error
|
|
||||||
prefer-rest-params:
|
|
||||||
- error
|
|
||||||
prefer-template:
|
|
||||||
- error
|
|
||||||
prefer-arrow-callback:
|
|
||||||
- error
|
|
||||||
- allowNamedFunctions: false
|
|
||||||
require-yield:
|
|
||||||
- error
|
|
||||||
symbol-description:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# Lodash
|
|
||||||
|
|
||||||
lodash/chain-style:
|
|
||||||
- error
|
|
||||||
- explicit
|
|
||||||
lodash/identity-shorthand:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
lodash/import-scope:
|
|
||||||
- error
|
|
||||||
- full
|
|
||||||
lodash/matches-prop-shorthand:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
lodash/matches-shorthand:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
lodash/no-commit:
|
|
||||||
- error
|
|
||||||
lodash/path-style:
|
|
||||||
- error
|
|
||||||
- array
|
|
||||||
lodash/prefer-compact:
|
|
||||||
- error
|
|
||||||
lodash/prefer-filter:
|
|
||||||
- error
|
|
||||||
- 5
|
|
||||||
lodash/prefer-flat-map:
|
|
||||||
- error
|
|
||||||
lodash/prefer-invoke-map:
|
|
||||||
- error
|
|
||||||
lodash/prefer-map:
|
|
||||||
- error
|
|
||||||
lodash/prefer-reject:
|
|
||||||
- error
|
|
||||||
lodash/prefer-thru:
|
|
||||||
- error
|
|
||||||
lodash/prefer-wrapper-method:
|
|
||||||
- error
|
|
||||||
lodash/prop-shorthand:
|
|
||||||
- error
|
|
||||||
- always
|
|
||||||
lodash/prefer-constant:
|
|
||||||
- error
|
|
||||||
- true
|
|
||||||
- true
|
|
||||||
lodash/prefer-get:
|
|
||||||
- error
|
|
||||||
- 2
|
|
||||||
lodash/prefer-includes:
|
|
||||||
- error
|
|
||||||
- includeNative: true
|
|
||||||
lodash/prefer-is-nil:
|
|
||||||
- error
|
|
||||||
lodash/prefer-lodash-chain:
|
|
||||||
- error
|
|
||||||
lodash/prefer-lodash-method:
|
|
||||||
- error
|
|
||||||
lodash/prefer-lodash-typecheck:
|
|
||||||
- error
|
|
||||||
lodash/prefer-matches:
|
|
||||||
- error
|
|
||||||
- 3
|
|
||||||
lodash/prefer-noop:
|
|
||||||
- error
|
|
||||||
lodash/prefer-over-quantifier:
|
|
||||||
- error
|
|
||||||
lodash/prefer-startswith:
|
|
||||||
- error
|
|
||||||
lodash/prefer-times:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# JSDoc
|
|
||||||
|
|
||||||
jsdoc/check-param-names:
|
|
||||||
- error
|
|
||||||
jsdoc/check-tag-names:
|
|
||||||
- error
|
|
||||||
jsdoc/newline-after-description:
|
|
||||||
- error
|
|
||||||
jsdoc/require-example:
|
|
||||||
- error
|
|
||||||
jsdoc/require-hyphen-before-param-description:
|
|
||||||
- error
|
|
||||||
jsdoc/require-param:
|
|
||||||
- error
|
|
||||||
jsdoc/require-param-description:
|
|
||||||
- error
|
|
||||||
jsdoc/require-param-type:
|
|
||||||
- error
|
|
||||||
jsdoc/require-returns-type:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# Node
|
|
||||||
|
|
||||||
node/no-deprecated-api:
|
|
||||||
- error
|
|
||||||
node/no-missing-import:
|
|
||||||
- error
|
|
||||||
node/no-missing-require:
|
|
||||||
- error
|
|
||||||
node/process-exit-as-throw:
|
|
||||||
- error
|
|
||||||
node/no-extraneous-require:
|
|
||||||
- error
|
|
||||||
node/no-extraneous-import:
|
|
||||||
- error
|
|
||||||
|
|
||||||
# React
|
|
||||||
|
|
||||||
react/jsx-uses-vars:
|
|
||||||
- error
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
files: ['*.jsx']
|
|
||||||
rules:
|
|
||||||
require-jsdoc:
|
|
||||||
- off
|
|
||||||
218
.github/actions/publish/action.yml
vendored
218
.github/actions/publish/action.yml
vendored
@@ -10,12 +10,12 @@ inputs:
|
|||||||
required: true
|
required: true
|
||||||
|
|
||||||
# --- custom environment
|
# --- custom environment
|
||||||
XCODE_APP_LOADER_EMAIL:
|
|
||||||
type: string
|
|
||||||
default: "accounts+apple@balena.io"
|
|
||||||
NODE_VERSION:
|
NODE_VERSION:
|
||||||
type: string
|
type: string
|
||||||
default: "16.x"
|
# Beware that native modules will be built for this version,
|
||||||
|
# which might not be compatible with the one used by pkg (see forge.sidecar.ts)
|
||||||
|
# https://github.com/vercel/pkg-fetch/releases
|
||||||
|
default: "20.x"
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: "true"
|
default: "true"
|
||||||
@@ -25,138 +25,174 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Download custom source artifact
|
- name: Download custom source artifact
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||||
path: ${{ runner.temp }}
|
path: ${{ runner.temp }}
|
||||||
|
|
||||||
- name: Extract custom source artifact
|
- name: Extract custom source artifact
|
||||||
shell: pwsh
|
if: runner.os != 'Windows'
|
||||||
|
shell: bash
|
||||||
working-directory: .
|
working-directory: .
|
||||||
run: tar -xf ${{ runner.temp }}/custom.tgz
|
run: tar -xf ${{ runner.temp }}/custom.tgz
|
||||||
|
|
||||||
|
- name: Extract custom source artifact
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
working-directory: .
|
||||||
|
run: C:\"Program Files"\Git\usr\bin\tar.exe --force-local -xf ${{ runner.temp }}\custom.tgz
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.NODE_VERSION }}
|
node-version: ${{ inputs.NODE_VERSION }}
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|
||||||
- name: Install yq
|
- name: Install host dependencies
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
if: runner.os == 'Linux'
|
||||||
run: choco install yq
|
shell: bash
|
||||||
if: runner.os == 'Windows'
|
run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm
|
||||||
|
|
||||||
|
- name: Install host dependencies
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
|
||||||
|
# This is a temporary workaround to make the job use Python 3.11 until
|
||||||
|
# we update to npm 10+.
|
||||||
|
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
# https://www.electron.build/code-signing.html
|
# https://www.electron.build/code-signing.html
|
||||||
# https://github.com/Apple-Actions/import-codesign-certs
|
# https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
|
||||||
- name: Import Apple code signing certificate
|
- name: Import Apple code signing certificate
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
uses: apple-actions/import-codesign-certs@v1
|
shell: bash
|
||||||
with:
|
run: |
|
||||||
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
KEY_CHAIN=build.keychain
|
||||||
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
CERTIFICATE_P12=certificate.p12
|
||||||
|
|
||||||
|
# Recreate the certificate from the secure environment variable
|
||||||
|
echo $CERTIFICATE_P12_B64 | base64 --decode > $CERTIFICATE_P12
|
||||||
|
|
||||||
|
# Create a keychain
|
||||||
|
security create-keychain -p actions $KEY_CHAIN
|
||||||
|
|
||||||
|
# Make the keychain the default so identities are found
|
||||||
|
security default-keychain -s $KEY_CHAIN
|
||||||
|
|
||||||
|
# Unlock the keychain
|
||||||
|
security unlock-keychain -p actions $KEY_CHAIN
|
||||||
|
|
||||||
|
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign
|
||||||
|
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
|
||||||
|
|
||||||
|
# remove certs
|
||||||
|
rm -fr *.p12
|
||||||
|
env:
|
||||||
|
CERTIFICATE_P12_B64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
|
||||||
|
CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
||||||
|
|
||||||
- name: Import Windows code signing certificate
|
- name: Import Windows code signing certificate
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
|
id: import_win_signing_cert
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
|
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:SM_CLIENT_CERT_FILE_B64
|
||||||
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
|
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12
|
||||||
Remove-Item -path ${{ runner.temp }} -include certificate.base64
|
Remove-Item -path ${{ runner.temp }} -include certificate.base64
|
||||||
|
|
||||||
Import-PfxCertificate `
|
echo "certFilePath=${{ runner.temp }}/Certificate_pkcs12.p12" >> $GITHUB_OUTPUT
|
||||||
-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:
|
env:
|
||||||
WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }}
|
||||||
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
|
- name: Package release
|
||||||
id: package_release
|
shell: bash
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
# IMPORTANT: before making changes to this step please consult @engineering in balena's chat.
|
||||||
run: |
|
run: |
|
||||||
set -ea
|
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
|
||||||
|
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
|
||||||
|
# export DEBUG='electron-forge:*,sidecar'
|
||||||
|
# fi
|
||||||
|
|
||||||
[[ '${{ 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)"
|
APPLICATION_VERSION="$(jq -r '.version' package.json)"
|
||||||
ARCHITECTURE_FLAGS="--${ELECTRON_BUILDER_ARCHITECTURE}"
|
HOST_ARCH="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
|
||||||
if [[ $runner_os =~ linux ]]; then
|
if [[ "${RUNNER_OS}" == Linux ]]; then
|
||||||
ELECTRON_BUILDER_OS='--linux'
|
PLATFORM=Linux
|
||||||
TARGETS="$(yq e .linux.target[] electron-builder.yml)"
|
SHA256SUM_BIN=sha256sum
|
||||||
|
|
||||||
elif [[ $runner_os =~ darwin|macos|osx ]]; then
|
elif [[ "${RUNNER_OS}" == macOS ]]; then
|
||||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
|
PLATFORM=Darwin
|
||||||
CSC_KEYCHAIN=signing_temp
|
SHA256SUM_BIN='shasum -a 256'
|
||||||
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
|
elif [[ "${RUNNER_OS}" == Windows ]]; then
|
||||||
ARCHITECTURE_FLAGS="--ia32 ${ARCHITECTURE_FLAGS}"
|
PLATFORM=Windows
|
||||||
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
|
SHA256SUM_BIN=sha256sum
|
||||||
CSC_LINK=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
|
|
||||||
ELECTRON_BUILDER_OS='--win'
|
# Install DigiCert Signing Manager Tools
|
||||||
TARGETS="$(yq e .win.target[] electron-builder.yml)"
|
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \
|
||||||
|
-H "x-api-key:$SM_API_KEY" \
|
||||||
|
-o smtools-windows-x64.msi
|
||||||
|
msiexec -i smtools-windows-x64.msi -qn
|
||||||
|
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
|
||||||
|
smksp_registrar.exe list
|
||||||
|
smctl.exe keypair ls
|
||||||
|
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
|
||||||
|
smksp_cert_sync.exe
|
||||||
|
|
||||||
|
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
|
||||||
|
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
|
||||||
|
|
||||||
else
|
else
|
||||||
exit 1
|
echo "ERROR: unexpected runner OS: ${RUNNER_OS}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm link electron-builder
|
# Currently, we can only build for the host architecture.
|
||||||
|
npx electron-forge make
|
||||||
for target in ${TARGETS}; do
|
|
||||||
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
|
||||||
--c.extraMetadata.analytics.sentry.token='https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632' \
|
|
||||||
--c.extraMetadata.analytics.amplitude.token='balena-etcher' \
|
|
||||||
--c.extraMetadata.packageType="${target}"
|
|
||||||
|
|
||||||
find dist -type f -maxdepth 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# collect all artifacts from subdirectories under a common top-level directory
|
||||||
|
mkdir -p dist
|
||||||
|
find ./out/make -type f \( \
|
||||||
|
-iname "*.zip" -o \
|
||||||
|
-iname "*.dmg" -o \
|
||||||
|
-iname "*.rpm" -o \
|
||||||
|
-iname "*.deb" -o \
|
||||||
|
-iname "*.AppImage" -o \
|
||||||
|
-iname "*Setup.exe" \
|
||||||
|
\) -ls -exec cp '{}' dist/ \;
|
||||||
|
|
||||||
|
if [[ -n "${SHA256SUM_BIN}" ]]; then
|
||||||
|
# Compute and save digests.
|
||||||
|
cd dist/
|
||||||
|
${SHA256SUM_BIN} *.* >"SHA256SUMS.${PLATFORM}.${HOST_ARCH}.txt"
|
||||||
|
fi
|
||||||
env:
|
env:
|
||||||
# Apple notarization (afterSignHook.js)
|
# ensure we sign the artifacts
|
||||||
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
|
NODE_ENV: production
|
||||||
|
# analytics tokens
|
||||||
|
SENTRY_TOKEN: https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632
|
||||||
|
AMPLITUDE_TOKEN: 'balena-etcher'
|
||||||
|
# Apple notarization
|
||||||
|
XCODE_APP_LOADER_EMAIL: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_EMAIL }}
|
||||||
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
|
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
|
XCODE_APP_LOADER_TEAM_ID: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_TEAM_ID }}
|
||||||
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
|
# Windows signing
|
||||||
CSC_FOR_PULL_REQUEST: true
|
SM_CLIENT_CERT_PASSWORD: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }}
|
||||||
|
SM_CLIENT_CERT_FILE: '${{ runner.temp }}\Certificate_pkcs12.p12'
|
||||||
# https://www.electron.build/auto-update.html#staged-rollouts
|
SM_HOST: ${{ fromJSON(inputs.secrets).SM_HOST }}
|
||||||
- name: Configure staged rollout(s)
|
SM_API_KEY: ${{ fromJSON(inputs.secrets).SM_API_KEY }}
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }}
|
||||||
run: |
|
TIMESTAMP_SERVER: http://timestamp.digicert.com
|
||||||
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 artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
|
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||||
path: dist
|
path: dist
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
|||||||
49
.github/actions/test/action.yml
vendored
49
.github/actions/test/action.yml
vendored
@@ -12,7 +12,7 @@ inputs:
|
|||||||
# --- custom environment
|
# --- custom environment
|
||||||
NODE_VERSION:
|
NODE_VERSION:
|
||||||
type: string
|
type: string
|
||||||
default: "16.x"
|
default: "20.10"
|
||||||
VERBOSE:
|
VERBOSE:
|
||||||
type: string
|
type: string
|
||||||
default: "true"
|
default: "true"
|
||||||
@@ -28,31 +28,54 @@ runs:
|
|||||||
node-version: ${{ inputs.NODE_VERSION }}
|
node-version: ${{ inputs.NODE_VERSION }}
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|
||||||
- name: Test release
|
- name: Install host dependencies
|
||||||
shell: bash --noprofile --norc -eo pipefail -x {0}
|
if: runner.os == 'Linux'
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -ea
|
sudo apt-get update && sudo apt-get install -y --no-install-recommends xvfb libudev-dev
|
||||||
|
cat < package.json | jq -r '.hostDependencies[][]' - | \
|
||||||
|
xargs -L1 echo | sed 's/|//g' | xargs -L1 \
|
||||||
|
sudo apt-get --ignore-missing install || true
|
||||||
|
|
||||||
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
|
- name: Install host dependencies
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
|
||||||
|
# This is a temporary workaround to make the job use Python 3.11 until
|
||||||
|
# we update to npm 10+.
|
||||||
|
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
|
- name: Test release
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
|
||||||
|
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
|
||||||
|
# export DEBUG='electron-forge:*,sidecar'
|
||||||
|
# fi
|
||||||
|
|
||||||
npm run flowzone-preinstall-${runner_os}
|
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run lint
|
||||||
npm run test-${runner_os}
|
npm run package
|
||||||
|
npm run test
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# https://www.electronjs.org/docs/latest/api/environment-variables
|
# https://www.electronjs.org/docs/latest/api/environment-variables
|
||||||
ELECTRON_NO_ATTACH_CONSOLE: true
|
ELECTRON_NO_ATTACH_CONSOLE: 'true'
|
||||||
|
|
||||||
- name: Compress custom source
|
- name: Compress custom source
|
||||||
shell: pwsh
|
if: runner.os != 'Windows'
|
||||||
|
shell: bash
|
||||||
run: tar -acf ${{ runner.temp }}/custom.tgz .
|
run: tar -acf ${{ runner.temp }}/custom.tgz .
|
||||||
|
|
||||||
|
- name: Compress custom source
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: C:\"Program Files"\Git\usr\bin\tar.exe --force-local -acf ${{ runner.temp }}\custom.tgz .
|
||||||
|
|
||||||
- name: Upload custom artifact
|
- name: Upload custom artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
|
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
|
||||||
path: ${{ runner.temp }}/custom.tgz
|
path: ${{ runner.temp }}/custom.tgz
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|||||||
8
.github/workflows/flowzone.yml
vendored
8
.github/workflows/flowzone.yml
vendored
@@ -1,5 +1,4 @@
|
|||||||
name: Flowzone
|
name: Flowzone
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, closed]
|
types: [opened, synchronize, closed]
|
||||||
@@ -8,7 +7,6 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize, closed]
|
types: [opened, synchronize, closed]
|
||||||
branches: [main, master]
|
branches: [main, master]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
flowzone:
|
flowzone:
|
||||||
name: Flowzone
|
name: Flowzone
|
||||||
@@ -20,11 +18,7 @@ jobs:
|
|||||||
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
tests_run_on: '["ubuntu-20.04","macos-latest","windows-2019"]'
|
custom_runs_on: '[["ubuntu-20.04"],["windows-2019"],["macos-12"],["macos-latest-xlarge"]]'
|
||||||
restrict_custom_actions: false
|
restrict_custom_actions: false
|
||||||
github_prerelease: true
|
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
|
|
||||||
cloudflare_website: "etcher"
|
cloudflare_website: "etcher"
|
||||||
|
|||||||
5
.github/workflows/winget.yml
vendored
5
.github/workflows/winget.yml
vendored
@@ -6,8 +6,9 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest # action can only be run on windows
|
runs-on: windows-latest # action can only be run on windows
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v1
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
identifier: Balena.Etcher
|
identifier: Balena.Etcher
|
||||||
installers-regex: 'balenaEtcher-Setup.*.exe$'
|
# matches something like "balenaEtcher-1.19.0.Setup.exe"
|
||||||
|
installers-regex: 'balenaEtcher-[\d.-]+\.Setup.exe$'
|
||||||
token: ${{ secrets.WINGET_PAT }}
|
token: ${{ secrets.WINGET_PAT }}
|
||||||
|
|||||||
112
.gitignore
vendored
112
.gitignore
vendored
@@ -1,40 +1,103 @@
|
|||||||
|
|
||||||
|
# -- ADD NEW ENTRIES AT THE END OF THE FILE ---
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
/logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
*.pid
|
*.pid
|
||||||
*.seed
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
/lib-cov
|
lib-cov
|
||||||
|
|
||||||
# Image stream output directory
|
|
||||||
/tests/image-stream/output
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
# Coverage directory used by tools like istanbul
|
||||||
/coverage
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# nyc test coverage
|
||||||
.grunt
|
.nyc_output
|
||||||
|
|
||||||
# node-waf configuration
|
# node-waf configuration
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
/build
|
build/Release
|
||||||
|
|
||||||
# Generated files
|
# Dependency directories
|
||||||
/generated
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
# Dependency directory
|
# TypeScript v1 declaration files
|
||||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
typings/
|
||||||
node_modules
|
|
||||||
|
|
||||||
# Compiled Etcher releases
|
# TypeScript cache
|
||||||
/dist
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# Webpack
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Electron-Forge
|
||||||
|
out/
|
||||||
|
|
||||||
|
# ---- Do not modify entries above this line ----
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
dist/
|
||||||
|
|
||||||
# Certificates
|
# Certificates
|
||||||
*.spc
|
*.spc
|
||||||
@@ -44,16 +107,17 @@ node_modules
|
|||||||
*.crt
|
*.crt
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# OSX files
|
# Secrets
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
# VSCode files
|
|
||||||
|
|
||||||
.vscode
|
|
||||||
.gitsecret/keys/random_seed
|
.gitsecret/keys/random_seed
|
||||||
!*.secret
|
!*.secret
|
||||||
secrets/APPLE_SIGNING_PASSWORD.txt
|
secrets/APPLE_SIGNING_PASSWORD.txt
|
||||||
secrets/WINDOWS_SIGNING_PASSWORD.txt
|
secrets/WINDOWS_SIGNING_PASSWORD.txt
|
||||||
secrets/XCODE_APP_LOADER_PASSWORD.txt
|
secrets/XCODE_APP_LOADER_PASSWORD.txt
|
||||||
secrets/WINDOWS_SIGNING.pfx
|
secrets/WINDOWS_SIGNING.pfx
|
||||||
|
|
||||||
|
# Image stream output directory
|
||||||
|
/tests/image-stream/output
|
||||||
|
|
||||||
|
#local development
|
||||||
|
.yalc
|
||||||
|
yalc.lock
|
||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,4 +0,0 @@
|
|||||||
[submodule "scripts/resin"]
|
|
||||||
path = scripts/resin
|
|
||||||
url = https://github.com/balena-io/scripts.git
|
|
||||||
branch = master
|
|
||||||
6
.prettierrc.js
Normal file
6
.prettierrc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(__dirname, "node_modules", "@balena", "lint", "config", ".prettierrc"), "utf8"),
|
||||||
|
);
|
||||||
@@ -1,3 +1,348 @@
|
|||||||
|
- commits:
|
||||||
|
- subject: "patch: replace deprecated pkg with yao-pkg and bump etcher-util node v
|
||||||
|
to 20.10"
|
||||||
|
hash: c696c389c9988c75ad9ccc472bdac7edefe762ed
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.19.8
|
||||||
|
title: ""
|
||||||
|
date: 2024-04-22T09:37:37.561Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: fix formating"
|
||||||
|
hash: 1a9a3d2cdc5642a754b73628f4ae2636e3ffd8eb
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
- subject: "patch: configure prettier in the project to use balena-lint
|
||||||
|
configuration"
|
||||||
|
hash: faeaa58ec548e47abaf30b2498ab145e7c0c6f76
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.19.7
|
||||||
|
title: ""
|
||||||
|
date: 2024-04-22T06:52:18.878Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: fix win signature process"
|
||||||
|
hash: f629e6d53b5329cd7e8105050df042f3873a35ee
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.19.6
|
||||||
|
title: ""
|
||||||
|
date: 2024-04-19T15:59:28.200Z
|
||||||
|
- commits:
|
||||||
|
- subject: Replace deprecated flowzone input tests_run_on
|
||||||
|
hash: bec0e50741bfeda63ca9785217576613f74ca043
|
||||||
|
body: |
|
||||||
|
The `custom_runs_on` array supports multiple runner labels
|
||||||
|
in nested arrays.
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
author: Kyle Harding
|
||||||
|
nested: []
|
||||||
|
version: 1.19.5
|
||||||
|
title: ""
|
||||||
|
date: 2024-02-14T19:51:16.321Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: remove screensaver error when not on etcher-pro"
|
||||||
|
hash: 196fd8ae24de2a23ebaeae736c6ca41007162fa1
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
- subject: "patch: fix typo in IPC server id"
|
||||||
|
hash: 5d436992423961258ad861c01e3b9b30f3317aab
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.19.4
|
||||||
|
title: ""
|
||||||
|
date: 2024-01-26T17:29:27.301Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update dependencies
|
||||||
|
hash: 0f2b4dbc106c55fe104f0b10e62c35c16bcfe9b3
|
||||||
|
body: >
|
||||||
|
- upgrade pretty_bytes to 6.1.1
|
||||||
|
|
||||||
|
- upgrade electron-remote to 2.1.0
|
||||||
|
|
||||||
|
- upgrade semver to 7.5.4 + @types/semver to 7.5.6
|
||||||
|
|
||||||
|
- upgrade chai to 4.3.11 + @types/chai to 4.3.10
|
||||||
|
|
||||||
|
- upgrade mocha to 10.2.0 + @types/mocha to 10.0.6
|
||||||
|
|
||||||
|
- upgrade sinon to 17.0.1 + @types/sinon to 17.0.2
|
||||||
|
|
||||||
|
- remove useless @types
|
||||||
|
|
||||||
|
- upgrade @svgr/webpack to 8.1.0
|
||||||
|
|
||||||
|
- upgrade @sentry/electron to 4.15.1
|
||||||
|
|
||||||
|
- upgrade tslib to 2.6.2
|
||||||
|
|
||||||
|
- upgrade immutable to 4.3.4
|
||||||
|
|
||||||
|
- upgrade redux to 4.2.1
|
||||||
|
|
||||||
|
- upgrade ts-node to 10.9.2 & ts-loader to 9.5.1
|
||||||
|
|
||||||
|
- remove mini-css-extract-plugin
|
||||||
|
|
||||||
|
- upgrade husky to 8.0.3
|
||||||
|
|
||||||
|
- upgrade uuid to 9.0.1
|
||||||
|
|
||||||
|
- upgrade lint-staged to 15.2.1
|
||||||
|
|
||||||
|
- upgrade @types/node to 18.11.9
|
||||||
|
|
||||||
|
- upgrade @fortawesome/fontawesome-free to 6.5.1
|
||||||
|
|
||||||
|
- upgrade i18next to 23.7.8 & react-i18next to 11.18.6
|
||||||
|
|
||||||
|
- bump react, react-dom + related @types to 17.0.2 and rendition to
|
||||||
|
35.1.0
|
||||||
|
|
||||||
|
- fix getuid for ts
|
||||||
|
|
||||||
|
- fix @types/react being in wrong deps
|
||||||
|
|
||||||
|
- upgrade @types/tmp to 0.2.6
|
||||||
|
|
||||||
|
- upgrade typescript to 5.3.3
|
||||||
|
|
||||||
|
- upgrade @types/mime-types to 2.1.4
|
||||||
|
|
||||||
|
- remove d3 from deps
|
||||||
|
|
||||||
|
- upgrade electron-updater to 6.1.7
|
||||||
|
|
||||||
|
- upgrade rendition to 35.1.2
|
||||||
|
|
||||||
|
- upgrade node-ipc to 9.2.3
|
||||||
|
|
||||||
|
- upgrade @types/node-ipc to 9.2.3
|
||||||
|
|
||||||
|
- upgrade electron to 27.1.3
|
||||||
|
|
||||||
|
- upgrade @electron-forge/* to 7.2.0
|
||||||
|
|
||||||
|
- upgrade @reforged/marker-appimage to 3.3.2
|
||||||
|
|
||||||
|
- upgrade style-loader to 3.3.3
|
||||||
|
|
||||||
|
- upgrade balena-lint to 7.2.4
|
||||||
|
|
||||||
|
- run CI with node 18.19
|
||||||
|
|
||||||
|
- add xxhash-addon to sidecar assets
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.19.3
|
||||||
|
title: ""
|
||||||
|
date: 2023-12-22T16:13:00.924Z
|
||||||
|
- commits:
|
||||||
|
- subject: "fix: typos"
|
||||||
|
hash: aaac1336702b7ac4a07992f41db4f0bcdb931c70
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Rotzbua
|
||||||
|
nested: []
|
||||||
|
version: 1.19.2
|
||||||
|
title: ""
|
||||||
|
date: 2023-12-22T12:57:35.441Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: update winget-releaser v2"
|
||||||
|
hash: ea184eb6352b7988c6ab1f439d30c297610cd84e
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Vedant
|
||||||
|
nested: []
|
||||||
|
version: 1.19.1
|
||||||
|
title: ""
|
||||||
|
date: 2023-12-22T08:12:34.451Z
|
||||||
|
- commits:
|
||||||
|
- subject: Use native ARM runner for Apple Silicon builds
|
||||||
|
hash: 01a96bb6de1ff00d20f7784469dd05286069e014
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: Akis Kesoglou
|
||||||
|
nested: []
|
||||||
|
- subject: Calculate and upload build artifact sha256 checksums
|
||||||
|
hash: 2e3a75e685258961bc8efdb95dde12727b93a04a
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: Akis Kesoglou
|
||||||
|
nested: []
|
||||||
|
- subject: Migrate build pipeline to Electron Forge
|
||||||
|
hash: bd33c5b092cb5224c8dfc4d5a2caf4684cee161d
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: minor
|
||||||
|
change-type: minor
|
||||||
|
author: Akis Kesoglou
|
||||||
|
nested: []
|
||||||
|
version: 1.19.0
|
||||||
|
title: ""
|
||||||
|
date: 2023-12-21T16:41:57.426Z
|
||||||
|
- commits:
|
||||||
|
- subject: Remove repo config from flowzone.yml
|
||||||
|
hash: ecb24dad251fbb9b3f92e5b404b66aedd155a584
|
||||||
|
body: |
|
||||||
|
This functionality is being deprecated in Flowzone.
|
||||||
|
|
||||||
|
See: https://github.com/product-os/flowzone/pull/833
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
author: Kyle Harding
|
||||||
|
nested: []
|
||||||
|
- subject: Update actions/upload-artifact to v4
|
||||||
|
hash: a970f55b555f69c5fcb40374eb50ad7b98cc8f96
|
||||||
|
body: |
|
||||||
|
Also ensure we are generating unique artifact names on upload.
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
signed-off-by: Kyle Harding <kyle@balena.io>
|
||||||
|
See: https://github.com/product-os/flowzone/pull/827
|
||||||
|
see: https://github.com/product-os/flowzone/pull/827
|
||||||
|
author: Kyle Harding
|
||||||
|
nested: []
|
||||||
|
version: 1.18.14
|
||||||
|
title: ""
|
||||||
|
date: 2023-12-20T16:23:00.875Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: upgrade to electron 25"
|
||||||
|
hash: f38bca290fe26121bed58d1131265e1aa350ddb5
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
- subject: "patch: refactor scanner, loader and flasher out of gui + upgrade to
|
||||||
|
electron 25"
|
||||||
|
hash: fb8ed5b529e22bc9e766bfe99c2b6955ed695b58
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.18.13
|
||||||
|
title: ""
|
||||||
|
date: 2023-10-16T13:32:26.738Z
|
||||||
|
- commits:
|
||||||
|
- subject: Update instructions for installing deb file
|
||||||
|
hash: acab03ad77a1c1901d0c8a65999e93c1d27169a0
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Jorge Capona
|
||||||
|
nested: []
|
||||||
|
version: 1.18.12
|
||||||
|
title: ""
|
||||||
|
date: 2023-07-19T10:24:22.407Z
|
||||||
|
- commits:
|
||||||
|
- subject: "fix: prevent stealing window focus from auth dialog"
|
||||||
|
hash: f716c74ef7cb164b4d825828e4e46033484ad9af
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: leadpogrommer
|
||||||
|
nested: []
|
||||||
|
version: 1.18.11
|
||||||
|
title: ""
|
||||||
|
date: 2023-07-13T14:31:40.021Z
|
||||||
|
- commits:
|
||||||
|
- subject: "spelling: validates"
|
||||||
|
hash: 06d246e3fd1c573b9e04d23ab3bc3c4036fb9859
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
author: Josh Soref
|
||||||
|
nested: []
|
||||||
|
- subject: "spelling: undefined"
|
||||||
|
hash: 67b26a5b69f819066c6419d3d915846b63fdbcf0
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
author: Josh Soref
|
||||||
|
nested: []
|
||||||
|
- subject: "spelling: except if"
|
||||||
|
hash: b4b9db7ffa2104c19e7bd079e4f394a817f40bc0
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
|
||||||
|
author: Josh Soref
|
||||||
|
nested: []
|
||||||
|
version: 1.18.10
|
||||||
|
title: ""
|
||||||
|
date: 2023-07-12T11:21:59.231Z
|
||||||
|
- commits:
|
||||||
|
- subject: Fix opening links from within SafeWebView
|
||||||
|
hash: 497bb0e2cbefad3e9a1188ee5df49cf61f6bd6e4
|
||||||
|
body: ""
|
||||||
|
footer:
|
||||||
|
Change-type: patch
|
||||||
|
change-type: patch
|
||||||
|
author: Akis Kesoglou
|
||||||
|
nested: []
|
||||||
|
version: 1.18.9
|
||||||
|
title: ""
|
||||||
|
date: 2023-07-12T09:07:17.666Z
|
||||||
|
- commits:
|
||||||
|
- subject: "Patch: Fix Support link"
|
||||||
|
hash: 882b385c88111a192e5f37e20c1c8aeca9950b21
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Oliver Plummer
|
||||||
|
nested: []
|
||||||
|
version: 1.18.8
|
||||||
|
title: ""
|
||||||
|
date: 2023-04-26T09:57:46.155Z
|
||||||
|
- commits:
|
||||||
|
- subject: "patch: update docs to remove cloudsmith install instructions for linux"
|
||||||
|
hash: 02a406711852cf237e41da4cd39350d8acc1f0b0
|
||||||
|
body: ""
|
||||||
|
footer: {}
|
||||||
|
author: Edwin Joassart
|
||||||
|
nested: []
|
||||||
|
version: 1.18.7
|
||||||
|
title: ""
|
||||||
|
date: 2023-04-25T15:25:35.584Z
|
||||||
- commits:
|
- commits:
|
||||||
- subject: add-flash-with-etcher-to-docs
|
- subject: add-flash-with-etcher-to-docs
|
||||||
hash: 856b426dc98925f5e339976a5cac144f4bb4ea59
|
hash: 856b426dc98925f5e339976a5cac144f4bb4ea59
|
||||||
@@ -190,10 +535,11 @@
|
|||||||
- subject: Switch to `@electron/remote`
|
- subject: Switch to `@electron/remote`
|
||||||
hash: 7ee174edcecbfc2d7370db6d4185b3ee4eedbe28
|
hash: 7ee174edcecbfc2d7370db6d4185b3ee4eedbe28
|
||||||
body: >
|
body: >
|
||||||
Electron 12 deprecated `electron.remote` and the functionality was removed
|
Electron 12 deprecated `electron.remote` and the functionality was
|
||||||
in Electron 14, but became available as a separate `@electron/remote`
|
removed in Electron 14, but became available as a separate
|
||||||
module. This commit makes the transition to the external module as an
|
`@electron/remote` module. This commit makes the transition to the
|
||||||
intermediary step to enable updating to a newer Electron version.
|
external module as an intermediary step to enable updating to a newer
|
||||||
|
Electron version.
|
||||||
footer:
|
footer:
|
||||||
Change-type: patch
|
Change-type: patch
|
||||||
change-type: patch
|
change-type: patch
|
||||||
@@ -238,8 +584,8 @@
|
|||||||
- subject: Lazily import Electron from child-writer process
|
- subject: Lazily import Electron from child-writer process
|
||||||
hash: 851219f835ed037d9fd970f538095e4b339c5342
|
hash: 851219f835ed037d9fd970f538095e4b339c5342
|
||||||
body: >
|
body: >
|
||||||
No idea how this *used* to work, but it doesn’t since 887ec428 and this is
|
No idea how this *used* to work, but it doesn’t since 887ec428 and this
|
||||||
fixing it properly.
|
is fixing it properly.
|
||||||
footer:
|
footer:
|
||||||
Change-type: patch
|
Change-type: patch
|
||||||
change-type: patch
|
change-type: patch
|
||||||
@@ -256,20 +602,33 @@
|
|||||||
step forward to upgrading to a newer Electron and Node version.
|
step forward to upgrading to a newer Electron and Node version.
|
||||||
|
|
||||||
|
|
||||||
Updates etcher-sdk and switches the redundant aws4-axios dependency to just axios.
|
Updates etcher-sdk and switches the redundant aws4-axios dependency to
|
||||||
|
just axios.
|
||||||
|
|
||||||
|
|
||||||
Also changed bundler to stop trying to bundle wasm files — they must be included inline with JS code as data — and removed some now redundant code.
|
Also changed bundler to stop trying to bundle wasm files — they must be
|
||||||
|
included inline with JS code as data — and removed some now redundant
|
||||||
|
code.
|
||||||
|
|
||||||
|
|
||||||
The crucial changes that enable support are:
|
The crucial changes that enable support are:
|
||||||
|
|
||||||
|
|
||||||
1. The update to etcher-sdk@8 where some dependency fixes and updates took place
|
1. The update to etcher-sdk@8 where some dependency fixes and updates
|
||||||
|
took place
|
||||||
|
|
||||||
2. The downgrade and pinning of "electron-rebuild" to v3.2.3 until we’re able to update to Electron >= 14.2. The patch we need to avoid is https://github.com/electron/rebuild/pull/907. Also see: https://github.com/nodejs/node-gyp/issues/2673 and https://github.com/electron/rebuild/issues/913
|
2. The downgrade and pinning of "electron-rebuild" to v3.2.3 until we’re
|
||||||
|
able to update to Electron >= 14.2. The patch we need to avoid is
|
||||||
|
https://github.com/electron/rebuild/pull/907. Also see:
|
||||||
|
https://github.com/nodejs/node-gyp/issues/2673 and
|
||||||
|
https://github.com/electron/rebuild/issues/913
|
||||||
|
|
||||||
3. A rule in webpack.config to ignore `aws-crt` which is a dependency of (ultimately) `aws4-axios` which is used by etcher-sdk and does a runtime check to its availability. We’re not currently using the “assume role” functionality (AFAIU) of aws4-axios and we don’t care that it’s not found, so force webpack to ignore the import. See https://github.com/aws/aws-sdk-js-v3/issues/3025
|
3. A rule in webpack.config to ignore `aws-crt` which is a dependency of
|
||||||
|
(ultimately) `aws4-axios` which is used by etcher-sdk and does a runtime
|
||||||
|
check to its availability. We’re not currently using the “assume role”
|
||||||
|
functionality (AFAIU) of aws4-axios and we don’t care that it’s not
|
||||||
|
found, so force webpack to ignore the import. See
|
||||||
|
https://github.com/aws/aws-sdk-js-v3/issues/3025
|
||||||
footer:
|
footer:
|
||||||
Change-type: minor
|
Change-type: minor
|
||||||
change-type: minor
|
change-type: minor
|
||||||
@@ -549,7 +908,8 @@
|
|||||||
body: >
|
body: >
|
||||||
Optimized several translations.
|
Optimized several translations.
|
||||||
|
|
||||||
This commit itself is only a patch, but as a pull request must have at least one commit with a change-type.
|
This commit itself is only a patch, but as a pull request must have at
|
||||||
|
least one commit with a change-type.
|
||||||
footer:
|
footer:
|
||||||
Change-Type: minor
|
Change-Type: minor
|
||||||
change-type: minor
|
change-type: minor
|
||||||
@@ -637,7 +997,8 @@
|
|||||||
removed](https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/)
|
removed](https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/)
|
||||||
|
|
||||||
|
|
||||||
We cannot use `latest` as the glibc version will cause issue with older ubuntu version.
|
We cannot use `latest` as the glibc version will cause issue with older
|
||||||
|
ubuntu version.
|
||||||
footer: {}
|
footer: {}
|
||||||
author: Edwin Joassart
|
author: Edwin Joassart
|
||||||
nested: []
|
nested: []
|
||||||
@@ -1184,7 +1545,8 @@
|
|||||||
- subject: Fixing call to electron block screensaver methods invocation
|
- subject: Fixing call to electron block screensaver methods invocation
|
||||||
hash: 1b5b64713505dfb69448bc2184839b4c23bd677b
|
hash: 1b5b64713505dfb69448bc2184839b4c23bd677b
|
||||||
body: >
|
body: >
|
||||||
Replacing `send` calls to `invoke` for `enable/disable-screensaver` calls.
|
Replacing `send` calls to `invoke` for `enable/disable-screensaver`
|
||||||
|
calls.
|
||||||
footer:
|
footer:
|
||||||
Change-type: patch
|
Change-type: patch
|
||||||
change-type: patch
|
change-type: patch
|
||||||
@@ -1936,11 +2298,11 @@
|
|||||||
- subject: Add support for basic auth when downloading images from URL.
|
- subject: Add support for basic auth when downloading images from URL.
|
||||||
hash: b2d0c1c9ddbbfe87d5a905d420d615821610e825
|
hash: b2d0c1c9ddbbfe87d5a905d420d615821610e825
|
||||||
body: >
|
body: >
|
||||||
When selecting "Flash from URL" the user can optionally provide a username
|
When selecting "Flash from URL" the user can optionally provide a
|
||||||
and password for basic authentication. The authentication input fields
|
username and password for basic authentication. The authentication input
|
||||||
are collapsed by default. When the authentication input fields are
|
fields are collapsed by default. When the authentication input fields
|
||||||
collapsed after entering values the values are cleared to ensure that
|
are collapsed after entering values the values are cleared to ensure
|
||||||
the user sees all parameter passed to the server.
|
that the user sees all parameter passed to the server.
|
||||||
footer:
|
footer:
|
||||||
Change-Type: minor
|
Change-Type: minor
|
||||||
change-type: minor
|
change-type: minor
|
||||||
@@ -2167,7 +2529,8 @@
|
|||||||
reloads without reloading the whole electron app.
|
reloads without reloading the whole electron app.
|
||||||
|
|
||||||
|
|
||||||
This patch also runs the development environment in development mode, which is much, much faster on builds and rebuilds.
|
This patch also runs the development environment in development mode,
|
||||||
|
which is much, much faster on builds and rebuilds.
|
||||||
footer: {}
|
footer: {}
|
||||||
author: Zane Hitchcox
|
author: Zane Hitchcox
|
||||||
nested: []
|
nested: []
|
||||||
@@ -2497,7 +2860,8 @@
|
|||||||
- subject: Ignore ENOENT errors on unlink in withTmpFile
|
- subject: Ignore ENOENT errors on unlink in withTmpFile
|
||||||
hash: 7bb2a23c4e94dcda6a7b494fe0435c0b59b56b06
|
hash: 7bb2a23c4e94dcda6a7b494fe0435c0b59b56b06
|
||||||
body: >
|
body: >
|
||||||
The temporary file might have been already deleted by cleanupTmpFiles
|
The temporary file might have been already deleted by
|
||||||
|
cleanupTmpFiles
|
||||||
footer:
|
footer:
|
||||||
Change-type: patch
|
Change-type: patch
|
||||||
change-type: patch
|
change-type: patch
|
||||||
@@ -2567,12 +2931,15 @@
|
|||||||
- subject: Pass strings between methods as std::string instead of char *
|
- subject: Pass strings between methods as std::string instead of char *
|
||||||
hash: 1ec6a8ffc4c9e138b78210f0db84a9ebd6c9182b
|
hash: 1ec6a8ffc4c9e138b78210f0db84a9ebd6c9182b
|
||||||
body: >
|
body: >
|
||||||
- Fixes "basic_string::_M_construct null not valid" exception
|
- Fixes "basic_string::_M_construct null not valid"
|
||||||
|
exception
|
||||||
aborting program, because WCharToUtf8() returned NULL
|
aborting program, because WCharToUtf8() returned NULL
|
||||||
in some cases, and NULL was being fed to string constructor.
|
in some cases, and NULL was being fed to string constructor.
|
||||||
- Fixes memory leak because memory allocated with calloc()
|
- Fixes memory leak because memory allocated with
|
||||||
|
calloc()
|
||||||
in WCharToUtf8() was not being freed anywhere
|
in WCharToUtf8() was not being freed anywhere
|
||||||
- Fixes undefined behavior because GetEnumeratorName() returns
|
- Fixes undefined behavior because GetEnumeratorName()
|
||||||
|
returns
|
||||||
pointer to stack memory, that goes outside of scope while
|
pointer to stack memory, that goes outside of scope while
|
||||||
pointer still is being used.
|
pointer still is being used.
|
||||||
|
|
||||||
@@ -4494,7 +4861,8 @@
|
|||||||
|
|
||||||
Although it's possible to use a PC keyboard on a Mac, it's unusual.
|
Although it's possible to use a PC keyboard on a Mac, it's unusual.
|
||||||
|
|
||||||
In any case, all of the macOS (not "Mac OS" for some years now) documentation refers to the "Opt" key.
|
In any case, all of the macOS (not "Mac OS" for some years now)
|
||||||
|
documentation refers to the "Opt" key.
|
||||||
- hash: ea11f179542794294f773f503d83dad3a10cda56
|
- hash: ea11f179542794294f773f503d83dad3a10cda56
|
||||||
author: Tom
|
author: Tom
|
||||||
footers:
|
footers:
|
||||||
@@ -4655,10 +5023,11 @@
|
|||||||
change-type: patch
|
change-type: patch
|
||||||
subject: Fixes the Command for macOS drive recovery
|
subject: Fixes the Command for macOS drive recovery
|
||||||
body: >-
|
body: >-
|
||||||
Changes the documentation to update the disktutil command which didn't fix
|
Changes the documentation to update the disktutil command which didn't
|
||||||
my case, cause the boot partition was broken.
|
fix my case, cause the boot partition was broken.
|
||||||
|
|
||||||
This way it rewrites the drive into a FAT32 partition editable in Unix/Windows.
|
This way it rewrites the drive into a FAT32 partition editable in
|
||||||
|
Unix/Windows.
|
||||||
- hash: b3f25c176b1bdb487d1a7bf111d7f170fe008842
|
- hash: b3f25c176b1bdb487d1a7bf111d7f170fe008842
|
||||||
author: Lorenzo Alberto Maria Ambrosi
|
author: Lorenzo Alberto Maria Ambrosi
|
||||||
footers:
|
footers:
|
||||||
@@ -7358,7 +7727,8 @@
|
|||||||
|
|
||||||
performance improvement
|
performance improvement
|
||||||
|
|
||||||
- Make Breadcrumbs and Icon pure components to stop frequent re-rendering
|
- Make Breadcrumbs and Icon pure components to stop frequent
|
||||||
|
re-rendering
|
||||||
|
|
||||||
- Initial support for array constraints
|
- Initial support for array constraints
|
||||||
|
|
||||||
@@ -7489,9 +7859,11 @@
|
|||||||
|
|
||||||
the `ETCHER_EXPERIMENTAL_FILE_PICKER` environment variable. Further
|
the `ETCHER_EXPERIMENTAL_FILE_PICKER` environment variable. Further
|
||||||
|
|
||||||
customisation can be done with the `ETCHER_FILE_BROWSER_CONSTRAIN_FOLDER`
|
customisation can be done with the
|
||||||
|
`ETCHER_FILE_BROWSER_CONSTRAIN_FOLDER`
|
||||||
|
|
||||||
variable that takes a path and allows one to constrain the file-picker to
|
variable that takes a path and allows one to constrain the file-picker
|
||||||
|
to
|
||||||
|
|
||||||
a folder.
|
a folder.
|
||||||
- hash: 687e0b563b0dc3619ece4ce49d353d5838a21ff6
|
- hash: 687e0b563b0dc3619ece4ce49d353d5838a21ff6
|
||||||
@@ -7585,15 +7957,18 @@
|
|||||||
changelog-entry: Add support for configuration files
|
changelog-entry: Add support for configuration files
|
||||||
subject: "feat(gui): Add ability to read settings from a config file"
|
subject: "feat(gui): Add ability to read settings from a config file"
|
||||||
body: >-
|
body: >-
|
||||||
This adds the capability to configure settings via a `.etcher.json` file,
|
This adds the capability to configure settings via a `.etcher.json`
|
||||||
|
file,
|
||||||
|
|
||||||
either in the user's home directory, or the current working directory.
|
either in the user's home directory, or the current working directory.
|
||||||
|
|
||||||
In the case of the home directory, the config file is `$HOME/.config/etcher/config.json`,
|
In the case of the home directory, the config file is
|
||||||
|
`$HOME/.config/etcher/config.json`,
|
||||||
|
|
||||||
while on Windows `$HOME/.etcher.json` is used.
|
while on Windows `$HOME/.etcher.json` is used.
|
||||||
|
|
||||||
The defined settings are merged with localStorage settings, and preceding
|
The defined settings are merged with localStorage settings, and
|
||||||
|
preceding
|
||||||
|
|
||||||
configuration files.
|
configuration files.
|
||||||
|
|
||||||
@@ -7703,7 +8078,8 @@
|
|||||||
change-type: patch
|
change-type: patch
|
||||||
subject: "doc: Update MAINTAINERS.md with Symantec Whitelisting"
|
subject: "doc: Update MAINTAINERS.md with Symantec Whitelisting"
|
||||||
body: >-
|
body: >-
|
||||||
This adds instructions for submitting Etcher for false positive detection
|
This adds instructions for submitting Etcher for false positive
|
||||||
|
detection
|
||||||
|
|
||||||
to Symantec Endpoint Protection.
|
to Symantec Endpoint Protection.
|
||||||
- hash: bb2dac75040554c0ba2c7e50ff9ecd61608e7d38
|
- hash: bb2dac75040554c0ba2c7e50ff9ecd61608e7d38
|
||||||
@@ -7879,9 +8255,11 @@
|
|||||||
change-type: patch
|
change-type: patch
|
||||||
subject: "fix(image-writer): Remove use of _.isError"
|
subject: "fix(image-writer): Remove use of _.isError"
|
||||||
body: >-
|
body: >-
|
||||||
`_.isError()` returns `true` for anything that has a `name` and `message`
|
`_.isError()` returns `true` for anything that has a `name` and
|
||||||
|
`message`
|
||||||
|
|
||||||
property, causing the check here to always keep the plain object as error.
|
property, causing the check here to always keep the plain object as
|
||||||
|
error.
|
||||||
- hash: 355373f24df6be0989fad9429c2230166b33a3bf
|
- hash: 355373f24df6be0989fad9429c2230166b33a3bf
|
||||||
author: Jonas Hermsmeier
|
author: Jonas Hermsmeier
|
||||||
footers:
|
footers:
|
||||||
@@ -7895,9 +8273,11 @@
|
|||||||
change-type: patch
|
change-type: patch
|
||||||
subject: "upgrade(package): Update drivelist 6.1.5 -> 6.1.7"
|
subject: "upgrade(package): Update drivelist 6.1.5 -> 6.1.7"
|
||||||
body: >-
|
body: >-
|
||||||
This fixes a ReferenceError that could occur when the DeviceNode was null,
|
This fixes a ReferenceError that could occur when the DeviceNode was
|
||||||
|
null,
|
||||||
|
|
||||||
as well as devices being null when run after the system recovers from sleep / standby.
|
as well as devices being null when run after the system recovers from
|
||||||
|
sleep / standby.
|
||||||
- hash: 6e7484d3dabc2aeaa7cd471822d7019860cc4a5c
|
- hash: 6e7484d3dabc2aeaa7cd471822d7019860cc4a5c
|
||||||
author: Benedict Aas
|
author: Benedict Aas
|
||||||
subject: "feat(GUI): display succeeded and failed devices on finish screen"
|
subject: "feat(GUI): display succeeded and failed devices on finish screen"
|
||||||
@@ -8058,7 +8438,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
This replaces shelling out to `diskpart` on Windows to clear
|
This replaces shelling out to `diskpart` on Windows to clear
|
||||||
|
|
||||||
the partition table with `win-drive-clean`, which does so via DeviceIoControl.
|
the partition table with `win-drive-clean`, which does so via
|
||||||
|
DeviceIoControl.
|
||||||
- hash: abf2dc3efcf214a68c0b0e329d57a3f66bb5d342
|
- hash: abf2dc3efcf214a68c0b0e329d57a3f66bb5d342
|
||||||
author: Benedict Aas
|
author: Benedict Aas
|
||||||
footers:
|
footers:
|
||||||
@@ -8165,15 +8546,18 @@
|
|||||||
This updates the instructions to open the Developer Tools in the issue
|
This updates the instructions to open the Developer Tools in the issue
|
||||||
template,
|
template,
|
||||||
|
|
||||||
as the keyboard shortcuts have changed to their defaults on Linux & Windows
|
as the keyboard shortcuts have changed to their defaults on Linux &
|
||||||
|
Windows
|
||||||
|
|
||||||
from [Ctrl]+[Alt]+[I] to [Ctrl]+[Shift]+[I].
|
from [Ctrl]+[Alt]+[I] to [Ctrl]+[Shift]+[I].
|
||||||
|
|
||||||
Further, the editor config is updated to allow trailing spaces in Markdown
|
Further, the editor config is updated to allow trailing spaces in
|
||||||
|
Markdown
|
||||||
|
|
||||||
files to add trailing spaces to the list items in the issue template, in
|
files to add trailing spaces to the list items in the issue template, in
|
||||||
|
|
||||||
order to avoid people not putting whitespace in between, causing the formatting
|
order to avoid people not putting whitespace in between, causing the
|
||||||
|
formatting
|
||||||
|
|
||||||
to not be parsed properly.
|
to not be parsed properly.
|
||||||
- hash: 3dd646485fa34437ac3adb3caa5a594d439f1f68
|
- hash: 3dd646485fa34437ac3adb3caa5a594d439f1f68
|
||||||
@@ -8257,7 +8641,8 @@
|
|||||||
This replaces use of `electron.app.getName()` with the package.json's
|
This replaces use of `electron.app.getName()` with the package.json's
|
||||||
`.displayName`
|
`.displayName`
|
||||||
|
|
||||||
property to ensure the correct application name is displayed when packaged.
|
property to ensure the correct application name is displayed when
|
||||||
|
packaged.
|
||||||
- hash: cf340f48c3582f3e96f7b2dc16c11f44b7661363
|
- hash: cf340f48c3582f3e96f7b2dc16c11f44b7661363
|
||||||
author: Jonas Hermsmeier
|
author: Jonas Hermsmeier
|
||||||
footers:
|
footers:
|
||||||
@@ -8433,7 +8818,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
This updates `resin-cli-visuals` in order to fix drive selection in
|
This updates `resin-cli-visuals` in order to fix drive selection in
|
||||||
|
|
||||||
the CLI, which was caused by incompatibility of two different `drivelist` versions
|
the CLI, which was caused by incompatibility of two different
|
||||||
|
`drivelist` versions
|
||||||
- hash: bde1e32e29ae75ccecf7fc3bc1b03efd6e4f67b8
|
- hash: bde1e32e29ae75ccecf7fc3bc1b03efd6e4f67b8
|
||||||
author: Jonas Hermsmeier
|
author: Jonas Hermsmeier
|
||||||
footers:
|
footers:
|
||||||
@@ -8731,9 +9117,11 @@
|
|||||||
changelog-entry: Remove stale `invalidKey` check in store.
|
changelog-entry: Remove stale `invalidKey` check in store.
|
||||||
subject: "refactor: remove stale invalid key check in store"
|
subject: "refactor: remove stale invalid key check in store"
|
||||||
body: >-
|
body: >-
|
||||||
We remove a piece of code checking whether `_.keys` returns any non-string
|
We remove a piece of code checking whether `_.keys` returns any
|
||||||
|
non-string
|
||||||
|
|
||||||
values in its array, but per the Lodash documentation `_.keys` always returns an
|
values in its array, but per the Lodash documentation `_.keys` always
|
||||||
|
returns an
|
||||||
|
|
||||||
array of strings.
|
array of strings.
|
||||||
- hash: 83528df18be32bfe62d3e9e4578101077769a7cf
|
- hash: 83528df18be32bfe62d3e9e4578101077769a7cf
|
||||||
@@ -8759,7 +9147,8 @@
|
|||||||
changelog-entry: Make the drive-selector button orange on warnings.
|
changelog-entry: Make the drive-selector button orange on warnings.
|
||||||
subject: "feat(GUI): warning makes drive-selector button orange"
|
subject: "feat(GUI): warning makes drive-selector button orange"
|
||||||
body: >-
|
body: >-
|
||||||
We make the drive-selector button orange when there is a warning attached
|
We make the drive-selector button orange when there is a warning
|
||||||
|
attached
|
||||||
|
|
||||||
to the image-drive pair.
|
to the image-drive pair.
|
||||||
- hash: 4ce89f97fe02d714ce7f247a6a03ad6d326c3a8a
|
- hash: 4ce89f97fe02d714ce7f247a6a03ad6d326c3a8a
|
||||||
@@ -8988,7 +9377,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
Due to some Windows systems missing certain C runtime libraries
|
Due to some Windows systems missing certain C runtime libraries
|
||||||
|
|
||||||
(Visual C/C++ 2012 / 2015 Redistributables), we ignore errors when loading
|
(Visual C/C++ 2012 / 2015 Redistributables), we ignore errors when
|
||||||
|
loading
|
||||||
|
|
||||||
this module until we can ensure distribution of those along with it.
|
this module until we can ensure distribution of those along with it.
|
||||||
- hash: 21e595466d5d950d7c38b2411791f756ec6ebdca
|
- hash: 21e595466d5d950d7c38b2411791f756ec6ebdca
|
||||||
@@ -9073,7 +9463,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
This updates the `postshrinkwrap` script to traverse the dependency tree
|
This updates the `postshrinkwrap` script to traverse the dependency tree
|
||||||
|
|
||||||
and remove all `from` fields to avoid inconsistent diffs across platforms,
|
and remove all `from` fields to avoid inconsistent diffs across
|
||||||
|
platforms,
|
||||||
|
|
||||||
environments and installs when shrinkwrapping anew.
|
environments and installs when shrinkwrapping anew.
|
||||||
- hash: 619051a4b0cd8995e31838f221386b9b44e6ffc8
|
- hash: 619051a4b0cd8995e31838f221386b9b44e6ffc8
|
||||||
@@ -9485,7 +9876,8 @@
|
|||||||
This works around the "Cannot fetch index base URL
|
This works around the "Cannot fetch index base URL
|
||||||
http://pypi.python.org/simple/"
|
http://pypi.python.org/simple/"
|
||||||
|
|
||||||
error by installing pip==9.0.1 directly from the pypi.python.org/packages/
|
error by installing pip==9.0.1 directly from the
|
||||||
|
pypi.python.org/packages/
|
||||||
- hash: c8b2b652354029cedceda2637bed13fee65f8587
|
- hash: c8b2b652354029cedceda2637bed13fee65f8587
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
footers:
|
footers:
|
||||||
@@ -9537,9 +9929,11 @@
|
|||||||
|
|
||||||
WARNING: Binary file: lib/blobs/usbboot/raspberrypi/bootcode.bin
|
WARNING: Binary file: lib/blobs/usbboot/raspberrypi/bootcode.bin
|
||||||
|
|
||||||
WARNING: Binary file: tests/image-stream/data/unrecognized/xz-without-extension
|
WARNING: Binary file:
|
||||||
|
tests/image-stream/data/unrecognized/xz-without-extension
|
||||||
|
|
||||||
WARNING: Binary file: tests/image-stream/data/unrecognized/xz-with-invalid-extension.foo
|
WARNING: Binary file:
|
||||||
|
tests/image-stream/data/unrecognized/xz-with-invalid-extension.foo
|
||||||
|
|
||||||
```
|
```
|
||||||
- hash: f4e0121639d8f2cbcc15b6577ec15d7ecbab7e71
|
- hash: f4e0121639d8f2cbcc15b6577ec15d7ecbab7e71
|
||||||
@@ -11272,7 +11666,8 @@
|
|||||||
|
|
||||||
https://developer.apple.com/library/mac/technotes/tn2206/_index.html
|
https://developer.apple.com/library/mac/technotes/tn2206/_index.html
|
||||||
|
|
||||||
> Code signing uses extended attributes to store signatures in non-Mach-O
|
> Code signing uses extended attributes to store signatures in
|
||||||
|
non-Mach-O
|
||||||
|
|
||||||
> executables such as script files. If the extended attributes are lost
|
> executables such as script files. If the extended attributes are lost
|
||||||
|
|
||||||
@@ -11284,7 +11679,8 @@
|
|||||||
|
|
||||||
> One way to guarantee preservation of extended attributes is by packing
|
> One way to guarantee preservation of extended attributes is by packing
|
||||||
|
|
||||||
> up your signed code in a read-write disk image (DMG) file before signing
|
> up your signed code in a read-write disk image (DMG) file before
|
||||||
|
signing
|
||||||
|
|
||||||
> and then, after signing, converting to read-only. You probably don't
|
> and then, after signing, converting to read-only. You probably don't
|
||||||
|
|
||||||
@@ -11354,23 +11750,32 @@
|
|||||||
|
|
||||||
Unhandled rejection TypeError: Cannot read property '0' of undefined
|
Unhandled rejection TypeError: Cannot read property '0' of undefined
|
||||||
|
|
||||||
at Number.indexedGetter (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
|
at Number.indexedGetter
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
|
||||||
|
|
||||||
at Number.tryCatcher (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
|
at Number.tryCatcher
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
|
||||||
|
|
||||||
at Promise._settlePromiseFromHandler (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
|
at Promise._settlePromiseFromHandler
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
|
||||||
|
|
||||||
at Promise._settlePromise (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
|
at Promise._settlePromise
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
|
||||||
|
|
||||||
at Promise._settlePromise0 (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
|
at Promise._settlePromise0
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
|
||||||
|
|
||||||
at Promise._settlePromises (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
|
at Promise._settlePromises
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
|
||||||
|
|
||||||
at Async._drainQueue (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
|
at Async._drainQueue
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
|
||||||
|
|
||||||
at Async._drainQueues (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
|
at Async._drainQueues
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
|
||||||
|
|
||||||
at Immediate.Async.drainQueues [as _onImmediate] (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
|
at Immediate.Async.drainQueues [as _onImmediate]
|
||||||
|
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
|
||||||
|
|
||||||
at processImmediate [as _immediateCallback] (timers.js:383:17)
|
at processImmediate [as _immediateCallback] (timers.js:383:17)
|
||||||
- hash: 6bd086f1c5c6654a47125cf2d46788655cae2553
|
- hash: 6bd086f1c5c6654a47125cf2d46788655cae2553
|
||||||
@@ -11386,7 +11791,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
From the documentation:
|
From the documentation:
|
||||||
|
|
||||||
> `useContentSize` Boolean - The `width` and `height` would be used as web
|
> `useContentSize` Boolean - The `width` and `height` would be used as
|
||||||
|
web
|
||||||
|
|
||||||
> page’s size, which means the actual window’s size will include window
|
> page’s size, which means the actual window’s size will include window
|
||||||
|
|
||||||
@@ -12203,7 +12609,8 @@
|
|||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
From https://medium.com/@kentcdodds/how-to-distribute-your-angularjs-module-e04d4dd58ddc#.yqg2zo8im
|
From
|
||||||
|
https://medium.com/@kentcdodds/how-to-distribute-your-angularjs-module-e04d4dd58ddc#.yqg2zo8im
|
||||||
- hash: b8f63af3f81bca3abd055303bc91ab35eb126655
|
- hash: b8f63af3f81bca3abd055303bc91ab35eb126655
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
footers:
|
footers:
|
||||||
@@ -12455,7 +12862,8 @@
|
|||||||
body: >-
|
body: >-
|
||||||
Electron no longer supports 10.8.
|
Electron no longer supports 10.8.
|
||||||
|
|
||||||
See http://electron.atom.io/docs/v0.37.5/tutorial/supported-platforms/#os-x
|
See
|
||||||
|
http://electron.atom.io/docs/v0.37.5/tutorial/supported-platforms/#os-x
|
||||||
- hash: 097c9a4aa37029154c3efe8564edbeef048926ad
|
- hash: 097c9a4aa37029154c3efe8564edbeef048926ad
|
||||||
author: Juan Cruz Viotti
|
author: Juan Cruz Viotti
|
||||||
subject: Add subtle hover styling to footer links
|
subject: Add subtle hover styling to footer links
|
||||||
|
|||||||
93
CHANGELOG.md
93
CHANGELOG.md
@@ -3,6 +3,99 @@
|
|||||||
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.19.8
|
||||||
|
## (2024-04-22)
|
||||||
|
|
||||||
|
* patch: replace deprecated pkg with yao-pkg and bump etcher-util node v to 20.10 [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.19.7
|
||||||
|
## (2024-04-22)
|
||||||
|
|
||||||
|
* patch: fix formating [Edwin Joassart]
|
||||||
|
* patch: configure prettier in the project to use balena-lint configuration [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.19.6
|
||||||
|
## (2024-04-19)
|
||||||
|
|
||||||
|
* patch: fix win signature process [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.19.5
|
||||||
|
## (2024-02-14)
|
||||||
|
|
||||||
|
* Replace deprecated flowzone input tests_run_on [Kyle Harding]
|
||||||
|
|
||||||
|
# v1.19.4
|
||||||
|
## (2024-01-26)
|
||||||
|
|
||||||
|
* patch: remove screensaver error when not on etcher-pro [Edwin Joassart]
|
||||||
|
* patch: fix typo in IPC server id [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.19.3
|
||||||
|
## (2023-12-22)
|
||||||
|
|
||||||
|
* Update dependencies [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.19.2
|
||||||
|
## (2023-12-22)
|
||||||
|
|
||||||
|
* fix: typos [Rotzbua]
|
||||||
|
|
||||||
|
# v1.19.1
|
||||||
|
## (2023-12-22)
|
||||||
|
|
||||||
|
* patch: update winget-releaser v2 [Vedant]
|
||||||
|
|
||||||
|
# v1.19.0
|
||||||
|
## (2023-12-21)
|
||||||
|
|
||||||
|
* Use native ARM runner for Apple Silicon builds [Akis Kesoglou]
|
||||||
|
* Calculate and upload build artifact sha256 checksums [Akis Kesoglou]
|
||||||
|
* Migrate build pipeline to Electron Forge [Akis Kesoglou]
|
||||||
|
|
||||||
|
# v1.18.14
|
||||||
|
## (2023-12-20)
|
||||||
|
|
||||||
|
* Remove repo config from flowzone.yml [Kyle Harding]
|
||||||
|
* Update actions/upload-artifact to v4 [Kyle Harding]
|
||||||
|
|
||||||
|
# v1.18.13
|
||||||
|
## (2023-10-16)
|
||||||
|
|
||||||
|
* patch: upgrade to electron 25 [Edwin Joassart]
|
||||||
|
* patch: refactor scanner, loader and flasher out of gui + upgrade to electron 25 [Edwin Joassart]
|
||||||
|
|
||||||
|
# v1.18.12
|
||||||
|
## (2023-07-19)
|
||||||
|
|
||||||
|
* Update instructions for installing deb file [Jorge Capona]
|
||||||
|
|
||||||
|
# v1.18.11
|
||||||
|
## (2023-07-13)
|
||||||
|
|
||||||
|
* fix: prevent stealing window focus from auth dialog [leadpogrommer]
|
||||||
|
|
||||||
|
# v1.18.10
|
||||||
|
## (2023-07-12)
|
||||||
|
|
||||||
|
* spelling: validates [Josh Soref]
|
||||||
|
* spelling: undefined [Josh Soref]
|
||||||
|
* spelling: except if [Josh Soref]
|
||||||
|
|
||||||
|
# v1.18.9
|
||||||
|
## (2023-07-12)
|
||||||
|
|
||||||
|
* Fix opening links from within SafeWebView [Akis Kesoglou]
|
||||||
|
|
||||||
|
# v1.18.8
|
||||||
|
## (2023-04-26)
|
||||||
|
|
||||||
|
* Patch: Fix Support link [Oliver Plummer]
|
||||||
|
|
||||||
|
# v1.18.7
|
||||||
|
## (2023-04-25)
|
||||||
|
|
||||||
|
* patch: update docs to remove cloudsmith install instructions for linux [Edwin Joassart]
|
||||||
|
|
||||||
# v1.18.6
|
# v1.18.6
|
||||||
## (2023-03-21)
|
## (2023-03-21)
|
||||||
|
|
||||||
|
|||||||
152
Makefile
152
Makefile
@@ -1,152 +0,0 @@
|
|||||||
# ---------------------------------------------------------------------
|
|
||||||
# Build configuration
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
RESIN_SCRIPTS ?= ./scripts/resin
|
|
||||||
export NPM_VERSION ?= 6.14.8
|
|
||||||
S3_BUCKET = artifacts.ci.balena-cloud.com
|
|
||||||
|
|
||||||
# This directory will be completely deleted by the `clean` rule
|
|
||||||
BUILD_DIRECTORY ?= dist
|
|
||||||
|
|
||||||
BUILD_TEMPORARY_DIRECTORY = $(BUILD_DIRECTORY)/.tmp
|
|
||||||
|
|
||||||
$(BUILD_DIRECTORY):
|
|
||||||
mkdir $@
|
|
||||||
|
|
||||||
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
|
||||||
mkdir $@
|
|
||||||
|
|
||||||
SHELL := /bin/bash
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Operating system and architecture detection
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
# http://stackoverflow.com/a/12099167
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
PLATFORM = win32
|
|
||||||
|
|
||||||
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
|
|
||||||
HOST_ARCH = x64
|
|
||||||
else
|
|
||||||
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
|
|
||||||
HOST_ARCH = x64
|
|
||||||
endif
|
|
||||||
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
|
|
||||||
HOST_ARCH = x86
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
else
|
|
||||||
ifeq ($(shell uname -s),Linux)
|
|
||||||
PLATFORM = linux
|
|
||||||
|
|
||||||
ifeq ($(shell uname -m),x86_64)
|
|
||||||
HOST_ARCH = x64
|
|
||||||
endif
|
|
||||||
ifneq ($(filter %86,$(shell uname -m)),)
|
|
||||||
HOST_ARCH = x86
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -m),armv7l)
|
|
||||||
HOST_ARCH = armv7hf
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -m),aarch64)
|
|
||||||
HOST_ARCH = aarch64
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -m),armv8)
|
|
||||||
HOST_ARCH = aarch64
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -m),arm64)
|
|
||||||
HOST_ARCH = aarch64
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -s),Darwin)
|
|
||||||
PLATFORM = darwin
|
|
||||||
|
|
||||||
ifeq ($(shell uname -m),x86_64)
|
|
||||||
HOST_ARCH = x64
|
|
||||||
endif
|
|
||||||
ifeq ($(shell uname -m),arm64)
|
|
||||||
HOST_ARCH = aarch64
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifndef PLATFORM
|
|
||||||
$(error We could not detect your host platform)
|
|
||||||
endif
|
|
||||||
ifndef HOST_ARCH
|
|
||||||
$(error We could not detect your host architecture)
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Default to host architecture. You can override by doing:
|
|
||||||
#
|
|
||||||
# make <target> TARGET_ARCH=<arch>
|
|
||||||
#
|
|
||||||
TARGET_ARCH ?= $(HOST_ARCH)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Electron
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
electron-develop:
|
|
||||||
git submodule update --init && \
|
|
||||||
npm ci && \
|
|
||||||
npm run webpack
|
|
||||||
|
|
||||||
electron-test:
|
|
||||||
$(RESIN_SCRIPTS)/electron/test.sh \
|
|
||||||
-b $(shell pwd) \
|
|
||||||
-s $(PLATFORM)
|
|
||||||
|
|
||||||
assets/dmg/background.tiff: assets/dmg/background.png assets/dmg/background@2x.png
|
|
||||||
tiffutil -cathidpicheck $^ -out $@
|
|
||||||
|
|
||||||
electron-build: assets/dmg/background.tiff | $(BUILD_TEMPORARY_DIRECTORY)
|
|
||||||
$(RESIN_SCRIPTS)/electron/build.sh \
|
|
||||||
-b $(shell pwd) \
|
|
||||||
-r $(TARGET_ARCH) \
|
|
||||||
-s $(PLATFORM) \
|
|
||||||
-v production \
|
|
||||||
-n $(BUILD_TEMPORARY_DIRECTORY)/npm
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# Phony targets
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
TARGETS = \
|
|
||||||
help \
|
|
||||||
info \
|
|
||||||
lint \
|
|
||||||
test \
|
|
||||||
clean \
|
|
||||||
distclean \
|
|
||||||
electron-develop \
|
|
||||||
electron-test \
|
|
||||||
electron-build
|
|
||||||
|
|
||||||
.PHONY: $(TARGETS)
|
|
||||||
|
|
||||||
lint:
|
|
||||||
npm run lint
|
|
||||||
|
|
||||||
test:
|
|
||||||
npm run test
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Available targets: $(TARGETS)"
|
|
||||||
|
|
||||||
info:
|
|
||||||
@echo "Platform : $(PLATFORM)"
|
|
||||||
@echo "Host arch : $(HOST_ARCH)"
|
|
||||||
@echo "Target arch : $(TARGET_ARCH)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BUILD_DIRECTORY)
|
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
rm -rf node_modules
|
|
||||||
rm -rf dist
|
|
||||||
rm -rf generated
|
|
||||||
rm -rf $(BUILD_TEMPORARY_DIRECTORY)
|
|
||||||
|
|
||||||
.DEFAULT_GOAL = help
|
|
||||||
133
README.md
133
README.md
@@ -17,13 +17,9 @@ was written correctly, and much more. It can also directly flash Raspberry Pi de
|
|||||||
|
|
||||||
## Supported Operating Systems
|
## Supported Operating Systems
|
||||||
|
|
||||||
- Linux (most distros)
|
- Linux; most distros; Intel 64-bit.
|
||||||
- macOS 10.10 (Yosemite) and later
|
- Windows 10 and later; Intel 64-bit.
|
||||||
- Microsoft Windows 7 and later
|
- macOS 10.13 (High Sierra) and later; both Intel and Apple Silicon.
|
||||||
|
|
||||||
**Note**: Etcher will run on any platform officially supported by
|
|
||||||
[Electron][electron]. Read more in their
|
|
||||||
[documentation][electron-supported-platforms].
|
|
||||||
|
|
||||||
## Installers
|
## Installers
|
||||||
|
|
||||||
@@ -32,132 +28,32 @@ installers for all supported operating systems.
|
|||||||
|
|
||||||
## Packages
|
## 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)
|
Package for Debian and Ubuntu can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
|
||||||
|
|
||||||
1. Add Etcher Debian repository:
|
##### Install .deb file using apt
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -1sLf \
|
sudo apt install ./balena-etcher_******_amd64.deb
|
||||||
'https://dl.cloudsmith.io/public/balena/etcher/setup.deb.sh' \
|
|
||||||
| sudo -E bash
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Update and install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt-get update #you can use apt instead of apt-get as well
|
|
||||||
sudo apt-get install balena-etcher-electron
|
|
||||||
```
|
|
||||||
>Note: after v1.7.9 the package name changed to `balena-etcher` (no electron at the end)
|
|
||||||
|
|
||||||
##### Uninstall
|
##### Uninstall
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt-get remove balena-etcher-electron
|
sudo apt remove balena-etcher
|
||||||
rm /etc/apt/sources.list.d/balena-etcher.list
|
```
|
||||||
apt-get clean
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
apt-get update
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
|
|
||||||
1. Add Etcher rpm repository:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -1sLf \
|
|
||||||
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
|
|
||||||
| sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update and install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo dnf install -y balena-etcher-electron
|
|
||||||
```
|
|
||||||
>Note: after v1.7.9 the package name changed to `balena-etcher` (no electron at the end)
|
|
||||||
|
|
||||||
###### Uninstall
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rm /etc/yum.repos.d/balena-etcher.repo
|
|
||||||
rm /etc/yum.repos.d/balena-etcher-source.repo
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Yum
|
##### Yum
|
||||||
|
|
||||||
1. Add Etcher rpm repository:
|
Package for Fedora-based and Redhat can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
|
||||||
|
|
||||||
```sh
|
1. Install using yum
|
||||||
curl -1sLf \
|
|
||||||
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
|
|
||||||
| sudo -E bash
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update and install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo yum install -y balena-etcher-electron
|
|
||||||
```
|
|
||||||
>Note: after v1.7.9 the package name changed to `balena-etcher` (no electron at the end)
|
|
||||||
|
|
||||||
###### Uninstall
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo yum remove -y balena-etcher-electron
|
sudo yum localinstall balena-etcher-***.x86_64.rpm
|
||||||
rm /etc/yum.repos.d/balena-etcher.repo
|
|
||||||
rm /etc/yum.repos.d/balena-etcher-source.repo
|
|
||||||
```
|
|
||||||
|
|
||||||
#### OpenSUSE LEAP & Tumbleweed install (zypper)
|
|
||||||
|
|
||||||
1. Add the repo
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -1sLf \
|
|
||||||
'https://dl.cloudsmith.io/public/balena/etcher/setup.rpm.sh' \
|
|
||||||
| sudo -E bash
|
|
||||||
```
|
|
||||||
2. Update and install
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo zypper up
|
|
||||||
sudo zypper install balena-etcher-electron
|
|
||||||
```
|
|
||||||
>Note: after v1.7.9 the package name changed to `balena-etcher` (no electron at the end)
|
|
||||||
|
|
||||||
##### 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)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo eopkg it etcher
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Uninstall
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo eopkg rm etcher
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Arch/Manjaro Linux (GNU/Linux x64)
|
#### Arch/Manjaro Linux (GNU/Linux x64)
|
||||||
@@ -173,6 +69,7 @@ yay -S balena-etcher
|
|||||||
```sh
|
```sh
|
||||||
yay -R balena-etcher
|
yay -R balena-etcher
|
||||||
```
|
```
|
||||||
|
|
||||||
#### WinGet (Windows)
|
#### WinGet (Windows)
|
||||||
|
|
||||||
This package is updated by [gh-action](https://github.com/vedantmgoyal2009/winget-releaser), and is kept up to date automatically.
|
This package is updated by [gh-action](https://github.com/vedantmgoyal2009/winget-releaser), and is kept up to date automatically.
|
||||||
@@ -215,11 +112,9 @@ the [license].
|
|||||||
[etcher]: https://balena.io/etcher
|
[etcher]: https://balena.io/etcher
|
||||||
[electron]: https://electronjs.org/
|
[electron]: https://electronjs.org/
|
||||||
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
|
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
|
||||||
[support]: https://github.com/balena-io/etcher/blob/master/SUPPORT.md
|
[support]: https://github.com/balena-io/etcher/blob/master/docs/SUPPORT.md
|
||||||
[contributing]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
|
[contributing]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
|
||||||
[user-documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
|
[user-documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
|
||||||
[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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
afterPack.js
31
afterPack.js
@@ -1,31 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const cp = require('child_process')
|
|
||||||
const fs = require('fs')
|
|
||||||
const outdent = require('outdent')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
exports.default = function(context) {
|
|
||||||
if (context.packager.platform.name !== 'linux') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const scriptPath = path.join(context.appOutDir, context.packager.executableName)
|
|
||||||
const binPath = scriptPath + '.bin'
|
|
||||||
cp.execFileSync('mv', [scriptPath, binPath])
|
|
||||||
fs.writeFileSync(
|
|
||||||
scriptPath,
|
|
||||||
outdent({trimTrailingNewline: false})`
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Resolve symlinks. Warning, readlink -f doesn't work on MacOS/BSD
|
|
||||||
script_dir="$(dirname "$(readlink -f "\${BASH_SOURCE[0]}")")"
|
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]] || [[ $ELECTRON_RUN_AS_NODE ]]; then
|
|
||||||
"\${script_dir}"/${context.packager.executableName}.bin "$@"
|
|
||||||
else
|
|
||||||
"\${script_dir}"/${context.packager.executableName}.bin "$@" --no-sandbox
|
|
||||||
fi
|
|
||||||
`
|
|
||||||
)
|
|
||||||
cp.execFileSync('chmod', ['+x', scriptPath])
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const { notarize } = require('electron-notarize')
|
|
||||||
const { ELECTRON_SKIP_NOTARIZATION } = process.env
|
|
||||||
|
|
||||||
async function main(context) {
|
|
||||||
const { electronPlatformName, appOutDir } = context
|
|
||||||
if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const appName = context.packager.appInfo.productFilename
|
|
||||||
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({
|
|
||||||
appBundleId: 'io.balena.etcher',
|
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
|
||||||
appleId,
|
|
||||||
appleIdPassword
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.default = main
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
owner: balena-io
|
|
||||||
repo: etcher
|
|
||||||
provider: github
|
|
||||||
updaterCacheDirName: balena-etcher-updater
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
boolen->boolean
|
|
||||||
aknowledge->acknowledge
|
|
||||||
seleted->selected
|
|
||||||
reming->remind
|
|
||||||
locl->local
|
|
||||||
subsribe->subscribe
|
|
||||||
unsubsribe->unsubscribe
|
|
||||||
calcluate->calculate
|
|
||||||
dictionaty->dictionary
|
|
||||||
@@ -75,9 +75,7 @@ cd etcher
|
|||||||
#### GUI
|
#### GUI
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Build the GUI
|
# Build and start application
|
||||||
npm run webpack #or npm run build
|
|
||||||
# Start Electron
|
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -104,7 +102,6 @@ systems as they can before sending a pull request.
|
|||||||
*The test suite is run automatically by CI servers when you send a pull
|
*The test suite is run automatically by CI servers when you send a pull
|
||||||
request.*
|
request.*
|
||||||
|
|
||||||
|
|
||||||
We make use of [EditorConfig] to communicate indentation, line endings and
|
We make use of [EditorConfig] to communicate indentation, line endings and
|
||||||
other text editing default. We encourage you to install the relevant plugin in
|
other text editing default. We encourage you to install the relevant plugin in
|
||||||
your text editor of choice to avoid having to fix any issues during the review
|
your text editor of choice to avoid having to fix any issues during the review
|
||||||
@@ -113,7 +110,8 @@ process.
|
|||||||
Updating a dependency
|
Updating a dependency
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
- Commit *both* `package.json` and `package-lock.json`.
|
- Install new version of dependency using npm
|
||||||
|
- Commit *both* `package.json` and `npm-shrinkwrap.json`.
|
||||||
|
|
||||||
Diffing Binaries
|
Diffing Binaries
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
@@ -58,30 +58,23 @@ export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
|
|||||||
|
|
||||||
##### Clean dist folder
|
##### Clean dist folder
|
||||||
|
|
||||||
**NOTE:** Make sure to adjust the path as necessary (here the Etcher repository has been cloned to `/home/$USER/code/etcher`)
|
Delete `.webpack` and `out/`.
|
||||||
|
|
||||||
##### Generating artifacts
|
##### Generating artifacts
|
||||||
|
|
||||||
The artifacts are generated by the CI and published as draft-release or pre-release.
|
The artifacts are generated by the CI and published as draft-release or pre-release.
|
||||||
`electron-builder` is used to create the packaged application.
|
Etcher is built with electron-forge. Run:
|
||||||
|
|
||||||
#### Mac OS
|
```
|
||||||
|
npm run make
|
||||||
|
```
|
||||||
|
|
||||||
**ATTENTION:** For production releases you'll need the code-signing key,
|
Our CI will appropriately sign artifacts for macOS and some Windows targets.
|
||||||
and set `CSC_NAME` to generate signed binaries on Mac OS.
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
|
|
||||||
**ATTENTION:** For production releases you'll need the code-signing key,
|
|
||||||
and set `CSC_LINK`, and `CSC_KEY_PASSWORD` to generate signed binaries on Windows.
|
|
||||||
|
|
||||||
**NOTE:**
|
|
||||||
- Keep in mind to also generate artifacts for x86, with `TARGET_ARCH=x86`.
|
|
||||||
|
|
||||||
|
|
||||||
### Uploading packages to Cloudfront
|
### Uploading packages to Cloudfront
|
||||||
|
|
||||||
Log in to cloudfront and upload the `rpm` and `deb` files.
|
Log in to cloudfront and upload the `rpm` and `deb` files.
|
||||||
|
|
||||||
### Dealing with a Problematic Release
|
### Dealing with a Problematic Release
|
||||||
|
|
||||||
|
|||||||
@@ -36,14 +36,17 @@ employee by asking for it from the relevant people.
|
|||||||
Packaging
|
Packaging
|
||||||
---------
|
---------
|
||||||
|
|
||||||
The resulting installers will be saved to `dist/out`.
|
Run the following command on each platform:
|
||||||
|
|
||||||
Run the following commands on all platforms with the right arguments:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./node_modules/electron-builder build <...>
|
npm run make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will produce all targets (eg. zip, dmg) specified in forge.config.ts for the
|
||||||
|
host platform and architecture.
|
||||||
|
|
||||||
|
The resulting artifacts can be found in `out/make`.
|
||||||
|
|
||||||
|
|
||||||
Publishing to Cloudfront
|
Publishing to Cloudfront
|
||||||
---------------------
|
---------------------
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
# https://www.electron.build/configuration/configuration
|
|
||||||
appId: io.balena.etcher
|
|
||||||
copyright: Copyright 2016-2023 Balena Ltd
|
|
||||||
productName: balenaEtcher
|
|
||||||
afterPack: ./afterPack.js
|
|
||||||
afterSign: ./afterSignHook.js
|
|
||||||
asar: false
|
|
||||||
files:
|
|
||||||
- generated
|
|
||||||
- lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
|
|
||||||
- lib/shared/catalina-sudo/sudo-askpass.osascript-en.js
|
|
||||||
mac:
|
|
||||||
icon: assets/icon.icns
|
|
||||||
category: public.app-category.developer-tools
|
|
||||||
hardenedRuntime: true
|
|
||||||
entitlements: "entitlements.mac.plist"
|
|
||||||
entitlementsInherit: "entitlements.mac.plist"
|
|
||||||
artifactName: "${productName}-${version}.${ext}"
|
|
||||||
target:
|
|
||||||
- dmg
|
|
||||||
dmg:
|
|
||||||
background: assets/dmg/background.tiff
|
|
||||||
icon: assets/icon.icns
|
|
||||||
iconSize: 110
|
|
||||||
contents:
|
|
||||||
- x: 140
|
|
||||||
y: 225
|
|
||||||
- x: 415
|
|
||||||
y: 225
|
|
||||||
type: link
|
|
||||||
path: /Applications
|
|
||||||
window:
|
|
||||||
width: 540
|
|
||||||
height: 405
|
|
||||||
win:
|
|
||||||
icon: assets/icon.ico
|
|
||||||
target:
|
|
||||||
- zip
|
|
||||||
- nsis
|
|
||||||
- portable
|
|
||||||
nsis:
|
|
||||||
oneClick: true
|
|
||||||
runAfterFinish: true
|
|
||||||
installerIcon: assets/icon.ico
|
|
||||||
uninstallerIcon: assets/icon.ico
|
|
||||||
deleteAppDataOnUninstall: true
|
|
||||||
license: LICENSE
|
|
||||||
artifactName: "${productName}-Setup-${version}.${ext}"
|
|
||||||
portable:
|
|
||||||
artifactName: "${productName}-Portable-${version}.${ext}"
|
|
||||||
requestExecutionLevel: user
|
|
||||||
linux:
|
|
||||||
icon: assets/iconset
|
|
||||||
target:
|
|
||||||
- AppImage
|
|
||||||
- rpm
|
|
||||||
- deb
|
|
||||||
category: Utility
|
|
||||||
packageCategory: utils
|
|
||||||
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.
|
|
||||||
appImage:
|
|
||||||
artifactName: ${productName}-${version}-${env.ELECTRON_BUILDER_ARCHITECTURE}.${ext}
|
|
||||||
deb:
|
|
||||||
priority: optional
|
|
||||||
compression: bzip2
|
|
||||||
depends:
|
|
||||||
- gconf-service
|
|
||||||
- gconf2
|
|
||||||
- libasound2
|
|
||||||
- libatk1.0-0
|
|
||||||
- libc6
|
|
||||||
- libcairo2
|
|
||||||
- libcups2
|
|
||||||
- libdbus-1-3
|
|
||||||
- libexpat1
|
|
||||||
- libfontconfig1
|
|
||||||
- libfreetype6
|
|
||||||
- libgbm1
|
|
||||||
- libgcc1
|
|
||||||
- libgconf-2-4
|
|
||||||
- libgdk-pixbuf2.0-0
|
|
||||||
- libglib2.0-0
|
|
||||||
- libgtk-3-0
|
|
||||||
- liblzma5
|
|
||||||
- libnotify4
|
|
||||||
- libnspr4
|
|
||||||
- libnss3
|
|
||||||
- libpango1.0-0 | libpango-1.0-0
|
|
||||||
- libstdc++6
|
|
||||||
- libx11-6
|
|
||||||
- libxcomposite1
|
|
||||||
- libxcursor1
|
|
||||||
- libxdamage1
|
|
||||||
- libxext6
|
|
||||||
- libxfixes3
|
|
||||||
- libxi6
|
|
||||||
- libxrandr2
|
|
||||||
- libxrender1
|
|
||||||
- libxss1
|
|
||||||
- libxtst6
|
|
||||||
- polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1
|
|
||||||
afterInstall: "./after-install.tpl"
|
|
||||||
rpm:
|
|
||||||
depends:
|
|
||||||
- util-linux
|
|
||||||
protocols:
|
|
||||||
name: etcher
|
|
||||||
schemes:
|
|
||||||
- etcher
|
|
||||||
@@ -14,5 +14,11 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.get-task-allow</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
157
forge.config.ts
Normal file
157
forge.config.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import type { ForgeConfig } from '@electron-forge/shared-types';
|
||||||
|
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
|
||||||
|
import { MakerZIP } from '@electron-forge/maker-zip';
|
||||||
|
import { MakerDeb } from '@electron-forge/maker-deb';
|
||||||
|
import { MakerRpm } from '@electron-forge/maker-rpm';
|
||||||
|
import { MakerDMG } from '@electron-forge/maker-dmg';
|
||||||
|
import { MakerAppImage } from '@reforged/maker-appimage';
|
||||||
|
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
|
||||||
|
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
|
||||||
|
|
||||||
|
import { mainConfig, rendererConfig } from './webpack.config';
|
||||||
|
import * as sidecar from './forge.sidecar';
|
||||||
|
|
||||||
|
import { hostDependencies, productDescription } from './package.json';
|
||||||
|
|
||||||
|
const osxSigningConfig: any = {};
|
||||||
|
let winSigningConfig: any = {};
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
osxSigningConfig.osxNotarize = {
|
||||||
|
tool: 'notarytool',
|
||||||
|
appleId: process.env.XCODE_APP_LOADER_EMAIL,
|
||||||
|
appleIdPassword: process.env.XCODE_APP_LOADER_PASSWORD,
|
||||||
|
teamId: process.env.XCODE_APP_LOADER_TEAM_ID,
|
||||||
|
};
|
||||||
|
|
||||||
|
winSigningConfig = {
|
||||||
|
signWithParams: `-sha1 ${process.env.SM_CODE_SIGNING_CERT_SHA1_HASH} -tr ${process.env.TIMESTAMP_SERVER} -td sha256 -fd sha256 -d balena-etcher`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: ForgeConfig = {
|
||||||
|
packagerConfig: {
|
||||||
|
asar: true,
|
||||||
|
icon: './assets/icon',
|
||||||
|
executableName:
|
||||||
|
process.platform === 'linux' ? 'balena-etcher' : 'balenaEtcher',
|
||||||
|
appBundleId: 'io.balena.etcher',
|
||||||
|
appCategoryType: 'public.app-category.developer-tools',
|
||||||
|
appCopyright: 'Copyright 2016-2023 Balena Ltd',
|
||||||
|
darwinDarkModeSupport: true,
|
||||||
|
protocols: [{ name: 'etcher', schemes: ['etcher'] }],
|
||||||
|
extraResource: [
|
||||||
|
'lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js',
|
||||||
|
'lib/shared/catalina-sudo/sudo-askpass.osascript-en.js',
|
||||||
|
],
|
||||||
|
osxSign: {
|
||||||
|
optionsForFile: () => ({
|
||||||
|
entitlements: './entitlements.mac.plist',
|
||||||
|
hardenedRuntime: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
...osxSigningConfig,
|
||||||
|
},
|
||||||
|
rebuildConfig: {},
|
||||||
|
makers: [
|
||||||
|
new MakerZIP(),
|
||||||
|
new MakerSquirrel({
|
||||||
|
setupIcon: 'assets/icon.ico',
|
||||||
|
...winSigningConfig,
|
||||||
|
}),
|
||||||
|
new MakerDMG({
|
||||||
|
background: './assets/dmg/background.tiff',
|
||||||
|
icon: './assets/icon.icns',
|
||||||
|
iconSize: 110,
|
||||||
|
contents: ((opts: { appPath: string }) => {
|
||||||
|
return [
|
||||||
|
{ x: 140, y: 250, type: 'file', path: opts.appPath },
|
||||||
|
{ x: 415, y: 250, type: 'link', path: '/Applications' },
|
||||||
|
];
|
||||||
|
}) as any, // type of MakerDMGConfig omits `appPath`
|
||||||
|
additionalDMGOptions: {
|
||||||
|
window: {
|
||||||
|
size: {
|
||||||
|
width: 540,
|
||||||
|
height: 425,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
x: 400,
|
||||||
|
y: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new MakerAppImage({
|
||||||
|
options: {
|
||||||
|
icon: './assets/icon.png',
|
||||||
|
categories: ['Utility'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new MakerRpm({
|
||||||
|
options: {
|
||||||
|
icon: './assets/icon.png',
|
||||||
|
categories: ['Utility'],
|
||||||
|
productDescription,
|
||||||
|
requires: ['util-linux'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new MakerDeb({
|
||||||
|
options: {
|
||||||
|
icon: './assets/icon.png',
|
||||||
|
categories: ['Utility'],
|
||||||
|
section: 'utils',
|
||||||
|
priority: 'optional',
|
||||||
|
productDescription,
|
||||||
|
scripts: {
|
||||||
|
postinst: './after-install.tpl',
|
||||||
|
},
|
||||||
|
depends: hostDependencies['debian'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
new AutoUnpackNativesPlugin({}),
|
||||||
|
new WebpackPlugin({
|
||||||
|
mainConfig,
|
||||||
|
renderer: {
|
||||||
|
config: rendererConfig,
|
||||||
|
nodeIntegration: true,
|
||||||
|
entryPoints: [
|
||||||
|
{
|
||||||
|
html: './lib/gui/app/index.html',
|
||||||
|
js: './lib/gui/app/renderer.ts',
|
||||||
|
name: 'main_window',
|
||||||
|
preload: {
|
||||||
|
js: './lib/gui/app/preload.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new sidecar.SidecarPlugin(),
|
||||||
|
],
|
||||||
|
hooks: {
|
||||||
|
readPackageJson: async (_config, packageJson) => {
|
||||||
|
packageJson.analytics = {};
|
||||||
|
|
||||||
|
if (process.env.SENTRY_TOKEN) {
|
||||||
|
packageJson.analytics.sentry = {
|
||||||
|
token: process.env.SENTRY_TOKEN,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.AMPLITUDE_TOKEN) {
|
||||||
|
packageJson.analytics.amplitude = {
|
||||||
|
token: 'balena-etcher',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageJson.packageType = 'dmg' | 'AppImage' | 'rpm' | 'deb' | 'zip' | 'nsis' | 'portable'
|
||||||
|
|
||||||
|
return packageJson;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
168
forge.sidecar.ts
Normal file
168
forge.sidecar.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { PluginBase } from '@electron-forge/plugin-base';
|
||||||
|
import {
|
||||||
|
ForgeHookMap,
|
||||||
|
ResolvedForgeConfig,
|
||||||
|
} from '@electron-forge/shared-types';
|
||||||
|
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
|
||||||
|
import { DefinePlugin } from 'webpack';
|
||||||
|
|
||||||
|
import { execFileSync } from 'child_process';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import * as d from 'debug';
|
||||||
|
|
||||||
|
const debug = d('sidecar');
|
||||||
|
|
||||||
|
function isStartScrpt(): boolean {
|
||||||
|
return process.env.npm_lifecycle_event === 'start';
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWebpackDefine(
|
||||||
|
config: ResolvedForgeConfig,
|
||||||
|
defineName: string,
|
||||||
|
binDir: string,
|
||||||
|
binName: string,
|
||||||
|
): ResolvedForgeConfig {
|
||||||
|
config.plugins.forEach((plugin) => {
|
||||||
|
if (plugin.name !== 'webpack' || !(plugin instanceof WebpackPlugin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mainConfig } = plugin.config as any;
|
||||||
|
if (mainConfig.plugins == null) {
|
||||||
|
mainConfig.plugins = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = isStartScrpt()
|
||||||
|
? // on `npm start`, point directly to the binary
|
||||||
|
path.resolve(binDir, binName)
|
||||||
|
: // otherwise point relative to the resources folder of the bundled app
|
||||||
|
binName;
|
||||||
|
|
||||||
|
debug(`define '${defineName}'='${value}'`);
|
||||||
|
|
||||||
|
mainConfig.plugins.push(
|
||||||
|
new DefinePlugin({
|
||||||
|
// expose path to helper via this webpack define
|
||||||
|
[defineName]: JSON.stringify(value),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build(
|
||||||
|
sourcesDir: string,
|
||||||
|
buildForArchs: string,
|
||||||
|
binDir: string,
|
||||||
|
binName: string,
|
||||||
|
) {
|
||||||
|
const commands: Array<[string, string[], object?]> = [
|
||||||
|
['tsc', ['--project', 'tsconfig.sidecar.json', '--outDir', sourcesDir]],
|
||||||
|
];
|
||||||
|
|
||||||
|
buildForArchs.split(',').forEach((arch) => {
|
||||||
|
const binPath = isStartScrpt()
|
||||||
|
? // on `npm start`, we don't know the arch we're building for at the time we're
|
||||||
|
// adding the webpack define, so we just build under binDir
|
||||||
|
path.resolve(binDir, binName)
|
||||||
|
: // otherwise build in arch-specific directory within binDir
|
||||||
|
path.resolve(binDir, arch, binName);
|
||||||
|
|
||||||
|
// FIXME: rebuilding mountutils shouldn't be necessary, but it is.
|
||||||
|
// It's coming from etcher-sdk, a fix has been upstreamed but to use
|
||||||
|
// the latest etcher-sdk we need to upgrade axios at the same time.
|
||||||
|
commands.push(['npm', ['rebuild', 'mountutils', `--arch=${arch}`]]);
|
||||||
|
|
||||||
|
commands.push([
|
||||||
|
'pkg',
|
||||||
|
[
|
||||||
|
path.join(sourcesDir, 'util', 'api.js'),
|
||||||
|
'-c',
|
||||||
|
'pkg-sidecar.json',
|
||||||
|
// `--no-bytecode` so that we can cross-compile for arm64 on x64
|
||||||
|
'--no-bytecode',
|
||||||
|
'--public',
|
||||||
|
'--public-packages',
|
||||||
|
'"*"',
|
||||||
|
// always build for host platform and node version
|
||||||
|
// https://github.com/vercel/pkg-fetch/releases
|
||||||
|
'--target',
|
||||||
|
`node20-${arch}`,
|
||||||
|
'--output',
|
||||||
|
binPath,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.forEach(([cmd, args, opt]) => {
|
||||||
|
debug('running command:', cmd, args.join(' '));
|
||||||
|
execFileSync(cmd, args, { shell: true, stdio: 'inherit', ...opt });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyArtifact(
|
||||||
|
buildPath: string,
|
||||||
|
arch: string,
|
||||||
|
binDir: string,
|
||||||
|
binName: string,
|
||||||
|
) {
|
||||||
|
const binPath = isStartScrpt()
|
||||||
|
? // on `npm start`, we don't know the arch we're building for at the time we're
|
||||||
|
// adding the webpack define, so look for the binary directly under binDir
|
||||||
|
path.resolve(binDir, binName)
|
||||||
|
: // otherwise look into arch-specific directory within binDir
|
||||||
|
path.resolve(binDir, arch, binName);
|
||||||
|
|
||||||
|
// buildPath points to appPath, which is inside resources dir which is the one we actually want
|
||||||
|
const resourcesPath = path.dirname(buildPath);
|
||||||
|
const dest = path.resolve(resourcesPath, path.basename(binPath));
|
||||||
|
debug(`copying '${binPath}' to '${dest}'`);
|
||||||
|
fs.copyFileSync(binPath, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SidecarPlugin extends PluginBase<void> {
|
||||||
|
name = 'sidecar';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.getHooks = this.getHooks.bind(this);
|
||||||
|
debug('isStartScript:', isStartScrpt());
|
||||||
|
}
|
||||||
|
|
||||||
|
getHooks(): ForgeHookMap {
|
||||||
|
const DEFINE_NAME = 'ETCHER_UTIL_BIN_PATH';
|
||||||
|
const BASE_DIR = path.join('out', 'sidecar');
|
||||||
|
const SRC_DIR = path.join(BASE_DIR, 'src');
|
||||||
|
const BIN_DIR = path.join(BASE_DIR, 'bin');
|
||||||
|
const BIN_NAME = `etcher-util${process.platform === 'win32' ? '.exe' : ''}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
resolveForgeConfig: async (currentConfig) => {
|
||||||
|
debug('resolveForgeConfig');
|
||||||
|
return addWebpackDefine(currentConfig, DEFINE_NAME, BIN_DIR, BIN_NAME);
|
||||||
|
},
|
||||||
|
generateAssets: async (_config, platform, arch) => {
|
||||||
|
debug('generateAssets', { platform, arch });
|
||||||
|
build(SRC_DIR, arch, BIN_DIR, BIN_NAME);
|
||||||
|
},
|
||||||
|
packageAfterCopy: async (
|
||||||
|
_config,
|
||||||
|
buildPath,
|
||||||
|
electronVersion,
|
||||||
|
platform,
|
||||||
|
arch,
|
||||||
|
) => {
|
||||||
|
debug('packageAfterCopy', {
|
||||||
|
buildPath,
|
||||||
|
electronVersion,
|
||||||
|
platform,
|
||||||
|
arch,
|
||||||
|
});
|
||||||
|
copyArtifact(buildPath, arch, BIN_DIR, BIN_NAME);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,30 +16,29 @@
|
|||||||
|
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import * as remote from '@electron/remote';
|
import * as remote from '@electron/remote';
|
||||||
import * as sdk from 'etcher-sdk';
|
import { debounce, capitalize, Dictionary, values } from 'lodash';
|
||||||
import * as _ from 'lodash';
|
|
||||||
import outdent from 'outdent';
|
import outdent from 'outdent';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
|
||||||
import * as packageJSON from '../../../package.json';
|
import * as packageJSON from '../../../package.json';
|
||||||
import { DrivelistDrive, isSourceDrive } from '../../shared/drive-constraints';
|
import { DrivelistDrive } from '../../shared/drive-constraints';
|
||||||
import * as EXIT_CODES from '../../shared/exit-codes';
|
import * as EXIT_CODES from '../../shared/exit-codes';
|
||||||
import * as messages from '../../shared/messages';
|
import * as messages from '../../shared/messages';
|
||||||
import * as availableDrives from './models/available-drives';
|
import * as availableDrives from './models/available-drives';
|
||||||
import * as flashState from './models/flash-state';
|
import * as flashState from './models/flash-state';
|
||||||
import { deselectImage, getImage } from './models/selection-state';
|
|
||||||
import * as settings from './models/settings';
|
import * as settings from './models/settings';
|
||||||
import { Actions, observe, store } from './models/store';
|
import { Actions, observe, store } from './models/store';
|
||||||
import * as analytics from './modules/analytics';
|
import * as analytics from './modules/analytics';
|
||||||
import { scanner as driveScanner } from './modules/drive-scanner';
|
import { startApiAndSpawnChild } from './modules/api';
|
||||||
import * as exceptionReporter from './modules/exception-reporter';
|
import * as exceptionReporter from './modules/exception-reporter';
|
||||||
import * as osDialog from './os/dialog';
|
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';
|
import * as i18next from 'i18next';
|
||||||
|
import { SourceMetadata } from '../../shared/typings/source-selector';
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'unhandledrejection',
|
'unhandledrejection',
|
||||||
@@ -89,7 +88,7 @@ analytics.logEvent('Application start', {
|
|||||||
version: currentVersion,
|
version: currentVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
const debouncedLog = _.debounce(console.log, 1000, { maxWait: 1000 });
|
const debouncedLog = debounce(console.log, 1000, { maxWait: 1000 });
|
||||||
|
|
||||||
function pluralize(word: string, quantity: number) {
|
function pluralize(word: string, quantity: number) {
|
||||||
return `${quantity} ${word}${quantity === 1 ? '' : 's'}`;
|
return `${quantity} ${word}${quantity === 1 ? '' : 's'}`;
|
||||||
@@ -115,7 +114,7 @@ observe(() => {
|
|||||||
// might cause some non-sense flashing state logs including
|
// might cause some non-sense flashing state logs including
|
||||||
// `undefined` values.
|
// `undefined` values.
|
||||||
debouncedLog(outdent({ newline: ' ' })`
|
debouncedLog(outdent({ newline: ' ' })`
|
||||||
${_.capitalize(currentFlashState.type)}
|
${capitalize(currentFlashState.type)}
|
||||||
${active},
|
${active},
|
||||||
${currentFlashState.percentage}%
|
${currentFlashState.percentage}%
|
||||||
at
|
at
|
||||||
@@ -128,173 +127,40 @@ observe(() => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
function setDrives(drives: Dictionary<DrivelistDrive>) {
|
||||||
* @summary The radix used by USB ID numbers
|
// prevent setting drives while flashing otherwise we might lose some while we unmount them
|
||||||
*/
|
if (!flashState.isFlashing()) {
|
||||||
const USB_ID_RADIX = 16;
|
availableDrives.setDrives(values(drives));
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary The expected length of a USB ID number
|
|
||||||
*/
|
|
||||||
const USB_ID_LENGTH = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Convert a USB id (e.g. product/vendor) to a string
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* console.log(usbIdToString(2652))
|
|
||||||
* > '0x0a5c'
|
|
||||||
*/
|
|
||||||
function usbIdToString(id: number): string {
|
|
||||||
return `0x${_.padStart(id.toString(USB_ID_RADIX), USB_ID_LENGTH, '0')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Product ID of BCM2708
|
|
||||||
*/
|
|
||||||
const USB_PRODUCT_ID_BCM2708_BOOT = 0x2763;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Product ID of BCM2710
|
|
||||||
*/
|
|
||||||
const USB_PRODUCT_ID_BCM2710_BOOT = 0x2764;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Compute module descriptions
|
|
||||||
*/
|
|
||||||
const COMPUTE_MODULE_DESCRIPTIONS: _.Dictionary<string> = {
|
|
||||||
[USB_PRODUCT_ID_BCM2708_BOOT]: 'Compute Module 1',
|
|
||||||
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3',
|
|
||||||
};
|
|
||||||
|
|
||||||
async function driveIsAllowed(drive: {
|
|
||||||
devicePath: string;
|
|
||||||
device: string;
|
|
||||||
raw: string;
|
|
||||||
}) {
|
|
||||||
const driveBlacklist = (await settings.get('driveBlacklist')) || [];
|
|
||||||
return !(
|
|
||||||
driveBlacklist.includes(drive.devicePath) ||
|
|
||||||
driveBlacklist.includes(drive.device) ||
|
|
||||||
driveBlacklist.includes(drive.raw)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Drive =
|
|
||||||
| sdk.sourceDestination.BlockDevice
|
|
||||||
| sdk.sourceDestination.UsbbootDrive
|
|
||||||
| sdk.sourceDestination.DriverlessDevice;
|
|
||||||
|
|
||||||
function prepareDrive(drive: Drive) {
|
|
||||||
if (drive instanceof sdk.sourceDestination.BlockDevice) {
|
|
||||||
// @ts-ignore (BlockDevice.drive is private)
|
|
||||||
return drive.drive;
|
|
||||||
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
|
|
||||||
// This is a workaround etcher expecting a device string and a size
|
|
||||||
// @ts-ignore
|
|
||||||
drive.device = drive.usbDevice.portId;
|
|
||||||
drive.size = null;
|
|
||||||
// @ts-ignore
|
|
||||||
drive.progress = 0;
|
|
||||||
drive.disabled = true;
|
|
||||||
drive.on('progress', (progress) => {
|
|
||||||
updateDriveProgress(drive, progress);
|
|
||||||
});
|
|
||||||
return drive;
|
|
||||||
} else if (drive instanceof sdk.sourceDestination.DriverlessDevice) {
|
|
||||||
const description =
|
|
||||||
COMPUTE_MODULE_DESCRIPTIONS[
|
|
||||||
drive.deviceDescriptor.idProduct.toString()
|
|
||||||
] || 'Compute Module';
|
|
||||||
return {
|
|
||||||
device: `${usbIdToString(
|
|
||||||
drive.deviceDescriptor.idVendor,
|
|
||||||
)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`,
|
|
||||||
displayName: 'Missing drivers',
|
|
||||||
description,
|
|
||||||
mountpoints: [],
|
|
||||||
isReadOnly: false,
|
|
||||||
isSystem: false,
|
|
||||||
disabled: true,
|
|
||||||
icon: 'warning',
|
|
||||||
size: null,
|
|
||||||
link: 'https://www.raspberrypi.com/documentation/computers/compute-module.html#flashing-the-compute-module-emmc',
|
|
||||||
linkCTA: 'Install',
|
|
||||||
linkTitle: 'Install missing drivers',
|
|
||||||
linkMessage: outdent`
|
|
||||||
Would you like to download the necessary drivers from the Raspberry Pi Foundation?
|
|
||||||
This will open your browser.
|
|
||||||
|
|
||||||
|
|
||||||
Once opened, download and run the installer from the "Windows Installer" section to install the drivers
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDrives(drives: _.Dictionary<DrivelistDrive>) {
|
// Spawning the child process without privileges to get the drives list
|
||||||
availableDrives.setDrives(_.values(drives));
|
// TODO: clean up this mess of exports
|
||||||
}
|
export let requestMetadata: any;
|
||||||
|
|
||||||
function getDrives() {
|
// start the api and spawn the child process
|
||||||
return _.keyBy(availableDrives.getDrives(), 'device');
|
startApiAndSpawnChild({
|
||||||
}
|
withPrivileges: false,
|
||||||
|
}).then(({ emit, registerHandler }) => {
|
||||||
|
// start scanning
|
||||||
|
emit('scan');
|
||||||
|
|
||||||
async function addDrive(drive: Drive) {
|
// make the sourceMetada awaitable to be used on source selection
|
||||||
const preparedDrive = prepareDrive(drive);
|
requestMetadata = async (params: any): Promise<SourceMetadata> => {
|
||||||
if (!(await driveIsAllowed(preparedDrive))) {
|
emit('sourceMetadata', JSON.stringify(params));
|
||||||
return;
|
|
||||||
}
|
|
||||||
const drives = getDrives();
|
|
||||||
drives[preparedDrive.device] = preparedDrive;
|
|
||||||
setDrives(drives);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeDrive(drive: Drive) {
|
return new Promise((resolve) =>
|
||||||
if (
|
registerHandler('sourceMetadata', (data: any) => {
|
||||||
drive instanceof sdk.sourceDestination.BlockDevice &&
|
resolve(JSON.parse(data));
|
||||||
// @ts-ignore BlockDevice.drive is private
|
}),
|
||||||
isSourceDrive(drive.drive, getImage())
|
);
|
||||||
) {
|
};
|
||||||
// Deselect the image if it was on the drive that was removed.
|
|
||||||
// This will also deselect the image if the drive mountpoints change.
|
|
||||||
deselectImage();
|
|
||||||
}
|
|
||||||
const preparedDrive = prepareDrive(drive);
|
|
||||||
const drives = getDrives();
|
|
||||||
delete drives[preparedDrive.device];
|
|
||||||
setDrives(drives);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDriveProgress(
|
registerHandler('drives', (data: any) => {
|
||||||
drive: sdk.sourceDestination.UsbbootDrive,
|
setDrives(JSON.parse(data));
|
||||||
progress: number,
|
});
|
||||||
) {
|
|
||||||
const drives = getDrives();
|
|
||||||
// @ts-ignore
|
|
||||||
const driveInMap = drives[drive.device];
|
|
||||||
if (driveInMap) {
|
|
||||||
// @ts-ignore
|
|
||||||
drives[drive.device] = { ...driveInMap, progress };
|
|
||||||
setDrives(drives);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
driveScanner.on('attach', addDrive);
|
|
||||||
driveScanner.on('detach', removeDrive);
|
|
||||||
|
|
||||||
driveScanner.on('error', (error) => {
|
|
||||||
// Stop the drive scanning loop in case of errors,
|
|
||||||
// otherwise we risk presenting the same error over
|
|
||||||
// and over again to the user, while also heavily
|
|
||||||
// spamming our error reporting service.
|
|
||||||
driveScanner.stop();
|
|
||||||
|
|
||||||
return exceptionReporter.report(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
driveScanner.start();
|
|
||||||
|
|
||||||
let popupExists = false;
|
let popupExists = false;
|
||||||
|
|
||||||
analytics.initAnalytics();
|
analytics.initAnalytics();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
|
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
|
||||||
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
|
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
|
||||||
import * as sourceDestination from 'etcher-sdk/build/source-destination/';
|
import * as sourceDestination from 'etcher-sdk/build/source-destination/';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
} from '../../styled-components';
|
} from '../../styled-components';
|
||||||
|
|
||||||
import { SourceMetadata } from '../source-selector/source-selector';
|
import { SourceMetadata } from '../../../../shared/typings/source-selector';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
import * as i18next from 'i18next';
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
@@ -310,9 +310,17 @@ export class DriveSelector extends React.Component<
|
|||||||
case compatibility.system():
|
case compatibility.system():
|
||||||
return warning.systemDrive();
|
return warning.systemDrive();
|
||||||
case compatibility.tooSmall():
|
case compatibility.tooSmall():
|
||||||
const size =
|
return warning.tooSmall(
|
||||||
this.state.image?.recommendedDriveSize || this.state.image?.size || 0;
|
{
|
||||||
return warning.tooSmall({ size }, drive);
|
size:
|
||||||
|
this.state.image?.recommendedDriveSize ||
|
||||||
|
this.state.image?.size ||
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
drive,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,11 +436,10 @@ export class DriveSelector extends React.Component<
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DrivesTable
|
<DrivesTable
|
||||||
refFn={(t) => {
|
refFn={() => {
|
||||||
if (t !== null) {
|
// noop
|
||||||
t.setRowSelection(selectedList);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
checkedItems={selectedList}
|
||||||
checkedRowsNumber={selectedList.length}
|
checkedRowsNumber={selectedList.length}
|
||||||
multipleSelection={this.props.multipleSelection}
|
multipleSelection={this.props.multipleSelection}
|
||||||
columns={this.tableColumns}
|
columns={this.tableColumns}
|
||||||
@@ -442,7 +449,10 @@ export class DriveSelector extends React.Component<
|
|||||||
isDrivelistDrive(row) && row.isSystem ? ['system'] : []
|
isDrivelistDrive(row) && row.isSystem ? ['system'] : []
|
||||||
}
|
}
|
||||||
rowKey="displayName"
|
rowKey="displayName"
|
||||||
onCheck={(rows: Drive[]) => {
|
onCheck={(rows) => {
|
||||||
|
if (rows == null) {
|
||||||
|
rows = [];
|
||||||
|
}
|
||||||
let newSelection = rows.filter(isDrivelistDrive);
|
let newSelection = rows.filter(isDrivelistDrive);
|
||||||
if (this.props.multipleSelection) {
|
if (this.props.multipleSelection) {
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
|
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Badge, Flex, Txt, ModalProps } from 'rendition';
|
import { Badge, Flex, Txt, ModalProps } from 'rendition';
|
||||||
import { Modal, ScrollableFlex } from '../../styled-components';
|
import { Modal, ScrollableFlex } from '../../styled-components';
|
||||||
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
import { middleEllipsis } from '../../utils/middle-ellipsis';
|
||||||
|
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
import { DriveWithWarnings } from '../../pages/main/Flash';
|
import { DriveWithWarnings } from '../../pages/main/Flash';
|
||||||
import * as i18next from 'i18next';
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import CircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle.svg';
|
import CircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle.svg';
|
||||||
import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/check-circle.svg';
|
import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-check.svg';
|
||||||
import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/times-circle.svg';
|
import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-xmark.svg';
|
||||||
import outdent from 'outdent';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition';
|
import { Flex, FlexProps, Link, TableColumn, Txt } from 'rendition';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -139,8 +138,9 @@ export function FlashResults({
|
|||||||
};
|
};
|
||||||
} & FlexProps) {
|
} & FlexProps) {
|
||||||
const [showErrorsInfo, setShowErrorsInfo] = React.useState(false);
|
const [showErrorsInfo, setShowErrorsInfo] = React.useState(false);
|
||||||
const allFailed = !skip && results.devices.successful === 0;
|
|
||||||
const someFailed = results.devices.failed !== 0 || errors.length !== 0;
|
const allFailed = !skip && results?.devices?.successful === 0;
|
||||||
|
const someFailed = results?.devices?.failed !== 0 || errors?.length !== 0;
|
||||||
const effectiveSpeed = bytesToMegabytes(getEffectiveSpeed(results)).toFixed(
|
const effectiveSpeed = bytesToMegabytes(getEffectiveSpeed(results)).toFixed(
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
);
|
);
|
||||||
this.entryHref = url.href;
|
this.entryHref = url.href;
|
||||||
// Events steal 'this'
|
// Events steal 'this'
|
||||||
|
this.handleDomReady = _.bind(this.handleDomReady, this);
|
||||||
this.didFailLoad = _.bind(this.didFailLoad, this);
|
this.didFailLoad = _.bind(this.didFailLoad, this);
|
||||||
this.didGetResponseDetails = _.bind(this.didGetResponseDetails, this);
|
this.didGetResponseDetails = _.bind(this.didGetResponseDetails, this);
|
||||||
// Make a persistent electron session for the webview
|
// Make a persistent electron session for the webview
|
||||||
@@ -121,6 +122,8 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
ref={this.webviewRef}
|
ref={this.webviewRef}
|
||||||
partition={ELECTRON_SESSION}
|
partition={ELECTRON_SESSION}
|
||||||
style={style}
|
style={style}
|
||||||
|
// @ts-ignore
|
||||||
|
allowpopups="true"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -134,8 +137,8 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
this.didFailLoad,
|
this.didFailLoad,
|
||||||
);
|
);
|
||||||
this.webviewRef.current.addEventListener(
|
this.webviewRef.current.addEventListener(
|
||||||
'new-window',
|
'dom-ready',
|
||||||
SafeWebview.newWindow,
|
this.handleDomReady,
|
||||||
);
|
);
|
||||||
this.webviewRef.current.addEventListener(
|
this.webviewRef.current.addEventListener(
|
||||||
'console-message',
|
'console-message',
|
||||||
@@ -157,8 +160,8 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
this.didFailLoad,
|
this.didFailLoad,
|
||||||
);
|
);
|
||||||
this.webviewRef.current.removeEventListener(
|
this.webviewRef.current.removeEventListener(
|
||||||
'new-window',
|
'dom-ready',
|
||||||
SafeWebview.newWindow,
|
this.handleDomReady,
|
||||||
);
|
);
|
||||||
this.webviewRef.current.removeEventListener(
|
this.webviewRef.current.removeEventListener(
|
||||||
'console-message',
|
'console-message',
|
||||||
@@ -168,6 +171,15 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
this.session.webRequest.onCompleted(null);
|
this.session.webRequest.onCompleted(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDomReady() {
|
||||||
|
const webview = this.webviewRef.current;
|
||||||
|
if (webview == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = webview.getWebContentsId();
|
||||||
|
electron.ipcRenderer.send('webview-dom-ready', id);
|
||||||
|
}
|
||||||
|
|
||||||
// Set the element state to hidden
|
// Set the element state to hidden
|
||||||
public didFailLoad() {
|
public didFailLoad() {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -196,17 +208,4 @@ export class SafeWebview extends React.PureComponent<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open link in browser if it's opened as a 'foreground-tab'
|
|
||||||
public static async newWindow(event: electron.NewWindowEvent) {
|
|
||||||
const url = new window.URL(event.url);
|
|
||||||
if (
|
|
||||||
(url.protocol === 'http:' || url.protocol === 'https:') &&
|
|
||||||
event.disposition === 'foreground-tab' &&
|
|
||||||
// Don't open links if they're disabled by the env var
|
|
||||||
!(await settings.get('disableExternalLinks'))
|
|
||||||
) {
|
|
||||||
electron.shell.openExternal(url.href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import GithubSvg from '@fortawesome/fontawesome-free/svgs/brands/github.svg';
|
import GithubSvg from '@fortawesome/fontawesome-free/svgs/brands/github.svg';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Box, Checkbox, Flex, TextWithCopy, Txt } from 'rendition';
|
import { Box, Checkbox, Flex, Txt } from 'rendition';
|
||||||
|
|
||||||
import { version, packageType } from '../../../../../package.json';
|
import { version, packageType } from '../../../../../package.json';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
@@ -61,7 +61,9 @@ const EPInfo = etcherProInfo();
|
|||||||
const InfoBox = (props: any) => (
|
const InfoBox = (props: any) => (
|
||||||
<Box fontSize={14}>
|
<Box fontSize={14}>
|
||||||
<Txt>{props.label}</Txt>
|
<Txt>{props.label}</Txt>
|
||||||
<TextWithCopy code text={props.value} copy={props.value} />
|
<Txt code copy={props.value}>
|
||||||
|
{props.value}{' '}
|
||||||
|
</Txt>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -17,16 +17,16 @@
|
|||||||
import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg';
|
import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg';
|
||||||
import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg';
|
import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg';
|
||||||
import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
|
import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg';
|
||||||
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
|
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
|
||||||
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
|
import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg';
|
||||||
import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
|
import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg';
|
||||||
import { sourceDestination } from 'etcher-sdk';
|
|
||||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||||
import * as _ from 'lodash';
|
import { uniqBy, isNil } from 'lodash';
|
||||||
import { GPTPartition, MBRPartition } from 'partitioninfo';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { requestMetadata } from '../../app';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
ButtonProps,
|
ButtonProps,
|
||||||
@@ -47,7 +47,7 @@ import { observe } from '../../models/store';
|
|||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
import * as exceptionReporter from '../../modules/exception-reporter';
|
import * as exceptionReporter from '../../modules/exception-reporter';
|
||||||
import * as osDialog from '../../os/dialog';
|
import * as osDialog from '../../os/dialog';
|
||||||
import { replaceWindowsNetworkDriveLetter } from '../../os/windows-network-drives';
|
|
||||||
import {
|
import {
|
||||||
ChangeButton,
|
ChangeButton,
|
||||||
DetailsText,
|
DetailsText,
|
||||||
@@ -64,8 +64,12 @@ import ImageSvg from '../../../assets/image.svg';
|
|||||||
import SrcSvg from '../../../assets/src.svg';
|
import SrcSvg from '../../../assets/src.svg';
|
||||||
import { DriveSelector } from '../drive-selector/drive-selector';
|
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 { isJson } from '../../../../shared/utils';
|
import { isJson } from '../../../../shared/utils';
|
||||||
|
import {
|
||||||
|
SourceMetadata,
|
||||||
|
Authentication,
|
||||||
|
Source,
|
||||||
|
} from '../../../../shared/typings/source-selector';
|
||||||
import * as i18next from 'i18next';
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
const recentUrlImagesKey = 'recentUrlImages';
|
const recentUrlImagesKey = 'recentUrlImages';
|
||||||
@@ -83,7 +87,7 @@ function normalizeRecentUrlImages(urls: any[]): URL[] {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((url) => url !== undefined);
|
.filter((url) => url !== undefined);
|
||||||
urls = _.uniqBy(urls, (url) => url.href);
|
urls = uniqBy(urls, (url) => url.href);
|
||||||
return urls.slice(urls.length - 5);
|
return urls.slice(urls.length - 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,24 +305,6 @@ const FlowSelector = styled(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type Source =
|
|
||||||
| typeof sourceDestination.File
|
|
||||||
| typeof sourceDestination.BlockDevice
|
|
||||||
| typeof sourceDestination.Http;
|
|
||||||
|
|
||||||
export interface SourceMetadata extends sourceDestination.Metadata {
|
|
||||||
hasMBR?: boolean;
|
|
||||||
partitions?: MBRPartition[] | GPTPartition[];
|
|
||||||
path: string;
|
|
||||||
displayName: string;
|
|
||||||
description: string;
|
|
||||||
SourceType: Source;
|
|
||||||
drive?: DrivelistDrive;
|
|
||||||
extension?: string;
|
|
||||||
archiveExtension?: string;
|
|
||||||
auth?: Authentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SourceSelectorProps {
|
interface SourceSelectorProps {
|
||||||
flashing: boolean;
|
flashing: boolean;
|
||||||
}
|
}
|
||||||
@@ -336,11 +322,6 @@ interface SourceSelectorState {
|
|||||||
imageLoading: boolean;
|
imageLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Authentication {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SourceSelector extends React.Component<
|
export class SourceSelector extends React.Component<
|
||||||
SourceSelectorProps,
|
SourceSelectorProps,
|
||||||
SourceSelectorState
|
SourceSelectorState
|
||||||
@@ -381,43 +362,11 @@ export class SourceSelector extends React.Component<
|
|||||||
this.setState({ imageLoading: true });
|
this.setState({ imageLoading: true });
|
||||||
await this.selectSource(
|
await this.selectSource(
|
||||||
imagePath,
|
imagePath,
|
||||||
isURL(this.normalizeImagePath(imagePath))
|
isURL(this.normalizeImagePath(imagePath)) ? 'Http' : 'File',
|
||||||
? sourceDestination.Http
|
|
||||||
: sourceDestination.File,
|
|
||||||
).promise;
|
).promise;
|
||||||
this.setState({ imageLoading: false });
|
this.setState({ imageLoading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createSource(
|
|
||||||
selected: string,
|
|
||||||
SourceType: Source,
|
|
||||||
auth?: Authentication,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
selected = await replaceWindowsNetworkDriveLetter(selected);
|
|
||||||
} catch (error: any) {
|
|
||||||
analytics.logException(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isJson(decodeURIComponent(selected))) {
|
|
||||||
const config: AxiosRequestConfig = JSON.parse(
|
|
||||||
decodeURIComponent(selected),
|
|
||||||
);
|
|
||||||
return new sourceDestination.Http({
|
|
||||||
url: config.url!,
|
|
||||||
axiosInstance: axios.create(_.omit(config, ['url'])),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SourceType === sourceDestination.File) {
|
|
||||||
return new sourceDestination.File({
|
|
||||||
path: selected,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new sourceDestination.Http({ url: selected, auth });
|
|
||||||
}
|
|
||||||
|
|
||||||
public normalizeImagePath(imgPath: string) {
|
public normalizeImagePath(imgPath: string) {
|
||||||
const decodedPath = decodeURIComponent(imgPath);
|
const decodedPath = decodeURIComponent(imgPath);
|
||||||
if (isJson(decodedPath)) {
|
if (isJson(decodedPath)) {
|
||||||
@@ -439,18 +388,16 @@ export class SourceSelector extends React.Component<
|
|||||||
SourceType: Source,
|
SourceType: Source,
|
||||||
auth?: Authentication,
|
auth?: Authentication,
|
||||||
): { promise: Promise<void>; cancel: () => void } {
|
): { promise: Promise<void>; cancel: () => void } {
|
||||||
let cancelled = false;
|
|
||||||
return {
|
return {
|
||||||
cancel: () => {
|
cancel: () => {
|
||||||
cancelled = true;
|
// noop
|
||||||
},
|
},
|
||||||
promise: (async () => {
|
promise: (async () => {
|
||||||
const sourcePath = isString(selected) ? selected : selected.device;
|
const sourcePath = isString(selected) ? selected : selected.device;
|
||||||
let source;
|
|
||||||
let metadata: SourceMetadata | undefined;
|
let metadata: SourceMetadata | undefined;
|
||||||
if (isString(selected)) {
|
if (isString(selected)) {
|
||||||
if (
|
if (
|
||||||
SourceType === sourceDestination.Http &&
|
SourceType === 'Http' &&
|
||||||
!isURL(this.normalizeImagePath(selected))
|
!isURL(this.normalizeImagePath(selected))
|
||||||
) {
|
) {
|
||||||
this.handleError(
|
this.handleError(
|
||||||
@@ -470,24 +417,14 @@ export class SourceSelector extends React.Component<
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
source = await this.createSource(selected, SourceType, auth);
|
|
||||||
|
|
||||||
if (cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const innerSource = await source.getInnerSource();
|
// this will send an event down the ipcMain asking for metadata
|
||||||
if (cancelled) {
|
// we'll get the response through an event
|
||||||
return;
|
|
||||||
}
|
|
||||||
metadata = await this.getMetadata(innerSource, selected);
|
|
||||||
if (cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
metadata.SourceType = SourceType;
|
|
||||||
|
|
||||||
if (!metadata.hasMBR && this.state.warning === null) {
|
metadata = await requestMetadata({ selected, SourceType, auth });
|
||||||
|
|
||||||
|
if (!metadata?.hasMBR && this.state.warning === null) {
|
||||||
analytics.logEvent('Missing partition table', { metadata });
|
analytics.logEvent('Missing partition table', { metadata });
|
||||||
this.setState({
|
this.setState({
|
||||||
warning: {
|
warning: {
|
||||||
@@ -503,12 +440,6 @@ export class SourceSelector extends React.Component<
|
|||||||
messages.error.openSource(sourcePath, error.message),
|
messages.error.openSource(sourcePath, error.message),
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
await source.close();
|
|
||||||
} catch (error: any) {
|
|
||||||
// Noop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (selected.partitionTableType === null) {
|
if (selected.partitionTableType === null) {
|
||||||
@@ -525,13 +456,14 @@ export class SourceSelector extends React.Component<
|
|||||||
displayName: selected.displayName,
|
displayName: selected.displayName,
|
||||||
description: selected.displayName,
|
description: selected.displayName,
|
||||||
size: selected.size as SourceMetadata['size'],
|
size: selected.size as SourceMetadata['size'],
|
||||||
SourceType: sourceDestination.BlockDevice,
|
SourceType: 'BlockDevice',
|
||||||
drive: selected,
|
drive: selected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metadata !== undefined) {
|
if (metadata !== undefined) {
|
||||||
metadata.auth = auth;
|
metadata.auth = auth;
|
||||||
|
metadata.SourceType = SourceType;
|
||||||
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
|
||||||
@@ -565,25 +497,6 @@ export class SourceSelector extends React.Component<
|
|||||||
analytics.logEvent(title, { path: sourcePath });
|
analytics.logEvent(title, { path: sourcePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getMetadata(
|
|
||||||
source: sourceDestination.SourceDestination,
|
|
||||||
selected: string | DrivelistDrive,
|
|
||||||
) {
|
|
||||||
const metadata = (await source.getMetadata()) as SourceMetadata;
|
|
||||||
const partitionTable = await source.getPartitionTable();
|
|
||||||
if (partitionTable) {
|
|
||||||
metadata.hasMBR = true;
|
|
||||||
metadata.partitions = partitionTable.partitions;
|
|
||||||
} else {
|
|
||||||
metadata.hasMBR = false;
|
|
||||||
}
|
|
||||||
if (isString(selected)) {
|
|
||||||
metadata.extension = path.extname(selected).slice(1);
|
|
||||||
metadata.path = selected;
|
|
||||||
}
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async openImageSelector() {
|
private async openImageSelector() {
|
||||||
analytics.logEvent('Open image selector');
|
analytics.logEvent('Open image selector');
|
||||||
this.setState({ imageSelectorOpen: true });
|
this.setState({ imageSelectorOpen: true });
|
||||||
@@ -596,7 +509,7 @@ export class SourceSelector extends React.Component<
|
|||||||
analytics.logEvent('Image selector closed');
|
analytics.logEvent('Image selector closed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.selectSource(imagePath, sourceDestination.File).promise;
|
await this.selectSource(imagePath, 'File').promise;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
exceptionReporter.report(error);
|
exceptionReporter.report(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -605,9 +518,9 @@ export class SourceSelector extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async onDrop(event: React.DragEvent<HTMLDivElement>) {
|
private async onDrop(event: React.DragEvent<HTMLDivElement>) {
|
||||||
const [file] = event.dataTransfer.files;
|
const file = event.dataTransfer.files.item(0);
|
||||||
if (file) {
|
if (file != null) {
|
||||||
await this.selectSource(file.path, sourceDestination.File).promise;
|
await this.selectSource(file.path, 'File').promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,7 +580,7 @@ export class SourceSelector extends React.Component<
|
|||||||
imageLoading,
|
imageLoading,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const selectionImage = selectionState.getImage();
|
const selectionImage = selectionState.getImage();
|
||||||
let image: SourceMetadata | DrivelistDrive =
|
let image =
|
||||||
selectionImage !== undefined ? selectionImage : ({} as SourceMetadata);
|
selectionImage !== undefined ? selectionImage : ({} as SourceMetadata);
|
||||||
|
|
||||||
image = image.drive ?? image;
|
image = image.drive ?? image;
|
||||||
@@ -723,7 +636,7 @@ export class SourceSelector extends React.Component<
|
|||||||
{i18next.t('cancel')}
|
{i18next.t('cancel')}
|
||||||
</ChangeButton>
|
</ChangeButton>
|
||||||
)}
|
)}
|
||||||
{!_.isNil(imageSize) && !imageLoading && (
|
{!isNil(imageSize) && !imageLoading && (
|
||||||
<DetailsText>{prettyBytes(imageSize)}</DetailsText>
|
<DetailsText>{prettyBytes(imageSize)}</DetailsText>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -770,7 +683,7 @@ export class SourceSelector extends React.Component<
|
|||||||
style={{
|
style={{
|
||||||
boxShadow: '0 3px 7px rgba(0, 0, 0, 0.3)',
|
boxShadow: '0 3px 7px rgba(0, 0, 0, 0.3)',
|
||||||
}}
|
}}
|
||||||
titleElement={
|
title={
|
||||||
<span>
|
<span>
|
||||||
<ExclamationTriangleSvg fill="#fca321" height="1em" />{' '}
|
<ExclamationTriangleSvg fill="#fca321" height="1em" />{' '}
|
||||||
<span>{this.state.warning.title}</span>
|
<span>{this.state.warning.title}</span>
|
||||||
@@ -827,7 +740,7 @@ export class SourceSelector extends React.Component<
|
|||||||
let promise;
|
let promise;
|
||||||
({ promise, cancel: cancelURLSelection } = this.selectSource(
|
({ promise, cancel: cancelURLSelection } = this.selectSource(
|
||||||
imageURL,
|
imageURL,
|
||||||
sourceDestination.Http,
|
'Http',
|
||||||
auth,
|
auth,
|
||||||
));
|
));
|
||||||
await promise;
|
await promise;
|
||||||
@@ -850,10 +763,7 @@ export class SourceSelector extends React.Component<
|
|||||||
if (originalList.length) {
|
if (originalList.length) {
|
||||||
const originalSource = originalList[0];
|
const originalSource = originalList[0];
|
||||||
if (selectionImage?.drive?.device !== originalSource.device) {
|
if (selectionImage?.drive?.device !== originalSource.device) {
|
||||||
this.selectSource(
|
this.selectSource(originalSource, 'BlockDevice');
|
||||||
originalSource,
|
|
||||||
sourceDestination.BlockDevice,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectionState.deselectImage();
|
selectionState.deselectImage();
|
||||||
@@ -868,7 +778,7 @@ export class SourceSelector extends React.Component<
|
|||||||
) {
|
) {
|
||||||
return selectionState.deselectImage();
|
return selectionState.deselectImage();
|
||||||
}
|
}
|
||||||
this.selectSource(drive, sourceDestination.BlockDevice);
|
this.selectSource(drive, 'BlockDevice');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
|
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Flex, FlexProps, Txt } from 'rendition';
|
import { Flex, FlexProps, Txt } from 'rendition';
|
||||||
|
|
||||||
|
|||||||
@@ -15,36 +15,36 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "SourceSansPro";
|
font-family: 'SourceSansPro';
|
||||||
src: url("./fonts/SourceSansPro-Regular.ttf") format("truetype");
|
src: url('./fonts/SourceSansPro-Regular.ttf') format('truetype');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "SourceSansPro";
|
font-family: 'SourceSansPro';
|
||||||
src: url("./fonts/SourceSansPro-SemiBold.ttf") format("truetype");
|
src: url('./fonts/SourceSansPro-SemiBold.ttf') format('truetype');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
/* Prevent white flash when running application */
|
/* Prevent white flash when running application */
|
||||||
background-color: #4d5057;
|
background-color: #4d5057;
|
||||||
|
|
||||||
/* Prevent WebView bounce effect in OS X */
|
/* Prevent WebView bounce effect in OS X */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent text selection */
|
/* Prevent text selection */
|
||||||
body {
|
body {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent blue outline */
|
/* Prevent blue outline */
|
||||||
@@ -52,15 +52,15 @@ a:focus,
|
|||||||
input:focus,
|
input:focus,
|
||||||
button:focus,
|
button:focus,
|
||||||
[tabindex]:focus,
|
[tabindex]:focus,
|
||||||
input[type="checkbox"] + div {
|
input[type='checkbox'] + div {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.disabled {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rendition-tooltip-root > div {
|
#rendition-tooltip-root > div {
|
||||||
font-family: "SourceSansPro", sans-serif;
|
font-family: 'SourceSansPro', sans-serif;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ const translation = {
|
|||||||
drive: '磁碟',
|
drive: '磁碟',
|
||||||
missingPartitionTable:
|
missingPartitionTable:
|
||||||
'看起來這不是一個可啟動的{{type}}。\n\n這個{{type}}似乎不包含分割表,因此您的設備可能無法識別或無法正確啟動。',
|
'看起來這不是一個可啟動的{{type}}。\n\n這個{{type}}似乎不包含分割表,因此您的設備可能無法識別或無法正確啟動。',
|
||||||
largeDriveSize: '這是個很大容量的磁碟!請檢查並確認它不包含對您來說存放很重要的資料',
|
largeDriveSize:
|
||||||
|
'這是個很大容量的磁碟!請檢查並確認它不包含對您來說存放很重要的資料',
|
||||||
systemDrive: '選擇系統分割區很危險,因為這將會刪除你的系統',
|
systemDrive: '選擇系統分割區很危險,因為這將會刪除你的系統',
|
||||||
sourceDrive: '來源映像檔位於這個分割區中',
|
sourceDrive: '來源映像檔位於這個分割區中',
|
||||||
noSpace: '磁碟空間不足。請插入另一個較大的磁碟並重試。',
|
noSpace: '磁碟空間不足。請插入另一個較大的磁碟並重試。',
|
||||||
@@ -130,8 +131,7 @@ const translation = {
|
|||||||
autoUpdate: '自動更新',
|
autoUpdate: '自動更新',
|
||||||
settings: '軟體設定',
|
settings: '軟體設定',
|
||||||
systemInformation: '系統資訊',
|
systemInformation: '系統資訊',
|
||||||
trimExtPartitions:
|
trimExtPartitions: '修改原始映像檔上未分配的空間(在 ext 類型分割區中)',
|
||||||
'修改原始映像檔上未分配的空間(在 ext 類型分割區中)',
|
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
edit: '編輯',
|
edit: '編輯',
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>balenaEtcher</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="index.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main id="main"></main>
|
|
||||||
<script src="http://localhost:3030/gui.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -3,10 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>balenaEtcher</title>
|
<title>balenaEtcher</title>
|
||||||
<link rel="stylesheet" type="text/css" href="index.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main id="main"></main>
|
<main id="main"></main>
|
||||||
<script src="gui.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -47,13 +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
|
||||||
try {
|
electron.ipcRenderer.send('disable-screensaver');
|
||||||
electron.ipcRenderer.invoke('disable-screensaver');
|
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
"Can't disable-screensaver, we're probably not running on a balena-electron env",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: Actions.SET_FLASHING_FLAG,
|
type: Actions.SET_FLASHING_FLAG,
|
||||||
data: {},
|
data: {},
|
||||||
@@ -76,7 +70,8 @@ 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.invoke('enable-screensaver');
|
|
||||||
|
electron.ipcRenderer.send('enable-screensaver');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDevicePaths(devicePaths: string[]) {
|
export function setDevicePaths(devicePaths: string[]) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function blink(t: number) {
|
|||||||
return Math.floor(t) % 2;
|
return Math.floor(t) % 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function one(_t: number) {
|
function one() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ function storeReducer(
|
|||||||
constraints.isDriveValid(drive, image) &&
|
constraints.isDriveValid(drive, image) &&
|
||||||
!drive.isReadOnly &&
|
!drive.isReadOnly &&
|
||||||
constraints.isDriveSizeRecommended(drive, image) &&
|
constraints.isDriveSizeRecommended(drive, image) &&
|
||||||
// We don't want to auto-select large drives execpt is autoSelectAllDrives is true
|
// We don't want to auto-select large drives except if autoSelectAllDrives is true
|
||||||
(!constraints.isDriveSizeLarge(drive) || shouldAutoselectAll) &&
|
(!constraints.isDriveSizeLarge(drive) || shouldAutoselectAll) &&
|
||||||
// We don't want to auto-select system drives
|
// We don't want to auto-select system drives
|
||||||
!constraints.isSystemDrive(drive)
|
!constraints.isSystemDrive(drive)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const anonymizeSentryData = (
|
|||||||
return event;
|
return event;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractPathRegex = /(.*)(^|\s)(file\:\/\/)?(\w\:)?([\\\/].+)/;
|
const extractPathRegex = /(.*)(^|\s)(file:\/\/)?(\w:)?([\\/].+)/;
|
||||||
const etcherSegmentMarkers = ['app.asar', 'Resources'];
|
const etcherSegmentMarkers = ['app.asar', 'Resources'];
|
||||||
|
|
||||||
export const anonymizePath = (input: string) => {
|
export const anonymizePath = (input: string) => {
|
||||||
@@ -156,7 +156,7 @@ function flattenObject(obj: any) {
|
|||||||
const toReturn: AnalyticsPayload = {};
|
const toReturn: AnalyticsPayload = {};
|
||||||
|
|
||||||
for (const i in obj) {
|
for (const i in obj) {
|
||||||
if (!obj.hasOwnProperty(i)) {
|
if (!Object.prototype.hasOwnProperty.call(obj, i)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ function flattenObject(obj: any) {
|
|||||||
if (typeof obj[i] === 'object' && obj[i] !== null) {
|
if (typeof obj[i] === 'object' && obj[i] !== null) {
|
||||||
const flatObject = flattenObject(obj[i]);
|
const flatObject = flattenObject(obj[i]);
|
||||||
for (const x in flatObject) {
|
for (const x in flatObject) {
|
||||||
if (!flatObject.hasOwnProperty(x)) {
|
if (!Object.prototype.hasOwnProperty.call(flatObject, x)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
183
lib/gui/app/modules/api.ts
Normal file
183
lib/gui/app/modules/api.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/** This function will :
|
||||||
|
* - start the ipc server (api)
|
||||||
|
* - spawn the child process (privileged or not)
|
||||||
|
* - wait for the child process to connect to the api
|
||||||
|
* - return a promise that will resolve with the emit function for the api
|
||||||
|
*
|
||||||
|
* //TODO:
|
||||||
|
* - this should be refactored to reverse the control flow:
|
||||||
|
* - the child process should be the server
|
||||||
|
* - this should be the client
|
||||||
|
* - replace the current node-ipc api with a websocket api
|
||||||
|
* - centralise the api for both the writer and the scanner instead of having two instances running
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ipc from 'node-ipc';
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as packageJSON from '../../../../package.json';
|
||||||
|
import * as permissions from '../../../shared/permissions';
|
||||||
|
import * as errors from '../../../shared/errors';
|
||||||
|
|
||||||
|
const THREADS_PER_CPU = 16;
|
||||||
|
|
||||||
|
// NOTE: Ensure this isn't disabled, as it will cause
|
||||||
|
// the stdout maxBuffer size to be exceeded when flashing
|
||||||
|
ipc.config.silent = true;
|
||||||
|
|
||||||
|
async function writerArgv(): Promise<string[]> {
|
||||||
|
let entryPoint = await window.etcher.getEtcherUtilPath();
|
||||||
|
// AppImages run over FUSE, so the files inside the mount point
|
||||||
|
// can only be accessed by the user that mounted the AppImage.
|
||||||
|
// This means we can't re-spawn Etcher as root from the same
|
||||||
|
// mount-point, and as a workaround, we re-mount the original
|
||||||
|
// AppImage as root.
|
||||||
|
if (os.platform() === 'linux' && process.env.APPIMAGE && process.env.APPDIR) {
|
||||||
|
entryPoint = entryPoint.replace(process.env.APPDIR, '');
|
||||||
|
return [
|
||||||
|
process.env.APPIMAGE,
|
||||||
|
'-e',
|
||||||
|
`require(\`\${process.env.APPDIR}${entryPoint}\`)`,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [entryPoint];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writerEnv(
|
||||||
|
IPC_CLIENT_ID: string,
|
||||||
|
IPC_SERVER_ID: string,
|
||||||
|
IPC_SOCKET_ROOT: string,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
IPC_SERVER_ID,
|
||||||
|
IPC_CLIENT_ID,
|
||||||
|
IPC_SOCKET_ROOT,
|
||||||
|
UV_THREADPOOL_SIZE: (os.cpus().length * THREADS_PER_CPU).toString(),
|
||||||
|
// This environment variable prevents the AppImages
|
||||||
|
// desktop integration script from presenting the
|
||||||
|
// "installation" dialog
|
||||||
|
SKIP: '1',
|
||||||
|
...(process.platform === 'win32' ? {} : process.env),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function spawnChild({
|
||||||
|
withPrivileges,
|
||||||
|
IPC_CLIENT_ID,
|
||||||
|
IPC_SERVER_ID,
|
||||||
|
IPC_SOCKET_ROOT,
|
||||||
|
}: {
|
||||||
|
withPrivileges: boolean;
|
||||||
|
IPC_CLIENT_ID: string;
|
||||||
|
IPC_SERVER_ID: string;
|
||||||
|
IPC_SOCKET_ROOT: string;
|
||||||
|
}) {
|
||||||
|
const argv = await writerArgv();
|
||||||
|
const env = writerEnv(IPC_CLIENT_ID, IPC_SERVER_ID, IPC_SOCKET_ROOT);
|
||||||
|
if (withPrivileges) {
|
||||||
|
return await permissions.elevateCommand(argv, {
|
||||||
|
applicationName: packageJSON.displayName,
|
||||||
|
environment: env,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const process = await spawn(argv[0], argv.slice(1), {
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
return { cancelled: false, process };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function terminateServer(server: any) {
|
||||||
|
// Turns out we need to destroy all sockets for
|
||||||
|
// the server to actually close. Otherwise, it
|
||||||
|
// just stops receiving any further connections,
|
||||||
|
// but remains open if there are active ones.
|
||||||
|
// @ts-ignore (no Server.sockets in @types/node-ipc)
|
||||||
|
for (const socket of server.sockets) {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace the custom ipc events by one generic "message" for all communication with the backend
|
||||||
|
function startApiAndSpawnChild({
|
||||||
|
withPrivileges,
|
||||||
|
}: {
|
||||||
|
withPrivileges: boolean;
|
||||||
|
}): Promise<any> {
|
||||||
|
// There might be multiple Etcher instances running at
|
||||||
|
// the same time, also we might spawn multiple child and api so we must ensure each IPC
|
||||||
|
// server/client has a different name.
|
||||||
|
const IPC_SERVER_ID = `etcher-server-${process.pid}-${Date.now()}-${
|
||||||
|
withPrivileges ? 'privileged' : 'unprivileged'
|
||||||
|
}`;
|
||||||
|
const IPC_CLIENT_ID = `etcher-client-${process.pid}-${Date.now()}-${
|
||||||
|
withPrivileges ? 'privileged' : 'unprivileged'
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const IPC_SOCKET_ROOT = path.join(
|
||||||
|
process.env.XDG_RUNTIME_DIR || os.tmpdir(),
|
||||||
|
path.sep,
|
||||||
|
);
|
||||||
|
|
||||||
|
ipc.config.id = IPC_SERVER_ID;
|
||||||
|
ipc.config.socketRoot = IPC_SOCKET_ROOT;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ipc.serve();
|
||||||
|
|
||||||
|
// log is special message which brings back the logs from the child process and prints them to the console
|
||||||
|
ipc.server.on('log', (message: string) => {
|
||||||
|
console.log(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// api to register more handlers with callbacks
|
||||||
|
const registerHandler = (event: string, handler: any) => {
|
||||||
|
ipc.server.on(event, handler);
|
||||||
|
};
|
||||||
|
|
||||||
|
// once api is ready (means child process is connected) we pass the emit and terminate function to the caller
|
||||||
|
ipc.server.on('ready', (_: any, socket) => {
|
||||||
|
const emit = (channel: string, data: any) => {
|
||||||
|
ipc.server.emit(socket, channel, data);
|
||||||
|
};
|
||||||
|
resolve({
|
||||||
|
emit,
|
||||||
|
terminateServer: () => terminateServer(ipc.server),
|
||||||
|
registerHandler,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// on api error we terminate
|
||||||
|
ipc.server.on('error', (error: any) => {
|
||||||
|
terminateServer(ipc.server);
|
||||||
|
const errorObject = errors.fromJSON(error);
|
||||||
|
reject(errorObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
// when the api is started we spawn the child process
|
||||||
|
ipc.server.on('start', async () => {
|
||||||
|
try {
|
||||||
|
const results = await spawnChild({
|
||||||
|
withPrivileges,
|
||||||
|
IPC_CLIENT_ID,
|
||||||
|
IPC_SERVER_ID,
|
||||||
|
IPC_SOCKET_ROOT,
|
||||||
|
});
|
||||||
|
// this will happen if the child is spawned withPrivileges and privileges has been rejected
|
||||||
|
if (results.cancelled) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// start the server
|
||||||
|
ipc.server.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { startApiAndSpawnChild };
|
||||||
@@ -17,38 +17,14 @@
|
|||||||
import { Drive as DrivelistDrive } from 'drivelist';
|
import { Drive as DrivelistDrive } from 'drivelist';
|
||||||
import * as sdk from 'etcher-sdk';
|
import * as sdk from 'etcher-sdk';
|
||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import * as ipc from 'node-ipc';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import * as packageJSON from '../../../../package.json';
|
|
||||||
import * as errors from '../../../shared/errors';
|
import * as errors from '../../../shared/errors';
|
||||||
import * as permissions from '../../../shared/permissions';
|
import { SourceMetadata } from '../../../shared/typings/source-selector';
|
||||||
import { getAppPath } from '../../../shared/utils';
|
|
||||||
import { SourceMetadata } from '../components/source-selector/source-selector';
|
|
||||||
import * as flashState from '../models/flash-state';
|
import * as flashState from '../models/flash-state';
|
||||||
import * as selectionState from '../models/selection-state';
|
import * as selectionState from '../models/selection-state';
|
||||||
import * as settings from '../models/settings';
|
import * as settings from '../models/settings';
|
||||||
import * as analytics from '../modules/analytics';
|
import * as analytics from '../modules/analytics';
|
||||||
import * as windowProgress from '../os/window-progress';
|
import * as windowProgress from '../os/window-progress';
|
||||||
|
import { startApiAndSpawnChild } from './api';
|
||||||
const THREADS_PER_CPU = 16;
|
|
||||||
|
|
||||||
// There might be multiple Etcher instances running at
|
|
||||||
// the same time, therefore we must ensure each IPC
|
|
||||||
// server/client has a different name.
|
|
||||||
const IPC_SERVER_ID = `etcher-server-${process.pid}`;
|
|
||||||
const IPC_CLIENT_ID = `etcher-client-${process.pid}`;
|
|
||||||
|
|
||||||
ipc.config.id = IPC_SERVER_ID;
|
|
||||||
ipc.config.socketRoot = path.join(
|
|
||||||
process.env.XDG_RUNTIME_DIR || os.tmpdir(),
|
|
||||||
path.sep,
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: Ensure this isn't disabled, as it will cause
|
|
||||||
// the stdout maxBuffer size to be exceeded when flashing
|
|
||||||
ipc.config.silent = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Handle a flash error and log it to analytics
|
* @summary Handle a flash error and log it to analytics
|
||||||
@@ -80,51 +56,7 @@ function handleErrorLogging(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function terminateServer() {
|
let cancelEmitter: (type: string) => void | undefined;
|
||||||
// Turns out we need to destroy all sockets for
|
|
||||||
// the server to actually close. Otherwise, it
|
|
||||||
// just stops receiving any further connections,
|
|
||||||
// but remains open if there are active ones.
|
|
||||||
// @ts-ignore (no Server.sockets in @types/node-ipc)
|
|
||||||
for (const socket of ipc.server.sockets) {
|
|
||||||
socket.destroy();
|
|
||||||
}
|
|
||||||
ipc.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function writerArgv(): string[] {
|
|
||||||
let entryPoint = path.join(getAppPath(), 'generated', 'child-writer.js');
|
|
||||||
// AppImages run over FUSE, so the files inside the mount point
|
|
||||||
// can only be accessed by the user that mounted the AppImage.
|
|
||||||
// This means we can't re-spawn Etcher as root from the same
|
|
||||||
// mount-point, and as a workaround, we re-mount the original
|
|
||||||
// AppImage as root.
|
|
||||||
if (os.platform() === 'linux' && process.env.APPIMAGE && process.env.APPDIR) {
|
|
||||||
entryPoint = entryPoint.replace(process.env.APPDIR, '');
|
|
||||||
return [
|
|
||||||
process.env.APPIMAGE,
|
|
||||||
'-e',
|
|
||||||
`require(\`\${process.env.APPDIR}${entryPoint}\`)`,
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
return [process.argv[0], entryPoint];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function writerEnv() {
|
|
||||||
return {
|
|
||||||
IPC_SERVER_ID,
|
|
||||||
IPC_CLIENT_ID,
|
|
||||||
IPC_SOCKET_ROOT: ipc.config.socketRoot,
|
|
||||||
ELECTRON_RUN_AS_NODE: '1',
|
|
||||||
UV_THREADPOOL_SIZE: (os.cpus().length * THREADS_PER_CPU).toString(),
|
|
||||||
// This environment variable prevents the AppImages
|
|
||||||
// desktop integration script from presenting the
|
|
||||||
// "installation" dialog
|
|
||||||
SKIP: '1',
|
|
||||||
...(process.platform === 'win32' ? {} : process.env),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FlashResults {
|
interface FlashResults {
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
@@ -144,22 +76,19 @@ async function performWrite(
|
|||||||
drives: DrivelistDrive[],
|
drives: DrivelistDrive[],
|
||||||
onProgress: sdk.multiWrite.OnProgressFunction,
|
onProgress: sdk.multiWrite.OnProgressFunction,
|
||||||
): Promise<{ cancelled?: boolean }> {
|
): Promise<{ cancelled?: boolean }> {
|
||||||
let cancelled = false;
|
|
||||||
let skip = false;
|
|
||||||
ipc.serve();
|
|
||||||
const { autoBlockmapping, decompressFirst } = await settings.getAll();
|
const { autoBlockmapping, decompressFirst } = await settings.getAll();
|
||||||
|
|
||||||
|
console.log({ image, drives });
|
||||||
|
|
||||||
|
// Spawn the child process with privileges and wait for the connection to be made
|
||||||
|
const { emit, registerHandler, terminateServer } =
|
||||||
|
await startApiAndSpawnChild({
|
||||||
|
withPrivileges: true,
|
||||||
|
});
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
ipc.server.on('error', (error) => {
|
|
||||||
terminateServer();
|
|
||||||
const errorObject = errors.fromJSON(error);
|
|
||||||
reject(errorObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.server.on('log', (message) => {
|
|
||||||
console.log(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
const flashResults: FlashResults = {};
|
const flashResults: FlashResults = {};
|
||||||
|
|
||||||
const analyticsData = {
|
const analyticsData = {
|
||||||
image,
|
image,
|
||||||
drives,
|
drives,
|
||||||
@@ -168,75 +97,51 @@ async function performWrite(
|
|||||||
flashInstanceUuid: flashState.getFlashUuid(),
|
flashInstanceUuid: flashState.getFlashUuid(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ipc.server.on('fail', ({ device, error }) => {
|
const onFail = ({ device, error }: { device: any; error: any }) => {
|
||||||
|
console.log('fail event');
|
||||||
|
console.log(device);
|
||||||
|
console.log(error);
|
||||||
if (device.devicePath) {
|
if (device.devicePath) {
|
||||||
flashState.addFailedDeviceError({ device, error });
|
flashState.addFailedDeviceError({ device, error });
|
||||||
}
|
}
|
||||||
handleErrorLogging(error, analyticsData);
|
handleErrorLogging(error, analyticsData);
|
||||||
});
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
ipc.server.on('done', (event) => {
|
const onDone = (event: any) => {
|
||||||
|
console.log('done event');
|
||||||
event.results.errors = event.results.errors.map(
|
event.results.errors = event.results.errors.map(
|
||||||
(data: Dictionary<any> & { message: string }) => {
|
(data: Dictionary<any> & { message: string }) => {
|
||||||
return errors.fromJSON(data);
|
return errors.fromJSON(data);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
flashResults.results = event.results;
|
flashResults.results = event.results;
|
||||||
});
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
ipc.server.on('abort', () => {
|
const onAbort = () => {
|
||||||
terminateServer();
|
console.log('abort event');
|
||||||
cancelled = true;
|
flashResults.cancelled = true;
|
||||||
});
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
ipc.server.on('skip', () => {
|
const onSkip = () => {
|
||||||
terminateServer();
|
console.log('skip event');
|
||||||
skip = true;
|
flashResults.skip = true;
|
||||||
});
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
ipc.server.on('state', onProgress);
|
const finish = () => {
|
||||||
|
|
||||||
ipc.server.on('ready', (_data, socket) => {
|
|
||||||
ipc.server.emit(socket, 'write', {
|
|
||||||
image,
|
|
||||||
destinations: drives,
|
|
||||||
SourceType: image.SourceType.name,
|
|
||||||
autoBlockmapping,
|
|
||||||
decompressFirst,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const argv = writerArgv();
|
|
||||||
|
|
||||||
ipc.server.on('start', async () => {
|
|
||||||
console.log(`Elevating command: ${argv.join(' ')}`);
|
|
||||||
const env = writerEnv();
|
|
||||||
try {
|
|
||||||
const results = await permissions.elevateCommand(argv, {
|
|
||||||
applicationName: packageJSON.displayName,
|
|
||||||
environment: env,
|
|
||||||
});
|
|
||||||
flashResults.cancelled = cancelled || results.cancelled;
|
|
||||||
flashResults.skip = skip;
|
|
||||||
} catch (error: any) {
|
|
||||||
// This happens when the child is killed using SIGKILL
|
|
||||||
const SIGKILL_EXIT_CODE = 137;
|
|
||||||
if (error.code === SIGKILL_EXIT_CODE) {
|
|
||||||
error.code = 'ECHILDDIED';
|
|
||||||
}
|
|
||||||
reject(error);
|
|
||||||
} finally {
|
|
||||||
console.log('Terminating IPC server');
|
|
||||||
terminateServer();
|
|
||||||
}
|
|
||||||
console.log('Flash results', flashResults);
|
console.log('Flash results', flashResults);
|
||||||
|
|
||||||
// The flash wasn't cancelled and we didn't get a 'done' event
|
// The flash wasn't cancelled and we didn't get a 'done' event
|
||||||
|
// Catch unexpected situation
|
||||||
if (
|
if (
|
||||||
!flashResults.cancelled &&
|
!flashResults.cancelled &&
|
||||||
!flashResults.skip &&
|
!flashResults.skip &&
|
||||||
flashResults.results === undefined
|
flashResults.results === undefined
|
||||||
) {
|
) {
|
||||||
|
console.log(flashResults);
|
||||||
reject(
|
reject(
|
||||||
errors.createUserError({
|
errors.createUserError({
|
||||||
title: 'The writer process ended unexpectedly',
|
title: 'The writer process ended unexpectedly',
|
||||||
@@ -244,15 +149,34 @@ async function performWrite(
|
|||||||
'Please try again, and contact the Etcher team if the problem persists',
|
'Please try again, and contact the Etcher team if the problem persists',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
resolve(flashResults);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the update lock timer to prevent longer
|
console.log('Terminating IPC server');
|
||||||
// flashing timing it out, and releasing the lock
|
terminateServer();
|
||||||
ipc.server.start();
|
resolve(flashResults);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerHandler('state', onProgress);
|
||||||
|
registerHandler('fail', onFail);
|
||||||
|
registerHandler('done', onDone);
|
||||||
|
registerHandler('abort', onAbort);
|
||||||
|
registerHandler('skip', onSkip);
|
||||||
|
|
||||||
|
cancelEmitter = (cancelStatus: string) => emit(cancelStatus);
|
||||||
|
|
||||||
|
// Now that we know we're connected we can instruct the child process to start the write
|
||||||
|
const parameters = {
|
||||||
|
image,
|
||||||
|
destinations: drives,
|
||||||
|
SourceType: image.SourceType,
|
||||||
|
autoBlockmapping,
|
||||||
|
decompressFirst,
|
||||||
|
};
|
||||||
|
console.log('params', parameters);
|
||||||
|
emit('write', parameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The process continue in the event handler
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -269,6 +193,7 @@ export async function flash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await flashState.setFlashingFlag();
|
await flashState.setFlashingFlag();
|
||||||
|
|
||||||
flashState.setDevicePaths(
|
flashState.setDevicePaths(
|
||||||
drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
|
drives.map((d) => d.devicePath).filter((p) => p != null) as string[],
|
||||||
);
|
);
|
||||||
@@ -284,6 +209,7 @@ export async function flash(
|
|||||||
|
|
||||||
analytics.logEvent('Flash', analyticsData);
|
analytics.logEvent('Flash', analyticsData);
|
||||||
|
|
||||||
|
// start api and call the flasher
|
||||||
try {
|
try {
|
||||||
const result = await write(image, drives, flashState.setProgressState);
|
const result = await write(image, drives, flashState.setProgressState);
|
||||||
await flashState.unsetFlashingFlag(result);
|
await flashState.unsetFlashingFlag(result);
|
||||||
@@ -292,8 +218,11 @@ export async function flash(
|
|||||||
cancelled: false,
|
cancelled: false,
|
||||||
errorCode: error.code,
|
errorCode: error.code,
|
||||||
});
|
});
|
||||||
|
|
||||||
windowProgress.clear();
|
windowProgress.clear();
|
||||||
|
|
||||||
const { results = {} } = flashState.getFlashResults();
|
const { results = {} } = flashState.getFlashResults();
|
||||||
|
|
||||||
const eventData = {
|
const eventData = {
|
||||||
...analyticsData,
|
...analyticsData,
|
||||||
errors: results.errors,
|
errors: results.errors,
|
||||||
@@ -304,7 +233,9 @@ export async function flash(
|
|||||||
analytics.logEvent('Write failed', eventData);
|
analytics.logEvent('Write failed', eventData);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
windowProgress.clear();
|
windowProgress.clear();
|
||||||
|
|
||||||
if (flashState.wasLastFlashCancelled()) {
|
if (flashState.wasLastFlashCancelled()) {
|
||||||
const eventData = {
|
const eventData = {
|
||||||
...analyticsData,
|
...analyticsData,
|
||||||
@@ -327,6 +258,7 @@ export async function flash(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Cancel write operation
|
* @summary Cancel write operation
|
||||||
|
* //TODO: find a better solution to handle cancellation
|
||||||
*/
|
*/
|
||||||
export async function cancel(type: string) {
|
export async function cancel(type: string) {
|
||||||
const status = type.toLowerCase();
|
const status = type.toLowerCase();
|
||||||
@@ -341,15 +273,7 @@ export async function cancel(type: string) {
|
|||||||
};
|
};
|
||||||
analytics.logEvent('Cancel', analyticsData);
|
analytics.logEvent('Cancel', analyticsData);
|
||||||
|
|
||||||
// Re-enable lock release on inactivity
|
if (cancelEmitter) {
|
||||||
|
cancelEmitter(status);
|
||||||
try {
|
|
||||||
// @ts-ignore (no Server.sockets in @types/node-ipc)
|
|
||||||
const [socket] = ipc.server.sockets;
|
|
||||||
if (socket !== undefined) {
|
|
||||||
ipc.server.emit(socket, status);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
analytics.logException(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import * as availableDrives from '../../models/available-drives';
|
|||||||
import * as flashState from '../../models/flash-state';
|
import * as flashState from '../../models/flash-state';
|
||||||
import * as selection from '../../models/selection-state';
|
import * as selection from '../../models/selection-state';
|
||||||
import * as analytics from '../../modules/analytics';
|
import * as analytics from '../../modules/analytics';
|
||||||
import { scanner as driveScanner } from '../../modules/drive-scanner';
|
|
||||||
import * as imageWriter from '../../modules/image-writer';
|
import * as imageWriter from '../../modules/image-writer';
|
||||||
import * as notification from '../../os/notification';
|
import * as notification from '../../os/notification';
|
||||||
import {
|
import {
|
||||||
@@ -95,10 +94,6 @@ async function flashImageToDrive(
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop scanning drives when flashing
|
|
||||||
// otherwise Windows throws EPERM
|
|
||||||
driveScanner.stop();
|
|
||||||
|
|
||||||
const iconPath = path.join('media', 'icon.png');
|
const iconPath = path.join('media', 'icon.png');
|
||||||
const basename = path.basename(image.path);
|
const basename = path.basename(image.path);
|
||||||
try {
|
try {
|
||||||
@@ -110,7 +105,7 @@ async function flashImageToDrive(
|
|||||||
cancelled,
|
cancelled,
|
||||||
} = flashState.getFlashResults();
|
} = flashState.getFlashResults();
|
||||||
if (!skip && !cancelled) {
|
if (!skip && !cancelled) {
|
||||||
if (results.devices.successful > 0) {
|
if (results?.devices?.successful > 0) {
|
||||||
notifySuccess(iconPath, basename, drives, results.devices);
|
notifySuccess(iconPath, basename, drives, results.devices);
|
||||||
} else {
|
} else {
|
||||||
notifyFailure(iconPath, basename, drives);
|
notifyFailure(iconPath, basename, drives);
|
||||||
@@ -129,7 +124,6 @@ async function flashImageToDrive(
|
|||||||
return errorMessage;
|
return errorMessage;
|
||||||
} finally {
|
} finally {
|
||||||
availableDrives.setDrives([]);
|
availableDrives.setDrives([]);
|
||||||
driveScanner.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/cog.svg';
|
import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/gear.svg';
|
||||||
import QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/question-circle.svg';
|
import QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-question.svg';
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import * as prettyBytes from 'pretty-bytes';
|
||||||
@@ -26,10 +26,8 @@ import styled from 'styled-components';
|
|||||||
import FinishPage from '../../components/finish/finish';
|
import FinishPage from '../../components/finish/finish';
|
||||||
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
|
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
|
||||||
import { SettingsModal } from '../../components/settings/settings';
|
import { SettingsModal } from '../../components/settings/settings';
|
||||||
import {
|
import { SourceSelector } from '../../components/source-selector/source-selector';
|
||||||
SourceMetadata,
|
import { SourceMetadata } from '../../../../shared/typings/source-selector';
|
||||||
SourceSelector,
|
|
||||||
} from '../../components/source-selector/source-selector';
|
|
||||||
import * as flashState from '../../models/flash-state';
|
import * as flashState from '../../models/flash-state';
|
||||||
import * as selectionState from '../../models/selection-state';
|
import * as selectionState from '../../models/selection-state';
|
||||||
import * as settings from '../../models/settings';
|
import * as settings from '../../models/settings';
|
||||||
@@ -118,10 +116,10 @@ interface MainPageState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MainPage extends React.Component<
|
export class MainPage extends React.Component<
|
||||||
{},
|
object,
|
||||||
MainPageState & MainPageStateFromStore
|
MainPageState & MainPageStateFromStore
|
||||||
> {
|
> {
|
||||||
constructor(props: {}) {
|
constructor(props: object) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
current: 'main',
|
current: 'main',
|
||||||
@@ -313,7 +311,7 @@ export class MainPage extends React.Component<
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
openExternal(
|
openExternal(
|
||||||
selectionState.getImage()?.supportUrl ||
|
selectionState.getImage()?.supportUrl ||
|
||||||
'https://github.com/balena-io/etcher/blob/master/SUPPORT.md',
|
'https://github.com/balena-io/etcher/blob/master/docs/SUPPORT.md',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
tabIndex={6}
|
tabIndex={6}
|
||||||
|
|||||||
12
lib/gui/app/preload.ts
Normal file
12
lib/gui/app/preload.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// See the Electron documentation for details on how to use preload scripts:
|
||||||
|
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
|
||||||
|
|
||||||
|
import * as webapi from '../webapi';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
etcher: typeof webapi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window['etcher'] = webapi;
|
||||||
@@ -6,10 +6,4 @@ import { ipcRenderer } from 'electron';
|
|||||||
|
|
||||||
ipcRenderer.send('change-lng', langParser());
|
ipcRenderer.send('change-lng', langParser());
|
||||||
|
|
||||||
if (module.hot) {
|
|
||||||
module.hot.accept('./app', () => {
|
|
||||||
main();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
Alert as AlertBase,
|
Alert as AlertBase,
|
||||||
@@ -113,14 +112,25 @@ export const DetailsText = (props: FlexProps) => (
|
|||||||
|
|
||||||
const modalFooterShadowCss = css`
|
const modalFooterShadowCss = css`
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: 0, linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%, 0,
|
background:
|
||||||
|
0,
|
||||||
|
linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%,
|
||||||
|
0,
|
||||||
linear-gradient(rgba(255, 255, 255, 0), rgba(221, 225, 240, 0.5) 70%) 0 100%;
|
linear-gradient(rgba(255, 255, 255, 0), rgba(221, 225, 240, 0.5) 70%) 0 100%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 100% 40px, 100% 40px, 100% 8px, 100% 8px;
|
background-size:
|
||||||
|
100% 40px,
|
||||||
|
100% 40px,
|
||||||
|
100% 8px,
|
||||||
|
100% 8px;
|
||||||
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
background-size: 100% 40px, 100% 40px, 100% 8px, 100% 8px;
|
background-size:
|
||||||
|
100% 40px,
|
||||||
|
100% 40px,
|
||||||
|
100% 8px,
|
||||||
|
100% 8px;
|
||||||
background-attachment: local, local, scroll, scroll;
|
background-attachment: local, local, scroll, scroll;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -236,16 +246,15 @@ export interface GenericTableProps<T> extends BaseTableProps<T> {
|
|||||||
showWarnings?: boolean;
|
showWarnings?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenericTable: <T>(
|
function GenericTable<T>(
|
||||||
props: GenericTableProps<T>,
|
props: GenericTableProps<T>,
|
||||||
) => React.ReactElement<GenericTableProps<T>> = <T extends {}>({
|
): React.ReactElement<GenericTableProps<T>> {
|
||||||
refFn,
|
return (
|
||||||
...props
|
<div>
|
||||||
}: GenericTableProps<T>) => (
|
<BaseTable<T> ref={props.refFn} {...props} />
|
||||||
<div>
|
</div>
|
||||||
<BaseTable<T> ref={refFn} {...props} />
|
);
|
||||||
</div>
|
}
|
||||||
);
|
|
||||||
|
|
||||||
function StyledTable<T>() {
|
function StyledTable<T>() {
|
||||||
return styled((props: GenericTableProps<T>) => (
|
return styled((props: GenericTableProps<T>) => (
|
||||||
@@ -284,7 +293,6 @@ function StyledTable<T>() {
|
|||||||
[data-display='table-body'] > [data-display='table-row'] {
|
[data-display='table-body'] > [data-display='table-row'] {
|
||||||
> [data-display='table-cell']:first-child {
|
> [data-display='table-cell']:first-child {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
width: 6%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> [data-display='table-cell']:last-child {
|
> [data-display='table-cell']:last-child {
|
||||||
@@ -319,7 +327,7 @@ function StyledTable<T>() {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Table = <T extends {}>(props: GenericTableProps<T>) => {
|
export const Table = <T extends object>(props: GenericTableProps<T>) => {
|
||||||
const TypedStyledFunctional = StyledTable<T>();
|
const TypedStyledFunctional = StyledTable<T>();
|
||||||
return <TypedStyledFunctional {...props} />;
|
return <TypedStyledFunctional {...props} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
|
||||||
|
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
|
||||||
|
// whether you're running in development or production).
|
||||||
|
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
|
||||||
|
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
|
||||||
|
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import * as remoteMain from '@electron/remote/main';
|
import * as remoteMain from '@electron/remote/main';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
@@ -21,7 +27,7 @@ import { promises as fs } from 'fs';
|
|||||||
import { platform } from 'os';
|
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 * as _ from 'lodash';
|
import * as lodash from 'lodash';
|
||||||
|
|
||||||
import './app/i18n';
|
import './app/i18n';
|
||||||
|
|
||||||
@@ -34,6 +40,8 @@ import * as SentryMain from '@sentry/electron/main';
|
|||||||
import * as packageJSON from '../../package.json';
|
import * as packageJSON from '../../package.json';
|
||||||
import { anonymizeSentryData } from './app/modules/analytics';
|
import { anonymizeSentryData } from './app/modules/analytics';
|
||||||
|
|
||||||
|
import { delay } from '../shared/utils';
|
||||||
|
|
||||||
const customProtocol = 'etcher';
|
const customProtocol = 'etcher';
|
||||||
const scheme = `${customProtocol}://`;
|
const scheme = `${customProtocol}://`;
|
||||||
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
const updatablePackageTypes = ['appimage', 'nsis', 'dmg'];
|
||||||
@@ -52,7 +60,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; // undefinded (default) means 100%
|
const shouldUpdate = release!.updateInfo.stagingPercentage !== 0; // undefined (default) means 100%
|
||||||
if (shouldUpdate && isOutdated) {
|
if (shouldUpdate && isOutdated) {
|
||||||
await autoUpdater.downloadUpdate();
|
await autoUpdater.downloadUpdate();
|
||||||
packageUpdated = true;
|
packageUpdated = true;
|
||||||
@@ -107,10 +115,10 @@ async function getCommandLineURL(argv: string[]): Promise<string | undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initSentryMain = _.once(() => {
|
const initSentryMain = lodash.once(() => {
|
||||||
const dsn =
|
const dsn =
|
||||||
settings.getSync('analyticsSentryToken') ||
|
settings.getSync('analyticsSentryToken') ||
|
||||||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
lodash.get(packageJSON, ['analytics', 'sentry', 'token']);
|
||||||
|
|
||||||
SentryMain.init({ dsn, beforeSend: anonymizeSentryData });
|
SentryMain.init({ dsn, beforeSend: anonymizeSentryData });
|
||||||
});
|
});
|
||||||
@@ -138,14 +146,6 @@ electron.app.on('open-url', async (event, data) => {
|
|||||||
await selectImageURL(data);
|
await selectImageURL(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
interface AutoUpdaterConfig {
|
|
||||||
autoDownload?: boolean;
|
|
||||||
autoInstallOnAppQuit?: boolean;
|
|
||||||
allowPrerelease?: boolean;
|
|
||||||
fullChangelog?: boolean;
|
|
||||||
allowDowngrade?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createMainWindow() {
|
async function createMainWindow() {
|
||||||
const fullscreen = Boolean(await settings.get('fullscreen'));
|
const fullscreen = Boolean(await settings.get('fullscreen'));
|
||||||
const defaultWidth = settings.DEFAULT_WIDTH;
|
const defaultWidth = settings.DEFAULT_WIDTH;
|
||||||
@@ -176,15 +176,16 @@ async function createMainWindow() {
|
|||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
webviewTag: true,
|
webviewTag: true,
|
||||||
zoomFactor: width / defaultWidth,
|
zoomFactor: width / defaultWidth,
|
||||||
|
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
electron.app.setAsDefaultProtocolClient(customProtocol);
|
electron.app.setAsDefaultProtocolClient(customProtocol);
|
||||||
|
|
||||||
mainWindow.setFullScreen(true);
|
// mainWindow.setFullScreen(true);
|
||||||
|
|
||||||
// Prevent flash of white when starting the application
|
// Prevent flash of white when starting the application
|
||||||
mainWindow.on('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
console.timeEnd('ready-to-show');
|
console.timeEnd('ready-to-show');
|
||||||
// Electron sometimes caches the zoomFactor
|
// Electron sometimes caches the zoomFactor
|
||||||
// making it obnoxious to switch back-and-forth
|
// making it obnoxious to switch back-and-forth
|
||||||
@@ -195,17 +196,11 @@ async function createMainWindow() {
|
|||||||
// Prevent external resources from being loaded (like images)
|
// Prevent external resources from being loaded (like images)
|
||||||
// when dropping them on the WebView.
|
// when dropping them on the WebView.
|
||||||
// See https://github.com/electron/electron/issues/5919
|
// See https://github.com/electron/electron/issues/5919
|
||||||
mainWindow.webContents.on('will-navigate', (event) => {
|
mainWindow.webContents.on('will-navigate', (event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.loadURL(
|
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
||||||
`file://${path.join(
|
|
||||||
'/',
|
|
||||||
...__dirname.split(path.sep).map(encodeURIComponent),
|
|
||||||
'index.html',
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const page = mainWindow.webContents;
|
const page = mainWindow.webContents;
|
||||||
remoteMain.enable(page);
|
remoteMain.enable(page);
|
||||||
@@ -241,6 +236,20 @@ electron.app.on('before-quit', () => {
|
|||||||
process.exit(EXIT_CODES.SUCCESS);
|
process.exit(EXIT_CODES.SUCCESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// this is replaced at build-time with the path to helper binary,
|
||||||
|
// relative to the app resources directory.
|
||||||
|
declare const ETCHER_UTIL_BIN_PATH: string;
|
||||||
|
|
||||||
|
electron.ipcMain.handle('get-util-path', () => {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// In development there is no "app bundle" and we're working directly with
|
||||||
|
// artifacts from the "out" directory, where this value point to.
|
||||||
|
return ETCHER_UTIL_BIN_PATH;
|
||||||
|
}
|
||||||
|
// In any other case, resolve the helper relative to resources path.
|
||||||
|
return path.resolve(process.resourcesPath, ETCHER_UTIL_BIN_PATH);
|
||||||
|
});
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
if (!electron.app.requestSingleInstanceLock()) {
|
if (!electron.app.requestSingleInstanceLock()) {
|
||||||
electron.app.quit();
|
electron.app.quit();
|
||||||
@@ -267,8 +276,33 @@ async function main(): Promise<void> {
|
|||||||
console.log('Build menu failed. ');
|
console.log('Build menu failed. ');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('webview-dom-ready', (_, id) => {
|
||||||
|
const webview = electron.webContents.fromId(id);
|
||||||
|
|
||||||
|
// Open link in browser if it's opened as a 'foreground-tab'
|
||||||
|
webview!.setWindowOpenHandler((event) => {
|
||||||
|
const url = new URL(event.url);
|
||||||
|
if (
|
||||||
|
(url.protocol === 'http:' || url.protocol === 'https:') &&
|
||||||
|
event.disposition === 'foreground-tab' &&
|
||||||
|
// Don't open links if they're disabled by the env var
|
||||||
|
!settings.getSync('disableExternalLinks')
|
||||||
|
) {
|
||||||
|
electron.shell.openExternal(url.href);
|
||||||
|
}
|
||||||
|
return { action: 'deny' };
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||||
|
// tslint:disable-next-line:no-var-requires
|
||||||
|
if (require('electron-squirrel-startup')) {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
||||||
console.time('ready-to-show');
|
console.time('ready-to-show');
|
||||||
|
|||||||
@@ -1,333 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 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 { Drive as DrivelistDrive } from 'drivelist';
|
|
||||||
import {
|
|
||||||
BlockDevice,
|
|
||||||
File,
|
|
||||||
Http,
|
|
||||||
Metadata,
|
|
||||||
SourceDestination,
|
|
||||||
} from 'etcher-sdk/build/source-destination';
|
|
||||||
import {
|
|
||||||
MultiDestinationProgress,
|
|
||||||
OnProgressFunction,
|
|
||||||
OnFailFunction,
|
|
||||||
decompressThenFlash,
|
|
||||||
DECOMPRESSED_IMAGE_PREFIX,
|
|
||||||
} from 'etcher-sdk/build/multi-write';
|
|
||||||
import { cleanupTmpFiles } from 'etcher-sdk/build/tmp';
|
|
||||||
import * as ipc from 'node-ipc';
|
|
||||||
import { totalmem } from 'os';
|
|
||||||
|
|
||||||
import { toJSON } from '../../shared/errors';
|
|
||||||
import { GENERAL_ERROR, SUCCESS } from '../../shared/exit-codes';
|
|
||||||
import { delay, isJson } from '../../shared/utils';
|
|
||||||
import { SourceMetadata } from '../app/components/source-selector/source-selector';
|
|
||||||
import axios from 'axios';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
ipc.config.id = process.env.IPC_CLIENT_ID as string;
|
|
||||||
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
|
|
||||||
|
|
||||||
// NOTE: Ensure this isn't disabled, as it will cause
|
|
||||||
// the stdout maxBuffer size to be exceeded when flashing
|
|
||||||
ipc.config.silent = true;
|
|
||||||
|
|
||||||
// > If set to 0, the client will NOT try to reconnect.
|
|
||||||
// See https://github.com/RIAEvangelist/node-ipc/
|
|
||||||
//
|
|
||||||
// The purpose behind this change is for this process
|
|
||||||
// to emit a "disconnect" event as soon as the GUI
|
|
||||||
// process is closed, so we can kill this process as well.
|
|
||||||
// @ts-ignore (0 is a valid value for stopRetrying and is not the same as false)
|
|
||||||
ipc.config.stopRetrying = 0;
|
|
||||||
|
|
||||||
const DISCONNECT_DELAY = 100;
|
|
||||||
const IPC_SERVER_ID = process.env.IPC_SERVER_ID as string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Send a log debug message to the IPC server
|
|
||||||
*/
|
|
||||||
function log(message: string) {
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('log', message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Terminate the child writer process
|
|
||||||
*/
|
|
||||||
async function terminate(exitCode: number) {
|
|
||||||
ipc.disconnect(IPC_SERVER_ID);
|
|
||||||
await cleanupTmpFiles(Date.now(), DECOMPRESSED_IMAGE_PREFIX);
|
|
||||||
process.nextTick(() => {
|
|
||||||
process.exit(exitCode || SUCCESS);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Handle a child writer error
|
|
||||||
*/
|
|
||||||
async function handleError(error: Error) {
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('error', toJSON(error));
|
|
||||||
await delay(DISCONNECT_DELAY);
|
|
||||||
await terminate(GENERAL_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FlashError extends Error {
|
|
||||||
description: string;
|
|
||||||
device: string;
|
|
||||||
code: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WriteResult {
|
|
||||||
bytesWritten?: number;
|
|
||||||
devices?: {
|
|
||||||
failed: number;
|
|
||||||
successful: number;
|
|
||||||
};
|
|
||||||
errors: FlashError[];
|
|
||||||
sourceMetadata?: Metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FlashResults extends WriteResult {
|
|
||||||
skip?: boolean;
|
|
||||||
cancelled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary writes the source to the destinations and valiates the writes
|
|
||||||
* @param {SourceDestination} source - source
|
|
||||||
* @param {SourceDestination[]} destinations - destinations
|
|
||||||
* @param {Boolean} verify - whether to validate the writes or not
|
|
||||||
* @param {Boolean} autoBlockmapping - whether to trim ext partitions before writing
|
|
||||||
* @param {Function} onProgress - function to call on progress
|
|
||||||
* @param {Function} onFail - function to call on fail
|
|
||||||
* @returns {Promise<{ bytesWritten, devices, errors} >}
|
|
||||||
*/
|
|
||||||
async function writeAndValidate({
|
|
||||||
source,
|
|
||||||
destinations,
|
|
||||||
verify,
|
|
||||||
autoBlockmapping,
|
|
||||||
decompressFirst,
|
|
||||||
onProgress,
|
|
||||||
onFail,
|
|
||||||
}: {
|
|
||||||
source: SourceDestination;
|
|
||||||
destinations: BlockDevice[];
|
|
||||||
verify: boolean;
|
|
||||||
autoBlockmapping: boolean;
|
|
||||||
decompressFirst: boolean;
|
|
||||||
onProgress: OnProgressFunction;
|
|
||||||
onFail: OnFailFunction;
|
|
||||||
}): Promise<WriteResult> {
|
|
||||||
const { sourceMetadata, failures, bytesWritten } = await decompressThenFlash({
|
|
||||||
source,
|
|
||||||
destinations,
|
|
||||||
onFail,
|
|
||||||
onProgress,
|
|
||||||
verify,
|
|
||||||
trim: autoBlockmapping,
|
|
||||||
numBuffers: Math.min(
|
|
||||||
2 + (destinations.length - 1) * 32,
|
|
||||||
256,
|
|
||||||
Math.floor(totalmem() / 1024 ** 2 / 8),
|
|
||||||
),
|
|
||||||
decompressFirst,
|
|
||||||
});
|
|
||||||
const result: WriteResult = {
|
|
||||||
bytesWritten,
|
|
||||||
devices: {
|
|
||||||
failed: failures.size,
|
|
||||||
successful: destinations.length - failures.size,
|
|
||||||
},
|
|
||||||
errors: [],
|
|
||||||
sourceMetadata,
|
|
||||||
};
|
|
||||||
for (const [destination, error] of failures) {
|
|
||||||
const err = error as FlashError;
|
|
||||||
const drive = destination as BlockDevice;
|
|
||||||
err.device = drive.device;
|
|
||||||
err.description = drive.description;
|
|
||||||
result.errors.push(err);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WriteOptions {
|
|
||||||
image: SourceMetadata;
|
|
||||||
destinations: DrivelistDrive[];
|
|
||||||
autoBlockmapping: boolean;
|
|
||||||
decompressFirst: boolean;
|
|
||||||
SourceType: string;
|
|
||||||
httpRequest?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc.connectTo(IPC_SERVER_ID, () => {
|
|
||||||
// Remove leftover tmp files older than 1 hour
|
|
||||||
cleanupTmpFiles(Date.now() - 60 * 60 * 1000);
|
|
||||||
process.once('uncaughtException', handleError);
|
|
||||||
|
|
||||||
// Gracefully exit on the following cases. If the parent
|
|
||||||
// process detects that child exit successfully but
|
|
||||||
// no flashing information is available, then it will
|
|
||||||
// assume that the child died halfway through.
|
|
||||||
|
|
||||||
process.once('SIGINT', async () => {
|
|
||||||
await terminate(SUCCESS);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.once('SIGTERM', async () => {
|
|
||||||
await terminate(SUCCESS);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The IPC server failed. Abort.
|
|
||||||
ipc.of[IPC_SERVER_ID].on('error', async () => {
|
|
||||||
await terminate(SUCCESS);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The IPC server was disconnected. Abort.
|
|
||||||
ipc.of[IPC_SERVER_ID].on('disconnect', async () => {
|
|
||||||
await terminate(SUCCESS);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.of[IPC_SERVER_ID].on('write', async (options: WriteOptions) => {
|
|
||||||
/**
|
|
||||||
* @summary Progress handler
|
|
||||||
* @param {Object} state - progress state
|
|
||||||
* @example
|
|
||||||
* writer.on('progress', onProgress)
|
|
||||||
*/
|
|
||||||
const onProgress = (state: MultiDestinationProgress) => {
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('state', state);
|
|
||||||
};
|
|
||||||
|
|
||||||
let exitCode = SUCCESS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Abort handler
|
|
||||||
* @example
|
|
||||||
* writer.on('abort', onAbort)
|
|
||||||
*/
|
|
||||||
const onAbort = async () => {
|
|
||||||
log('Abort');
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('abort');
|
|
||||||
await delay(DISCONNECT_DELAY);
|
|
||||||
await terminate(exitCode);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSkip = async () => {
|
|
||||||
log('Skip validation');
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('skip');
|
|
||||||
await delay(DISCONNECT_DELAY);
|
|
||||||
await terminate(exitCode);
|
|
||||||
};
|
|
||||||
|
|
||||||
ipc.of[IPC_SERVER_ID].on('cancel', onAbort);
|
|
||||||
|
|
||||||
ipc.of[IPC_SERVER_ID].on('skip', onSkip);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @summary Failure handler (non-fatal errors)
|
|
||||||
* @param {SourceDestination} destination - destination
|
|
||||||
* @param {Error} error - error
|
|
||||||
* @example
|
|
||||||
* writer.on('fail', onFail)
|
|
||||||
*/
|
|
||||||
const onFail = (destination: SourceDestination, error: Error) => {
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('fail', {
|
|
||||||
// TODO: device should be destination
|
|
||||||
// @ts-ignore (destination.drive is private)
|
|
||||||
device: destination.drive,
|
|
||||||
error: toJSON(error),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const destinations = options.destinations.map((d) => d.device);
|
|
||||||
const imagePath = options.image.path;
|
|
||||||
log(`Image: ${imagePath}`);
|
|
||||||
log(`Devices: ${destinations.join(', ')}`);
|
|
||||||
log(`Auto blockmapping: ${options.autoBlockmapping}`);
|
|
||||||
log(`Decompress first: ${options.decompressFirst}`);
|
|
||||||
const dests = options.destinations.map((destination) => {
|
|
||||||
return new BlockDevice({
|
|
||||||
drive: destination,
|
|
||||||
unmountOnSuccess: true,
|
|
||||||
write: true,
|
|
||||||
direct: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const { SourceType } = options;
|
|
||||||
try {
|
|
||||||
let source;
|
|
||||||
if (options.image.drive) {
|
|
||||||
source = new BlockDevice({
|
|
||||||
drive: options.image.drive,
|
|
||||||
direct: !options.autoBlockmapping,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (SourceType === File.name) {
|
|
||||||
source = new File({
|
|
||||||
path: imagePath,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const decodedImagePath = decodeURIComponent(imagePath);
|
|
||||||
if (isJson(decodedImagePath)) {
|
|
||||||
const imagePathObject = JSON.parse(decodedImagePath);
|
|
||||||
source = new Http({
|
|
||||||
url: imagePathObject.url,
|
|
||||||
avoidRandomAccess: true,
|
|
||||||
axiosInstance: axios.create(_.omit(imagePathObject, ['url'])),
|
|
||||||
auth: options.image.auth,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
source = new Http({
|
|
||||||
url: imagePath,
|
|
||||||
avoidRandomAccess: true,
|
|
||||||
auth: options.image.auth,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const results = await writeAndValidate({
|
|
||||||
source,
|
|
||||||
destinations: dests,
|
|
||||||
verify: true,
|
|
||||||
autoBlockmapping: options.autoBlockmapping,
|
|
||||||
decompressFirst: options.decompressFirst,
|
|
||||||
onProgress,
|
|
||||||
onFail,
|
|
||||||
});
|
|
||||||
log(`Finish: ${results.bytesWritten}`);
|
|
||||||
results.errors = results.errors.map((error) => {
|
|
||||||
return toJSON(error);
|
|
||||||
});
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('done', { results });
|
|
||||||
await delay(DISCONNECT_DELAY);
|
|
||||||
await terminate(exitCode);
|
|
||||||
} catch (error: any) {
|
|
||||||
exitCode = GENERAL_ERROR;
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('error', toJSON(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.of[IPC_SERVER_ID].on('connect', () => {
|
|
||||||
log(
|
|
||||||
`Successfully connected to IPC server: ${IPC_SERVER_ID}, socket root ${ipc.config.socketRoot}`,
|
|
||||||
);
|
|
||||||
ipc.of[IPC_SERVER_ID].emit('ready', {});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
15
lib/gui/webapi.ts
Normal file
15
lib/gui/webapi.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Anything exported from this module will become available to the
|
||||||
|
// renderer process via preload. They're accessible as `window.etcher.foo()`.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
// FIXME: this is a workaround for the renderer to be able to find the etcher-util
|
||||||
|
// binary. We should instead export a function that asks the main process to launch
|
||||||
|
// the binary itself.
|
||||||
|
export async function getEtcherUtilPath(): Promise<string> {
|
||||||
|
const utilPath = await ipcRenderer.invoke('get-util-path');
|
||||||
|
console.log(utilPath);
|
||||||
|
return utilPath;
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@ import { join } from 'path';
|
|||||||
import { env } from 'process';
|
import { env } from 'process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { getAppPath } from '../utils';
|
|
||||||
import { supportedLocales } from '../../gui/app/i18n';
|
import { supportedLocales } from '../../gui/app/i18n';
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
@@ -27,6 +26,15 @@ const execFileAsync = promisify(execFile);
|
|||||||
const SUCCESSFUL_AUTH_MARKER = 'AUTHENTICATION SUCCEEDED';
|
const SUCCESSFUL_AUTH_MARKER = 'AUTHENTICATION SUCCEEDED';
|
||||||
const EXPECTED_SUCCESSFUL_AUTH_MARKER = `${SUCCESSFUL_AUTH_MARKER}\n`;
|
const EXPECTED_SUCCESSFUL_AUTH_MARKER = `${SUCCESSFUL_AUTH_MARKER}\n`;
|
||||||
|
|
||||||
|
function getAskPassScriptPath(lang: string): string {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// Force webpack's hand to bundle the script.
|
||||||
|
return require.resolve(`./sudo-askpass.osascript-${lang}.js`);
|
||||||
|
}
|
||||||
|
// Otherwise resolve the script relative to resources path.
|
||||||
|
return join(process.resourcesPath, `sudo-askpass.osascript-${lang}.js`);
|
||||||
|
}
|
||||||
|
|
||||||
export async function sudo(
|
export async function sudo(
|
||||||
command: string,
|
command: string,
|
||||||
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
|
||||||
@@ -47,11 +55,7 @@ export async function sudo(
|
|||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: {
|
env: {
|
||||||
PATH: env.PATH,
|
PATH: env.PATH,
|
||||||
SUDO_ASKPASS: join(
|
SUDO_ASKPASS: getAskPassScriptPath(lang),
|
||||||
getAppPath(),
|
|
||||||
__dirname,
|
|
||||||
`sudo-askpass.osascript-${lang}.js`,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Drive } from 'drivelist';
|
import { Drive } from 'drivelist';
|
||||||
import * as _ from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
import * as pathIsInside from 'path-is-inside';
|
import * as pathIsInside from 'path-is-inside';
|
||||||
|
|
||||||
import * as messages from './messages';
|
import * as messages from './messages';
|
||||||
import { SourceMetadata } from '../gui/app/components/source-selector/source-selector';
|
import { SourceMetadata } from './typings/source-selector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary The default unknown size for things such as images and drives
|
* @summary The default unknown size for things such as images and drives
|
||||||
@@ -210,8 +210,8 @@ export function getDriveImageCompatibilityStatuses(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!_.isNil(drive) &&
|
!isNil(drive) &&
|
||||||
!_.isNil(drive.size) &&
|
!isNil(drive.size) &&
|
||||||
!isDriveLargeEnough(drive, image)
|
!isDriveLargeEnough(drive, image)
|
||||||
) {
|
) {
|
||||||
statusList.push(statuses.small);
|
statusList.push(statuses.small);
|
||||||
@@ -229,7 +229,7 @@ export function getDriveImageCompatibilityStatuses(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
image !== undefined &&
|
image !== undefined &&
|
||||||
!_.isNil(drive) &&
|
!isNil(drive) &&
|
||||||
!isDriveSizeRecommended(drive, image)
|
!isDriveSizeRecommended(drive, image)
|
||||||
) {
|
) {
|
||||||
statusList.push(statuses.sizeNotRecommended);
|
statusList.push(statuses.sizeNotRecommended);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import { outdent } from 'outdent';
|
import { outdent } from 'outdent';
|
||||||
import * as prettyBytes from 'pretty-bytes';
|
import prettyBytes from 'pretty-bytes';
|
||||||
import '../gui/app/i18n';
|
import '../gui/app/i18n';
|
||||||
import * as i18next from 'i18next';
|
import * as i18next from 'i18next';
|
||||||
|
|
||||||
@@ -164,11 +164,11 @@ export const error = {
|
|||||||
? i18next.t('message.toDrive', {
|
? i18next.t('message.toDrive', {
|
||||||
description: drives[0].description,
|
description: drives[0].description,
|
||||||
name: drives[0].displayName,
|
name: drives[0].displayName,
|
||||||
})
|
})
|
||||||
: i18next.t('message.toTarget', {
|
: i18next.t('message.toTarget', {
|
||||||
count: drives.length,
|
count: drives.length,
|
||||||
num: drives.length,
|
num: drives.length,
|
||||||
});
|
});
|
||||||
return i18next.t('message.flashError', {
|
return i18next.t('message.flashError', {
|
||||||
image: imageBasename,
|
image: imageBasename,
|
||||||
targets: target,
|
targets: target,
|
||||||
|
|||||||
@@ -70,14 +70,14 @@ export async function isElevated(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return process.geteuid() === UNIX_SUPERUSER_USER_ID;
|
return process.geteuid!() === UNIX_SUPERUSER_USER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Check if the current process is running with elevated permissions
|
* @summary Check if the current process is running with elevated permissions
|
||||||
*/
|
*/
|
||||||
export function isElevatedUnixSync(): boolean {
|
export function isElevatedUnixSync(): boolean {
|
||||||
return process.geteuid() === UNIX_SUPERUSER_USER_ID;
|
return process.geteuid!() === UNIX_SUPERUSER_USER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeSh(value: any): string {
|
function escapeSh(value: any): string {
|
||||||
|
|||||||
23
lib/shared/typings/source-selector.ts
Normal file
23
lib/shared/typings/source-selector.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { GPTPartition, MBRPartition } from 'partitioninfo';
|
||||||
|
import { sourceDestination } from 'etcher-sdk';
|
||||||
|
import { DrivelistDrive } from '../drive-constraints';
|
||||||
|
|
||||||
|
export type Source = 'File' | 'BlockDevice' | 'Http';
|
||||||
|
|
||||||
|
export interface SourceMetadata extends sourceDestination.Metadata {
|
||||||
|
hasMBR?: boolean;
|
||||||
|
partitions?: MBRPartition[] | GPTPartition[];
|
||||||
|
path: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
SourceType: Source;
|
||||||
|
drive?: DrivelistDrive;
|
||||||
|
extension?: string;
|
||||||
|
archiveExtension?: string;
|
||||||
|
auth?: Authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Authentication {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
@@ -14,9 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import { Dictionary } from 'lodash';
|
|
||||||
|
|
||||||
import * as errors from './errors';
|
import * as errors from './errors';
|
||||||
|
|
||||||
export function isValidPercentage(percentage: any): boolean {
|
export function isValidPercentage(percentage: any): boolean {
|
||||||
@@ -38,19 +35,6 @@ export async function delay(duration: number): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAppPath(): string {
|
|
||||||
return (
|
|
||||||
(require('electron').app || require('@electron/remote').app)
|
|
||||||
.getAppPath()
|
|
||||||
// With macOS universal builds, getAppPath() returns the path to an app.asar file containing an index.js file which will
|
|
||||||
// include the app-x64 or app-arm64 folder depending on the arch.
|
|
||||||
// We don't care about the app.asar file, we want the actual folder.
|
|
||||||
.replace(/\.asar$/, () =>
|
|
||||||
process.platform === 'darwin' ? '-' + process.arch : '',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isJson(jsonString: string) {
|
export function isJson(jsonString: string) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(jsonString);
|
JSON.parse(jsonString);
|
||||||
|
|||||||
203
lib/util/api.ts
Normal file
203
lib/util/api.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 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 * as ipc from 'node-ipc';
|
||||||
|
import { Dictionary, values } from 'lodash';
|
||||||
|
|
||||||
|
import type { MultiDestinationProgress } from 'etcher-sdk/build/multi-write';
|
||||||
|
|
||||||
|
import { toJSON } from '../shared/errors';
|
||||||
|
import { GENERAL_ERROR, SUCCESS } from '../shared/exit-codes';
|
||||||
|
import { delay } from '../shared/utils';
|
||||||
|
import { WriteOptions } from './types/types';
|
||||||
|
import { write, cleanup } from './child-writer';
|
||||||
|
import { startScanning } from './scanner';
|
||||||
|
import { getSourceMetadata } from './source-metadata';
|
||||||
|
import { DrivelistDrive } from '../shared/drive-constraints';
|
||||||
|
|
||||||
|
ipc.config.id = process.env.IPC_CLIENT_ID as string;
|
||||||
|
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
|
||||||
|
|
||||||
|
// NOTE: Ensure this isn't disabled, as it will cause
|
||||||
|
// the stdout maxBuffer size to be exceeded when flashing
|
||||||
|
ipc.config.silent = true;
|
||||||
|
|
||||||
|
// > If set to 0, the client will NOT try to reconnect.
|
||||||
|
// See https://github.com/RIAEvangelist/node-ipc/
|
||||||
|
//
|
||||||
|
// The purpose behind this change is for this process
|
||||||
|
// to emit a "disconnect" event as soon as the GUI
|
||||||
|
// process is closed, so we can kill this process as well.
|
||||||
|
|
||||||
|
// @ts-ignore (0 is a valid value for stopRetrying and is not the same as false)
|
||||||
|
ipc.config.stopRetrying = 0;
|
||||||
|
|
||||||
|
const DISCONNECT_DELAY = 100;
|
||||||
|
const IPC_SERVER_ID = process.env.IPC_SERVER_ID as string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Send a message to the IPC server
|
||||||
|
*/
|
||||||
|
function emit(channel: string, message?: any) {
|
||||||
|
ipc.of[IPC_SERVER_ID].emit(channel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Send a log debug message to the IPC server
|
||||||
|
*/
|
||||||
|
function log(message: string) {
|
||||||
|
if (console?.log) {
|
||||||
|
console.log(message);
|
||||||
|
}
|
||||||
|
emit('log', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Terminate the child process
|
||||||
|
*/
|
||||||
|
async function terminate(exitCode: number) {
|
||||||
|
ipc.disconnect(IPC_SERVER_ID);
|
||||||
|
await cleanup(Date.now());
|
||||||
|
process.nextTick(() => {
|
||||||
|
process.exit(exitCode || SUCCESS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Handle errors
|
||||||
|
*/
|
||||||
|
async function handleError(error: Error) {
|
||||||
|
emit('error', toJSON(error));
|
||||||
|
await delay(DISCONNECT_DELAY);
|
||||||
|
await terminate(GENERAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Abort handler
|
||||||
|
* @example
|
||||||
|
*/
|
||||||
|
const onAbort = async (exitCode: number) => {
|
||||||
|
log('Abort');
|
||||||
|
emit('abort');
|
||||||
|
await delay(DISCONNECT_DELAY);
|
||||||
|
await terminate(exitCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSkip = async (exitCode: number) => {
|
||||||
|
log('Skip validation');
|
||||||
|
emit('skip');
|
||||||
|
await delay(DISCONNECT_DELAY);
|
||||||
|
await terminate(exitCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
ipc.connectTo(IPC_SERVER_ID, () => {
|
||||||
|
// Gracefully exit on the following cases. If the parent
|
||||||
|
// process detects that child exit successfully but
|
||||||
|
// no flashing information is available, then it will
|
||||||
|
// assume that the child died halfway through.
|
||||||
|
|
||||||
|
process.once('uncaughtException', handleError);
|
||||||
|
|
||||||
|
process.once('SIGINT', async () => {
|
||||||
|
await terminate(SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.once('SIGTERM', async () => {
|
||||||
|
await terminate(SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The IPC server failed. Abort.
|
||||||
|
ipc.of[IPC_SERVER_ID].on('error', async () => {
|
||||||
|
await terminate(SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The IPC server was disconnected. Abort.
|
||||||
|
ipc.of[IPC_SERVER_ID].on('disconnect', async () => {
|
||||||
|
await terminate(SUCCESS);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.of[IPC_SERVER_ID].on('sourceMetadata', async (params) => {
|
||||||
|
const { selected, SourceType, auth } = JSON.parse(params);
|
||||||
|
try {
|
||||||
|
const sourceMatadata = await getSourceMetadata(
|
||||||
|
selected,
|
||||||
|
SourceType,
|
||||||
|
auth,
|
||||||
|
);
|
||||||
|
emitSourceMetadata(sourceMatadata);
|
||||||
|
} catch (error: any) {
|
||||||
|
emitFail(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.of[IPC_SERVER_ID].on('scan', async () => {
|
||||||
|
startScanning();
|
||||||
|
});
|
||||||
|
|
||||||
|
// write handler
|
||||||
|
ipc.of[IPC_SERVER_ID].on('write', async (options: WriteOptions) => {
|
||||||
|
// Remove leftover tmp files older than 1 hour
|
||||||
|
cleanup(Date.now() - 60 * 60 * 1000);
|
||||||
|
|
||||||
|
let exitCode = SUCCESS;
|
||||||
|
|
||||||
|
ipc.of[IPC_SERVER_ID].on('cancel', () => onAbort(exitCode));
|
||||||
|
|
||||||
|
ipc.of[IPC_SERVER_ID].on('skip', () => onSkip(exitCode));
|
||||||
|
|
||||||
|
const results = await write(options);
|
||||||
|
|
||||||
|
if (results.errors.length > 0) {
|
||||||
|
results.errors = results.errors.map((error: any) => {
|
||||||
|
return toJSON(error);
|
||||||
|
});
|
||||||
|
exitCode = GENERAL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('done', { results });
|
||||||
|
await delay(DISCONNECT_DELAY);
|
||||||
|
await terminate(exitCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.of[IPC_SERVER_ID].on('connect', () => {
|
||||||
|
log(
|
||||||
|
`Successfully connected to IPC server: ${IPC_SERVER_ID}, socket root ${ipc.config.socketRoot}`,
|
||||||
|
);
|
||||||
|
emit('ready', {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function emitLog(message: string) {
|
||||||
|
log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitState(state: MultiDestinationProgress) {
|
||||||
|
emit('state', state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitFail(data: any) {
|
||||||
|
emit('fail', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitDrives(drives: Dictionary<DrivelistDrive>) {
|
||||||
|
emit('drives', JSON.stringify(values(drives)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitSourceMetadata(sourceMetadata: any) {
|
||||||
|
emit('sourceMetadata', JSON.stringify(sourceMetadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { emitLog, emitState, emitFail, emitDrives, emitSourceMetadata };
|
||||||
201
lib/util/child-writer.ts
Normal file
201
lib/util/child-writer.ts
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 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.
|
||||||
|
*
|
||||||
|
* This file handles the writer process.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
OnProgressFunction,
|
||||||
|
OnFailFunction,
|
||||||
|
decompressThenFlash,
|
||||||
|
DECOMPRESSED_IMAGE_PREFIX,
|
||||||
|
MultiDestinationProgress,
|
||||||
|
} from 'etcher-sdk/build/multi-write';
|
||||||
|
|
||||||
|
import { totalmem } from 'os';
|
||||||
|
|
||||||
|
import { cleanupTmpFiles } from 'etcher-sdk/build/tmp';
|
||||||
|
|
||||||
|
import {
|
||||||
|
File,
|
||||||
|
Http,
|
||||||
|
BlockDevice,
|
||||||
|
SourceDestination,
|
||||||
|
} from 'etcher-sdk/build/source-destination';
|
||||||
|
|
||||||
|
import { WriteResult, FlashError, WriteOptions } from './types/types';
|
||||||
|
|
||||||
|
import { isJson } from '../shared/utils';
|
||||||
|
import { toJSON } from '../shared/errors';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
import { emitLog, emitState, emitFail } from './api';
|
||||||
|
|
||||||
|
async function write(options: WriteOptions) {
|
||||||
|
/**
|
||||||
|
* @summary Failure handler (non-fatal errors)
|
||||||
|
* @param {SourceDestination} destination - destination
|
||||||
|
* @param {Error} error - error
|
||||||
|
*/
|
||||||
|
const onFail = (destination: SourceDestination, error: Error) => {
|
||||||
|
emitFail({
|
||||||
|
// TODO: device should be destination
|
||||||
|
|
||||||
|
// @ts-ignore (destination.drive is private)
|
||||||
|
device: destination.drive,
|
||||||
|
error: toJSON(error),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Progress handler
|
||||||
|
* @param {Object} state - progress state
|
||||||
|
* @example
|
||||||
|
* writer.on('progress', onProgress)
|
||||||
|
*/
|
||||||
|
const onProgress = (state: MultiDestinationProgress) => {
|
||||||
|
emitState(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write the image to the destinations
|
||||||
|
const destinations = options.destinations.map((d) => d.device);
|
||||||
|
const imagePath = options.image.path;
|
||||||
|
emitLog(`Image: ${imagePath}`);
|
||||||
|
emitLog(`Devices: ${destinations.join(', ')}`);
|
||||||
|
emitLog(`Auto blockmapping: ${options.autoBlockmapping}`);
|
||||||
|
emitLog(`Decompress first: ${options.decompressFirst}`);
|
||||||
|
const dests = options.destinations.map((destination) => {
|
||||||
|
return new BlockDevice({
|
||||||
|
drive: destination,
|
||||||
|
unmountOnSuccess: true,
|
||||||
|
write: true,
|
||||||
|
direct: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const { SourceType } = options;
|
||||||
|
try {
|
||||||
|
let source;
|
||||||
|
if (options.image.drive) {
|
||||||
|
source = new BlockDevice({
|
||||||
|
drive: options.image.drive,
|
||||||
|
direct: !options.autoBlockmapping,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (SourceType === File.name) {
|
||||||
|
source = new File({
|
||||||
|
path: imagePath,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const decodedImagePath = decodeURIComponent(imagePath);
|
||||||
|
if (isJson(decodedImagePath)) {
|
||||||
|
const imagePathObject = JSON.parse(decodedImagePath);
|
||||||
|
source = new Http({
|
||||||
|
url: imagePathObject.url,
|
||||||
|
avoidRandomAccess: true,
|
||||||
|
axiosInstance: axios.create(omit(imagePathObject, ['url'])),
|
||||||
|
auth: options.image.auth,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
source = new Http({
|
||||||
|
url: imagePath,
|
||||||
|
avoidRandomAccess: true,
|
||||||
|
auth: options.image.auth,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await writeAndValidate({
|
||||||
|
source,
|
||||||
|
destinations: dests,
|
||||||
|
verify: true,
|
||||||
|
autoBlockmapping: options.autoBlockmapping,
|
||||||
|
decompressFirst: options.decompressFirst,
|
||||||
|
onProgress,
|
||||||
|
onFail,
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error: any) {
|
||||||
|
return { errors: [error] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @summary clean up tmp files */
|
||||||
|
export async function cleanup(until: number) {
|
||||||
|
await cleanupTmpFiles(until, DECOMPRESSED_IMAGE_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary writes the source to the destinations and validates the writes
|
||||||
|
* @param {SourceDestination} source - source
|
||||||
|
* @param {SourceDestination[]} destinations - destinations
|
||||||
|
* @param {Boolean} verify - whether to validate the writes or not
|
||||||
|
* @param {Boolean} autoBlockmapping - whether to trim ext partitions before writing
|
||||||
|
* @param {Function} onProgress - function to call on progress
|
||||||
|
* @param {Function} onFail - function to call on fail
|
||||||
|
* @returns {Promise<{ bytesWritten, devices, errors} >}
|
||||||
|
*/
|
||||||
|
async function writeAndValidate({
|
||||||
|
source,
|
||||||
|
destinations,
|
||||||
|
verify,
|
||||||
|
autoBlockmapping,
|
||||||
|
decompressFirst,
|
||||||
|
onProgress,
|
||||||
|
onFail,
|
||||||
|
}: {
|
||||||
|
source: SourceDestination;
|
||||||
|
destinations: BlockDevice[];
|
||||||
|
verify: boolean;
|
||||||
|
autoBlockmapping: boolean;
|
||||||
|
decompressFirst: boolean;
|
||||||
|
onProgress: OnProgressFunction;
|
||||||
|
onFail: OnFailFunction;
|
||||||
|
}): Promise<WriteResult> {
|
||||||
|
const { sourceMetadata, failures, bytesWritten } = await decompressThenFlash({
|
||||||
|
source,
|
||||||
|
destinations,
|
||||||
|
onFail,
|
||||||
|
onProgress,
|
||||||
|
verify,
|
||||||
|
trim: autoBlockmapping,
|
||||||
|
numBuffers: Math.min(
|
||||||
|
2 + (destinations.length - 1) * 32,
|
||||||
|
256,
|
||||||
|
Math.floor(totalmem() / 1024 ** 2 / 8),
|
||||||
|
),
|
||||||
|
decompressFirst,
|
||||||
|
});
|
||||||
|
const result: WriteResult = {
|
||||||
|
bytesWritten,
|
||||||
|
devices: {
|
||||||
|
failed: failures.size,
|
||||||
|
successful: destinations.length - failures.size,
|
||||||
|
},
|
||||||
|
errors: [],
|
||||||
|
sourceMetadata,
|
||||||
|
};
|
||||||
|
for (const [destination, error] of failures) {
|
||||||
|
const err = error as FlashError;
|
||||||
|
const drive = destination as BlockDevice;
|
||||||
|
err.device = drive.device;
|
||||||
|
err.description = drive.description;
|
||||||
|
result.errors.push(err);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { write };
|
||||||
@@ -30,14 +30,13 @@ const adapters: Adapter[] = [
|
|||||||
|
|
||||||
// Can't use permissions.isElevated() here as it returns a promise and we need to set
|
// Can't use permissions.isElevated() here as it returns a promise and we need to set
|
||||||
// module.exports = scanner right now.
|
// module.exports = scanner right now.
|
||||||
if (platform !== 'linux' || geteuid() === 0) {
|
if (platform !== 'linux' || (geteuid && geteuid() === 0)) {
|
||||||
adapters.push(new UsbbootDeviceAdapter());
|
adapters.push(new UsbbootDeviceAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform === 'win32') {
|
if (platform === 'win32') {
|
||||||
const {
|
const {
|
||||||
DriverlessDeviceAdapter: driverless,
|
DriverlessDeviceAdapter: driverless,
|
||||||
// tslint:disable-next-line:no-var-requires
|
|
||||||
} = require('etcher-sdk/build/scanner/adapters/driverless');
|
} = require('etcher-sdk/build/scanner/adapters/driverless');
|
||||||
adapters.push(new driverless());
|
adapters.push(new driverless());
|
||||||
}
|
}
|
||||||
183
lib/util/scanner.ts
Normal file
183
lib/util/scanner.ts
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
import { scanner as driveScanner } from './drive-scanner';
|
||||||
|
import * as sdk from 'etcher-sdk';
|
||||||
|
import { DrivelistDrive } from '../shared/drive-constraints';
|
||||||
|
import outdent from 'outdent';
|
||||||
|
import { Dictionary, values, keyBy, padStart } from 'lodash';
|
||||||
|
import { emitDrives } from './api';
|
||||||
|
|
||||||
|
let availableDrives: DrivelistDrive[] = [];
|
||||||
|
|
||||||
|
export function hasAvailableDrives() {
|
||||||
|
return availableDrives.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
driveScanner.on('error', (error) => {
|
||||||
|
// Stop the drive scanning loop in case of errors,
|
||||||
|
// otherwise we risk presenting the same error over
|
||||||
|
// and over again to the user, while also heavily
|
||||||
|
// spamming our error reporting service.
|
||||||
|
driveScanner.stop();
|
||||||
|
|
||||||
|
console.log('scanner error', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setDrives(drives: Dictionary<DrivelistDrive>) {
|
||||||
|
availableDrives = values(drives);
|
||||||
|
emitDrives(drives);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDrives() {
|
||||||
|
return keyBy(availableDrives, 'device');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDrive(drive: Drive) {
|
||||||
|
const preparedDrive = prepareDrive(drive);
|
||||||
|
if (!(await driveIsAllowed(preparedDrive))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const drives = getDrives();
|
||||||
|
drives[preparedDrive.device] = preparedDrive;
|
||||||
|
|
||||||
|
setDrives(drives);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeDrive(drive: Drive) {
|
||||||
|
const preparedDrive = prepareDrive(drive);
|
||||||
|
const drives = getDrives();
|
||||||
|
delete drives[preparedDrive.device];
|
||||||
|
setDrives(drives);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function driveIsAllowed(drive: {
|
||||||
|
devicePath: string;
|
||||||
|
device: string;
|
||||||
|
raw: string;
|
||||||
|
}) {
|
||||||
|
// const driveBlacklist = (await settings.get("driveBlacklist")) || [];
|
||||||
|
const driveBlacklist: any[] = [];
|
||||||
|
return !(
|
||||||
|
driveBlacklist.includes(drive.devicePath) ||
|
||||||
|
driveBlacklist.includes(drive.device) ||
|
||||||
|
driveBlacklist.includes(drive.raw)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Drive =
|
||||||
|
| sdk.sourceDestination.BlockDevice
|
||||||
|
| sdk.sourceDestination.UsbbootDrive
|
||||||
|
| sdk.sourceDestination.DriverlessDevice;
|
||||||
|
|
||||||
|
function prepareDrive(drive: Drive) {
|
||||||
|
if (drive instanceof sdk.sourceDestination.BlockDevice) {
|
||||||
|
// @ts-ignore (BlockDevice.drive is private)
|
||||||
|
return drive.drive;
|
||||||
|
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
|
||||||
|
// This is a workaround etcher expecting a device string and a size
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
drive.device = drive.usbDevice.portId;
|
||||||
|
drive.size = null;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
drive.progress = 0;
|
||||||
|
drive.disabled = true;
|
||||||
|
drive.on('progress', (progress) => {
|
||||||
|
updateDriveProgress(drive, progress);
|
||||||
|
});
|
||||||
|
return drive;
|
||||||
|
} else if (drive instanceof sdk.sourceDestination.DriverlessDevice) {
|
||||||
|
const description =
|
||||||
|
COMPUTE_MODULE_DESCRIPTIONS[
|
||||||
|
drive.deviceDescriptor.idProduct.toString()
|
||||||
|
] || 'Compute Module';
|
||||||
|
return {
|
||||||
|
device: `${usbIdToString(
|
||||||
|
drive.deviceDescriptor.idVendor,
|
||||||
|
)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`,
|
||||||
|
displayName: 'Missing drivers',
|
||||||
|
description,
|
||||||
|
mountpoints: [],
|
||||||
|
isReadOnly: false,
|
||||||
|
isSystem: false,
|
||||||
|
disabled: true,
|
||||||
|
icon: 'warning',
|
||||||
|
size: null,
|
||||||
|
link: 'https://www.raspberrypi.com/documentation/computers/compute-module.html#flashing-the-compute-module-emmc',
|
||||||
|
linkCTA: 'Install',
|
||||||
|
linkTitle: 'Install missing drivers',
|
||||||
|
linkMessage: outdent`
|
||||||
|
Would you like to download the necessary drivers from the Raspberry Pi Foundation?
|
||||||
|
This will open your browser.
|
||||||
|
|
||||||
|
|
||||||
|
Once opened, download and run the installer from the "Windows Installer" section to install the drivers
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The radix used by USB ID numbers
|
||||||
|
*/
|
||||||
|
const USB_ID_RADIX = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary The expected length of a USB ID number
|
||||||
|
*/
|
||||||
|
const USB_ID_LENGTH = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Convert a USB id (e.g. product/vendor) to a string
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* console.log(usbIdToString(2652))
|
||||||
|
* > '0x0a5c'
|
||||||
|
*/
|
||||||
|
function usbIdToString(id: number): string {
|
||||||
|
return `0x${padStart(id.toString(USB_ID_RADIX), USB_ID_LENGTH, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDriveProgress(
|
||||||
|
drive: sdk.sourceDestination.UsbbootDrive,
|
||||||
|
progress: number,
|
||||||
|
) {
|
||||||
|
const drives = getDrives();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const driveInMap = drives[drive.device];
|
||||||
|
if (driveInMap) {
|
||||||
|
// @ts-ignore
|
||||||
|
drives[drive.device] = { ...driveInMap, progress };
|
||||||
|
setDrives(drives);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Product ID of BCM2708
|
||||||
|
*/
|
||||||
|
const USB_PRODUCT_ID_BCM2708_BOOT = 0x2763;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Product ID of BCM2710
|
||||||
|
*/
|
||||||
|
const USB_PRODUCT_ID_BCM2710_BOOT = 0x2764;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Compute module descriptions
|
||||||
|
*/
|
||||||
|
const COMPUTE_MODULE_DESCRIPTIONS: Dictionary<string> = {
|
||||||
|
[USB_PRODUCT_ID_BCM2708_BOOT]: 'Compute Module 1',
|
||||||
|
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3',
|
||||||
|
};
|
||||||
|
|
||||||
|
const startScanning = () => {
|
||||||
|
driveScanner.on('attach', (drive) => addDrive(drive));
|
||||||
|
driveScanner.on('detach', (drive) => removeDrive(drive));
|
||||||
|
driveScanner.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopScanning = () => {
|
||||||
|
driveScanner.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
export { startScanning, stopScanning };
|
||||||
93
lib/util/source-metadata.ts
Normal file
93
lib/util/source-metadata.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/** Get metadata for a source */
|
||||||
|
|
||||||
|
import { sourceDestination } from 'etcher-sdk';
|
||||||
|
import { replaceWindowsNetworkDriveLetter } from '../gui/app/os/windows-network-drives';
|
||||||
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
import { isJson } from '../shared/utils';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {
|
||||||
|
SourceMetadata,
|
||||||
|
Authentication,
|
||||||
|
Source,
|
||||||
|
} from '../shared/typings/source-selector';
|
||||||
|
import { DrivelistDrive } from '../shared/drive-constraints';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
function isString(value: any): value is string {
|
||||||
|
return typeof value === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSource(
|
||||||
|
selected: string,
|
||||||
|
SourceType: Source,
|
||||||
|
auth?: Authentication,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
selected = await replaceWindowsNetworkDriveLetter(selected);
|
||||||
|
} catch (error: any) {
|
||||||
|
// TODO: analytics.logException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJson(decodeURIComponent(selected))) {
|
||||||
|
const config: AxiosRequestConfig = JSON.parse(decodeURIComponent(selected));
|
||||||
|
return new sourceDestination.Http({
|
||||||
|
url: config.url!,
|
||||||
|
axiosInstance: axios.create(omit(config, ['url'])),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SourceType === 'File') {
|
||||||
|
return new sourceDestination.File({
|
||||||
|
path: selected,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new sourceDestination.Http({ url: selected, auth });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMetadata(
|
||||||
|
source: sourceDestination.SourceDestination,
|
||||||
|
selected: string | DrivelistDrive,
|
||||||
|
) {
|
||||||
|
const metadata = (await source.getMetadata()) as SourceMetadata;
|
||||||
|
const partitionTable = await source.getPartitionTable();
|
||||||
|
if (partitionTable) {
|
||||||
|
metadata.hasMBR = true;
|
||||||
|
metadata.partitions = partitionTable.partitions;
|
||||||
|
} else {
|
||||||
|
metadata.hasMBR = false;
|
||||||
|
}
|
||||||
|
if (isString(selected)) {
|
||||||
|
metadata.extension = path.extname(selected).slice(1);
|
||||||
|
metadata.path = selected;
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSourceMetadata(
|
||||||
|
selected: string | DrivelistDrive,
|
||||||
|
SourceType: Source,
|
||||||
|
auth?: Authentication,
|
||||||
|
) {
|
||||||
|
if (isString(selected)) {
|
||||||
|
const source = await createSource(selected, SourceType, auth);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const innerSource = await source.getInnerSource();
|
||||||
|
|
||||||
|
const metadata = await getMetadata(innerSource, selected);
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
} catch (error: any) {
|
||||||
|
// TODO: handle error
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
await source.close();
|
||||||
|
} catch (error: any) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getSourceMetadata };
|
||||||
33
lib/util/types/types.d.ts
vendored
Normal file
33
lib/util/types/types.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Metadata } from 'etcher-sdk/build/source-destination';
|
||||||
|
import { SourceMetadata } from '../../shared/typings/source-selector';
|
||||||
|
import { Drive as DrivelistDrive } from 'drivelist';
|
||||||
|
|
||||||
|
export interface WriteResult {
|
||||||
|
bytesWritten?: number;
|
||||||
|
devices?: {
|
||||||
|
failed: number;
|
||||||
|
successful: number;
|
||||||
|
};
|
||||||
|
errors: FlashError[];
|
||||||
|
sourceMetadata?: Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlashError extends Error {
|
||||||
|
description: string;
|
||||||
|
device: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlashResults extends WriteResult {
|
||||||
|
skip?: boolean;
|
||||||
|
cancelled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WriteOptions {
|
||||||
|
image: SourceMetadata;
|
||||||
|
destinations: DrivelistDrive[];
|
||||||
|
autoBlockmapping: boolean;
|
||||||
|
decompressFirst: boolean;
|
||||||
|
SourceType: string;
|
||||||
|
httpRequest?: any;
|
||||||
|
}
|
||||||
38989
npm-shrinkwrap.json
generated
Normal file
38989
npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
36145
package-lock.json
generated
36145
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
207
package.json
207
package.json
@@ -2,9 +2,10 @@
|
|||||||
"name": "balena-etcher",
|
"name": "balena-etcher",
|
||||||
"private": true,
|
"private": true,
|
||||||
"displayName": "balenaEtcher",
|
"displayName": "balenaEtcher",
|
||||||
"version": "1.18.6",
|
"productName": "balenaEtcher",
|
||||||
|
"version": "1.19.8",
|
||||||
"packageType": "local",
|
"packageType": "local",
|
||||||
"main": "generated/etcher.js",
|
"main": ".webpack/main",
|
||||||
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
|
||||||
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
|
||||||
"homepage": "https://github.com/balena-io/etcher",
|
"homepage": "https://github.com/balena-io/etcher",
|
||||||
@@ -13,119 +14,139 @@
|
|||||||
"url": "git@github.com:balena-io/etcher.git"
|
"url": "git@github.com:balena-io/etcher.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run webpack",
|
"prettify": "prettier --write lib/**/*.css && balena-lint --fix --typescript typings lib tests forge.config.ts forge.sidecar.ts webpack.config.ts",
|
||||||
"flowzone-preinstall-linux": "sudo apt-get update && 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",
|
"lint": "npm run prettify && catch-uncommitted",
|
||||||
"flowzone-preinstall-macos": "true",
|
"test-gui": "xvfb-maybe electron-mocha --recursive --reporter spec --window-config tests/gui/window-config.json --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox --renderer tests/gui/**/*.ts",
|
||||||
"flowzone-preinstall-windows": "npx node-gyp install",
|
"test-shared": "xvfb-maybe electron-mocha --recursive --reporter spec --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox tests/shared/**/*.ts",
|
||||||
"flowzone-preinstall": "npm run flowzone-preinstall-linux",
|
"test": "npm run test-gui && npm run test-shared",
|
||||||
"lint-css": "prettier --write lib/**/*.css",
|
"package": "electron-forge package",
|
||||||
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts",
|
"start": "electron-forge start",
|
||||||
"lint": "npm run lint-ts && npm run lint-css",
|
"make": "electron-forge make"
|
||||||
"postinstall": "electron-rebuild -t prod,dev,optional",
|
|
||||||
"sanity-checks": "bash scripts/ci/ensure-all-file-extensions-in-gitattributes.sh",
|
|
||||||
"start": "./node_modules/.bin/electron .",
|
|
||||||
"test-gui": "electron-mocha --recursive --reporter spec --window-config tests/gui/window-config.json --require ts-node/register/transpile-only --require-main tests/gui/allow-renderer-process-reuse.ts --full-trace --no-sandbox --renderer tests/gui/**/*.ts",
|
|
||||||
"test-shared": "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-macos": "npm run lint && npm run test-gui && npm run test-shared && npm run sanity-checks",
|
|
||||||
"test-linux": "npm run lint && xvfb-run --auto-servernum npm run test-gui && xvfb-run --auto-servernum npm run test-shared && npm run sanity-checks",
|
|
||||||
"test-windows": "npm run lint && npm run test-gui && npm run test-shared && npm run sanity-checks",
|
|
||||||
"test": "echo npm run test-{linux,windows,macos}",
|
|
||||||
"watch": "webpack serve --no-optimization-minimize --config ./webpack.dev.config.ts",
|
|
||||||
"webpack": "webpack"
|
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "npm run prettify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
|
||||||
"./**/*.{ts,tsx}": [
|
|
||||||
"npm run lint-ts"
|
|
||||||
],
|
|
||||||
"./**/*.css": [
|
|
||||||
"npm run lint-css"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"author": "Balena Ltd. <hello@balena.io>",
|
"author": "Balena Ltd. <hello@balena.io>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"dependencies": {
|
||||||
"@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",
|
||||||
"@electron/remote": "^2.0.9",
|
"@electron/remote": "^2.1.0",
|
||||||
"@fortawesome/fontawesome-free": "5.15.4",
|
"@fortawesome/fontawesome-free": "6.5.1",
|
||||||
"@sentry/electron": "^4.1.2",
|
"@sentry/electron": "^4.15.1",
|
||||||
"@svgr/webpack": "5.5.0",
|
|
||||||
"@types/chai": "4.3.4",
|
|
||||||
"@types/copy-webpack-plugin": "6.4.3",
|
|
||||||
"@types/mime-types": "2.1.1",
|
|
||||||
"@types/mini-css-extract-plugin": "1.4.3",
|
|
||||||
"@types/mocha": "^9.1.1",
|
|
||||||
"@types/node": "^16.18.12",
|
|
||||||
"@types/node-ipc": "9.2.0",
|
|
||||||
"@types/react": "16.14.34",
|
|
||||||
"@types/react-dom": "16.9.17",
|
|
||||||
"@types/semver": "7.3.13",
|
|
||||||
"@types/sinon": "9.0.11",
|
|
||||||
"@types/terser-webpack-plugin": "5.0.4",
|
|
||||||
"@types/tmp": "0.2.3",
|
|
||||||
"@types/webpack-node-externals": "2.5.3",
|
|
||||||
"analytics-client": "^2.0.1",
|
"analytics-client": "^2.0.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^1.6.0",
|
||||||
"chai": "4.3.7",
|
|
||||||
"copy-webpack-plugin": "7.0.0",
|
|
||||||
"css-loader": "5.2.7",
|
|
||||||
"d3": "4.13.0",
|
|
||||||
"debug": "4.3.4",
|
"debug": "4.3.4",
|
||||||
"electron": "^19.1.9",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-updater": "6.1.7",
|
||||||
"electron-mocha": "^11.0.2",
|
"etcher-sdk": "9.0.0",
|
||||||
"electron-notarize": "1.2.2",
|
"i18next": "23.7.8",
|
||||||
"electron-rebuild": "^3.2.9",
|
|
||||||
"electron-updater": "5.3.0",
|
|
||||||
"esbuild-loader": "2.20.0",
|
|
||||||
"etcher-sdk": "8.3.1",
|
|
||||||
"file-loader": "6.2.0",
|
|
||||||
"husky": "4.3.8",
|
|
||||||
"i18next": "21.10.0",
|
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"lint-staged": "10.5.4",
|
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mini-css-extract-plugin": "1.6.2",
|
|
||||||
"mocha": "^9.1.1",
|
|
||||||
"native-addon-loader": "2.0.1",
|
|
||||||
"node-ipc": "9.2.1",
|
"node-ipc": "9.2.1",
|
||||||
"omit-deep-lodash": "1.1.7",
|
|
||||||
"outdent": "0.8.0",
|
"outdent": "0.8.0",
|
||||||
"path-is-inside": "1.0.2",
|
"path-is-inside": "1.0.2",
|
||||||
"pnp-webpack-plugin": "1.7.0",
|
|
||||||
"pretty-bytes": "5.6.0",
|
"pretty-bytes": "5.6.0",
|
||||||
"react": "16.8.5",
|
"react": "17.0.2",
|
||||||
"react-dom": "16.8.5",
|
"react-dom": "17.0.2",
|
||||||
"react-i18next": "11.18.6",
|
"react-i18next": "13.5.0",
|
||||||
"redux": "4.2.0",
|
"redux": "4.2.1",
|
||||||
"rendition": "19.3.2",
|
"rendition": "35.1.2",
|
||||||
"semver": "7.3.8",
|
"semver": "7.5.4",
|
||||||
"simple-progress-webpack-plugin": "1.1.2",
|
|
||||||
"sinon": "9.2.4",
|
|
||||||
"string-replace-loader": "3.1.0",
|
|
||||||
"style-loader": "2.0.0",
|
|
||||||
"styled-components": "5.3.6",
|
"styled-components": "5.3.6",
|
||||||
"sys-class-rgb-led": "3.0.1",
|
"sys-class-rgb-led": "3.0.1",
|
||||||
"terser-webpack-plugin": "5.3.6",
|
"uuid": "9.0.1"
|
||||||
"ts-loader": "8.4.0",
|
},
|
||||||
"ts-node": "9.1.1",
|
"devDependencies": {
|
||||||
"tslib": "2.4.1",
|
"@balena/lint": "7.2.4",
|
||||||
"typescript": "4.4.4",
|
"@electron-forge/cli": "7.2.0",
|
||||||
|
"@electron-forge/maker-deb": "7.2.0",
|
||||||
|
"@electron-forge/maker-dmg": "7.2.0",
|
||||||
|
"@electron-forge/maker-rpm": "7.2.0",
|
||||||
|
"@electron-forge/maker-squirrel": "7.2.0",
|
||||||
|
"@electron-forge/maker-zip": "7.2.0",
|
||||||
|
"@electron-forge/plugin-auto-unpack-natives": "7.2.0",
|
||||||
|
"@electron-forge/plugin-webpack": "7.2.0",
|
||||||
|
"@reforged/maker-appimage": "3.3.2",
|
||||||
|
"@svgr/webpack": "8.1.0",
|
||||||
|
"@types/chai": "4.3.11",
|
||||||
|
"@types/debug": "^4.1.12",
|
||||||
|
"@types/mime-types": "2.1.4",
|
||||||
|
"@types/mocha": "^10.0.6",
|
||||||
|
"@types/node": "^20.11.6",
|
||||||
|
"@types/node-ipc": "9.2.3",
|
||||||
|
"@types/react": "17.0.2",
|
||||||
|
"@types/react-dom": "17.0.2",
|
||||||
|
"@types/semver": "7.5.6",
|
||||||
|
"@types/sinon": "17.0.2",
|
||||||
|
"@types/tmp": "0.2.6",
|
||||||
|
"@vercel/webpack-asset-relocator-loader": "1.7.3",
|
||||||
|
"catch-uncommitted": "^2.0.0",
|
||||||
|
"chai": "4.3.10",
|
||||||
|
"css-loader": "5.2.7",
|
||||||
|
"electron": "27.1.3",
|
||||||
|
"electron-mocha": "^12.2.0",
|
||||||
|
"file-loader": "6.2.0",
|
||||||
|
"husky": "8.0.3",
|
||||||
|
"mocha": "^10.2.0",
|
||||||
|
"native-addon-loader": "2.0.1",
|
||||||
|
"node-loader": "^2.0.0",
|
||||||
|
"omit-deep-lodash": "1.1.7",
|
||||||
|
"@yao-pkg/pkg": "^5.11.1",
|
||||||
|
"sinon": "17.0.1",
|
||||||
|
"string-replace-loader": "3.1.0",
|
||||||
|
"style-loader": "3.3.3",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tslib": "2.6.2",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
"url-loader": "4.1.1",
|
"url-loader": "4.1.1",
|
||||||
"uuid": "8.3.2",
|
"xvfb-maybe": "^0.2.1"
|
||||||
"webpack": "5.75.0",
|
},
|
||||||
"webpack-cli": "4.10.0",
|
"hostDependencies": {
|
||||||
"webpack-dev-server": "4.11.1"
|
"debian": [
|
||||||
|
"gconf-service",
|
||||||
|
"gconf2",
|
||||||
|
"libasound2",
|
||||||
|
"libatk1.0-0",
|
||||||
|
"libc6",
|
||||||
|
"libcairo2",
|
||||||
|
"libcups2",
|
||||||
|
"libdbus-1-3",
|
||||||
|
"libexpat1",
|
||||||
|
"libfontconfig1",
|
||||||
|
"libfreetype6",
|
||||||
|
"libgbm1",
|
||||||
|
"libgcc1",
|
||||||
|
"libgconf-2-4",
|
||||||
|
"libgdk-pixbuf2.0-0",
|
||||||
|
"libglib2.0-0",
|
||||||
|
"libgtk-3-0",
|
||||||
|
"liblzma5",
|
||||||
|
"libnotify4",
|
||||||
|
"libnspr4",
|
||||||
|
"libnss3",
|
||||||
|
"libpango1.0-0 | libpango-1.0-0",
|
||||||
|
"libstdc++6",
|
||||||
|
"libx11-6",
|
||||||
|
"libxcomposite1",
|
||||||
|
"libxcursor1",
|
||||||
|
"libxdamage1",
|
||||||
|
"libxext6",
|
||||||
|
"libxfixes3",
|
||||||
|
"libxi6",
|
||||||
|
"libxrandr2",
|
||||||
|
"libxrender1",
|
||||||
|
"libxss1",
|
||||||
|
"libxtst6",
|
||||||
|
"polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=18 <20"
|
||||||
},
|
},
|
||||||
"versionist": {
|
"versionist": {
|
||||||
"publishedAt": "2023-03-21T13:24:18.905Z"
|
"publishedAt": "2024-04-22T09:37:38.023Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
pkg-sidecar.json
Normal file
12
pkg-sidecar.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
"node_modules/usb/**",
|
||||||
|
"node_modules/lzma-native/**",
|
||||||
|
"node_modules/drivelist/**",
|
||||||
|
"node_modules/mountutils/**",
|
||||||
|
"node_modules/winusb-driver-generator/**",
|
||||||
|
"node_modules/node-raspberrypi-usbboot/**",
|
||||||
|
"node_modules/xxhash-addon/**",
|
||||||
|
"node_modules/axios/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
awscli==1.27.28
|
|
||||||
shyaml==0.6.2
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
###
|
|
||||||
# Copyright 2017 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.
|
|
||||||
###
|
|
||||||
|
|
||||||
set -u
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Read list of wildcards from .gitattributes
|
|
||||||
wildcards=()
|
|
||||||
while IFS='' read -r line || [[ -n "$line" ]]; do
|
|
||||||
if [[ -n "$line" ]]; then
|
|
||||||
if [[ ! "$line" =~ "^#" ]]; then
|
|
||||||
filetype=$(echo "$line" | cut -d ' ' -f 2)
|
|
||||||
if [[ "$filetype" == "text" ]] || [[ "$filetype" == "binary" ]]; then
|
|
||||||
wildcards+=("$(echo "$line" | cut -d ' ' -f 1)")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done < .gitattributes
|
|
||||||
|
|
||||||
# Verify those wildcards against all files stored in the repo
|
|
||||||
git ls-tree -r HEAD | while IFS='' read line; do
|
|
||||||
if [[ "$(echo $line | cut -d ' ' -f 2)" == "blob" ]]; then
|
|
||||||
# the cut delimiter in the line below is actually a tab character, not a space
|
|
||||||
filename=$(basename $(echo "$line" | cut -d ' ' -f 2))
|
|
||||||
found_match=0
|
|
||||||
for wildcard in "${wildcards[@]}"; do
|
|
||||||
if [[ "$filename" = $wildcard ]]; then
|
|
||||||
found_match=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ $found_match -eq 0 ]]; then
|
|
||||||
echo "No wildcards match $filename"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* This script is in charge of cleaning the `shrinkwrap` file.
|
|
||||||
*
|
|
||||||
* `npm shrinkwrap` has a bug where it will add optional dependencies
|
|
||||||
* to `npm-shrinkwrap.json`, therefore causing errors if these optional
|
|
||||||
* dependendencies are platform dependent and you then try to build
|
|
||||||
* the project in another platform.
|
|
||||||
*
|
|
||||||
* As a workaround, we keep a list of platform dependent dependencies in
|
|
||||||
* the `platformSpecificDependencies` property of `package.json`,
|
|
||||||
* and manually remove them from `npm-shrinkwrap.json` if they exist.
|
|
||||||
*
|
|
||||||
* See: https://github.com/npm/npm/issues/2679
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { writeFile } from 'fs';
|
|
||||||
import * as omit from 'omit-deep-lodash';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { promisify } from 'util';
|
|
||||||
|
|
||||||
import * as shrinkwrap from '../npm-shrinkwrap.json';
|
|
||||||
import * as packageInfo from '../package.json';
|
|
||||||
|
|
||||||
const writeFileAsync = promisify(writeFile);
|
|
||||||
|
|
||||||
const JSON_INDENT = 2;
|
|
||||||
const SHRINKWRAP_FILENAME = path.join(__dirname, '..', 'npm-shrinkwrap.json');
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
try {
|
|
||||||
const cleaned = omit(shrinkwrap, packageInfo.platformSpecificDependencies);
|
|
||||||
for (const item of Object.values(cleaned.dependencies)) {
|
|
||||||
// @ts-ignore
|
|
||||||
item.dev = true;
|
|
||||||
}
|
|
||||||
await writeFileAsync(
|
|
||||||
SHRINKWRAP_FILENAME,
|
|
||||||
JSON.stringify(cleaned, null, JSON_INDENT),
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(`[ERROR] Couldn't write shrinkwrap file: ${error.stack}`);
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
`[OK] Wrote shrinkwrap file to ${path.relative(
|
|
||||||
__dirname,
|
|
||||||
SHRINKWRAP_FILENAME,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
Submodule scripts/resin deleted from 8dfa21cfc2
182
test-wrapper.ts
Normal file
182
test-wrapper.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* This is a test wrapper for etcher-utils.
|
||||||
|
* The only use for this file is debugging while developing etcher-utils.
|
||||||
|
* It will create a IPC server, spawn the cli version of etcher-writer, and wait for it to connect.
|
||||||
|
* Requires elevated privileges to work (launch with sudo)
|
||||||
|
* Note that you'll need to to edit `ipc.server.on('ready', ...` function based on what you want to test.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as ipc from 'node-ipc';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import * as packageJSON from './package.json';
|
||||||
|
import * as permissions from './lib/shared/permissions';
|
||||||
|
|
||||||
|
// if (process.argv.length !== 3) {
|
||||||
|
// console.error('Expects an image to flash as only arg!');
|
||||||
|
// process.exit(1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const THREADS_PER_CPU = 16;
|
||||||
|
|
||||||
|
// There might be multiple Etcher instances running at
|
||||||
|
// the same time, therefore we must ensure each IPC
|
||||||
|
// server/client has a different name.
|
||||||
|
const IPC_SERVER_ID = `etcher-server-${process.pid}`;
|
||||||
|
const IPC_CLIENT_ID = `etcher-client-${process.pid}`;
|
||||||
|
|
||||||
|
ipc.config.id = IPC_SERVER_ID;
|
||||||
|
ipc.config.socketRoot = path.join(
|
||||||
|
process.env.XDG_RUNTIME_DIR || os.tmpdir(),
|
||||||
|
path.sep,
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: Ensure this isn't disabled, as it will cause
|
||||||
|
// the stdout maxBuffer size to be exceeded when flashing
|
||||||
|
ipc.config.silent = true;
|
||||||
|
|
||||||
|
function writerArgv(): string[] {
|
||||||
|
const entryPoint = path.join('./generated/etcher-util');
|
||||||
|
return [entryPoint];
|
||||||
|
}
|
||||||
|
|
||||||
|
function writerEnv() {
|
||||||
|
return {
|
||||||
|
IPC_SERVER_ID,
|
||||||
|
IPC_CLIENT_ID,
|
||||||
|
IPC_SOCKET_ROOT: ipc.config.socketRoot,
|
||||||
|
UV_THREADPOOL_SIZE: (os.cpus().length * THREADS_PER_CPU).toString(),
|
||||||
|
// This environment variable prevents the AppImages
|
||||||
|
// desktop integration script from presenting the
|
||||||
|
// "installation" dialog
|
||||||
|
SKIP: '1',
|
||||||
|
...(process.platform === 'win32' ? {} : process.env),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function start(): Promise<any> {
|
||||||
|
ipc.serve();
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
ipc.server.on('error', (message) => {
|
||||||
|
console.log('IPC server error', message);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('log', (message) => {
|
||||||
|
console.log('log', message);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('fail', ({ device, error }) => {
|
||||||
|
console.log('failure', error, device);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('done', (event) => {
|
||||||
|
console.log('done', event);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('abort', () => {
|
||||||
|
console.log('abort');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('skip', () => {
|
||||||
|
console.log('skip');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('state', (progress) => {
|
||||||
|
console.log('progress', progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('drives', (drives) => {
|
||||||
|
console.log('drives', drives);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.server.on('ready', (_data, socket) => {
|
||||||
|
console.log('ready');
|
||||||
|
ipc.server.emit(socket, 'scan', {});
|
||||||
|
// ipc.server.emit(socket, "hello", { message: "world" });
|
||||||
|
// ipc.server.emit(socket, "write", {
|
||||||
|
// image: {
|
||||||
|
// path: process.argv[2],
|
||||||
|
// displayName: "Random image for test",
|
||||||
|
// description: "Random image for test",
|
||||||
|
// SourceType: "File",
|
||||||
|
// },
|
||||||
|
// destinations: [
|
||||||
|
// {
|
||||||
|
// size: 15938355200,
|
||||||
|
// isVirtual: false,
|
||||||
|
// enumerator: "DiskArbitration",
|
||||||
|
// logicalBlockSize: 512,
|
||||||
|
// raw: "/dev/rdisk4",
|
||||||
|
// error: null,
|
||||||
|
// isReadOnly: false,
|
||||||
|
// displayName: "/dev/disk4",
|
||||||
|
// blockSize: 512,
|
||||||
|
// isSCSI: false,
|
||||||
|
// isRemovable: true,
|
||||||
|
// device: "/dev/disk4",
|
||||||
|
// busVersion: null,
|
||||||
|
// isSystem: false,
|
||||||
|
// busType: "USB",
|
||||||
|
// isCard: false,
|
||||||
|
// isUSB: true,
|
||||||
|
// devicePath:
|
||||||
|
// "IODeviceTree:/arm-io@10F00000/usb-drd1@2280000/usb-drd1-port-hs@01100000",
|
||||||
|
// mountpoints: [
|
||||||
|
// {
|
||||||
|
// path: "/Volumes/flash-rootB",
|
||||||
|
// label: "flash-rootB",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: "/Volumes/flash-rootA",
|
||||||
|
// label: "flash-rootA",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: "/Volumes/flash-boot",
|
||||||
|
// label: "flash-boot",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// description: "Generic Flash Disk Media",
|
||||||
|
// isUAS: null,
|
||||||
|
// partitionTableType: "mbr",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// SourceType: "File",
|
||||||
|
// autoBlockmapping: true,
|
||||||
|
// decompressFirst: true,
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
const argv = writerArgv();
|
||||||
|
|
||||||
|
ipc.server.on('start', async () => {
|
||||||
|
console.log(`Elevating command: ${argv.join(' ')}`);
|
||||||
|
const env = writerEnv();
|
||||||
|
try {
|
||||||
|
await permissions.elevateCommand(argv, {
|
||||||
|
applicationName: packageJSON.displayName,
|
||||||
|
environment: env,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log('error', error);
|
||||||
|
// This happens when the child is killed using SIGKILL
|
||||||
|
const SIGKILL_EXIT_CODE = 137;
|
||||||
|
if (error.code === SIGKILL_EXIT_CODE) {
|
||||||
|
error.code = 'ECHILDDIED';
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
} finally {
|
||||||
|
console.log('Terminating IPC server');
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear the update lock timer to prevent longer
|
||||||
|
// flashing timing it out, and releasing the lock
|
||||||
|
ipc.server.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// tslint:disable-next-line:no-var-requires
|
|
||||||
const { app } = require('electron');
|
const { app } = require('electron');
|
||||||
|
|
||||||
if (app !== undefined) {
|
if (app !== undefined) {
|
||||||
// tslint:disable-next-line:no-var-requires
|
|
||||||
const remoteMain = require('@electron/remote/main');
|
const remoteMain = require('@electron/remote/main');
|
||||||
|
|
||||||
remoteMain.initialize();
|
remoteMain.initialize();
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import * as _ from 'lodash';
|
|
||||||
import { stub } from 'sinon';
|
import { stub } from 'sinon';
|
||||||
|
|
||||||
import * as settings from '../../../lib/gui/app/models/settings';
|
import * as settings from '../../../lib/gui/app/models/settings';
|
||||||
@@ -47,8 +46,8 @@ describe('Browser: settings', () => {
|
|||||||
const writeConfigFileStub = stub();
|
const writeConfigFileStub = stub();
|
||||||
writeConfigFileStub.returns(Promise.reject(new Error('settings error')));
|
writeConfigFileStub.returns(Promise.reject(new Error('settings error')));
|
||||||
|
|
||||||
const p = settings.set('foo', 'baz', writeConfigFileStub);
|
const promise = settings.set('foo', 'baz', writeConfigFileStub);
|
||||||
await checkError(p, async (error) => {
|
await checkError(promise, async (error) => {
|
||||||
expect(error).to.be.an.instanceof(Error);
|
expect(error).to.be.an.instanceof(Error);
|
||||||
expect(error.message).to.equal('settings error');
|
expect(error.message).to.equal('settings error');
|
||||||
expect(await settings.get('foo')).to.equal('bar');
|
expect(await settings.get('foo')).to.equal('bar');
|
||||||
|
|||||||
@@ -1156,7 +1156,7 @@ describe('Shared: DriveConstraints', function () {
|
|||||||
'/dev/disk4',
|
'/dev/disk4',
|
||||||
'/dev/disk5',
|
'/dev/disk5',
|
||||||
'/dev/disk6',
|
'/dev/disk6',
|
||||||
];
|
];
|
||||||
const drives = [
|
const drives = [
|
||||||
{
|
{
|
||||||
device: drivePaths[0],
|
device: drivePaths[0],
|
||||||
|
|||||||
17
tsconfig.sidecar.json
Normal file
17
tsconfig.sidecar.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"typeRoots": ["./node_modules/@types", "./typings"],
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true
|
||||||
|
},
|
||||||
|
"include": ["lib/util"]
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"module": "es2015",
|
|
||||||
"target": "es2019",
|
|
||||||
"jsx": "react",
|
|
||||||
"typeRoots": ["./node_modules/@types", "./typings"],
|
|
||||||
"importHelpers": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"lib": ["dom", "esnext"],
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"pretty": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"baseUrl": "./src",
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"allowJs": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"lib/**/*.ts",
|
|
||||||
"node_modules/electron/**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1
typings/pnp-webpack-plugin/index.d.ts
vendored
1
typings/pnp-webpack-plugin/index.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
declare module 'pnp-webpack-plugin';
|
|
||||||
1
typings/resin-corvus/index.d.ts
vendored
1
typings/resin-corvus/index.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
declare module 'resin-corvus/browser';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
declare module 'simple-progress-webpack-plugin';
|
|
||||||
@@ -14,137 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as CopyPlugin from 'copy-webpack-plugin';
|
import type { Configuration, ModuleOptions } from 'webpack';
|
||||||
import { readdirSync } from 'fs';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
import * as os from 'os';
|
|
||||||
import outdent from 'outdent';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { env } from 'process';
|
|
||||||
import * as SimpleProgressWebpackPlugin from 'simple-progress-webpack-plugin';
|
|
||||||
import * as TerserPlugin from 'terser-webpack-plugin';
|
|
||||||
import {
|
import {
|
||||||
BannerPlugin,
|
BannerPlugin,
|
||||||
IgnorePlugin,
|
IgnorePlugin,
|
||||||
NormalModuleReplacementPlugin,
|
NormalModuleReplacementPlugin,
|
||||||
} from 'webpack';
|
} from 'webpack';
|
||||||
import * as PnpWebpackPlugin from 'pnp-webpack-plugin';
|
|
||||||
|
|
||||||
import * as tsconfigRaw from './tsconfig.webpack.json';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't webpack package.json as sentry tokens
|
|
||||||
* will be inserted in it after webpacking
|
|
||||||
*/
|
|
||||||
function externalPackageJson(packageJsonPath: string) {
|
|
||||||
return (
|
|
||||||
{ request }: { context: string; request: string },
|
|
||||||
callback: (error?: Error | null, result?: string) => void,
|
|
||||||
) => {
|
|
||||||
if (_.endsWith(request, 'package.json')) {
|
|
||||||
return callback(null, `commonjs ${packageJsonPath}`);
|
|
||||||
}
|
|
||||||
return callback();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function platformSpecificModule(
|
|
||||||
platform: string,
|
|
||||||
module: string,
|
|
||||||
replacement = '{}',
|
|
||||||
) {
|
|
||||||
// Resolves module on platform, otherwise resolves the replacement
|
|
||||||
return (
|
|
||||||
{ request }: { context: string; request: string },
|
|
||||||
callback: (error?: Error, result?: string, type?: string) => void,
|
|
||||||
) => {
|
|
||||||
if (request === module && os.platform() !== platform) {
|
|
||||||
callback(undefined, replacement);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function renameNodeModules(resourcePath: string) {
|
|
||||||
// electron-builder excludes the node_modules folder even if you specifically include it
|
|
||||||
// Work around by renaming it to "modules"
|
|
||||||
// See https://github.com/electron-userland/electron-builder/issues/4545
|
|
||||||
return (
|
|
||||||
path
|
|
||||||
.relative(__dirname, resourcePath)
|
|
||||||
.replace('node_modules', 'modules')
|
|
||||||
// use the same name on all architectures so electron-builder can build a universal dmg on mac
|
|
||||||
.replace(LZMA_BINDINGS_FOLDER, LZMA_BINDINGS_FOLDER_RENAMED)
|
|
||||||
// file-loader expects posix paths, even on Windows
|
|
||||||
.replace(/\\/g, '/')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
const files = readdirSync(
|
|
||||||
path.join('node_modules', 'lzma-native', 'prebuilds'),
|
|
||||||
);
|
|
||||||
const bindingsFolder = files.find(
|
|
||||||
(f) =>
|
|
||||||
f.startsWith(os.platform()) &&
|
|
||||||
f.endsWith(env.npm_config_target_arch || os.arch()),
|
|
||||||
);
|
|
||||||
if (bindingsFolder === undefined) {
|
|
||||||
throw new Error('Could not find lzma_native binding');
|
|
||||||
}
|
|
||||||
return bindingsFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LZMA_BINDINGS_FOLDER = findLzmaNativeBindingsFolder();
|
|
||||||
const LZMA_BINDINGS_FOLDER_RENAMED = 'binding';
|
|
||||||
|
|
||||||
interface ReplacementRule {
|
interface ReplacementRule {
|
||||||
search: string;
|
search: string;
|
||||||
@@ -164,158 +40,58 @@ function replace(test: RegExp, ...replacements: ReplacementRule[]) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonConfig = {
|
const rules: Required<ModuleOptions>['rules'] = [
|
||||||
mode: 'production',
|
// Add support for native node modules
|
||||||
optimization: {
|
{
|
||||||
moduleIds: 'natural',
|
// We're specifying native_modules in the test because the asset relocator loader generates a
|
||||||
minimize: true,
|
// "fake" .node file which is really a cjs file.
|
||||||
minimizer: [
|
test: /native_modules[/\\].+\.node$/,
|
||||||
new TerserPlugin({
|
use: 'node-loader',
|
||||||
parallel: true,
|
|
||||||
terserOptions: {
|
|
||||||
compress: false,
|
|
||||||
mangle: false,
|
|
||||||
format: {
|
|
||||||
comments: false,
|
|
||||||
ecma: 2020,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extractComments: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
|
||||||
|
parser: { amd: false },
|
||||||
|
use: {
|
||||||
|
loader: '@vercel/webpack-asset-relocator-loader',
|
||||||
|
options: {
|
||||||
|
outputAssetBase: 'native_modules',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
exclude: /(node_modules|\.webpack)/,
|
||||||
|
use: {
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: {
|
||||||
|
transpileOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||||
|
loader: 'file-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.svg$/,
|
||||||
|
use: '@svgr/webpack',
|
||||||
|
},
|
||||||
|
// force axios to use http backend (not xhr) to support streams
|
||||||
|
replace(/node_modules\/axios\/lib\/defaults\.js$/, {
|
||||||
|
search: './adapters/xhr',
|
||||||
|
replace: './adapters/http',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const rendererConfig: Configuration = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules,
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
use: ['style-loader', 'css-loader'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: { name: renameNodeModules },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.svg$/,
|
|
||||||
use: '@svgr/webpack',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'esbuild-loader',
|
|
||||||
options: {
|
|
||||||
loader: 'tsx',
|
|
||||||
target: 'es2021',
|
|
||||||
tsconfigRaw,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// don't import WeakMap polyfill in deep-map-keys (required in corvus)
|
|
||||||
replace(/node_modules\/deep-map-keys\/lib\/deep-map-keys\.js$/, {
|
|
||||||
search: "var WeakMap = require('es6-weak-map');",
|
|
||||||
replace: '',
|
|
||||||
}),
|
|
||||||
// force axios to use http backend (not xhr) to support streams
|
|
||||||
replace(/node_modules\/axios\/lib\/defaults\.js$/, {
|
|
||||||
search: './adapters/xhr',
|
|
||||||
replace: './adapters/http',
|
|
||||||
}),
|
|
||||||
// remove bindings magic from drivelist
|
|
||||||
replace(
|
|
||||||
/node_modules\/drivelist\/js\/index\.js$/,
|
|
||||||
{
|
|
||||||
search: 'require("bindings");',
|
|
||||||
replace: "require('../build/Release/drivelist.node')",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
search: "bindings('drivelist')",
|
|
||||||
replace: 'bindings',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
replace(
|
|
||||||
/node_modules\/lzma-native\/index\.js$/,
|
|
||||||
// remove node-pre-gyp magic from lzma-native
|
|
||||||
{
|
|
||||||
search: `require('node-gyp-build')(__dirname);`,
|
|
||||||
replace: `require('./prebuilds/${LZMA_BINDINGS_FOLDER}/electron.napi.node')`,
|
|
||||||
},
|
|
||||||
// use regular stream module instead of readable-stream
|
|
||||||
{
|
|
||||||
search: "var stream = require('readable-stream');",
|
|
||||||
replace: "var stream = require('stream');",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// remove node-pre-gyp magic from usb
|
|
||||||
replace(/node_modules\/usb\/dist\/usb\/bindings\.js$/, {
|
|
||||||
search: `require('node-gyp-build')(path_1.join(__dirname, '..', '..'));`,
|
|
||||||
replace: `require('../../prebuilds/${USB_BINDINGS_FOLDER}/${USB_BINDINGS_FILE}')`,
|
|
||||||
}),
|
|
||||||
// remove bindings magic from mountutils
|
|
||||||
replace(/node_modules\/mountutils\/index\.js$/, {
|
|
||||||
search: outdent`
|
|
||||||
require('bindings')({
|
|
||||||
bindings: 'MountUtils',
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
module_root: __dirname
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
})
|
|
||||||
`,
|
|
||||||
replace: "require('./build/Release/MountUtils.node')",
|
|
||||||
}),
|
|
||||||
// remove bindings magic from winusb-driver-generator
|
|
||||||
replace(/node_modules\/winusb-driver-generator\/index\.js$/, {
|
|
||||||
search: outdent`
|
|
||||||
require('bindings')({
|
|
||||||
bindings: 'Generator',
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
module_root: __dirname
|
|
||||||
/* eslint-enable camelcase */
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
replace: "require('./build/Release/Generator.node')",
|
|
||||||
}),
|
|
||||||
replace(/node_modules\/node-raspberrypi-usbboot\/build\/index\.js$/, {
|
|
||||||
search:
|
|
||||||
"return await readFile(Path.join(__dirname, '..', 'blobs', filename));",
|
|
||||||
replace: outdent`
|
|
||||||
const remote = require('@electron/remote');
|
|
||||||
return await readFile(
|
|
||||||
Path.join(
|
|
||||||
// With macOS universal builds, getAppPath() returns the path to an app.asar file containing an index.js file which will
|
|
||||||
// include the app-x64 or app-arm64 folder depending on the arch.
|
|
||||||
// We don't care about the app.asar file, we want the actual folder.
|
|
||||||
remote.app.getAppPath().replace(/\\.asar$/, () => process.platform === 'darwin' ? '-' + process.arch : ''),
|
|
||||||
'generated',
|
|
||||||
__dirname.replace('node_modules', 'modules'),
|
|
||||||
'..',
|
|
||||||
'blobs',
|
|
||||||
filename
|
|
||||||
)
|
|
||||||
);
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
// Copy native modules to generated folder
|
|
||||||
{
|
|
||||||
test: /\.node$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'native-addon-loader',
|
|
||||||
options: { name: renameNodeModules },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.node', '.js', '.json', '.ts', '.tsx'],
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
PnpWebpackPlugin,
|
|
||||||
new SimpleProgressWebpackPlugin({
|
|
||||||
format: process.env.WEBPACK_PROGRESS || 'verbose',
|
|
||||||
}),
|
|
||||||
// Force axios to use http.js, not xhr.js as we need stream support
|
// Force axios to use http.js, not xhr.js as we need stream support
|
||||||
// (its package.json file replaces http with xhr for browser targets).
|
// (its package.json file replaces http with xhr for browser targets).
|
||||||
new NormalModuleReplacementPlugin(
|
new NormalModuleReplacementPlugin(
|
||||||
@@ -330,100 +106,25 @@ const commonConfig = {
|
|||||||
new IgnorePlugin({
|
new IgnorePlugin({
|
||||||
resourceRegExp: /^aws-crt$/,
|
resourceRegExp: /^aws-crt$/,
|
||||||
}),
|
}),
|
||||||
],
|
|
||||||
resolveLoader: {
|
|
||||||
plugins: [PnpWebpackPlugin.moduleLoader(module)],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'generated'),
|
|
||||||
filename: '[name].js',
|
|
||||||
},
|
|
||||||
externals: [
|
|
||||||
// '../package.json' because we are in 'generated'
|
|
||||||
externalPackageJson('../package.json'),
|
|
||||||
// Only exists on windows
|
|
||||||
platformSpecificModule('win32', 'winusb-driver-generator'),
|
|
||||||
// Not needed but required by resin-corvus > os-locale > execa > cross-spawn
|
|
||||||
platformSpecificModule('none', 'spawn-sync'),
|
|
||||||
// Not needed as we replace all requires for it
|
|
||||||
platformSpecificModule('none', 'node-pre-gyp', '{ find: () => {} }'),
|
|
||||||
// Not needed as we replace all requires for it
|
|
||||||
platformSpecificModule('none', 'bindings'),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const guiConfigCopyPatterns = [
|
|
||||||
{
|
|
||||||
from: 'node_modules/node-raspberrypi-usbboot/blobs',
|
|
||||||
to: 'modules/node-raspberrypi-usbboot/blobs',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
// liblzma.dll is required on Windows for lzma-native
|
|
||||||
guiConfigCopyPatterns.push({
|
|
||||||
from: `node_modules/lzma-native/prebuilds/${LZMA_BINDINGS_FOLDER}/liblzma.dll`,
|
|
||||||
to: `modules/lzma-native/prebuilds/${LZMA_BINDINGS_FOLDER_RENAMED}/liblzma.dll`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const guiConfig = {
|
|
||||||
...commonConfig,
|
|
||||||
target: 'electron-renderer',
|
|
||||||
node: {
|
|
||||||
__dirname: true,
|
|
||||||
__filename: true,
|
|
||||||
},
|
|
||||||
entry: {
|
|
||||||
gui: path.join(__dirname, 'lib', 'gui', 'app', 'renderer.ts'),
|
|
||||||
},
|
|
||||||
// entry: path.join(__dirname, 'lib', 'gui', 'app', 'renderer.ts'),
|
|
||||||
plugins: [
|
|
||||||
...commonConfig.plugins,
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{ from: 'lib/gui/app/index.html', to: 'index.html' },
|
|
||||||
// electron-builder doesn't bundle folders named "assets"
|
|
||||||
// See https://github.com/electron-userland/electron-builder/issues/4545
|
|
||||||
{ from: 'assets/icon.png', to: 'media/icon.png' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
// Remove "Download the React DevTools for a better development experience" message
|
// Remove "Download the React DevTools for a better development experience" message
|
||||||
new BannerPlugin({
|
new BannerPlugin({
|
||||||
banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };',
|
banner: '__REACT_DEVTOOLS_GLOBAL_HOOK__ = { isDisabled: true };',
|
||||||
raw: true,
|
raw: true,
|
||||||
}),
|
}),
|
||||||
new CopyPlugin({ patterns: guiConfigCopyPatterns }),
|
|
||||||
],
|
],
|
||||||
};
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'],
|
||||||
const mainConfig = {
|
|
||||||
...commonConfig,
|
|
||||||
target: 'electron-main',
|
|
||||||
node: {
|
|
||||||
__dirname: false,
|
|
||||||
__filename: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const etcherConfig = {
|
export const mainConfig: Configuration = {
|
||||||
...mainConfig,
|
|
||||||
entry: {
|
entry: {
|
||||||
etcher: path.join(__dirname, 'lib', 'gui', 'etcher.ts'),
|
etcher: './lib/gui/etcher.ts',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const childWriterConfig = {
|
|
||||||
...mainConfig,
|
|
||||||
entry: {
|
|
||||||
'child-writer': path.join(
|
|
||||||
__dirname,
|
|
||||||
'lib',
|
|
||||||
'gui',
|
|
||||||
'modules',
|
|
||||||
'child-writer.ts',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default [guiConfig, etcherConfig, childWriterConfig];
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import configs from './webpack.config';
|
|
||||||
import { WebpackOptionsNormalized } from 'webpack';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const [
|
|
||||||
guiConfig,
|
|
||||||
etcherConfig,
|
|
||||||
childWriterConfig,
|
|
||||||
] = (configs as unknown) as WebpackOptionsNormalized[];
|
|
||||||
|
|
||||||
configs.forEach((config) => {
|
|
||||||
config.mode = 'development';
|
|
||||||
// @ts-ignore
|
|
||||||
config.devtool = 'source-map';
|
|
||||||
});
|
|
||||||
|
|
||||||
guiConfig.devServer = {
|
|
||||||
hot: true,
|
|
||||||
port: 3030,
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.copyFileSync('./lib/gui/app/index.dev.html', './generated/index.html');
|
|
||||||
|
|
||||||
export default [guiConfig, etcherConfig, childWriterConfig];
|
|
||||||
Reference in New Issue
Block a user