Merge pull request #3071 from balena-io/flash-from-url

Flash from url
This commit is contained in:
Alexis Svinartchouk 2020-04-27 18:51:35 +02:00 committed by GitHub
commit 83ed333fa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 552 additions and 279 deletions

View File

@ -133,7 +133,8 @@ export function TargetSelector(props: TargetSelectorProps) {
return ( return (
<StepButton <StepButton
tabindex={targets.length > 0 ? -1 : 2} primary
tabIndex={targets.length > 0 ? -1 : 2}
disabled={props.disabled} disabled={props.disabled}
onClick={props.openDriveSelector} onClick={props.openDriveSelector}
> >

View File

@ -17,6 +17,7 @@
import * as React from 'react'; import * as React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { position, right } from 'styled-system'; import { position, right } from 'styled-system';
import { BaseButton, ThemedProvider } from '../../styled-components'; import { BaseButton, ThemedProvider } from '../../styled-components';
const Div = styled.div<any>` const Div = styled.div<any>`

View File

@ -134,6 +134,7 @@ export class ProgressButton extends React.Component<ProgressButtonProps> {
return ( return (
<StepSelection> <StepSelection>
<StepButton <StepButton
primary
onClick={this.props.callback} onClick={this.props.callback}
disabled={this.props.disabled} disabled={this.props.disabled}
> >

View File

@ -14,13 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import * as sdk from 'etcher-sdk'; import { faFile, faLink } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { sourceDestination } from 'etcher-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { GPTPartition, MBRPartition } from 'partitioninfo'; import { GPTPartition, MBRPartition } from 'partitioninfo';
import * as path from 'path'; import * as path from 'path';
import * as React from 'react'; import * as React from 'react';
import { Modal } from 'rendition'; import { ButtonProps, Card as BaseCard, Input, Modal, Txt } from 'rendition';
import { default as styled } from 'styled-components'; import styled from 'styled-components';
import * as errors from '../../../../shared/errors'; import * as errors from '../../../../shared/errors';
import * as messages from '../../../../shared/messages'; import * as messages from '../../../../shared/messages';
@ -35,15 +37,51 @@ import { replaceWindowsNetworkDriveLetter } from '../../os/windows-network-drive
import { import {
ChangeButton, ChangeButton,
DetailsText, DetailsText,
Footer,
StepButton, StepButton,
StepNameButton, StepNameButton,
StepSelection, StepSelection,
Underline,
} from '../../styled-components'; } from '../../styled-components';
import { colors } from '../../theme';
import { middleEllipsis } from '../../utils/middle-ellipsis'; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { SVGIcon } from '../svg-icon/svg-icon'; import { SVGIcon } from '../svg-icon/svg-icon';
const recentUrlImagesKey = 'recentUrlImages';
function normalizeRecentUrlImages(urls: any): string[] {
if (!Array.isArray(urls)) {
urls = [];
}
return _.chain(urls)
.filter(_.isString)
.reject(_.isEmpty)
.uniq()
.takeRight(5)
.value();
}
function getRecentUrlImages(): string[] {
let urls = [];
try {
urls = JSON.parse(localStorage.getItem(recentUrlImagesKey) || '[]');
} catch {
// noop
}
return normalizeRecentUrlImages(urls);
}
function setRecentUrlImages(urls: string[]) {
localStorage.setItem(
recentUrlImagesKey,
JSON.stringify(normalizeRecentUrlImages(urls)),
);
}
const Card = styled(BaseCard)`
hr {
margin: 5px 0;
}
`;
// TODO move these styles to rendition // TODO move these styles to rendition
const ModalText = styled.p` const ModalText = styled.p`
a { a {
@ -55,16 +93,6 @@ const ModalText = styled.p`
} }
`; `;
const mainSupportedExtensions = _.intersection(
['img', 'iso', 'zip'],
supportedFormats.getAllExtensions(),
);
const extraSupportedExtensions = _.difference(
supportedFormats.getAllExtensions(),
mainSupportedExtensions,
).sort();
function getState() { function getState() {
return { return {
hasImage: selectionState.hasImage(), hasImage: selectionState.hasImage(),
@ -73,36 +101,144 @@ function getState() {
}; };
} }
interface ImageSelectorProps { const URLSelector = ({ done }: { done: (imageURL: string) => void }) => {
flashing: boolean; const [imageURL, setImageURL] = React.useState('');
const [recentImages, setRecentImages]: [
string[],
(value: React.SetStateAction<string[]>) => void,
] = React.useState([]);
const [loading, setLoading] = React.useState(false);
React.useEffect(() => {
const fetchRecentUrlImages = async () => {
const recentUrlImages: string[] = await getRecentUrlImages();
setRecentImages(recentUrlImages);
};
fetchRecentUrlImages();
}, []);
return (
<Modal
primaryButtonProps={{
disabled: loading,
}}
done={async () => {
setLoading(true);
const sanitizedRecentUrls = normalizeRecentUrlImages([
...recentImages,
imageURL,
]);
setRecentUrlImages(sanitizedRecentUrls);
await done(imageURL);
}}
>
<label style={{ width: '100%' }}>
<Txt mb="10px" fontSize="20px">
Use Image URL
</Txt>
<Input
value={imageURL}
placeholder="Enter a valid URL"
type="text"
onChange={(evt: React.ChangeEvent<HTMLInputElement>) =>
setImageURL(evt.target.value)
}
/>
</label>
{!_.isEmpty(recentImages) && (
<div>
Recent
<Card
style={{ padding: '10px 15px' }}
rows={_.map(recentImages, recent => (
<Txt
key={recent}
onClick={() => {
setImageURL(recent);
}}
>
<span>
{_.last(_.split(recent, '/'))} - {recent}
</span>
</Txt>
))}
/>
</div>
)}
</Modal>
);
};
interface Flow {
icon?: JSX.Element;
onClick: (evt: MouseEvent) => void;
label: string;
} }
interface ImageSelectorState { const FlowSelector = styled(
({ flow, ...props }: { flow: Flow; props?: ButtonProps }) => {
return (
<StepButton plain onClick={flow.onClick} icon={flow.icon} {...props}>
{flow.label}
</StepButton>
);
},
)`
border-radius: 24px;
:enabled:hover {
background-color: ${colors.primary.background};
color: ${colors.primary.foreground};
svg {
color: ${colors.primary.foreground}!important;
}
}
`;
export type Source =
| typeof sourceDestination.File
| typeof sourceDestination.Http;
export interface SourceOptions {
imagePath: string;
SourceType: Source;
}
interface SourceSelectorProps {
flashing: boolean;
afterSelected: (options: SourceOptions) => void;
}
interface SourceSelectorState {
hasImage: boolean; hasImage: boolean;
imageName: string; imageName: string;
imageSize: number; imageSize: number;
warning: { message: string; title: string | null } | null; warning: { message: string; title: string | null } | null;
showImageDetails: boolean; showImageDetails: boolean;
showURLSelector: boolean;
} }
export class ImageSelector extends React.Component< export class SourceSelector extends React.Component<
ImageSelectorProps, SourceSelectorProps,
ImageSelectorState SourceSelectorState
> { > {
private unsubscribe: () => void; private unsubscribe: () => void;
private afterSelected: SourceSelectorProps['afterSelected'];
constructor(props: ImageSelectorProps) { constructor(props: SourceSelectorProps) {
super(props); super(props);
this.state = { this.state = {
...getState(), ...getState(),
warning: null, warning: null,
showImageDetails: false, showImageDetails: false,
showURLSelector: false,
}; };
this.openImageSelector = this.openImageSelector.bind(this); this.openImageSelector = this.openImageSelector.bind(this);
this.openURLSelector = this.openURLSelector.bind(this);
this.reselectImage = this.reselectImage.bind(this); this.reselectImage = this.reselectImage.bind(this);
this.onDrop = this.onDrop.bind(this); this.onDrop = this.onDrop.bind(this);
this.showSelectedImageDetails = this.showSelectedImageDetails.bind(this); this.showSelectedImageDetails = this.showSelectedImageDetails.bind(this);
this.afterSelected = props.afterSelected.bind(this);
} }
public componentDidMount() { public componentDidMount() {
@ -122,11 +258,11 @@ export class ImageSelector extends React.Component<
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid, flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid,
}); });
this.openImageSelector(); selectionState.deselectImage();
} }
private selectImage( private selectImage(
image: sdk.sourceDestination.Metadata & { image: sourceDestination.Metadata & {
path: string; path: string;
extension: string; extension: string;
hasMBR: boolean; hasMBR: boolean;
@ -203,7 +339,7 @@ export class ImageSelector extends React.Component<
} }
} }
private async selectImageByPath(imagePath: string) { private async selectImageByPath({ imagePath, SourceType }: SourceOptions) {
try { try {
imagePath = await replaceWindowsNetworkDriveLetter(imagePath); imagePath = await replaceWindowsNetworkDriveLetter(imagePath);
} catch (error) { } catch (error) {
@ -220,12 +356,31 @@ export class ImageSelector extends React.Component<
return; return;
} }
const source = new sdk.sourceDestination.File({ let source;
path: imagePath, if (SourceType === sourceDestination.File) {
}); source = new sourceDestination.File({
path: imagePath,
});
} else {
if (
!_.startsWith(imagePath, 'https://') &&
!_.startsWith(imagePath, 'http://')
) {
const invalidImageError = errors.createUserError({
title: 'Unsupported protocol',
description: messages.error.unsupportedProtocol(),
});
osDialog.showError(invalidImageError);
analytics.logEvent('Unsupported protocol', { path: imagePath });
return;
}
source = new sourceDestination.Http(imagePath);
}
try { try {
const innerSource = await source.getInnerSource(); const innerSource = await source.getInnerSource();
const metadata = (await innerSource.getMetadata()) as sdk.sourceDestination.Metadata & { const metadata = (await innerSource.getMetadata()) as sourceDestination.Metadata & {
hasMBR: boolean; hasMBR: boolean;
partitions: MBRPartition[] | GPTPartition[]; partitions: MBRPartition[] | GPTPartition[];
path: string; path: string;
@ -241,6 +396,10 @@ export class ImageSelector extends React.Component<
metadata.path = imagePath; metadata.path = imagePath;
metadata.extension = path.extname(imagePath).slice(1); metadata.extension = path.extname(imagePath).slice(1);
this.selectImage(metadata); this.selectImage(metadata);
this.afterSelected({
imagePath,
SourceType,
});
} catch (error) { } catch (error) {
const imageError = errors.createUserError({ const imageError = errors.createUserError({
title: 'Error opening image', title: 'Error opening image',
@ -278,7 +437,10 @@ export class ImageSelector extends React.Component<
}); });
return; return;
} }
this.selectImageByPath(imagePath); this.selectImageByPath({
imagePath,
SourceType: sourceDestination.File,
});
} catch (error) { } catch (error) {
exceptionReporter.report(error); exceptionReporter.report(error);
} }
@ -287,10 +449,25 @@ export class ImageSelector extends React.Component<
private onDrop(event: React.DragEvent<HTMLDivElement>) { private onDrop(event: React.DragEvent<HTMLDivElement>) {
const [file] = event.dataTransfer.files; const [file] = event.dataTransfer.files;
if (file) { if (file) {
this.selectImageByPath(file.path); this.selectImageByPath({
imagePath: file.path,
SourceType: sourceDestination.File,
});
} }
} }
private openURLSelector() {
analytics.logEvent('Open image URL selector', {
applicationSessionUuid:
store.getState().toJS().applicationSessionUuid || '',
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid,
});
this.setState({
showURLSelector: true,
});
}
private onDragOver(event: React.DragEvent<HTMLDivElement>) { private onDragOver(event: React.DragEvent<HTMLDivElement>) {
// Needed to get onDrop events on div elements // Needed to get onDrop events on div elements
event.preventDefault(); event.preventDefault();
@ -316,7 +493,7 @@ export class ImageSelector extends React.Component<
// TODO add a visual change when dragging a file over the selector // TODO add a visual change when dragging a file over the selector
public render() { public render() {
const { flashing } = this.props; const { flashing } = this.props;
const { showImageDetails } = this.state; const { showImageDetails, showURLSelector } = this.state;
const hasImage = selectionState.hasImage(); const hasImage = selectionState.hasImage();
@ -353,7 +530,7 @@ export class ImageSelector extends React.Component<
</StepNameButton> </StepNameButton>
{!flashing && ( {!flashing && (
<ChangeButton plain mb={14} onClick={this.reselectImage}> <ChangeButton plain mb={14} onClick={this.reselectImage}>
Change Remove
</ChangeButton> </ChangeButton>
)} )}
<DetailsText> <DetailsText>
@ -362,15 +539,24 @@ export class ImageSelector extends React.Component<
</> </>
) : ( ) : (
<StepSelection> <StepSelection>
<StepButton onClick={this.openImageSelector}> <FlowSelector
Select image key="Flash from file"
</StepButton> flow={{
<Footer> onClick: this.openImageSelector,
{mainSupportedExtensions.join(', ')}, and{' '} label: 'Flash from file',
<Underline tooltip={extraSupportedExtensions.join(', ')}> icon: <FontAwesomeIcon icon={faFile} />,
many more }}
</Underline> />
</Footer> ;
<FlowSelector
key="Flash from URL"
flow={{
onClick: this.openURLSelector,
label: 'Flash from URL',
icon: <FontAwesomeIcon icon={faLink} />,
}}
/>
;
</StepSelection> </StepSelection>
)} )}
</div> </div>
@ -413,6 +599,35 @@ export class ImageSelector extends React.Component<
{selectionState.getImagePath()} {selectionState.getImagePath()}
</Modal> </Modal>
)} )}
{showURLSelector && (
<URLSelector
done={async (imagePath: string) => {
// Avoid analytics and selection state changes
// if no file was resolved from the dialog.
if (!imagePath) {
analytics.logEvent('URL selector closed', {
applicationSessionUuid: store.getState().toJS()
.applicationSessionUuid,
flashingWorkflowUuid: store.getState().toJS()
.flashingWorkflowUuid,
});
this.setState({
showURLSelector: false,
});
return;
}
await this.selectImageByPath({
imagePath,
SourceType: sourceDestination.Http,
});
this.setState({
showURLSelector: false,
});
}}
/>
)}
</> </>
); );
} }

View File

@ -25,6 +25,7 @@ import * as path from 'path';
import * as packageJSON from '../../../../package.json'; import * as packageJSON from '../../../../package.json';
import * as errors from '../../../shared/errors'; import * as errors from '../../../shared/errors';
import * as permissions from '../../../shared/permissions'; import * as permissions from '../../../shared/permissions';
import { SourceOptions } from '../components/source-selector/source-selector';
import * as flashState from '../models/flash-state'; import * as flashState from '../models/flash-state';
import * as selectionState from '../models/selection-state'; import * as selectionState from '../models/selection-state';
import * as settings from '../models/settings'; import * as settings from '../models/settings';
@ -143,6 +144,7 @@ export function performWrite(
image: string, image: string,
drives: DrivelistDrive[], drives: DrivelistDrive[],
onProgress: sdk.multiWrite.OnProgressFunction, onProgress: sdk.multiWrite.OnProgressFunction,
source: SourceOptions,
): Promise<{ cancelled?: boolean }> { ): Promise<{ cancelled?: boolean }> {
let cancelled = false; let cancelled = false;
ipc.serve(); ipc.serve();
@ -169,7 +171,7 @@ export function performWrite(
trim: settings.get('trim'), trim: settings.get('trim'),
}; };
ipc.server.on('fail', ({ error }) => { ipc.server.on('fail', ({ error }: { error: Error & { code: string } }) => {
handleErrorLogging(error, analyticsData); handleErrorLogging(error, analyticsData);
}); });
@ -191,6 +193,8 @@ export function performWrite(
ipc.server.emit(socket, 'write', { ipc.server.emit(socket, 'write', {
imagePath: image, imagePath: image,
destinations: drives, destinations: drives,
source,
SourceType: source.SourceType.name,
validateWriteOnSuccess: settings.get('validateWriteOnSuccess'), validateWriteOnSuccess: settings.get('validateWriteOnSuccess'),
trim: settings.get('trim'), trim: settings.get('trim'),
unmountOnSuccess: settings.get('unmountOnSuccess'), unmountOnSuccess: settings.get('unmountOnSuccess'),
@ -252,6 +256,7 @@ export function performWrite(
export async function flash( export async function flash(
image: string, image: string,
drives: DrivelistDrive[], drives: DrivelistDrive[],
source: SourceOptions,
): Promise<void> { ): Promise<void> {
if (flashState.isFlashing()) { if (flashState.isFlashing()) {
throw new Error('There is already a flash in progress'); throw new Error('There is already a flash in progress');
@ -281,6 +286,7 @@ export async function flash(
image, image,
drives, drives,
flashState.setProgressState, flashState.setProgressState,
source,
); );
flashState.unsetFlashingFlag(result); flashState.unsetFlashingFlag(result);
} catch (error) { } catch (error) {

View File

@ -22,6 +22,7 @@ import * as constraints from '../../../../shared/drive-constraints';
import * as messages from '../../../../shared/messages'; import * as messages from '../../../../shared/messages';
import { DriveSelectorModal } from '../../components/drive-selector/DriveSelectorModal'; import { DriveSelectorModal } from '../../components/drive-selector/DriveSelectorModal';
import { ProgressButton } from '../../components/progress-button/progress-button'; import { ProgressButton } from '../../components/progress-button/progress-button';
import { SourceOptions } from '../../components/source-selector/source-selector';
import { SVGIcon } from '../../components/svg-icon/svg-icon'; import { SVGIcon } from '../../components/svg-icon/svg-icon';
import * as availableDrives from '../../models/available-drives'; import * as availableDrives from '../../models/available-drives';
import * as flashState from '../../models/flash-state'; import * as flashState from '../../models/flash-state';
@ -71,7 +72,10 @@ const getErrorMessageFromCode = (errorCode: string) => {
return ''; return '';
}; };
async function flashImageToDrive(goToSuccess: () => void): Promise<string> { async function flashImageToDrive(
goToSuccess: () => void,
sourceOptions: SourceOptions,
): Promise<string> {
const devices = selection.getSelectedDevices(); const devices = selection.getSelectedDevices();
const image: any = selection.getImage(); const image: any = selection.getImage();
const drives = _.filter(availableDrives.getDrives(), (drive: any) => { const drives = _.filter(availableDrives.getDrives(), (drive: any) => {
@ -89,7 +93,7 @@ async function flashImageToDrive(goToSuccess: () => void): Promise<string> {
const iconPath = '../../assets/icon.png'; const iconPath = '../../assets/icon.png';
const basename = path.basename(image.path); const basename = path.basename(image.path);
try { try {
await imageWriter.flash(image.path, drives); await imageWriter.flash(image.path, drives, sourceOptions);
if (!flashState.wasLastFlashCancelled()) { if (!flashState.wasLastFlashCancelled()) {
const flashResults: any = flashState.getFlashResults(); const flashResults: any = flashState.getFlashResults();
notification.send( notification.send(
@ -119,7 +123,7 @@ async function flashImageToDrive(goToSuccess: () => void): Promise<string> {
if (!errorMessage) { if (!errorMessage) {
error.image = basename; error.image = basename;
analytics.logException(error); analytics.logException(error);
errorMessage = messages.error.genericFlashError(); errorMessage = messages.error.genericFlashError(error);
} }
return errorMessage; return errorMessage;
@ -160,7 +164,17 @@ const formatSeconds = (totalSeconds: number) => {
return `${minutes}m${seconds}s`; return `${minutes}m${seconds}s`;
}; };
export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => { interface FlashProps {
shouldFlashStepBeDisabled: boolean;
goToSuccess: () => void;
source: SourceOptions;
}
export const Flash = ({
shouldFlashStepBeDisabled,
goToSuccess,
source,
}: FlashProps) => {
const state: any = flashState.getFlashState(); const state: any = flashState.getFlashState();
const isFlashing = flashState.isFlashing(); const isFlashing = flashState.isFlashing();
const flashErrorCode = flashState.getLastFlashErrorCode(); const flashErrorCode = flashState.getLastFlashErrorCode();
@ -179,7 +193,7 @@ export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => {
return; return;
} }
setErrorMessage(await flashImageToDrive(goToSuccess)); setErrorMessage(await flashImageToDrive(goToSuccess, source));
}; };
const handleFlashErrorResponse = (shouldRetry: boolean) => { const handleFlashErrorResponse = (shouldRetry: boolean) => {
@ -215,7 +229,7 @@ export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => {
return; return;
} }
setErrorMessage(await flashImageToDrive(goToSuccess)); setErrorMessage(await flashImageToDrive(goToSuccess, source));
}; };
return ( return (
@ -299,7 +313,11 @@ export const Flash = ({ shouldFlashStepBeDisabled, goToSuccess }: any) => {
done={() => handleFlashErrorResponse(true)} done={() => handleFlashErrorResponse(true)}
action={'Retry'} action={'Retry'}
> >
<Txt>{errorMessage}</Txt> <Txt>
{_.map(errorMessage.split('\n'), (message, key) => (
<p key={key}>{message}</p>
))}
</Txt>
</Modal> </Modal>
)} )}

View File

@ -16,25 +16,32 @@
import { faCog, faQuestionCircle } from '@fortawesome/free-solid-svg-icons'; import { faCog, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { sourceDestination } from 'etcher-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as path from 'path'; import * as path from 'path';
import * as React from 'react'; import * as React from 'react';
import { Button } from 'rendition'; import { Flex } from 'rendition';
import styled from 'styled-components';
import { FeaturedProject } from '../../components/featured-project/featured-project'; import { FeaturedProject } from '../../components/featured-project/featured-project';
import FinishPage from '../../components/finish/finish'; import FinishPage from '../../components/finish/finish';
import { ImageSelector } from '../../components/image-selector/image-selector';
import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos'; import { ReducedFlashingInfos } from '../../components/reduced-flashing-infos/reduced-flashing-infos';
import { SafeWebview } from '../../components/safe-webview/safe-webview'; import { SafeWebview } from '../../components/safe-webview/safe-webview';
import { SettingsModal } from '../../components/settings/settings'; import { SettingsModal } from '../../components/settings/settings';
import {
SourceOptions,
SourceSelector,
} from '../../components/source-selector/source-selector';
import { SVGIcon } from '../../components/svg-icon/svg-icon'; import { SVGIcon } from '../../components/svg-icon/svg-icon';
import * as flashState from '../../models/flash-state'; import * as flashState from '../../models/flash-state';
import * as selectionState from '../../models/selection-state'; import * as selectionState from '../../models/selection-state';
import * as settings from '../../models/settings'; import * as settings from '../../models/settings';
import { observe } from '../../models/store'; import { observe } from '../../models/store';
import { open as openExternal } from '../../os/open-external/services/open-external'; import { open as openExternal } from '../../os/open-external/services/open-external';
import { ThemedProvider } from '../../styled-components'; import {
import { colors } from '../../theme'; IconButton as BaseIcon,
ThemedProvider,
} from '../../styled-components';
import { middleEllipsis } from '../../utils/middle-ellipsis'; import { middleEllipsis } from '../../utils/middle-ellipsis';
import { bytesToClosestUnit } from '../../../../shared/units'; import { bytesToClosestUnit } from '../../../../shared/units';
@ -42,6 +49,10 @@ import { bytesToClosestUnit } from '../../../../shared/units';
import { DriveSelector } from './DriveSelector'; import { DriveSelector } from './DriveSelector';
import { Flash } from './Flash'; import { Flash } from './Flash';
const Icon = styled(BaseIcon)`
margin-right: 20px;
`;
function getDrivesTitle() { function getDrivesTitle() {
const drives = selectionState.getSelectedDrives(); const drives = selectionState.getSelectedDrives();
@ -80,6 +91,7 @@ interface MainPageState {
current: 'main' | 'success'; current: 'main' | 'success';
isWebviewShowing: boolean; isWebviewShowing: boolean;
hideSettings: boolean; hideSettings: boolean;
source: SourceOptions;
} }
export class MainPage extends React.Component< export class MainPage extends React.Component<
@ -92,6 +104,10 @@ export class MainPage extends React.Component<
current: 'main', current: 'main',
isWebviewShowing: false, isWebviewShowing: false,
hideSettings: true, hideSettings: true,
source: {
imagePath: '',
SourceType: sourceDestination.File,
},
...this.stateHelper(), ...this.stateHelper(),
}; };
} }
@ -153,29 +169,22 @@ export class MainPage extends React.Component<
right: 0, right: 0,
}} }}
> >
<Button <Icon
icon={<FontAwesomeIcon icon={faCog} />} icon={<FontAwesomeIcon icon={faCog} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain plain
onClick={() => this.setState({ hideSettings: false })}
tabIndex={5} tabIndex={5}
onClick={() => this.setState({ hideSettings: false })}
/> />
{!settings.get('disableExternalLinks') && ( {!settings.get('disableExternalLinks') && (
<Button <Icon
icon={<FontAwesomeIcon icon={faQuestionCircle} />} icon={<FontAwesomeIcon icon={faQuestionCircle} />}
color={colors.secondary.background}
fontSize={24}
style={{ width: '30px' }}
plain
onClick={() => onClick={() =>
openExternal( openExternal(
selectionState.getImageSupportUrl() || selectionState.getImageSupportUrl() ||
'https://github.com/balena-io/etcher/blob/master/SUPPORT.md', 'https://github.com/balena-io/etcher/blob/master/SUPPORT.md',
) )
} }
tabIndex={5} tabIndex={6}
/> />
)} )}
</span> </span>
@ -188,12 +197,17 @@ export class MainPage extends React.Component<
/> />
)} )}
<div <Flex
className="page-main row around-xs" className="page-main row around-xs"
style={{ margin: '110px 50px' }} style={{ margin: '110px 50px' }}
> >
<div className="col-xs"> <div className="col-xs">
<ImageSelector flashing={this.state.isFlashing} /> <SourceSelector
flashing={this.state.isFlashing}
afterSelected={(source: SourceOptions) =>
this.setState({ source })
}
/>
</div> </div>
<div className="col-xs"> <div className="col-xs">
@ -242,9 +256,10 @@ export class MainPage extends React.Component<
<Flash <Flash
goToSuccess={() => this.setState({ current: 'success' })} goToSuccess={() => this.setState({ current: 'success' })}
shouldFlashStepBeDisabled={shouldFlashStepBeDisabled} shouldFlashStepBeDisabled={shouldFlashStepBeDisabled}
source={this.state.source}
/> />
</div> </div>
</div> </Flex>
</ThemedProvider> </ThemedProvider>
); );
} else if (this.state.current === 'success') { } else if (this.state.current === 'success') {

View File

@ -15,7 +15,7 @@
*/ */
import * as React from 'react'; import * as React from 'react';
import { Button, Flex, Provider, Txt } from 'rendition'; import { Button, ButtonProps, Flex, Provider, Txt } from 'rendition';
import styled from 'styled-components'; import styled from 'styled-components';
import { space } from 'styled-system'; import { space } from 'styled-system';
@ -33,19 +33,21 @@ const theme = {
opacity: 1, opacity: 1,
}, },
extend: () => ` extend: () => `
width: 200px; && {
height: 48px; width: 200px;
font-size: 16px; height: 48px;
font-size: 16px;
&:disabled { &:disabled {
background-color: ${colors.dark.disabled.background}; background-color: ${colors.dark.disabled.background};
color: ${colors.dark.disabled.foreground}; color: ${colors.dark.disabled.foreground};
opacity: 1; opacity: 1;
&:hover { &:hover {
background-color: ${colors.dark.disabled.background}; background-color: ${colors.dark.disabled.background};
color: ${colors.dark.disabled.foreground}; color: ${colors.dark.disabled.foreground};
} }
}
} }
`, `,
}, },
@ -59,26 +61,47 @@ export const BaseButton = styled(Button)`
height: 48px; height: 48px;
`; `;
export const StepButton = (props: any) => ( export const IconButton = styled(props => <Button plain {...props} />)`
<BaseButton primary {...props}></BaseButton> &&& {
); width: 24px;
height: 24px;
font-size: 24px;
color: #fff;
export const ChangeButton = styled(BaseButton)` > svg {
color: ${colors.primary.background}; font-size: 1em;
padding: 0;
width: 100%;
height: auto;
&:enabled {
&:hover,
&:focus,
&:active {
color: #8f9297;
} }
} }
${space}
`; `;
export const StepNameButton = styled(BaseButton)`
export const StepButton = styled((props: ButtonProps) => (
<Button {...props}></Button>
))`
color: rgba(255, 255, 255, 0.7);
margin: auto;
`;
export const ChangeButton = styled(Button)`
&& {
border-radius: 24px;
color: ${colors.primary.background};
padding: 0;
height: 18px;
font-size: 14px;
&:enabled {
&:hover,
&:focus,
&:active {
color: #8f9297;
}
}
${space}
}
`;
export const StepNameButton = styled(Button)`
border-radius: 24px;
margin: auto;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -44,7 +44,7 @@ export const colors = {
}, },
primary: { primary: {
foreground: '#fff', foreground: '#fff',
background: '#2297de', background: '#00aeef',
}, },
secondary: { secondary: {
foreground: '#000', foreground: '#000',

View File

@ -20,8 +20,10 @@ import * as sdk from 'etcher-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as ipc from 'node-ipc'; import * as ipc from 'node-ipc';
import { File, Http } from 'etcher-sdk/build/source-destination';
import { toJSON } from '../../shared/errors'; import { toJSON } from '../../shared/errors';
import { GENERAL_ERROR, SUCCESS } from '../../shared/exit-codes'; import { GENERAL_ERROR, SUCCESS } from '../../shared/exit-codes';
import { SourceOptions } from '../app/components/source-selector/source-selector';
ipc.config.id = process.env.IPC_CLIENT_ID as string; ipc.config.id = process.env.IPC_CLIENT_ID as string;
ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string; ipc.config.socketRoot = process.env.IPC_SOCKET_ROOT as string;
@ -136,6 +138,8 @@ interface WriteOptions {
unmountOnSuccess: boolean; unmountOnSuccess: boolean;
validateWriteOnSuccess: boolean; validateWriteOnSuccess: boolean;
trim: boolean; trim: boolean;
source: SourceOptions;
SourceType: string;
} }
ipc.connectTo(IPC_SERVER_ID, () => { ipc.connectTo(IPC_SERVER_ID, () => {
@ -224,9 +228,15 @@ ipc.connectTo(IPC_SERVER_ID, () => {
direct: true, direct: true,
}); });
}); });
const source = new sdk.sourceDestination.File({ const { SourceType } = options;
path: options.imagePath, let source;
}); if (SourceType === File.name) {
source = new File({
path: options.imagePath,
});
} else {
source = new Http(options.imagePath);
}
try { try {
const results = await writeAndValidate( const results = await writeAndValidate(
source, source,

View File

@ -131,8 +131,8 @@ export const error = {
].join(' '); ].join(' ');
}, },
genericFlashError: () => { genericFlashError: (err: Error) => {
return 'Something went wrong. If it is a compressed image, please check that the archive is not corrupted.'; return `Something went wrong. If it is a compressed image, please check that the archive is not corrupted.\n${err.message}`;
}, },
validation: () => { validation: () => {
@ -191,4 +191,8 @@ export const error = {
'Please try again, and contact the Etcher team if the problem persists.', 'Please try again, and contact the Etcher team if the problem persists.',
].join(' '); ].join(' ');
}, },
unsupportedProtocol: () => {
return 'Only http:// and https:// URLs are supported.';
},
}; };

302
npm-shrinkwrap.json generated
View File

@ -114,18 +114,23 @@
} }
}, },
"@babel/runtime-corejs2": { "@babel/runtime-corejs2": {
"version": "7.7.4", "version": "7.9.2",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.7.4.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.9.2.tgz",
"integrity": "sha512-hKNcmHQbBSJFnZ82ewYtWDZ3fXkP/l1XcfRtm7c8gHPM/DMecJtFFBEp7KMLZTuHwwb7RfemHdsEnd7L916Z6A==", "integrity": "sha512-ayjSOxuK2GaSDJFCtLgHnYjuMyIpViNujWrZo8GUpN60/n7juzJKK5yOo6RFVb0zdU9ACJFK+MsZrUnj3OmXMw==",
"requires": { "requires": {
"core-js": "^2.6.5", "core-js": "^2.6.5",
"regenerator-runtime": "^0.13.2" "regenerator-runtime": "^0.13.4"
}, },
"dependencies": { "dependencies": {
"core-js": { "core-js": {
"version": "2.6.10", "version": "2.6.11",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
} }
} }
}, },
@ -447,9 +452,9 @@
"dev": true "dev": true
}, },
"@types/color": { "@types/color": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.1.tgz",
"integrity": "sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q==", "integrity": "sha512-oeUWVaAwI+xINDUx+3F2vJkl/vVB03VChFF/Gl3iQCdbcakjuoJyMOba+3BXRtnBhxZ7uBYqQBi9EpLnvSoztA==",
"requires": { "requires": {
"@types/color-convert": "*" "@types/color-convert": "*"
} }
@ -520,14 +525,14 @@
} }
}, },
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.3", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
"integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==" "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA=="
}, },
"@types/lodash": { "@types/lodash": {
"version": "4.14.144", "version": "4.14.149",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.144.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
"integrity": "sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg==" "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ=="
}, },
"@types/marked": { "@types/marked": {
"version": "0.3.0", "version": "0.3.0",
@ -625,9 +630,9 @@
} }
}, },
"@types/react-jsonschema-form": { "@types/react-jsonschema-form": {
"version": "1.6.6", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/@types/react-jsonschema-form/-/react-jsonschema-form-1.6.6.tgz", "resolved": "https://registry.npmjs.org/@types/react-jsonschema-form/-/react-jsonschema-form-1.7.1.tgz",
"integrity": "sha512-Z3mY9Kij0AFMXsswvEYLm/i+F4aRNVzRjLqAYdX4mxJcpMYJdKxH6+BnT+orXI+i9AoED0XR9DOt7ofcI5JrKQ==", "integrity": "sha512-mUU3efUOfEupoDSlCgJTOHEXJ90ZClQLThmoobkV6e8wnkSDP9EiUYza5br/QaJF8EAQFS/l/STfb/WDiUVr1w==",
"requires": { "requires": {
"@types/json-schema": "*", "@types/json-schema": "*",
"@types/react": "*" "@types/react": "*"
@ -667,9 +672,9 @@
} }
}, },
"@types/sanitize-html": { "@types/sanitize-html": {
"version": "1.20.2", "version": "1.22.0",
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-1.20.2.tgz", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-1.22.0.tgz",
"integrity": "sha512-SrefiiBebGIhxEFkpbbYOwO1S6+zQLWAC4s4tipchlHq1aO9bp0xiapM7Zm0ml20MF+3OePWYdksB1xtneKPxg==", "integrity": "sha512-zRtkG+Z9ikdEyIQL6ZouqZIPWnzoioJxgxAUFOc91f+nC8yB4GOMzGpy9V3WYfmkjaVNL4zlsycwHWykFq1HNg==",
"requires": { "requires": {
"@types/htmlparser2": "*" "@types/htmlparser2": "*"
} }
@ -746,12 +751,9 @@
} }
}, },
"@types/uuid": { "@types/uuid": {
"version": "3.4.6", "version": "3.4.8",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.6.tgz", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.8.tgz",
"integrity": "sha512-cCdlC/1kGEZdEglzOieLDYBxHsvEOIg7kp/2FYyVR9Pxakq+Qf/inL3RKQ+PA8gOlI/NnL+fXmQH12nwcGzsHw==", "integrity": "sha512-zHWce3allXWSmRx6/AGXKCtSOA7JjeWd2L3t4aHfysNk8mouQnWCocveaT7a4IEIlPVHp81jzlnknqTgCjCLXA=="
"requires": {
"@types/node": "*"
}
}, },
"@types/webpack": { "@types/webpack": {
"version": "4.41.2", "version": "4.41.2",
@ -1477,11 +1479,6 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true "dev": true
}, },
"array-uniq": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
},
"array-unique": { "array-unique": {
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@ -3016,9 +3013,9 @@
"dev": true "dev": true
}, },
"copy-to-clipboard": { "copy-to-clipboard": {
"version": "3.2.0", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
"integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==", "integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
"requires": { "requires": {
"toggle-selection": "^1.0.6" "toggle-selection": "^1.0.6"
} }
@ -3147,11 +3144,11 @@
} }
}, },
"crypto-random-string": { "crypto-random-string": {
"version": "3.0.1", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.0.1.tgz", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.2.0.tgz",
"integrity": "sha512-dUL0cJ4PBLanJGJQBHQUkvZ3C4q13MXzl54oRqAIiJGiNkOZ4JDwkg/SBo7daGghzlJv16yW1p/4lIQukmbedA==", "integrity": "sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==",
"requires": { "requires": {
"type-fest": "^0.5.2" "type-fest": "^0.8.1"
} }
}, },
"css": { "css": {
@ -3515,29 +3512,29 @@
} }
}, },
"dagre": { "dagre": {
"version": "0.8.4", "version": "0.8.5",
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.4.tgz", "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
"integrity": "sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==", "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==",
"requires": { "requires": {
"graphlib": "^2.1.7", "graphlib": "^2.1.8",
"lodash": "^4.17.4" "lodash": "^4.17.15"
} }
}, },
"dagre-d3-unofficial": { "dagre-d3": {
"version": "0.6.4", "version": "0.6.4",
"resolved": "https://registry.npmjs.org/dagre-d3-unofficial/-/dagre-d3-unofficial-0.6.4.tgz", "resolved": "https://registry.npmjs.org/dagre-d3/-/dagre-d3-0.6.4.tgz",
"integrity": "sha512-xihvMCALDS/X646WyqBSMN1kl7f0K1Urd42EKYgmyISwML1Bep1MCMm+2Q3TIJe6y8TwHKmW6oYXnP5I0J/LIg==", "integrity": "sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==",
"requires": { "requires": {
"d3": "^5.12", "d3": "^5.14",
"dagre": "^0.8.4", "dagre": "^0.8.5",
"graphlib": "^2.1.7", "graphlib": "^2.1.8",
"lodash": "^4.17.15" "lodash": "^4.17.15"
}, },
"dependencies": { "dependencies": {
"d3": { "d3": {
"version": "5.14.2", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-5.14.2.tgz", "resolved": "https://registry.npmjs.org/d3/-/d3-5.15.0.tgz",
"integrity": "sha512-Ccipa9XrYW5N0QkP6u0Qb8kU6WekIXBiDenmZm1zLvuq/9pBBhRCJLCICEOsH5Og4B0Xw02bhqGkK5VN/oPH0w==", "integrity": "sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg==",
"requires": { "requires": {
"d3-array": "1", "d3-array": "1",
"d3-axis": "1", "d3-axis": "1",
@ -4260,18 +4257,6 @@
"requires": { "requires": {
"domelementtype": "^2.0.1", "domelementtype": "^2.0.1",
"entities": "^2.0.0" "entities": "^2.0.0"
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
} }
}, },
"domain-browser": { "domain-browser": {
@ -4281,25 +4266,26 @@
"dev": true "dev": true
}, },
"domelementtype": { "domelementtype": {
"version": "1.3.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
}, },
"domhandler": { "domhandler": {
"version": "2.4.2", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
"requires": { "requires": {
"domelementtype": "1" "domelementtype": "^2.0.1"
} }
}, },
"domutils": { "domutils": {
"version": "1.7.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
"requires": { "requires": {
"dom-serializer": "0", "dom-serializer": "^0.2.1",
"domelementtype": "1" "domelementtype": "^2.0.1",
"domhandler": "^3.0.0"
} }
}, },
"dot-prop": { "dot-prop": {
@ -5194,9 +5180,9 @@
} }
}, },
"entities": { "entities": {
"version": "1.1.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}, },
"env-paths": { "env-paths": {
"version": "2.2.0", "version": "2.2.0",
@ -6586,11 +6572,11 @@
"dev": true "dev": true
}, },
"graphlib": { "graphlib": {
"version": "2.1.7", "version": "2.1.8",
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
"integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
"requires": { "requires": {
"lodash": "^4.17.5" "lodash": "^4.17.15"
} }
}, },
"grommet": { "grommet": {
@ -6799,9 +6785,9 @@
} }
}, },
"hoist-non-react-statics": { "hoist-non-react-statics": {
"version": "3.3.1", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": { "requires": {
"react-is": "^16.7.0" "react-is": "^16.7.0"
} }
@ -6890,28 +6876,14 @@
} }
}, },
"htmlparser2": { "htmlparser2": {
"version": "3.10.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"requires": { "requires": {
"domelementtype": "^1.3.1", "domelementtype": "^2.0.1",
"domhandler": "^2.3.0", "domhandler": "^3.0.0",
"domutils": "^1.5.1", "domutils": "^2.0.0",
"entities": "^1.1.1", "entities": "^2.0.0"
"inherits": "^2.0.1",
"readable-stream": "^3.1.1"
},
"dependencies": {
"readable-stream": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
} }
}, },
"http-cache-semantics": { "http-cache-semantics": {
@ -8463,9 +8435,9 @@
} }
}, },
"markdown-to-jsx": { "markdown-to-jsx": {
"version": "6.10.3", "version": "6.11.0",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.10.3.tgz", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.0.tgz",
"integrity": "sha512-PSoUyLnW/xoW6RsxZrquSSz5eGEOTwa15H5eqp3enmrp8esmgDJmhzd6zmQ9tgAA9TxJzx1Hmf3incYU/IamoQ==", "integrity": "sha512-RH7LCJQ4RFmPqVeZEesKaO1biRzB/k4utoofmTCp3Eiw6D7qfvK8fzZq/2bjEJAtVkfPrM5SMt5APGf2rnaKMg==",
"requires": { "requires": {
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"unquote": "^1.1.0" "unquote": "^1.1.0"
@ -8600,15 +8572,15 @@
"dev": true "dev": true
}, },
"mermaid": { "mermaid": {
"version": "8.4.3", "version": "8.4.8",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.3.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.4.8.tgz",
"integrity": "sha512-qgjIINkm9hqzMt1n7cZ9of1ZXhwqSOPrvuwcBMz3RBq/D+c4T/vN3RAjpW+ZINGbbhD+fMESl/xFvqI9svxt/w==", "integrity": "sha512-sumTNBFwMX7oMQgogdr3NhgTeQOiwcEsm23rQ4KHGW7tpmvMwER1S+1gjCSSnqlmM/zw7Ga7oesYCYicKboRwQ==",
"requires": { "requires": {
"@braintree/sanitize-url": "^3.1.0", "@braintree/sanitize-url": "^3.1.0",
"crypto-random-string": "^3.0.1", "crypto-random-string": "^3.0.1",
"d3": "^5.7.0", "d3": "^5.7.0",
"dagre": "^0.8.4", "dagre": "^0.8.4",
"dagre-d3-unofficial": "0.6.4", "dagre-d3": "^0.6.4",
"graphlib": "^2.1.7", "graphlib": "^2.1.7",
"he": "^1.2.0", "he": "^1.2.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
@ -8618,9 +8590,9 @@
}, },
"dependencies": { "dependencies": {
"d3": { "d3": {
"version": "5.14.2", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-5.14.2.tgz", "resolved": "https://registry.npmjs.org/d3/-/d3-5.15.0.tgz",
"integrity": "sha512-Ccipa9XrYW5N0QkP6u0Qb8kU6WekIXBiDenmZm1zLvuq/9pBBhRCJLCICEOsH5Og4B0Xw02bhqGkK5VN/oPH0w==", "integrity": "sha512-C+E80SL2nLLtmykZ6klwYj5rPqB5nlfN5LdWEAVdWPppqTD8taoJi2PxLZjPeYT8FFRR2yucXq+kBlOnnvZeLg==",
"requires": { "requires": {
"d3-array": "1", "d3-array": "1",
"d3-axis": "1", "d3-axis": "1",
@ -9109,9 +9081,9 @@
} }
}, },
"moment-mini": { "moment-mini": {
"version": "2.22.1", "version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.22.1.tgz", "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.24.0.tgz",
"integrity": "sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw==" "integrity": "sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ=="
}, },
"mountutils": { "mountutils": {
"version": "1.3.19", "version": "1.3.19",
@ -9270,9 +9242,9 @@
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
}, },
"nanoid": { "nanoid": {
"version": "2.1.7", "version": "2.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-fmS3qwDldm4bE01HCIRqNk+f255CNjnAoeV3Zzzv0KemObHKqYgirVaZA9DtKcjogicWjYcHkJs4D5A8CjnuVQ==" "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
}, },
"nanomatch": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
@ -10266,9 +10238,9 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "7.0.23", "version": "7.0.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.23.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz",
"integrity": "sha512-hOlMf3ouRIFXD+j2VJecwssTwbvsPGJVMzupptg+85WA+i7MwyrydmQAgY3R+m0Bc0exunhbJmijy8u8+vufuQ==", "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==",
"requires": { "requires": {
"chalk": "^2.4.2", "chalk": "^2.4.2",
"source-map": "^0.6.1", "source-map": "^0.6.1",
@ -10634,9 +10606,9 @@
"integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==" "integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw=="
}, },
"react-jsonschema-form": { "react-jsonschema-form": {
"version": "1.8.0", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.8.0.tgz", "resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.8.1.tgz",
"integrity": "sha512-3rZZ1tCG+vtXRXEdX751t5L1c1TUGk1pvSY/4LzBrUo5FlwulnwJpkosE4BqwSRxvfMckP8YoHFQV4KjzaHGgQ==", "integrity": "sha512-aaDloxNAcGXOOOcdKOxxqEEn5oDlPUZgWcs8unXXB9vjBRgCF8rCm/wVSv1u2G5ih0j/BX6Ewd/WjI2g00lPdg==",
"requires": { "requires": {
"@babel/runtime-corejs2": "^7.4.5", "@babel/runtime-corejs2": "^7.4.5",
"ajv": "^6.7.0", "ajv": "^6.7.0",
@ -10649,9 +10621,9 @@
}, },
"dependencies": { "dependencies": {
"core-js": { "core-js": {
"version": "2.6.10", "version": "2.6.11",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
} }
} }
}, },
@ -10985,9 +10957,9 @@
"dev": true "dev": true
}, },
"rendition": { "rendition": {
"version": "11.24.0", "version": "11.24.2",
"resolved": "https://registry.npmjs.org/rendition/-/rendition-11.24.0.tgz", "resolved": "https://registry.npmjs.org/rendition/-/rendition-11.24.2.tgz",
"integrity": "sha512-1aFKDyuqx6myF5gcVJZYUMucaOSdGU0KnTnyBPZAdFJQcG8xLmJMy7x+4Pd5QxRyK16W16s3VUBzvQaLeSJmyA==", "integrity": "sha512-e24GiqiiQtzb2SiqFCn6r/peXHeHjzVEcmQTSpki694Ub3RFkOMheY+KC9hCpf7qXTN6wfv1xhEzSb9cdD82zw==",
"requires": { "requires": {
"@types/color": "^3.0.0", "@types/color": "^3.0.0",
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
@ -11028,9 +11000,9 @@
}, },
"dependencies": { "dependencies": {
"@types/node": { "@types/node": {
"version": "10.17.6", "version": "10.17.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz",
"integrity": "sha512-0a2X6cgN3RdPBL2MIlR6Lt0KlM7fOFsutuXcdglcOq6WvLnYXgPQSh0Mx6tO1KCAE8MxbHSOSTWDoUxRq+l3DA==" "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q=="
}, },
"color": { "color": {
"version": "3.1.2", "version": "3.1.2",
@ -11470,19 +11442,19 @@
} }
}, },
"sanitize-html": { "sanitize-html": {
"version": "1.20.1", "version": "1.22.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.22.1.tgz",
"integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", "integrity": "sha512-++IMC00KfMQc45UWZJlhWOlS9eMrME38sFG9GXfR+k6oBo9JXSYQgTOZCl9j3v/smFTRNT9XNwz5DseFdMY+2Q==",
"requires": { "requires": {
"chalk": "^2.4.1", "chalk": "^2.4.1",
"htmlparser2": "^3.10.0", "htmlparser2": "^4.1.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.escaperegexp": "^4.1.2", "lodash.escaperegexp": "^4.1.2",
"lodash.isplainobject": "^4.0.6", "lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1", "lodash.isstring": "^4.0.1",
"lodash.mergewith": "^4.6.1", "lodash.mergewith": "^4.6.2",
"postcss": "^7.0.5", "postcss": "^7.0.27",
"srcset": "^1.0.0", "srcset": "^2.0.1",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },
"dependencies": { "dependencies": {
@ -12187,9 +12159,9 @@
"dev": true "dev": true
}, },
"slugify": { "slugify": {
"version": "1.3.6", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.3.6.tgz", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.0.tgz",
"integrity": "sha512-wA9XS475ZmGNlEnYYLPReSfuz/c3VQsEMoU43mi6OnKMCdbnFXd4/Yg7J0lBv8jkPolacMpOrWEaoYxuE1+hoQ==" "integrity": "sha512-FtLNsMGBSRB/0JOE2A0fxlqjI6fJsgHGS13iTuVT28kViI4JjUiNqp/vyis0ZXYcMnpR3fzGNkv+6vRlI2GwdQ=="
}, },
"snapdragon": { "snapdragon": {
"version": "0.8.2", "version": "0.8.2",
@ -12436,13 +12408,9 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
}, },
"srcset": { "srcset": {
"version": "1.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", "resolved": "https://registry.npmjs.org/srcset/-/srcset-2.0.1.tgz",
"integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", "integrity": "sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ=="
"requires": {
"array-uniq": "^1.0.2",
"number-is-nan": "^1.0.0"
}
}, },
"sshpk": { "sshpk": {
"version": "1.16.1", "version": "1.16.1",
@ -13271,9 +13239,9 @@
"dev": true "dev": true
}, },
"type-fest": { "type-fest": {
"version": "0.5.2", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
}, },
"typed-error": { "typed-error": {
"version": "3.2.0", "version": "3.2.0",
@ -13293,9 +13261,9 @@
"dev": true "dev": true
}, },
"ua-parser-js": { "ua-parser-js": {
"version": "0.7.20", "version": "0.7.21",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
}, },
"udif": { "udif": {
"version": "0.17.0", "version": "0.17.0",
@ -13321,9 +13289,9 @@
} }
}, },
"uglify-js": { "uglify-js": {
"version": "3.7.1", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.1.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz",
"integrity": "sha512-pnOF7jY82wdIhATVn87uUY/FHU+MDUdPLkmGFvGoclQmeu229eTkbG5gjGGBi3R7UuYYSEeYXY/TTY5j2aym2g==", "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==",
"requires": { "requires": {
"commander": "~2.20.3", "commander": "~2.20.3",
"source-map": "~0.6.1" "source-map": "~0.6.1"
@ -14516,4 +14484,4 @@
} }
} }
} }
} }

View File

@ -74,7 +74,7 @@
"react": "^16.8.5", "react": "^16.8.5",
"react-dom": "^16.8.5", "react-dom": "^16.8.5",
"redux": "^3.5.2", "redux": "^3.5.2",
"rendition": "^11.24.0", "rendition": "^11.24.2",
"request": "^2.81.0", "request": "^2.81.0",
"resin-corvus": "^2.0.5", "resin-corvus": "^2.0.5",
"roboto-fontface": "^0.9.0", "roboto-fontface": "^0.9.0",

View File

@ -16,6 +16,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { Drive as DrivelistDrive } from 'drivelist'; import { Drive as DrivelistDrive } from 'drivelist';
import { sourceDestination } from 'etcher-sdk';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as ipc from 'node-ipc'; import * as ipc from 'node-ipc';
import { assert, SinonStub, stub } from 'sinon'; import { assert, SinonStub, stub } from 'sinon';
@ -28,6 +29,12 @@ const fakeDrive: DrivelistDrive = {};
describe('Browser: imageWriter', () => { describe('Browser: imageWriter', () => {
describe('.flash()', () => { describe('.flash()', () => {
const imagePath = 'foo.img';
const sourceOptions = {
imagePath,
SourceType: sourceDestination.File,
};
describe('given a successful write', () => { describe('given a successful write', () => {
let performWriteStub: SinonStub; let performWriteStub: SinonStub;
@ -51,7 +58,7 @@ describe('Browser: imageWriter', () => {
sourceChecksum: '1234', sourceChecksum: '1234',
}); });
imageWriter.flash('foo.img', [fakeDrive]).finally(() => { imageWriter.flash(imagePath, [fakeDrive], sourceOptions).finally(() => {
expect(flashState.isFlashing()).to.be.false; expect(flashState.isFlashing()).to.be.false;
}); });
}); });
@ -62,19 +69,23 @@ describe('Browser: imageWriter', () => {
sourceChecksum: '1234', sourceChecksum: '1234',
}); });
const writing = imageWriter.flash('foo.img', [fakeDrive]); const writing = imageWriter.flash(
imageWriter.flash('foo.img', [fakeDrive]).catch(_.noop); imagePath,
[fakeDrive],
sourceOptions,
);
imageWriter.flash(imagePath, [fakeDrive], sourceOptions).catch(_.noop);
writing.finally(() => { writing.finally(() => {
assert.calledOnce(performWriteStub); assert.calledOnce(performWriteStub);
}); });
}); });
it('should reject the second flash attempt', () => { it('should reject the second flash attempt', () => {
imageWriter.flash('foo.img', [fakeDrive]); imageWriter.flash(imagePath, [fakeDrive], sourceOptions);
let rejectError: Error; let rejectError: Error;
imageWriter imageWriter
.flash('foo.img', [fakeDrive]) .flash(imagePath, [fakeDrive], sourceOptions)
.catch(error => { .catch(error => {
rejectError = error; rejectError = error;
}) })
@ -103,7 +114,7 @@ describe('Browser: imageWriter', () => {
it('should set flashing to false when done', () => { it('should set flashing to false when done', () => {
imageWriter imageWriter
.flash('foo.img', [fakeDrive]) .flash(imagePath, [fakeDrive], sourceOptions)
.catch(_.noop) .catch(_.noop)
.finally(() => { .finally(() => {
expect(flashState.isFlashing()).to.be.false; expect(flashState.isFlashing()).to.be.false;
@ -112,7 +123,7 @@ describe('Browser: imageWriter', () => {
it('should set the error code in the flash results', () => { it('should set the error code in the flash results', () => {
imageWriter imageWriter
.flash('foo.img', [fakeDrive]) .flash(imagePath, [fakeDrive], sourceOptions)
.catch(_.noop) .catch(_.noop)
.finally(() => { .finally(() => {
const flashResults = flashState.getFlashResults(); const flashResults = flashState.getFlashResults();
@ -128,7 +139,7 @@ describe('Browser: imageWriter', () => {
let rejection: Error; let rejection: Error;
imageWriter imageWriter
.flash('foo.img', [fakeDrive]) .flash(imagePath, [fakeDrive], sourceOptions)
.catch(error => { .catch(error => {
rejection = error; rejection = error;
}) })