UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

488 lines (449 loc) 15.9 kB
"use strict"; require( "colors" ); const FS = require( "fs" ), RL = require( "readline" ), Tpl = require( "./template" ), Path = require( "path" ), Input = require( 'readline-sync' ), Fatal = require( "./fatal" ), Package = require( "./package" ), Template = require( "./template" ), PathUtils = require( "./pathutils" ), ChildProcess = require( 'child_process' ); var OPTIONS = {}; exports.start = function ( pkg ) { if ( !isInEmptyFolder() ) { console.log(); yesno( "Are you sure you still want to continue in this non empty folder?\n" + "This will DELETE all the files!".bgRed.white + " ", stepStart ); } else { stepStart(); }; }; function stepStart() { try { if ( !isGitInstalled() ) return; OPTIONS = {}; menu( [ "Create an empty fresh new project.", "Create from existing sources." ], function ( choice ) { if ( choice == 1 ) onMenuProjectType(); else onExistingSources(); } ); } catch ( ex ) { fatal( "Fatal error in function `stepStart`!\n" + JSON.stringify( ex, null, ' ' ) ); } }; function onExistingSources() { try { input( "Folder where your HTML files are: ", function ( path ) { path = Path.resolve( path ); if ( !FS.existsSync( path ) ) { fatal( "I can't find this folder:\n" + path ); } else { var stat = FS.statSync( path ); if ( !stat.isDirectory() ) { fatal( "Sorry but this is not a folder:\n" + path ); } else { OPTIONS.sources = path; onMenuProjectType(); } } } ); } catch ( ex ) { fatal( "Fatal error in function `onExistingSources`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function onMenuProjectType() { try { var defaults = { name: '', desc: '', author: '' }; var packageFilename = "./package.json"; if ( FS.existsSync( packageFilename ) ) { try { var pkg = JSON.parse( FS.readFileSync( packageFilename ) ); defaults.name = pkg.name; defaults.desc = pkg.description; defaults.author = pkg.author; } catch ( ex ) { console.error( "For information, your current `package.json` file is not parsable!".red ); } } inputs( { name: [ "Project's name: ", defaults.name ], desc: [ "Project's description: ", defaults.desc ], author: [ "Author (should be github username if you use github): ", defaults.author ] }, function ( values ) { copyToOptions( values ); OPTIONS.url = "https://github.com/" + OPTIONS.author + "/" + OPTIONS.name + ".git"; OPTIONS.bugs = "https://github.com/" + OPTIONS.author + "/" + OPTIONS.name + "/issues"; OPTIONS.homepage = "https://" + OPTIONS.author + ".github.io/" + OPTIONS.name; var versionParts = Package.version.split( '.' ); while ( versionParts.length > 2 ) { versionParts.pop(); } OPTIONS.version = versionParts.join( '.' ); stepSelectType( function () { yesno( "Do you want to use GitHub? ", stepGithub, function () { console.log(); console.log( "Initializing git..." ); exec( "git init" ); exec( "git add .gitignore" ); exec( "git add . -A" ); exec( 'git commit -m "First commit."' ); exec( 'mkdir submodules' ); exec( 'git submodule add https://github.com/tolokoban/toloframework.git submodules/tfw' ); stepEnd(); } ); } ); } ); } catch ( ex ) { fatal( "Fatal error in function `onMenuProjectType`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function stepGithub() { try { console.log( "Deleting all files in the current folder..." ); cleanDir( "." ); console.log( "Cloning GitHub's repository..." ); exec( "git clone " + OPTIONS.url + " ." ); console.log( "Preparing branch gh-pages...".cyan ); var branches = exec( "git branch" ); console.log( "Preparing pg-pages...".cyan ); if ( branches.indexOf( " gh-pages" ) == -1 ) { exec( "git branch gh-pages" ); exec( "git push -u origin gh-pages" ); } if ( !FS.existsSync( "www" ) ) { FS.mkdirSync( "www" ); } else { PathUtils.rmdir( "./www" ); } exec( "git clone " + OPTIONS.url + " ./www" ); exec( "cd www && git checkout gh-pages" ); stepEnd(); } catch ( ex ) { fatal( "Fatal error in function `stepGithub`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function copyExistingSources() { try { console.log( "Copying existing file..." ); copyFile( OPTIONS.sources, "./src" ); } catch ( ex ) { fatal( "Fatal error in function `copyExistingSources`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function stepEnd() { try { console.log( "package.json" ); FS.writeFileSync( "./package.json", Template.file( "package.json", OPTIONS ).out ); console.log( "karma.conf.js" ); FS.writeFileSync( "./karma.conf.js", Template.file( "karma.conf.js", OPTIONS ).out ); console.log( ".gitignore" ); FS.writeFileSync( "./.gitignore", ` *~ .#* tmp/ www/ node_modules/ spec/mod/ ` ); console.log( "src" ); Template.files( "src", "./src" ); console.log( "Downloading external modules..." ); exec( "npm update" ); if ( OPTIONS.sources ) copyExistingSources(); exec( 'git add . -A' ); exec( 'git commit -m "tfw init"' ); console.log(); console.log( "------------------------------------------------------------" ); console.log(); console.log( "The initialization is done." ); console.log( "Your compiled project will be build in your `www` folder." ); console.log(); console.log( "To start automated tests, please type:" ); console.log( "> " + "npm test".yellow ); console.log(); console.log( "To perform a full clean-up, please type:" ); console.log( "> " + "npm run clean".yellow ); console.log(); console.log( "To build in DEBUG mode, please type:" ); console.log( "> " + "npm run debug".yellow ); console.log( "If you want the files to be watched during DEBUG mode, please type:" ); console.log( "> " + "npm run watch".yellow ); console.log(); console.log( "To build in RELEASE mode, please type:" ); console.log( "> " + "npm run release".yellow ); console.log(); } catch ( ex ) { fatal( "Fatal error in function `stepEnd`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function stepSelectType( nextStep ) { try { menu( [ "Browser's application", "Node-webkit's application" ], function ( v ) { if ( v == 1 ) { OPTIONS.type = "web"; } else { OPTIONS.type = "desktop"; } nextStep(); } ); } catch ( ex ) { fatal( "Fatal error in function `stepSelectType`!\n" + JSON.stringify( ex, null, ' ' ) ); } } /** * Display a list of items and ask the user to select one. * Each item is numbered and the menu is displayed again if the user * enter a non-existing value. * The function `nextStep` is called with the choice as a number. */ function menu( items, nextStep ) { const rl = RL.createInterface( { input: process.stdin, output: process.stdout } ); console.log(); items.forEach( function ( item, idx ) { var out = idx < 10 ? ' ' : ''; out += ( "" + ( 1 + idx ) + ") " ).yellow; out += item; console.log( out ); } ); console.log(); rl.question( "Your choice: ", ( ans ) => { rl.close(); var choice = parseInt( ans ); if ( isNaN( ans ) || ans < 1 || ans > items.length ) { menu( items, nextStep ); } else { console.log(); nextStep( choice ); } } ); } function yesno( caption, yesStep, noStep, defaultValue ) { if ( typeof defaultValue === 'undefined' ) defaultValue = 'N'; defaultValue = defaultValue.trim().toUpperCase(); if ( Array.isArray( caption ) ) caption = caption[ 0 ]; input( [ caption, defaultValue ], function ( ans ) { ans = ans.trim().toUpperCase(); if ( ans == "" ) ans = defaultValue; if ( ans == 'Y' ) yesStep(); else if ( ans == 'N' ) { if ( typeof noStep === 'function' ) { noStep(); } } else { console.log( "Please answer Y or N!".red ); yesno( caption, yesStep, noStep, defaultValue ); } } ); } function input( caption, nextStep ) { if ( typeof caption !== 'string' && !Array.isArray( caption ) ) { inputs( caption, nextStep ); } else { const rl = RL.createInterface( { input: process.stdin, output: process.stdout } ); var text, defaultValue = ''; if ( Array.isArray( caption ) ) { if ( caption.length > 1 ) { defaultValue = caption[ 1 ]; } if ( typeof defaultValue !== 'string' ) defaultValue = ''; text = caption[ 0 ]; if ( defaultValue != '' ) { text += ( "[" + defaultValue + "] " ).gray; } } else { text = caption; } rl.question( text, ( ans ) => { rl.close(); if ( ans.trim() == '' ) ans = defaultValue; nextStep( ans ); } ); } } function inputs( captions, nextStep ) { var items = []; var k, v; for ( k in captions ) { v = captions[ k ]; items.push( [ k, v ] ); } // The resuls of the inputs are stored here. var values = {}; var callback = function () { if ( items.length == 0 ) { nextStep( values ); } else { var item = items.shift(); input( item[ 1 ], function ( value ) { values[ item[ 0 ] ] = value; callback(); } ); } }; callback(); } /** * Check if we are in an empty folder. */ function isInEmptyFolder() { var files = FS.readdirSync( '.' ); try { if ( files.length == 0 ) return true; console.log( Fatal.format( "You should be in an empty folder to create a new project!\n" + "If you continue, all the files in this folder will be DELETED!" ) ); console.log( "\nWe suggest that you create an fresh new directory, like this:" ); console.log( "\n> " + "mkdir my-project-folder".yellow.italic ); console.log( "> " + "cd my-project-folder".yellow.italic ); console.log( "> " + "tfw init".yellow.italic ); return false; } catch ( ex ) { fatal( "Fatal error in function `files`!\n" + JSON.stringify( ex, null, ' ' ) ); } } /** * Check if `git` is installed on this system. */ function isGitInstalled() { var result = exec( "git --version", true ); try { if ( !result || result.indexOf( "git" ) < 0 || result.indexOf( "version" ) < 0 ) { console.log( Fatal.format( "`git` is required by the ToloFrameWork!\n" + "Please install it:" ) ); console.log( "\n> " + "sudo apt-get install git".yellow.italic ); return false; } return true; } catch ( ex ) { fatal( "Fatal error in function `result`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function copyToOptions( values ) { var k, v; try { for ( k in values ) { v = values[ k ]; OPTIONS[ k ] = v; } return OPTIONS; } catch ( ex ) { fatal( "Fatal error in function `k`!\n" + JSON.stringify( ex, null, ' ' ) ); } } function exec( cmd, silent ) { try { if ( !silent ) { console.log( "> " + cmd.yellow ); } return ChildProcess.execSync( cmd ).toString(); } catch ( ex ) { console.log( ( "" + ex ).red ); } } /** * Remove all the files and folder in `path`, but not `path` itself. */ function cleanDir( path ) { var files = FS.readdirSync( path ); try { files.forEach( function ( file ) { var fullpath = Path.join( path, file ); if ( !FS.existsSync( fullpath ) ) return; var stat = FS.statSync( fullpath ); try { if ( stat.isDirectory() ) PathUtils.rmdir( fullpath ); else FS.unlinkSync( fullpath ); } catch ( ex ) { console.error( "Unable to delete `" + fullpath + "`!" ); console.error( ex ); } } ); } catch ( ex ) { fatal( "Fatal error in function `files`!\n" + JSON.stringify( ex, null, ' ' ) ); } } /** * Show a nice error message. */ function fatal( msg ) { console.log( Fatal.format( msg ) ); } var BUFFER = new Buffer( 64 * 1024 ); function copyFile( src, dst ) { try { if ( !FS.existsSync( src ) ) { fatal( "Unable to copy missing file: " + src + "\ninto: " + dst ); } var stat = FS.statSync( src ); if ( stat.isDirectory() ) { // We need to copy a whole directory. if ( FS.existsSync( dst ) ) { // Check if the destination is a directory. stat = FS.statSync( dst ); if ( !stat.isDirectory() ) { fatal( "Destination is not a directory: \"" + dst + "\"!\nSource is \"" + src + "\"." ); } } else { // Make destination directory. PathUtils.mkdir( dst ); } var files = FS.readdirSync( src ); files.forEach( function ( filename ) { copyFile( Path.join( src, filename ), Path.join( dst, filename ) ); } ); return; } var bytesRead, pos, rfd, wfd; PathUtils.mkdir( Path.dirname( dst ) ); try { rfd = FS.openSync( src, "r" ); } catch ( ex ) { fatal( "Unable to open file \"" + src + "\" for reading!\n" + JSON.stringify( ex, null, ' ' ) ); } try { wfd = FS.openSync( dst, "w" ); } catch ( ex ) { fatal( "Unable to open file \"" + dst + "\" for writing!\n" + JSON.stringify( ex, null, ' ' ) ); } bytesRead = 1; pos = 0; while ( bytesRead > 0 ) { try { bytesRead = FS.readSync( rfd, BUFFER, 0, 64 * 1024, pos ); } catch ( ex ) { fatal( "Unable to read file \"" + src + "\"!\n" + JSON.stringify( ex, null, ' ' ) ); } FS.writeSync( wfd, BUFFER, 0, bytesRead ); pos += bytesRead; } FS.closeSync( rfd ); FS.closeSync( wfd ); } catch ( ex ) { fatal( "Fatal error in function `copyFile`!\n" + JSON.stringify( ex, null, ' ' ) ); } };