Compare commits

..

No commits in common. "master" and "v1.0.0-beta.3" have entirely different histories.

246 changed files with 12915 additions and 63068 deletions

View File

@ -1,2 +0,0 @@
*
!requirements.txt

View File

@ -7,15 +7,4 @@ indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[*.ts]
indent_style = tab
[*.tsx]
indent_style = tab
insert_final_newline = true

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",
},
};

68
.gitattributes vendored
View File

@ -1,68 +0,0 @@
# default
* text
# Javascript files must retain LF line-endings (to keep eslint happy)
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
# CSS and SCSS files must retain LF line-endings (to keep ensure-staged-sass.sh happy)
*.css text eol=lf
*.scss text eol=lf
# Text files
Dockerfile* text
.dockerignore text
.editorconfig text
etcher text
.git* text
*.html text
*.json text eol=lf
*.cpp text
*.h text
*.gyp text
LICENSE text
Makefile text
*.md text
*.sh text
*.bat text
*.svg text
*.yml text
*.patch text
*.txt text
*.tpl text
CODEOWNERS text
*.plist text
# Binary files (no line-ending conversions)
*.bz2 binary diff=hex
*.gz binary diff=hex
*.icns binary diff=hex
*.ico binary diff=hex
*.tiff binary diff=hex
*.img binary diff=hex
*.iso binary diff=hex
*.png binary diff=hex
*.bin binary diff=hex
*.elf binary diff=hex
*.xz binary diff=hex
*.zip binary diff=hex
*.dtb binary diff=hex
*.dtbo binary diff=hex
*.dat binary diff=hex
*.bin binary diff=hex
*.dmg binary diff=hex
*.rpi-sdcard binary diff=hex
*.wic binary diff=hex
*.foo binary diff=hex
*.eot binary diff=hex
*.otf binary diff=hex
*.woff binary diff=hex
*.woff2 binary diff=hex
*.ttf binary diff=hex
xz-without-extension binary diff=hex
wmic-output.txt binary diff=hex
# gitsecret
*.secret binary
.gitsecret/** binary

View File

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

View File

@ -1,205 +0,0 @@
---
name: package and publish GitHub (draft) release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: "JSON stringified object containing all the inputs from the calling workflow"
required: true
secrets:
description: "JSON stringified object containing all the secrets from the calling workflow"
required: true
# --- custom environment
NODE_VERSION:
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: "20.x"
VERBOSE:
type: string
default: "true"
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: "composite"
steps:
- name: Download custom source artifact
uses: actions/download-artifact@v4
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}
- name: Extract custom source artifact
if: runner.os != 'Windows'
shell: bash
working-directory: .
run: tar -xf ${{ runner.temp }}/custom.tgz
- name: Extract custom source artifact
if: runner.os == 'Windows'
shell: pwsh
working-directory: .
run: C:\"Program Files"\Git\usr\bin\tar.exe --force-local -xf ${{ runner.temp }}\custom.tgz
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.NODE_VERSION }}
cache: npm
- name: Install host dependencies
if: runner.os == 'Linux'
shell: bash
run: sudo apt-get install -y --no-install-recommends fakeroot dpkg rpm
# rpmbuild will strip binaries by default, which breaks the sidecar.
# Use a macro to override the "strip" to bypass stripping.
- name: Configure rpmbuild to not strip executables
if: runner.os == 'Linux'
shell: bash
run: echo '%__strip /usr/bin/true' > ~/.rpmmacros
- 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://dev.to/rwwagner90/signing-electron-apps-with-github-actions-4cof
- name: Import Apple code signing certificate
if: runner.os == 'macOS'
shell: bash
run: |
KEY_CHAIN=build.keychain
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
if: runner.os == 'Windows'
id: import_win_signing_cert
shell: powershell
run: |
Set-Content -Path ${{ runner.temp }}/certificate.base64 -Value $env:SM_CLIENT_CERT_FILE_B64
certutil -decode ${{ runner.temp }}/certificate.base64 ${{ runner.temp }}/Certificate_pkcs12.p12
Remove-Item -path ${{ runner.temp }} -include certificate.base64
echo "certFilePath=${{ runner.temp }}/Certificate_pkcs12.p12" >> $GITHUB_OUTPUT
env:
SM_CLIENT_CERT_FILE_B64: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_FILE_B64 }}
- name: Package release
shell: bash
# IMPORTANT: before making changes to this step please consult @engineering in balena's chat.
run: |
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
# export DEBUG='electron-forge:*,sidecar'
# fi
APPLICATION_VERSION="$(jq -r '.version' package.json)"
HOST_ARCH="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
if [[ "${RUNNER_OS}" == Linux ]]; then
PLATFORM=Linux
SHA256SUM_BIN=sha256sum
elif [[ "${RUNNER_OS}" == macOS ]]; then
PLATFORM=Darwin
SHA256SUM_BIN='shasum -a 256'
elif [[ "${RUNNER_OS}" == Windows ]]; then
PLATFORM=Windows
SHA256SUM_BIN=sha256sum
# Install DigiCert Signing Manager Tools
curl --silent --retry 3 --fail https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download \
-H "x-api-key:$SM_API_KEY" \
-o smtools-windows-x64.msi
msiexec -i smtools-windows-x64.msi -qn
PATH="/c/Program Files/DigiCert/DigiCert One Signing Manager Tools:${PATH}"
smksp_registrar.exe list
smctl.exe keypair ls
/c/Windows/System32/certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
# (signtool.exe) https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md#installed-windows-sdks
PATH="/c/Program Files (x86)/Windows Kits/10/bin/${runner_arch}:${PATH}"
else
echo "ERROR: unexpected runner OS: ${RUNNER_OS}"
exit 1
fi
# Currently, we can only build for the host architecture.
npx electron-forge make
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:
# ensure we sign the artifacts
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_TEAM_ID: ${{ fromJSON(inputs.secrets).XCODE_APP_LOADER_TEAM_ID }}
# Windows signing
SM_CLIENT_CERT_PASSWORD: ${{ fromJSON(inputs.secrets).SM_CLIENT_CERT_PASSWORD }}
SM_CLIENT_CERT_FILE: '${{ runner.temp }}\Certificate_pkcs12.p12'
SM_HOST: ${{ fromJSON(inputs.secrets).SM_HOST }}
SM_API_KEY: ${{ fromJSON(inputs.secrets).SM_API_KEY }}
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ fromJSON(inputs.secrets).SM_CODE_SIGNING_CERT_SHA1_HASH }}
TIMESTAMP_SERVER: http://timestamp.digicert.com
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: gh-release-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: dist
retention-days: 1
if-no-files-found: error

View File

@ -1,87 +0,0 @@
---
name: test release
# https://github.com/product-os/flowzone/tree/master/.github/actions
inputs:
json:
description: 'JSON stringified object containing all the inputs from the calling workflow'
required: true
secrets:
description: 'JSON stringified object containing all the secrets from the calling workflow'
required: true
# --- custom environment
NODE_VERSION:
type: string
default: '20.10'
VERBOSE:
type: string
default: 'true'
runs:
# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
using: 'composite'
steps:
# https://github.com/actions/setup-node#caching-global-packages-data
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ inputs.NODE_VERSION }}
cache: npm
- name: Install host dependencies
if: runner.os == 'Linux'
shell: bash
run: |
sudo apt-get update && sudo apt-get install -y --no-install-recommends xvfb libudev-dev
cat < package.json | jq -r '.hostDependencies[][]' - | \
xargs -L1 echo | sed 's/|//g' | xargs -L1 \
sudo apt-get --ignore-missing install || true
- 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: |
## FIXME: causes issues with `xxhash` which tries to load a debug build which doens't exist and cannot be compiled
# if [[ '${{ inputs.VERBOSE }}' =~ on|On|Yes|yes|true|True ]]; then
# export DEBUG='electron-forge:*,sidecar'
# fi
npm ci
# as the shrinkwrap might have been done on mac/linux, this is ensure the package is there for windows
if [[ "$RUNNER_OS" == "Windows" ]]; then
npm i -D winusb-driver-generator
fi
npm run lint
npm run package
npm run wdio # test stage, note that it requires the package to be done first
env:
# https://www.electronjs.org/docs/latest/api/environment-variables
ELECTRON_NO_ATTACH_CONSOLE: 'true'
- name: Compress custom source
if: runner.os != 'Windows'
shell: bash
run: tar -acf ${{ runner.temp }}/custom.tgz .
- name: Compress custom source
if: runner.os == 'Windows'
shell: pwsh
run: C:\"Program Files"\Git\usr\bin\tar.exe --force-local -acf ${{ runner.temp }}\custom.tgz .
- name: Upload custom artifact
uses: actions/upload-artifact@v4
with:
name: custom-${{ github.event.pull_request.head.sha || github.event.head_commit.id }}-${{ runner.os }}-${{ runner.arch }}
path: ${{ runner.temp }}/custom.tgz
retention-days: 1

View File

@ -1,41 +0,0 @@
name: Flowzone
on:
pull_request:
types: [opened, synchronize, closed]
branches: [main, master]
# allow external contributions to use secrets within trusted code
pull_request_target:
types: [opened, synchronize, closed]
branches: [main, master]
jobs:
flowzone:
name: Flowzone
uses: product-os/flowzone/.github/workflows/flowzone.yml@master
# prevent duplicate workflows and only allow one `pull_request` or `pull_request_target` for
# internal or external contributions respectively
if: |
(github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request') ||
(github.event.pull_request.head.repo.full_name != github.repository && github.event_name == 'pull_request_target')
secrets: inherit
with:
custom_test_matrix: >
{
"os": [
["ubuntu-22.04"],
["windows-2019"],
["macos-13"],
["macos-latest-xlarge"]
]
}
custom_publish_matrix: >
{
"os": [
["ubuntu-22.04"],
["windows-2019"],
["macos-13"],
["macos-latest-xlarge"]
]
}
restrict_custom_actions: false
github_prerelease: true
cloudflare_website: "etcher"

View File

@ -1,14 +0,0 @@
name: Publish to WinGet
on:
release:
types: [released]
jobs:
publish:
runs-on: windows-latest # action can only be run on windows
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: Balena.Etcher
# matches something like "balenaEtcher-1.19.0.Setup.exe"
installers-regex: 'balenaEtcher-[\d.-]+\.Setup.exe$'
token: ${{ secrets.WINGET_PAT }}

101
.gitignore vendored
View File

@ -1,103 +1,35 @@
# -- ADD NEW ENTRIES AT THE END OF THE FILE ---
# Logs
logs
*.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
pids
*.pid
*.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
bower_components
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.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/
# Compiled Electron releases
etcher-release/
# Certificates
*.spc
@ -106,18 +38,3 @@ dist/
*.cer
*.crt
*.pem
# Secrets
.gitsecret/keys/random_seed
!*.secret
secrets/APPLE_SIGNING_PASSWORD.txt
secrets/WINDOWS_SIGNING_PASSWORD.txt
secrets/XCODE_APP_LOADER_PASSWORD.txt
secrets/WINDOWS_SIGNING.pfx
# Image stream output directory
/tests/image-stream/output
#local development
.yalc
yalc.lock

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
secrets/APPLE_SIGNING_PASSWORD.txt:5c9cfeb1ea5142b547bc842cc6e0b4a932641ae9811ee47abe2c3953f2a4de5d
secrets/WINDOWS_SIGNING_PASSWORD.txt:852e431628494f2559793c39cf09c34e9406dd79bb15b90c9f88194020470568
secrets/XCODE_APP_LOADER_PASSWORD.txt:005eb9a3c7035c77232973c9355468fc396b94e62783fb8e6dce16bce95b94a1
secrets/WINDOWS_SIGNING.pfx:929f401db38733ffc41572539de7c0d938023af51ed06c205a72a71c1f815714
secrets/APPLE_SIGNING.p12:61abf7b4ff2eec76ce889d71bcdd568b99a6a719b4947ac20f03966265b0946a

9
.hound.yml Normal file
View File

@ -0,0 +1,9 @@
javascript:
config_file: .jshintrc
jscs:
enabled: true
config_file: .jscsrc
scss:
enabled: false

306
.jscsrc Normal file
View File

@ -0,0 +1,306 @@
{
"jsDoc": {
"checkAnnotations": {
"preset": "jsdoc3",
"extra": {
"fulfil": true
}
},
"checkParamExistence": true,
"checkParamNames": true,
"requireParamTypes": true,
"checkReturnTypes": true,
"checkRedundantReturns": true,
"requireReturnTypes": true,
"checkTypes": "capitalizedNativeCase",
"checkRedundantAccess": true,
"requireHyphenBeforeDescription": true,
"requireNewlineAfterDescription": true,
"requireDescriptionCompleteSentence": true,
"requireParamDescription": true,
"checkRedundantParams": true
},
"disallowAnonymousFunctions": false,
"disallowShorthandArrowFunctions": true,
"disallowCapitalizedComments": false,
"disallowCommaBeforeLineBreak": false,
"disallowCurlyBraces": false,
"disallowDanglingUnderscores": false,
"disallowEmptyBlocks": true,
"disallowFunctionDeclarations": true,
"disallowIdenticalDestructuringNames": true,
"disallowImplicitTypeConversion": [
"numeric",
"boolean",
"binary",
"string"
],
"disallowKeywordsOnNewLine": [
"else",
"else if"
],
"disallowKeywords": [
"with",
"for"
],
"disallowMixedSpacesAndTabs": true,
"disallowMultiLineTernary": true,
"disallowMultipleLineBreaks": true,
"disallowMultipleLineStrings": true,
"disallowMultipleSpaces": true,
"disallowMultipleVarDecl": true,
"disallowNamedUnassignedFunctions": true,
"disallowNestedTernaries": false,
"disallowNewlineBeforeBlockStatements": true,
"disallowNodeTypes": [
"LabeledStatement"
],
"disallowNotOperatorsInConditionals": false,
"disallowObjectKeysOnNewLine": false,
"disallowOperatorBeforeLineBreak": [
"+",
"."
],
"disallowPaddingNewLinesAfterBlocks": false,
"disallowPaddingNewLinesAfterUseStrict": false,
"disallowPaddingNewLinesBeforeExport": false,
"disallowPaddingNewlinesBeforeKeywords": false,
"disallowPaddingNewLinesBeforeLineComments": false,
"disallowPaddingNewlinesInBlocks": false,
"disallowPaddingNewLinesInObjects": false,
"disallowParenthesesAroundArrowParam": false,
"disallowQuotedKeysInObjects": {
"allExcept": [
"reserved"
]
},
"disallowSemicolons": false,
"disallowShorthandArrowFunctions": true,
"disallowSpaceAfterBinaryOperators": false,
"disallowSpaceAfterComma": false,
"disallowSpaceAfterKeywords": false,
"disallowSpaceAfterLineComment": false,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpaceBeforeBinaryOperators": false,
"disallowSpaceBeforeBlockStatements": false,
"disallowSpaceBeforeComma": true,
"disallowSpaceBeforeKeywords": false,
"disallowSpaceBeforeObjectValues": false,
"disallowSpaceBeforePostfixUnaryOperators": [
"++",
"--"
],
"disallowSpaceBeforeSemicolon": true,
"disallowSpaceBetweenArguments": false,
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpacesInConditionalExpression": false,
"disallowSpacesInForStatement": false,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInFunction": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInGenerator": {
"beforeStar": true
},
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": false,
"disallowSpacesInsideBrackets": false,
"disallowSpacesInsideObjectBrackets": false,
"disallowSpacesInsideParentheses": true,
"disallowSpacesInsideParenthesizedExpression": true,
"disallowTabs": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
"disallowUnusedParams": true,
"disallowVar": false,
"disallowYodaConditions": [
"==",
"===",
"!=",
"!=="
],
"maximumLineLength": 130,
"maximumNumberOfLines": 500,
"requireAlignedMultilineParams": true,
"requireAlignedObjectValues": false,
"requireAnonymousFunctions": true,
"requireArrayDestructuring": false,
"requireArrowFunctions": false,
"requireBlocksOnNewline": {
"includeComments": true
},
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireCapitalizedComments": true,
"requireCapitalizedConstructors": true,
"requireCommaBeforeLineBreak": true,
"requireCurlyBraces": [
"if",
"else",
"for",
"while",
"do",
"try",
"catch",
"case",
"default"
],
"requireDollarBeforejQueryAssignment": true,
"requireDotNotation": true,
"requireEarlyReturn": true,
"requireEnhancedObjectLiterals": false,
"requireFunctionDeclarations": false,
"requireLineBreakAfterVariableAssignment": true,
"requireLineFeedAtFileEnd": true,
"requireMatchingFunctionName": true,
"requireMultiLineTernary": false,
"requireMultipleVarDecl": false,
"requireNewlineBeforeBlockStatements": false,
"requireNumericLiterals": true,
"requireObjectDestructuring": false,
"requireObjectKeysOnNewLine": true,
"requireOperatorBeforeLineBreak": [
"?",
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"requirePaddingNewLineAfterVariableDeclaration": false,
"requirePaddingNewLinesAfterBlocks": true,
"requirePaddingNewLinesAfterUseStrict": true,
"requirePaddingNewLinesBeforeExport": true,
"requirePaddingNewlinesBeforeKeywords": [
"do",
"for",
"if",
"while"
],
"requirePaddingNewLinesBeforeLineComments": true,
"requirePaddingNewlinesInBlocks": false,
"requirePaddingNewLinesInObjects": true,
"requireParenthesesAroundArrowParam": true,
"requireParenthesesAroundIIFE": true,
"requireQuotedKeysInObjects": false,
"requireSemicolons": true,
"requireShorthandArrowFunctions": false,
"requireSpaceAfterBinaryOperators": [
"=",
",",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!=="
],
"requireSpaceBeforeBinaryOperators": [
"=",
"+",
"-",
"/",
"*",
"==",
"===",
"!=",
"!=="
],
"requireSpaceAfterComma": true,
"requireSpaceAfterKeywords": [
"do",
"for",
"if",
"else",
"switch",
"case",
"try",
"catch",
"void",
"while",
"with",
"return",
"typeof"
],
"requireSpaceAfterLineComment": false,
"requireSpaceAfterObjectKeys": false,
"requireSpaceAfterPrefixUnaryOperators": false,
"requireSpaceBeforeBlockStatements": 1,
"requireSpaceBeforeComma": false,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"requireSpaceBeforeObjectValues": true,
"requireSpaceBeforePostfixUnaryOperators": false,
"requireSpaceBetweenArguments": true,
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInCallExpression": false,
"requireSpacesInConditionalExpression": {
"afterTest": true,
"beforeConsequent": true,
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunctionDeclaration": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInGenerator": {
"afterStar": true
},
"requireSpacesInNamedFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"requireSpacesInsideArrayBrackets": "all",
"requireSpacesInsideObjectBrackets": "all",
"requireSpacesInsideParentheses": false,
"requireSpacesInsideParenthesizedExpression": false,
"requireSpread": false,
"requireTemplateStrings": false,
"requireTrailingComma": false,
"requireVarDeclFirst": false,
"requireYodaConditions": false,
"safeContextKeyword": [
"self"
],
"validateAlignedFunctionParameters": false,
"validateCommentPosition": {
"position": "above"
},
"validateIndentation": 2,
"validateLineBreaks": "LF",
"validateNewlineAfterArrayElements": true,
"validateOrderInObjectKeys": false,
"validateParameterSeparator": ", ",
"validateQuoteMarks": "'"
}

120
.jshintrc Normal file
View File

@ -0,0 +1,120 @@
{
// --------------------------------------------------------------------
// JSHint Configuration, Strict Edition
// --------------------------------------------------------------------
//
// This is a options template for [JSHint][1], using [JSHint example][2]
// and [Ory Band's example][3] as basis and setting config values to
// be most strict:
//
// * set all enforcing options to true
// * set all relaxing options to false
// * set all environment options to false, except the browser value
// * set all JSLint legacy options to false
//
// [1]: http://www.jshint.com/
// [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json
// [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc
//
// @author http://michael.haschke.biz/
// @license http://unlicense.org/
"esversion" : 6,
// == Enforcing Options ===============================================
//
// These options tell JSHint to be more strict towards your code. Use
// them if you want to allow only a safe subset of JavaScript, very
// useful when your codebase is shared with a big number of developers
// with different skill levels.
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
"curly" : true, // Require {} for every new block or scope.
"eqeqeq" : true, // Require triple equals i.e. `===`.
"forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef" : true, // Prohibit variable use before definition.
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : true, // Prohibit use of constructors for side-effects.
"plusplus" : true, // Prohibit use of `++` & `--`.
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
"undef" : true, // Require all non-global variables be declared before they are used.
"strict" : true, // Require `use strict` pragma in every file.
"trailing" : true, // Prohibit trailing whitespaces.
"maxlen" : 130, // Limit line length.
"unused" : true, // Prohibit unused variables.
"undef" : true, // Prohibit undefined variables.
// == Relaxing Options ================================================
//
// These options allow you to suppress certain types of warnings. Use
// them only if you are absolutely positive that you know what you are
// doing.
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // Tolerate use of `== null`.
"esnext" : true, // Allow ES.next specific features such as `const` and `let`.
"evil" : false, // Tolerate use of `eval`.
"expr" : true, // Tolerate `ExpressionStatement` as Programs.
"funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
"globalstrict" : true, // Allow global "use strict" (also enables 'strict').
"iterator" : false, // Allow usage of __iterator__ property.
"lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block.
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"laxcomma" : false, // Suppress warnings about comma-first coding style.
"loopfunc" : false, // Allow functions to be defined within loops.
"multistr" : false, // Tolerate multi-line strings.
"onecase" : false, // Tolerate switches with just one case.
"proto" : false, // Tolerate __proto__ property. This property is deprecated.
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
"scripturl" : false, // Tolerate script-targeted URLs.
"smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
"validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.
// == Environments ====================================================
//
// These options pre-define global variables that are exposed by
// popular JavaScript libraries and runtime environments—such as
// browser or node.js.
"browser" : true, // Standard browser globals e.g. `window`, `document`.
"couch" : false, // Enable globals exposed by CouchDB.
"devel" : false, // Allow development statements e.g. `console.log();`.
"dojo" : false, // Enable globals exposed by Dojo Toolkit.
"esnext" : false, // Enable globals exposed by ES6.
"mocha" : true, // Enable globals exposed by Mocha.
"jquery" : false, // Enable globals exposed by jQuery JavaScript library.
"mootools" : false, // Enable globals exposed by MooTools JavaScript framework.
"node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment.
"nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape.
"prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework.
"rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment.
"wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host.
// == JSLint Legacy ===================================================
//
// These options are legacy from JSLint. Aside from bug fixes they will
// not be improved in any way and might be removed at any point.
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"passfail" : false, // Stop on first error.
"white" : false, // Check against strict whitespace and indentation rules.
// == Undocumented Options ============================================
//
// While I've found these options in [example1][2] and [example2][3]
// they are not described in the [JSHint Options documentation][4].
//
// [4]: http://www.jshint.com/options/
"maxerr" : 100, // Maximum errors before stopping.
"indent" : 4 // Specify indentation spacing
}

1
.nvmrc
View File

@ -1 +0,0 @@
18

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"),
);

39
.travis.yml Normal file
View File

@ -0,0 +1,39 @@
language: cpp
sudo: false
env:
- NODE_VERSION="4"
os:
- linux
- osx
before_install:
- rm -rf ~/.nvm
- git clone https://github.com/creationix/nvm.git ~/.nvm
- source ~/.nvm/nvm.sh
- nvm --version
- nvm install $NODE_VERSION
- node --version
- npm --version
- npm config set spin=false
install:
- npm install
before_script:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export DISPLAY=:99.0;
sh -e /etc/init.d/xvfb start;
fi
script:
- npm test
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/8b150eaf525c280ec2ac
on_success: change
on_failure: always
on_start: never

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

93
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,93 @@
# Contributing guide
Thanks for your interest in contributing to this project! This document aims to serve as a friendly guide for making your first contribution.
## Prerequisites
- [NodeJS](https://nodejs.org).
- [Bower](http://bower.io).
- [Gulp](http://gulpjs.com).
## Cloning
First, clone the repository:
``` shell
$ git clone https://github/resin-io/etcher
```
Make sure you install all the dependencies before attempting to run the application:
``` shell
$ npm install && bower install
```
## Developing
We rely on [gulp](http://gulpjs.com) to provide an automated developing workflow in which your changes will automatically be detected and the necessary resources will be rebuilt for you.
First make sure you have [gulp](http://gulpjs.com) installed as a global dependency:
``` shell
$ npm install -g gulp
```
Run the `watch` task to initialise the build system. We encourage to have this command running in the background all the time as you develop, and check the output from time to time, since it'll let you know of any issues and/or warnings in your changes:
``` javascript
$ gulp watch
```
We make use of [EditorConfig](http://editorconfig.org) to communicate indentation, line endings and 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 process.
## Running
You can run the application with the following command in the root of the project:
``` shell
$ npm start
```
## Testing
We include a test suite that covers both the code running in the main process and in the rendered process.
In order to avoid inaccurate results, the test suites run in a real Electron instance each in the respective process. This means that running the test suite is not a cheap operation and therefore we decided to not run it by default in the `watch` gulp task to not disrupt the user development workflow.
To run the test suite, run the following command:
``` shell
npm test
```
Given the nature of this application, not everything can be unit tested. For example:
- The writing operating on real raw devices.
- Platform inconsistencies.
- Style changes.
- Artwork.
We encourage our contributors to test the application on as many operating systems as they can before sending a pull request.
*The test suite is run automatically by CI servers when you send a pull request.*
## Sending a pull request
When sending a pull request, consider the following guidelines:
- Write a concise commit message explaining your changes.
- If applies, write more descriptive information in the commit body.
- Mention the operating systems with the corresponding versions in which you tested your changes.
- If your change affects the visuals of the application, consider attaching a screenshot.
- Refer to the issue/s your pull request fixes, so they're closed automatically when your pull request is merged.
- Write a descriptive pull request title.
- Squash commits when possible, for example, when commiting review changes.
Before your pull request can be merged, the following conditions must hold:
- The linter doesn't throw any warning.
- All the tests passes.
- The coding style aligns with the project's convention.
- Your changes are confirmed to be working in recent versions of the operating systems we support.
Don't hesitate to get in touch if you have any questions or need any help!

264
Makefile Normal file
View File

@ -0,0 +1,264 @@
ELECTRON_PACKAGER=./node_modules/.bin/electron-packager
ELECTRON_BUILDER=./node_modules/.bin/electron-builder
ELECTRON_OSX_SIGN=./node_modules/.bin/electron-osx-sign
ELECTRON_IGNORE=$(shell node scripts/packageignore.js)
ELECTRON_VERSION=0.36.11
ETCHER_VERSION=$(shell node -e "console.log(require('./package.json').version)")
APPLICATION_NAME=$(shell node -e "console.log(require('./package.json').displayName)")
APPLICATION_DESCRIPTION=$(shell node -e "console.log(require('./package.json').description)")
APPLICATION_COPYRIGHT=$(shell node -e "console.log(require('./package.json').copyright)")
COMPANY_NAME="Resinio Ltd"
SIGN_IDENTITY_OSX="Developer ID Application: Rulemotion Ltd (66H43P8FRG)"
S3_BUCKET="resin-production-downloads"
sign-win32 = osslsigncode sign \
-certs certificate.crt.pem \
-key certificate.key.pem \
-h sha1 \
-t http://timestamp.comodoca.com \
-n "$(APPLICATION_NAME) - $(ETCHER_VERSION)"\
-in $(1) \
-out $(dir $(1))Signed.exe \
&& mv $(dir $(1))Signed.exe $(1)
etcher-release/Etcher-darwin-x64: .
$(ELECTRON_PACKAGER) . $(APPLICATION_NAME) \
--platform=darwin \
--arch=x64 \
--version=$(ELECTRON_VERSION) \
--ignore="$(ELECTRON_IGNORE)" \
--asar \
--app-copyright="$(APPLICATION_COPYRIGHT)" \
--app-version="$(ETCHER_VERSION)" \
--build-version="$(ETCHER_VERSION)" \
--helper-bundle-id="io.resin.etcher-helper" \
--app-bundle-id="io.resin.etcher" \
--app-category-type="public.app-category.developer-tools" \
--icon="assets/icon.icns" \
--overwrite \
--out=$(dir $@)
rm $@/LICENSE
rm $@/LICENSES.chromium.html
rm $@/version
etcher-release/Etcher-linux-x86: .
$(ELECTRON_PACKAGER) . $(APPLICATION_NAME) \
--platform=linux \
--arch=ia32 \
--version=$(ELECTRON_VERSION) \
--ignore="$(ELECTRON_IGNORE)" \
--asar \
--app-version="$(ETCHER_VERSION)" \
--build-version="$(ETCHER_VERSION)" \
--overwrite \
--out=$(dir $@)
mv $(dir $@)Etcher-linux-ia32 $@
mv $@/Etcher $@/etcher
upx -9 $@/etcher $@/libnode.so
etcher-release/Etcher-linux-x64: .
$(ELECTRON_PACKAGER) . $(APPLICATION_NAME) \
--platform=linux \
--arch=x64 \
--version=$(ELECTRON_VERSION) \
--ignore="$(ELECTRON_IGNORE)" \
--asar \
--app-version="$(ETCHER_VERSION)" \
--build-version="$(ETCHER_VERSION)" \
--overwrite \
--out=$(dir $@)
mv $@/Etcher $@/etcher
upx -9 $@/etcher $@/*.so*
etcher-release/Etcher-win32-x86: .
$(ELECTRON_PACKAGER) . $(APPLICATION_NAME) \
--platform=win32 \
--arch=ia32 \
--version=$(ELECTRON_VERSION) \
--ignore="$(ELECTRON_IGNORE)" \
--icon="assets/icon.ico" \
--asar \
--app-copyright="$(APPLICATION_COPYRIGHT)" \
--app-version="$(ETCHER_VERSION)" \
--build-version="$(ETCHER_VERSION)" \
--version-string.CompanyName=$(COMPANY_NAME) \
--version-string.FileDescription="$(APPLICATION_NAME)" \
--version-string.OriginalFilename=$(notdir $@) \
--version-string.ProductName="$(APPLICATION_NAME) -- $(APPLICATION_DESCRIPTION)" \
--version-string.InternalName="$(APPLICATION_NAME)" \
--overwrite \
--out=$(dir $@)
mv $(dir $@)Etcher-win32-ia32 $@
$(call sign-win32,$@/Etcher.exe)
upx -9 $@/*.dll
etcher-release/Etcher-win32-x64: .
$(ELECTRON_PACKAGER) . $(APPLICATION_NAME) \
--platform=win32 \
--arch=x64 \
--version=$(ELECTRON_VERSION) \
--ignore="$(ELECTRON_IGNORE)" \
--icon="assets/icon.ico" \
--asar \
--app-copyright="$(APPLICATION_COPYRIGHT)" \
--app-version="$(ETCHER_VERSION)" \
--build-version="$(ETCHER_VERSION)" \
--version-string.CompanyName=$(COMPANY_NAME) \
--version-string.FileDescription="$(APPLICATION_NAME)" \
--version-string.OriginalFilename=$(notdir $@) \
--version-string.ProductName="$(APPLICATION_NAME) -- $(APPLICATION_DESCRIPTION)" \
--version-string.InternalName="$(APPLICATION_NAME)" \
--overwrite \
--out=$(dir $@)
$(call sign-win32,$@/Etcher.exe)
upx -9 $@/*.dll
sign-osx = $(ELECTRON_OSX_SIGN) $(1) --platform darwin --verbose --identity $(SIGN_IDENTITY_OSX) \
&& codesign --verify --deep --display --verbose=4 $(1) \
&& spctl --ignore-cache --no-cache --assess --type execute --verbose=4 $(1)
etcher-release/installers/Etcher-darwin-x64.zip: etcher-release/Etcher-darwin-x64 package.json
mkdir -p $(dir $@)
$(call sign-osx,$</$(APPLICATION_NAME).app)
pushd $< && zip -r -9 $(notdir $@) $(APPLICATION_NAME).app && popd
mv $</$(notdir $@) $@
etcher-release/installers/Etcher-darwin-x64.dmg: etcher-release/Etcher-darwin-x64 package.json
# Create temporal read-write DMG image
hdiutil create \
-srcfolder $< \
-volname "$(APPLICATION_NAME)" \
-fs HFS+ \
-fsargs "-c c=64,a=16,e=16" \
-format UDRW \
-size 600M $<.dmg
# Mount temporal DMG image, so we can modify it
hdiutil attach $<.dmg -readwrite -noverify
# Wait for a bit to ensure the image is mounted
sleep 2
# Link to /Applications within the DMG
pushd /Volumes/$(APPLICATION_NAME) && ln -s /Applications && popd
# Symlink MacOS/Etcher to MacOS/Electron since for some reason, the Electron
# binary tries to be ran in some systems.
# See https://github.com/Microsoft/vscode/issues/92
cp -p /Volumes/$(APPLICATION_NAME)/$(APPLICATION_NAME).app/Contents/MacOS/Etcher /Volumes/$(APPLICATION_NAME)/$(APPLICATION_NAME).app/Contents/MacOS/Electron
# Set the DMG icon image
# Writing this hexadecimal buffer to the com.apple.FinderInfo
# extended attribute does the trick.
# See https://github.com/LinusU/node-appdmg/issues/14#issuecomment-29080500
cp assets/icon.icns /Volumes/$(APPLICATION_NAME)/.VolumeIcon.icns
xattr -wx com.apple.FinderInfo "0000000000000000040000000000000000000000000000000000000000000000" /Volumes/$(APPLICATION_NAME)
# Configure background image.
# We use tiffutil to create a "Multirepresentation Tiff file".
# This allows us to show the retina and non-retina image when appropriate.
mkdir /Volumes/$(APPLICATION_NAME)/.background
tiffutil -cathidpicheck assets/osx/installer.png assets/osx/installer@2x.png \
-out /Volumes/$(APPLICATION_NAME)/.background/installer.tiff
# This AppleScript performs the following tasks
# - Set the window basic properties.
# - Set the window size and position.
# - Set the icon size.
# - Arrange the icons.
echo ' \
tell application "Finder" \n\
tell disk "$(APPLICATION_NAME)" \n\
open \n\
set current view of container window to icon view \n\
set toolbar visible of container window to false \n\
set statusbar visible of container window to false \n\
set the bounds of container window to {400, 100, 944, 530} \n\
set viewOptions to the icon view options of container window \n\
set arrangement of viewOptions to not arranged \n\
set icon size of viewOptions to 110 \n\
set background picture of viewOptions to file ".background:installer.tiff" \n\
set position of item "$(APPLICATION_NAME).app" of container window to {140, 225} \n\
set position of item "Applications" of container window to {415, 225} \n\
close \n\
open \n\
update without registering applications \n\
delay 2 \n\
close \n\
end tell \n\
end tell \n\
' | osascript
sync
$(call sign-osx,/Volumes/$(APPLICATION_NAME)/$(APPLICATION_NAME).app)
# Unmount temporal DMG image.
hdiutil detach /Volumes/$(APPLICATION_NAME)
# Convert temporal DMG image into a production-ready
# compressed and read-only DMG image.
mkdir -p $(dir $@)
hdiutil convert $<.dmg \
-format UDZO \
-imagekey zlib-level=9 \
-o $@
# Cleanup temporal DMG image.
rm $<.dmg
etcher-release/installers/Etcher-linux-x64.tar.gz: etcher-release/Etcher-linux-x64
mkdir -p $(dir $@)
tar -zcf $@ $<
etcher-release/installers/Etcher-linux-x86.tar.gz: etcher-release/Etcher-linux-x86
mkdir -p $(dir $@)
tar -zcf $@ $<
etcher-release/installers/Etcher-win32-x64.exe: etcher-release/Etcher-win32-x64 package.json
$(ELECTRON_BUILDER) $< \
--platform=win \
--out=$(dir $@)win32-x64
mv $(dir $@)win32-x64/Etcher\ Setup.exe $@
rmdir $(dir $@)win32-x64
$(call sign-win32,$@)
etcher-release/installers/Etcher-win32-x86.exe: etcher-release/Etcher-win32-x86 package.json
$(ELECTRON_BUILDER) $< \
--platform=win \
--out=$(dir $@)win32-x86
mv $(dir $@)win32-x86/Etcher\ Setup.exe $@
rmdir $(dir $@)win32-x86
$(call sign-win32,$@)
package-osx: etcher-release/Etcher-darwin-x64
package-linux: etcher-release/Etcher-linux-x86 etcher-release/Etcher-linux-x64
package-win32: etcher-release/Etcher-win32-x86 etcher-release/Etcher-win32-x64
package-all: package-osx package-linux package-win32
installer-osx: etcher-release/installers/Etcher-darwin-x64.dmg
installer-linux: etcher-release/installers/Etcher-linux-x64.tar.gz etcher-release/installers/Etcher-linux-x86.tar.gz
installer-win32: etcher-release/installers/Etcher-win32-x64.exe etcher-release/installers/Etcher-win32-x86.exe
installer-all: installer-osx installer-linux installer-win32
s3-upload = aws s3api put-object \
--bucket $(S3_BUCKET) \
--acl public-read \
--key etcher/$(ETCHER_VERSION)/$(notdir $(1)) \
--body $(1)
upload-linux-x64: etcher-release/installers/Etcher-linux-x64.tar.gz
$(call s3-upload,$<)
upload-linux-x86: etcher-release/installers/Etcher-linux-x86.tar.gz
$(call s3-upload,$<)
upload-win32-x64: etcher-release/installers/Etcher-win32-x64.exe
$(call s3-upload,$<)
upload-win32-x86: etcher-release/installers/Etcher-win32-x86.exe
$(call s3-upload,$<)
upload-osx: etcher-release/installers/Etcher-darwin-x64.dmg etcher-release/installers/Etcher-darwin-x64.zip
$(call s3-upload,$<)
$(call s3-upload,$(word 2,$^))
upload-linux: upload-linux-x64 upload-linux-x86
upload-win32: upload-win32-x64 upload-win32-x86
upload-all: upload-osx upload-linux upload-win32
release:
rm -rf node_modules/
npm install --force
make upload-all
clean:
rm -rf etcher-release/

85
PUBLISHING.md Normal file
View File

@ -0,0 +1,85 @@
Publishing Etcher
=================
This is a small guide to package and publish Etcher to all supported operating systems.
Prequisites
-----------
- [NodeJS](https://nodejs.org)
- [GNU Make](https://www.gnu.org/software/make/)
- [wine (for Windows)](https://www.winehq.org)
- [nsis (for Windows)](http://nsis.sourceforge.net/Main_Page)
- [XCode (for OS X)](https://developer.apple.com/xcode://developer.apple.com/xcode/)
- [AWS CLI (for uploading packages)](https://aws.amazon.com/cli://aws.amazon.com/cli/)
- [osslsigncode (for signing the Windows installers)](https://sourceforge.net/projects/osslsigncode/)
- [UPX](http://upx.sourceforge.net)
If you're going to generate installers for another platform than the one you're currently running, make sure you force-install all NPM dependencies, so optional dependencies marked for a certain operating system get installed regardless of the host operating system
```sh
npm install --force
```
You can run the following command at any time to start from a fresh state:
```sh
make clean
```
Signing
-------
### OS X
1. Get our Apple Developer ID certificate for signing applications distributed outside the Mac App Store from the Resin.io Apple account.
2. Install the Developer ID certificate to your Mac's Keychain by double clicking on the certificate file.
The application will be signed automatically using this certificate when packaging for OS X.
### Windows
1. Get access to our code signing certificate and decryption key as a Resin.io employee by asking for it to the relevant people.
2. Place the cert and key in the root of the Etcher repository naming them `certificate.crt.pem` and `certificate.key.pem`, respectively.
The application and installer will be signed automatically using these certificates when packaging for Windows.
Packaging
---------
Run the following command to make installers for all supported operating systems:
```sh
make installer-all
```
You can replace `all` with `osx`, `linux` or `win32` to only generate installers for those platforms:
```sh
make installer-osx
make installer-linux
make installer-win32
```
The resulting installers will be saved to `etcher-release/installers`.
Uploading
---------
Make sure you have the [AWS CLI tool](https://aws.amazon.com/cli://aws.amazon.com/cli/) installed and configured to access Resin.io's production downloads S3 bucket.
Run the following command to upload all installers:
```sh
make upload-all
```
As with the `installer` rule, you can replace `all` with `osx`, `linux` or `win32` to only publish those platform's installers:
```sh
make upload-osx
make upload-linux
make upload-win32
```

131
README.md
View File

@ -1,120 +1,45 @@
# Etcher
Etcher
======
> Flash OS images to SD cards & USB drives, safely and easily.
> A better way to flash OS images to SD cards
Etcher is a powerful OS image flasher built with web technologies to ensure
flashing an SDCard or USB drive is a pleasant and safe experience. It protects
you from accidentally writing to your hard-drives, ensures every byte of data
was written correctly, and much more. It can also directly flash Raspberry Pi devices that support [USB device boot mode](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#usb-device-boot-mode).
[![dependencies](https://david-dm.org/resin-io/etcher.svg)](https://david-dm.org/resin-io/etcher.svg)
[![Build Status](https://travis-ci.org/resin-io/etcher.svg?branch=master)](https://travis-ci.org/resin-io/etcher)
[![Build status](https://ci.appveyor.com/api/projects/status/xggqv231byfhync1/branch/master?svg=true)](https://ci.appveyor.com/project/resin-io/etcher/branch/master)
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/resin-io/chat)
[![Stories in Ready](https://badge.waffle.io/resin-io/etcher.svg?label=in progress&title=In Progress)](https://waffle.io/resin-io/etcher)
[![Current Release](https://img.shields.io/github/release/balena-io/etcher.svg?style=flat-square)](https://balena.io/etcher)
[![License](https://img.shields.io/github/license/balena-io/etcher.svg?style=flat-square)](https://github.com/balena-io/etcher/blob/master/LICENSE)
[![Balena.io Forums](https://img.shields.io/discourse/https/forums.balena.io/topics.svg?style=flat-square&label=balena.io%20forums)](https://forums.balena.io/c/etcher)
***
---
[**Download**](http://etcher.io) | [**Support**](https://github.com/resin-io/etcher/blob/master/SUPPORT.md) | [**Contributing**](https://github.com/resin-io/etcher/blob/master/CONTRIBUTING.md) | [**CLI**](https://github.com/resin-io/etcher-cli)
[**Download**][etcher] | [**Support**][support] | [**Documentation**][user-documentation] | [**Contributing**][contributing] | [**Roadmap**][milestones]
![Etcher](https://raw.githubusercontent.com/resin-io/etcher/master/screenshot.png)
## Supported Operating Systems
Installers
----------
- Linux; most distros; Intel 64-bit.
- Windows 10 and later; Intel 64-bit.
- macOS 10.13 (High Sierra) and later; both Intel and Apple Silicon.
Refer to the [downloads page](http://etcher.io) for the latest pre-made installers for all supported operating systems.
## Installers
Developing
----------
Refer to the [downloads page][etcher] for the latest pre-made
installers for all supported operating systems.
## Packages
#### Debian and Ubuntu based Package Repository (GNU/Linux x86/x64)
Package for Debian and Ubuntu can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
##### Install .deb file using apt
```sh
sudo apt install ./balena-etcher_******_amd64.deb
```
##### Uninstall
```sh
sudo apt remove balena-etcher
```
#### Redhat (RHEL) and Fedora-based Package Repository (GNU/Linux x86/x64)
##### Yum
Package for Fedora-based and Redhat can be downloaded from the [Github release page](https://github.com/balena-io/etcher/releases/)
1. Install using yum
You can manually run the application with the following commands:
```sh
sudo yum localinstall balena-etcher-***.x86_64.rpm
git clone https://github.com/resin-io/etcher
cd etcher
npm install && bower install
npm start
```
#### Arch/Manjaro Linux (GNU/Linux x64)
Take a look at our [contributing guide](https://github.com/resin-io/etcher/blob/master/CONTRIBUTING.md).
Etcher is offered through the Arch User Repository and can be installed on both Manjaro and Arch systems. You can compile it from the source code in this repository using [`balena-etcher`](https://aur.archlinux.org/packages/balena-etcher/). The following example uses a common AUR helper to install the latest release:
Support
-------
```sh
yay -S balena-etcher
```
If you're having any problem, please [raise an issue](https://github.com/resin-io/etcher/issues/new) on GitHub and the Resin.io team will be happy to help.
##### Uninstall
License
-------
```sh
yay -R balena-etcher
```
#### WinGet (Windows)
This package is updated by [gh-action](https://github.com/vedantmgoyal2009/winget-releaser), and is kept up to date automatically.
```sh
winget install balenaEtcher #or Balena.Etcher
```
##### Uninstall
```sh
winget uninstall balenaEtcher
```
#### Chocolatey (Windows)
This package is maintained by [@majkinetor](https://github.com/majkinetor), and
is kept up to date automatically.
```sh
choco install etcher
```
##### Uninstall
```sh
choco uninstall etcher
```
## Support
If you're having any problem, please [raise an issue][newissue] on GitHub, and
the balena.io team will be happy to help.
## License
Etcher is free software and may be redistributed under the terms specified in
the [license].
[etcher]: https://balena.io/etcher
[electron]: https://electronjs.org/
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
[support]: https://github.com/balena-io/etcher/blob/master/docs/SUPPORT.md
[contributing]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
[user-documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
[milestones]: https://github.com/balena-io/etcher/milestones
[newissue]: https://github.com/balena-io/etcher/issues/new
[license]: https://github.com/balena-io/etcher/blob/master/LICENSE
Etcher is free software, and may be redistributed under the terms specified in the [license](https://github.com/resin-io/etcher/blob/master/LICENSE).

13
SUPPORT.md Normal file
View File

@ -0,0 +1,13 @@
Getting help with Etcher
========================
There are various ways get support for Etcher if you experience an issue or have an idea you'd like to share with us.
Gitter
------
We have a [Gitter chat room](https://gitter.im/resin-io/chat) for Resin.io which is open to everyone, please come join us :). The room covers Resin.io as a whole, including but not limited to Etcher. Drop us a line there and the Resin.io staff and community users will be happy to assist.
GitHub
------
If you encounter an issue or have a suggestion, head on over to Etcher's [issue tracker](https://github.com/resin-io/etcher/issues) and if there isn't a ticket covering it, [create one](https://github.com/resin-io/etcher/issues/new).

View File

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

35
appveyor.yml Normal file
View File

@ -0,0 +1,35 @@
# appveyor file
# http://www.appveyor.com/docs/appveyor-yml
init:
- git config --global core.autocrlf input
cache:
- C:\Users\appveyor\.node-gyp
- '%AppData%\npm-cache'
# what combinations to test
environment:
matrix:
- nodejs_version: 4
install:
- ps: Install-Product node $env:nodejs_version x64
- npm -g install npm@2
- set PATH=%APPDATA%\npm;%PATH%
- npm install
build: off
test_script:
- node --version
- npm --version
- cmd: npm test
notifications:
- provider: Webhook
url: https://webhooks.gitter.im/e/3d5ed28fa9ae4c25f46f
on_build_success: false
on_build_failure: true
on_build_status_changed: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

BIN
assets/icon.icns Executable file → Normal file

Binary file not shown.

BIN
assets/icon.ico Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

BIN
assets/icon.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

18
assets/images/drive.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 134.229 134.229" enable-background="new 0 0 134.229 134.229"
xml:space="preserve">
<g>
<g>
<path fill="#FFFFFF" d="M21.343,112.528c2.317,0,4.195,1.875,4.195,4.189c0,2.319-1.878,4.201-4.195,4.201
c-2.32,0-4.199-1.882-4.199-4.201C17.144,114.403,19.022,112.528,21.343,112.528z"/>
<path fill="#FFFFFF" d="M131.246,110.53L119.604,5.8C119.25,2.615,116.047,0,112.48,0H21.754c-3.568,0-6.777,2.615-7.127,5.8
L2.984,110.53c0,0.129-0.061,0.232-0.061,0.359v11.667c0,6.437,5.237,11.673,11.667,11.673h105.05
c6.431,0,11.667-5.236,11.667-11.673v-11.667C131.307,110.762,131.246,110.652,131.246,110.53z M125.474,122.556
c0,3.222-2.631,5.84-5.84,5.84H14.59c-3.206,0-5.836-2.618-5.836-5.84v-11.667c0-3.221,2.63-5.839,5.836-5.839h105.05
c3.203,0,5.834,2.618,5.834,5.839V122.556L125.474,122.556z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

18
assets/images/etcher.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

28
assets/images/flash.svg Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="500.028 0 999.944 999.986" enable-background="new 500.028 0 999.944 999.986"
xml:space="preserve">
<g>
<g>
<path fill="#FFFFFF" d="M1174.844,499.923c0-96.492-78.443-174.991-174.991-174.991c-96.492,0-174.991,78.498-174.991,174.991
s78.498,174.991,174.991,174.991c50.949,0,96.548-22.254,128.442-57.25l-29.505-94.243l67.853,26.292
C1171.491,533.927,1174.844,517.428,1174.844,499.923z M999.853,599.922c-55.196,0-99.999-44.802-99.999-99.999
s44.802-99.999,99.999-99.999c55.196,0,99.999,44.802,99.999,99.999S1055.091,599.922,999.853,599.922z"/>
<path fill="#FFFFFF" d="M1109.296,675.808c7.055-8.843,11.693-15.255,15.395-20.802c-34.353,27.647-77.339,44.9-124.74,44.9
c-110.295,0-199.983-89.689-199.983-199.983S889.656,299.94,999.951,299.94s199.983,89.689,199.983,199.983
c0,20.648-4.051,40.248-9.849,58.996l5.7,2.151c15.395,5.951,29.854,13.495,43.098,22.255
c-2.249-40.542,6.147-85.302,44.649-129.587l72.1-83.192l15.2,108.995c4.904,34.995,42.847,82.899,76.389,125.187
c10.505,13.3,21.053,26.655,31.251,40.248c13.9-45.948,21.5-94.592,21.5-144.997C1499.972,223.886,1276.128,0,999.993,0
S500.028,223.886,500.028,499.979s223.83,499.965,499.965,499.965c44.048,0,86.489-6.245,127.24-17.002
c-44.802-45.096-72.24-106.593-72.24-173.286C1054.896,744.206,1086.245,704.712,1109.296,675.808z"/>
<path fill="#FFFFFF" d="M1283.476,756.709c-3.604-54.791-29.603-119.445-105.95-149.048
c26.948,86.252-72.743,101.438-72.743,202.037c0,91.589,62.698,167.838,147.246,190.288c-27.843-25.845,10.1-76.096-18.846-112.6
c36.35,11.553,69.097,38.153,69.097,84.799c36.392-24.755-8.955-92.497,58.354-137.341
c-20.592,61.092,34.995,90.639,25.901,130.887c-2.654,11.805-8.494,20.899-15.898,28.597
c75.243-27.954,129.196-99.691,129.196-184.644c0-109.093-164.289-219.933-178.637-323.228
C1233.183,587.921,1321.782,652.017,1283.476,756.709z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

16
assets/images/image.svg Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 41 41" enable-background="new 0 0 41 41" xml:space="preserve">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,0C31.822,0,41,9.178,41,20.5S31.822,41,20.5,41
S0,31.822,0,20.5S9.178,0,20.5,0z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#555760" d="M20.5,12c4.694,0,8.5,3.806,8.5,8.5S25.194,29,20.5,29
S12,25.194,12,20.5S15.806,12,20.5,12z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,12.625c4.349,0,7.875,3.526,7.875,7.875
s-3.526,7.875-7.875,7.875s-7.875-3.526-7.875-7.875S16.151,12.625,20.5,12.625z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#555760" d="M20.5,16c2.485,0,4.5,2.015,4.5,4.5S22.985,25,20.5,25
S16,22.985,16,20.5S18.015,16,20.5,16z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

37
assets/images/resin.svg Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 837 244" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
<title>Group + resin_text</title>
<desc>Created with Sketch.</desc>
<defs>
<path id="path-1" d="M0.246338028,0.51912 L581.907901,0.51912 L581.907901,102.74868 L0.246338028,102.74868 L0.246338028,0.51912 Z"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Group-+-resin_text" sketch:type="MSLayerGroup">
<g id="Group" transform="translate(0.500000, 0.000000)" sketch:type="MSShapeGroup">
<g id="Shape">
<path d="M105.67438,0 L0,61 L105.67438,122 L211,61 L105.67438,0 Z" fill="#E0A800"></path>
<path d="M0,61 L0,183 L105.67438,244 L105.67438,122 L0,61 Z" fill="#FFEC52"></path>
<path d="M105.67438,244 L211,183 L211,61 L105.67438,122 L105.67438,244 Z" fill="#F7C30F"></path>
</g>
<g transform="translate(105.789471, 122.000000) rotate(180.000000) translate(-105.789471, -122.000000) translate(36.159471, 41.480000)" id="Shape">
<path d="M69.7450909,0 L0,40.26 L69.7450909,80.52 L139.26,40.26 L69.7450909,0 Z" fill="#E0A800"></path>
<path d="M0,40.26 L0,120.78 L69.7450909,161.04 L69.7450909,80.52 L0,40.26 Z" fill="#FFEC52"></path>
<path d="M69.7450909,161.04 L139.26,120.78 L139.26,40.26 L69.7450909,80.52 L69.7450909,161.04 Z" fill="#F7C30F"></path>
</g>
<g transform="translate(52.837190, 61.000000)" id="Shape">
<path d="M52.8371901,0 L0,30.5 L52.8371901,61 L105.5,30.5 L52.8371901,0 Z" fill="#E0A800"></path>
<path d="M0,30.5 L0,91.5 L52.8371901,122 L52.8371901,61 L0,30.5 Z" fill="#FFEC52"></path>
<path d="M52.8371901,122 L105.5,91.5 L105.5,30.5 L52.8371901,61 L52.8371901,122 Z" fill="#F7C30F"></path>
</g>
</g>
<g id="resin_text" transform="translate(254.500000, 70.000000)">
<mask id="mask-2" sketch:name="Clip 2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Clip-2"></g>
<path d="M530.14407,81.44416 C514.30043,81.44416 501.80288,68.19424 501.80288,51.40524 C501.80288,35.329 514.653514,22.07496 529.971634,22.07496 C545.638732,22.07496 558.31693,35.50204 558.31693,51.76368 C558.31693,68.19424 545.638732,81.44416 530.14407,81.44416 L530.14407,81.44416 Z M529.790986,0.51912 C501.449796,0.51912 478.199592,23.66528 478.199592,51.76368 C478.199592,80.031 501.449796,103 529.971634,103 C558.31693,103 581.907901,80.031 581.907901,52.46408 C581.907901,23.31508 559.199641,0.51912 529.790986,0.51912 L529.790986,0.51912 Z M440.173211,100.70516 L463.591746,100.70516 L463.591746,2.81396 L440.173211,2.81396 L440.173211,100.70516 Z M394.046415,100.70516 L417.460845,100.70516 L417.460845,78.08636 L394.046415,78.08636 L394.046415,100.70516 Z M334.358711,0.51912 C323.265289,0.51912 315.517958,3.87692 306.892021,12.17872 L306.892021,2.81808 L285.234803,2.81808 L285.234803,100.70516 L308.653338,100.70516 L308.653338,54.59 C308.653338,41.51312 309.531944,35.85636 312.524951,31.08952 C316.220021,25.25972 322.382577,22.07496 329.776824,22.07496 C341.399873,22.07496 348.445141,26.67288 348.445141,52.8184 L348.445141,100.70516 L371.855465,100.70516 L371.855465,48.2246 C371.855465,30.7352 370.102359,22.25212 364.818408,14.65484 C358.47931,5.46724 347.915514,0.51912 334.358711,0.51912 L334.358711,0.51912 Z M242.97962,100.70516 L266.394049,100.70516 L266.394049,2.81396 L242.97962,2.81396 L242.97962,100.70516 Z M202.830627,40.98164 C186.810444,35.85636 186.284923,35.50204 186.284923,30.37676 C186.284923,25.60992 190.156535,22.07496 195.268049,22.07496 C200.19481,22.07496 203.364359,24.72824 204.423613,29.8494 L227.312521,29.8494 C226.42981,11.4742 211.813754,0.51912 195.268049,0.51912 C177.129359,0.51912 162.866387,13.59188 162.866387,30.37676 C162.866387,44.69376 170.613718,52.8184 189.984099,59.35684 C205.651197,64.65928 206.533908,66.24548 206.533908,71.90636 C206.533908,77.3736 201.956127,81.44416 195.966007,81.44416 C189.105493,81.44416 185.229775,77.91332 182.938831,69.6074 L159.347859,69.6074 C161.811239,92.40336 175.721127,103 195.091507,103 C214.634324,103 229.952444,88.33692 229.952444,69.6074 C229.952444,55.11324 223.260261,47.3388 202.830627,40.98164 L202.830627,40.98164 Z M73.2486127,41.51312 C76.9477887,28.9636 86.452331,22.07496 100.362218,22.07496 C114.801732,22.07496 124.306275,28.9636 127.299282,41.51312 L73.2486127,41.51312 L73.2486127,41.51312 Z M100.009134,0.51912 C71.3107535,0.51912 48.5942817,23.31508 48.5942817,52.10976 C48.5942817,80.7314 71.6597324,103 101.421472,103 C121.136725,103 141.385711,91.86776 147.897246,71.72096 L122.368415,71.72096 C116.386507,78.795 110.57293,81.44416 100.887739,81.44416 C86.9860634,81.44416 76.7671408,74.01992 73.9506761,62.18728 L149.490232,62.18728 C150.192296,58.82536 150.368838,56.70768 150.368838,53.17272 C150.368838,22.7836 129.064704,0.51912 100.009134,0.51912 L100.009134,0.51912 Z M21.4806761,12.35588 L21.4806761,2.81808 L0,2.81808 L0,100.70516 L23.4185352,100.70516 L23.4185352,46.27996 C23.4185352,31.08952 30.2831549,23.48812 44.3736901,23.13792 L44.3736901,0.51912 L42.6082676,0.51912 C32.5740986,0.51912 27.6432324,3.34544 21.4806761,12.35588 L21.4806761,12.35588 Z" id="Fill-1" fill="#FFFFFF" sketch:type="MSShapeGroup" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
assets/osx/installer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/osx/installer@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

20
bower.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "etcher",
"version": "v1.0.0-beta.3",
"homepage": "https://github.com/resin-io/etcher",
"authors": [
"Juan Cruz Viotti <juan@resin.io>"
],
"main": "lib/templates/index.html",
"license": "Apache-2.0",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular-mixpanel": "~1.1.2"
}
}

6417
build/css/main.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +0,0 @@
Etcher Architecture
===================
This document aims to serve as a high-level overview of how Etcher works,
specially oriented for contributors who want to understand the big picture.
Technologies
------------
This is a non exhaustive list of the major frameworks, libraries, and other
technologies used in Etcher that you should become familiar with:
- [Electron][electron]
- [NodeJS][nodejs]
- [Redux][redux]
- [ImmutableJS][immutablejs]
- [Sass][sass]
- [Mocha][mocha]
- [JSDoc][jsdoc]
Module architecture
-------------------
Instead of embedding all the functionality required to create a full-featured
image writer as a monolithic project, we try to hard to follow the ["lego block
approach"][lego-blocks].
This has the advantage of allowing other applications to re-use logic we
implemented for Etcher in their own project, even for things we didn't expect,
which leads to users benefitting from what we've built, and we benefitting from
user's bug reports, suggestions, etc, as an indirect way to make Etcher better.
The fact that low-level details are scattered around many different modules can
make it challenging for a new contributor to wrap their heads around the
project as a whole, and get a clear high level view of how things work or where
to submit their work or bug reports.
These are the main Etcher components, in a nutshell:
- [Drivelist](https://github.com/balena-io-modules/drivelist)
As the name implies, this module's duty is to detect the connected drives
uniformly in all major operating systems, along with valuable metadata, like if
a drive is removable or not, to prevent users from trying to write an image to
a system drive.
- [Etcher](https://github.com/balena-io/etcher)
This is the *"main repository"*, from which you're reading this from, which is
basically the front-end and glue for all previously listed projects.
Summary
-------
We always welcome contributions to Etcher as well as our documentation. If you
want to give back, but feel that your knowledge on how Etcher works is not
enough to tackle a bug report or feature request, use that as your advantage,
since fresh eyes could help unveil things that we take for granted, but should
be documented instead!
[lego-blocks]: https://github.com/sindresorhus/ama/issues/10#issuecomment-117766328
[exit-codes]: https://github.com/balena-io/etcher/blob/master/lib/shared/exit-codes.js
[gui-dir]: https://github.com/balena-io/etcher/tree/master/lib/gui
[electron]: http://electron.atom.io
[nodejs]: https://nodejs.org
[redux]: http://redux.js.org
[immutablejs]: http://facebook.github.io/immutable-js/
[sass]: http://sass-lang.com
[mocha]: http://mochajs.org
[jsdoc]: http://usejsdoc.org

View File

@ -1,86 +0,0 @@
Commit Guidelines
=================
We enforce certain rules on commits with the following goals in mind:
- Be able to reliably auto-generate the `CHANGELOG.md` *without* any human
intervention.
- Be able to automatically and correctly increment the semver version number
based on what was done since the last release.
- Be able to get a quick overview of what happened to the project by glancing
over the commit history.
- Be able to automatically reference relevant changes from a dependency
upgrade.
Commit structure
----------------
Each commit message needs to specify the semver-type. Which can be `patch|minor|major`.
See the [Semantic Versioning][semver] specification for a more detailed explanation of the meaning of these types.
See balena commit guidelines for more info about the whole commit structure.
```
<semver-type>: <subject>
```
or
```
<subject>
<BLANK LINE>
<details>
<BLANK LINE>
Change-Type: <semver-type>
```
The subject should not contain more than 70 characters, including the type and
scope, and the body should be wrapped at 72 characters.
Tags
----
### `See: <url>`/`Link: <url>`
This tag can be used to reference a resource that is relevant to the commit,
and can be repeated multiple times in the same commit.
Resource examples include:
- A link to pull requests.
- A link to a GitHub issue.
- A link to a website providing useful information.
- A commit hash.
Its recommended that you avoid relative URLs, and that you include the whole
commit hash to avoid any potential ambiguity issues in the future.
If the commit type equals `upgrade`, this tag should be present, and should
link to the CHANGELOG section of the dependency describing the changes
introduced from the previously used version.
Examples:
```
See: https://github.com/xxx/yyy/
See: 49d89b4acebd80838303b011d30517cd6229fdbe
Link: https://github.com/xxx/yyy/issues/zzz
```
### `Closes: <url>`/`Fixes: <url>`
This tag is used to make GitHub close the referenced issue automatically when
the commit is merged.
Its recommended that you provide the absolute URL to the GitHub issue rather
than simply writing the ID prefixed by a hash tag for convenience when browsing
the commit history outside the GitHub web interface.
A commit can include multiple instances of this tag.
Examples:
```
Closes: https://github.com/balena-io/etcher/issues/XXX
Fixes: https://github.com/balena-io/etcher/issues/XXX
```
[semver]: http://semver.org

View File

@ -1,184 +0,0 @@
Contributing Guide
==================
Thanks for your interest in contributing to this project! This document aims to
serve as a friendly guide for making your first contribution.
High-level Etcher overview
--------------------------
Make sure you checkout our [ARCHITECTURE.md][ARCHITECTURE] guide, which aims to
explain how all the pieces fit together.
Developing
----------
### Prerequisites
#### Common
- [NodeJS](https://nodejs.org) (at least v16.11)
- [Python 3](https://www.python.org)
- [jq](https://stedolan.github.io/jq/)
- [curl](https://curl.haxx.se/)
- [npm](https://www.npmjs.com/)
```sh
pip install -r requirements.txt
```
You might need to run this with `sudo` or administrator permissions.
#### Windows
- [NSIS v2.51](http://nsis.sourceforge.net/Main_Page) (v3.x won't work)
- Either one of the following:
- [Visual C++ 2019 Build Tools](https://visualstudio.microsoft.com/vs/features/cplusplus/) containing standalone compilers, libraries and scripts
- The [windows-build-tools](https://github.com/felixrieseberg/windows-build-tools#windows-build-tools) should be installed along with NodeJS
- [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/) (free) (other editions, like Professional and Enterprise, should work too)
**NOTE:** Visual Studio doesn't install C++ by default. You have to rerun the
setup, select "Modify" and then check `Visual C++ -> Common Tools for Visual
C++` (see http://stackoverflow.com/a/31955339)
- [MinGW](http://www.mingw.org)
You might need to `npm config set msvs_version 2019` for node-gyp to correctly detect
the version of Visual Studio you're using (in this example VS2019).
The following MinGW packages are required:
- `msys-make`
- `msys-unzip`
- `msys-zip`
- `msys-bash`
- `msys-coreutils`
#### macOS
- [Xcode](https://developer.apple.com/xcode/)
It's not enough to have [Xcode Command Line Tools] installed. Xcode must be installed
as well.
#### Linux
- `libudev-dev` for libusb (for example install with `sudo apt install libudev-dev`, or on fedora `systemd-devel` contains the required package)
### Cloning the project
```sh
git clone --recursive https://github.com/balena-io/etcher
cd etcher
```
### Running the application
#### GUI
```sh
# Build and start application
npm start
```
Testing
-------
To run the test suite, run the following command:
```sh
npm test
```
Given the nature of this application, not everything can be unit tested. For
example:
- The writing operating on real raw devices.
- Platform inconsistencies.
- Style changes.
- Artwork.
We encourage our contributors to test the application on as many operating
systems as they can before sending a pull request.
*The test suite is run automatically by CI servers when you send a pull
request.*
We make use of [EditorConfig] to communicate indentation, line endings and
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
process.
Updating a dependency
---------------------
- Install new version of dependency using npm
- Commit *both* `package.json` and `npm-shrinkwrap.json`.
Diffing Binaries
----------------
Binary files are tagged as "binary" in the `.gitattributes` file, but also have
a `diff=hex` tag, which allows you to see hexdump-style diffs for binaries,
if you add the following to either your global or repository-local git config:
```sh
$ git config diff.hex.textconv hexdump
$ git config diff.hex.binary true
```
And global, respectively:
```sh
$ git config --global diff.hex.textconv hexdump
$ git config --global diff.hex.binary true
```
If you don't have `hexdump` available on your platform,
you can try [hxd], which is also a bit faster.
Commit Guidelines
-----------------
See [COMMIT-GUIDELINES.md][COMMIT-GUIDELINES] for a thorough guide on how to
write commit messages.
Sending a pull request
----------------------
When sending a pull request, consider the following guidelines:
- Write a concise commit message explaining your changes.
- If applies, write more descriptive information in the commit body.
- Mention the operating systems with the corresponding versions in which you
tested your changes.
- If your change affects the visuals of the application, consider attaching a
screenshot.
- Refer to the issue/s your pull request fixes, so they're closed automatically
when your pull request is merged.
- Write a descriptive pull request title.
- Squash commits when possible, for example, when committing review changes.
Before your pull request can be merged, the following conditions must hold:
- The linter doesn't throw any warning.
- All the tests pass.
- The coding style aligns with the project's convention.
- Your changes are confirmed to be working in recent versions of the operating
systems we support.
Don't hesitate to get in touch if you have any questions or need any help!
[ARCHITECTURE]: https://github.com/balena-io/etcher/blob/master/docs/ARCHITECTURE.md
[COMMIT-GUIDELINES]: https://github.com/balena-io/etcher/blob/master/docs/COMMIT-GUIDELINES.md
[EditorConfig]: http://editorconfig.org
[shrinkwrap]: https://docs.npmjs.com/cli/shrinkwrap
[hxd]: https://github.com/jhermsmeier/hxd
[Xcode Command Line Tools]: https://developer.apple.com/library/content/technotes/tn2339/_index.html

View File

@ -1,52 +0,0 @@
## Why is my drive not bootable?
Etcher copies images to drives byte by byte, without doing any transformation to the final device, which means images that require special treatment to be made bootable, like Windows images, will not work out of the box. In these cases, the general advice is to use software specific to those kind of images, usually available from the image publishers themselves. You can find more information [here](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#why-is-my-drive-not-bootable).
## How can I configure persistent storage?
Some programs, usually oriented at making GNU/Linux live USB drives, include an option to set persistent storage. This is currently not supported by Etcher, so if you require this functionality, we advise to fallback to [UNetbootin](https://unetbootin.github.io/).
## How do I flash Ubuntu ISOs
Ubuntu images (and potentially some other related GNU/Linux distributions) have a peculiar format that allows the image to boot without any further modification from both CDs and USB drives.
A consequence of this enhancement is that some programs, like parted get confused about the drive's format and partition table, printing warnings such as:
> /dev/xxx contains GPT signatures, indicating that it has a GPT table. However, it does not have a valid fake msdos partition table, as it should. Perhaps it was corrupted -- possibly by a program that doesn't understand GPT partition tables. Or perhaps you deleted the GPT table, and are now using an msdos partition table. Is this a GPT partition table? Both the primary and backup GPT tables are corrupt. Try making a fresh table, and using Parted's rescue feature to recover partitions.
> Warning: The driver descriptor says the physical block size is 2048 bytes, but Linux says it is 512 bytes.
All these warnings are safe to ignore, and your drive should be able to boot without any problems.
Refer to [the following message from Ubuntu's mailing list](https://lists.ubuntu.com/archives/ubuntu-devel/2011-June/033495.html) if you want to learn more.
## How do I run Etcher on Wayland?
The XWayland Server provides backwards compatibility to run any X client on Wayland, including Etcher.
This usually works out of the box on mainstream GNU/Linux distributions that properly support Wayland. If it doesn't, make sure the xwayland.so module is being loaded by declaring it in your [weston.ini](http://manpages.ubuntu.com/manpages/wily/man5/weston.ini.5.html):
```
[core]
modules=xwayland.so
```
## What are the runtime GNU/LINUX dependencies?
[This entry](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#runtime-gnulinux-dependencies) aims to provide an up to date list of runtime dependencies needed to run Etcher on a GNU/Linux system.
## How can I recover the broken drive?
Sometimes, things might go wrong, and you end up with a half-flashed drive that is unusable by your operating systems, and common graphical tools might even refuse to get it back to a normal state.
To solve these kinds of problems, we've collected [a list of fail-proof methods](https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md#recovering-broken-drives) to completely erase your drive in major operating systems.
## I receive "No polkit authentication agent found" error in GNU/Linux
Etcher requires an available [polkit authentication agent](https://wiki.archlinux.org/index.php/Polkit#Authentication_agents) in your system in order to show a secure password prompt dialog to perform elevation. Make sure you have one installed for the desktop environment of your choice.
## May I run Etcher in older macOS versions?
Etcher GUI is based on the [Electron](http://electron.atom.io/) framework, [which only supports macOS 10.10 and newer versions](https://github.com/electron/electron/blob/master/docs/tutorial/support.md#supported-platforms).
## Can I use the Flash With Etcher button on my site?
You can use the Flash with Etcher button on your site or blog, if you have an OS that you want your users to be able to easily flash using Etcher, add the following code where you want to button to be:
`<a href="https://efp.balena.io/open-image-url?imageUrl=<your image URL>"><img src="http://balena.io/flash-with-etcher.png" /></a>`

View File

@ -1,112 +0,0 @@
Maintaining Etcher
==================
This document is meant to serve as a guide for maintainers to perform common tasks.
Releasing
---------
### Release Types
- **draft**: A continues snapshot of current master, made by the CI services
- **pre-release** (default): A continues snapshot of current master, made by the CI services
- **release**: Full releases
Draft release is created from each PR, tagged with the branch name.
All merged PR will generate a new tag/version as a *pre-release*.
Mark the pre-release as final when it is necessary, then distribute the packages in alternative channels as necessary.
#### Preparation
- [Prepare the new version](#preparing-a-new-version)
- [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
- [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
- Upload build artifacts to GitHub release draft
#### Testing
- Test the prepared release and build artifacts properly on **all supported operating systems** to prevent regressions that went uncaught by the CI tests (see [MANUAL-TESTING.md](MANUAL-TESTING.md))
- If regressions or other issues arise, create issues on the repository for each one, and decide whether to fix them in this release (meaning repeating the process up until this point), or to follow up with a patch release
#### Publishing
- [Publish release draft on GitHub](https://github.com/balena-io/etcher/releases)
- [Post release note to forums](https://forums.balena.io/c/etcher)
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
- [Update the website](https://github.com/balena-io/etcher-homepage)
- Wait 2-3 hours for analytics (Sentry, Amplitude) to trickle in and check for elevated error rates, or regressions
- If regressions arise; pull the release, and release a patched version, else:
- [Upload deb & rpm packages to Cloudfront](#uploading-packages-to-cloudfront)
- Post changelog with `#release-notes` tag on internal chat
- If this release packs noteworthy major changes:
- Write a blog post about it, and / or
- Write about it to the Etcher mailing list
### Generating binaries
**Environment**
Make sure to set the analytics tokens when generating production release binaries:
```bash
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
```
#### Linux
##### Clean dist folder
Delete `.webpack` and `out/`.
##### Generating artifacts
The artifacts are generated by the CI and published as draft-release or pre-release.
Etcher is built with electron-forge. Run:
```
npm run make
```
Our CI will appropriately sign artifacts for macOS and some Windows targets.
### Uploading packages to Cloudfront
Log in to cloudfront and upload the `rpm` and `deb` files.
### Dealing with a Problematic Release
There can be times where a release is accidentally plagued with bugs. If you
released a new version and notice the error rates are higher than normal, then
revert the problematic release as soon as possible, until the bugs are fixed.
You can revert a version by deleting its builds from the S3 bucket and Bintray.
Refer to the `Makefile` for the up to date information about the S3 bucket
where we push builds to, and get in touch with the balena.io operations team to
get write access to it.
The Etcher update notifier dialog and the website only show the a certain
version if all the expected files have been uploaded to it, so deleting a
single package or two is enough to bring down the whole version.
Use the following command to delete files from S3:
```bash
aws s3api delete-object --bucket <bucket name> --key <file name>
```
The Bintray dashboard provides an easy way to delete a version's files.
### Submitting binaries to Symantec
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
- Fill out form:
- **Select Submission Type:** "Provide a direct download URL"
- **Name of the software being detected:** Etcher
- **Name of detection given by Symantec product:** WS.Reputation.1
- **Contact name:** Balena.io Ltd
- **E-mail address:** hello@etcher.io
- **Are you the creator or distributor of the software in question?** Yes

View File

@ -1,115 +0,0 @@
Manual Testing
==============
This document describes a high-level script of manual tests to check for. We
should aim to replace items on this list with automated Spectron test cases.
Image Selection
---------------
- [ ] Cancel image selection dialog
- [ ] Select an unbootable image (without a partition table), and expect a
sensible warning
- [ ] Attempt to select a ZIP archive with more than one image
- [ ] Attempt to select a tar archive (with any compression method)
- [ ] Change image selection
- [ ] Select a Windows image, and expect a sensible warning
Drive Selection
---------------
- [ ] Open the drive selection modal
- [ ] Switch drive selection
- [ ] Insert a single drive, and expect auto-selection
- [ ] Insert more than one drive, and don't expect auto-selection
- [ ] Insert a locked SD Card and expect a warning
- [ ] Insert a too small drive and expect a warning
- [ ] Put an image into a drive and attempt to flash the image to the drive
that contains it
- [ ] Attempt to flash a compressed image (for which we can get the
uncompressed size) into a drive that is big enough to hold the compressed
image, but not big enough to hold the uncompressed version
- [ ] Enable "Unsafe Mode" and attempt to select a system drive
- [ ] Enable "Unsafe Mode", and if there is only one system drive (and no
removable ones), don't expect autoselection
Image Support
-------------
Run the following tests with and without validation enabled:
- [ ] Flash an uncompressed image
- [ ] Flash a Bzip2 image
- [ ] Flash a XZ image
- [ ] Flash a ZIP image
- [ ] Flash a GZ image
- [ ] Flash a DMG image
- [ ] Flash an image whose size is not a multiple of 512 bytes
- [ ] Flash a compressed image whose size is not a multiple of 512 bytes
- [ ] Flash an archive whose image size is not a multiple of 512 bytes
- [ ] Flash an archive image containing a logo
- [ ] Flash an archive image containing a blockmap file
- [ ] Flash an archive image containing a manifest metadata file
Flashing Process
----------------
- [ ] Unplug the drive during flash or validation
- [ ] Click "Flash", cancel elevation dialog, and click "Flash" again
- [ ] Start flashing an image, try to close Etcher, cancel the application
close warning dialog, and check that Etcher continues to flash the image
### Child Writer
- [ ] Kill the child writer process (i.e. with `SIGINT` or `SIGKILL`), and
check that the UI reacts appropriately
- [ ] Close the application while flashing using the window manager close icon
- [ ] Close the application while flashing using the OS keyboard shortcut
- [ ] Close the application from the terminal using Ctrl-C while flashing
- [ ] Force kill the application (using a process monitor tool, etc)
In all these cases, the child writer process should not remain alive. Note that
in some systems you need to open your process monitor tool of choice with extra
permissions to see the elevated child writer process.
GUI
----
- [ ] Close application from the terminal using Ctrl-C while the application is
idle
- [ ] Click footer links that take you to an external website
- [ ] Attempt to change image or drive selection while flashing
- [ ] Go to the settings page while flashing and come back
- [ ] Flash consecutive images without closing the application
- [ ] Remove the selected drive right before clicking "Flash"
- [ ] Minimize the application
- [ ] Start the application given no internet connection
Success Banner
--------------
- [ ] Click an external link on the success banner (with and without internet
connection)
Elevation Prompt
----------------
- [ ] Flash an image as `root`/administrator
- [ ] Reject elevation prompt
- [ ] Put incorrect elevation prompt password
- [ ] Unplug the drive during elevation
Unmounting
----------
- [ ] Disable unmounting and flash an image
- [ ] Flash an image with a file system that is readable by the host OS, and
check that is unmounted correctly
Analytics
---------
- [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
check that no request is sent
- [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
F5), and check that initial events are not sent to Amplitude**

View File

@ -1,82 +0,0 @@
Publishing Etcher
=================
This is a small guide to package and publish Etcher to all supported operating
systems.
Release Types
-------------
Etcher supports **pre-release** and **final** release types as does Github. Each is
published to Github releases.
The release version is generated automatically from the commit messasges.
Signing
-------
### OS X
1. Get our Apple Developer ID certificate for signing applications distributed
outside the Mac App Store from the balena.io Apple account.
2. Install the Developer ID certificate to your Mac's Keychain by double
clicking on the certificate file.
The application will be signed automatically using this certificate when
packaging for OS X.
### Windows
1. Get access to our code signing certificate and decryption key as a balena.io
employee by asking for it from the relevant people.
2. Place the certificate in the root of the Etcher repository naming it
`certificate.p12`.
Packaging
---------
Run the following command on each platform:
```sh
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
---------------------
We publish GNU/Linux Debian packages to [Cloudfront][cloudfront].
Log in to cloudfront and upload the `rpm` and `deb` files.
Publishing to Homebrew Cask
---------------------------
1. Update [`Casks/etcher.rb`][etcher-cask-file] with the new version and
`sha256`
2. Send a PR with the changes above to
[`caskroom/homebrew-cask`][homebrew-cask]
Announcing
----------
Post messages to the [Etcher forum][balena-forum-etcher] announcing the new version
of Etcher, and including the relevant section of the Changelog.
[aws-cli]: https://aws.amazon.com/cli
[cloudfront]: https://cloudfront.com
[etcher-cask-file]: https://github.com/caskroom/homebrew-cask/blob/master/Casks/balenaetcher.rb
[homebrew-cask]: https://github.com/caskroom/homebrew-cask
[balena-forum-etcher]: https://forums.balena.io/c/etcher
[github-releases]: https://github.com/balena-io/etcher/releases
Updating EFP / Success-Banner
-----------------------------
Etcher Featured Project is automatically run based on an algorithm which promoted projects from the balena marketplace which have been contributed by the community, the algorithm prioritises projects which give uses the best experience. Editing both EFP and the Etcher Success-Banner can only be done by someone from balena, instruction are on the [Etcher-EFP repo (private)](https://github.com/balena-io/etcher-efp)

View File

@ -1,43 +0,0 @@
Getting help with BalenaEtcher
===============================
There are various ways to get support for Etcher if you experience an issue or
have an idea you'd like to share with us.
Documentation
------
We have answers to a variety of frequently asked questions in the [user
documentation][documentation] and also in the [FAQs][faq] on the Etcher website.
Forums
------
We have a [Discourse forum][discourse] which is open to everyone, so please
come join us :). Drop us a line there and the balena.io staff and community
users will be happy to assist. Your question might already be answered, so take
a look at the existing threads before opening a new one!
Make sure to mention the following information to help us provide better
support:
- The BalenaEtcher version you're running.
- The operating system you're running Etcher in.
- Relevant logging output, if any, from DevTools, which you can open by
pressing `Ctrl+Shift+I` or `Cmd+Alt+I` depending on your platform.
GitHub
------
If you encounter an issue or have a suggestion, head on over to BalenaEtcher's [issue
tracker][issues] and if there isn't a ticket covering it, [create
one][new-issue].
[discourse]: https://forums.balena.io/c/etcher
[issues]: https://github.com/balena-io/etcher/issues
[new-issue]: https://github.com/balena-io/etcher/issues/new
[documentation]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
[faq]: https://etcher.io

View File

@ -1,226 +0,0 @@
Etcher User Documentation
=========================
This document contains how-tos and FAQs oriented to Etcher users.
Config
------
Etcher's configuration is saved to the `config.json` file in the apps folder.
Not all the options are surfaced to the UI. You may edit this file to tweak settings even before launching the app.
Why is my drive not bootable?
-----------------------------
Etcher copies images to drives byte by byte, without doing any transformation
to the final device, which means images that require special treatment to be
made bootable, like Windows images, will not work out of the box. In these
cases, the general advice is to use software specific to those kind of
images, usually available from the image publishers themselves.
Images known to require special treatment:
- Microsoft Windows (use [Windows USB/DVD Download Tool][windows-usb-tool],
[Rufus][rufus], or [WoeUSB][woeusb]).
- Windows 10 IoT (use the [Windows 10 IoT Core Dashboard][windows-iot-dashboard])
How can I configure persistent storage?
---------------------------------------
Some programs, usually oriented at making GNU/Linux live USB drives, include an
option to set persistent storage. This is currently not supported by Etcher, so
if you require this functionality, we advise to fallback to
[UNetbootin][unetbootin].
Deactivate desktop shortcut prompt on GNU/Linux
-----------------------------------------------
This is a feature provided by [AppImages][appimage], where the applications
prompts the user to automatically register a desktop shortcut to easily access
the application.
To deactivate this feature, `touch` any of the files listed below:
- `$HOME/.local/share/appimagekit/no_desktopintegration`
- `/usr/share/appimagekit/no_desktopintegration`
- `/etc/appimagekit/no_desktopintegration`
Alternatively, set the `SKIP` environment variable before executing the
AppImage:
```sh
SKIP=1 ./Etcher-linux-<arch>.AppImage
```
Flashing Ubuntu ISOs
--------------------
Ubuntu images (and potentially some other related GNU/Linux distributions) have
a peculiar format that allows the image to boot without any further
modification from both CDs and USB drives.
A consequence of this enhancement is that some programs, like `parted` get
confused about the drive's format and partition table, printing warnings such
as:
> /dev/xxx contains GPT signatures, indicating that it has a GPT table.
> However, it does not have a valid fake msdos partition table, as it should.
> Perhaps it was corrupted -- possibly by a program that doesn't understand GPT
> partition tables. Or perhaps you deleted the GPT table, and are now using an
> msdos partition table. Is this a GPT partition table? Both the primary and
> backup GPT tables are corrupt. Try making a fresh table, and using Parted's
> rescue feature to recover partitions.
***
> Warning: The driver descriptor says the physical block size is 2048 bytes,
> but Linux says it is 512 bytes.
All these warnings are **safe to ignore**, and your drive should be able to
boot without any problems.
Refer to [the following message from Ubuntu's mailing
list](https://lists.ubuntu.com/archives/ubuntu-devel/2011-June/033495.html) if
you want to learn more.
Running on Wayland
------------------
Electron is based on Gtk2, which can't run natively on Wayland. Fortunately,
the [XWayland Server][xwayland] provides backwards compatibility to run *any* X
client on Wayland, including Etcher.
This usually works out of the box on mainstream GNU/Linux distributions that
properly support Wayland. If it doesn't, make sure the `xwayland.so` module is
being loaded by declaring it in your [weston.ini]:
```
[core]
modules=xwayland.so
```
Runtime GNU/Linux dependencies
------------------------------
This entry aims to provide an up to date list of runtime dependencies needed to
run Etcher on a GNU/Linux system.
### Electron specific
> See [brightray's gyp file](https://github.com/electron/brightray/blob/master/brightray.gyp#L4)
- gtk+-2.0
- dbus-1
- x11
- xi
- xcursor
- xdamage
- xrandr
- xcomposite
- xext
- xfixes
- xrender
- xtst
- xscrnsaver
- gmodule-2.0
- nss
### Optional dependencies:
- libnotify (for notifications)
- libspeechd (for text-to-speech)
### Etcher specific:
- liblzma (for xz decompression)
Recovering broken drives
------------------------
Sometimes, things might go wrong, and you end up with a half-flashed drive that
is unusable by your operating systems, and common graphical tools might even
refuse to get it back to a normal state.
To solve these kinds of problems, we've collected a list of fail-proof methods
to completely erase your drive in major operating systems.
### Windows
In Windows, we'll use [diskpart], a command line utility tool that comes
pre-installed in all modern Windows versions.
- Open `cmd.exe` from either the list of all installed applications, or from
the "Run..." dialog usually accessible by pressing Ctrl+X.
- Type `diskpart.exe` and press "Enter". You'll be asked to provide
administrator permissions, and a new prompt window will appear. The following
commands should be run **in the new window**.
- Run `list disk` to list the available drives. Take note of the number id that
identifies the drive you want to clean.
- Run `select disk N`, where `N` corresponds to the id from the previous step.
- Run `clean`. This command will completely clean your drive by erasing any
existent filesystem.
- Run `create partition primary`. This command will create a new partition.
- Run `active`. This command will active the partition.
- Run `list partition`. This command will show available partition.
- Run `select partition N`, where `N` corresponds to the id of the newly available partition.
- Run `format override quick`. This command will format the partition. You can choose a specific formatting by adding `FS=xx` where `xx` could be `NTFS or FAT or FAT32` after `format`. Example : `format FS=NTFS override quick`
- Run `exit` to quit diskpart.
### OS X
Run the following command in `Terminal.app`, replacing `N` by the corresponding
disk number, which you can find by running `diskutil list`:
```sh
diskutil eraseDisk FAT32 UNTITLED MBRFormat /dev/diskN
```
### GNU/Linux
Make sure the drive is unmounted (`umount /dev/xxx`), and run the following
command as `root`, replacing `xxx` by your actual device path:
```sh
dd if=/dev/zero of=/dev/xxx bs=512 count=1 conv=notrunc
```
"No polkit authentication agent found" error in GNU/Linux
----------------------------------------------------------
Etcher requires an available [polkit authentication
agent](https://wiki.archlinux.org/index.php/Polkit#Authentication_agents) in
your system in order to show a secure password prompt dialog to perform
elevation. Make sure you have one installed for the desktop environment of your
choice.
Running in older macOS versions
-------------------------------
Etcher GUI is based on the [Electron][electron] framework, [which only supports
macOS 10.10 (Yosemite) and newer versions][electron-supported-platforms].
[balena.io]: https://balena.io
[appimage]: http://appimage.org
[xwayland]: https://wayland.freedesktop.org/xserver.html
[weston.ini]: http://manpages.ubuntu.com/manpages/wily/man5/weston.ini.5.html
[diskpart]: https://technet.microsoft.com/en-us/library/cc770877(v=ws.11).aspx
[electron]: https://electronjs.org/
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
[publishing]: https://github.com/balena-io/etcher/blob/master/docs/PUBLISHING.md
[windows-usb-tool]: https://www.microsoft.com/en-us/download/windows-usb-dvd-download-tool
[rufus]: https://rufus.akeo.ie
[unetbootin]: https://unetbootin.github.io
[windows-iot-dashboard]: https://developer.microsoft.com/en-us/windows/iot/downloads
[woeusb]: https://github.com/slacka/WoeUSB
See [PUBLISHING](/docs/PUBLISHING.md) for more details about release types.

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
</dict>
</plist>

View File

@ -1,159 +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 { exec } from 'child_process';
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/sudo/sudo-askpass.osascript-zh.js',
'lib/shared/sudo/sudo-askpass.osascript-en.js',
],
osxSign: {
optionsForFile: () => ({
entitlements: './entitlements.mac.plist',
hardenedRuntime: true,
}),
},
...osxSigningConfig,
},
rebuildConfig: {
onlyModules: [], // prevent rebuilding *any* native modules as they won't be used by electron but by the sidecar
},
makers: [
new MakerZIP(),
new MakerSquirrel({
setupIcon: 'assets/icon.ico',
loadingGif: 'assets/icon.png',
...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: {
postPackage: async (_forgeConfig, options) => {
if (options.platform === 'linux') {
// symlink the etcher binary from balena-etcher to balenaEtcher to ensure compatibility with the wdio suite and the old name
await new Promise<void>((resolve, reject) => {
exec(
`ln -s "${options.outputPaths}/balena-etcher" "${options.outputPaths}/balenaEtcher"`,
(err) => {
if (err) {
reject(err);
} else {
resolve();
}
},
);
});
}
},
},
};
export default config;

View File

@ -1,168 +0,0 @@
import { PluginBase } from '@electron-forge/plugin-base';
import type {
ForgeHookMap,
ResolvedForgeConfig,
} from '@electron-forge/shared-types';
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
import { DefinePlugin } from 'webpack';
import { execFileSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as d from 'debug';
const debug = d('sidecar');
function isStartScrpt(): boolean {
return process.env.npm_lifecycle_event === 'start';
}
function addWebpackDefine(
config: ResolvedForgeConfig,
defineName: string,
binDir: string,
binName: string,
): ResolvedForgeConfig {
config.plugins.forEach((plugin) => {
if (plugin.name !== 'webpack' || !(plugin instanceof WebpackPlugin)) {
return;
}
const { mainConfig } = plugin.config as any;
if (mainConfig.plugins == null) {
mainConfig.plugins = [];
}
const value = isStartScrpt()
? // on `npm start`, point directly to the binary
path.resolve(binDir, binName)
: // otherwise point relative to the resources folder of the bundled app
binName;
debug(`define '${defineName}'='${value}'`);
mainConfig.plugins.push(
new DefinePlugin({
// expose path to helper via this webpack define
[defineName]: JSON.stringify(value),
}),
);
});
return config;
}
function build(
sourcesDir: string,
buildForArchs: string,
binDir: string,
binName: string,
) {
const commands: Array<[string, string[], object?]> = [
['tsc', ['--project', 'tsconfig.sidecar.json', '--outDir', sourcesDir]],
];
buildForArchs.split(',').forEach((arch) => {
const binPath = isStartScrpt()
? // on `npm start`, we don't know the arch we're building for at the time we're
// adding the webpack define, so we just build under binDir
path.resolve(binDir, binName)
: // otherwise build in arch-specific directory within binDir
path.resolve(binDir, arch, binName);
// FIXME: rebuilding mountutils shouldn't be necessary, but it is.
// It's coming from etcher-sdk, a fix has been upstreamed but to use
// the latest etcher-sdk we need to upgrade axios at the same time.
commands.push(['npm', ['rebuild', 'mountutils', `--arch=${arch}`]]);
commands.push([
'pkg',
[
path.join(sourcesDir, 'util', 'api.js'),
'-c',
'pkg-sidecar.json',
// `--no-bytecode` so that we can cross-compile for arm64 on x64
'--no-bytecode',
'--public',
'--public-packages',
'"*"',
// always build for host platform and node version
// https://github.com/vercel/pkg-fetch/releases
'--target',
`node20-${arch}`,
'--output',
binPath,
],
]);
});
commands.forEach(([cmd, args, opt]) => {
debug('running command:', cmd, args.join(' '));
execFileSync(cmd, args, { shell: true, stdio: 'inherit', ...opt });
});
}
function copyArtifact(
buildPath: string,
arch: string,
binDir: string,
binName: string,
) {
const binPath = isStartScrpt()
? // on `npm start`, we don't know the arch we're building for at the time we're
// adding the webpack define, so look for the binary directly under binDir
path.resolve(binDir, binName)
: // otherwise look into arch-specific directory within binDir
path.resolve(binDir, arch, binName);
// buildPath points to appPath, which is inside resources dir which is the one we actually want
const resourcesPath = path.dirname(buildPath);
const dest = path.resolve(resourcesPath, path.basename(binPath));
debug(`copying '${binPath}' to '${dest}'`);
fs.copyFileSync(binPath, dest);
}
export class SidecarPlugin extends PluginBase<void> {
name = 'sidecar';
constructor() {
super();
this.getHooks = this.getHooks.bind(this);
debug('isStartScript:', isStartScrpt());
}
getHooks(): ForgeHookMap {
const DEFINE_NAME = 'ETCHER_UTIL_BIN_PATH';
const BASE_DIR = path.join('out', 'sidecar');
const SRC_DIR = path.join(BASE_DIR, 'src');
const BIN_DIR = path.join(BASE_DIR, 'bin');
const BIN_NAME = `etcher-util${process.platform === 'win32' ? '.exe' : ''}`;
return {
resolveForgeConfig: async (currentConfig) => {
debug('resolveForgeConfig');
return addWebpackDefine(currentConfig, DEFINE_NAME, BIN_DIR, BIN_NAME);
},
generateAssets: async (_config, platform, arch) => {
debug('generateAssets', { platform, arch });
build(SRC_DIR, arch, BIN_DIR, BIN_NAME);
},
packageAfterCopy: async (
_config,
buildPath,
electronVersion,
platform,
arch,
) => {
debug('packageAfterCopy', {
buildPath,
electronVersion,
platform,
arch,
});
copyArtifact(buildPath, arch, BIN_DIR, BIN_NAME);
},
};
}
}

54
gulpfile.js Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const gulp = require('gulp');
const jscs = require('gulp-jscs');
const jshint = require('gulp-jshint');
const jshintStylish = require('jshint-stylish');
const sass = require('gulp-sass');
const paths = {
scripts: [
'./tests/**/*.spec.js',
'./lib/**/*.js',
'gulpfile.js'
],
sass: [
'./lib/**/*.scss'
],
sassMain: './lib/scss/main.scss'
};
gulp.task('sass', function() {
return gulp.src(paths.sassMain)
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('./build/css'));
});
gulp.task('lint', function() {
return gulp.src(paths.scripts)
.pipe(jshint())
.pipe(jshint.reporter(jshintStylish))
.pipe(jscs())
.pipe(jscs.reporter());
});
gulp.task('watch', [ 'lint', 'sass' ], function() {
gulp.watch(paths.scripts, [ 'lint' ]);
gulp.watch(paths.sass, [ 'sass' ]);
});

