Compare commits

..

1 Commits

Author SHA1 Message Date
Kyle Harding
2b63fbed03 Remove repo config from flowzone.yml
This functionality is being deprecated in Flowzone.

See: https://github.com/product-os/flowzone/pull/833

Change-type: patch
Signed-off-by: Kyle Harding <kyle@balena.io>
2023-12-19 18:15:38 -05:00
71 changed files with 11241 additions and 17072 deletions

View File

@@ -1,10 +0,0 @@
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 Normal file
View File

@@ -0,0 +1,454 @@
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

View File

@@ -10,11 +10,11 @@ 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
# 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: "18.x" default: "18.x"
VERBOSE: VERBOSE:
type: string type: string
@@ -25,14 +25,14 @@ runs:
using: "composite" using: "composite"
steps: steps:
- name: Download custom source artifact - name: Download custom source artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
path: ${{ runner.temp }} path: ${{ runner.temp }}
- name: Extract custom source artifact - name: Extract custom source artifact
if: runner.os != 'Windows' if: runner.os != 'Windows'
shell: bash shell: pwsh
working-directory: . working-directory: .
run: tar -xf ${{ runner.temp }}/custom.tgz run: tar -xf ${{ runner.temp }}/custom.tgz
@@ -48,151 +48,122 @@ runs:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Install host dependencies - name: Install yq
if: runner.os == 'Linux' shell: bash --noprofile --norc -eo pipefail -x {0}
shell: bash run: choco install yq
run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm if: runner.os == 'Windows'
- 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://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof # https://github.com/Apple-Actions/import-codesign-certs
- name: Import Apple code signing certificate - name: Import Apple code signing certificate
if: runner.os == 'macOS' if: runner.os == 'macOS'
shell: bash uses: apple-actions/import-codesign-certs@v1
run: | with:
KEY_CHAIN=build.keychain p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
CERTIFICATE_P12=certificate.p12 p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
# 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:SM_CLIENT_CERT_FILE_B64 Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12 certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/certificate.pfx
Remove-Item -path ${{ runner.temp }} -include certificate.base64 Remove-Item -path ${{ runner.temp }} -include certificate.base64
echo "certFilePath=${{ runner.temp }}/Certificate_pkcs12.p12" >> $GITHUB_OUTPUT Import-PfxCertificate `
-FilePath ${{ runner.temp }}/certificate.pfx `
-CertStoreLocation Cert:\CurrentUser\My `
-Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
Remove-Item -path ${{ runner.temp }} -include certificate.pfx
env: env:
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }} WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
# ... or refactor (e.g.) https://github.com/samuelmeuli/action-electron-builder
# https://github.com/product-os/scripts/tree/master/electron
# https://github.com/product-os/scripts/tree/master/shared
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
- name: Package release - name: Package release
shell: bash id: package_release
# IMPORTANT: before making changes to this step please consult @engineering in balena's chat. shell: bash --noprofile --norc -eo pipefail -x {0}
run: | run: |
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled set -ea
# 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)"
HOST_ARCH="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')" ARCHITECTURE_FLAGS="--${ELECTRON_BUILDER_ARCHITECTURE}"
if [[ "${RUNNER_OS}" == Linux ]]; then if [[ $runner_os =~ linux ]]; then
PLATFORM=Linux ELECTRON_BUILDER_OS='--linux'
SHA256SUM_BIN=sha256sum TARGETS="$(yq e .linux.target[] electron-builder.yml)"
elif [[ "${RUNNER_OS}" == macOS ]]; then elif [[ $runner_os =~ darwin|macos|osx ]]; then
PLATFORM=Darwin CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
SHA256SUM_BIN='shasum -a 256' CSC_KEYCHAIN=signing_temp
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
ELECTRON_BUILDER_OS='--mac'
TARGETS="$(yq e .mac.target[] electron-builder.yml)"
elif [[ "${RUNNER_OS}" == Windows ]]; then elif [[ $runner_os =~ windows|win ]]; then
PLATFORM=Windows ARCHITECTURE_FLAGS="--ia32 ${ARCHITECTURE_FLAGS}"
SHA256SUM_BIN=sha256sum CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
CSC_LINK=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
# Install DigiCert Signing Manager Tools ELECTRON_BUILDER_OS='--win'
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \ TARGETS="$(yq e .win.target[] electron-builder.yml)"
-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
echo "ERROR: unexpected runner OS: ${RUNNER_OS}"
exit 1 exit 1
fi fi
# Currently, we can only build for the host architecture. npm link electron-builder
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:
# ensure we sign the artifacts # Apple notarization (afterSignHook.js)
NODE_ENV: production XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }}
# 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 }}
XCODE_APP_LOADER_TEAM_ID: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_TEAM_ID }} # https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks
# Windows signing # https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks
SM_CLIENT_CERT_PASSWORD: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }} CSC_FOR_PULL_REQUEST: true
SM_CLIENT_CERT_FILE: '${{ runner.temp }}\Certificate_pkcs12.p12'
SM_HOST: ${{ fromJSON(inputs.secrets).SM_HOST }} # https://www.electron.build/auto-update.html#staged-rollouts
SM_API_KEY: ${{ fromJSON(inputs.secrets).SM_API_KEY }} - name: Configure staged rollout(s)
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }} shell: bash --noprofile --norc -eo pipefail -x {0}
TIMESTAMP_SERVER: http://timestamp.digicert.com run: |
set -ea
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
percentage="$(cat < repo.yml | yq e .triggerNotification.stagingPercentage)"
find dist -type f -maxdepth 1 \
-name "latest*.yml" \
-exec yq -i e .version=\"${{ steps.package_release.outputs.version }}\" {} \;
find dist -type f -maxdepth 1 \
-name "latest*.yml" \
-exec yq -i e .stagingPercentage=\"$percentage\" {} \;
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }} name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}
path: dist path: dist
retention-days: 1 retention-days: 1
if-no-files-found: error

View File

@@ -12,7 +12,7 @@ inputs:
# --- custom environment # --- custom environment
NODE_VERSION: NODE_VERSION:
type: string type: string
default: "18.18" default: "16.x"
VERBOSE: VERBOSE:
type: string type: string
default: "true" default: "true"
@@ -28,44 +28,27 @@ runs:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Install host dependencies
if: runner.os == 'Linux'
shell: bash
run: |
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
- 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'
- name: Test release - name: Test release
shell: bash shell: bash --noprofile --norc -eo pipefail -x {0}
run: | run: |
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled set -ea
# 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:]')"
npm run flowzone-preinstall-${runner_os}
npm ci npm ci
npm run lint npm run build
npm run package npm run test-${runner_os}
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
if: runner.os != 'Windows' if: runner.os != 'Windows'
shell: bash shell: pwsh
run: tar -acf ${{ runner.temp }}/custom.tgz . run: tar -acf ${{ runner.temp }}/custom.tgz .
- name: Compress custom source - name: Compress custom source
@@ -74,8 +57,8 @@ runs:
run: C:\"Program Files"\Git\usr\bin\tar.exe --force-local -acf ${{ runner.temp }}\custom.tgz . 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@v4 uses: actions/upload-artifact@v3
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}
path: ${{ runner.temp }}/custom.tgz path: ${{ runner.temp }}/custom.tgz
retention-days: 1 retention-days: 1

View File

@@ -18,7 +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:
custom_runs_on: '[["ubuntu-20.04"],["windows-2019"],["macos-12"],["macos-latest-xlarge"]]' tests_run_on: '["ubuntu-20.04","macos-latest","windows-2019"]'
restrict_custom_actions: false restrict_custom_actions: false
github_prerelease: true github_prerelease: true
cloudflare_website: "etcher" cloudflare_website: "etcher"

View File

@@ -6,9 +6,8 @@ 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@v2 - uses: vedantmgoyal2009/winget-releaser@v1
with: with:
identifier: Balena.Etcher identifier: Balena.Etcher
# matches something like "balenaEtcher-1.19.0.Setup.exe" installers-regex: 'balenaEtcher-Setup.*.exe$'
installers-regex: 'balenaEtcher-[\d.-]+\.Setup.exe$'
token: ${{ secrets.WINGET_PAT }} token: ${{ secrets.WINGET_PAT }}

113
.gitignore vendored
View File

@@ -1,103 +1,41 @@
# -- 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
# nyc test coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.nyc_output .grunt
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html) # Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release /build
# Dependency directories # Generated files
node_modules/ /generated
jspm_packages/ /binaries
# TypeScript v1 declaration files # Dependency directory
typings/ # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
# TypeScript cache # Compiled Etcher releases
*.tsbuildinfo /dist
# 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
@@ -107,17 +45,16 @@ dist/
*.crt *.crt
*.pem *.pem
# Secrets # OSX files
.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 Normal file
View File

@@ -0,0 +1,4 @@
[submodule "scripts/resin"]
path = scripts/resin
url = https://github.com/balena-io/scripts.git
branch = master

2
.nvmrc
View File

@@ -1 +1 @@
18 16

View File

@@ -1,6 +0,0 @@
const fs = require("fs");
const path = require("path");
module.exports = JSON.parse(
fs.readFileSync(path.join(__dirname, "node_modules", "@balena", "lint", "config", ".prettierrc"), "utf8"),
);

View File

@@ -1,230 +1,3 @@
- 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: - commits:
- subject: "patch: upgrade to electron 25" - subject: "patch: upgrade to electron 25"
hash: f38bca290fe26121bed58d1131265e1aa350ddb5 hash: f38bca290fe26121bed58d1131265e1aa350ddb5
@@ -591,33 +364,20 @@
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 Updates etcher-sdk and switches the redundant aws4-axios dependency to just axios.
just axios.
Also changed bundler to stop trying to bundle wasm files — they must be 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.
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 1. The update to etcher-sdk@8 where some dependency fixes and updates took place
took place
2. The downgrade and pinning of "electron-rebuild" to v3.2.3 until were 2. The downgrade and pinning of "electron-rebuild" to v3.2.3 until were 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
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 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. Were not currently using the “assume role” functionality (AFAIU) of aws4-axios and we dont care that its not found, so force webpack to ignore the import. See https://github.com/aws/aws-sdk-js-v3/issues/3025
(ultimately) `aws4-axios` which is used by etcher-sdk and does a runtime
check to its availability. Were not currently using the “assume role”
functionality (AFAIU) of aws4-axios and we dont care that its 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
@@ -897,8 +657,7 @@
body: > body: >
Optimized several translations. Optimized several translations.
This commit itself is only a patch, but as a pull request must have at This commit itself is only a patch, but as a pull request must have at least one commit with a change-type.
least one commit with a change-type.
footer: footer:
Change-Type: minor Change-Type: minor
change-type: minor change-type: minor
@@ -986,8 +745,7 @@
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 We cannot use `latest` as the glibc version will cause issue with older ubuntu version.
ubuntu version.
footer: {} footer: {}
author: Edwin Joassart author: Edwin Joassart
nested: [] nested: []
@@ -2518,8 +2276,7 @@
reloads without reloading the whole electron app. reloads without reloading the whole electron app.
This patch also runs the development environment in development mode, This patch also runs the development environment in development mode, which is much, much faster on builds and rebuilds.
which is much, much faster on builds and rebuilds.
footer: {} footer: {}
author: Zane Hitchcox author: Zane Hitchcox
nested: [] nested: []
@@ -2924,11 +2681,9 @@
exception 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 - Fixes memory leak because memory allocated with calloc()
calloc()
in WCharToUtf8() was not being freed anywhere in WCharToUtf8() was not being freed anywhere
- Fixes undefined behavior because GetEnumeratorName() - Fixes undefined behavior because GetEnumeratorName() returns
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.
@@ -4850,8 +4605,7 @@
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) In any case, all of the macOS (not "Mac OS" for some years now) documentation refers to the "Opt" key.
documentation refers to the "Opt" key.
- hash: ea11f179542794294f773f503d83dad3a10cda56 - hash: ea11f179542794294f773f503d83dad3a10cda56
author: Tom author: Tom
footers: footers:
@@ -5015,8 +4769,7 @@
Changes the documentation to update the disktutil command which didn't Changes the documentation to update the disktutil command which didn't
fix 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 This way it rewrites the drive into a FAT32 partition editable in Unix/Windows.
Unix/Windows.
- hash: b3f25c176b1bdb487d1a7bf111d7f170fe008842 - hash: b3f25c176b1bdb487d1a7bf111d7f170fe008842
author: Lorenzo Alberto Maria Ambrosi author: Lorenzo Alberto Maria Ambrosi
footers: footers:
@@ -7716,8 +7469,7 @@
performance improvement performance improvement
- Make Breadcrumbs and Icon pure components to stop frequent - Make Breadcrumbs and Icon pure components to stop frequent re-rendering
re-rendering
- Initial support for array constraints - Initial support for array constraints
@@ -7848,11 +7600,9 @@
the `ETCHER_EXPERIMENTAL_FILE_PICKER` environment variable. Further the `ETCHER_EXPERIMENTAL_FILE_PICKER` environment variable. Further
customisation can be done with the customisation can be done with the `ETCHER_FILE_BROWSER_CONSTRAIN_FOLDER`
`ETCHER_FILE_BROWSER_CONSTRAIN_FOLDER`
variable that takes a path and allows one to constrain the file-picker variable that takes a path and allows one to constrain the file-picker to
to
a folder. a folder.
- hash: 687e0b563b0dc3619ece4ce49d353d5838a21ff6 - hash: 687e0b563b0dc3619ece4ce49d353d5838a21ff6
@@ -7951,13 +7701,11 @@
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 In the case of the home directory, the config file is `$HOME/.config/etcher/config.json`,
`$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 The defined settings are merged with localStorage settings, and preceding
preceding
configuration files. configuration files.
@@ -8247,8 +7995,7 @@
`_.isError()` returns `true` for anything that has a `name` and `_.isError()` returns `true` for anything that has a `name` and
`message` `message`
property, causing the check here to always keep the plain object as property, causing the check here to always keep the plain object as error.
error.
- hash: 355373f24df6be0989fad9429c2230166b33a3bf - hash: 355373f24df6be0989fad9429c2230166b33a3bf
author: Jonas Hermsmeier author: Jonas Hermsmeier
footers: footers:
@@ -8265,8 +8012,7 @@
This fixes a ReferenceError that could occur when the DeviceNode was This fixes a ReferenceError that could occur when the DeviceNode was
null, null,
as well as devices being null when run after the system recovers from as well as devices being null when run after the system recovers from sleep / standby.
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"
@@ -8427,8 +8173,7 @@
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 the partition table with `win-drive-clean`, which does so via DeviceIoControl.
DeviceIoControl.
- hash: abf2dc3efcf214a68c0b0e329d57a3f66bb5d342 - hash: abf2dc3efcf214a68c0b0e329d57a3f66bb5d342
author: Benedict Aas author: Benedict Aas
footers: footers:
@@ -8535,18 +8280,15 @@
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 & as the keyboard shortcuts have changed to their defaults on Linux & Windows
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 Further, the editor config is updated to allow trailing spaces in Markdown
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 order to avoid people not putting whitespace in between, causing the formatting
formatting
to not be parsed properly. to not be parsed properly.
- hash: 3dd646485fa34437ac3adb3caa5a594d439f1f68 - hash: 3dd646485fa34437ac3adb3caa5a594d439f1f68
@@ -8630,8 +8372,7 @@
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 property to ensure the correct application name is displayed when packaged.
packaged.
- hash: cf340f48c3582f3e96f7b2dc16c11f44b7661363 - hash: cf340f48c3582f3e96f7b2dc16c11f44b7661363
author: Jonas Hermsmeier author: Jonas Hermsmeier
footers: footers:
@@ -8807,8 +8548,7 @@
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 the CLI, which was caused by incompatibility of two different `drivelist` versions
`drivelist` versions
- hash: bde1e32e29ae75ccecf7fc3bc1b03efd6e4f67b8 - hash: bde1e32e29ae75ccecf7fc3bc1b03efd6e4f67b8
author: Jonas Hermsmeier author: Jonas Hermsmeier
footers: footers:
@@ -9109,8 +8849,7 @@
We remove a piece of code checking whether `_.keys` returns any We remove a piece of code checking whether `_.keys` returns any
non-string non-string
values in its array, but per the Lodash documentation `_.keys` always values in its array, but per the Lodash documentation `_.keys` always returns an
returns an
array of strings. array of strings.
- hash: 83528df18be32bfe62d3e9e4578101077769a7cf - hash: 83528df18be32bfe62d3e9e4578101077769a7cf
@@ -9366,8 +9105,7 @@
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 (Visual C/C++ 2012 / 2015 Redistributables), we ignore errors when loading
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
@@ -9452,8 +9190,7 @@
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 and remove all `from` fields to avoid inconsistent diffs across platforms,
platforms,
environments and installs when shrinkwrapping anew. environments and installs when shrinkwrapping anew.
- hash: 619051a4b0cd8995e31838f221386b9b44e6ffc8 - hash: 619051a4b0cd8995e31838f221386b9b44e6ffc8
@@ -9865,8 +9602,7 @@
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 error by installing pip==9.0.1 directly from the pypi.python.org/packages/
pypi.python.org/packages/
- hash: c8b2b652354029cedceda2637bed13fee65f8587 - hash: c8b2b652354029cedceda2637bed13fee65f8587
author: Juan Cruz Viotti author: Juan Cruz Viotti
footers: footers:
@@ -9918,11 +9654,9 @@
WARNING: Binary file: lib/blobs/usbboot/raspberrypi/bootcode.bin WARNING: Binary file: lib/blobs/usbboot/raspberrypi/bootcode.bin
WARNING: Binary file: WARNING: Binary file: tests/image-stream/data/unrecognized/xz-without-extension
tests/image-stream/data/unrecognized/xz-without-extension
WARNING: Binary file: WARNING: Binary file: tests/image-stream/data/unrecognized/xz-with-invalid-extension.foo
tests/image-stream/data/unrecognized/xz-with-invalid-extension.foo
``` ```
- hash: f4e0121639d8f2cbcc15b6577ec15d7ecbab7e71 - hash: f4e0121639d8f2cbcc15b6577ec15d7ecbab7e71
@@ -11655,8 +11389,7 @@
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 > Code signing uses extended attributes to store signatures in non-Mach-O
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
@@ -11668,8 +11401,7 @@
> 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 > up your signed code in a read-write disk image (DMG) file before signing
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
@@ -11739,32 +11471,23 @@
Unhandled rejection TypeError: Cannot read property '0' of undefined Unhandled rejection TypeError: Cannot read property '0' of undefined
at Number.indexedGetter at Number.indexedGetter (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/call_get.js:106:15)
at Number.tryCatcher at Number.tryCatcher (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler at Promise._settlePromiseFromHandler (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:503:31)
at Promise._settlePromise at Promise._settlePromise (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:560:18)
at Promise._settlePromise0 at Promise._settlePromise0 (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:605:10)
at Promise._settlePromises at Promise._settlePromises (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/promise.js:684:18)
at Async._drainQueue at Async._drainQueue (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:126:16)
at Async._drainQueues at Async._drainQueues (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
(/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:136:10)
at Immediate.Async.drainQueues [as _onImmediate] at Immediate.Async.drainQueues [as _onImmediate] (/home/parallels/Projects/etcher/node_modules/bluebird/js/release/async.js:16:14)
(/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
@@ -11780,8 +11503,7 @@
body: >- body: >-
From the documentation: From the documentation:
> `useContentSize` Boolean - The `width` and `height` would be used as > `useContentSize` Boolean - The `width` and `height` would be used as web
web
> pages size, which means the actual windows size will include window > pages size, which means the actual windows size will include window
@@ -12598,8 +12320,7 @@
]); ]);
From From https://medium.com/@kentcdodds/how-to-distribute-your-angularjs-module-e04d4dd58ddc#.yqg2zo8im
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:
@@ -12851,8 +12572,7 @@
body: >- body: >-
Electron no longer supports 10.8. Electron no longer supports 10.8.
See See http://electron.atom.io/docs/v0.37.5/tutorial/supported-platforms/#os-x
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

