Merge pull request #4132 from balena-io/switch-to-electron-forge

Modernize build pipeline
This commit is contained in:
dfunckt 2023-12-21 18:41:10 +02:00 committed by GitHub
commit 6e4db830e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 8044 additions and 6285 deletions

View File

@ -10,12 +10,12 @@ inputs:
required: true required: true
# --- custom environment # --- custom environment
XCODE_APP_LOADER_EMAIL:
type: string
default: "accounts+apple@balena.io"
NODE_VERSION: NODE_VERSION:
type: string type: string
default: "18.x" # Beware that native modules will be built for this version,
# which might not be compatible with the one used by pkg (see forge.sidecar.ts)
# https://github.com/vercel/pkg-fetch/releases
default: "18.18"
VERBOSE: VERBOSE:
type: string type: string
default: "true" default: "true"
@ -27,12 +27,12 @@ runs:
- name: Download custom source artifact - name: Download custom source artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }} path: ${{ runner.temp }}
- name: Extract custom source artifact - name: Extract custom source artifact
if: runner.os != 'Windows' if: runner.os != 'Windows'
shell: pwsh shell: bash
working-directory: . working-directory: .
run: tar -xf ${{ runner.temp }}/custom.tgz run: tar -xf ${{ runner.temp }}/custom.tgz
@ -48,22 +48,54 @@ runs:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Install yq - name: Install host dependencies
shell: bash --noprofile --norc -eo pipefail -x {0} if: runner.os == 'Linux'
run: choco install yq shell: bash
if: runner.os == 'Windows' run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm
- name: Install host dependencies
if: runner.os == 'macOS'
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
# This is a temporary workaround to make the job use Python 3.11 until
# we update to npm 10+.
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
with:
python-version: '3.11'
# https://www.electron.build/code-signing.html # https://www.electron.build/code-signing.html
# https://github.com/Apple-Actions/import-codesign-certs # https://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
- name: Import Apple code signing certificate - name: Import Apple code signing certificate
if: runner.os == 'macOS' if: runner.os == 'macOS'
uses: apple-actions/import-codesign-certs@v1 shell: bash
with: run: |
p12-file-base64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }} KEY_CHAIN=build.keychain
p12-password: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} CERTIFICATE_P12=certificate.p12
# Recreate the certificate from the secure environment variable
echo $CERTIFICATE_P12_B64 | base64 --decode > $CERTIFICATE_P12
# Create a keychain
security create-keychain -p actions $KEY_CHAIN
# Make the keychain the default so identities are found
security default-keychain -s $KEY_CHAIN
# Unlock the keychain
security unlock-keychain -p actions $KEY_CHAIN
security import $CERTIFICATE_P12 -k $KEY_CHAIN -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k actions $KEY_CHAIN
# remove certs
rm -fr *.p12
env:
CERTIFICATE_P12_B64: ${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }}
- name: Import Windows code signing certificate - name: Import Windows code signing certificate
if: runner.os == 'Windows' if: runner.os == 'Windows'
id: import_win_signing_cert
shell: powershell shell: powershell
run: | run: |
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:WINDOWS_CERTIFICATE
@ -75,95 +107,79 @@ runs:
-CertStoreLocation Cert:\CurrentUser\My ` -CertStoreLocation Cert:\CurrentUser\My `
-Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText) -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
Remove-Item -path ${{ runner.temp }} -include certificate.pfx echo "certFilePath=${{ runner.temp }}/certificate.pfx" >> $GITHUB_OUTPUT
env: env:
WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }} WINDOWS_CERTIFICATE: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }} WINDOWS_CERTIFICATE_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
# ... or refactor (e.g.) https://github.com/samuelmeuli/action-electron-builder
# https://github.com/product-os/scripts/tree/master/electron
# https://github.com/product-os/scripts/tree/master/shared
# https://github.com/product-os/balena-concourse/blob/master/pipelines/github-events/template.yml
- name: Package release - name: Package release
id: package_release shell: bash
shell: bash --noprofile --norc -eo pipefail -x {0} # IMPORTANT: before making changes to this step please consult @engineering in balena's chat.
run: | run: |
set -ea if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
export DEBUG='electron-forge:*,sidecar'
fi
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
runner_arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
ELECTRON_BUILDER_ARCHITECTURE="${runner_arch}"
APPLICATION_VERSION="$(jq -r '.version' package.json)" APPLICATION_VERSION="$(jq -r '.version' package.json)"
ARCHITECTURE_FLAGS="--${ELECTRON_BUILDER_ARCHITECTURE}" HOST_ARCH="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
if [[ $runner_os =~ linux ]]; then if [[ "${RUNNER_OS}" == Linux ]]; then
ELECTRON_BUILDER_OS='--linux' PLATFORM=Linux
TARGETS="$(yq e .linux.target[] electron-builder.yml)" SHA256SUM_BIN=sha256sum
elif [[ $runner_os =~ darwin|macos|osx ]]; then elif [[ "${RUNNER_OS}" == macOS ]]; then
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).APPLE_SIGNING_PASSWORD }} PLATFORM=Darwin
CSC_KEYCHAIN=signing_temp SHA256SUM_BIN='shasum -a 256'
CSC_LINK=${{ fromJSON(inputs.secrets).APPLE_SIGNING }}
ELECTRON_BUILDER_OS='--mac'
TARGETS="$(yq e .mac.target[] electron-builder.yml)"
elif [[ $runner_os =~ windows|win ]]; then elif [[ "${RUNNER_OS}" == Windows ]]; then
ARCHITECTURE_FLAGS="--ia32 ${ARCHITECTURE_FLAGS}" PLATFORM=Windows
CSC_KEY_PASSWORD=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }} SHA256SUM_BIN=sha256sum
CSC_LINK=${{ fromJSON(inputs.secrets).WINDOWS_SIGNING }}
ELECTRON_BUILDER_OS='--win'
TARGETS="$(yq e .win.target[] electron-builder.yml)"
else else
echo "ERROR: unexpected runner OS: ${RUNNER_OS}"
exit 1 exit 1
fi fi
npm link electron-builder # Currently, we can only build for the host architecture.
npx electron-forge make
for target in ${TARGETS}; do
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
--c.extraMetadata.analytics.sentry.token='https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632' \
--c.extraMetadata.analytics.amplitude.token='balena-etcher' \
--c.extraMetadata.packageType="${target}"
find dist -type f -maxdepth 1
done
echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT echo "version=${APPLICATION_VERSION}" >> $GITHUB_OUTPUT
# collect all artifacts from subdirectories under a common top-level directory
mkdir -p dist
find ./out/make -type f \( \
-iname "*.zip" -o \
-iname "*.dmg" -o \
-iname "*.rpm" -o \
-iname "*.deb" -o \
-iname "*.AppImage" -o \
-iname "*Setup.exe" \
\) -ls -exec cp '{}' dist/ \;
if [[ -n "${SHA256SUM_BIN}" ]]; then
# Compute and save digests.
cd dist/
${SHA256SUM_BIN} *.* >"SHA256SUMS.${PLATFORM}.${HOST_ARCH}.txt"
fi
env: env:
# Apple notarization (afterSignHook.js) # ensure we sign the artifacts
XCODE_APP_LOADER_EMAIL: ${{ inputs.XCODE_APP_LOADER_EMAIL }} NODE_ENV: production
# analytics tokens
SENTRY_TOKEN: https://739bbcfc0ba4481481138d3fc831136d@o95242.ingest.sentry.io/4504451487301632
AMPLITUDE_TOKEN: 'balena-etcher'
# Apple notarization
XCODE_APP_LOADER_EMAIL: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_EMAIL }}
XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }} XCODE_APP_LOADER_PASSWORD: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_PASSWORD }}
# https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks XCODE_APP_LOADER_TEAM_ID: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_TEAM_ID }}
# https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks#about-workflow-runs-from-public-forks # Windows signing
CSC_FOR_PULL_REQUEST: true WINDOWS_SIGNING_CERT_PATH: ${{ steps.import_win_signing_cert.outputs.certFilePath }}
WINDOWS_SIGNING_PASSWORD: ${{ fromJSON(inputs.secrets).WINDOWS_SIGNING_PASSWORD }}
# https://www.electron.build/auto-update.html#staged-rollouts
- name: Configure staged rollout(s)
shell: bash --noprofile --norc -eo pipefail -x {0}
run: |
set -ea
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x
percentage="$(cat < repo.yml | yq e .triggerNotification.stagingPercentage)"
find dist -type f -maxdepth 1 \
-name "latest*.yml" \
-exec yq -i e .version=\"${{ steps.package_release.outputs.version }}\" {} \;
find dist -type f -maxdepth 1 \
-name "latest*.yml" \
-exec yq -i e .stagingPercentage=\"$percentage\" {} \;
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }} name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: dist path: dist
retention-days: 1 retention-days: 1
if-no-files-found: error