258
lib/browser/app.js Normal file
View File

@ -0,0 +1,258 @@
/*
* Copyright 2016 Resin.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.
*/
/**
* @module Etcher
*/
'use strict';
var angular = require('angular');
const _ = require('lodash');
const electron = require('electron');
const dialog = electron.remote.require('./src/dialog');
const app = angular.module('Etcher', [
require('angular-ui-router'),
// Etcher modules
require('./browser/modules/drive-scanner'),
require('./browser/modules/image-writer'),
require('./browser/modules/analytics'),
// Models
require('./browser/models/selection-state'),
require('./browser/models/settings'),
// Components
require('./browser/components/progress-button/progress-button'),
require('./browser/components/drive-selector/drive-selector'),
require('./browser/components/svg-icon/svg-icon'),
// Pages
require('./browser/pages/finish/finish'),
require('./browser/pages/settings/settings'),
// OS
require('./browser/os/notification/notification'),
require('./browser/os/window-progress/window-progress'),
require('./browser/os/open-external/open-external'),
require('./browser/os/dropzone/dropzone'),
// Utils
require('./browser/utils/if-state/if-state'),
require('./browser/utils/notifier/notifier'),
require('./browser/utils/path/path')
]);
app.run(function(AnalyticsService) {
AnalyticsService.logEvent('Application start');
});
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/main');
$stateProvider
.state('main', {
url: '/main',
controller: 'AppController as app',
templateUrl: './partials/main.html'
});
});
app.controller('AppController', function(
$q,
$state,
$scope,
NotifierService,
DriveScannerService,
SelectionStateModel,
SettingsModel,
ImageWriterService,
AnalyticsService,
DriveSelectorService,
OSWindowProgressService,
OSNotificationService
) {
let self = this;
this.selection = SelectionStateModel;
this.writer = ImageWriterService;
this.scanner = DriveScannerService;
this.settings = SettingsModel.data;
this.success = true;
// This catches the case where the user enters
// the settings screen when a flash finished
// and goes back to the main screen with the back button.
if (!this.writer.isFlashing()) {
this.selection.clear({
// Preserve image, in case there is one, otherwise
// we revert the behaviour of "Use same image".
preserveImage: true
});
this.writer.resetState();
}
NotifierService.subscribe($scope, 'image-writer:state', function(state) {
AnalyticsService.log(`Progress (${state.type}): ${state.progress}% at ${state.speed} MB/s`);
OSWindowProgressService.set(state.progress);
});
this.scanner.start(2000).on('error', dialog.showError).on('scan', function(drives) {
// Cover the case where you select a drive, but then eject it.
if (self.selection.hasDrive() && !_.find(drives, self.selection.isCurrentDrive)) {
self.selection.removeDrive();
}
if (_.isEmpty(drives)) {
DriveSelectorService.close();
}
// Notice we only autoselect the drive if there is an image,
// which means that the first step was completed successfully,
// otherwise the drive is selected while the drive step is disabled
// which looks very weird.
if (drives.length === 1 && self.selection.hasImage()) {
const drive = _.first(drives);
// Do not autoselect the same drive over and over again
// and fill the logs unnecessary.
// `angular.equals` is used instead of `_.isEqual` to
// cope with `$$hashKey`.
if (!angular.equals(self.selection.getDrive(), drive)) {
AnalyticsService.logEvent('Auto-select drive', {
device: drive.device
});
self.selectDrive(drive);
}
}
});
// We manually add `style="display: none;"` to <body>
// and unset it here instead of using ngCloak since
// the latter takes effect as soon as the Angular
// library was loaded, but doesn't always mean that
// the application is ready, causing the application
// to be shown in an unitialized state for some milliseconds.
// Here in the controller, we are sure things are
// completely up and running.
document.querySelector('body').style.display = 'initial';
this.selectImage = function(image) {
self.selection.setImage(image);
AnalyticsService.logEvent('Select image', {
image: image
});
};
this.openImageSelector = function() {
return $q.when(dialog.selectImage()).then(function(image) {
// Avoid analytics and selection state changes
// if no file was resolved from the dialog.
if (!image) {
return;
}
self.selectImage(image);
});
};
this.selectDrive = function(drive) {
self.selection.setDrive(drive);
AnalyticsService.logEvent('Select drive', {
device: drive.device
});
};
this.openDriveSelector = function() {
DriveSelectorService.open().then(self.selectDrive);
};
this.reselectImage = function() {
if (self.writer.isFlashing()) {
return;
}
// Reselecting an image automatically
// de-selects the current drive, if any.
// This is made so the user effectively
// "returns" to the first step.
self.selection.clear();
self.openImageSelector();
AnalyticsService.logEvent('Reselect image');
};
this.reselectDrive = function() {
if (self.writer.isFlashing()) {
return;
}
self.openDriveSelector();
AnalyticsService.logEvent('Reselect drive');
};
this.restartAfterFailure = function() {
self.selection.clear({
preserveImage: true
});
self.writer.resetState();
self.success = true;
AnalyticsService.logEvent('Restart after failure');
};
this.flash = function(image, drive) {
// Stop scanning drives when flashing
// otherwise Windows throws EPERM
self.scanner.stop();
AnalyticsService.logEvent('Flash', {
image: image,
device: drive.device
});
return self.writer.flash(image, drive).then(function(success) {
// TODO: Find a better way to manage flash/check
// success/error state than a global boolean flag.
self.success = success;
if (self.success) {
OSNotificationService.send('Success!', 'Your flash is complete');
AnalyticsService.logEvent('Done');
$state.go('success');
} else {
OSNotificationService.send('Oops!', 'Looks like your flash has failed');
AnalyticsService.logEvent('Flash error');
}
})
.catch(function(error) {
self.writer.resetState();
dialog.showError(error);
})
.finally(OSWindowProgressService.clear);
};
});

