Skip to content

Commit

Permalink
feat(global-cli): initial draft of global cli module
Browse files Browse the repository at this point in the history
  • Loading branch information
ZauberNerd authored and dmbch committed Oct 13, 2017
1 parent 7397d42 commit f7a245f
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 163 deletions.
216 changes: 179 additions & 37 deletions packages/cli/hops.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,183 @@
#!/usr/bin/env node
'use strict';

var program = require('commander');
var rimraf = require('rimraf');

var hopsConfig = require('hops-config');
var commands = require('./');

function cleanup () {
var dirs = [hopsConfig.buildDir, hopsConfig.cacheDir];
return Promise.all(dirs.map(function (dir) {
return new Promise(function (resolve) {
rimraf(dir, resolve);
});
}));
}

program
.version(require('./package.json').version)
.description('Commands: start, serve, develop, build')
.option('-s, --static', 'Statically build locations')
.arguments('<command>')
.action(function run (command) {
cleanup().then(function () {
switch (command) {
case 'start':
return run(process.env.NODE_ENV === 'production' ? 'serve' : 'develop');
case 'serve':
return commands.runServe(program);
case 'develop':
return commands.runDevelop(program);
case 'build':
return commands.runBuild(program);
default:
console.error('invalid command: ' + command);
process.exit(1);
var fs = require('fs');
var path = require('path');
var execSync = require('child_process').execSync;
var resolveCwd = require('resolve-cwd');
var validatePackageName = require('validate-npm-package-name');

var packageManifest = require('./package.json');

var getLocalCliPath = function () {
try {
return resolveCwd('hops-local-cli');
} catch (error) {
return null;
}
};

var PACKAGES_TO_INSTALL = [
'hops-local-cli'
];

function globalCLI (argv) {
return require('yargs')
.version(packageManifest.version)
.usage('Usage: $0 <command> [options]')
.command('init <project-name>', 'Generates a new project with the specified name')
.option('template', {
type: 'string',
describe: 'Use this with the npm package name of a template to ' +
'initialize with a different template',
default: 'hops-template-default'
})
.option('verbose', {
type: 'boolean',
describe: 'Increase verbosity of command',
default: false
})
.option('npm', {
type: 'boolean',
describe: 'Force usage of `npm` instead of yarn',
default: false
})
.example(
'$0 init my-project',
'Creates the folder my-project inside the current directory and ' +
'initializes a sample hops project inside it.'
)
.example(
'$0 init --template hops-template-malt my-project',
'Creates the folder my-project inside the current directory and ' +
'initializes an example project using malt inside it.'
)
.help('h')
.alias('h', 'help')
.demandCommand()
.wrap(72)
.parse(argv);
}

function validateName (name) {
var validationResult = validatePackageName(name);
if (!validationResult.validForNewPackages) {
console.error(
'Cannot create a project with the name:',
name,
'because of the following npm restrictions:'
);
if (validationResult.errors) {
validationResult.errors.forEach(function (msg) { console.error(msg); });
}
if (validationResult.warnings) {
validationResult.warnings.forEach(function (msg) { console.warn(msg); });
}
});
})
.parse(process.argv);
process.exit(1);
}
}

function createDirectory (root) {
if (fs.existsSync(root)) {
console.error(
'A directory with the name:',
name,
'already exists in:',
process.cwd(),
'\nPlease remove this directory or choose a different project-name.'
);
process.exit(1);
}

fs.mkdirSync(root);
}

function writePackageManifest (root) {
fs.writeFileSync(
path.join(root, 'package.json'),
JSON.stringify({
name: name,
version: '1.0.0',
private: true
}, null, 2)
);
}

function isYarnAvailable () {
try {
execSync('yarn --version', { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}

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

try {
execSync(command.join(' '), { stdio: 'inherit' });
} catch (error) {
console.error(error.message);
if (options.verbose) {
console.error(error);
console.error('Command: "', command.join(' '), 'has failed.');
}
process.exit(1);
}
}

var localCliPath = getLocalCliPath();
var argv = process.argv.slice(2);

var isInsideHopsProject = false;
try {
var manifest = require(path.resolve(process.cwd(), 'package.json'));
if (manifest.dependencies) {
isInsideHopsProject = Boolean(manifest.dependencies['hops-local-cli']);
}
} catch (error) {
isInsideHopsProject = false;
}

if (isInsideHopsProject) {
if (localCliPath) {
require(localCliPath).run(argv);
} else {
console.error(
'It appears that we are inside a hops project but the dependencies have',
'not been installed.\n',
'Please execute "yarn install" or "npm install" and retry.'
);
process.exit(1);
}
} else {
var options = globalCLI(argv);
var name = options.projectName;
var root = process.cwd();

validateName(name);
createDirectory(path.resolve(root, name));
writePackageManifest(path.resolve(root, name));
process.chdir(path.resolve(root, name));
installPackages(PACKAGES_TO_INSTALL, options);
require(getLocalCliPath()).init(root, name, options);
}
29 changes: 0 additions & 29 deletions packages/cli/index.js

This file was deleted.

15 changes: 5 additions & 10 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
"cli"
],
"license": "MIT",
"main": "index.js",
"bin": {
"hops": "hops.js"
},
"preferGlobal": true,
"files": [
"hops.js",
"index.js",
"commands",
"lib"
"hops.js"
],
"scripts": {
"test": "echo \"Error: run tests in xing/hops repo\" && exit 1"
Expand All @@ -25,10 +22,8 @@
"url": "https://github.com/xing/hops.git"
},
"dependencies": {
"commander": "^2.11.0",
"hops-build": "6.2.8",
"hops-express": "6.2.8",
"hops-config": "6.2.8",
"rimraf": "^2.6.2"
"resolve-cwd": "^2.0.0",
"validate-npm-package-name": "^3.0.0",
"yargs": "^9.0.1"
}
}
90 changes: 3 additions & 87 deletions packages/cli/readme.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,11 @@
# 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.
# Hops CLI

``` 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
```
### Target Audience

## 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');
}
}

```
### Example

Don't forget to [configure the scripts section](#configure-packagejson) of your package.json. And you're done!

0 comments on commit f7a245f

Please sign in to comment.