Skip to content

Commit

Permalink
feat: add ability to select package between npm and yarn (#447)
Browse files Browse the repository at this point in the history
  • Loading branch information
vhashimotoo authored Aug 6, 2020
1 parent 7bab157 commit f9fe9db
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 49 deletions.
44 changes: 43 additions & 1 deletion src/renderer/components/settings-execution.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Callout, Checkbox, FormGroup, InputGroup } from '@blueprintjs/core';
import {
Callout,
Checkbox,
FormGroup,
InputGroup,
Radio,
RadioGroup,
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import * as React from 'react';

import { IPackageManager } from '../npm';
import { AppState } from '../state';

export interface ExecutionSettingsProps {
Expand Down Expand Up @@ -122,7 +130,41 @@ export class ExecutionSettings extends React.Component<ExecutionSettingsProps> {
/>
</FormGroup>
</Callout>
<br />
<Callout>
<FormGroup>
<span style={{ marginRight: 4 }}>
Electron Fiddle will install packages on runtime if they are
imported within your fiddle with <code>require</code>. It uses{' '}
<a href="https://www.npmjs.com/" target="_blank" rel="noreferrer">
npm
</a>{' '}
as its package manager by default, but{' '}
<a
href="https://classic.yarnpkg.com/lang/en/"
target="_blank"
rel="noreferrer"
>
Yarn
</a>{' '}
is also available.
</span>
<RadioGroup
onChange={this.handlePMChange}
selectedValue={this.props.appState.packageManager}
inline={true}
>
<Radio label="npm" value="npm" />
<Radio label="yarn" value="yarn" />
</RadioGroup>
</FormGroup>
</Callout>
</div>
);
}

private handlePMChange = (event: React.FormEvent<HTMLInputElement>) => {
this.props.appState.packageManager = event.currentTarget
.value as IPackageManager;
};
}
31 changes: 21 additions & 10 deletions src/renderer/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { exec } from '../utils/exec';

const { builtinModules } = require('module');