View File

@@ -3,56 +3,6 @@
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.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 # v1.18.13
## (2023-10-16) ## (2023-10-16)

152
Makefile Normal file
View File

@@ -0,0 +1,152 @@
# ---------------------------------------------------------------------
# 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

View File

@@ -17,9 +17,13 @@ was written correctly, and much more. It can also directly flash Raspberry Pi de
## Supported Operating Systems ## Supported Operating Systems
- Linux; most distros; Intel 64-bit. - Linux (most distros)
- Windows 10 and later; Intel 64-bit. - macOS 10.10 (Yosemite) and later
- macOS 10.13 (High Sierra) and later; both Intel and Apple Silicon. - Microsoft Windows 7 and later
**Note**: Etcher will run on any platform officially supported by
[Electron][electron]. Read more in their
[documentation][electron-supported-platforms].
## Installers ## Installers

31
afterPack.js Normal file
View File

@@ -0,0 +1,31 @@
'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])
}

25
afterSignHook.js Normal file
View File

@@ -0,0 +1,25 @@
'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

4
dev-app-update.yml Normal file
View File

@@ -0,0 +1,4 @@
owner: balena-io
repo: etcher
provider: github
updaterCacheDirName: balena-etcher-updater

9
dictionary.txt Normal file
View File

@@ -0,0 +1,9 @@
boolen->boolean
aknowledge->acknowledge
seleted->selected
reming->remind
locl->local
subsribe->subscribe
unsubsribe->unsubscribe
calcluate->calculate
dictionaty->dictionary