View File

@ -12,7 +12,7 @@ inputs:
# --- custom environment # --- custom environment
NODE_VERSION: NODE_VERSION:
type: string type: string
default: "16.x" default: "18.18"
VERBOSE: VERBOSE:
type: string type: string
default: "true" default: "true"
@ -28,27 +28,45 @@ runs:
node-version: ${{ inputs.NODE_VERSION }} node-version: ${{ inputs.NODE_VERSION }}
cache: npm cache: npm
- name: Test release - name: Install host dependencies
shell: bash --noprofile --norc -eo pipefail -x {0} if: runner.os == 'Linux'
shell: bash
run: | run: |
set -ea sudo apt-get install -y --no-install-recommends xvfb libudev-dev
cat < package.json | jq -r '.hostDependencies[][]' - | \
xargs -L1 echo | sed 's/|//g' | xargs -L1 \
sudo apt-get --ignore-missing install || true
[[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]] && set -x - name: Install host dependencies
if: runner.os == 'macOS'
# FIXME: Python 3.12 dropped distutils that node-gyp depends upon.
# This is a temporary workaround to make the job use Python 3.11 until
# we update to npm 10+.
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4
with:
python-version: '3.11'
- name: Test release
shell: bash
run: |
if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
export DEBUG='electron-forge:*,sidecar'
fi
runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')" runner_os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
npm run flowzone-preinstall-${runner_os}
npm ci npm ci
npm run build npm run lint
npm run package
npm run test-${runner_os} npm run test-${runner_os}
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: pwsh shell: bash
run: tar -acf ${{ runner.temp }}/custom.tgz . run: tar -acf ${{ runner.temp }}/custom.tgz .
- name: Compress custom source - name: Compress custom source
@ -59,6 +77,6 @@ runs:
- name: Upload custom artifact - name: Upload custom artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }} name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz path: ${{ runner.temp }}/custom.tgz
retention-days: 1 retention-days: 1

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

View File

@ -9,5 +9,6 @@ jobs:
- uses: vedantmgoyal2009/winget-releaser@v1 - uses: vedantmgoyal2009/winget-releaser@v1
with: with:
identifier: Balena.Etcher identifier: Balena.Etcher
installers-regex: 'balenaEtcher-Setup.*.exe$' # matches something like "balenaEtcher-1.19.0.Setup.exe"
installers-regex: 'balenaEtcher-[\d.-]+\.Setup.exe$'
token: ${{ secrets.WINGET_PAT }} token: ${{ secrets.WINGET_PAT }}

113
.gitignore vendored
View File