export interface NpmOperationOptions {
export type IPackageManager = 'npm' | 'yarn';

export interface PMOperationOptions {
dir: string;
packageManager: IPackageManager;
}

export let isInstalled: boolean | null = null;
Expand Down Expand Up @@ -107,29 +110,37 @@ export function findModules(input: string): Array<string> {
/**
* Installs given modules to a given folder.
*
* @param {NpmOperationOptions} { dir }
* @param {PMOperationOptions} { dir, packageManager }
* @param {...Array<string>} names
* @returns {Promise<string>}
*/
export async function installModules(
{ dir }: NpmOperationOptions,
{ dir, packageManager }: PMOperationOptions,
...names: Array<string>
): Promise<string> {
const nameArgs = names.length > 0 ? ['-S', ...names] : ['--dev --prod'];
let nameArgs: Array<string> = [];

if (packageManager === 'npm') {
nameArgs = names.length > 0 ? ['-S', ...names] : ['--dev --prod'];
} else {
nameArgs = [...names];
}

const installCommand = packageManager === 'npm' ? 'npm install' : 'yarn add';

return exec(dir, [`npm install`].concat(nameArgs).join(' '));
return exec(dir, [installCommand].concat(nameArgs).join(' '));
}

/**
* Execute an "npm run" command
* Execute an "{packageManager} run" command
*
* @param {NpmOperationOptions} { dir }
* @param {PMOperationOptions} { dir, packageManager }
* @param {string} command
* @returns {Promise<string>}
*/
export function npmRun(
{ dir }: NpmOperationOptions,
export function packageRun(
{ dir, packageManager }: PMOperationOptions,
command: string,
): Promise<string> {
return exec(dir, `npm run ${command}`);
return exec(dir, `${packageManager} run ${command}`);
}
35 changes: 21 additions & 14 deletions src/renderer/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
findModulesInEditors,
getIsNpmInstalled,
installModules,
npmRun,
packageRun,
PMOperationOptions,
} from './npm';
import { AppState } from './state';

Expand Down Expand Up @@ -58,11 +59,12 @@ export class Runner {

const values = await getEditorValues(options);
const dir = await this.saveToTemp(options);
const packageManager = this.appState.packageManager;

if (!dir) return false;

try {
await this.installModulesForEditor(values, dir);
await this.installModulesForEditor(values, { dir, packageManager });
} catch (error) {
console.error('Runner: Could not install modules', error);
fileManager.cleanup(dir);
Expand Down Expand Up @@ -139,13 +141,15 @@ export class Runner {
);
if (!dir) return false;

const packageManager = this.appState.packageManager;

// Files are now saved to temp, let's install Forge and dependencies
if (!(await this.npmInstall(dir))) return false;
if (!(await this.packageInstall({ dir, packageManager }))) return false;

// Cool, let's run "package"
try {
console.log(`Now creating ${strings[1].toLowerCase()}...`);
pushOutput(await npmRun({ dir }, operation));
pushOutput(await packageRun({ dir, packageManager }, operation));
pushOutput(`✅ ${strings[1]} successfully created.`, { isNotPre: true });
} catch (error) {
pushError(`Creating ${strings[1].toLowerCase()} failed.`, error);
Expand All @@ -167,9 +171,9 @@ export class Runner {
*/
public async installModulesForEditor(
values: EditorValues,
dir: string,
pmOptions: PMOperationOptions,
): Promise<void> {
const modules = await findModulesInEditors(values);
const modules = findModulesInEditors(values);
const { pushOutput } = this.appState;

if (modules && modules.length > 0) {
Expand All @@ -186,10 +190,13 @@ export class Runner {
return;
}

pushOutput(`Installing npm modules: ${modules.join(', ')}...`, {
isNotPre: true,
});
pushOutput(await installModules({ dir }, ...modules));
pushOutput(
`Installing node modules using ${
pmOptions.packageManager
}: ${modules.join(', ')}...`,
{ isNotPre: true },
);
pushOutput(await installModules(pmOptions, ...modules));
}
}

Expand Down Expand Up @@ -274,16 +281,16 @@ export class Runner {

/**
* Installs modules in a given directory (we're basically
* just running "npm install")
* just running "{packageManager} install")
*
* @param {string} dir
* @param {PMOperationOptions} options
* @returns
* @memberof Runner
*/
public async npmInstall(dir: string): Promise<boolean> {
public async packageInstall(options: PMOperationOptions): Promise<boolean> {
try {
this.appState.pushOutput(`Now running "npm install..."`);
this.appState.pushOutput(await installModules({ dir }));
this.appState.pushOutput(await installModules(options));
return true;
} catch (error) {
this.appState.pushError('Failed to run "npm install".', error);
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { activateTheme } from './themes';

import { waitForEditorsToMount } from '../utils/editor-mounted';
import { sortedElectronMap } from '../utils/sorted-electron-map';
import { IPackageManager } from './npm';
import {
addLocalVersion,
ElectronReleaseChannel,
Expand Down Expand Up @@ -124,6 +125,8 @@ export class AppState {
(this.retrieve('executionFlags') as Array<string>) === null
? []
: (this.retrieve('executionFlags') as Array<string>);
@observable public packageManager: IPackageManager =
(localStorage.getItem('packageManager') as IPackageManager) || 'npm';

// -- Various session-only state ------------------
@observable public gistId: string | undefined;
Expand Down Expand Up @@ -232,6 +235,7 @@ export class AppState {
autorun(() => this.save('version', this.version));
autorun(() => this.save('channelsToShow', this.channelsToShow));
autorun(() => this.save('statesToShow', this.statesToShow));
autorun(() => this.save('packageManager', this.packageManager ?? 'npm'));

autorun(() => {
if (typeof this.isUnsaved === 'undefined') return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,56 @@ exports[`ExecutionSettings component renders 1`] = `
/>
</Blueprint3.FormGroup>
</Blueprint3.Callout>
<br />
<Blueprint3.Callout>
<Blueprint3.FormGroup>
<span
style={
Object {
"marginRight": 4,
}
}
>
Electron Fiddle will install packages on runtime if they are imported within your fiddle with
<code>
require
</code>
. It uses
<a
href="https://www.npmjs.com/"
rel="noreferrer"
target="_blank"
>
npm
</a>
as its package manager by default, but
<a
href="https://classic.yarnpkg.com/lang/en/"
rel="noreferrer"
target="_blank"
>
Yarn
</a>
is also available.
</span>
<Blueprint3.RadioGroup
inline={true}
onChange={[Function]}
>
<Blueprint3.Radio
label="npm"
value="npm"
/>
<Blueprint3.Radio
label="yarn"
value="yarn"
/>
</Blueprint3.RadioGroup>
</Blueprint3.FormGroup>
</Blueprint3.Callout>
</div>
`;
65 changes: 49 additions & 16 deletions tests/renderer/npm-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
findModulesInEditors,
getIsNpmInstalled,
installModules,
npmRun,
packageRun,
} from '../../src/renderer/npm';
import { exec } from '../../src/utils/exec';
import { overridePlatform, resetPlatform } from '../utils';
Expand Down Expand Up @@ -102,30 +102,63 @@ describe('npm', () => {
});

describe('installModules()', () => {
it('attempts to install a single module', async () => {
installModules({ dir: '/my/directory' }, 'say', 'thing');
describe('npm', () => {
it('attempts to install a single module', async () => {
installModules(
{ dir: '/my/directory', packageManager: 'npm' },
'say',
'thing',
);

expect(exec).toHaveBeenCalledWith(
'/my/directory',
'npm install -S say thing',
);
});

expect(exec).toHaveBeenCalledWith(
'/my/directory',
'npm install -S say thing',
);
it('attempts to installs all modules', async () => {
installModules({ dir: '/my/directory', packageManager: 'npm' });

expect(exec).toHaveBeenCalledWith(
'/my/directory',
'npm install --dev --prod',
);
});
});

it('attempts to installs all modules', async () => {
installModules({ dir: '/my/directory' });
describe('yarn', () => {
it('attempts to install a single module', async () => {
installModules(
{ dir: '/my/directory', packageManager: 'yarn' },
'say',
'thing',
);

expect(exec).toHaveBeenCalledWith(
'/my/directory',
'yarn add say thing',
);
});

expect(exec).toHaveBeenCalledWith(
'/my/directory',
'npm install --dev --prod',
);
it('attempts to installs all modules', async () => {
installModules({ dir: '/my/directory', packageManager: 'yarn' });

expect(exec).toHaveBeenCalledWith('/my/directory', 'yarn add');
});
});
});

describe('npmRun()', () => {
it('attempts to run a command', async () => {
npmRun({ dir: '/my/directory' }, 'package');
describe('packageRun()', () => {
it('attempts to run a command via npm', async () => {
packageRun({ dir: '/my/directory', packageManager: 'npm' }, 'package');

expect(exec).toHaveBeenCalledWith('/my/directory', 'npm run package');
});

it('attempts to run a command via yarn', async () => {
packageRun({ dir: '/my/directory', packageManager: 'yarn' }, 'package');

expect(exec).toHaveBeenCalledWith('/my/directory', 'yarn run package');
});
});
});
Loading

0 comments on commit f9fe9db

Please sign in to comment.