View File

@@ -75,7 +75,9 @@ cd etcher
#### GUI #### GUI
```sh ```sh
# Build and start application # Build the GUI
npm run webpack #or npm run build
# Start Electron
npm start npm start
``` ```
@@ -102,6 +104,7 @@ 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
@@ -110,8 +113,7 @@ process.
Updating a dependency Updating a dependency
--------------------- ---------------------
- Install new version of dependency using npm - Commit *both* `package.json` and `package-lock.json`.
- Commit *both* `package.json` and `npm-shrinkwrap.json`.
Diffing Binaries Diffing Binaries
---------------- ----------------

View File

@@ -58,18 +58,25 @@ export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
##### Clean dist folder ##### Clean dist folder
Delete `.webpack` and `out/`. **NOTE:** Make sure to adjust the path as necessary (here the Etcher repository has been cloned to `/home/$USER/code/etcher`)
##### 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.
Etcher is built with electron-forge. Run: `electron-builder` is used to create the packaged application.
``` #### Mac OS
npm run make
```
Our CI will appropriately sign artifacts for macOS and some Windows targets. **ATTENTION:** For production releases you'll need the code-signing key,
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

View File

@@ -36,17 +36,14 @@ employee by asking for it from the relevant people.
Packaging Packaging
--------- ---------
Run the following command on each platform: The resulting installers will be saved to `dist/out`.
Run the following commands on all platforms with the right arguments:
```sh ```sh
npm run make ./node_modules/electron-builder build <...>
``` ```
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
--------------------- ---------------------

