UNPKG

toloframework

Version:

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

482 lines (444 loc) 14.9 kB
require("colors"); var FS = require("fs"); var RL = require("readline"); var Tpl = require("./template"); var Path = require("path"); var Input = require('readline-sync'); var Fatal = require("./fatal"); var Package = require("./package"); var Template = require("./template"); var PathUtils = require("./pathutils"); var ChildProcess = require('child_process'); var OPTIONS = {}; exports.start = function( package ) { 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.bold.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.log("For information, your current `package.json` file is parsable!".red.bold); } } 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."'); 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 + " ."); if( !FS.existsSync( "www" ) ) { FS.mkdirSync( "www" ); } else { PathUtils.rmdir("./www"); } exec( "git clone " + OPTIONS.url + " ./www" ); console.log( "Preparing branch gh-pages...".cyan ); var branches = exec( "git branch" ); if( branches.indexOf( " gh-pages" ) == -1 ) { exec( "git branch gh-pages" ); } 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 { FS.writeFileSync("./package.json", Template.file( "package.json", OPTIONS ).out); FS.writeFileSync("./karma.conf.js", Template.file( "karma.conf.js", OPTIONS ).out); FS.writeFileSync("./.gitignore", Template.file( ".gitignore", OPTIONS ).out); 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 build in DEBUG mode, please type:"); console.log("> " + "npm run debug".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 = "firefoxos"; } else { OPTIONS.type = "nodewekit"; } 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.bold; 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.bold); 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].bold; if (defaultValue != ''){ text += ("[" + defaultValue + "] ").bold.gray; } } else { text = caption.bold; } 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.bold ); } } /** * 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, ' ')); } };