@ -1,41 +1,103 @@
# -- ADD NEW ENTRIES AT THE END OF THE FILE ---
# Logs # Logs
/logs logs
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data # Runtime data
pids pids
*.pid *.pid
*.seed *.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover # Directory for instrumented libs generated by jscoverage/JSCover
/lib-cov lib-cov
# Image stream output directory
/tests/image-stream/output
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
/coverage coverage
*.lcov
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) # nyc test coverage
.grunt .nyc_output
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
/build build/Release
# Generated files # Dependency directories
/generated node_modules/
/binaries jspm_packages/
# Dependency directory # TypeScript v1 declaration files
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git typings/
node_modules
# Compiled Etcher releases # TypeScript cache
/dist *.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Webpack
.webpack/
# Vite
.vite/
# Electron-Forge
out/
# ---- Do not modify entries above this line ----
# Build artifacts
dist/
# Certificates # Certificates
*.spc *.spc
@ -45,16 +107,17 @@ node_modules
*.crt *.crt
*.pem *.pem
# OSX files # Secrets
.DS_Store
# VSCode files
.vscode
.gitsecret/keys/random_seed .gitsecret/keys/random_seed
!*.secret !*.secret
secrets/APPLE_SIGNING_PASSWORD.txt secrets/APPLE_SIGNING_PASSWORD.txt
secrets/WINDOWS_SIGNING_PASSWORD.txt secrets/WINDOWS_SIGNING_PASSWORD.txt
secrets/XCODE_APP_LOADER_PASSWORD.txt secrets/XCODE_APP_LOADER_PASSWORD.txt
secrets/WINDOWS_SIGNING.pfx secrets/WINDOWS_SIGNING.pfx
# Image stream output directory
/tests/image-stream/output
#local development
.yalc
yalc.lock

4
.gitmodules vendored
View File

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

2
.nvmrc
View File

@ -1 +1 @@
16 18

152
Makefile
View File

@ -1,152 +0,0 @@
# ---------------------------------------------------------------------
# Build configuration
# ---------------------------------------------------------------------
RESIN_SCRIPTS ?= ./scripts/resin
export NPM_VERSION ?= 6.14.8
S3_BUCKET = artifacts.ci.balena-cloud.com
# This directory will be completely deleted by the `clean` rule
BUILD_DIRECTORY ?= dist
BUILD_TEMPORARY_DIRECTORY = $(BUILD_DIRECTORY)/.tmp
$(BUILD_DIRECTORY):
mkdir $@
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
mkdir $@
SHELL := /bin/bash
# ---------------------------------------------------------------------
# Operating system and architecture detection
# ---------------------------------------------------------------------
# http://stackoverflow.com/a/12099167
ifeq ($(OS),Windows_NT)
PLATFORM = win32
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
HOST_ARCH = x64
else
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
HOST_ARCH = x64
endif
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
HOST_ARCH = x86
endif
endif
else
ifeq ($(shell uname -s),Linux)
PLATFORM = linux
ifeq ($(shell uname -m),x86_64)
HOST_ARCH = x64
endif
ifneq ($(filter %86,$(shell uname -m)),)
HOST_ARCH = x86
endif
ifeq ($(shell uname -m),armv7l)
HOST_ARCH = armv7hf
endif
ifeq ($(shell uname -m),aarch64)
HOST_ARCH = aarch64
endif
ifeq ($(shell uname -m),armv8)
HOST_ARCH = aarch64
endif
ifeq ($(shell uname -m),arm64)
HOST_ARCH = aarch64
endif
endif
ifeq ($(shell uname -s),Darwin)
PLATFORM = darwin
ifeq ($(shell uname -m),x86_64)
HOST_ARCH = x64
endif
ifeq ($(shell uname -m),arm64)
HOST_ARCH = aarch64
endif
endif
endif
ifndef PLATFORM
$(error We could not detect your host platform)
endif
ifndef HOST_ARCH
$(error We could not detect your host architecture)
endif
# Default to host architecture. You can override by doing:
#
# make <target> TARGET_ARCH=<arch>
#
TARGET_ARCH ?= $(HOST_ARCH)
# ---------------------------------------------------------------------
# Electron
# ---------------------------------------------------------------------
electron-develop:
git submodule update --init && \
npm ci && \
npm run webpack
electron-test:
$(RESIN_SCRIPTS)/electron/test.sh \
-b $(shell pwd) \
-s $(PLATFORM)
assets/dmg/background.tiff: assets/dmg/background.png assets/dmg/background@2x.png
tiffutil -cathidpicheck $^ -out $@
electron-build: assets/dmg/background.tiff | $(BUILD_TEMPORARY_DIRECTORY)
$(RESIN_SCRIPTS)/electron/build.sh \
-b $(shell pwd) \
-r $(TARGET_ARCH) \
-s $(PLATFORM) \
-v production \
-n $(BUILD_TEMPORARY_DIRECTORY)/npm
# ---------------------------------------------------------------------
# Phony targets
# ---------------------------------------------------------------------
TARGETS = \
help \
info \
lint \
test \
clean \
distclean \
electron-develop \
electron-test \
electron-build
.PHONY: $(TARGETS)
lint:
npm run lint
test:
npm run test
help:
@echo "Available targets: $(TARGETS)"
info:
@echo "Platform : $(PLATFORM)"
@echo "Host arch : $(HOST_ARCH)"
@echo "Target arch : $(TARGET_ARCH)"
clean:
rm -rf $(BUILD_DIRECTORY)
distclean: clean
rm -rf node_modules
rm -rf dist
rm -rf generated
rm -rf $(BUILD_TEMPORARY_DIRECTORY)
.DEFAULT_GOAL = help

View File

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

View File

@ -1,31 +0,0 @@
'use strict'
const cp = require('child_process')
const fs = require('fs')
const outdent = require('outdent')
const path = require('path')
exports.default = function(context) {
if (context.packager.platform.name !== 'linux') {
return
}
const scriptPath = path.join(context.appOutDir, context.packager.executableName)
const binPath = scriptPath + '.bin'
cp.execFileSync('mv', [scriptPath, binPath])
fs.writeFileSync(
scriptPath,
outdent({trimTrailingNewline: false})`
#!/bin/bash
# Resolve symlinks. Warning, readlink -f doesn't work on MacOS/BSD
script_dir="$(dirname "$(readlink -f "\${BASH_SOURCE[0]}")")"
if [[ $EUID -ne 0 ]] || [[ $ELECTRON_RUN_AS_NODE ]]; then
"\${script_dir}"/${context.packager.executableName}.bin "$@"
else
"\${script_dir}"/${context.packager.executableName}.bin "$@" --no-sandbox
fi
`
)
cp.execFileSync('chmod', ['+x', scriptPath])
}

View File

