Skip to content

Commit

Permalink
feat(local-cli): implement build, develop and serve commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ZauberNerd authored and dmbch committed Oct 13, 2017
1 parent 520c1d7 commit 6378026
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 0 deletions.
27 changes: 27 additions & 0 deletions packages/local-cli/commands/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

var hopsBuild = require('hops-build');

module.exports = function buildCommand (callback) {
return {
command: 'build',
describe: 'Builds the browser and server JS bundles',
builder: {
static: {
alias: 's',
default: false,
describe: 'Statically build locations',
type: 'boolean'
},
clean: {
alias: 'c',
default: true,
describe: 'Clean up artifacts in build / cache directories before building',
type: 'boolean'
}
},
handler: function buildHandler (argv) {
hopsBuild.runBuild(argv, callback);
}
};
};
22 changes: 22 additions & 0 deletions packages/local-cli/commands/develop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

var hopsBuild = require('hops-build');

module.exports = function developCommand (callback) {
return {
command: 'develop',
describe: 'Starts a webpack-dev-server to enable local development with ' +
'hot code reloading',
builder: {
clean: {
alias: 'c',
default: true,
describe: 'Clean up artifacts in build / cache directories before building',
type: 'boolean'
}
},
handler: function developHandler (argv) {
hopsBuild.runServer(argv, callback);
}
};
};
15 changes: 15 additions & 0 deletions packages/local-cli/commands/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

var hopsExpress = require('hops-express');

module.exports = function serveCommand (callback) {
return {
command: 'serve',
describe: 'Starts a production-ready Node.js server to serve your ' +
'application',
builder: {},
handler: function serveHandler () {
hopsExpress.startServer(callback);
}
};
};
10 changes: 10 additions & 0 deletions packages/local-cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env node
'use strict';

var init = require('./init');
var run = require('./run');

module.exports = {
init: init,
run: run
};
32 changes: 32 additions & 0 deletions packages/local-cli/init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

var pm = require('./lib/package-manager');

function init (appRoot, appName, options) {
var useYarn = !!(pm.isGlobalCliUsingYarn() || pm.getYarnVersionIfAvailable());

var prodDependencies = [
'hops-express',
'hops-react',
'hops-redux',
'react',
'react-dom',
'react-helmet',
'react-redux',
'react-router',
'react-router-dom',
'redux',
'redux-thunk'
];
var devDependencies = [
'hops-build',
'jest',
'jest-preset-hops',
'react-test-renderer'
];
pm.installPackages(prodDependencies, { yarn: useYarn });
pm.installPackages(devDependencies, { yarn: useYarn, dev: true });

// fetch template from github/npm and copy files into app folder
}
module.exports = init;
99 changes: 99 additions & 0 deletions packages/local-cli/lib/package-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict';

var fs = require('fs');
var path = require('path');
var execSync = require('child_process').execSync;

function execIgnoreStdError (command, options) {
var commandToRun = process.platform.indexOf('win') === 0
? command + ' 2> NUL'
: command + ' 2> /dev/null';

if (options.verbose) {
console.log('Executing:', commandToRun);
}
var result = (options.execSync || execSync)(commandToRun);
return result.toString('utf-8').trim();
}

function getYarnVersionIfAvailable (options) {
try {
return execIgnoreStdError('yarn --version', options);
} catch (error) {
return null;
}
}
module.exports.getYarnVersionIfAvailable = getYarnVersionIfAvailable;

function getNpmVersionIfAvailable (options) {
try {
return execIgnoreStdError('npm --version', options);
} catch (error) {
return null;
}
}
module.exports.getNpmVersionIfAvailable = getNpmVersionIfAvailable;

function isGlobalCliUsingYarn (projectPath, options) {
return fs.existsSync(path.join(projectPath, 'yarn.lock'));
}
module.exports.isGlobalCliUsingYarn = isGlobalCliUsingYarn;

function installPackages (packages, options) {
var command = null;
if (options.installCommand) {
command = options.installCommand.split(' ');
} else {
if (options.yarn) {
command = [
'yarn',
'add',
'--exact'
];
if (options.dev) {
command.push('--dev');
}
} else {
command = [
'npm',
'install',
'--save-exact'
];
if (options.dev) {
command.push('--save-dev');
} else {
command.push('--save');
}
}
if (options.verbose) {
command.add('--verbose');
}
}
Array.prototype.push.apply(command, packages);

try {
if (options.verbose) {
console.log('Executing:', command.join(' '));
}
(options.execSync || execSync)(command.join(' '), { stdio: 'inherit' });
return true;
} catch (error) {
console.error(error.message);
if (options.verbose) {
console.error(error);
console.error('Command: "', command.join(' '), 'has failed.');
}
}
return false;
}
module.exports.installPackages = installPackages;

