1 | "use strict";
|
2 |
|
3 | require( "colors" );
|
4 | const
|
5 | FS = require( "fs" ),
|
6 | RL = require( "readline" ),
|
7 | Tpl = require( "./template" ),
|
8 | Path = require( "path" ),
|
9 | Input = require( 'readline-sync' ),
|
10 | Fatal = require( "./fatal" ),
|
11 | Package = require( "./package" ),
|
12 | Template = require( "./template" ),
|
13 | PathUtils = require( "./pathutils" ),
|
14 | ChildProcess = require( 'child_process' );
|
15 |
|
16 |
|
17 | var OPTIONS = {};
|
18 |
|
19 | exports.start = function ( pkg ) {
|
20 | if ( !isInEmptyFolder() ) {
|
21 | console.log();
|
22 | yesno( "Are you sure you still want to continue in this non empty folder?\n" +
|
23 | "This will DELETE all the files!".bgRed.bold.white + " ", stepStart );
|
24 | } else {
|
25 | stepStart();
|
26 | };
|
27 | };
|
28 |
|
29 |
|
30 | function stepStart() {
|
31 | try {
|
32 | if ( !isGitInstalled() ) return;
|
33 | OPTIONS = {};
|
34 | menu( [
|
35 | "Create an empty fresh new project.",
|
36 | "Create from existing sources."
|
37 | ], function ( choice ) {
|
38 | if ( choice == 1 ) onMenuProjectType();
|
39 | else onExistingSources();
|
40 | } );
|
41 | } catch ( ex ) {
|
42 | fatal( "Fatal error in function `stepStart`!\n" + JSON.stringify( ex, null, ' ' ) );
|
43 | }
|
44 | };
|
45 |
|
46 |
|
47 | function onExistingSources() {
|
48 | try {
|
49 | input( "Folder where your HTML files are: ", function ( path ) {
|
50 | path = Path.resolve( path );
|
51 | if ( !FS.existsSync( path ) ) {
|
52 | fatal( "I can't find this folder:\n" + path );
|
53 | } else {
|
54 | var stat = FS.statSync( path );
|
55 | if ( !stat.isDirectory() ) {
|
56 | fatal( "Sorry but this is not a folder:\n" + path );
|
57 | } else {
|
58 | OPTIONS.sources = path;
|
59 | onMenuProjectType();
|
60 | }
|
61 | }
|
62 | } );
|
63 | } catch ( ex ) {
|
64 | fatal( "Fatal error in function `onExistingSources`!\n" + JSON.stringify( ex, null, ' ' ) );
|
65 | }
|
66 | }
|
67 |
|
68 |
|
69 | function onMenuProjectType() {
|
70 | try {
|
71 | var defaults = { name: '', desc: '', author: '' };
|
72 | var packageFilename = "./package.json";
|
73 | if ( FS.existsSync( packageFilename ) ) {
|
74 | try {
|
75 | var pkg = JSON.parse( FS.readFileSync( packageFilename ) );
|
76 | defaults.name = pkg.name;
|
77 | defaults.desc = pkg.description;
|
78 | defaults.author = pkg.author;
|
79 | } catch ( ex ) {
|
80 | console.error( "For information, your current `package.json` file is not parsable!".red.bold );
|
81 | }
|
82 | }
|
83 |
|
84 | inputs( {
|
85 | name: [ "Project's name: ", defaults.name ],
|
86 | desc: [ "Project's description: ", defaults.desc ],
|
87 | author: [ "Author (should be github username if you use github): ", defaults.author ]
|
88 | }, function ( values ) {
|
89 | copyToOptions( values );
|
90 | OPTIONS.url = "https://github.com/" + OPTIONS.author + "/" + OPTIONS.name + ".git";
|
91 | OPTIONS.bugs = "https://github.com/" + OPTIONS.author + "/" + OPTIONS.name + "/issues";
|
92 | OPTIONS.homepage = "https://" + OPTIONS.author + ".github.io/" + OPTIONS.name;
|
93 | var versionParts = Package.version.split( '.' );
|
94 | while ( versionParts.length > 2 ) {
|
95 | versionParts.pop();
|
96 | }
|
97 | OPTIONS.version = versionParts.join( '.' );
|
98 | stepSelectType( function () {
|
99 | yesno(
|
100 | "Do you want to use GitHub? ",
|
101 | stepGithub,
|
102 | function () {
|
103 | console.log();
|
104 | console.log( "Initializing git..." );
|
105 | exec( "git init" );
|
106 | exec( "git add .gitignore" );
|
107 | exec( "git add . -A" );
|
108 | exec( 'git commit -m "First commit."' );
|
109 | exec( 'mkdir submodules' );
|
110 | exec(
|
111 | 'git submodule add https://github.com/tolokoban/toloframework.git submodules/tfw'
|
112 | );
|
113 | stepEnd();
|
114 | }
|
115 | );
|
116 | } );
|
117 | } );
|
118 | } catch ( ex ) {
|
119 | fatal( "Fatal error in function `onMenuProjectType`!\n" + JSON.stringify( ex, null, ' ' ) );
|
120 | }
|
121 | }
|
122 |
|
123 |
|
124 | function stepGithub() {
|
125 | try {
|
126 | console.log( "Deleting all files in the current folder..." );
|
127 | cleanDir( "." );
|
128 | console.log( "Cloning GitHub's repository..." );
|
129 | exec( "git clone " + OPTIONS.url + " ." );
|
130 | console.log( "Preparing branch gh-pages...".cyan );
|
131 | var branches = exec( "git branch" );
|
132 | console.log( "Preparing pg-pages...".cyan );
|
133 | if ( branches.indexOf( " gh-pages" ) == -1 ) {
|
134 | exec( "git branch gh-pages" );
|
135 | exec( "git push -u origin gh-pages" );
|
136 | }
|
137 | if ( !FS.existsSync( "www" ) ) {
|
138 | FS.mkdirSync( "www" );
|
139 | } else {
|
140 | PathUtils.rmdir( "./www" );
|
141 | }
|
142 | exec( "git clone " + OPTIONS.url + " ./www" );
|
143 | exec( "cd www && git checkout gh-pages" );
|
144 | stepEnd();
|
145 | } catch ( ex ) {
|
146 | fatal( "Fatal error in function `stepGithub`!\n" + JSON.stringify( ex, null, ' ' ) );
|
147 | }
|
148 | }
|
149 |
|
150 | function copyExistingSources() {
|
151 | try {
|
152 | console.log( "Copying existing file..." );
|
153 | copyFile( OPTIONS.sources, "./src" );
|
154 | } catch ( ex ) {
|
155 | fatal( "Fatal error in function `copyExistingSources`!\n" + JSON.stringify( ex, null, ' ' ) );
|
156 | }
|
157 | }
|
158 |
|
159 | function stepEnd() {
|
160 | try {
|
161 | console.log( "package.json" );
|
162 | FS.writeFileSync( "./package.json", Template.file( "package.json", OPTIONS ).out );
|
163 | console.log( "karma.conf.js" );
|
164 | FS.writeFileSync( "./karma.conf.js", Template.file( "karma.conf.js", OPTIONS ).out );
|
165 | console.log( ".gitignore" );
|
166 | FS.writeFileSync( "./.gitignore", `
|
167 | *~
|
168 | .#*
|
169 | tmp/
|
170 | www/
|
171 | node_modules/
|
172 | spec/mod/
|
173 | ` );
|
174 | console.log( "src" );
|
175 | Template.files( "src", "./src" );
|
176 | console.log( "Downloading external modules..." );
|
177 | exec( "npm update" );
|
178 | if ( OPTIONS.sources ) copyExistingSources();
|
179 | exec( 'git add . -A' );
|
180 | exec( 'git commit -m "tfw init"' );
|
181 |
|
182 | console.log();
|
183 | console.log( "------------------------------------------------------------" );
|
184 | console.log();
|
185 | console.log( "The initialization is done." );
|
186 | console.log( "Your compiled project will be build in your `www` folder." );
|
187 | console.log();
|
188 | console.log( "To start automated tests, please type:" );
|
189 | console.log( "> " + "npm test".yellow );
|
190 | console.log();
|
191 | console.log( "To perform a full clean-up, please type:" );
|
192 | console.log( "> " + "npm run clean".yellow );
|
193 | console.log();
|
194 | console.log( "To build in DEBUG mode, please type:" );
|
195 | console.log( "> " + "npm run debug".yellow );
|
196 | console.log( "If you want the files to be watched during DEBUG mode, please type:" );
|
197 | console.log( "> " + "npm run watch".yellow );
|
198 | console.log();
|
199 | console.log( "To build in RELEASE mode, please type:" );
|
200 | console.log( "> " + "npm run release".yellow );
|
201 | console.log();
|
202 | } catch ( ex ) {
|
203 | fatal( "Fatal error in function `stepEnd`!\n" + JSON.stringify( ex, null, ' ' ) );
|
204 | }
|
205 | }
|
206 |
|
207 |
|
208 | function stepSelectType( nextStep ) {
|
209 | try {
|
210 | menu( [ "Browser's application", "Node-webkit's application" ], function ( v ) {
|
211 | if ( v == 1 ) {
|
212 | OPTIONS.type = "web";
|
213 | } else {
|
214 | OPTIONS.type = "desktop";
|
215 | }
|
216 | nextStep();
|
217 | } );
|
218 | } catch ( ex ) {
|
219 | fatal( "Fatal error in function `stepSelectType`!\n" + JSON.stringify( ex, null, ' ' ) );
|
220 | }
|
221 | }
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 | function menu( items, nextStep ) {
|
230 | const rl = RL.createInterface( {
|
231 | input: process.stdin,
|
232 | output: process.stdout
|
233 | } );
|
234 | console.log();
|
235 | items.forEach( function ( item, idx ) {
|
236 | var out = idx < 10 ? ' ' : '';
|
237 | out += ( "" + ( 1 + idx ) + ") " ).yellow.bold;
|
238 | out += item;
|
239 | console.log( out );
|
240 | } );
|
241 | console.log();
|
242 | rl.question( "Your choice: ", ( ans ) => {
|
243 | rl.close();
|
244 | var choice = parseInt( ans );
|
245 | if ( isNaN( ans ) || ans < 1 || ans > items.length ) {
|
246 | menu( items, nextStep );
|
247 | } else {
|
248 | console.log();
|
249 | nextStep( choice );
|
250 | }
|
251 | } );
|
252 | }
|
253 |
|
254 | function yesno( caption, yesStep, noStep, defaultValue ) {
|
255 | if ( typeof defaultValue === 'undefined' ) defaultValue = 'N';
|
256 | defaultValue = defaultValue.trim().toUpperCase();
|
257 |
|
258 | if ( Array.isArray( caption ) ) caption = caption[ 0 ];
|
259 | input( [ caption, defaultValue ], function ( ans ) {
|
260 | ans = ans.trim().toUpperCase();
|
261 | if ( ans == "" ) ans = defaultValue;
|
262 | if ( ans == 'Y' ) yesStep();
|
263 | else if ( ans == 'N' ) {
|
264 | if ( typeof noStep === 'function' ) {
|
265 | noStep();
|
266 | }
|
267 | } else {
|
268 | console.log( "Please answer Y or N!".red.bold );
|
269 | yesno( caption, yesStep, noStep, defaultValue );
|
270 | }
|
271 | } );
|
272 | }
|
273 |
|
274 | function input( caption, nextStep ) {
|
275 | if ( typeof caption !== 'string' && !Array.isArray( caption ) ) {
|
276 | inputs( caption, nextStep );
|
277 | } else {
|
278 | const rl = RL.createInterface( {
|
279 | input: process.stdin,
|
280 | output: process.stdout
|
281 | } );
|
282 | var text, defaultValue = '';
|
283 | if ( Array.isArray( caption ) ) {
|
284 | if ( caption.length > 1 ) {
|
285 | defaultValue = caption[ 1 ];
|
286 | }
|
287 | if ( typeof defaultValue !== 'string' ) defaultValue = '';
|
288 | text = caption[ 0 ].bold;
|
289 | if ( defaultValue != '' ) {
|
290 | text += ( "[" + defaultValue + "] " ).bold.gray;
|
291 | }
|
292 | } else {
|
293 | text = caption.bold;
|
294 | }
|
295 | rl.question( text, ( ans ) => {
|
296 | rl.close();
|
297 | if ( ans.trim() == '' ) ans = defaultValue;
|
298 | nextStep( ans );
|
299 | } );
|
300 | }
|
301 | }
|
302 |
|
303 | function inputs( captions, nextStep ) {
|
304 | var items = [];
|
305 | var k, v;
|
306 | for ( k in captions ) {
|
307 | v = captions[ k ];
|
308 | items.push( [ k, v ] );
|
309 | }
|
310 |
|
311 | var values = {};
|
312 |
|
313 | var callback = function () {
|
314 | if ( items.length == 0 ) {
|
315 | nextStep( values );
|
316 | } else {
|
317 | var item = items.shift();
|
318 | input( item[ 1 ], function ( value ) {
|
319 | values[ item[ 0 ] ] = value;
|
320 | callback();
|
321 | } );
|
322 | }
|
323 | };
|
324 |
|
325 | callback();
|
326 | }
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | function isInEmptyFolder() {
|
332 | var files = FS.readdirSync( '.' );
|
333 | try {
|
334 | if ( files.length == 0 ) return true;
|
335 | console.log( Fatal.format(
|
336 | "You should be in an empty folder to create a new project!\n" +
|
337 | "If you continue, all the files in this folder will be DELETED!"
|
338 | ) );
|
339 | console.log( "\nWe suggest that you create an fresh new directory, like this:" );
|
340 | console.log( "\n> " + "mkdir my-project-folder".yellow.italic );
|
341 | console.log( "> " + "cd my-project-folder".yellow.italic );
|
342 | console.log( "> " + "tfw init".yellow.italic );
|
343 | return false;
|
344 | } catch ( ex ) {
|
345 | fatal( "Fatal error in function `files`!\n" + JSON.stringify( ex, null, ' ' ) );
|
346 | }
|
347 | }
|
348 |
|
349 |
|
350 |
|
351 |
|
352 | function isGitInstalled() {
|
353 | var result = exec( "git --version", true );
|
354 | try {
|
355 | if ( !result || result.indexOf( "git" ) < 0 || result.indexOf( "version" ) < 0 ) {
|
356 | console.log( Fatal.format(
|
357 | "`git` is required by the ToloFrameWork!\n" +
|
358 | "Please install it:" ) );
|
359 | console.log( "\n> " + "sudo apt-get install git".yellow.italic );
|
360 | return false;
|
361 | }
|
362 | return true;
|
363 | } catch ( ex ) {
|
364 | fatal( "Fatal error in function `result`!\n" + JSON.stringify( ex, null, ' ' ) );
|
365 | }
|
366 | }
|
367 |
|
368 | function copyToOptions( values ) {
|
369 | var k, v;
|
370 | try {
|
371 | for ( k in values ) {
|
372 | v = values[ k ];
|
373 | OPTIONS[ k ] = v;
|
374 | }
|
375 | return OPTIONS;
|
376 | } catch ( ex ) {
|
377 | fatal( "Fatal error in function `k`!\n" + JSON.stringify( ex, null, ' ' ) );
|
378 | }
|
379 | }
|
380 |
|
381 | function exec( cmd, silent ) {
|
382 | try {
|
383 | if ( !silent ) {
|
384 | console.log( "> " + cmd.yellow );
|
385 | }
|
386 | return ChildProcess.execSync( cmd ).toString();
|
387 | } catch ( ex ) {
|
388 | console.log( ( "" + ex ).red.bold );
|
389 | }
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | function cleanDir( path ) {
|
397 | var files = FS.readdirSync( path );
|
398 | try {
|
399 | files.forEach( function ( file ) {
|
400 | var fullpath = Path.join( path, file );
|
401 | if ( !FS.existsSync( fullpath ) ) return;
|
402 | var stat = FS.statSync( fullpath );
|
403 | try {
|
404 | if ( stat.isDirectory() ) PathUtils.rmdir( fullpath );
|
405 | else FS.unlinkSync( fullpath );
|
406 | } catch ( ex ) {
|
407 | console.error( "Unable to delete `" + fullpath + "`!" );
|
408 | console.error( ex );
|
409 | }
|
410 | } );
|
411 | } catch ( ex ) {
|
412 | fatal( "Fatal error in function `files`!\n" + JSON.stringify( ex, null, ' ' ) );
|
413 | }
|
414 | }
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 | function fatal( msg ) {
|
421 | console.log( Fatal.format( msg ) );
|
422 | }
|
423 |
|
424 |
|
425 |
|
426 | var BUFFER = new Buffer( 64 * 1024 );
|
427 |
|
428 | function copyFile( src, dst ) {
|
429 | try {
|
430 | if ( !FS.existsSync( src ) ) {
|
431 | fatal( "Unable to copy missing file: " + src + "\ninto: " + dst );
|
432 | }
|
433 | var stat = FS.statSync( src );
|
434 | if ( stat.isDirectory() ) {
|
435 |
|
436 | if ( FS.existsSync( dst ) ) {
|
437 |
|
438 | stat = FS.statSync( dst );
|
439 | if ( !stat.isDirectory() ) {
|
440 | fatal( "Destination is not a directory: \"" + dst +
|
441 | "\"!\nSource is \"" + src + "\"." );
|
442 | }
|
443 | } else {
|
444 |
|
445 | PathUtils.mkdir( dst );
|
446 | }
|
447 | var files = FS.readdirSync( src );
|
448 | files.forEach(
|
449 | function ( filename ) {
|
450 | copyFile(
|
451 | Path.join( src, filename ),
|
452 | Path.join( dst, filename )
|
453 | );
|
454 | }
|
455 | );
|
456 | return;
|
457 | }
|
458 |
|
459 | var bytesRead, pos, rfd, wfd;
|
460 | PathUtils.mkdir( Path.dirname( dst ) );
|
461 | try {
|
462 | rfd = FS.openSync( src, "r" );
|
463 | } catch ( ex ) {
|
464 | fatal( "Unable to open file \"" + src + "\" for reading!\n" + JSON.stringify( ex, null, ' ' ) );
|
465 | }
|
466 | try {
|
467 | wfd = FS.openSync( dst, "w" );
|
468 | } catch ( ex ) {
|
469 | fatal( "Unable to open file \"" + dst + "\" for writing!\n" + JSON.stringify( ex, null, ' ' ) );
|
470 | }
|
471 | bytesRead = 1;
|
472 | pos = 0;
|
473 | while ( bytesRead > 0 ) {
|
474 | try {
|
475 | bytesRead = FS.readSync( rfd, BUFFER, 0, 64 * 1024, pos );
|
476 | } catch ( ex ) {
|
477 | fatal( "Unable to read file \"" + src + "\"!\n" + JSON.stringify( ex, null, ' ' ) );
|
478 | }
|
479 | FS.writeSync( wfd, BUFFER, 0, bytesRead );
|
480 | pos += bytesRead;
|
481 | }
|
482 | FS.closeSync( rfd );
|
483 | FS.closeSync( wfd );
|
484 | } catch ( ex ) {
|
485 | fatal( "Fatal error in function `copyFile`!\n" + JSON.stringify( ex, null, ' ' ) );
|
486 | }
|
487 | }; |
\ | No newline at end of file |