feat: add ability to select package between npm and yarn (#447)
This commit is contained in:
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user