@ -1,25 +0,0 @@
'use strict'
const { notarize } = require('electron-notarize')
const { ELECTRON_SKIP_NOTARIZATION } = process.env
async function main(context) {
const { electronPlatformName, appOutDir } = context
if (electronPlatformName !== 'darwin' || ELECTRON_SKIP_NOTARIZATION === 'true') {
return
}
const appName = context.packager.appInfo.productFilename
const appleId = process.env.XCODE_APP_LOADER_EMAIL || 'accounts+apple@balena.io'
const appleIdPassword = process.env.XCODE_APP_LOADER_PASSWORD
// https://github.com/electron/notarize/blob/main/README.md
await notarize({
appBundleId: 'io.balena.etcher',
appPath: `${appOutDir}/${appName}.app`,
appleId,
appleIdPassword
})
}
exports.default = main

View File

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

View File

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

View File

@ -75,9 +75,7 @@ cd etcher
#### GUI #### GUI
```sh ```sh
# Build the GUI # Build and start application
npm run webpack #or npm run build
# Start Electron
npm start npm start
``` ```
@ -104,7 +102,6 @@ systems as they can before sending a pull request.
*The test suite is run automatically by CI servers when you send a pull *The test suite is run automatically by CI servers when you send a pull
request.* request.*
We make use of [EditorConfig] to communicate indentation, line endings and We make use of [EditorConfig] to communicate indentation, line endings and
other text editing default. We encourage you to install the relevant plugin in other text editing default. We encourage you to install the relevant plugin in
your text editor of choice to avoid having to fix any issues during the review your text editor of choice to avoid having to fix any issues during the review
@ -113,7 +110,8 @@ process.
Updating a dependency Updating a dependency
--------------------- ---------------------
- Commit *both* `package.json` and `package-lock.json`. - Install new version of dependency using npm
- Commit *both* `package.json` and `npm-shrinkwrap.json`.
Diffing Binaries Diffing Binaries
---------------- ----------------

View File