110
electron-builder.yml Normal file
View File

@@ -0,0 +1,110 @@
# 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

View File

@@ -1,157 +0,0 @@
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;

View File

@@ -1,168 +0,0 @@
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',
`node18-${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);
},
};
}
}

View File

@@ -38,6 +38,7 @@ 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 { promises } from 'dns';
import { SourceMetadata } from '../../shared/typings/source-selector'; import { SourceMetadata } from '../../shared/typings/source-selector';
window.addEventListener( window.addEventListener(
@@ -134,7 +135,7 @@ function setDrives(drives: Dictionary<DrivelistDrive>) {
} }
} }
// Spawning the child process without privileges to get the drives list // Spwaning the child process without privileges to get the drives list
// TODO: clean up this mess of exports // TODO: clean up this mess of exports
export let requestMetadata: any; export let requestMetadata: any;

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.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 '../../../../shared/typings/source-selector'; import { SourceMetadata } from '../source-selector/source-selector';
import { middleEllipsis } from '../../utils/middle-ellipsis'; import { middleEllipsis } from '../../utils/middle-ellipsis';
import * as i18next from 'i18next'; import * as i18next from 'i18next';
@@ -310,17 +310,9 @@ export class DriveSelector extends React.Component<
case compatibility.system(): case compatibility.system():
return warning.systemDrive(); return warning.systemDrive();
case compatibility.tooSmall(): case compatibility.tooSmall():
return warning.tooSmall( const size =
{ this.state.image?.recommendedDriveSize || this.state.image?.size || 0;
size: return warning.tooSmall({ size }, drive);
this.state.image?.recommendedDriveSize ||
this.state.image?.size ||
0,
},
drive,
);
default:
return '';
} }
} }
@@ -436,10 +428,11 @@ export class DriveSelector extends React.Component<
) : ( ) : (
<> <>
<DrivesTable <DrivesTable
refFn={() => { refFn={(t) => {
// noop if (t !== null) {
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}
@@ -449,10 +442,7 @@ export class DriveSelector extends React.Component<
isDrivelistDrive(row) && row.isSystem ? ['system'] : [] isDrivelistDrive(row) && row.isSystem ? ['system'] : []
} }
rowKey="displayName" rowKey="displayName"
onCheck={(rows) => { onCheck={(rows: Drive[]) => {
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) {

View File

@@ -1,10 +1,11 @@
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.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 prettyBytes from 'pretty-bytes'; import * as prettyBytes from 'pretty-bytes';
import { DriveWithWarnings } from '../../pages/main/Flash'; import { DriveWithWarnings } from '../../pages/main/Flash';
import * as i18next from 'i18next'; import * as i18next from 'i18next';

View File

@@ -15,8 +15,9 @@
*/ */
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/circle-check.svg'; import CheckCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/check-circle.svg';
import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-xmark.svg'; import TimesCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/times-circle.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';

View File

@@ -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, Txt } from 'rendition'; import { Box, Checkbox, Flex, TextWithCopy, 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,9 +61,7 @@ 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>
<Txt code copy={props.value}> <TextWithCopy code text={props.value} copy={props.value} />
{props.value}{' '}
</Txt>
</Box> </Box>
); );

View File

@@ -17,7 +17,7 @@
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/triangle-exclamation.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.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 { ipcRenderer, IpcRendererEvent } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
@@ -388,9 +388,10 @@ 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: () => {
// noop cancelled = true;
}, },
promise: (async () => { promise: (async () => {
const sourcePath = isString(selected) ? selected : selected.device; const sourcePath = isString(selected) ? selected : selected.device;
@@ -518,8 +519,8 @@ 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.item(0); const [file] = event.dataTransfer.files;
if (file != null) { if (file) {
await this.selectSource(file.path, 'File').promise; await this.selectSource(file.path, 'File').promise;
} }
} }
@@ -580,7 +581,7 @@ export class SourceSelector extends React.Component<
imageLoading, imageLoading,
} = this.state; } = this.state;
const selectionImage = selectionState.getImage(); const selectionImage = selectionState.getImage();
let image = let image: SourceMetadata | DrivelistDrive =
selectionImage !== undefined ? selectionImage : ({} as SourceMetadata); selectionImage !== undefined ? selectionImage : ({} as SourceMetadata);
image = image.drive ?? image; image = image.drive ?? image;
@@ -683,7 +684,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)',
}} }}
title={ titleElement={
<span> <span>
<ExclamationTriangleSvg fill="#fca321" height="1em" />{' '} <ExclamationTriangleSvg fill="#fca321" height="1em" />{' '}
<span>{this.state.warning.title}</span> <span>{this.state.warning.title}</span>

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/triangle-exclamation.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg';
import * as React from 'react'; import * as React from 'react';
import { Flex, FlexProps, Txt } from 'rendition'; import { Flex, FlexProps, Txt } from 'rendition';

View File

@@ -15,15 +15,15 @@
*/ */
@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;
} }
@@ -52,7 +52,7 @@ 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;
} }
@@ -62,5 +62,5 @@ input[type='checkbox'] + div {
} }
#rendition-tooltip-root > div { #rendition-tooltip-root > div {
font-family: 'SourceSansPro', sans-serif; font-family: "SourceSansPro", sans-serif;
} }

View File

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

View File

@@ -3,8 +3,10 @@
<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>

View File

@@ -47,7 +47,13 @@ export function isFlashing(): boolean {
*/ */
export function setFlashingFlag() { export function setFlashingFlag() {
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods // see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
electron.ipcRenderer.send('disable-screensaver'); try {
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: {},
@@ -70,8 +76,7 @@ export function unsetFlashingFlag(results: {
data: results, data: results,
}); });
// see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods // see https://github.com/balenablocks/balena-electron-env/blob/4fce9c461f294d4a768db8f247eea6f75d7b08b0/README.md#remote-methods
electron.ipcRenderer.invoke('enable-screensaver');
electron.ipcRenderer.send('enable-screensaver');
} }
export function setDevicePaths(devicePaths: string[]) { export function setDevicePaths(devicePaths: string[]) {

View File

@@ -43,7 +43,7 @@ function blink(t: number) {
return Math.floor(t) % 2; return Math.floor(t) % 2;
} }
function one() { function one(_t: number) {
return 1; return 1;
} }

View File

@@ -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 (!Object.prototype.hasOwnProperty.call(obj, i)) { if (!obj.hasOwnProperty(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 (!Object.prototype.hasOwnProperty.call(flatObject, x)) { if (!flatObject.hasOwnProperty(x)) {
continue; continue;
} }

View File

@@ -18,6 +18,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as packageJSON from '../../../../package.json'; import * as packageJSON from '../../../../package.json';
import * as permissions from '../../../shared/permissions'; import * as permissions from '../../../shared/permissions';
import { getAppPath } from '../../../shared/get-app-path';
import * as errors from '../../../shared/errors'; import * as errors from '../../../shared/errors';
const THREADS_PER_CPU = 16; const THREADS_PER_CPU = 16;
@@ -26,8 +27,8 @@ const THREADS_PER_CPU = 16;
// the stdout maxBuffer size to be exceeded when flashing // the stdout maxBuffer size to be exceeded when flashing
ipc.config.silent = true; ipc.config.silent = true;
async function writerArgv(): Promise<string[]> { function writerArgv(): string[] {
let entryPoint = await window.etcher.getEtcherUtilPath(); let entryPoint = path.join(getAppPath(), 'generated', 'etcher-util');
// AppImages run over FUSE, so the files inside the mount point // AppImages run over FUSE, so the files inside the mount point
// can only be accessed by the user that mounted the AppImage. // can only be accessed by the user that mounted the AppImage.
// This means we can't re-spawn Etcher as root from the same // This means we can't re-spawn Etcher as root from the same
@@ -74,7 +75,7 @@ async function spawnChild({
IPC_SERVER_ID: string; IPC_SERVER_ID: string;
IPC_SOCKET_ROOT: string; IPC_SOCKET_ROOT: string;
}) { }) {
const argv = await writerArgv(); const argv = writerArgv();
const env = writerEnv(IPC_CLIENT_ID, IPC_SERVER_ID, IPC_SOCKET_ROOT); const env = writerEnv(IPC_CLIENT_ID, IPC_SERVER_ID, IPC_SOCKET_ROOT);
if (withPrivileges) { if (withPrivileges) {
return await permissions.elevateCommand(argv, { return await permissions.elevateCommand(argv, {
@@ -112,10 +113,10 @@ function startApiAndSpawnChild({
// server/client has a different name. // server/client has a different name.
const IPC_SERVER_ID = `etcher-server-${process.pid}-${Date.now()}-${ const IPC_SERVER_ID = `etcher-server-${process.pid}-${Date.now()}-${
withPrivileges ? 'privileged' : 'unprivileged' withPrivileges ? 'privileged' : 'unprivileged'
}`; }}}`;
const IPC_CLIENT_ID = `etcher-client-${process.pid}-${Date.now()}-${ const IPC_CLIENT_ID = `etcher-client-${process.pid}-${Date.now()}-${
withPrivileges ? 'privileged' : 'unprivileged' withPrivileges ? 'privileged' : 'unprivileged'
}`; }}`;
const IPC_SOCKET_ROOT = path.join( const IPC_SOCKET_ROOT = path.join(
process.env.XDG_RUNTIME_DIR || os.tmpdir(), process.env.XDG_RUNTIME_DIR || os.tmpdir(),

View File

@@ -25,6 +25,7 @@ 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'; import { startApiAndSpawnChild } from './api';
import { terminateScanningServer } from '../app';
/** /**
* @summary Handle a flash error and log it to analytics * @summary Handle a flash error and log it to analytics
@@ -80,13 +81,7 @@ async function performWrite(
console.log({ image, drives }); console.log({ image, drives });
// Spawn the child process with privileges and wait for the connection to be made return await new Promise(async (resolve, reject) => {
const { emit, registerHandler, terminateServer } =
await startApiAndSpawnChild({
withPrivileges: true,
});
return await new Promise((resolve, reject) => {
const flashResults: FlashResults = {}; const flashResults: FlashResults = {};
const analyticsData = { const analyticsData = {
@@ -97,7 +92,7 @@ async function performWrite(
flashInstanceUuid: flashState.getFlashUuid(), flashInstanceUuid: flashState.getFlashUuid(),
}; };
const onFail = ({ device, error }: { device: any; error: any }) => { const onFail = ({ device, error }) => {
console.log('fail event'); console.log('fail event');
console.log(device); console.log(device);
console.log(error); console.log(error);
@@ -108,7 +103,7 @@ async function performWrite(
finish(); finish();
}; };
const onDone = (event: any) => { const onDone = (event) => {
console.log('done event'); 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 }) => {
@@ -135,7 +130,7 @@ async function performWrite(
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 // Catch unexepected situation
if ( if (
!flashResults.cancelled && !flashResults.cancelled &&
!flashResults.skip && !flashResults.skip &&
@@ -156,6 +151,12 @@ async function performWrite(
resolve(flashResults); resolve(flashResults);
}; };
// Spawn the child process with privileges and wait for the connection to be made
const { emit, registerHandler, terminateServer } =
await startApiAndSpawnChild({
withPrivileges: true,
});
registerHandler('state', onProgress); registerHandler('state', onProgress);
registerHandler('fail', onFail); registerHandler('fail', onFail);
registerHandler('done', onDone); registerHandler('done', onDone);
@@ -165,15 +166,15 @@ async function performWrite(
cancelEmitter = (cancelStatus: string) => emit(cancelStatus); cancelEmitter = (cancelStatus: string) => emit(cancelStatus);
// Now that we know we're connected we can instruct the child process to start the write // Now that we know we're connected we can instruct the child process to start the write
const parameters = { const paramaters = {
image, image,
destinations: drives, destinations: drives,
SourceType: image.SourceType, SourceType: image.SourceType,
autoBlockmapping, autoBlockmapping,
decompressFirst, decompressFirst,
}; };
console.log('params', parameters); console.log('params', paramaters);
emit('write', parameters); emit('write', paramaters);
}); });
// The process continue in the event handler // The process continue in the event handler

View File

@@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/gear.svg'; import CogSvg from '@fortawesome/fontawesome-free/svgs/solid/cog.svg';
import QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/circle-question.svg'; import QuestionCircleSvg from '@fortawesome/fontawesome-free/svgs/solid/question-circle.svg';
import * as path from 'path'; import * as path from 'path';
import * as prettyBytes from 'pretty-bytes'; import * as prettyBytes from 'pretty-bytes';
@@ -116,10 +116,10 @@ interface MainPageState {
} }
export class MainPage extends React.Component< export class MainPage extends React.Component<
object, {},
MainPageState & MainPageStateFromStore MainPageState & MainPageStateFromStore
> { > {
constructor(props: object) { constructor(props: {}) {
super(props); super(props);
this.state = { this.state = {
current: 'main', current: 'main',

View File

@@ -1,12 +0,0 @@
// 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;

View File

@@ -6,4 +6,10 @@ import { ipcRenderer } from 'electron';
ipcRenderer.send('change-lng', langParser()); ipcRenderer.send('change-lng', langParser());
if (module.hot) {
module.hot.accept('./app', () => {
main();
});
}
main(); main();

View File

@@ -14,6 +14,7 @@
* 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,
@@ -112,25 +113,14 @@ export const DetailsText = (props: FlexProps) => (
const modalFooterShadowCss = css` const modalFooterShadowCss = css`
overflow: auto; overflow: auto;
background: background: 0, linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%, 0,
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: background-size: 100% 40px, 100% 40px, 100% 8px, 100% 8px;
100% 40px,
100% 40px,
100% 8px,
100% 8px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-color: white; background-color: white;
background-size: background-size: 100% 40px, 100% 40px, 100% 8px, 100% 8px;
100% 40px,
100% 40px,
100% 8px,
100% 8px;
background-attachment: local, local, scroll, scroll; background-attachment: local, local, scroll, scroll;
`; `;
@@ -246,15 +236,16 @@ export interface GenericTableProps<T> extends BaseTableProps<T> {
showWarnings?: boolean; showWarnings?: boolean;
} }
function GenericTable<T>( const GenericTable: <T>(
props: GenericTableProps<T>, props: GenericTableProps<T>,
): React.ReactElement<GenericTableProps<T>> { ) => React.ReactElement<GenericTableProps<T>> = <T extends {}>({
return ( refFn,
...props
}: GenericTableProps<T>) => (
<div> <div>
<BaseTable<T> ref={props.refFn} {...props} /> <BaseTable<T> ref={refFn} {...props} />
</div> </div>
); );
}
function StyledTable<T>() { function StyledTable<T>() {
return styled((props: GenericTableProps<T>) => ( return styled((props: GenericTableProps<T>) => (
@@ -293,6 +284,7 @@ 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 {
@@ -327,7 +319,7 @@ function StyledTable<T>() {
`; `;
} }
export const Table = <T extends object>(props: GenericTableProps<T>) => { export const Table = <T extends {}>(props: GenericTableProps<T>) => {
const TypedStyledFunctional = StyledTable<T>(); const TypedStyledFunctional = StyledTable<T>();
return <TypedStyledFunctional {...props} />; return <TypedStyledFunctional {...props} />;
}; };

View File

@@ -14,12 +14,6 @@
* 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';
@@ -40,8 +34,6 @@ 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'];
@@ -146,6 +138,14 @@ 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,7 +176,6 @@ async function createMainWindow() {
contextIsolation: false, contextIsolation: false,
webviewTag: true, webviewTag: true,
zoomFactor: width / defaultWidth, zoomFactor: width / defaultWidth,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}, },
}); });
@@ -196,11 +195,17 @@ 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: any) => { mainWindow.webContents.on('will-navigate', (event) => {
event.preventDefault(); event.preventDefault();
}); });
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY); mainWindow.loadURL(
`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);
@@ -236,20 +241,6 @@ 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();
@@ -281,7 +272,7 @@ async function main(): Promise<void> {
const webview = electron.webContents.fromId(id); const webview = electron.webContents.fromId(id);
// Open link in browser if it's opened as a 'foreground-tab' // Open link in browser if it's opened as a 'foreground-tab'
webview!.setWindowOpenHandler((event) => { webview.setWindowOpenHandler((event) => {
const url = new URL(event.url); const url = new URL(event.url);
if ( if (
(url.protocol === 'http:' || url.protocol === 'https:') && (url.protocol === 'http:' || url.protocol === 'https:') &&
@@ -296,13 +287,6 @@ async function main(): Promise<void> {
}); });
} }
} }
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
// tslint:disable-next-line:no-var-requires
if (require('electron-squirrel-startup')) {
app.quit();
}
main(); main();
console.time('ready-to-show'); console.time('ready-to-show');