View File

@ -0,0 +1,68 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const _ = require('lodash');
module.exports = function($uibModalInstance, DriveSelectorStateService, DriveScannerService) {
/**
* @summary The drive selector state
* @property
* @type Object
*
* @description
* The state has been splitted from the controller for
* testability purposes.
*/
this.state = DriveSelectorStateService;
/**
* @summary The drive scanner service
* @property
* @type Object
*
* @description
* We expose the whole service instead of the `.drives`
* property, which is the one we're interested in since
* this allows the property to be automatically updated
* when `DriveScannerService` detects a change in the drives.
*/
this.scanner = DriveScannerService;
/**
* @summary Close the modal and resolve the selected drive
* @function
* @public
*
* @example
* DriveSelectorController.closeModal();
*/
this.closeModal = function() {
const selectedDrive = DriveSelectorStateService.getSelectedDrive();
// Sanity check to cover the case where a drive is selected,
// the drive is then unplugged from the computer and the modal
// is resolved with a non-existent drive.
if (!selectedDrive || !_.includes(this.scanner.drives, selectedDrive)) {
return $uibModalInstance.dismiss();
}
return $uibModalInstance.close(selectedDrive);
};
};

View File

@ -0,0 +1,35 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Components.DriveSelector
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.DriveSelector';
const DriveSelector = angular.module(MODULE_NAME, [
require('angular-ui-bootstrap'),
require('../../../browser/modules/drive-scanner'),
require('../../../browser/models/selection-state')
]);
DriveSelector.controller('DriveSelectorController', require('./controllers/drive-selector'));
DriveSelector.service('DriveSelectorStateService', require('./services/drive-selector-state'));
DriveSelector.service('DriveSelectorService', require('./services/drive-selector'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,75 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const _ = require('lodash');
module.exports = function(SelectionStateModel) {
let self = this;
/**
* @summary Toggle select drive
* @function
* @public
*
* @param {Object} drive - drive
*
* @example
* DriveSelectorController.toggleSelectDrive({ drive });
*/
this.toggleSelectDrive = function(drive) {
if (this.isSelectedDrive(drive)) {
SelectionStateModel.removeDrive();
} else {
SelectionStateModel.setDrive(drive);
}
};
/**
* @summary Check if a drive is the selected one
* @function
* @public
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is selected
*
* @example
* if (DriveSelectorController.isSelectedDrive({ drive })) {
* console.log('The drive is selected!');
* }
*/
this.isSelectedDrive = function(drive) {
if (!_.has(drive, 'device')) {
return false;
}
return drive.device === _.get(self.getSelectedDrive(), 'device');
};
/**
* @summary Get selected drive
* @function
* @public
*
* @returns {Object} selected drive
*
* @example
* const drive = DriveSelectorStateService.getSelectedDrive();
*/
this.getSelectedDrive = SelectionStateModel.getDrive;
};

View File

@ -0,0 +1,70 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
module.exports = function($uibModal, $q) {
let modal = null;
/**
* @summary Open the drive selector widget
* @function
* @public
*
* @fulfil {(Object|Undefined)} - selected drive
* @returns {Promise}
*
* @example
* DriveSelectorService.open().then(function(drive) {
* console.log(drive);
* });
*/
this.open = function() {
modal = $uibModal.open({
animation: true,
templateUrl: './browser/components/drive-selector/templates/drive-selector-modal.tpl.html',
controller: 'DriveSelectorController as modal',
size: 'sm'
});
return modal.result;
};
/**
* @summary Close the drive selector widget
* @function
* @public
*
* @fulfil {Undefined}
* @returns {Promise}
*
* @example
* DriveSelectorService.close();
*/
this.close = function() {
if (modal) {
return modal.dismiss();
}
// Resolve `undefined` if the modal
// was already closed for consistency
return $q.resolve();
};
};

View File

@ -0,0 +1,17 @@
<div class="modal-header">
<h4 class="modal-title">SELECT A DRIVE</h4>
<button class="btn btn-default btn-sm" ng-click="modal.closeModal()">CLOSE</button>
</div>
<div class="modal-body">
<ul class="list-group">
<li class="list-group-item" ng-repeat="drive in modal.scanner.drives"
ng-click="modal.state.toggleSelectDrive(drive)">
<div>
<h4 class="list-group-item-heading">{{ drive.description }} - {{ drive.size }}</h4>
<p class="list-group-item-text">{{ drive.name }}</p>
</div>
<span class="tick tick--success" ng-disabled="!modal.state.isSelectedDrive(drive)"></span>
</li>
</ul>
</div>

View File

@ -0,0 +1,44 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @summary ProgressButton directive
* @function
* @public
*
* @description
* This directive provides a button containing a progress bar inside.
* The button is styled by default as a primary button.
*
* @returns {Object} directive
*
* @example
* <progress-button percentage="{{ 40 }}" striped>My Progress Button</progress-button>
*/
module.exports = function() {
return {
templateUrl: './browser/components/progress-button/templates/progress-button.tpl.html',
restrict: 'E',
replace: true,
transclude: true,
scope: {
percentage: '=',
striped: '@'
}
};
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,14 +14,15 @@
* limitations under the License.
*/
import { expect } from 'chai';
import { bytesToMegabytes } from '../../lib/shared/units';
'use strict';
describe('Shared: Units', function () {
describe('.bytesToMegabytes()', function () {
it('should convert bytes to megabytes', function () {
expect(bytesToMegabytes(1.2e7)).to.equal(12);
expect(bytesToMegabytes(332000)).to.equal(0.332);
});
});
});
/**
* @module Etcher.Components.ProgressButton
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.ProgressButton';
const ProgressButton = angular.module(MODULE_NAME, []);
ProgressButton.directive('progressButton', require('./directives/progress-button'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,124 @@
/*
* Copyright 2016 Resin.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.
*/
/**
* A button with a progress bar inside.
*
* From http://tympanus.net/Development/ProgressButtonStyles/
*
* The state of the progress bar is controller by the width, in percentage,
* of `.progress-button__bar`.
*
* The current percentage also needs to be reflected in the top level
* `.progress-button` element as a `percentage` attribute, without the percentage sign.
*
* If there is an action in place, the `active` attribute must be set to `true`.
* This is useful to determine if the progress bar is paused from the point of view
* of the styling.
*
* You can optionally pass the `.progress-button--striped` modified to get a striped
* progress bar.
*
* The stripe implementation idea was taken from:
*
* https://css-tricks.com/css3-progress-bars/
*
* Usage:
*
* <button class="progress-button progress-button--primary" percentage="50" active="true">
* <span class="progress-button__content">Button text</span>
* <span class="progress-button__bar" style="width: 50%;"></span>
* </button>
*/
$progress-button-stripes-width: 20px;
$progress-button-stripes-animation-duration: 1s;
.progress-button {
@extend .btn;
}
.progress-button--primary {
@extend .btn-primary;
.progress-button__bar {
background: lighten($brand-primary, 5);
}
$progress-button-stripes-background-color: desaturate($brand-primary, 5%);
$progress-button-stripes-color: desaturate(darken($brand-primary, 18%), 20%);
&.progress-button--striped {
background-image: -webkit-gradient(linear, 0 0, 100% 100%,
color-stop(0.25, $progress-button-stripes-color),
color-stop(0.25, $progress-button-stripes-background-color),
color-stop(0.50, $progress-button-stripes-background-color),
color-stop(0.50, $progress-button-stripes-color),
color-stop(0.75, $progress-button-stripes-color),
color-stop(0.75, $progress-button-stripes-background-color),
to($progress-button-stripes-background-color));
}
}
.progress-button[percentage="100"][active="false"] .progress-button__bar {
background-color: $brand-success;
}
.progress-button[percentage="100"][active="true"] .progress-button__bar {
background-color: $brand-warning;
}
// Prevent the button from being clickable
// when it has an active progress bar.
.progress-button[active="true"] {
pointer-events: none;
}
.progress-button__content {
position: relative;
z-index: 10;
}
.progress-button__bar {
position: absolute;
left: 0;
top: 0;
width: 0;
height: 100%;
// Subtle progress bar animation
transition: width 0.3s;
}
.progress-button--striped {
background-size: $progress-button-stripes-width $progress-button-stripes-width;
animation: progress-button-stripes $progress-button-stripes-animation-duration linear infinite;
overflow: hidden;
}
@keyframes progress-button-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: $progress-button-stripes-width $progress-button-stripes-width;
}
}

View File

@ -0,0 +1,7 @@
<button class="progress-button progress-button--primary"
ng-class="{
'progress-button--striped': striped && striped != 'false'
}">
<span class="progress-button__content" ng-transclude></span>
<span class="progress-button__bar" ng-style="{ width: percentage + '%' }"></span>
</button>

