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
JavaScript
;
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, ' ' ) );
}
};