View File

@@ -1,15 +0,0 @@
//
// 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;
}

10
lib/pkg-sidekick.json Normal file
View File

@@ -0,0 +1,10 @@
{
"bin": "build/util/child-writer.js",
"pkg": {
"assets": [
"node_modules/usb/prebuilds/darwin-x64+arm64/node.napi.node",
"node_modules/lzma-native/prebuilds/darwin-arm64/node.napi.node",
"node_modules/drivelist/build/Release/drivelist.node"
]
}
}

View File

@@ -19,6 +19,7 @@ import { join } from 'path';
import { env } from 'process'; import { env } from 'process';
import { promisify } from 'util'; import { promisify } from 'util';
import { getAppPath } from '../get-app-path';
import { supportedLocales } from '../../gui/app/i18n'; import { supportedLocales } from '../../gui/app/i18n';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -26,15 +27,6 @@ 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 }> {
@@ -55,7 +47,11 @@ export async function sudo(
encoding: 'utf8', encoding: 'utf8',
env: { env: {
PATH: env.PATH, PATH: env.PATH,
SUDO_ASKPASS: getAskPassScriptPath(lang), SUDO_ASKPASS: join(
getAppPath(),
__dirname,
`sudo-askpass.osascript-${lang}.js`,
),
}, },
}, },
); );

