Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: yarn install (through a flag) #225

Merged
merged 8 commits into from
Jul 16, 2017
33 changes: 14 additions & 19 deletions src/commands/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import promisify from 'es6-promisify';
import spawn from 'cross-spawn-promise';
import path from 'path';
import which from 'which';
import { install, initialize, pkgScripts } from './../lib/setup';

const TEMPLATES = {
full: 'examples/full',
Expand Down Expand Up @@ -44,6 +45,11 @@ export default asyncCommand({
],
default: 'full'
},
yarn: {
description: "Use 'yarn' instead of 'npm'",
type: 'boolean',
default: false
},
less: {
description: 'Pre-install LESS support',
type: 'boolean',
Expand Down Expand Up @@ -126,18 +132,11 @@ export default asyncCommand({

spinner.text = 'Initializing project';

await npm(target, ['init', '-y']);
await initialize(argv.yarn, target);

let pkg = JSON.parse(await fs.readFile(path.resolve(target, 'package.json')));

pkg.scripts = {
...(pkg.scripts || {}),
start: 'if-env NODE_ENV=production && npm run -s serve || npm run -s dev',
build: 'preact build',
serve: 'preact build && preact serve',
dev: 'preact watch',
test: 'eslint src && preact test'
};
pkg.scripts = await pkgScripts(argv.yarn, pkg);

try {
await fs.stat(path.resolve(target, 'src'));
Expand All @@ -155,8 +154,7 @@ export default asyncCommand({
if (argv.install) {
spinner.text = 'Installing dev dependencies';

await npm(target, [
'install', '--save-dev',
await install(argv.yarn, target, [
'preact-cli',
'if-env',
'eslint',
Expand All @@ -179,12 +177,11 @@ export default asyncCommand({
'stylus',
'stylus-loader'
] : [])
].filter(Boolean));
], 'dev');

spinner.text = 'Installing dependencies';

await npm(target, [
'install', '--save',
await install(argv.yarn, target, [
'preact',
'preact-compat',
'preact-router'
Expand All @@ -202,21 +199,19 @@ export default asyncCommand({
\u001b[32mcd ${path.relative(process.cwd(), target)}\u001b[39m

To start a development live-reload server:
\u001b[32mnpm start\u001b[39m
\u001b[32m${argv.yarn === true ? 'yarnpkg start' : 'npm start'}\u001b[39m

To create a production build (in ./build):
\u001b[32mnpm run build\u001b[39m
\u001b[32m${argv.yarn === true ? 'yarnpkg build' : 'npm run build'}\u001b[39m

To start a production HTTP/2 server:
\u001b[32mnpm run serve\u001b[39m
\u001b[32m${argv.yarn === true ? 'yarnpkg serve' : 'npm run serve'}\u001b[39m
`) + '\n';
}
});

const trimLeft = (string) => string.trim().replace(/^\t+/gm, '');

const npm = (cwd, args) => spawn('npm', args, { cwd, stdio: 'ignore' });

// Initializes the folder using `git init` and a proper `.gitignore` file
// if `git` is present in the $PATH.
async function initializeVersionControl(target) {
Expand Down
55 changes: 55 additions & 0 deletions src/lib/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import spawn from 'cross-spawn-promise';
import { commandExists } from './shell';

const initialize = async (yarn, cwd) => {
let isYarnAvailable = await commandExists('yarn');

if (isYarnAvailable && yarn) {
return await spawn('yarn', ['init', '-y'], { cwd, stdio: 'ignore' });
}

await spawn('npm', ['init', '-y'], { cwd, stdio: 'ignore' });
};

const install = async (yarn, cwd, packages, env) => {
let isDev = env === 'dev' ? true : false;
let isYarnAvailable = await commandExists('yarn');
let toInstall = packages.filter(Boolean);

if (isYarnAvailable && yarn) {
let args = ['add'];
if (isDev) {
args.push('-D');
}

return await spawn('yarn', [...args, ...toInstall], { cwd, stdio: 'ignore' });
}

await spawn('npm', ['install', isDev ? '--save-dev' : '--save', ...toInstall], { cwd, stdio: 'ignore' });
};

const pkgScripts = async (yarn, pkg) => {
let isYarnAvailable = await commandExists('yarn');

if (isYarnAvailable && yarn) {
return {
...(pkg.scripts || {}),
start: 'if-env NODE_ENV=production && yarnpkg -s serve || yarnpkg -s dev',
build: 'preact build',
serve: 'preact build && preact serve',
dev: 'preact watch',
test: 'eslint src && preact test'
};
}

return {
...(pkg.scripts || {}),
start: 'if-env NODE_ENV=production && npm run -s serve || npm run -s dev',
build: 'preact build',
serve: 'preact build && preact serve',
dev: 'preact watch',
test: 'eslint src && preact test'
};
};

export { install, initialize, pkgScripts };
13 changes: 13 additions & 0 deletions src/lib/shell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import promisify from 'es6-promisify';
import which from 'which';

const commandExists = async cmd => {
try {
await promisify(which)(cmd);
return true;
} catch (e){
return false;
}
};

export { commandExists };
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import loaderUtils from 'loader-utils';
import fs from 'fs';
import spawn from 'cross-spawn-promise';
import path from 'path';
import { install } from './../setup';

/**
* This is a pass-through loader that runs `npm install --save` for the specified dependencies when invoked.
* Chain it before loaders to conditionally install them on first use.
* Quickly checks if any modules are alread installed and skips them.
* @param {object} options
* @param {boolean|string} [options.save=false] If `true`, runs `npm i --save`. If `"dev"`, runs `npm i --save-dev`.
* @param {Array<String>|String} modules A list of modules to install.
* @param {boolean|string} [options.save=false] If `true`, runs `npm i --save`. If `"dev"`, runs `npm i --save-dev`.
* @param {Array<String>|String} modules A list of modules to install.
*
* @example // Install and use less-loader and less when encountering `.less` files:
*
Expand All @@ -30,19 +31,15 @@ const CACHE = {};

function isInstalled(dep) {
return CACHE[dep] || (CACHE[dep] = new Promise( resolve => {
fs.stat(resolve('node_modules', dep), err => {
fs.stat(path.resolve('node_modules', dep), err => {
resolve(!err);
});
}));
}

function installDeps(deps, save) {
process.stdout.write(`\nInstalling ${deps.join(' ')}..`);
return spawn('npm', [
'install', save && (save==='dev' ? '--save-dev' : '--save'),
'--prefix', process.cwd(),
...deps
].filter(Boolean)).then( () => {
return install(false, process.cwd(), deps, save).then( () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to allow yarn here if it's installed? We could check for a yarn.lock as an indication of whether the current repo is set up with yarn.

process.stdout.write(` ..${deps.length} installed.\n`);
});
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/webpack/webpack-base-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default (env) => {
test: /\.less$/,
use: [
{
loader: resolve(__dirname, './npm-install-loader'),
loader: resolve(__dirname, './dependency-install-loader'),
options: {
modules: ['less', 'less-loader'],
save: true
Expand All @@ -126,7 +126,7 @@ export default (env) => {
test: /\.s[ac]ss$/,
use: [
{
loader: resolve(__dirname, './npm-install-loader'),
loader: resolve(__dirname, './dependency-install-loader'),
options: {
modules: ['node-sass', 'sass-loader'],
save: true
Expand All @@ -143,7 +143,7 @@ export default (env) => {
test: /\.styl$/,
use: [
{
loader: resolve(__dirname, './npm-install-loader'),
loader: resolve(__dirname, './dependency-install-loader'),
options: {
modules: ['stylus', 'stylus-loader'],
save: true
Expand Down