Skip to content

Commit 6853958

Browse files
discosultanfahad19
authored andcommitted
frint-cli: Introduce "new" command (#325)
* Add new command * Fix typo * Include branch within repo * Update new command * Merge path param into example; updated docs * Improve wording * Improve wording * Update doc for Command not available. cmd * Improve wording * Modify after code review * Improve after code review * Improve after code review * Refactor after code review * Update default org for and commands * Rename another org ref to frintjs
1 parent f8cd1bd commit 6853958

File tree

7 files changed

+208
-14
lines changed

7 files changed

+208
-14
lines changed

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,16 @@ Install [`frint-cli`](https://frint.js.org/docs/packages/frint-cli/):
1717
$ npm install -g frint-cli
1818
```
1919

20-
Create a new empty directory:
21-
22-
```
23-
$ mkdir my-directory && cd my-directory
24-
```
25-
2620
Initialize an example app:
2721

2822
```
29-
$ frint init --example kitchensink
23+
$ frint new my-directory --example kitchensink
3024
```
3125

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

3428
```
29+
$ cd my-directory
3530
$ npm install
3631
$ npm start
3732
```

packages/frint-cli/README.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,44 @@ Will list all the commands available to you.
4141

4242
### `init`
4343

44-
Scaffolds a new FrintJS application in an empty directory:
44+
Scaffolds a new FrintJS application in the current directory:
4545

4646
```
4747
$ mkdir my-directory && cd my-directory
48-
4948
$ frint init
5049
```
5150

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

5453
```
55-
$ frint init --example exampleName
54+
$ frint init --example kitchensink
55+
```
56+
57+
### `new`
58+
59+
Scaffolds a new FrintJS application in the current directory:
60+
61+
```
62+
$ mkdir my-directory && cd my-directory
63+
$ frint new
64+
```
65+
66+
Scaffolds a new FrintJS application in the specified directory:
67+
68+
```
69+
$ frint new my-directory
70+
```
71+
72+
To scaffold a certain example, as available in the repository [here](https://github.com/frintjs/frint/tree/master/examples):
73+
74+
```
75+
$ frint new my-directory --example kitchensink
76+
```
77+
78+
It is also possible to scaffold an example from an arbitrary repository:
79+
80+
```
81+
$ frint new my-directory --example frintjs/frint-vue/tree/master/examples/basic
5682
```
5783

5884
### `version`

packages/frint-cli/bin/frint.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const app = new App();
1010

1111
app.registerApp(require('../commands/version'));
1212
app.registerApp(require('../commands/init'));
13+
app.registerApp(require('../commands/new'));
1314
app.registerApp(require('../commands/help'));
1415

1516
const command = app.get('command');

packages/frint-cli/commands/help.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable global-require, import/no-dynamic-require */
22
const createApp = require('frint').createApp;
33

4-
const descriptionText = `
4+
const DESCRIPTION_TEXT = `
55
Usage:
66
77
$ frint help <commandName>
@@ -21,7 +21,7 @@ module.exports = createApp({
2121
},
2222
{
2323
name: 'description',
24-
useValue: descriptionText,
24+
useValue: DESCRIPTION_TEXT,
2525
},
2626
{
2727
name: 'execute',

packages/frint-cli/commands/init.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const tar = require('tar');
44

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

7-
const descriptionText = `
7+
const DESCRIPTION_TEXT = `
88
Usage:
99
1010
$ frint init
@@ -30,7 +30,7 @@ module.exports = createApp({
3030
},
3131
{
3232
name: 'description',
33-
useValue: descriptionText,
33+
useValue: DESCRIPTION_TEXT,
3434
},
3535
{
3636
name: 'execute',

packages/frint-cli/commands/new.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* eslint-disable no-use-before-define */
2+
3+
const mkdirp = require('mkdirp');
4+
const request = require('request');
5+
const tar = require('tar');
6+
7+
const createApp = require('frint').createApp;
8+
9+
const DEFAULT_ORG = 'frintjs';
10+
const DEFAULT_REPO = 'frint';
11+
const DEFAULT_BRANCH = 'master';
12+
const DEFAULT_EXAMPLES_DIR = 'examples';
13+
const DEFAULT_EXAMPLE = 'counter';
14+
const DESCRIPTION_TEXT = `
15+
Usage:
16+
17+
$ frint new
18+
$ frint new <directory>
19+
$ frint new <directory> --example <example>
20+
21+
Example:
22+
23+
$ frint new myapp --example kitchensink
24+
$ frint new myapp --example frint-vue/tree/master/examples/basic
25+
26+
You can find a list of all available official examples here:
27+
https://github.com/frintjs/frint/tree/master/examples
28+
`.trim();
29+
const INVALID_EXAMPLE_ARG_TEXT = `
30+
Invalid <example> value. Must be in one of the following formats:
31+
32+
* <name>
33+
* <organization>/<repository>/tree/<branch>/**
34+
`.trim();
35+
const COMPLETION_TEXT = `
36+
Done!
37+
38+
Please run these two commands to start your application:
39+
{}
40+
$ npm install
41+
$ npm start
42+
`.trim();
43+
44+
module.exports = createApp({
45+
name: 'new',
46+
providers: [
47+
{
48+
name: 'summary',
49+
useValue: 'Scaffolds a new Frint app in specified directory',
50+
},
51+
{
52+
name: 'description',
53+
useValue: DESCRIPTION_TEXT,
54+
},
55+
{
56+
name: 'execute',
57+
useFactory: function useFactory(deps) {
58+
return function execute() {
59+
deps.console.log('Initializing...');
60+
Promise.resolve(deps)
61+
.then(mapDepsToCtx)
62+
.then(createOutputDir)
63+
.then(streamExampleToOutputDir)
64+
.then(ctx => deps.console.log(getCompletionText(ctx)))
65+
.catch(deps.console.error);
66+
};
67+
},
68+
deps: [
69+
'console',
70+
'params',
71+
'pwd',
72+
],
73+
}
74+
],
75+
});
76+
77+
function mapDepsToCtx(deps) {
78+
return new Promise((resolve, reject) => {
79+
// The <example> param has two shapes:
80+
// * <name> - example name from the official Frint GitHub repository
81+
// * <organization>/<repo>/tree/<branch>/** - full GitHub path to an arbitrary example
82+
const example = deps.params.example || DEFAULT_EXAMPLE;
83+
if (!example.match(/(^(\w|-)+$)|^\/?(\w|-)+(\/(\w|-)+){3,}\/?$/)) {
84+
reject(INVALID_EXAMPLE_ARG_TEXT);
85+
}
86+
87+
// If <directory> is specified, it is taken as the 1st value from params _ array.
88+
// Note that this array does not include the <example> flag.
89+
const isOutputCurrentDir = deps.params._.length === 0;
90+
91+
const ctx = {
92+
isOutputCurrentDir,
93+
outputDir: isOutputCurrentDir ? deps.pwd : deps.params._[0],
94+
};
95+
96+
const isCustomExample = example.indexOf('/') >= 0;
97+
if (isCustomExample) {
98+
populateCtxForCustomRepo(ctx, example);
99+
} else {
100+
populateCtxForDefaultRepo(ctx, example);
101+
}
102+
103+
resolve(ctx);
104+
});
105+
}
106+
107+
function populateCtxForCustomRepo(ctx, example) {
108+
// Split by '/' and filter out empty results.
109+
// <example> arg might start or end with a separator.
110+
const exampleParts = example.split('/').filter(str => str !== '');
111+
ctx.org = exampleParts[0];
112+
ctx.repo = exampleParts[1];
113+
ctx.branch = exampleParts[3];
114+
ctx.examplePath = exampleParts.slice(4).join('/');
115+
}
116+
117+
function populateCtxForDefaultRepo(ctx, example) {
118+
ctx.org = DEFAULT_ORG;
119+
ctx.repo = DEFAULT_REPO;
120+
ctx.branch = DEFAULT_BRANCH;
121+
ctx.examplePath = `${DEFAULT_EXAMPLES_DIR}/${example}`;
122+
}
123+
124+
function createOutputDir(ctx) {
125+
return new Promise((resolve, reject) => {
126+
mkdirp(ctx.outputDir, function mkdirpCallback(error) {
127+
if (error) {
128+
reject(error);
129+
return;
130+
}
131+
resolve(ctx);
132+
});
133+
});
134+
}
135+
136+
function streamExampleToOutputDir(ctx) {
137+
return new Promise((resolve, reject) => {
138+
request(`https://codeload.github.com/${ctx.org}/${ctx.repo}/tar.gz/${ctx.branch}`)
139+
.on('error', reject)
140+
.pipe(tar.x({
141+
filter: p => p.indexOf(`${ctx.repo}-${ctx.branch}/${ctx.examplePath}/`) === 0,
142+
strip: 3,
143+
C: ctx.outputDir,
144+
}))
145+
.on('error', reject)
146+
.on('finish', resolve(ctx));
147+
});
148+
}
149+
150+
function getCompletionText(ctx) {
151+
return COMPLETION_TEXT.replace(
152+
"{}",
153+
ctx.isOutputCurrentDir ? "" : `\n $ cd ${ctx.outputDir}`);
154+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* eslint-disable import/no-extraneous-dependencies, func-names */
2+
/* global describe, it */
3+
const expect = require('chai').expect;
4+
const App = require('frint').App;
5+
6+
const createRootApp = require('../root/index.mock');
7+
const CommandApp = require('./new');
8+
9+
describe('frint-cli › commands › new', function () {
10+
it('is a Frint App', function () {
11+
const RootApp = createRootApp();
12+
const rootApp = new RootApp();
13+
rootApp.registerApp(CommandApp);
14+
const commandApp = rootApp.getAppInstance('new');
15+
16+
expect(commandApp).to.be.an.instanceOf(App);
17+
});
18+
});

0 commit comments

Comments
 (0)