View File

@@ -0,0 +1,12 @@
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 : '',
)
);
}

View File

@@ -16,7 +16,7 @@
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import { outdent } from 'outdent'; import { outdent } from 'outdent';
import prettyBytes from 'pretty-bytes'; import * as prettyBytes from 'pretty-bytes';
import '../gui/app/i18n'; import '../gui/app/i18n';
import * as i18next from 'i18next'; import * as i18next from 'i18next';

View File

@@ -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 {

View File

@@ -15,18 +15,17 @@
*/ */
import * as ipc from 'node-ipc'; 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 { toJSON } from '../shared/errors';
import { GENERAL_ERROR, SUCCESS } from '../shared/exit-codes'; import { GENERAL_ERROR, SUCCESS } from '../shared/exit-codes';
import { delay } from '../shared/utils'; import { delay } from '../shared/utils';
import { WriteOptions } from './types/types'; import { WriteOptions } from './types/types';
import { MultiDestinationProgress } from 'etcher-sdk/build/multi-write';
import { write, cleanup } from './child-writer'; import { write, cleanup } from './child-writer';
import { startScanning } from './scanner'; import { startScanning } from './scanner';
import { getSourceMetadata } from './source-metadata'; import { getSourceMetadata } from './source-metadata';
import { DrivelistDrive } from '../shared/drive-constraints'; import { DrivelistDrive } from '../shared/drive-constraints';
import { Dictionary, values } from 'lodash';
ipc.config.id = process.env.IPC_CLIENT_ID as string; ipc.config.id = process.env.IPC_CLIENT_ID as string;
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string; ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
@@ -41,7 +40,6 @@ ipc.config.silent = true;
// The purpose behind this change is for this process // The purpose behind this change is for this process
// to emit a "disconnect" event as soon as the GUI // to emit a "disconnect" event as soon as the GUI
// process is closed, so we can kill this process as well. // 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) // @ts-ignore (0 is a valid value for stopRetrying and is not the same as false)
ipc.config.stopRetrying = 0; ipc.config.stopRetrying = 0;

View File

@@ -52,7 +52,6 @@ async function write(options: WriteOptions) {
const onFail = (destination: SourceDestination, error: Error) => { const onFail = (destination: SourceDestination, error: Error) => {
emitFail({ emitFail({
// TODO: device should be destination // TODO: device should be destination
// @ts-ignore (destination.drive is private) // @ts-ignore (destination.drive is private)
device: destination.drive, device: destination.drive,
error: toJSON(error), error: toJSON(error),

View File

@@ -30,13 +30,14 @@ 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 && geteuid() === 0)) { if (platform !== 'linux' || 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());
} }

View File

@@ -73,11 +73,9 @@ function prepareDrive(drive: Drive) {
return drive.drive; return drive.drive;
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) { } else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
// This is a workaround etcher expecting a device string and a size // This is a workaround etcher expecting a device string and a size
// @ts-ignore // @ts-ignore
drive.device = drive.usbDevice.portId; drive.device = drive.usbDevice.portId;
drive.size = null; drive.size = null;
// @ts-ignore // @ts-ignore
drive.progress = 0; drive.progress = 0;
drive.disabled = true; drive.disabled = true;
@@ -142,7 +140,6 @@ function updateDriveProgress(
progress: number, progress: number,
) { ) {
const drives = getDrives(); const drives = getDrives();
// @ts-ignore // @ts-ignore
const driveInMap = drives[drive.device]; const driveInMap = drives[drive.device];
if (driveInMap) { if (driveInMap) {

25375
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,9 @@
"name": "balena-etcher", "name": "balena-etcher",
"private": true, "private": true,
"displayName": "balenaEtcher", "displayName": "balenaEtcher",
"productName": "balenaEtcher", "version": "1.18.13",
"version": "1.19.7",
"packageType": "local", "packageType": "local",
"main": ".webpack/main", "main": "generated/etcher.js",
"description": "Flash OS images to SD cards and USB drives, safely and easily.", "description": "Flash OS images to SD cards and USB drives, safely and easily.",
"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",
@@ -14,139 +13,123 @@
"url": "git@github.com:balena-io/etcher.git" "url": "git@github.com:balena-io/etcher.git"
}, },
"scripts": { "scripts": {
"prettify": "prettier --write lib/**/*.css && balena-lint --fix --typescript typings lib tests forge.config.ts forge.sidecar.ts webpack.config.ts", "build": "npm run webpack && npm run build:sidecar",
"lint": "npm run prettify && catch-uncommitted", "build:rebuild-mountutils": "cd node_modules/mountutils && npm rebuild",
"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", "build:sidecar": "npm run build:rebuild-mountutils && tsc --project tsconfig.sidecar.json && pkg build/util/api.js -c pkg-sidecar.json --target node18 --output generated/etcher-util",
"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-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",
"test": "npm run test-gui && npm run test-shared", "flowzone-preinstall-macos": "true",
"package": "electron-forge package", "flowzone-preinstall-windows": "npx node-gyp install",
"start": "electron-forge start", "flowzone-preinstall": "npm run flowzone-preinstall-linux",
"make": "electron-forge make" "lint-css": "prettier --write lib/**/*.css",
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts",
"lint": "npm run lint-ts && npm run lint-css",
"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": "npm run prettify" "pre-commit": "lint-staged"
} }
}, },
"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",
"dependencies": { "devDependencies": {
"@babel/register": "^7.22.15",
"@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.1.0", "@electron/remote": "^2.0.9",
"@fortawesome/fontawesome-free": "6.5.1", "@fortawesome/fontawesome-free": "5.15.4",
"@sentry/electron": "^4.15.1", "@sentry/electron": "^4.1.2",
"@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": "^1.6.0", "axios": "^0.27.2",
"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-squirrel-startup": "^1.0.0", "electron": "^25.8.2",
"electron-updater": "6.1.7", "electron-builder": "^23.6.0",
"etcher-sdk": "9.0.0", "electron-mocha": "^11.0.2",
"i18next": "23.7.8", "electron-notarize": "1.2.2",
"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",
"pkg": "^5.8.1",
"pnp-webpack-plugin": "1.7.0",
"pretty-bytes": "5.6.0", "pretty-bytes": "5.6.0",
"react": "17.0.2", "react": "16.8.5",
"react-dom": "17.0.2", "react-dom": "16.8.5",
"react-i18next": "13.5.0", "react-i18next": "11.18.6",
"redux": "4.2.1", "redux": "4.2.0",
"rendition": "35.1.2", "rendition": "19.3.2",
"semver": "7.5.4", "semver": "7.3.8",
"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",
"uuid": "9.0.1" "terser-webpack-plugin": "5.3.6",
}, "ts-loader": "8.4.0",
"devDependencies": { "ts-node": "9.1.1",
"@balena/lint": "7.2.4", "tslib": "2.4.1",
"@electron-forge/cli": "7.2.0", "typescript": "4.4.4",
"@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": "^18.11.9",
"@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",
"pkg": "^5.8.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",
"xvfb-maybe": "^0.2.1" "uuid": "8.3.2",
}, "webpack": "5.75.0",
"hostDependencies": { "webpack-cli": "4.10.0",
"debian": [ "webpack-dev-server": "4.11.1"
"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": ">=18 <20" "node": ">=18 <20"
}, },
"versionist": { "versionist": {
"publishedAt": "2024-04-22T06:52:19.375Z" "publishedAt": "2023-10-16T13:32:27.552Z"
} }
} }

View File

@@ -5,8 +5,6 @@
"node_modules/drivelist/**", "node_modules/drivelist/**",
"node_modules/mountutils/**", "node_modules/mountutils/**",
"node_modules/winusb-driver-generator/**", "node_modules/winusb-driver-generator/**",
"node_modules/node-raspberrypi-usbboot/**", "node_modules/node-raspberrypi-usbboot/**"
"node_modules/xxhash-addon/**",
"node_modules/axios/**"
] ]
} }

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
awscli==1.27.28
shyaml==0.6.2

