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

frint-cli: Introduce "new" command #325

Merged
merged 17 commits into from Nov 6, 2017
@@ -17,21 +17,16 @@ Install [`frint-cli`](https://frint.js.org/docs/packages/frint-cli/):
$ npm install -g frint-cli
```

Create a new empty directory:

```
$ mkdir my-directory && cd my-directory
```

Initialize an example app:

```
$ frint init --example kitchensink
$ frint new my-directory --example kitchensink
```

Now you can install all the dependencies, and start the application:

```
$ cd my-directory
$ npm install
$ npm start
```
@@ -41,18 +41,44 @@ Will list all the commands available to you.

### `init`

Scaffolds a new FrintJS application in an empty directory:
Scaffolds a new FrintJS application in the current directory:

```
$ mkdir my-directory && cd my-directory
$ frint init
```

To scaffold a certain example, as available in the repository [here](https://github.com/Travix-International/frint/tree/master/examples):

```
$ frint init --example exampleName
$ frint init --example kitchensink
```

### `new`

Scaffolds a new FrintJS application in the current directory:

```
$ mkdir my-directory && cd my-directory
$ frint new
```

Scaffolds a new FrintJS application in the specified directory:

```
$ frint new my-directory
```

To scaffold a certain example, as available in the repository [here](https://github.com/Travix-International/frint/tree/master/examples):

```
$ frint new my-directory --example kitchensink
```

It is also possible to scaffold an example from an arbitrary repository:

```
$ frint new my-directory --example frintjs/frint-vue/tree/master/examples/basic
```

### `version`
@@ -8,6 +8,7 @@ const app = new App();

app.registerApp(require('../commands/version'));
app.registerApp(require('../commands/init'));
app.registerApp(require('../commands/new'));
app.registerApp(require('../commands/help'));

const command = app.get('command');
@@ -1,7 +1,7 @@
/* eslint-disable global-require, import/no-dynamic-require */
const createApp = require('frint').createApp;

const descriptionText = `
const DESCRIPTION_TEXT = `
Usage:
$ frint help <commandName>
@@ -21,7 +21,7 @@ module.exports = createApp({
},
{
name: 'description',
useValue: descriptionText,
useValue: DESCRIPTION_TEXT,
},
{
name: 'execute',
@@ -4,7 +4,7 @@ const tar = require('tar');

const createApp = require('frint').createApp;

const descriptionText = `
const DESCRIPTION_TEXT = `
Usage:
$ frint init
@@ -30,7 +30,7 @@ module.exports = createApp({
},
{
name: 'description',
useValue: descriptionText,
useValue: DESCRIPTION_TEXT,
},
{
name: 'execute',
@@ -0,0 +1,145 @@
/* eslint-disable no-use-before-define */

const mkdirp = require('mkdirp');
const request = require('request');
const tar = require('tar');

const createApp = require('frint').createApp;

const DEFAULT_ORGANIZATION = 'Travix-International';
const DEFAULT_REPOSITORY = 'frint';
const DEFAULT_BRANCH = 'master';
const DEFAULT_EXAMPLES_DIR = 'examples';
const DEFAULT_EXAMPLE = 'counter';
const DESCRIPTION_TEXT = `
Usage:
$ frint new
$ frint new <directory>
$ frint new <directory> --example <example>
Example:
$ frint new myapp --example kitchensink
$ frint new myapp --example frint-vue/tree/master/examples/basic
You can find a list of all available official examples here:
https://github.com/Travix-International/frint/tree/master/examples
`.trim();
const INVALID_EXAMPLE_ARG_TEXT = `
Invalid <example> value. Must be in one of the following formats:

This comment has been minimized.

Copy link
@fahad19

fahad19 Oct 3, 2017

Member

really loving the detailed error output 💖

* <name>
* <organization>/<repository>/tree/<branch>/**
`.trim();
const COMPLETION_TEXT = `
Done!
Please run these two commands to start your application:

This comment has been minimized.

Copy link
@fahad19

fahad19 Oct 6, 2017

Member

depending on whether <directory> argument was given or not, we can also add an addition cd command.

Two scenarios:

1: with directory:

$ frint new myapp

Done!

Please run:

$ cd myapp
$ npm install
$ npm start

2: without directory

$ frint new

Done!

$ npm install
$ npm start

Sorry for nitpicking. Just trying to come up with as many use cases as possible :)

This comment has been minimized.

Copy link
@discosultan

discosultan Oct 6, 2017

Author Contributor

Sorry for nitpicking. Just trying to come up with as many use cases as possible :)

No worries, good feedback is good!

I was thinking about it actually or alternatively adjusting the wording to specify to run the following commands in the app folder.

$ npm install
$ npm start
`.trim();

module.exports = createApp({
name: 'new',
providers: [
{
name: 'summary',
useValue: 'Scaffolds a new Frint app in specified directory',
},
{
name: 'description',
useValue: DESCRIPTION_TEXT,
},
{
name: 'execute',
useFactory: function useFactory(deps) {
return function execute() {
deps.console.log('Initializing...');
Promise.resolve(deps)
.then(mapDepsToContext)
.then(createOutputDirectory)
.then(streamExampleToOutputDirectory)
.then(() => deps.console.log(COMPLETION_TEXT))
.catch(deps.console.error);
};
},
deps: [
'console',
'params',
'pwd',
],
}
],
});

function mapDepsToContext(deps) {
return new Promise((resolve, reject) => {
// The <example> param has two shapes:
// * <name> - example name from the official Frint GitHub repository
// * <organization>/<repo>/tree/<branch>/** - full GitHub path to an arbitrary example
const example = deps.params.example || DEFAULT_EXAMPLE;
if (!example.match(/(^(\w|-)+$)|^\/?(\w|-)+(\/(\w|-)+){3,}\/?$/)) {
reject(INVALID_EXAMPLE_ARG_TEXT);
}

const isCustomExample = example.indexOf('/') >= 0;
resolve(isCustomExample ? getContextForCustomRepo() : getContextForDefaultRepo());

function getContextForDefaultRepo() {

This comment has been minimized.

Copy link
@fahad19

fahad19 Oct 6, 2017

Member

if this function receives example as an argument, then it doesn't have to be defined inside a function. they can all be in the root:

function getContextForDefaultRepo(example) {
  return '';
}

function getOutputDirectory(yargs) {
  return '';
}

function getContextForCustomRepo(example) {
  return '';
}

function mapDepsToContext(deps) {
  return '';
}

This comment has been minimized.

Copy link
@discosultan

discosultan Oct 6, 2017

Author Contributor

Sure, I used nested functions more as a way of grouping rather than for making use of closures. It's a matter of preference, so it should be ideally consistent across the board.

@fahad19 , so you suggest to keep functions flattened within the file root and keep them as pure as possible?

This comment has been minimized.

Copy link
@fahad19

fahad19 Oct 6, 2017

Member

yes please. purer the better :)

return {
organization: DEFAULT_ORGANIZATION,
repository: DEFAULT_REPOSITORY,
branch: DEFAULT_BRANCH,
examplePath: `${DEFAULT_EXAMPLES_DIR}/${example}`,
outputDirectory: getOutputDirectory(),
};
}

function getContextForCustomRepo() {
// Split by '/' and filter out empty results.
// <example> arg might start or end with a separator.
const exampleParts = example.split('/').filter(str => str !== '');
return {
organization: exampleParts[0],
repository: exampleParts[1],
branch: exampleParts[3],
examplePath: exampleParts.slice(4).join('/'),
outputDirectory: getOutputDirectory(),
};
}

function getOutputDirectory() {
// If <directory> is specified, it is taken as the 1st value from params _ array.
// Note that this array does not include the <example> flag.
return deps.params._.length >= 1 ? deps.params._[0] : deps.pwd;
}
});
}

function createOutputDirectory(ctx) {
return new Promise((resolve, reject) => {
mkdirp(ctx.outputDirectory, function mkdirpCallback(error) {
if (error) {
reject(error);
return;
}
resolve(ctx);
});
});
}

function streamExampleToOutputDirectory(ctx) {
return new Promise((resolve, reject) => {
request(`https://codeload.github.com/${ctx.organization}/${ctx.repository}/tar.gz/${ctx.branch}`)
.on('error', reject)
.pipe(tar.x({
filter: p => p.indexOf(`${ctx.repository}-${ctx.branch}/${ctx.examplePath}/`) === 0,
strip: 3,
C: ctx.outputDirectory,
}))
.on('error', reject)
.on('finish', resolve);
});
}
@@ -0,0 +1,18 @@
/* eslint-disable import/no-extraneous-dependencies, func-names */
/* global describe, it */
const expect = require('chai').expect;
const App = require('frint').App;

const createRootApp = require('../root/index.mock');
const CommandApp = require('./new');

describe('frint-cli › commands › new', function () {
it('is a Frint App', function () {
const RootApp = createRootApp();
const rootApp = new RootApp();
rootApp.registerApp(CommandApp);
const commandApp = rootApp.getAppInstance('new');

expect(commandApp).to.be.an.instanceOf(App);
});
});
@@ -272,10 +272,9 @@ Install `frint-cli` package, then init the `router` example and run it.
```
$ npm install -g frint-cli
$ mkdir my-frint-app
$ cd my-frint-app
$ frint new my-directory --example router
$ frint init --example router
$ cd my-directory
$ npm install
$ npm start
```
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.