feat: add ability to select package between npm and yarn (#447)

This commit is contained in:
Vlad Hashimoto
2020-08-06 08:45:34 +03:00
committed by GitHub
parent 7bab157863
commit f9fe9dba84
7 changed files with 201 additions and 49 deletions
+43 -1
View File
@@ -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 {
@@ -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;
};
}
+21 -10
View File
@@ -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;
@@ -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> = [];
return exec(dir, [`npm install`].concat(nameArgs).join(' '));
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, [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}`);
}
+21 -14
View File
@@ -11,7 +11,8 @@ import {
findModulesInEditors,
getIsNpmInstalled,
installModules,
npmRun,
packageRun,
PMOperationOptions,
} from './npm';
import { AppState } from './state';
@@ -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);
@@ -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);
@@ -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) {
@@ -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));
}
}
@@ -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);
+4
View File
@@ -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,
@@ -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;
@@ -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;
@@ -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>
`;
+49 -16
View File
@@ -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';
@@ -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',
'npm install --dev --prod',
);
expect(exec).toHaveBeenCalledWith(
'/my/directory',
'yarn add say thing',
);
});
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');
});
});
});
+12 -8
View File
@@ -8,7 +8,7 @@ import {
findModulesInEditors,
getIsNpmInstalled,
installModules,
npmRun,
packageRun,
} from '../../src/renderer/npm';
import { ForgeCommands, Runner } from '../../src/renderer/runner';
import { AppState } from '../../src/renderer/state';
@@ -201,7 +201,9 @@ describe('Runner component', () => {
describe('installModules()', () => {
it('installs modules', async () => {
expect(await instance.npmInstall('')).toBe(true);
expect(
await instance.packageInstall({ dir: '', packageManager: 'npm' }),
).toBe(true);
expect(installModules).toHaveBeenCalled();
});
@@ -210,7 +212,9 @@ describe('Runner component', () => {
throw new Error('bwap bwap');
});
expect(await instance.npmInstall('')).toBe(false);
expect(
await instance.packageInstall({ dir: '', packageManager: 'npm' }),
).toBe(false);
expect(installModules).toHaveBeenCalled();
});
});
@@ -245,7 +249,7 @@ describe('Runner component', () => {
);
});
it('handles an error in npmInstall()', async () => {
it('handles an error in packageInstall()', async () => {
(installModules as jest.Mock).mockImplementationOnce(() => {
throw new Error('bwap bwap');
});
@@ -255,8 +259,8 @@ describe('Runner component', () => {
);
});
it('handles an error in npmRun()', async () => {
(npmRun as jest.Mock).mockImplementationOnce(() => {
it('handles an error in packageRun()', async () => {
(packageRun as jest.Mock).mockImplementationOnce(() => {
throw new Error('bwap bwap');
});
@@ -287,7 +291,7 @@ describe('Runner component', () => {
preload: '',
css: '',
},
'/fake/path',
{ dir: '/fake/path', packageManager: 'npm' },
);
expect(installModules).toHaveBeenCalledTimes(0);
@@ -305,7 +309,7 @@ describe('Runner component', () => {
preload: '',
css: '',
},
'/fake/path',
{ dir: '/fake/path', packageManager: 'npm' },
);
expect(installModules).toHaveBeenCalledTimes(1);