View File

@@ -0,0 +1,52 @@
#!/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

View File

@@ -0,0 +1,52 @@
/**
* 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();

1
scripts/resin Submodule

Submodule scripts/resin added at 8dfa21cfc2

View File

@@ -1,6 +1,8 @@
// 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();

View File

@@ -15,6 +15,7 @@
*/ */
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';
@@ -46,8 +47,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 promise = settings.set('foo', 'baz', writeConfigFileStub); const p = settings.set('foo', 'baz', writeConfigFileStub);
await checkError(promise, async (error) => { await checkError(p, 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');

View File

@@ -1,17 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2019",
"allowJs": false, "allowJs": false,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"typeRoots": ["./node_modules/@types", "./typings"], "typeRoots": ["./node_modules/@types", "./typings"],
"module": "commonjs", "module": "CommonJS",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true "isolatedModules": true,
"outDir": "build"
}, },
"include": ["lib/util"] "include": ["lib/util"]
} }

28
tsconfig.webpack.json Normal file
View File

@@ -0,0 +1,28 @@
{
"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 Normal file
View File

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

1
typings/resin-corvus/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'resin-corvus/browser';

View File

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

View File

@@ -14,13 +14,48 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Configuration, ModuleOptions } from 'webpack'; import * as CopyPlugin from 'copy-webpack-plugin';
import * as _ from 'lodash';
import * as path from 'path';
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 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')
// file-loader expects posix paths, even on Windows
.replace(/\\/g, '/')
);
}
interface ReplacementRule { interface ReplacementRule {
search: string; search: string;
@@ -40,34 +75,28 @@ function replace(test: RegExp, ...replacements: ReplacementRule[]) {
}; };
} }
const rules: Required<ModuleOptions>['rules'] = [ const commonConfig = {
// Add support for native node modules mode: 'production',
{ optimization: {
// We're specifying native_modules in the test because the asset relocator loader generates a moduleIds: 'natural',
// "fake" .node file which is really a cjs file. minimize: true,
test: /native_modules[/\\].+\.node$/, minimizer: [
use: 'node-loader', new TerserPlugin({
}, parallel: true,
{ terserOptions: {
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/, compress: false,
parser: { amd: false }, mangle: false,
use: { format: {
loader: '@vercel/webpack-asset-relocator-loader', comments: false,
options: { ecma: 2020,
outputAssetBase: 'native_modules',
},
},
},
{
test: /\.tsx?$/,
exclude: /(node_modules|\.webpack)/,
use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
}, },
}, },
extractComments: false,
}),
],
}, },
module: {
rules: [
{ {
test: /\.css$/, test: /\.css$/,
use: ['style-loader', 'css-loader'], use: ['style-loader', 'css-loader'],
@@ -75,23 +104,45 @@ const rules: Required<ModuleOptions>['rules'] = [
{ {
test: /\.(woff|woff2|eot|ttf|otf)$/, test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader', loader: 'file-loader',
options: { name: renameNodeModules },
}, },
{ {
test: /\.svg$/, test: /\.svg$/,
use: '@svgr/webpack', 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 // force axios to use http backend (not xhr) to support streams
replace(/node_modules\/axios\/lib\/defaults\.js$/, { replace(/node_modules\/axios\/lib\/defaults\.js$/, {
search: './adapters/xhr', search: './adapters/xhr',
replace: './adapters/http', replace: './adapters/http',
}), }),
]; ],
},
export const rendererConfig: Configuration = { resolve: {
module: { extensions: ['.js', '.json', '.ts', '.tsx'],
rules,
}, },
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(
@@ -106,25 +157,62 @@ export const rendererConfig: Configuration = {
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'),
],
};
const guiConfig = {
...commonConfig,
target: 'electron-renderer',
node: {
__dirname: true,
__filename: true,
},
entry: {
gui: 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,
}), }),
], ],
resolve: { };
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'],
const mainConfig = {
...commonConfig,
target: 'electron-main',
node: {
__dirname: false,
__filename: true,
}, },
}; };
export const mainConfig: Configuration = { const etcherConfig = {
...mainConfig,
entry: { entry: {
etcher: './lib/gui/etcher.ts', etcher: path.join(__dirname, 'lib', 'gui', 'etcher.ts'),
},
module: {
rules,
},
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
}, },
}; };
export default [guiConfig, etcherConfig];

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

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