@ -58,25 +58,18 @@ export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
##### Clean dist folder ##### Clean dist folder
**NOTE:** Make sure to adjust the path as necessary (here the Etcher repository has been cloned to `/home/$USER/code/etcher`) Delete `.webpack` and `out/`.
##### Generating artifacts ##### Generating artifacts
The artifacts are generated by the CI and published as draft-release or pre-release. The artifacts are generated by the CI and published as draft-release or pre-release.
`electron-builder` is used to create the packaged application. Etcher is built with electron-forge. Run:
#### Mac OS ```
npm run make
```
**ATTENTION:** For production releases you'll need the code-signing key, Our CI will appropriately sign artifacts for macOS and some Windows targets.
and set `CSC_NAME` to generate signed binaries on Mac OS.
#### Windows
**ATTENTION:** For production releases you'll need the code-signing key,
and set `CSC_LINK`, and `CSC_KEY_PASSWORD` to generate signed binaries on Windows.
**NOTE:**
- Keep in mind to also generate artifacts for x86, with `TARGET_ARCH=x86`.
### Uploading packages to Cloudfront ### Uploading packages to Cloudfront

View File

@ -36,14 +36,17 @@ employee by asking for it from the relevant people.
Packaging Packaging
--------- ---------
The resulting installers will be saved to `dist/out`. Run the following command on each platform:
Run the following commands on all platforms with the right arguments:
```sh ```sh
./node_modules/electron-builder build <...> npm run make
``` ```
This will produce all targets (eg. zip, dmg) specified in forge.config.ts for the
host platform and architecture.
The resulting artifacts can be found in `out/make`.
Publishing to Cloudfront Publishing to Cloudfront
--------------------- ---------------------

View File

@ -1,110 +0,0 @@
# https://www.electron.build/configuration/configuration
appId: io.balena.etcher
copyright: Copyright 2016-2023 Balena Ltd
productName: balenaEtcher
afterPack: ./afterPack.js
afterSign: ./afterSignHook.js
asar: false
files:
- generated
- lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js
- lib/shared/catalina-sudo/sudo-askpass.osascript-en.js
mac:
icon: assets/icon.icns
category: public.app-category.developer-tools
hardenedRuntime: true
entitlements: "entitlements.mac.plist"
entitlementsInherit: "entitlements.mac.plist"
artifactName: "${productName}-${version}.${ext}"
target:
- dmg
dmg:
background: assets/dmg/background.tiff
icon: assets/icon.icns
iconSize: 110
contents:
- x: 140
y: 225
- x: 415
y: 225
type: link
path: /Applications
window:
width: 540
height: 405
win:
icon: assets/icon.ico
target:
- zip
- nsis
- portable
nsis:
oneClick: true
runAfterFinish: true
installerIcon: assets/icon.ico
uninstallerIcon: assets/icon.ico
deleteAppDataOnUninstall: true
license: LICENSE
artifactName: "${productName}-Setup-${version}.${ext}"
portable:
artifactName: "${productName}-Portable-${version}.${ext}"
requestExecutionLevel: user
linux:
icon: assets/iconset
target:
- AppImage
- rpm
- deb
category: Utility
packageCategory: utils
executableName: balena-etcher
synopsis: balenaEtcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.
appImage:
artifactName: ${productName}-${version}-${env.ELECTRON_BUILDER_ARCHITECTURE}.${ext}
deb:
priority: optional
compression: bzip2
depends:
- gconf-service
- gconf2
- libasound2
- libatk1.0-0
- libc6
- libcairo2
- libcups2
- libdbus-1-3
- libexpat1
- libfontconfig1
- libfreetype6
- libgbm1
- libgcc1
- libgconf-2-4
- libgdk-pixbuf2.0-0
- libglib2.0-0
- libgtk-3-0
- liblzma5
- libnotify4
- libnspr4
- libnss3
- libpango1.0-0 | libpango-1.0-0
- libstdc++6
- libx11-6
- libxcomposite1
- libxcursor1
- libxdamage1
- libxext6
- libxfixes3
- libxi6
- libxrandr2
- libxrender1
- libxss1
- libxtst6
- polkit-1-auth-agent | policykit-1-gnome | polkit-kde-1
afterInstall: "./after-install.tpl"
rpm:
depends:
- util-linux
protocols:
name: etcher
schemes:
- etcher

158
forge.config.ts Normal file
View File

@ -0,0 +1,158 @@
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 = {
certificateFile: process.env.WINDOWS_SIGNING_CERT_PATH,
certificatePassword: process.env.WINDOWS_SIGNING_PASSWORD,
};
}
const config: ForgeConfig = {
packagerConfig: {
asar: true,
icon: './assets/icon',
executableName:
process.platform === 'linux' ? 'balena-etcher' : 'balenaEtcher',
appBundleId: 'io.balena.etcher',
appCategoryType: 'public.app-category.developer-tools',
appCopyright: 'Copyright 2016-2023 Balena Ltd',
darwinDarkModeSupport: true,
protocols: [{ name: 'etcher', schemes: ['etcher'] }],
extraResource: [
'lib/shared/catalina-sudo/sudo-askpass.osascript-zh.js',
'lib/shared/catalina-sudo/sudo-askpass.osascript-en.js',
],
osxSign: {
optionsForFile: () => ({
entitlements: './entitlements.mac.plist',
hardenedRuntime: true,
}),
},
...osxSigningConfig,
},
rebuildConfig: {},
makers: [
new MakerZIP(),
new MakerSquirrel({
setupIcon: 'assets/icon.ico',
...winSigningConfig,
}),
new MakerDMG({
background: './assets/dmg/background.tiff',
icon: './assets/icon.icns',
iconSize: 110,
contents: ((opts: { appPath: string }) => {
return [
{ x: 140, y: 250, type: 'file', path: opts.appPath },
{ x: 415, y: 250, type: 'link', path: '/Applications' },
];
}) as any, // type of MakerDMGConfig omits `appPath`
additionalDMGOptions: {
window: {
size: {
width: 540,
height: 425,
},
position: {
x: 400,
y: 500,
},
},
},
}),
new MakerAppImage({
options: {
icon: './assets/icon.png',
categories: ['Utility'],
},
}),
new MakerRpm({
options: {
icon: './assets/icon.png',
categories: ['Utility'],
productDescription,
requires: ['util-linux'],
},
}),
new MakerDeb({
options: {
icon: './assets/icon.png',
categories: ['Utility'],
section: 'utils',
priority: 'optional',
productDescription,
scripts: {
postinst: './after-install.tpl',
},
depends: hostDependencies['debian'],
},
}),
],
plugins: [
new AutoUnpackNativesPlugin({}),
new WebpackPlugin({
mainConfig,
renderer: {
config: rendererConfig,
nodeIntegration: true,
entryPoints: [
{
html: './lib/gui/app/index.html',
js: './lib/gui/app/renderer.ts',
name: 'main_window',
preload: {
js: './lib/gui/app/preload.ts',
},
},
],
},
}),
new sidecar.SidecarPlugin(),
],
hooks: {
readPackageJson: async (_config, packageJson) => {
packageJson.analytics = {};
if (process.env.SENTRY_TOKEN) {
packageJson.analytics.sentry = {
token: process.env.SENTRY_TOKEN,
};
}
if (process.env.AMPLITUDE_TOKEN) {
packageJson.analytics.amplitude = {
token: 'balena-etcher',
};
}
// packageJson.packageType = 'dmg' | 'AppImage' | 'rpm' | 'deb' | 'zip' | 'nsis' | 'portable'
return packageJson;
},
},
};
export default config;

168
forge.sidecar.ts Normal file
View File

@ -0,0 +1,168 @@
import { PluginBase } from '@electron-forge/plugin-base';
import {
ForgeHookMap,
ResolvedForgeConfig,
} from '@electron-forge/shared-types';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
import { DefinePlugin } from 'webpack';
import { execFileSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as d from 'debug';
const debug = d('sidecar');
function isStartScrpt(): boolean {
return process.env.npm_lifecycle_event === 'start';
}
function addWebpackDefine(
config: ResolvedForgeConfig,
defineName: string,
binDir: string,
binName: string,
): ResolvedForgeConfig {
config.plugins.forEach((plugin) => {
if (plugin.name !== 'webpack' || !(plugin instanceof WebpackPlugin)) {
return;
}
const { mainConfig } = plugin.config as any;
if (mainConfig.plugins == null) {
mainConfig.plugins = [];
}
const value = isStartScrpt()
? // on `npm start`, point directly to the binary
path.resolve(binDir, binName)
: // otherwise point relative to the resources folder of the bundled app
binName;
debug(`define '${defineName}'='${value}'`);
mainConfig.plugins.push(
new DefinePlugin({
// expose path to helper via this webpack define
[defineName]: JSON.stringify(value),
}),
);
});
return config;
}
function build(
sourcesDir: string,
buildForArchs: string,
binDir: string,
binName: string,
) {
const commands: Array<[string, string[], object?]> = [
['tsc', ['--project', 'tsconfig.sidecar.json', '--outDir', sourcesDir]],
];
buildForArchs.split(',').forEach((arch) => {
const binPath = isStartScrpt()
? // on `npm start`, we don't know the arch we're building for at the time we're
// adding the webpack define, so we just build under binDir
path.resolve(binDir, binName)
: // otherwise build in arch-specific directory within binDir
path.resolve(binDir, arch, binName);
// FIXME: rebuilding mountutils shouldn't be necessary, but it is.
// It's coming from etcher-sdk, a fix has been upstreamed but to use
// the latest etcher-sdk we need to upgrade axios at the same time.
commands.push(['npm', ['rebuild', 'mountutils', `--arch=${arch}`]]);
commands.push([
'pkg',
[
path.join(sourcesDir, 'util', 'api.js'),
'-c',
'pkg-sidecar.json',
// `--no-bytecode` so that we can cross-compile for arm64 on x64
'--no-bytecode',
'--public',
'--public-packages',
'"*"',
// always build for host platform and node version
// https://github.com/vercel/pkg-fetch/releases
'--target',
`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

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

View File

