1 | const fs = require('fs');
|
2 | const path = require('path');
|
3 | const spawn = require('child_process').spawn;
|
4 | const entries = require('object.entries');
|
5 | const elegantSpinner = require('elegant-spinner');
|
6 | const logUpdate = require('log-update');
|
7 | const colors = require('colors');
|
8 | const pkg = require('../package.json');
|
9 |
|
10 | const spacer = ''.hidden;
|
11 | const listitem = '';
|
12 | const listitem1 = '▪︎'.yellow + '▪︎'.yellow.dim;
|
13 | const listitem2 = '';
|
14 | const spinner = elegantSpinner();
|
15 | const workingDir = process.cwd();
|
16 |
|
17 | let start = 0;
|
18 | let started = 0;
|
19 | let finished = 0;
|
20 | let ivalMain = null;
|
21 | let ivalSpinner = null;
|
22 | let cmdArgs = {};
|
23 | let currentPath = '';
|
24 | let projectDirName = '';
|
25 |
|
26 | const next = () => {
|
27 | finished = started;
|
28 | };
|
29 |
|
30 | const trimLeft = str => str.replace(/^\s+/, '');
|
31 | const getTimestamp = () => Date.now();
|
32 | const getTimer = startx => Date.now() - startx;
|
33 |
|
34 | const getPackage = () => require(path.join(workingDir, '/', projectDirName, '/package.json'));
|
35 |
|
36 | const startSpinner = (msg) => {
|
37 | clearInterval(ivalSpinner);
|
38 |
|
39 | let text = '';
|
40 | msg = msg || 'installing packages';
|
41 | if (!cmdArgs.quiet) {
|
42 | text = colors.bold(msg + ' ');
|
43 | } else {
|
44 | text = colors.white('please wait during installation ');
|
45 | }
|
46 |
|
47 | ivalSpinner = setInterval(() => {
|
48 | let counterText = '';
|
49 | if (!cmdArgs.quiet) {
|
50 | counterText = colors.dim(' (' + getTimer(start) + ' ms)');
|
51 | }
|
52 | logUpdate(spacer + text + colors.yellow(spinner()) + counterText);
|
53 | }, 50);
|
54 | };
|
55 |
|
56 | const log = (msg) => {
|
57 | if (!cmdArgs.quiet) {
|
58 | console.log(spacer + msg);
|
59 | }
|
60 | next();
|
61 | };
|
62 |
|
63 | const logVersionWarning = (str) => {
|
64 | const latestPackageVersion = str.trim();
|
65 | if (latestPackageVersion && latestPackageVersion !== pkg.version) {
|
66 | console.log(spacer + colors.magenta('v' + latestPackageVersion + ' is available!'));
|
67 | }
|
68 | next();
|
69 | };
|
70 |
|
71 | const chdir = (dir) => {
|
72 | try {
|
73 | process.chdir(dir);
|
74 | next();
|
75 | } catch (err) {
|
76 | console.log('CHDIR:ERROR: ' + err);
|
77 | }
|
78 | };
|
79 |
|
80 | const createFolder = (parts, i) => {
|
81 | if (typeof parts[i] !== 'undefined') {
|
82 | currentPath += '/' + parts[i];
|
83 | next();
|
84 | }
|
85 | const dir = path.join.apply(null, parts.slice(0, i));
|
86 | fs.existsSync(dir) || fs.mkdirSync(dir);
|
87 | };
|
88 |
|
89 | const createFolders = (dirPath) => {
|
90 | const incl = str => dirPath.includes(str);
|
91 |
|
92 | if (
|
93 | (incl('vscode') && !cmdArgs.flow) ||
|
94 | (incl('flow-typed') && !cmdArgs.flow) ||
|
95 | (incl('mocks') && !cmdArgs.test) ||
|
96 | (incl('mocks') && cmdArgs.styled)
|
97 | ) {
|
98 | next();
|
99 | return;
|
100 | }
|
101 | dirPath = './' + projectDirName + '/' + dirPath;
|
102 | const parts = dirPath.split(path.sep);
|
103 | currentPath = '.';
|
104 | for (let i = 1; i <= parts.length; i += 1) {
|
105 | if (i !== parts.length) {
|
106 | createFolder(parts, i);
|
107 | } else {
|
108 | createFolder(parts, i, next);
|
109 | }
|
110 | }
|
111 | };
|
112 |
|
113 | const writeFile = (filePath, content) => {
|
114 | const incl = str => filePath.includes(str);
|
115 |
|
116 | if (
|
117 | (incl('eslint') && !cmdArgs.lint) ||
|
118 | (incl('jest') && !cmdArgs.test) ||
|
119 | (incl('spec') && !cmdArgs.test) ||
|
120 | (incl('mocks') && !cmdArgs.test) ||
|
121 | (incl('mocks') && cmdArgs.styled) ||
|
122 | (incl('state') && !cmdArgs.redux && !cmdArgs.mobx) ||
|
123 | (incl('store') && !cmdArgs.redux) ||
|
124 | (incl('vscode') && !cmdArgs.flow) ||
|
125 | (incl('app/types') && !cmdArgs.flow && !cmdArgs.mobx) ||
|
126 | (incl('app/style.') && cmdArgs.styled) ||
|
127 | (incl('app/styled.') && !cmdArgs.styled) ||
|
128 | (incl('flowConfig') && !cmdArgs.flow) ||
|
129 | (incl('flow-typed') && !cmdArgs.flow) ||
|
130 | (incl('flow-typed') && incl('redux') && !cmdArgs.redux) ||
|
131 | (incl('flow-typed/mobx') && !cmdArgs.mobx) ||
|
132 | (incl('flow-typed/react-router-dom') && !cmdArgs.router) ||
|
133 | (incl('flow-typed/styled-components') && !cmdArgs.styled)
|
134 | ) {
|
135 | next();
|
136 | return;
|
137 | }
|
138 |
|
139 | if (content) {
|
140 | content = trimLeft(content);
|
141 | }
|
142 |
|
143 | fs.writeFile(path.join(workingDir, '/', projectDirName, '/', filePath), content, (err) => {
|
144 | if (err) return log(err);
|
145 | let fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
|
146 | fileName = colors.yellow(fileName);
|
147 | filePath = filePath.substring(0, filePath.lastIndexOf('/'));
|
148 | filePath = filePath.length > 0 ? colors.white(filePath + '/') : '';
|
149 | const msg = listitem + filePath + fileName;
|
150 | log(msg);
|
151 | return next();
|
152 | });
|
153 | };
|
154 |
|
155 | const execCommand = (cmdString, options = {}) => {
|
156 | if (options.dependencies && !cmdArgs.install) {
|
157 | setTimeout(next, 0);
|
158 | return;
|
159 | }
|
160 | !options.version && !options.callback && startSpinner();
|
161 |
|
162 | const cmd = cmdString.split(' ').slice(0, 1)[0];
|
163 | const args = cmdString.split(' ').slice(1);
|
164 | const prc = spawn(cmd, args);
|
165 | let result = '';
|
166 |
|
167 | prc.stdout.setEncoding('utf8');
|
168 |
|
169 | prc.stdout.on('data', (data) => {
|
170 | const str = data.toString();
|
171 | if (options.version) {
|
172 | logVersionWarning(str);
|
173 | } else if (options.callback) {
|
174 | result = str.trim();
|
175 | }
|
176 | });
|
177 |
|
178 | prc.stdout.on('end', () => {
|
179 | if (options.callback) {
|
180 | options.callback(result);
|
181 | } else {
|
182 | setTimeout(next, 0);
|
183 | }
|
184 | });
|
185 | };
|
186 |
|
187 | const sortObject = (obj) => {
|
188 | const tempArray = [];
|
189 | const tempObject = {};
|
190 | Object.keys(obj).forEach(key => tempArray.push(key));
|
191 | tempArray.sort().forEach((item) => {
|
192 | tempObject[item] = obj[item];
|
193 | });
|
194 | return tempObject;
|
195 | };
|
196 |
|
197 | const getVersions = (dependencies, devDependencies) => {
|
198 | if (cmdArgs.install) {
|
199 | setTimeout(next, 0);
|
200 | return;
|
201 | }
|
202 | dependencies = dependencies.trim().split(' ');
|
203 | devDependencies = devDependencies.trim().split(' ');
|
204 |
|
205 | let dependenciesCounter = dependencies.length + devDependencies.length;
|
206 | const dependenciesObject = {};
|
207 | const devDependenciesObject = {};
|
208 |
|
209 | const get = (deps, obj) => {
|
210 | const depsObject = {};
|
211 | deps.forEach((dep) => {
|
212 | execCommand('npm view ' + dep + ' dist-tags.latest', {
|
213 | callback: (version) => {
|
214 | obj[dep] = '^' + version;
|
215 | dependenciesCounter -= 1;
|
216 | }
|
217 | });
|
218 |
|
219 | if (dep === 'eslint-config-airbnb') {
|
220 | dependenciesCounter += 1;
|
221 | execCommand('npm info eslint-config-airbnb@latest peerDependencies', {
|
222 | callback: (res) => {
|
223 | res = res.replace(/'/g, '"');
|
224 | res = res.replace(/eslint:/g, '"eslint":');
|
225 | const airbnbVersions = JSON.parse(res);
|
226 | Object.keys(airbnbVersions).forEach((key) => {
|
227 | obj[key] = airbnbVersions[key];
|
228 | });
|
229 | dependenciesCounter -= 1;
|
230 | }
|
231 | });
|
232 | }
|
233 | });
|
234 | };
|
235 |
|
236 | get(dependencies, dependenciesObject);
|
237 | get(devDependencies, devDependenciesObject);
|
238 |
|
239 | const ival = setInterval(() => {
|
240 | startSpinner('get latest version numbers');
|
241 | if (dependenciesCounter === 0) {
|
242 | clearInterval(ivalSpinner);
|
243 | clearInterval(ival);
|
244 | setTimeout(() => {
|
245 | const pkgApp = getPackage();
|
246 | pkgApp.dependencies = sortObject(dependenciesObject);
|
247 | pkgApp.devDependencies = sortObject(devDependenciesObject);
|
248 | writeFile('package.json', JSON.stringify(pkgApp, null, 2));
|
249 | }, 0);
|
250 | }
|
251 | }, 50);
|
252 | };
|
253 |
|
254 | const sequence = (actions) => {
|
255 | started -= 1;
|
256 | ivalMain = setInterval(() => {
|
257 | if (actions.length !== finished && (started === finished || started === -1)) {
|
258 | clearInterval(ivalSpinner);
|
259 | started += 1;
|
260 | if (actions.length > started) {
|
261 | const args = actions[started].slice(1);
|
262 | if (args.length === 1) {
|
263 | actions[started][0].call(this, args[0]);
|
264 | } else {
|
265 | actions[started][0].apply(this, args);
|
266 | }
|
267 | } else {
|
268 | startSpinner();
|
269 | }
|
270 | }
|
271 | }, 100);
|
272 | };
|
273 |
|
274 | const displayDependencies = () => {
|
275 | const pkgApp = getPackage();
|
276 | const dependencies = entries(Object.assign(pkgApp.dependencies, pkgApp.devDependencies));
|
277 | dependencies.sort();
|
278 | dependencies.forEach((dependency) => {
|
279 | log(
|
280 | listitem +
|
281 | colors.white(dependency[0]) + ': '.white +
|
282 | colors.yellow(dependency[1])
|
283 | );
|
284 | });
|
285 | };
|
286 |
|
287 | const displayAvailableScripts = () => {
|
288 | const pkgApp = getPackage();
|
289 | const port = pkgApp.scripts.start.split('--port ')[1];
|
290 | const scripts = entries(Object.assign({}, pkgApp.scripts));
|
291 | scripts.forEach((script) => {
|
292 | log(colors.white('yarn ' + script[0]));
|
293 | script[0] === 'start' && log(colors.white('open http://localhost:' + port + '/'));
|
294 | });
|
295 | };
|
296 |
|
297 | const countAllNodeModules = () => {
|
298 | let counter = 0;
|
299 | if (cmdArgs.install) {
|
300 | const dir = path.join(workingDir, '/', projectDirName, '/node_modules/');
|
301 | const files = fs.readdirSync(dir);
|
302 | files.forEach((file) => {
|
303 | if (fs.statSync(dir + file).isDirectory()) {
|
304 | if (file.charAt(0) !== '.') {
|
305 | counter += 1;
|
306 | }
|
307 | }
|
308 | });
|
309 | }
|
310 | return counter;
|
311 | };
|
312 |
|
313 | const exit = () => {
|
314 | const pkgApp = getPackage();
|
315 | const port = pkgApp.scripts.start.split('--port ')[1];
|
316 | const secs = getTimer(start) / 1000;
|
317 | const finishedMsg = listitem2 + colors.green('Done in ' + secs.toFixed(2) + 's.');
|
318 | const numModules = countAllNodeModules();
|
319 | let listMsg;
|
320 |
|
321 | if (!cmdArgs.quiet) {
|
322 | if (cmdArgs.install) {
|
323 | listMsg = 'installed packages';
|
324 | } else {
|
325 | listMsg = 'latest versions';
|
326 | }
|
327 | logUpdate(spacer + listMsg.bold);
|
328 | displayDependencies();
|
329 | log('');
|
330 |
|
331 | if (numModules > 0) {
|
332 | log(colors.dim(spacer + '(' + numModules + ' node modules)'));
|
333 | log('');
|
334 | }
|
335 |
|
336 | log(colors.bold(finishedMsg));
|
337 | log('');
|
338 | } else {
|
339 | logUpdate(spacer + finishedMsg + ' \n');
|
340 | }
|
341 |
|
342 | log('usage'.bold);
|
343 | log(colors.white('cd ' + projectDirName));
|
344 | if (!cmdArgs.install) {
|
345 | log(colors.white('yarn install'));
|
346 | }
|
347 | displayAvailableScripts();
|
348 | log('');
|
349 |
|
350 | clearInterval(ivalMain);
|
351 | clearInterval(ivalSpinner);
|
352 | };
|
353 |
|
354 | const sleep = (delay) => {
|
355 | setTimeout(() => {
|
356 | next();
|
357 | }, delay);
|
358 | };
|
359 |
|
360 | const starting = (headertext) => {
|
361 | start = getTimestamp();
|
362 | log('');
|
363 | log(colors.bold(headertext));
|
364 | next();
|
365 | };
|
366 |
|
367 | const init = (args, dir) => {
|
368 | cmdArgs = args;
|
369 | projectDirName = dir;
|
370 | next();
|
371 | };
|
372 |
|
373 | module.exports = {
|
374 | chdir,
|
375 | createFolders,
|
376 | exec: execCommand,
|
377 | exit,
|
378 | getTimestamp,
|
379 | getTimer,
|
380 | init,
|
381 | log,
|
382 | next,
|
383 | sequence,
|
384 | sleep,
|
385 | starting,
|
386 | versions: getVersions,
|
387 | writeFile
|
388 | };
|