View File

@ -0,0 +1,63 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const path = require('path');
const fs = require('fs');
/**
* @summary SVGIcon directive
* @function
* @public
*
* @description
* This directive provides an easy way to load SVG icons
* by embedding the SVG contents inside the element, making
* it possible to style icons with CSS.
*
* @example
* <svg-icon path="path/to/icon.svg" width="40px" height="40px"></svg-icon>
*/
module.exports = function() {
return {
templateUrl: './browser/components/svg-icon/templates/svg-icon.tpl.html',
replace: true,
restrict: 'E',
scope: {
path: '@',
width: '@',
height: '@'
},
link: function(scope, element) {
// This means the path to the icon should be
// relative to *this directory*.
// TODO: There might be a way to compute the path
// relatively to the `index.html`.
const imagePath = path.join(__dirname, scope.path);
const contents = fs.readFileSync(imagePath, {
encoding: 'utf8'
});
element.html(contents);
element.css('width', scope.width || '40px');
element.css('height', scope.height || '40px');
}
};
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,6 @@
* limitations under the License.
*/
export const SUCCESS = 0;
export const GENERAL_ERROR = 1;
export const VALIDATION_ERROR = 2;
export const CANCELLED = 3;
.svg-icon[disabled] path {
fill: $color-disabled;
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Components.SVGIcon
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Components.SVGIcon';
const SVGIcon = angular.module(MODULE_NAME, []);
SVGIcon.directive('svgIcon', require('./directives/svg-icon'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1 @@
<div class="svg-icon"></div>

View File

@ -0,0 +1,200 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Models.SelectionState
*/
const _ = require('lodash');
const angular = require('angular');
const MODULE_NAME = 'Etcher.Models.SelectionState';
const SelectionStateModel = angular.module(MODULE_NAME, []);
SelectionStateModel.service('SelectionStateModel', function() {
let self = this;
/**
* @summary Selection state
* @type Object
* @private
*/
let selection = {};
/**
* @summary Set a drive
* @function
* @public
*
* @param {Object} drive - drive
*
* @example
* SelectionStateModel.setDrive({
* device: '/dev/disk2'
* });
*/
this.setDrive = function(drive) {
selection.drive = drive;
};
/**
* @summary Set a image
* @function
* @public
*
* @param {String} image - image
*
* @example
* SelectionStateModel.setImage('foo.img');
*/
this.setImage = function(image) {
selection.image = image;
};
/**
* @summary Get drive
* @function
* @public
*
* @returns {Object} drive
*
* @example
* const drive = SelectionStateModel.getDrive();
*/
this.getDrive = function() {
if (_.isEmpty(selection.drive)) {
return;
}
return selection.drive;
};
/**
* @summary Get image
* @function
* @public
*
* @returns {String} image
*
* @example
* const image = SelectionStateModel.getImage();
*/
this.getImage = function() {
return selection.image;
};
/**
* @summary Check if there is a selected drive
* @function
* @public
*
* @returns {Boolean} whether there is a selected drive
*
* @example
* if (SelectionStateModel.hasDrive()) {
* console.log('There is a drive!');
* }
*/
this.hasDrive = function() {
return Boolean(self.getDrive());
};
/**
* @summary Check if there is a selected image
* @function
* @public
*
* @returns {Boolean} whether there is a selected image
*
* @example
* if (SelectionStateModel.hasImage()) {
* console.log('There is an image!');
* }
*/
this.hasImage = function() {
return Boolean(self.getImage());
};
/**
* @summary Remove drive
* @function
* @public
*
* @example
* SelectionStateModel.removeDrive();
*/
this.removeDrive = _.partial(self.setDrive, undefined);
/**
* @summary Remove image
* @function
* @public
*
* @example
* SelectionStateModel.removeImage();
*/
this.removeImage = _.partial(self.setImage, undefined);
/**
* @summary Clear selections
* @function
* @public
*
* @param {Object} options - options
* @param {Boolean} [options.preserveImage] - preserve image
*
* @example
* SelectionStateModel.clear();
*
* @example
* SelectionStateModel.clear({ preserveImage: true });
*/
this.clear = function(options) {
if (options && options.preserveImage) {
selection = _.pick(selection, 'image');
} else {
selection = {};
}
};
/**
* @summary Check if a drive is the current drive
* @function
* @public
*
* @param {Object} drive - drive
* @returns {Boolean} whether the drive is the current drive
*
* @example
* if (SelectionStateModel.isCurrentDrive({
* device: '/dev/sdb',
* description: 'DataTraveler 2.0',
* size: '7.3G',
* mountpoint: '/media/UNTITLED',
* name: '/dev/sdb',
* system: false
* })) {
* console.log('This is the current drive!');
* }
*/
this.isCurrentDrive = function(drive) {
return angular.equals(self.getDrive(), drive);
};
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,45 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Models.Settings
*/
const angular = require('angular');
require('ngstorage');
const MODULE_NAME = 'Etcher.Models.Settings';
const SettingsModel = angular.module(MODULE_NAME, [
'ngStorage'
]);
SettingsModel.service('SettingsModel', function($localStorage) {
/**
* @summary Settings data
* @type Object
* @public
*/
this.data = $localStorage.$default({
errorReporting: true,
unmountOnSuccess: true,
validateWriteOnSuccess: true
});
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,159 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.analytics
*/
const _ = require('lodash');
const angular = require('angular');
const username = require('username');
const app = require('electron').remote.app;
const packageJSON = require('../../../package.json');
// Force Mixpanel snippet to load Mixpanel locally
// instead of using a CDN for performance reasons
window.MIXPANEL_CUSTOM_LIB_URL = '../bower_components/mixpanel/mixpanel.js';
require('../../../bower_components/mixpanel/mixpanel-jslib-snippet.js');
require('../../../bower_components/angular-mixpanel/src/angular-mixpanel');
const MODULE_NAME = 'Etcher.analytics';
const analytics = angular.module(MODULE_NAME, [
'analytics.mixpanel',
require('../models/settings')
]);
analytics.config(function($mixpanelProvider) {
$mixpanelProvider.apiKey('63e5fc4563e00928da67d1226364dd4c');
$mixpanelProvider.superProperties({
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
distinct_id: username.sync(),
// jscs:enable requireCamelCaseOrUpperCaseIdentifiers
electron: app.getVersion(),
node: process.version,
arch: process.arch,
version: packageJSON.version
});
});
// TrackJS integration
// http://docs.trackjs.com/tracker/framework-integrations
analytics.run(function($window) {
$window.trackJs.configure({
userId: username.sync(),
version: packageJSON.version
});
});
analytics.config(function($provide) {
$provide.decorator('$exceptionHandler', function($delegate, $window, $injector) {
return function(exception, cause) {
const SettingsModel = $injector.get('SettingsModel');
if (SettingsModel.data.errorReporting) {
$window.trackJs.track(exception);
}
$delegate(exception, cause);
};
});
$provide.decorator('$log', function($delegate, $window, $injector) {
// Save the original $log.debug()
let debugFn = $delegate.debug;
$delegate.debug = function(message) {
message = new Date() + ' ' + message;
const SettingsModel = $injector.get('SettingsModel');
if (SettingsModel.data.errorReporting) {
$window.trackJs.console.debug(message);
}
debugFn.call(null, message);
};
return $delegate;
});
});
analytics.service('AnalyticsService', function($log, $mixpanel, SettingsModel) {
let self = this;
/**
* @summary Log a debug message
* @function
* @public
*
* @description
* This function sends the debug message to TrackJS only.
*
* @param {String} message - message
*
* @example
* AnalyticsService.log('Hello World');
*/
this.log = function(message) {
$log.debug(message);
};
/**
* @summary Log an event
* @function
* @public
*
* @description
* This function sends the debug message to TrackJS and Mixpanel.
*
* @param {String} message - message
* @param {Object} [data] - event data
*
* @example
* AnalyticsService.logEvent('Select image', {
* image: '/dev/disk2'
* });
*/
this.logEvent = function(message, data) {
if (SettingsModel.data.errorReporting) {
// Clone data before passing it to `mixpanel.track`
// since this function mutates the object adding
// some custom private Mixpanel properties.
$mixpanel.track(message, _.clone(data));
}
if (data) {
message += ` (${JSON.stringify(data)})`;
}
self.log(message);
};
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,164 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.drive-scanner
*/
const angular = require('angular');
const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const drivelist = require('drivelist');
const MODULE_NAME = 'Etcher.drive-scanner';
const driveScanner = angular.module(MODULE_NAME, [
require('angular-q-promisify')
]);
driveScanner.service('DriveScannerService', function($q, $interval, $timeout) {
let self = this;
let interval = null;
/**
* @summary List of available drives
* @type {Object[]}
* @public
*/
this.drives = [];
/**
* @summary Check if there are available drives
* @function
* @public
*
* @returns {Boolean} whether there are available drives
*
* @example
* if (DriveScannerService.hasAvailableDrives()) {
* console.log('There are available drives!');
* }
*/
this.hasAvailableDrives = function() {
return !_.isEmpty(self.drives);
};
/**
* @summary Set the list of drives
* @function
* @private
*
* @param {Object[]} drives - drives
*
* @example
* DriveScannerService.scan().then(function(drives) {
* DriveScannerService.setDrives(drives);
* });
*/
this.setDrives = function(drives) {
// Only update if something has changed
// to avoid unnecessary DOM manipulations
// angular.equals ignores $$hashKey by default
if (!angular.equals(self.drives, drives)) {
self.drives = drives;
}
};
/**
* @summary Get available drives
* @function
* @public
*
* @fulfil {Object[]} - drives
* @returns {Promise}
*
* @example
* DriveScannerService.scan().then(function(drives) {
* console.log(drives);
* });
*/
this.scan = function() {
return $q.promisify(drivelist.list)().then(function(drives) {
return _.filter(drives, function(drive) {
return !drive.system;
});
});
};
/**
* @summary Scan drives and populate `.drives`
* @function
* @public
*
* @description
* This function returns an event emitter instance
* that emits a `scan` event everything it scans
* the drives successfully.
*
* @param {Number} ms - interval milliseconds
* @returns {EventEmitter} event emitter instance
*
* @example
* const emitter = DriveScannerService.start(2000);
*
* emitter.on('scan', function(drives) {
* console.log(drives);
* });
*/
this.start = function(ms) {
let emitter = new EventEmitter();
const fn = function() {
return self.scan().then(function(drives) {
emitter.emit('scan', drives);
self.setDrives(drives);
}).catch(function(error) {
emitter.emit('error', error);
});
};
// Make sure any pending interval is cancelled
// to avoid potential memory leaks.
self.stop();
// Call fn after in the next process tick
// to be able to capture the first run
// in unit tests.
$timeout(function() {
fn();
interval = $interval(fn, ms);
});
return emitter;
};
/**
* @summary Stop scanning drives
* @function
* @public
*
* @example
* DriveScannerService.stop();
*/
this.stop = function() {
$interval.cancel(interval);
};
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,173 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.image-writer
*/
const angular = require('angular');
const electron = require('electron');
if (window.mocha) {
var writer = electron.remote.require(require('path').join(__dirname, '..', '..', 'src', 'writer'));
} else {
var writer = electron.remote.require('./src/writer');
}
const MODULE_NAME = 'Etcher.image-writer';
const imageWriter = angular.module(MODULE_NAME, [
require('../models/settings'),
require('../utils/notifier/notifier')
]);
imageWriter.service('ImageWriterService', function($q, $timeout, SettingsModel, NotifierService) {
let self = this;
let flashing = false;
/**
* @summary Reset flash state
* @function
* @public
*
* @example
* ImageWriterService.resetState();
*/
this.resetState = function() {
self.state = {
progress: 0,
speed: 0
};
};
/**
* @summary Flash progress state
* @type Object
* @public
*/
this.state = {};
this.resetState();
/**
* @summary Check if currently flashing
* @function
* @private
*
* @returns {Boolean} whether is flashing or not
*
* @example
* if (ImageWriterService.isFlashing()) {
* console.log('We\'re currently flashing');
* }
*/
this.isFlashing = function() {
return flashing;
};
/**
* @summary Set the flashing status
* @function
* @private
*
* @description
* This function is extracted for testing purposes.
*
* @param {Boolean} status - flashing status
*
* @example
* ImageWriterService.setFlashing(true);
*/
this.setFlashing = function(status) {
flashing = Boolean(status);
};
/**
* @summary Perform write operation
* @function
* @private
*
* @description
* This function is extracted for testing purposes.
*
* @param {String} image - image path
* @param {Object} drive - drive
* @param {Function} onProgress - in progress callback (state)
*
* @returns {Promise}
*
* @example
* ImageWriter.performWrite('path/to/image.img', {
* device: '/dev/disk2'
* }, function(state) {
* console.log(state.percentage);
* });
*/
this.performWrite = function(image, drive, onProgress) {
return $q.when(writer.writeImage(image, drive, SettingsModel.data, onProgress));
};
/**
* @summary Flash an image to a drive
* @function
* @public
*
* @description
* This function will update `ImageWriterService.state` with the current writing state.
*
* @param {String} image - image path
* @param {Object} drive - drive
*
* @returns {Promise}
*
* @example
* ImageWriterService.flash('foo.img', {
* device: '/dev/disk2'
* }).then(function() {
* console.log('Write completed!');
* });
*/
this.flash = function(image, drive) {
if (self.isFlashing()) {
return $q.reject(new Error('There is already a flash in progress'));
}
self.setFlashing(true);
return self.performWrite(image, drive, function(state) {
// Safely bring the state to the world of Angular
$timeout(function() {
self.state = {
type: state.type,
progress: Math.floor(state.percentage),
// Transform bytes to megabytes preserving only two decimal places
speed: Math.floor(state.speed / 1e+6 * 100) / 100 || 0
};
NotifierService.emit('image-writer:state', self.state);
});
}).finally(function() {
self.setFlashing(false);
});
};
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,71 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const _ = require('lodash');
/**
* @summary Dropzone directive
* @function
* @public
*
* @description
* This directive provides an attribute to detect a file
* being dropped into the element.
*
* @param {Object} $timeout - Angular's timeout wrapper
* @returns {Object} directive
*
* @example
* <div os-dropzone="doSomething($file)">Drag a file here</div>
*/
module.exports = function($timeout) {
return {
restrict: 'A',
scope: {
osDropzone: '&'
},
link: function(scope, element) {
const domElement = element[0];
// See https://github.com/electron/electron/blob/master/docs/api/file-object.md
// We're not interested in these events
domElement.ondragover = _.constant(false);
domElement.ondragleave = _.constant(false);
domElement.ondragend = _.constant(false);
domElement.ondrop = function(event) {
event.preventDefault();
const filename = event.dataTransfer.files[0].path;
// Safely bring this to the word of Angular
$timeout(function() {
scope.osDropzone({
// Pass the filename as a named
// parameter called `$file`
$file: filename
});
});
return false;
};
}
};
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,16 +14,15 @@
* limitations under the License.
*/
import { logException } from '../modules/analytics';
import { showError } from '../os/dialog';
'use strict';
/**
* @summary Report an exception
* @module Etcher.OS.Dropzone
*/
export function report(exception?: Error) {
if (exception === undefined) {
return;
}
showError(exception);
logException(exception);
}
const angular = require('angular');
const MODULE_NAME = 'Etcher.OS.Dropzone';
const OSDropzone = angular.module(MODULE_NAME, []);
OSDropzone.directive('osDropzone', require('./directives/dropzone'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,31 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.OS.Notification
*
* The purpose of this module is to provide an easy way
* to send desktop notifications.
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.OS.Notification';
const OSNotification = angular.module(MODULE_NAME, []);
OSNotification.service('OSNotificationService', require('./services/notification'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,55 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const electron = require('electron');
module.exports = function() {
/**
* @summary Send a notification
* @function
* @public
*
* @description
* This function makes use of Electron's notification desktop
* integration feature. See:
* http://electron.atom.io/docs/v0.37.5/tutorial/desktop-environment-integration/
*
* @param {String} title - notification title
* @param {String} body - notification body
* @returns {Object} HTML5 notification object
*
* @example
* const notification = OSNotificationService.send('Hello', 'Foo Bar Bar');
* notification.onclick = function() {
* console.log('The notification has been clicked');
* };
*/
this.send = function(title, body) {
// `app.dock` is only defined in OS X
if (electron.remote.app.dock) {
electron.remote.app.dock.bounce();
}
return new Notification(title, {
body: body
});
};
};

View File

@ -0,0 +1,72 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const electron = require('electron');
const shell = electron.remote.require('shell');
const os = require('os');
const nodeOpen = require('open');
/**
* @summary OsOpenExternal directive
* @function
* @public
*
* @description
* This directive provides an attribute to open an external
* resource with the default operating system action.
*
* @returns {Object} directive
*
* @example
* <button os-open-external="https://resin.io">Resin.io</button>
*/
module.exports = function() {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attributes) {
// This directive might be added to elements
// other than buttons.
element.css('cursor', 'pointer');
element.on('click', function() {
// Electron's `shell.openExternal()` fails on GNU/Linux
// when Electron is ran with `sudo`.
// The issue was reported, and this is a workaround until
// its fixed on the Electron side.
// `node-open` is smart enough to check the `$SUDO_USER`
// environment variable and to prepend `sudo -u <user>`
// if needed.
// We keep `shell.openExternal()` for OSes other than
// Linux since we intend to fully rely on it when the
// issue is fixed, and since its closer integration with
// the operating system might lead to more accurate results
// than a third party NPM module.
//
// See https://github.com/electron/electron/issues/5039
if (os.platform() === 'linux') {
return nodeOpen(attributes.osOpenExternal);
}
shell.openExternal(attributes.osOpenExternal);
});
}
};
};

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.OS.OpenExternal
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.OS.OpenExternal';
const OSOpenExternal = angular.module(MODULE_NAME, []);
OSOpenExternal.directive('osOpenExternal', require('./directives/open-external'));
module.exports = MODULE_NAME;

View File

@ -0,0 +1,73 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
const electron = require('electron');
module.exports = function() {
const self = this;
/**
* @summary A reference to the current renderer Electron window
* @property
* @protected
*
* @description
* Since electron only has one renderer view, we can assume the
* current window is the one with id == 1.
*
* We expose this property to `this` for testability purposes.
*/
this.currentWindow = electron.remote.BrowserWindow.fromId(1);
/**
* @summary Set operating system window progress
* @function
* @public
*
* @description
* Show progress inline in operating system task bar
*
* @param {Number} percentage - percentage
*
* @example
* OSWindowProgressService.set(85);
*/
this.set = function(percentage) {
if (percentage > 100 || percentage < 0) {
throw new Error(`Invalid window progress percentage: ${percentage}`);
}
self.currentWindow.setProgressBar(percentage / 100);
};
/**
* @summary Clear the operating system window progress bar
* @function
* @public
*
* @example
* OSWindowProgressService.clear();
*/
this.clear = function() {
// Passing 0 or null/undefined doesn't work.
self.currentWindow.setProgressBar(-1);
};
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,22 +14,19 @@
* limitations under the License.
*/
import * as electron from 'electron';
import * as settings from '../../../models/settings';
import { logEvent } from '../../../modules/analytics';
'use strict';
/**
* @summary Open an external resource
* @module Etcher.OS.WindowProgress
*
* The purpose of this module is to provide an easy way
* to interact with the operating system's window progress
* functionality, as described in Electron docs.
*/
export async function open(url: string) {
// Don't open links if they're disabled by the env var
if (await settings.get('disableExternalLinks')) {
return;
}
logEvent('Open external link', { url });
const angular = require('angular');
const MODULE_NAME = 'Etcher.OS.WindowProgress';
const OSWindowProgress = angular.module(MODULE_NAME, []);
OSWindowProgress.service('OSWindowProgressService', require('./services/window-progress'));
if (url) {
electron.shell.openExternal(url);
}
}
module.exports = MODULE_NAME;

View File

@ -0,0 +1,46 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
module.exports = function($state, SelectionStateModel, ImageWriterService, AnalyticsService, SettingsModel) {
/**
* @summary Settings data
* @type Object
* @public
*/
this.settings = SettingsModel.data;
/**
* @summary Restart the flashing process
* @function
* @public
*
* @param {Object} [options] - options
* @param {Boolean} [options.preserveImage=false] - preserve image
*
* @example
* FinishController.restart({ preserveImage: true });
*/
this.restart = function(options) {
SelectionStateModel.clear(options);
ImageWriterService.resetState();
AnalyticsService.logEvent('Restart', options);
$state.go('main');
};
};

View File

@ -0,0 +1,50 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Pages.Finish
*
* The finish page represents the application state where
* the the flash/validation has completed.
*
* Its purpose is to display success or failure information,
* as well as the "next steps".
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Pages.Finish';
const FinishPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../modules/image-writer'),
require('../../modules/analytics'),
require('../../models/selection-state'),
require('../../models/settings')
]);
FinishPage.controller('FinishController', require('./controllers/finish'));
FinishPage.config(function($stateProvider) {
$stateProvider
.state('success', {
url: '/success',
controller: 'FinishController as finish',
templateUrl: './browser/pages/finish/templates/success.tpl.html'
});
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,32 @@
<div class="row around-xs">
<div class="col-xs">
<div class="box text-center">
<h3><span class="tick tick--success" class="space-right-tiny"></span> Flash Complete!</h3>
<p class="soft space-vertical-medium" ng-show="finish.settings.unmountOnSuccess">Safely ejected and ready for use</p>
<div class="row center-xs space-vertical-large">
<div class="col-xs-4 space-medium">
<div class="box">
<p class="soft button-label">Would you like to flash the same image?</p>
<button class="btn btn-primary btn-brick" ng-click="finish.restart({ preserveImage: true })">
Use <b>same</b> image
</button>
</div>
</div>
<div class="col-xs separator-xs"></div>
<div class="col-xs-4 space-medium">
<div class="box">
<p class="soft button-label">Would you like to flash a new image?</p>
<button class="btn btn-primary btn-brick" ng-click="finish.restart()">
Use <b>new</b> image
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,19 +14,15 @@
* limitations under the License.
*/
import * as React from 'react';
'use strict';
import { BaseButton } from '../../styled-components';
import * as i18next from 'i18next';
module.exports = function(SettingsModel) {
export interface FlashAnotherProps {
onClick: () => void;
}
/**
* @summary Settings data
* @type Object
* @public
*/
this.storage = SettingsModel.data;
export const FlashAnother = (props: FlashAnotherProps) => {
return (
<BaseButton primary onClick={props.onClick}>
{i18next.t('flash.another')}
</BaseButton>
);
};

View File

@ -0,0 +1,41 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Pages.Settings
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Pages.Settings';
const SettingsPage = angular.module(MODULE_NAME, [
require('angular-ui-router'),
require('../../models/settings')
]);
SettingsPage.controller('SettingsController', require('./controllers/settings'));
SettingsPage.config(function($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings',
controller: 'SettingsController as settings',
templateUrl: './browser/pages/settings/templates/settings.tpl.html'
});
});
module.exports = MODULE_NAME;

View File

@ -0,0 +1,24 @@
<div class="text-left">
<h1 class="space-bottom-large">Settings</h1>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="settings.storage.errorReporting">
<span>Enable error reporting</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="settings.storage.unmountOnSuccess">
<span>Enable auto-unmounting on success</span>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="settings.storage.validateWriteOnSuccess">
<span>Enable write validation on success</span>
</label>
</div>
</div>

View File

@ -0,0 +1,54 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @summary HideIfState directive
* @function
* @public
*
* @description
* This directive provides an attribute to hide an element
* when the current UI Router state matches the specified one.
*
* @param {Object} $state - ui router $state
* @returns {Object} directive
*
* @example
* <button hide-if-state="settings" ui-sref="main">Go Back</button>
*/
module.exports = function($state) {
return {
restrict: 'A',
scope: {
hideIfState: '@'
},
link: function(scope, element) {
scope.$watch(function() {
return $state.is(scope.hideIfState);
}, function(isState) {
if (isState) {
element.css('display', 'none');
} else {
element.css('display', 'initial');
}
});
}
};
};

View File

@ -0,0 +1,54 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @summary ShowIfState directive
* @function
* @public
*
* @description
* This directive provides an attribute to show an element
* when the current UI Router state matches the specified one.
*
* @param {Object} $state - ui router $state
* @returns {Object} directive
*
* @example
* <button show-if-state="main" ui-sref="settings">Settings</button>
*/
module.exports = function($state) {
return {
restrict: 'A',
scope: {
showIfState: '@'
},
link: function(scope, element) {
scope.$watch(function() {
return $state.is(scope.showIfState);
}, function(isState) {
if (isState) {
element.css('display', 'initial');
} else {
element.css('display', 'none');
}
});
}
};
};

View File

@ -0,0 +1,36 @@
/*
* Copyright 2016 Resin.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.
*/
'use strict';
/**
* @module Etcher.Utils.IfState
*
* The purpose of this module is to provide an attribute
* directive to show/hide an element when the current UI Router
* state matches the one specified in the attribute.
*/
const angular = require('angular');
const MODULE_NAME = 'Etcher.Utils.IfState';
const IfState = angular.module(MODULE_NAME, [
require('angular-ui-router')
]);
IfState.directive('showIfState', require('./directives/show-if-state'));
IfState.directive('hideIfState', require('./directives/hide-if-state'));
module.exports = MODULE_NAME;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 balena.io
* Copyright 2016 Resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,28 +14,18 @@
* limitations under the License.
*/
import { basename } from 'path';
'use strict';
export const SUPPORTED_EXTENSIONS = [
'bin',
'bz2',
'dmg',
'dsk',
'etch',
'gz',
'hddimg',
'img',
'iso',
'raw',
'rpi-sdimg',
'sdcard',
'vhd',
'wic',
'xz',
'zip',
];
/**
* @module Etcher.Utils.Notifier
*
* The purpose of this module is to provide a safe way
* to emit and receive events from the $rootScope.
*/
export function looksLikeWindowsImage(imagePath: string): boolean {
const regex = /windows|win7|win8|win10|winxp/i;
return regex.test(basename(imagePath));
}
const angular = require('angular');
const MODULE_NAME = 'Etcher.Utils.Notifier';
const Notifier = angular.module(MODULE_NAME, []);
Notifier.service('NotifierService', require('./services/notifier'));
module.exports = MODULE_NAME;

Some files were not shown because too many files have changed in this diff Show More