@ -3,10 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>balenaEtcher</title> <title>balenaEtcher</title>
<link rel="stylesheet" type="text/css" href="index.css">
</head> </head>
<body> <body>
<main id="main"></main> <main id="main"></main>
<script src="gui.js"></script>
</body> </body>
</html> </html>

View File

@ -18,7 +18,6 @@ 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;
@ -27,8 +26,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;
function writerArgv(): string[] { async function writerArgv(): Promise<string[]> {
let entryPoint = path.join(getAppPath(), 'generated', 'etcher-util'); let entryPoint = await window.etcher.getEtcherUtilPath();
// 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
@ -75,7 +74,7 @@ async function spawnChild({
IPC_SERVER_ID: string; IPC_SERVER_ID: string;
IPC_SOCKET_ROOT: string; IPC_SOCKET_ROOT: string;
}) { }) {
const argv = writerArgv(); const argv = await 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, {

12
lib/gui/app/preload.ts Normal file
View File

@ -0,0 +1,12 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
import * as webapi from '../webapi';
declare global {
interface Window {
etcher: typeof webapi;
}
}
window['etcher'] = webapi;

View File

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

View File

@ -14,6 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
// whether you're running in development or production).
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
import * as electron from 'electron'; import * as electron from 'electron';
import * as remoteMain from '@electron/remote/main'; import * as remoteMain from '@electron/remote/main';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
@ -176,6 +182,7 @@ async function createMainWindow() {
contextIsolation: false, contextIsolation: false,
webviewTag: true, webviewTag: true,
zoomFactor: width / defaultWidth, zoomFactor: width / defaultWidth,
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
}, },
}); });
@ -199,13 +206,7 @@ async function createMainWindow() {
event.preventDefault(); event.preventDefault();
}); });
mainWindow.loadURL( mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
`file://${path.join(
'/',
...__dirname.split(path.sep).map(encodeURIComponent),
'index.html',
)}`,
);
const page = mainWindow.webContents; const page = mainWindow.webContents;
remoteMain.enable(page); remoteMain.enable(page);
@ -241,6 +242,20 @@ electron.app.on('before-quit', () => {
process.exit(EXIT_CODES.SUCCESS); process.exit(EXIT_CODES.SUCCESS);
}); });
// this is replaced at build-time with the path to helper binary,
// relative to the app resources directory.
declare const ETCHER_UTIL_BIN_PATH: string;
electron.ipcMain.handle('get-util-path', () => {
if (process.env.NODE_ENV === 'development') {
// In development there is no "app bundle" and we're working directly with
// artifacts from the "out" directory, where this value point to.
return ETCHER_UTIL_BIN_PATH;
}
// In any other case, resolve the helper relative to resources path.
return path.resolve(process.resourcesPath, ETCHER_UTIL_BIN_PATH);
});
async function main(): Promise<void> { async function main(): Promise<void> {
if (!electron.app.requestSingleInstanceLock()) { if (!electron.app.requestSingleInstanceLock()) {
electron.app.quit(); electron.app.quit();
@ -287,6 +302,13 @@ 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');

15
lib/gui/webapi.ts Normal file
View File

@ -0,0 +1,15 @@
//
// Anything exported from this module will become available to the
// renderer process via preload. They're accessible as `window.etcher.foo()`.
//
import { ipcRenderer } from 'electron';
// FIXME: this is a workaround for the renderer to be able to find the etcher-util
// binary. We should instead export a function that asks the main process to launch
// the binary itself.
export async function getEtcherUtilPath(): Promise<string> {
const utilPath = await ipcRenderer.invoke('get-util-path');
console.log(utilPath);
return utilPath;
}

View File

@ -1,10 +0,0 @@
{
"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,7 +19,6 @@ import { join } from 'path';
import { env } from 'process'; import { env } from 'process';
import { promisify } from 'util'; import { promisify } from 'util';
import { getAppPath } from '../get-app-path';
import { supportedLocales } from '../../gui/app/i18n'; import { supportedLocales } from '../../gui/app/i18n';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@ -27,6 +26,15 @@ const execFileAsync = promisify(execFile);
const SUCCESSFUL_AUTH_MARKER = 'AUTHENTICATION SUCCEEDED'; const SUCCESSFUL_AUTH_MARKER = 'AUTHENTICATION SUCCEEDED';
const EXPECTED_SUCCESSFUL_AUTH_MARKER = `${SUCCESSFUL_AUTH_MARKER}\n`; const EXPECTED_SUCCESSFUL_AUTH_MARKER = `${SUCCESSFUL_AUTH_MARKER}\n`;
function getAskPassScriptPath(lang: string): string {
if (process.env.NODE_ENV === 'development') {
// Force webpack's hand to bundle the script.
return require.resolve(`./sudo-askpass.osascript-${lang}.js`);
}
// Otherwise resolve the script relative to resources path.
return join(process.resourcesPath, `sudo-askpass.osascript-${lang}.js`);
}
export async function sudo( export async function sudo(
command: string, command: string,
): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> { ): Promise<{ cancelled: boolean; stdout?: string; stderr?: string }> {
@ -47,11 +55,7 @@ export async function sudo(
encoding: 'utf8', encoding: 'utf8',
env: { env: {
PATH: env.PATH, PATH: env.PATH,
SUDO_ASKPASS: join( SUDO_ASKPASS: getAskPassScriptPath(lang),
getAppPath(),
__dirname,
`sudo-askpass.osascript-${lang}.js`,
),
}, },
}, },
); );

View File

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

12620
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,10 @@
"name": "balena-etcher", "name": "balena-etcher",
"private": true, "private": true,
"displayName": "balenaEtcher", "displayName": "balenaEtcher",
"productName": "balenaEtcher",
"version": "1.18.14", "version": "1.18.14",
"packageType": "local", "packageType": "local",
"main": "generated/etcher.js", "main": ".webpack/main",
"description": "Flash OS images to SD cards and USB drives, safely and easily.", "description": "Flash OS images to SD cards and USB drives, safely and easily.",
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.", "productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
"homepage": "https://github.com/balena-io/etcher", "homepage": "https://github.com/balena-io/etcher",
@ -13,27 +14,18 @@
"url": "git@github.com:balena-io/etcher.git" "url": "git@github.com:balena-io/etcher.git"
}, },
"scripts": { "scripts": {
"build": "npm run webpack && npm run build:sidecar",
"build:rebuild-mountutils": "cd node_modules/mountutils && npm rebuild",
"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",
"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",
"flowzone-preinstall-macos": "true",
"flowzone-preinstall-windows": "npx node-gyp install",
"flowzone-preinstall": "npm run flowzone-preinstall-linux",
"lint-css": "prettier --write lib/**/*.css", "lint-css": "prettier --write lib/**/*.css",
"lint-ts": "balena-lint --fix --typescript typings lib tests scripts/clean-shrinkwrap.ts webpack.config.ts", "lint-ts": "balena-lint --fix --typescript typings lib tests forge.config.ts forge.sidecar.ts webpack.config.ts",
"lint": "npm run lint-ts && npm run lint-css", "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-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-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-windows": "npm run lint && npm run test-gui && npm run test-shared",
"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-macos": "npm run lint && npm run test-gui && npm run test-shared",
"test-windows": "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",
"test": "echo npm run test-{linux,windows,macos}", "test": "echo npm run test-{linux,windows,macos}",
"watch": "webpack serve --no-optimization-minimize --config ./webpack.dev.config.ts", "package": "electron-forge package",
"webpack": "webpack" "start": "electron-forge start",
"make": "electron-forge make"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
@ -50,16 +42,50 @@
}, },
"author": "Balena Ltd. <hello@balena.io>", "author": "Balena Ltd. <hello@balena.io>",
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "dependencies": {
"@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.0.9", "@electron/remote": "^2.0.9",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@sentry/electron": "^4.1.2", "@sentry/electron": "^4.1.2",
"analytics-client": "^2.0.1",
"axios": "^0.27.2",
"d3": "4.13.0",
"debug": "4.3.4",
"electron-squirrel-startup": "^1.0.0",
"electron-updater": "5.3.0",
"etcher-sdk": "8.3.1",
"i18next": "21.10.0",
"immutable": "3.8.2",
"lodash": "4.17.21",
"node-ipc": "9.2.1",
"outdent": "0.8.0",
"path-is-inside": "1.0.2",
"pretty-bytes": "5.6.0",
"react": "16.8.5",
"react-dom": "16.8.5",
"react-i18next": "11.18.6",
"redux": "4.2.0",
"rendition": "19.3.2",
"semver": "7.3.8",
"styled-components": "5.3.6",
"sys-class-rgb-led": "3.0.1",
"uuid": "8.3.2"
},
"devDependencies": {
"@balena/lint": "5.4.2",
"@electron-forge/cli": "6.4.2",
"@electron-forge/maker-deb": "6.4.2",
"@electron-forge/maker-dmg": "6.4.2",
"@electron-forge/maker-rpm": "6.4.2",
"@electron-forge/maker-squirrel": "6.4.2",
"@electron-forge/maker-zip": "6.4.2",
"@electron-forge/plugin-auto-unpack-natives": "6.4.2",
"@electron-forge/plugin-webpack": "6.4.2",
"@reforged/maker-appimage": "3.3.1",
"@svgr/webpack": "5.5.0", "@svgr/webpack": "5.5.0",
"@types/chai": "4.3.4", "@types/chai": "4.3.4",
"@types/copy-webpack-plugin": "6.4.3", "@types/copy-webpack-plugin": "6.4.3",
"@types/debug": "^4.1.12",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/mini-css-extract-plugin": "1.4.3", "@types/mini-css-extract-plugin": "1.4.3",
"@types/mocha": "^9.1.1", "@types/mocha": "^9.1.1",
@ -69,62 +95,68 @@
"@types/react-dom": "16.9.17", "@types/react-dom": "16.9.17",
"@types/semver": "7.3.13", "@types/semver": "7.3.13",
"@types/sinon": "9.0.11", "@types/sinon": "9.0.11",
"@types/terser-webpack-plugin": "5.0.4",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/webpack-node-externals": "2.5.3", "@vercel/webpack-asset-relocator-loader": "1.7.3",
"analytics-client": "^2.0.1",
"axios": "^0.27.2",
"chai": "4.3.7", "chai": "4.3.7",
"copy-webpack-plugin": "7.0.0",
"css-loader": "5.2.7", "css-loader": "5.2.7",
"d3": "4.13.0", "electron": "25.8.2",
"debug": "4.3.4",
"electron": "^25.8.2",
"electron-builder": "^23.6.0",
"electron-mocha": "^11.0.2", "electron-mocha": "^11.0.2",
"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", "file-loader": "6.2.0",
"husky": "4.3.8", "husky": "4.3.8",
"i18next": "21.10.0",
"immutable": "3.8.2",
"lint-staged": "10.5.4", "lint-staged": "10.5.4",
"lodash": "4.17.21",
"mini-css-extract-plugin": "1.6.2", "mini-css-extract-plugin": "1.6.2",
"mocha": "^9.1.1", "mocha": "^9.1.1",
"native-addon-loader": "2.0.1", "native-addon-loader": "2.0.1",
"node-ipc": "9.2.1", "node-loader": "^2.0.0",
"omit-deep-lodash": "1.1.7", "omit-deep-lodash": "1.1.7",
"outdent": "0.8.0",
"path-is-inside": "1.0.2",
"pkg": "^5.8.1", "pkg": "^5.8.1",
"pnp-webpack-plugin": "1.7.0",
"pretty-bytes": "5.6.0",
"react": "16.8.5",
"react-dom": "16.8.5",
"react-i18next": "11.18.6",
"redux": "4.2.0",
"rendition": "19.3.2",
"semver": "7.3.8",
"simple-progress-webpack-plugin": "1.1.2",
"sinon": "9.2.4", "sinon": "9.2.4",
"string-replace-loader": "3.1.0", "string-replace-loader": "3.1.0",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"styled-components": "5.3.6", "ts-loader": "^9.5.0",
"sys-class-rgb-led": "3.0.1", "ts-node": "^10.9.1",
"terser-webpack-plugin": "5.3.6",
"ts-loader": "8.4.0",
"ts-node": "9.1.1",
"tslib": "2.4.1", "tslib": "2.4.1",
"typescript": "4.4.4", "typescript": "4.4.4",
"url-loader": "4.1.1", "url-loader": "4.1.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"

View File

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

View File

@ -1,52 +0,0 @@
#!/bin/bash
###
# Copyright 2017 balena.io
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
set -u
set -e
# Read list of wildcards from .gitattributes
wildcards=()
while IFS='' read -r line || [[ -n "$line" ]]; do
if [[ -n "$line" ]]; then
if [[ ! "$line" =~ "^#" ]]; then
filetype=$(echo "$line" | cut -d ' ' -f 2)
if [[ "$filetype" == "text" ]] || [[ "$filetype" == "binary" ]]; then
wildcards+=("$(echo "$line" | cut -d ' ' -f 1)")
fi
fi
fi
done < .gitattributes
# Verify those wildcards against all files stored in the repo
git ls-tree -r HEAD | while IFS='' read line; do
if [[ "$(echo $line | cut -d ' ' -f 2)" == "blob" ]]; then
# the cut delimiter in the line below is actually a tab character, not a space
filename=$(basename $(echo "$line" | cut -d ' ' -f 2))
found_match=0
for wildcard in "${wildcards[@]}"; do
if [[ "$filename" = $wildcard ]]; then
found_match=1
break
fi
done
if [[ $found_match -eq 0 ]]; then
echo "No wildcards match $filename"
exit 1
fi
fi
done

View File

@ -1,52 +0,0 @@
/**
* This script is in charge of cleaning the `shrinkwrap` file.
*
* `npm shrinkwrap` has a bug where it will add optional dependencies
* to `npm-shrinkwrap.json`, therefore causing errors if these optional
* dependendencies are platform dependent and you then try to build
* the project in another platform.
*
* As a workaround, we keep a list of platform dependent dependencies in
* the `platformSpecificDependencies` property of `package.json`,
* and manually remove them from `npm-shrinkwrap.json` if they exist.
*
* See: https://github.com/npm/npm/issues/2679
*/
import { writeFile } from 'fs';
import * as omit from 'omit-deep-lodash';
import * as path from 'path';
import { promisify } from 'util';
import * as shrinkwrap from '../npm-shrinkwrap.json';
import * as packageInfo from '../package.json';
const writeFileAsync = promisify(writeFile);
const JSON_INDENT = 2;
const SHRINKWRAP_FILENAME = path.join(__dirname, '..', 'npm-shrinkwrap.json');
async function main() {
try {
const cleaned = omit(shrinkwrap, packageInfo.platformSpecificDependencies);
for (const item of Object.values(cleaned.dependencies)) {
// @ts-ignore
item.dev = true;
}
await writeFileAsync(
SHRINKWRAP_FILENAME,
JSON.stringify(cleaned, null, JSON_INDENT),
);
} catch (error: any) {
console.log(`[ERROR] Couldn't write shrinkwrap file: ${error.stack}`);
process.exitCode = 1;
}
console.log(
`[OK] Wrote shrinkwrap file to ${path.relative(
__dirname,
SHRINKWRAP_FILENAME,
)}`,
);
}
main();

@ -1 +0,0 @@
Subproject commit 8dfa21cfc23b1dbc0eaa22b5dbdf1f5c796b0c2c

View File

@ -11,8 +11,7 @@
"module": "CommonJS", "module": "CommonJS",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true
"outDir": "build"
}, },
"include": ["lib/util"] "include": ["lib/util"]
} }