function isPackageInstalled (name) {
try {
require.resolve(name);
return true;
} catch (error) {
return false;
}
}
module.exports.isPackageInstalled = isPackageInstalled;
35 changes: 35 additions & 0 deletions packages/local-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "hops-local-cli",
"version": "6.2.8",
"description": "CLI tool to build, run and develop Hops based projects",
"keywords": [
"hops",
"cli"
],
"license": "MIT",
"bin": {
"hops": "run.js"
},
"main": "index.js",
"files": [
"index.js",
"init.js",
"run.js",
"commands",
"lib"
],
"scripts": {
"test": "echo \"Error: run tests in xing/hops repo\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/xing/hops.git"
},
"dependencies": {
"yargs": "^9.0.1"
},
"optionalDependencies": {
"hops-build": "6.2.8",
"hops-express": "6.2.8"
}
}
95 changes: 95 additions & 0 deletions packages/local-cli/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Hops CLI

hops-cli provides a small set of commands to manage your hops project. Installing hops-cli is the minimum requirement for being able to start working with Hops.

# Installation
## Minimum installation for any project
We assume that you already created a folder for your project and initialized it with `npm init -y`. Go to the root folder of your project and install hops-cli. This installs hops-config and hops-server as dependencies as well:
``` bash
npm install --save hops-cli
```

That's it - at least this is sufficient to get you started with a very basic setup.

## Additional setup for a React project
In case you are going to implement a project with React, you need to install further packages:
``` bash
npm install --save react react-dom react-helmet react-router react-router-dom hops-react
```

# Usage
## Available commands
The following commands are provided by hops-cli:

- `hops build` - initiates a project build
- `hops develop` - starts a Webpack development server
- `hops serve` - initiates a project build, stores the build artifacts in the file system and starts a production (Express) server
- `hops start` - if NODE_ENV is set to production, this runs `hops serve`. Otherwise, `hops develop` gets executed

Please note that hops-cli is not meant to be called directly, but rather to be added to your project's package.json file and then called indirectly by using npm or yarn (see below).

## Configure package.json
To actually use the hops-cli commands, you have to add them to the scripts section of your project's package.json. Hops is designed to leverage npm/yarn features, so it does not make much sense to call the hops executable directly.

``` JSON
"scripts": {
"build": "hops build",
"develop": "hops develop",
"serve": "hops serve",
"start": "hops start"
},
"config": {
"hops": {}
}
```

However, for simple projects, it should be sufficient to just add `"start": "hops start"` here.

To learn what configuration options are supported, please see the [`hops-config` docs](https://github.com/xing/hops/tree/master/packages/config#hops-config).

## Static mode
hops-cli can be used as a static site generator, too. To enable static mode, pass `--static` or `-s` to the above commands and configure a `locations` array to your package.json file.

``` JSON
"scripts": {
"start": "hops start --static"
},
"config": {
"hops": {
"locations": ["/"]
}
}
```

## Use via npm
After that, you can execute them via npm like so:

``` bash
npm run build
npm run develop
npm run serve
npm start
npm start --production
```

## Very basic example app
To try out the [minimum installation described above](#minimum-installation-for-any-project), create an index.js file in the root folder of your project and put the following code in there:

``` js
export default function (req, res, next) {
if (req) {
console.log('server');
if (req.path === '/') {
res.set('Content-Type', 'text/html');
res.send('<script src="/main.js"></script>');
} else {
next();
}
} else {
console.log('browser');
}
}

```

Don't forget to [configure the scripts section](#configure-packagejson) of your package.json. And you're done!
31 changes: 31 additions & 0 deletions packages/local-cli/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
'use strict';

var yargs = require('yargs');

var pm = require('./lib/package-manager');
var packageManifest = require('./package.json');

module.exports = function run (argv, callback) {
var args = yargs
.version(packageManifest.version)
.usage('Usage: $0 <command> [options]')
.help('help')
.alias('h', 'help')
.demandCommand();

if (pm.isPackageInstalled('hops-build')) {
args.command(require('./commands/build')(callback));
args.command(require('./commands/develop')(callback));
}

if (pm.isPackageInstalled('hops-express')) {
args.command(require('./commands/serve')(callback));
}

args.parse(argv);
};

if (require.main === module) {
module.exports(process.argv.slice(2));
}

0 comments on commit 6378026

Please sign in to comment.