View File

@ -1,28 +0,0 @@
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"module": "es2015",
"target": "es2019",
"jsx": "react",
"typeRoots": ["./node_modules/@types", "./typings"],
"importHelpers": true,
"allowSyntheticDefaultImports": true,
"lib": ["dom", "esnext"],
"declaration": true,
"declarationMap": true,
"pretty": true,
"sourceMap": true,
"baseUrl": "./src",
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true
},
"include": [
"lib/**/*.ts",
"node_modules/electron/**/*.d.ts"
]
}

View File

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

View File

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

View File

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

View File

@ -14,48 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import * as CopyPlugin from 'copy-webpack-plugin'; import type { Configuration, ModuleOptions } from 'webpack';
import * as _ from 'lodash'; 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;
@ -75,28 +41,34 @@ function replace(test: RegExp, ...replacements: ReplacementRule[]) {
}; };
} }
const commonConfig = { const rules: Required<ModuleOptions>['rules'] = [
mode: 'production', // Add support for native node modules
optimization: { {
moduleIds: 'natural', // We're specifying native_modules in the test because the asset relocator loader generates a
minimize: true, // "fake" .node file which is really a cjs file.
minimizer: [ test: /native_modules[/\\].+\.node$/,
new TerserPlugin({ use: 'node-loader',
parallel: true, },
terserOptions: { {
compress: false, test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
mangle: false, parser: { amd: false },
format: { use: {
comments: false, loader: '@vercel/webpack-asset-relocator-loader',
ecma: 2020, options: {
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'],
@ -104,45 +76,23 @@ const commonConfig = {
{ {
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',
}), }),
], ];
},
resolve: { export const rendererConfig: Configuration = {
extensions: ['.js', '.json', '.ts', '.tsx'], module: {
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(
@ -157,62 +107,25 @@ const commonConfig = {
new IgnorePlugin({ new IgnorePlugin({
resourceRegExp: /^aws-crt$/, resourceRegExp: /^aws-crt$/,
}), }),
],
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
output: {
path: path.join(__dirname, 'generated'),
filename: '[name].js',
},
externals: [
// '../package.json' because we are in 'generated'
externalPackageJson('../package.json'),
],
};
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,
}, },
}; };
const etcherConfig = { export const mainConfig: Configuration = {
...mainConfig,
entry: { entry: {
etcher: path.join(__dirname, 'lib', 'gui', 'etcher.ts'), etcher: './lib/gui/etcher.ts',
},
module: {
rules,
},
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
}, },
}; };
export default [guiConfig, etcherConfig];

View File

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